This is the Developer's Manual for CU Network Roboboard v2.1 and operating system in Lego Robots configuration.
By Brian Shucker
January 2007
Updated by Kevin Bauer
January 2007
The roboboard hardware is derived primarily from two sources: the Atmel at91SAM7S64 development kit reference design, and the older CU roboboard. The schematics and layout are here. Each schematic sheet is explained below.
Just about everything on this sheet is copied directly from the Atmel reference design. The CPU behaves the same way as it does in the Atmel dev board, with the same clock speed (~48MHz), JTAG interface, and so on. Of course, the peripherals are all different.
This has all the miscellaneous devices. Most of this stuff is pretty standard. The fast LED is called that because the LED_FAST signal line is connected direclty to the CPU--the LED_SLOW signals go through the i2c bus, which is much slower. LED_FAST is actually connected to a timer output on the CPU, so it's easy to generate various waveforms on it in hardware.
The SPI bus uses a hardware decoder, which is a little weird. There are actually three chip-select lines coming from the CPU: CS_1, CS_2, and AUX_CS, which is actually CS_0. The AUX_CS line is connected directly to the off-board connector, while the other two go to a decoder and then to the chip selects for onboard peripherals. It's done this way because CS_0 is special--if the board is going to be the slave, then it needs to listen on CS_0. Thus, in principle you could hook two boards together via SPI, and put one in master mode (selecting AUX_CS), and the other in slave mode.
The motor signals need to be level shifted from 3.3V to 5V. Other than that, the motor circuit is pretty much the same as that from the old roboboard, and pretty standard for an L298 driver chip. Note that the direction lines (MOT_FX and MOT_RX, where X=0 to 3) are slow I/O lines, but the enable lines are fast. This is because the PWM signal that controls the motor speed is sent on the enable, while the direction changes relatively infrequently.
The current through the motor driver goes through a 0.5-Ohm current sensing resistor and then to ground. Measuring the voltage across that resistor tells us the current. The rest of the stuff on this page is just filtering.
The JTAG interface is copied from the Atmel reference design. The USB interface is copied as well, although the pullup resistor is controlled via a slow I/O line instead of connecting directly to the CPU. The serial interface is a pretty standard RS232 level shifter, and is wired so the roboboard connects to a PC via a standard straight-through serial cable.
The Roomba interface is just another UART, but it's level-shifted to 5V. The TX and RX lines are shared with digital input 5 and 6. The TS5A23157 is there to switch the function of those lines--when the Roomba is present, the lines connect to the Roomba; otherwise, they connect to the digital inputs. When the Roomba is plugged in, it pulls the NROOMBA signal to GND, which is how it is detected.
This is the central switchboard, so to speak, of the roboboard design. The CPU has 32 digital I/O lines. Each line can be used as a regular digital I/O, or assigned to specific peripheral functions. Only certain peripheral functions can be assigned to certain lines--see the at91SAM7S256 datasheet for an explanation of what is allowed to go where.
The PA bus is the one that goes directly to the CPU digital I/O. This sheet shows how each line is assigned to a particular function. There are many constraints: for example, the SRV signals that control servos need to be attached to the PWM peripheral, the LED_FAST signal needs to be connected to a timer peripheral, the SPI and I2C lines must go to their respective peripheral functions, and so on.
To provide extra digital I/O, the roboboard has six PCF8574 bus expander chips. These chips connect to the I2C bus, and they provide 8 digital I/O lines each. However, they are fairly slow. Thus, the expanded I/O lines are used for low-speed peripherals, like the LEDs, the DIP switches, and such.
There are three power supplies on the roboboard, all derived from a single 12V unregulated input (the battery). Although 12V is the intended input voltage, anything from 7.2V to 16V should be ok.
The first power supply is +12V unregulated, and it simply connects to the battery. This supply is used to power the motors and other analog devices.
The second supply is +5V regulated. It powers the 5V logic components like the LCD screen, motor drivers, and level shifters. It also supplies power to the servos. With that in mind, the 5V supply can handle up to 2A of load. Make sure to size servos appropriately so as not to exceed that limit.
The final supply is +3.3V regulated. It powers the majority of the logic, including the CPU. It also supplies power to the sensor ports. The maximum load on the 3.3V supply is 600mA.
Both the 5V and 3.3V supplies use switching regulators, so they are highly efficient regardless of the input voltage. Also, there is a power jumper in series with each supply (JP3 on 3.3V, and JP4 on 5V). These are the right place to insert an ammeter to measure current consumption on the individual supplies. There is also a header available to provide off-board access to each supply.
This is copied entirely from the cc2500 reference design from Chipcon.
The digital inputs go to the CPU via a transceiver which is just a buffer (so if somebody accidentally zaps one, it gets the transceiver and not the CPU). The slow digital I/O ports are connected to the I2C lines, so they can be used as input and output, but they're slow and not interrupt-capable. If you're not using those ports, the last two I2C chips (U6 and U8) do not need to be installed.
There is a 16-bit, 4-channel ADC connected via the SPI bus that does most of the analog input. Three of the lines are connected directly to the accelerometer (X, Y, and Z), so they can be read without having to set any mux lines. The fourth channel goes to one of the multiplexers, which then connects to the first 8 analog ports (8-15). The mux selection is controlled by three GPIO lines of the CPU. The connection order on the analog ports is funny because of the way the mux pins are laid out--it's much easier to route this way.
The second multiplexer connects to the robo knob, battery monitor, motor current sensors, and the lst two analog ports (16-17). These lines all feed into one of the CPUs ADC channels, which is significantly less accurate than the dedicated off-chip ADC. However, it's certainly accurate enough for current sensing, battery monitoring, and the knob. Keep in mind that the last two analog input ports are not as accurate (and don't operate at the same speed) as the first eight.
The software is built using IAR Embedded Workbench. It's basically derived from the FreeRTOS demo for the at91SAM7S64 using IAR tools. It is optimized (mostly) for usablity and maintenance, as opposed to efficiency. However, there are some places where efficiency was important.
The code is divided into a number of directories:
at91lib contains library code for the at91SAMS256 from Atmel. The files are unmodified from the Atmel stock versions. They are primarily header files, which define all of the registers, the memory map, and various other bits of the processor hardware.
codeimage contains a java program that translates binaries into ascii-encoded files that can be transferred via serial port.
experiments contains a variety of projects used to test pieces of the OS. As the OS has evolved, many of the experiments have become obsolete and cannot be run on the current version. However, it's still useful to see the original code.
kernel contains a slightly modified version of the FreeRTOS kernel. It is derived from the at91SAM7S64 IAR example from FreeRTOS. Also, the kernel.c file contains wrapper functions and other roboboard-specific functions.
network contains the network stack, including TDMA MAC, stop-and-wait protocol, and a reliable block transfer protocol (BTP). The BTP protocol still has outstanding bugs, mostly related to efficiency.
apps contains the user-level applications. This is where new application projects should be created.
boot contains the boot loader and initialization code.
drivers contains device drivers for all of the roboboard peripheral devices. In general, drivers involve direct knowledge of the hardware device they are associated with (that is, if you need a datasheet to write the software, then it's in the drivers directory as opposed to the lib directory). Many of these drivers call each other. Particularly, the low-level drivers such as SPI and I2C are called by the higher-level drivers, such as dataflash and expio.
Note that inside drivers/include is the file roboboard_hw.h, which contains a huge number of #defines that describe how the hardware is wired.
lib contains library code for user utilities and high-level devices like servos and shaft encoders, which have a device driver between them and the actual hardware. The robo library is found here; it contains some lego-specific code, as well as a number of wrapper functions to simplify the interface for students.
system contains the system utilies, such as the shell, remote procedure call, and the health monitor. There is a separate legoshell for lego robots that replaces the usual shell.
Ints vs no ints: some instructions on the ARM are not allowed from an interrupt context. For example, if you try to disable interrupts from within an interrupt handler, you *might* get a prefetch abort. For this reason, there are places in the code where there are "noints" versions of certain functions, so they can be called from an interrupt handler efficiently.
RAM functions: when the boot loader is writing the flash, it needs to run its code out of RAM, to avoid running code that it's in the process of overwriting. Any function that gets called by the boot loader during the reprogram function has to be a RAM function.
Timing issues: some things are slow, like I2C operations, and some are fast, like SPI operations. There are occasionally times when interrupts are disabled for a significant period of time. For example, it takes about 4ms to read or write a dataflash page. During that time, the motor PWM signal and shaft encoders may skip a few beats because their interrupt is delayed (this might come up with the log message system).
For the most part, the configuraion is copied from the FreeRTOS demo app. For lego robots, LEGO is #defined (in the C compiler preprocessor section). This is only referenced in init.c, so it knows to start the lego shell and not the network stack.
An extra linker option is given to generate a plain binary (.bin) file. This file is exactly what needs to be copied into flash memory on the CPU. A post-build command line (under build actions) calls Codeimage to convert the .bin file into a .img file. Codeimage is a command-line program that reads in the binary, formats it into 512-byte pages, encodes the pages with base64 (to get ascii characters), and adds a checksum to each page. It also prepends information about the size of the image, and adds the prog command to the front. The resulting .img file is exactly the string of bytes that needs to be sent to the shell in order to reprogram the board. Thus, the .img file can simply be dragged onto TeraTerm to reprogram the board.
The FreeRTOS configuration is pretty standard, and is found in the FreeRTOS config header file.
It is important to read the section in the FreeRTOS documentation about interrupts on the at91SAM7S64. Adding an interrupt involves editing the isr.s79 assembly file to create the proper ISR entry point. Just follow the pattern of the interrupts that are already there.
FreeRTOS has been modified in one significant way. The roboboard's serial port is connected to the DBGU UART. Interrupts from that UART come on the SYS interrupt line, which is the same interrupt used by the programmable interval timer that FreeRTOS uses for its scheduler. FreeRTOS assumes that all SYS interrupts are from the interval timer (and thus indicate a clock tick), but this is not the case, since the UART can also cause SYS interrupts.
The portasm.s79 file was modified so that the SYS interrupt first calls uartHandler. If it was just a UART interrupt, the handler returns false and we skip the rest of the SYS ISR. If there is actually an interval timer interrupt pending, the uart handler returns true, and the usual FreeRTOS ISR is run.
The program entry point is actually in Cstartup.s79, which then jumps to Cstartup_SAM7.c. Those are FreeRTOS files that just start up the CPU (setting up memory sections, configuring the clock, etc.) and then jump to main.
The main function is boot.c. Main will run the preinit function (in init.c), which does all the low-level hardware initialization that needs to happen before the boot loader runs and the kernel starts. Next the boot loader runs.
The boot loader reads the boot control block from dataflash, which tells it if the board needs to be reprogrammed. If so, the new image is copied from dataflash into program flash, and the board is reset. Otherwise, the boot loader returns.
The boot control block also contains configuration info, such as the board ID and message log pointer. A pointer to the boot block can be retrieved with bootGetBootBlock(). If the info is changed, it can be committed to flash with bootCommitBootBlock(). Be careful editing the boot_control structure, as the old boot info might be misinterpreted in a bad way on the first boot with the new structure. In general, it's advisable to add new fields at the end of the structure.
After the boot loader returns, the main() function calls kernelMain(), which creates the start thread and then fires up the FreeRTOS scheduler. From this point on, the OS is running under FreeRTOS control. The start thread first performs the postInit, which initializes all the drivers that need to come up after the kernel. This usually means that the driver uses interrupts, queues, or threads that require kernel support.
One of the systems initialized in postInit is the shell. The shell runs at high priority and processes commands from the uart, including the prog command. The user reprograms the board by simply downloading a .img file (in TeraTerm, just drag it onto the window). The first thing in the .img file is the "prog" command, then the code size, then the code pages. The pages are received, checked for corruption, and stored in dataflash. Then the boot block is written, to indicate that the image should be copied into program flash on the next reboot. The user needs to reset the board for the new code to take effect.
After the postInit, the start thread spawns the heartbeat thread, which runs at highest priority and blinks the blue LED, to tell us that the OS is alive. Then the welcome message is printed to the LCD. If the choose button is not pushed, then the user's main function (roboMain) is called.
Most of the drivers are fairly straightforward. To really understand what's going on, refer to the schematics and the at91SAM7S256 datasheet, which explains what all the registers do.
Nothing terribly interesting here. Look at the schematics to see how the various sensors get mapped to ADC channels, internal and external.
This one is kind of a beast. Read the cc2500 datasheet to see how the radio works. Basically, there's a receive queue (for asychronous receive), and sends are done synchronously. Whenever a packet is received, the GDO0 interrupt fires, and we read the packet out and call the received packet handler. The default handler puts the packet on the queue. Alternately, the MAC can set its own packet handler via cc2500SetPacketHandler() (TDMA does this).
Sends are handled right away in the calling thread's context. We wait for the txSem to be available first. That sem is posted by the GD02 interrupt handler; the interrupt fires whenever the tx fifo is clear. It is also possible to send from an interrupt context, with cc2500SendPacketNoInts(). That sends the packet right away, without checking to see if the fifo is clear. The MAC layer might want to do this, since it knows the state and may need to get its timing right. TDMA does it, since it always transmits during a timer interrupt.
Nothing terribly interesting. The page transfers have to be done atomically, since the chip expects the CS line to be down the whole time. It should be possible to remove this restriction and thus eliminate a long period with interrupts disabled (about 4ms), but it hasn't been worth the trouble.
Mostly simple. There are features for the user to register a callback function that will be called whenever the input changes. There is also a general callback function, which can register a callback on ANY line, not just a digital port. This feature is used by the cc2500 driver to get its GD0 interrupts.
There is a potential bug with the input change interrupt--sometimes is stops firing for no apparent reason. This only happens with external interrupts, never with the ones coming from the radio. For this and other reasons (debouncing, in particular), the shaft encoders don't use the input change interrupt.
This handles the i2c expander chips. It's pretty simple stuff, but make sure you set the direction of the pins correctly--if the pin is an input, then writes to it will be ignored. Most of the functions specify a byte (which specifies which bus expander chip), and a bit within that chip. Those values are all #defined in roboboard_hw.h.
This driver operates in several modes. In "dumb" mode, which it enters after the preinit, it just uses busy-waits. This is used while the boot loader is running. In interrupt mode, which is entered in postInit (so it's the normal mode), operations set up a transfer and then wait for an interrupt, which posts a semaphore. A subset of this is multi-write mode, where a bunch of transfers are set up. The interrupt fires for each one, but just loads the next byte each time, and posts the semaphore at the end.
This driver is unfinished and untested. It's supposed to generate a square wave at some frequency that an IR receiver module will pick up.
Nothing extraordinary here. The LCD is a standard hitachi LCD controller, bit-banged over the expio system. There is one outstanding bug: occasionally, the LCD gets into a funky state and displays garbage until it's reset. As a temporary workaround, the driver resets it every few newlines. It's a hack, but it helps until we find the bug.
Yes, we've found the world's simplest device driver.
The motor direction lines are through expio (slow), but the enable lines (which generate the PWM enable signal to create different speeds) are connected to the CPU's PIO lines directly. The PWM period is divided into certain number of time steps; during each step, the enable line can be on or off. The enablePWM array has a bit mask for each time step of the lines that should be on, and the disablePWM array has a bit mask of the lines that should be off. The motor interrupt fires at 1000Hz; it reads the next value in the arrays and sets the enable lines accordingly. The motorSet function just sets the direction lines through expio, and then it sets the bits in enablePWM and disablePWM to generate the appropriate PWM signal. It's done this way to make the interrupt fast.
The motor interrupt also calls the shaft encoder update function. This is a bit of a hack, but it lets us share the timer interrupt, since they're both on the same frequency.
This is a pretty straightforward implementation of the PWM functions described in the datasheet.
See the roomba serial control interface (SCI) documentation for an explanation of what this driver is doing. It's a straight implementation of the SCI.
SPI functonality is implemented as you would expect from the datasheet.
This is another straightforward implementation from the datasheet. As implemented now, the timer always uses the same source clock, which is MCK/1024. This gives about 20uS resolution and a max period of about 1.4s, which is fine for everything so far. The timer calls back a user function when it fires. The callback function should return true if it causes a context switch, or false if it does not. This is a standard FreeRTOS idiom.
This driver is somewhat sophisticated, because it uses DMA (the peripheral DMA controller (PDC) in datasheet terms). It needs to use DMA for flow control--there are times (particularly dataflash writes) when interrupts are disabled for long enough that uart traffic is missed. Sending Xoff is insufficient, because PC uarts have buffers that are several characters long. The uart driver needs to be able to receive a number of characters without servicing an interrupt, so we can send Xoff and then still have room to receive the characters that are already in the pipe from the PC's point of view.
With this in mind, the uart receive is set up like a circular queue. There are two DMA buffers that are next to each other in memory. The DMA controller ping-pongs between them, so in effect it writes sequentially through a circular queue (the first half of the queue is buffer1, the second is buffer2, then repeat). There is a register that tells how many bytes have been transferred, so the queue fill level is always available. There is an interrupt every time one of the buffers is filled, at which time the appropriate registers are set to keep the ping-pong action going.
Often, the user calls uartRead before an entire PDC buffer is filled, but some bytes are already available. In that case, we read the received character count from the PDC register, and *preread* some of the bytes--we go ahead and retrieve them now, and just record that they're already received. When the buffer end is reached and the PDC switches to the other buffer, the number of new bytes (the buffer size minus however many were pre-read) is recorded as available.
The driver uses Xon/Xoff for flow control, so it's advisable to only transfer readable characters. For this reason, program images are base64 encoded before being transferred.
Writing to the uart is accomplished through a simple, inefficient busy-wait, since there hadn't been a case yet where we care to optimize that.
This isn't really a driver; it's the FreeRTOS USB demo, modified to run on the roboboard. Calling usbStartDemo() will start it up. Check out the FreeRTOS documentation to see how this thing works.
The network stack is unused, and generally not all that suitable, for lego robots. The TDMA driver works fine, but it requires configuration with a master and ID numbers on every client board. The stopwait protocol is useful, and could easily be made MAC-independent. The block transfer protocol (btp) is only valuable with TDMA, and is not suffieciently well developed.
It would be valuable to implement a CSMA MAC and then modify the stopwait protocol to call that instead of TDMA. Use TDMA as an example for how to call the cc2500 driver.
This is very useful stuff. When an assert fails, interrupts are disabled, the LEDs blink, and a debug message is repeated on the uart.
It may be valuable in the future to display the debug message on the LCD as well. That will require modifying the LCD write function to operate without interrupts.
The only interesting thing here is that the encoderUpdate() function is called from the motor timer interrupt, since that saves us a timer block for other uses. The rate is 1KHz, so the encoders should be reliable up to about 250 ticks per second, and maybe close to 500. One issue: when reading/writing a dataflash page (think log message), interrupts are disabled for 4ms at a time, which could cause the encoder to skip if it's counting faster than about 100 ticks per second.
See the notes in the code and the example in songs.c.
This has mostly wrappers for the lego robots students. One interesting function is startTournamentClock(), which spawns a kernel-priority supervisor thread that kills main and shuts down the motors after 90 seconds.
This is really just a wrapper for the pwm driver, with the right ranges for a standard servo.
These are example songs. Enjoy.
This is the shell for lego robots--nothing exciting design-wise.
The message logger uses the dataflash as a circular queue for log messages. It uses all of the dataflash that isn't used for programming, which is most of it.
This is the board health monitor. The init function does startup-time checks, and the monitor thread runs in the background and does continual checking. At the moment, the monitor thread is not active, and the only startup check is for a brownout reset.
It would be valuable to update this to add a good battery level monitor.
These are not used for lego robots; they were used for remote management of robots in distributed control experiments. They are incompatible with the legoshell.