CSE 466 Lab 5: Introduction to the iMote2
Introduction
Now that you've worked with a simple 8-bit microcontroller, we'll be
exploring more powerful embedded devices. For the rest of the quarter,
we'll be using the iMote2, which has a 32-bit, 415 MHz ARMv5
microcontroller (The PXA271), with 32 MB of flash and 32 MB of RAM.
This microcontroller was designed for use in cell phones and PDAs. This
lab will introduce you to the iMote2 platform, and using Linux as an
embedded operating system.
WARNING:
NEVER
attach or detach the iMote2 from the debug board or the sensor board
when power is attached. ALWAYS
make sure you've unplugged ALL of the USB cables when connecting or
disconnecting the sensor board or debug board.
Objectives
In this lab, you will learn:
- how to write user-level applications and load them onto the
iMote2
- how to write, load, debug, and use Linux kernel modules
(drivers)
- how to use the PXA271's hardware timers
- how to control sensors and peripherals on the Basic Sensor
Board
- how applications communicate with hardware devices in Linux
- the basics of the Linux character device driver module
Hints
- Practice
incremental design. Take this lab step by step and test
along the way to make sure you understand your code and verify that it
is working as you expected. Coding the entire project then trying to
debug a problem can be challenging. Make sure to test pieces
of the assignment one at a time and convince yourself that they are
working before moving on to another piece.
- Remember that even though you're working with an operating
system now, the same basic concepts still apply to interrupt service
routines. Try to minimize the amount of time that it takes an
ISR to complete.
- You can't use printf in your kernel
modules. Instead, you can use
printk, which will write messages to a special
buffer inside the kernel.
You can then use the dmesg command to print the
contents of this buffer.
dmesg -c will print the buffer, and then clear
it.
Suggested Reading and Resources
Part 1: Getting acquainted with the platform
In this section, you will become familiar with the new boards
in your kit, and learn the basics of connecting to the iMote2 with a
computer to install and run programs.
In your kit, you should now have five boards: two iMote2s,
two basic sensor boards (BSBs), and one debug board.
The iMote2 |
The Basic Sensor Board (BSB) |
The Debug Board |
The iMote2 is a complete system consisting of a PXA271 Xscale
microprocessor, 32 MB of flash program and data storage, 32 MB of RAM,
and an 802.15.4 ZigBee wireless radio. It also has an RGB LED
on the board that can be used as a simple output device for debugging.
It has two sets of connectors for expansion: the basic
connector set (shown in the photo above) and the advanced connector
set. You will use both expansion connectors by the end of the
course.
The Basic Sensor Board (BSB) connects to the iMote2 via the
basic connector side. It provides an accelerometer, light
sensor, temperature and humidity sensor, and a general purpose A-D
converter. In this course, we will primarily use the BSB for
the accelerometer.
The debug board features the same USB-to-serial
interface chip that you used in Lab 4. You'll generally be
using the USB ethernet interface to connect to the iMote2, but the
debug board is useful for logging in if you cannot establish a network
connection or need to change the network settings. The debug
board also has a JTAG interface which can be used to re-flash an iMote2
if the software or configuration becomes corrupted or damaged.
We've pre-loaded the Linux kernel, root filesystem, and the
bootloader (BLOB) onto your iMote2 using the JTAG interface.
You should be able to follow these directions to log into the
iMote2:
- Connect the iMote2 to the computer with a USB cable, and
press the power button. The LED will turn blue while the
iMote2 is starting up, and then green when startup is complete.
Wait for the LED to turn green.
- Launch PuTTY, and enter 192.168.99.101 for the IP address.
- Once you see the login prompt, log in with username root and password rootme.
You're now logged in to the iMote2. Take a moment to
familiarize yourself with the environment. It's more or less
a standard Linux shell from which you can manage files and execute
programs.
Next, you'll compile your first kernel module (driver) and
user-level application for the iMote2. This simple driver
will blink the LED on the iMote2, and the user application will allow
you to change the rate at which the LED blinks.
- Download and unzip the blink
application code to your attu account.
- To compile the kernel module, simply run
make
.
This should produce a file called blink-mod.ko, which is
the kernel module.
- To compile the user application, run:
/cse/courses/cse466/iMote2/cross-compiler/arm/3.4.1/bin/arm-linux-gcc
-o blink-app blink-app.c
This should produce a file called blink-app,
which is a user-level utility to set the blink rate.
- Copy the files over to the iMote2 using WinSCP3.
To do so:
- Launch WinSCP3 from the desktop.
- Click "New."
- Enter the IP address of the iMote for the hostname, root as the
username, and rootme
as the password.
- Optionally, click "Save" and "OK" to save the settings
for use the next time you connect.
- Click "OK."
- Drag the files from your O: drive
(o:\unix\homes\iws\<username> maps to your UNIX home
directory) to the WinSCP window to transfer them to your iMote2.
- To load your kernel module, run
insmod blink-mod.ko
on the iMote2 console. This "inserts" the module into the
running kernel. For more information on insmod, see the
manual page.
- In Linux, like all other UNIX-like operating systems,
devices appear as special files. Devices can either be character devices,
which act like infinite streams of bytes, or block devices, which
communicate with fixed-size blocks of data. Devices are part
of the regular file system namespace, usually appearing in the
/dev
directory. For each device "file," the filesystem contains an
entry mapping that "file" to a device number, split into major and
minor parts. Major numbers correspond to a particular kind of
device, or a driver responsible for that device. Minor
numbers correspond to specific instances of those devices, which are
managed by a common driver. You can view these major and
minor numbers by running ls -l /dev
.
Since the number of available major device numbers is small,
well-behaved drivers request a major number when they are inserted into
the kernel. Now that the kernel module has been laoded, we
need to find out which major device number Linux has assigned to our
new device. To do so, run cat /proc/devices
.
Under "Character devicces," you should see a line like 254
blink
. In this case, 254 is the major device
number. Since we only have one blink device, we use 0 as the
minor number. To make a filesystem entry for the blink
device, run mknod /dev/blink c 254 0
.
This creates a character device named /dev/blink that points
to device number 254,0. For more information on mknod, see
the manual page.
- Make sure that blink-app is executable by running
chmod
+x blink-app
.
- Execute
./blink-app 1
to blink
the LED at 1 Hz.
- To remove the blink module, execute
rmmod blink
.
For more information on rmmod
, see
the manual page.
Part 2: Extending the blink module
Now that you've successfully built a kernel module and an application
that uses it, let's take a look at how it works. From there, you'll
extend the blink module to drive three PWM signals to the tri-color LED
(similar to lab 3).
Let's take a look at the blink-mod.c file.
- At the top, you'll notice several headers are included. The
quick reference guides at the back of chapters 2-4 of Linux Device Drivers
will tell you which header files you need for various kernel functions
and data structures.
- The MODULE_AUTHOR, MODULE_DESCRIPTION,
MODULE_VERSION, and MODULE_LICENSE
macros embed information into the final kernel module file.
- The blink_fops structure points to supported file
operations that
can be performed on the device. A pointer to this structure is passed
when registering the driver. For more information, see chapter 3 of Linux
Device Drivers.
- The module_param macro lets you declare parameters that can
be
specified when the module is loaded. For more information, see chapter
2 of Linux Device Drivers.
- The blink_ioctl function receives all
ioctl() calls on the device file from user
applications. ioctl()
is used for sending commands and setting and querying configuration
information to and from the device when regular "read" and "write"
semantics don't fit. For example, ioctl is used
to eject CD-ROMs, set serial port baud rates, etc. For more information
about ioctl, see chapter 6 of Linux
Device Drivers.
- The blink_irq_handler function is
called when the timer
interrupt is fired. Note that on the PXA271, the interrupt for timers 4
through 11 are shared, and you must inspect the OSSR register to see
which timer has fired. See the PXA271 Developer's Manual
for more details. If the interrupt is not handled, IRQ_RETVAL(IRQ_NONE)
is returned. If the interrupt is for this module, IRQ_RETVAL(IRQ_HANDLED)
is returned. Chapter 10 of Linux Device Drivers
has more information on interrupt handling under Linux.
- init_function is the function called
when the module is
loaded. Its responsibilities include allocating a major device number,
allocating a character device for that device number, pointing the
character device to the structure of available file operations,
registering the interrupt handler with the kernel, and setting up the
hardware timer. Note that unlike main, the function name isn't anything
special -- a macro at the bottom of the file declares which function
performs initialization.
- unload_function is called when the
module is unloaded. It
unregisters the interrupt handler and frees the character device and
device number. Note that if a module doesn't clean up properly, the
kernel will likely crash (called a "kernel panic") since data
structures inside the kernel will point to code and datathat's no
longer there. Like init_function, a macro at the bottom of the file
declares which function should be called when the module is unloaded.
Now, your task is to modify the blink module and application so that
you can set the tri-color LED to any color using PWM. Specifically:
- Modify the blink kernel module to generate PWM signals to
all three
LEDs, like you did in lab 3. You should be able independently control
each LED’s duty cycle.
- Modify blink-app into a user-level application that sets
the color
output of the tri-color LED. You’ll need to extend the kernel module’s
ioctl interface for this.
Question 1: To acknowledge an interrupt, you write a 1 to
the corresponding bit in
the correct interrupt flag register. This clears
the corresponding bit, and
bits to which you wrote zeros are left alone. What is the reason behind
this seemingly
strange convention?
Question 2: In blink_irq_handler,
we acknowledge the compare match interrupt
for timer 4 with the code OSSR = OIER_E4 where OIER_E4
is defined as (1<<4).
What could go wrong if you did OSSR |= OIER_E4
instead? (This was a bug in the
skeleten code distributed for previous quarters.)
Part 3: Using the accelerometer
In this part, you'll finish implementing a driver for the accelerometer
on the Basic Sensor Board, and write an application that converts
accelerometer readings to colors, like in lab 3.
- Download the accelerometer skeleton code here. It should contain the
following files:
- st-accel.h -- header file for accelerometer driver,
shared between the module and application
- accel.c -- accelerometer driver skeleton code
- accel-dump.c -- program that continually reads and
displays accelerometer values
- Before getting started, here's some things to note about
the skeleton driver:
- In addition to the ioctl syscall,
the read syscall is implemented. The driver emits
a stream of sensor readings that can be read with read.
If no sensor readings are available, the application sleeps until data
is available.
- To read from an accelerometer register, use the accel_read_reg
function. To write to an accelerometer register, use the accel_write_reg
function. Make sure you read the code for these functions and
understand what they are doing.
- A GPIO interrupt is triggered when data is ready.
NOTE:
If the debug board is connected and set to use UART0, this
interrupt may not be properly triggered. You shouldn't try to
write your accelerometer driver with the debug board connected.
- The ssp_* functions are part of
the Linux kernel, and interface with the PXA271's synchronous serial
port controller. See /cse/courses/cse466/iMote2/sources/linux-2.6.14/include/asm/arch-pxa/ssp.h
for more details.
- You can write data into the circular buffer read by the
application by calling write_data_into_buffer.
You may also need to wake up processes that are waiting for
accelerometer data. Refer to Chapter 7 of Linux Device Drivers.
- Since the driver simply produces a stream of bytes, the
accel-dump
application expects two sync bytes to precede each accelerometer
reading (0xA5 0xA5).
- Implement accel_irq_handler and accel_ioctl
in accel.c.
- Test your module with the provided accel-dump
application.
- Create an application that translates the accelerometer
readings
into color values on the RGB LED. You do not need to do any fancy HSV
to RGB mapping
-- simply using each axis of the accelerometer to control a single
color is sufficient.
Question 3: If the measurements for each of the axes are
not read by
the time the next
measurement is ready, the accelerometer will stall and set flags to
indicate that an
overrun has occurred. Why might this be desired behavior, rather than
simply overwriting
the old values?
Question 4: Why is
it particularly important to minimize the number of
SPI transactions in
the interrupt service routine?
Deliverables
- The following files should be turned in:
- Your code, both for your user applications and your kernel modules
- Your answers to the questions (.txt, .rtf, .pdf, or .doc; NO .docx)
- Submit files to the Catalyst drop box for your section:
- Demonstrate
your completed Part 3 to a TA during the first half-hour of
next week's lab period.