CSE 466 Lab 5: Introduction to the iMote2 and Embedded Linux
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, 416 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
- How to use the PXA271's hardware timers
- How to control sensors and peripherals on the iMote2
- How applications communicate with devices in Linux
- The basics of the Linux character device model
Suggested Reading and Resources
WARNING: There is some important and really useful information in this section. Don't skip over it!
- Chapters 1-4 and 10 from the Linux Device Drivers book. This is a lot of reading to do, but I suggest that you sit down with the book and at least skim through it. This book is one of the best resources on the Linux kernel. It's also available on Safari Books (free access on-campus thanks to the Engineering Library, and you can use the library proxy from off-campus) if you like the web interface better than the individual chapter PDF files. It's also a good reference to have in dead tree form if you're interested in doing more Linux development in the future.
- The Linux kernel APIs are sort of a moving target, and there have been a few changes since the third edition of Linux Device Drivers was published. (The book covers kernel 2.6.10; we are using 2.6.28.) This page lists recent changes to the kernel API.
- The Documentation subdirectory in the Linux kernel source tree provides up-to-date documentation on many of the important kernel APIs. There is a copy of the 2.6.28 kernel installed at /cse/courses/cse466/iMote2/sources/linux-2.6.28 that you can refer to. You can also download the kernel sources from kernel.org.
- It's not required reading for any of the labs, but Building Embedded Linux Systems (link to Safari Books) provides a good overview of a lot of the lower-level details of an embedded Linux system (and Linux in general). It discusses the purpose of all of the files on the root filesystem, how the boot process is handled, and many other topics pertaining to embedded Linux.
- The Intel PXA271 Developer's Manual
- The iMote2 datasheet
- The Basic Sensor Board (BSB) datasheet
- The accelerometer datasheet
Useful Software
- PuTTY is a simple, lightweight SSH client. You can use whatever SSH client that's included on the lab machines, but it's pretty heavy weight for our purposes (we don't need X forwarding and stuff like that.) PuTTY is nice and simple.
- WinSCP is a simple SFTP client. Again, feel free to use whatever SFTP client is already on the lab machines, but for working with the iMote2, WinSCP is nicer.
Part 1: Getting acquainted with the platform
WARNING: The iMote2 and BSB are not indestructible. Make sure you observe reasonable precautions for handling static-sensitive components when handling the bare boards (you don't need special grounding straps, but don't rub it all over synthetic fabrics.) Try to keep the boards inside the anti-static bag when you put everything away in your kit.
In your kit, you will have two iMote2 boards and two basic sensor boards. Your kit also contains two USB A to mini B cables to connect the iMote2s to your workstation, and a battery and charger for a SuperBird board, which you will receive later.
The iMote2 |
The Basic Sensor Board (BSB) |
The Debug Board |
The Basic Sensor Board (BSB) contains an accelerometer, light sensor, temperature and humidity sensor, and a general-purpose ADC converter. In this course, we will primarily use the BSB for its 3-axis accelerometer.
The debug board features the same USB interface chip you used in Lab 4. It lets you tap into the iMote2's serial ports. While you'll generally be using the USB Ethernet interface on the iMote2 itself to access the Linux console and program the iMote2, the debug board can be helpful for logging in when you cannot establish a network connection to the iMote2. The debug board is not included in your kit, but there are several available should you need one.
We've pre-loaded the Linux kernel, the filesystem, and the bootloader (BLOB) on your iMote2 using the JTAG interface. You should be able to follow these directions and log into the iMote2:
- Connect your iMote2 to the computer with a USB cable, press the power button, and wait for the LED to turn green.
- Launch PuTTY. If PuTTY is not installed on the lab machines, you can download it here.
- Connect to 192.168.24.1
- Once you see the login prompt, log in with username root and password rootme.
Your iMote2 is running Linux 2.6.28, with a small set of userspace programs. To enable easy access to the iMote2, it runs an "ethernet gadget" driver that uses the USB interface to make the iMote2 appear as a USB ethernet card. After it's booted up, it starts a small DHCP server so that when you connect it to the PC, an IP address (192.168.24.110) is automatically assigned to this interface on the PC side. The dropbear SSH server is a small, lightweight SSH server that allows you to log in over this network interface to access your iMote2. SFTP is also supported, and we'll be using this to transfer files to and from the iMote2.
NOTE: The Windows driver for connecting to the ethernet gadget is located at /cse/courses/cse466/iMote2/sources/linux-2.6.28/Documentation/usb/linux.inf. You'll need this if you want to connect your iMote2 to your Windows laptop, or if the lab workstations ask for it.
Take a moment to explore the filesystem on your iMote2. There isn't a lot there, so if you've ever wondered what's really essential to a small Linux system, now is a good time to have a look around and familiarize yourself with the essentials.
Now that you've logged in to your iMote2, let's compile our first kernel module and user-level application! First, set up your attu account to use the cross-compilation tools required to produce code that runs on the iMote2's Xscale processor. The tools are located in /cse/courses/cse466/iMote2/cross-compiler/arm-xscale-linux-gnu/bin, which you should add to your $PATH environment variable. If you are using bash, add the following to your .bashrc file and restarting your session (all on one line; it's wrapped here so that it doesn't go off the page on printed copies):
export PATH=$PATH:/cse/courses/cse466/iMote2/cross-compiler/
arm-xscale-linux-gnu/bin
If you are using a different shell, Google, or the man page for your shell, can probably tell you how to add things to your profile. You can also always specify the full path to the compiler every time you invoke it, if you really want to do all of that extra typing.
Check to make sure that you can invoke the compiler by running arm-xscale-linux-gnu-gcc from your shell prompt. It should tell you that there are no input files (since you haven't specified any) and exit.
Now, we'll start working on our first kernel module and userspace program. This will be a driver that uses one of the PXA271's hardware timers to blink the red component of the LED on the iMote2. In this section, you'll compile the provided code and get it loaded on your iMote2. In the next, you'll adapt the module to have a shorter period and adjustable duty cycle, so that you will have PWM control of the RGB LED.
- Download and unzip the blink starter 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.
- Have a look at the Makefile in the blink directory to see what it's doing. In order to build kernel modules, you need a configured kernel installed somewhere in order to use its build system. We have the Linux kernel installed in the course directory for CSE466 on attu. The Makefile simply changes into this directory, and calls the kernel build system for the objects specified in the Makefile.
- This produces a kernel module specifically for this version of Linux. Since the APIs are constantly changing, it is important that modules be built for specific kernels.
- To compile the application, run:
arm-xscale-linux-gnu-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 of the LED.
- Copy the files over to the iMote2 using WinSCP3. To do so:
- Launch WinSCP3.
- Click "New."
- Enter the same IP address you used to connect with PuTTY for the hostname, root as the username, and rootme as the password.
- Optionally click "Save" and "OK" to save the settings for use next time.
- 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 load your kernel module, run insmod blink-mod.ko on the iMote2 console. 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, and block devices, which communicate with fixed-size blocks of data. Devices are part of the regular filesystem namespace, usually appearing under 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 number by running ls -l /dev.
Since the number of 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 loaded, 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 devices," 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.
- Execute ./blink-app 1 to blink the LED at 1 Hz. Try some different frequencies.
- To remove the blink module, execute rmmod blink. For more information on rmmod, see the manual page.
- Note that when you execute rmmod, it might complain that the module was not found, since the module isn't actually installed on the system in the proper location. However, it will still do the right thing and unload the module.
- You can always get a list of currently loaded kernel modules by running lsmod. This is a good way to check that your module is being loaded and unloaded properly.
Part 2: Extending the blink module for PWM
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).
The PXA271 does have hardware for PWM. However, the pins that are PWM-capable aren't wired up to the LEDs. (In fact, they're not wired to anything at all on the iMote2.) So, you'll be doing pulse width modulation in software. Remember Question 1 on Lab 3? Now's the time to put that pseudocode into practice.
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. You should edit these macros to reflect your changes to the module, such as your names instead of Karl's, and an updated description of the module to reflect its new functionality.
- The blink_fops structure points to supported file operations that can be performed on the driver's character 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 and is being handled, 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" if things completely lock up, or an "oops" if the kernel detects the error but is able to mostly recover) since data structures inside the kernel will point to code and data that'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.
- You should also rename the module to reflect its new functionality.
NOTE: Hardware timers are a scarce resource. Your PWM module should not use any more timers than the basic blink module, or you will run into problems with other devices that need timers.
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. These functions implement an SPI protocol for reading and writing from the registers on the accelerometer.
- A GPIO interrupt is triggered when data is ready.
- The ssp_* functions are part of the Linux kernel (implemented in the ssp module) and interface with the PXA271's synchronous serial port controller. See /cse/courses/cse466/iMote2/sources/linux-2.6.28/arch/arm/mach-pxa/include/mach/ssp.h for more details.
- You can write data into the circular buffer read by the application by calling write_data_into_buffer.
- 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. 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.
- You are welcome to change the format of the data in the circular buffer if you wish.
Part 4: Using the radio
In this part, you will be introduced to the radio functionality of the iMote2. We will give you example code for sending and receiving from the radio, but you will have to come up with your own packet structure for your communications protocol.
NOTE: All of the iMote2s are configured to use the same IP address. If you want to connect both iMote2s to your workstation at the same time while you are working with the radio, you should change one of the IP addresses.
The IP address is set in /etc/rcS/S04network.sh, where the interface is brought up by ifconfig. You can change the iMote2 to a different subnet by changing the third octet of the IP address. You should then also edit /etc/udhcpd.conf to set the DHCP server to assign IP addresses in the new subnet.
Remember what you set the new IP address to! However, if you mess up and can't connect, you can always use a debug board to connect over the iMote2's serial port and edit the network settings.
- Download the radio code here and un-zip it in your attu account. It should contain the following files:
- tosmac.h: header file for the radio driver
- count_and_send.c: a program that increments a counter at 1Hz, displays the counter value as a color on the LED, and sends the value with the radio
- receive_to_led.c: a program that receives the count via the radio and displays it as a color on the LED
- led.h: a header file for the simple LED driver that these programs use
- a Makefile that will build the sample applications
- Change the GROUP_NUMBER constant in both .c files to your group's number. This will let you send and receive messages on a channel unique to your group, which is helpful when you are trying to debug your radio code. Note, however, that your communications protocol in your own programs, when finished, should use some kind of unique identifier or address and ignore packets that are not your own. Your unique channel (again, for debugging purposes only) will be your group number plus 11.
- Run make to compile the applications. Load count_and_send onto one of your iMote2s and receive_to_led on the other. (Make sure that you are not running your PWM driver, which will interfere with the basic LED driver.)
- You should see the LEDs on both iMote2s updating at the same time to the same colors. The sending iMote2 will also print out the current value that it is sending, and the receiving iMote2 will print out the value that it is receiving.
- Once you're convinced that the programs work, change the channel back to 11 (set GROUP_NUMBER to 0) and implement a communications protocol so that your receiving iMote2 will only update its LED based on messages it receives from your sending iMote2, even when there are several other iMote2s transmitting on the same channel. The design of your protocol is up to you, but you should use at least a unique identifier (use your group number) and an arbitrary node ID unique to each of your iMote2s.
Note that the tos_mac driver does not implement the ZigBee protocol stack; rather, it implements the TinyOS MAC (which is where the driver gets its name). The TinyOS MAC includes 16-bit group and address fields; however, the driver is kind of a buggy hack and these don't always work properly. For the labs in this course, you will set the address to 0xFFFF, which broadcasts packets, and should implement your own addressing in the payload of your messages.
Part 5: Putting it all together
In this part, you will combine the elements from Parts 1-4 and add bi-directional communication between your iMote2s.
- Using the modules you have developed so far, create two programs to run on your iMote2s:
- The first program should read your accelerometer value and send readings out over the radio using the packet structure you've developed.
- The second program should receive the accelerometer readings via the radio and update the color of its LED using your PWM driver.
- Your receiving iMote2 should also read from its own accelerometer, and transmit a "stop" signal to the sending iMote2 and turn off its LED when it is turned upside down. When it is turned right-side-up again, it should transmit a "start" signal.
- The sending iMote2 should react to a "stop" signal by ceasing to transmit packets and updating the color on its own LED instead. When the "start" signal is received, it should turn off its own LED and begin transmitting packets with accelerometer readings once again.
While you're designing your programs, you should keep the following things in mind:
- In the sample code, reading from the tos_mac driver is done in blocking mode. That is, when you try to read from the device, your program will wait until a packet has been received before continuing on to the next line. This works well for uni-directional communication, but not as well for bi-directional communication (unless you implement multiple threads for sending and receiving.) See the man page for open for more information on non-blocking I/O.
- You probably don't want to send packets as fast as you possibly can. Your sending iMote2 should wait some amount of time between packets that still feels responsive, but is not as fast as possible. If you're sending as fast as possible, you will jam the channel with an unnecessary amount of traffic, making it difficult for other communication (such as other groups' implementations) to take place. Only use as much bandwidth as is necessary for your system to feel responsive.
- Your final implementation should function on channel 11 and ignore packets sent by other groups.
Deliverables
You have two weeks to complete this lab. There are no deliverables for the first week, but to stay on schedule, you should have working LED PWM and accelerometer drivers.
Turn in all required documents an e-mail attachment to cse466-tas@cs. For all files turned in, the comments at the top of the file should contain:
- Both partners' full name, login and student number.
- The lab number and the part of the lab (e.g. "Lab4, Part2").
- Demonstrate your system of sensor to radio to radio to LEDs to a T.A. You can either do this during lab, or during the first 1/2 hour of the lab section when the assignment is due.
- Grading will focus on whether it works, and reasonable responsiveness.
- At the end of the second week, you should turn in source files for:
- all of the kernel modules you developed or modified, and
- the user application you wrote for Part 6.
You should create a .zip or .tar archive of all of the files you are turning in.