SENG 466 Software for Embedded and Mechatronics Systems Project 1...
Transcript of SENG 466 Software for Embedded and Mechatronics Systems Project 1...
SENG 466 Software for Embedded and Mechatronics Systems
Project 1 Report
Team Members:
Jackson Hong
Derek Jacoby
Riley Nold
i
Table of Contents List of Tables and Figures ......................................................................................................................................... ii
Introduction and Problem Statement ..................................................................................................................... 1
Collaboration Tools ................................................................................................................................................. 1
AVR for Eclipse ..................................................................................................................................................... 1
RXTX and Target Management ............................................................................................................................ 2
Subversive ........................................................................................................................................................... 3
Test Scenario ........................................................................................................................................................... 4
Hardware Setup ....................................................................................................................................................... 5
The Base Station .................................................................................................................................................. 6
The Remote Station ............................................................................................................................................. 7
Project Components ................................................................................................................................................ 8
Motor Wiring and Control ................................................................................................................................... 8
Servo Motor Control .......................................................................................................................................... 10
nRF24L01+ Radio Transceiver ........................................................................................................................... 15
Joystick .............................................................................................................................................................. 17
MaxSonar-EZ1 Sonar Sensor ............................................................................................................................. 20
The Hardware Basics ..................................................................................................................................... 20
Obtaining the Sensor Data ............................................................................................................................ 20
How It Works ................................................................................................................................................. 21
The Code Structure ........................................................................................................................................ 21
Problems and Solutions ................................................................................................................................. 23
BlinkM RGB LED ................................................................................................................................................. 23
The Hardware Basics ..................................................................................................................................... 23
How It Works ................................................................................................................................................. 23
The Code Structure ........................................................................................................................................ 24
Reference .............................................................................................................................................................. 26
ii
List of Tables and Figures Table 1 - Seeeduino Base Station Pin Assignment .................................................................................................. 6
Table 2 - Seeeduino Remote Station Pin Assignment ............................................................................................. 7
Table 3 - Radio Transceiver Connections .............................................................................................................. 15
Figure 1 - AVR for Eclipse......................................................................................................................................... 2
Figure 2 - Terminal Within Eclipse ........................................................................................................................... 3
Figure 3 - SVN Integration in Eclipse ....................................................................................................................... 4
Figure 4 - Overall Setup ........................................................................................................................................... 5
Figure 5 - Base Station ............................................................................................................................................. 6
Figure 6 - Remote Station ........................................................................................................................................ 7
Figure 7 - Solarbotics L298 Motor Controller .......................................................................................................... 8
Figure 8 - Neutral Pulse Width .............................................................................................................................. 13
Figure 9 - Minimum Servo Pulse Width ................................................................................................................. 14
Figure 10 - Maximum Servo Pulse Width .............................................................................................................. 14
Figure 11 - Joystick Pins ......................................................................................................................................... 19
Figure 12 - MaxSonar-EZ1 Sonar Sensor ............................................................................................................... 20
Figure 13 - BlinkM RGB LED ................................................................................................................................... 23
1
Introduction and Problem Statement The goal of the first project is to demonstrate mastery over the individual component parts of the final
hovercraft solution. These parts include the following:
Seeeduino microcontrollers
Motor and servo motor outputs via the L298 motor controller
joystick and button inputs
I2C LED
Sonar input sensor for distance measurement
Radio connection for a control station and a remote station
In this first demonstration of functionality, the parts will be assembled on a non-moving platform so some of
the sensors and motor outputs will be put to somewhat different uses than will be expected in the final
hovercraft.
In addition, this first project is where we put together the support tools to allow us to develop as a team. In our
case, we have decided upon the following:
Google calendar to coordinate availability
Google code as a source code repository to allow backup and change tracking
Eclipse as a development environment
Serial.print as the primary debugging mechanism, with the output viewable in Eclipse
This report will detail the steps we have taken in each of these areas to create a test platform for the
technologies and teamwork that we will require to execute on the final project.
The component parts were each worked on individually, and then the final phase was to integrate them into a
test scenario. Subsequent sections will detail the initial implementations of each section and then the
integration work.
Collaboration Tools The final project of this course involves a rather complicated design in both hardware and software. As a result,
collaboration tools are needed to synchronize the work from everybody. In the end, we decided to use the free
SVN provided by Google Code and Eclipse to help us in the development of the project.
Because we decided to use Eclipse as our development platform and Google SVN to manage our source code, it
is ideal to have Eclipse coordinate everything. Also, the serial terminal window is very useful for debugging
purposes; however, having to rely on Tetra Term is very inconvenient and a much better solution would be to
have Eclipse connect to the serial port so everything can be done within Eclipse without the need of outside
tools.
AVR for Eclipse Eclipse is a very modern IDE with many features. With this AVR for Eclipse plug-in, the user is set free of the
hassle with makefiles. And with a simple click, it will download the HEX dump to the microcontroller.
2
Figure 1 - AVR for Eclipse
Eclipse Update Site: http://avr-eclipse.sourceforge.net/updatesite/.
RXTX and Target Management RXTX is a Java library, using a native implementation (via JNI), providing serial and parallel communication for
the Java Development Toolkit (JDK). Along with Target Management, which creates data models and
frameworks to configure and manage remote (mainframe down to embedded) systems, their connections, and
their services, allows the user to have a serial console terminal directly from within Eclipse.
3
Figure 2 - Terminal Within Eclipse
The advantage of having the ability to see serial port communication from within Eclipse is that it helps with
the development flow as well as debugging the software. After all, using the Serial.println() function from the
Arduino library remains the primary debugging method we employ in this project.
Eclipse Update Site: http://rxtx.qbang.org/eclipse/
Eclipse Update Site: http://download.eclipse.org/dsdp/tm/updates/3.0
Subversive The Subversive provides Subversion (SVN) integration for Eclipse. The Subversive plug-in gives the user the
ability to work with the SVN version control system from the Eclipse workbench. This is a must have for this
project because each member is responsible for different sections of the code so a source control system is
crucial.
4
Figure 3 - SVN Integration in Eclipse
Eclipse Update Site: http://community.polarion.com/projects/subversive/download/eclipse/2.0/update-site/
Test Scenario In order to show that we can use each component in an integrated scenario, we need to define some actions.
First, the components will be broken into a base unit and a remote unit. The sonar will be placed with the
remote unit for greatest realism with respect to our final project hovercraft scenario.
The base unit consists of a Seeeduino microcontroller that accepts the joystick input, including button presses.
On the output side, the I2C LED will be placed on the base unit. The remote unit consists of the second
Seeeduino, a motor output via the L298, a servo motor output through the L298, and the sonar input.
When the joystick (actually a flight controller) is turned to the right or left, the base unit Seeeduino will relay
those commands to the remote unit Seeeduino and the servo motor will move to the right or left. When the
lever on the flight controller is moved up the motor attached to the remote Seeeduino will move from stopped
up through full speed. The motor will have a fan blade attached so that this motion is apparent. When a button
on the flight controller is pressed, the LED onboard the remote Seeeduino will be lit and it will go out when the
button is released.
Normally, the I2C LED on the control unit is green. When an object (a hand, for instance) comes close to the
sonar sensor the LED will reduce the intensity of green.
5
Hardware Setup
Figure 4 - Overall Setup
On the left hand side is the base unit; on the right is the remote unit. The yellow battery on top of the flight
controller is driving the motors. The seeduinos at this point are driven from the USB port of the attached
laptops.
6
The Base Station
Figure 5 - Base Station
The base station setup is fairly simple. The red board is the Seeeduino. On the white breadboard is an I2C LED
and a radio which are wired to the Seeeduino. Also wired through the breadboard is the flight controller (the
black connector at the bottom of the image is the cable from the flight controller.)
Table 1 - Seeeduino Base Station Pin Assignment
Seedunio Pin Destination
Analog 5 Y-Axis
Analog 6 X-Axis
Analog 7 Button
19 Radio IRQ
42 Radio CSN
43 Radio CE
48 Radio Vcc
50 Radio MISO
51 Radio MOSI
52 Radio SCK
Gnd Radio Gnd
I2C Vcc LED Vcc
I2C SCL (21) C on the LED
20 D on the LED
7
The Remote Station
Figure 6 - Remote Station
The remote station setup is slightly more complicated. The yellow battery on top of the flight controller powers
the L298 motor controller and the servo motor. The aluminum heat sink next to the yellow battery is attached
to the L298 to prevent overheating. On the corner of the desk on the other side of the battery you can see the
fan attached to our motor. The white breadboard contains the sonar and the radio. Attached to the bottom
right of the breadboard are the connections between the Seeeduino, the power supply, and the servo motor.
Table 2 - Seeeduino Remote Station Pin Assignment
Seeeduino Pin Destination
7 Servo Data Line
11 Motor Forward
12 Motor Reverse
PE7 Sonar Receive
PE2 Sonar Transmit
19 Radio IRQ
42 Radio CSN
43 Radio CE
48 Radio Vcc
50 Radio MISO
51 Radio MOSI
52 Radio SCK
Gnd Radio Gnd
8
Project Components
Motor Wiring and Control
This section is concerned only with the standard DC motor that is driving the fan in this project; the servo
motor will be handled in a later section.
The Arduino libraries make simple motor control very easy. They hide all of the details of pulse width
modulation and allow one to simply do an analog write to the pin that is controlling the motor. The only thing
to make sure of, really, is that the pins you want to use the base library functions with are not attached to a
timer that you are using for something else. We had to move our motor output pins a couple of times as we
added the sonar and servo motor to the system so that we could keep our control systems appropriately
separated.
Here is a picture of the Solarbotics L298 motor controller (without the heat sink shown in the first picture in
this report):
Figure 7 - Solarbotics L298 Motor Controller
Since we were using only a single motor for this initial project, we used only one side of the L298. When we
add a second motor in the next phase of our hovercraft project we will use a similar control strategy, but will
wire it to the other side of the L298.
The three large screw connectors on the front of the board are for attaching power and ground, the centre of
the 3 connectors is a +5V regulated output for wiring to the control circuit for each side.
The three small header connectors on the front of the board are for wiring power (from the +5 volt regulated
supply) and two motor input connections from the Seeeduino (one for forward, one for reverse.)
The two screw connectors on the left side of the L298 are wired to the motor itself.
In our bouncing around trying to find a set of PWM pins whose timer we didn't want to use for other purposes,
we eventually ended up driving the motor from PWM pins 11 and 12 which are on timer 1. As we add more
devices in the next phase of our project we may end up moving these again since there is really no reason to be
tying up a 16-bit timer for motor PWM and we could as easily use the 8-bit timer.
9
Setup of the motor pins is very simple, assign a variable to hold the pin numbers (11 and 12) and then set the
pinMode to OUTPUT. Even this step is not strictly necessary since the PWM pins are set to output by default in
the Arduino init function, but since we were having problems with that function (to be described in the next
section) we've decided to explicitly set the pinMode for all the pins we use.
After setup, the values for the motor direction and speed are coming from the base station to the remote
station over the radio. Both the position of the flight controller axis controlling the motor and the axis
controlling the servo are sent as a string to the radio receive handler on the remote station, there they are
processed. The motor control code looks like this:
//set fan position
if (fanPos < 660) {
fanVal = map(fanPos, 120, 660, 0, 255);
d = 1;
fanVal = 255 - fanVal;
}
if (fanPos > 720) {
fanVal = map(fanPos, 720, 1023, 0, 255);
d = 2;
}
switch (d) {
case 1:
analogWrite(motor1Pin1, fanVal);
analogWrite(motor1Pin2, 0);
break;
case 2:
analogWrite(motor1Pin1, 0);
analogWrite(motor1Pin2, fanVal);
break;
default:
analogWrite(motor1Pin1, 0);
analogWrite(motor1Pin2, 0);
servoVal = 3000;
}
The mid-position of the forward/backward axis of the flight controller is centered on 690. Since these changes
slightly each time the controller is used, we elected to leave a space of 30 on either side of the center position
before beginning to operate the motor. The default value in the switch statement sets both channels to zero,
stopping the motor.
If the flight controller is pushed forward beyond this zone, values greater than 720, then map the values in that
range to a value between 0 and 255 and set the switch variable, d, to 2. If it's pulled back lower than 660 then
map that to a value between 0 and 255 and set the switch variable to 1. Note that pulled all the way back
should be the fastest reverse speed, so the fanVal is inverted (255-fanVal) to set this value properly. The switch
statement merely writes the appropriate speed value to the proper output pin to drive the motor.
Under the covers, what is going on here is that the Arduino libraries take care of the proper pulse width
modulation to proportionally drive the motor at a duty cycle related to the value of the output pin. All of this is
10
hidden from the end user of the Arduino libraries, but the mechanism will be discussed in the next section on
the servo motors.
Finally, the L298 is in the middle of the wiring from the Seeeduino to the motor so that sufficient power can be
delivered to the motor to drive it – the output pins do not have the current capacity to drive the motor
directly.
Servo Motor Control Arduino libraries exist to drive the servo motor, but we were not able to get them to function properly. In
addition, other groups had problems with the built in libraries being disrupted by use of the motor and sonar.
So we elected to abandon the built-in Arduino libraries and write the control code directly using timer 4.
First, since we've just discussed the related code in the rxhandler for motors, let's look at the rxhandler for the
servo:
if (servoPos < 300) {
servoVal = map(servoPos, 0, 300, 1800, 3000);
}
if (servoPos > 350) {
servoVal = map(servoPos, 350, 920, 3000, 4200);
}
if ((servoPos > 300) & (servoPos < 350)) {
servoVal = 3000;
}
servoSet(servoVal);
The flight controller we are using has a rather compressed and un-centered range for this axis. At rest it reads a
value of approximately 323. Similar to the rationale for the motor, we've left a bit of room on either side
before we begin driving the servo. The highest value of the flight controller is only 920, so that's what we've
used to map the position, in this case, to a value between 1800 and 4200. As we'll see in a few moments, this
corresponds to a range of .9 ms to 2.1 ms which the servo datasheet lists as the rated range of our servo.
ServoSet is a function in servo.cpp that sets the register value for the timer.
Let's look at the code for setting up and controlling the servo motor:
static unsigned int servoValue = 2397;
static unsigned char sreg;
void servoInit() {
/*
* set non-inverted PCM mode (COM4B1 set, COM4B0 unset),
* mode 14 (fast PWM, TOP in ICR4)
* set prescale to 64 - CS 40, 41 set, 42 unset
*/
TCCR4A = 0b100010; // COM4B1, COM4B0, COM4C1, COM4C0, WGM41, and WGM40
TCCR4B = 0b11010; // WGM43, WGM42, CS42, CS41, and CS40
/*
* initialize the servo pin as an output:
11
* pinMode(ServoPin, OUTPUT);
*/
DDRH |= _BV(DDH4);
/*
* Set top to 40000 (which will be 20ms with the prescaler)
*/
sreg = SREG;
cli();
ICR4 = 40000;
SREG = sreg;
/*
* the time it should be up for no movement is 3054, this
* is just an initial value. it gets set to servoValue the
* first time the interrupt is hit.
*/
sreg = SREG;
cli();
OCR4B = (unsigned int) 1500;
SREG = sreg;
return;
}
void servoSet(unsigned int value) {
servoValue = value;
sreg = SREG;
cli();
OCR4B = servoValue;
SREG = sreg;
Serial.print(servoValue);
Serial.println();
return;
}
Let's look at the setup of the timer. Chapter 17 of the datasheet is the bible for understanding what is going on
here. First, let's look at our waveform generation bits, WGM40, WGM41, WGM42, WGM43. The table to refer
to is on page 148 of the datasheet. We want “fast-PWM” mode. This means that we want a pulse width
modulation mode, and we don't care too much about the shape of the waveform. The other PWM modes try
to control the phase and frequency of the PWM pulses in ways that we don't really need. In addition, since we
want to control the overall clock frequency we want to set our own TOP value. This can be done either by
setting the OCR4A register or the ICR4 register. Since we may want to control more than two servos at some
point in the future, I've chosen to store this TOP value in ICR4. This means that we want to set mode 14, or
WGM41, WGM42, and WGM43 set, and WGM40 unset.
Next, let's take care of the directionality of the pulse. We want to be normally low, with the pin set high for the
first short part of the cycle. This is non-inverted mode, so we set COM4B1 to allow the pulse to be output on
the pin, and unset COM4B0 so that we do not invert the signal. (See page 160 of the datasheet.)
Next, let's set the prescale value. If we operate unscaled our clock frequency is 16MHz, but since we want a
frequency of 50Hz on our output pin (equivalent to a 20ms pulse frequency as specified by our servo
12
datasheet), we could not store a large enough value in a 16-bit register to accomplish that at the full clock
speed. The smallest prescale value we can use to accomplish this is 8, so we set bit CS41 and unset CS40 and
CS42. This comes from the table on page 162 of the ATMEL1280 datasheet.
After setting the control register values, set the data direction register to output. This can be done either
through the Arduino libraries by setting the pinMode to OUTPUT, or directly on the DDRH register by setting
the DDH4 bit using the command DDRH |= _BV(DDH4). (Just to decipher some of the pin naming – we are
driving our servo motor from PWM pin 7, which corresponds to pin PH4 on the Seeeduino schematic. This
happens to be on port H, so we are setting the DDH4 bit of the port H data direction register.)
It's come time to start populating our data registers. It's worth noting that the ordering of these operations is
critical. The Arduino libraries, in the init() function, set the timer4 control registers to 8-bit fast PWM mode.
This masks out the high byte of the data registers. There is a section of the datasheet that goes into great detail
on how to set the contents of a 16-bit register using the 8-bit data bus, and I initially thought that we were
falling afoul of this somehow. In actuality the AVR compiler takes care of the complexities of setting the
register, and the high byte was simply being masked by the Arduino setup of the register. In summary, do all of
your controls register setup BEFORE you attempt to write to any of your data registers. Saving the status
register and calling cli(); and then restoring the status register after populating the data register is a bit of
paranoia left over from the investigation of this ordering problem. But it's probably good practice to do in any
case. If an interrupt comes in the middle of a set of the 16 bit register (since it takes two operations on the 8-
bit data bus) then it's theoretically possible that the register will get set to an improper value. The cli(); call
suspends interrupt processing until we are done setting the register.
The servo datasheet specifies an interval of 20msecs between pulses, and the width of the pulse determines
the position of the servo motor. The width is to be .9 ms to 2.1 ms with the servo position centered at 1.5 ms.
An interval of 20 ms corresponds to a frequency of 50Hz. From the Atmel 1280 datasheet, we find the
following formula for determining the frequency of a motor control signal:
𝐹𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦𝑃𝑊𝑀 =𝑓𝐶𝑙𝑜𝑐𝑘 𝐼/𝑂
𝑁 × (1 + 𝑇𝑂𝑃)
The clock I/O is 16 MHz, N is the pre-scaler value (8 in our case), and TOP is the value of the TOP register (ICR4
in our case). Rearranging this we find that a TOP value of 40000 will give us a PWM frequency of 50Hz, so we
set our ICR4 register to 40000.
Next, we want to set an output compare register to a value that will leave the servo in the neutral position.
This has been previously specified as 1.5 ms. Since we can see from the previous calculation that one counter
increment corresponds to 2usecs, this means that we want to set out output compare to 3000 to maintain a
neutral position at the centre of travel. This value is stored in OCR4B.
So at this point, we have a pulse with a width of 1.5msecs being generated every 20msecs. This appears as
follows on the logic analyzer:
13
Figure 8 - Neutral Pulse Width
Going back to our mapping functions, we can see the minimum and maximum positions, at .9msec and 2.1
msec respectively, should be found at each extreme of the flight controller travel. This can be verified on the
logic analyzer as shown in the following images:
14
Figure 9 - Minimum Servo Pulse Width
Figure 10 - Maximum Servo Pulse Width
Before using the logic analyzer to confirm pulse width values, considerable difficulties were caused by
improperly setting the output compare register. If the servo motor is driven with a pulse width outside of it's
15
rated range, the small plastic gears can easily strip. On the smaller HS55 servo motors, the electronics can also
become overdriven and be damaged. In fact, some careful examination of 3 failed HS55 servo motors showed
that all three had electrical issues and two of the three had stripped gears. On the larger HS81, the electronics
seem more prone to survive, but the gears still strip very easily. New gear sets are available for the HS81 from
Robotshop.ca for $5.45 per set under part number RB-Tig-01. (Please note that not all of the damaged servos I
examined were damaged by me, although I do take full responsibility for one of the HS55 servos and one of the
HS81 servos. Three new gear sets for the HS81 are on the way...)
Use of the logic analyzer to examine pulse widths is consequently highly encouraged before connecting your
servos. Setting it up is fairly simple – load the control program from the website listed on the front of the
analyzer, plug it into a USB port, connect the grey wire to a ground pin on the Arduino, the black wire to the
servo output pin, and start sampling. Taking 1 million samples at a 1 MHz rate seemed to work well in our case.
One final issue that is worth mentioning is the timing code here it is possible to achieve timing to within 2
microseconds. The servo motor actually uses larger time slices than that, it appears, and a variation of 1-2
microseconds in timing of a pulse is able to push the servo over an internal boundary which causes jitter. This 1
microsecond difference is visible in the logic analyzer if you zoom in. Initially, the center value was set as 3054
counter ticks, a leftover value from the old servo which was not centered quite right. At this value the new
servo had quite a pronounced jitter as the logic analyzer showed variation from 1524 microseconds to 1525
and rarely 1526 microseconds. This 1524 to 1525 microsecond boundary was apparently the edge of a more
pronounced movement within the servo motor and when the center value was reset to 3000 counter ticks the
center-position-jitter went away entirely.
nRF24L01+ Radio Transceiver There are three aspects to the nRF24L01 radio that deserve discussion. The first, and simplest, is the wiring of
the radio. Although it is the simplest part of getting the radio working, there are still several problems that can
ensue. Second, we'll briefly discuss the radio driver that Neil, the TA, has written. There are a couple of things
in the driver code that need changing if you change the pins that the radio sits on. Finally, the radio client will
be discussed.
The radio is wired to the Seeeduino SPI pins (50-52), these are hardwired to the Arduino's SPI module and
should generally not be changed. The other pins are configurable in order to avoid conflicts with other devices,
though. The following table shows the connections as wired in our implementation:
Table 3 - Radio Transceiver Connections
Seeeduino Pins Radio Module
19 Radio IRQ
42 Radio CSN
43 Radio CE
48 Radio Vcc
50 Radio MISO
51 Radio MOSI
52 Radio SCK
Gnd Radio Gnd
The CSN pin was originally wired to pin 53 in the reference implementation on the AT90, but Neil moved it to
pin 7 in his driver. We've chosen to move it to pin 42, mostly for ease of wiring, but also to avoid conflicting
16
with the servo code previously described. Similarly the CE connection has been moved to pin 43 so as not to
conflict with the PWM outputs.
An interesting problem occurred with the CE pin on the base station radio. We were getting thirty or forty
packets through the radio and then it would inexplicably die. The number of packets was variable, but the
problem always occurred within a minute or two of starting the base station. We finally determined that the CE
pin was not well wired and the intermittent connection was causing the radio to go into a state which was only
recoverable by resetting it.
Another pin that caused wiring difficulties was the IRQ. The main problem here is the renaming of pins that
takes place between the Arduino documentation and the Atmel1280 datasheet. The Arduino naming really
makes very little sense, but on some earlier version of the board the interrupts were probably named
sequentially. The first two interrupts (0 and 1) are pretty easy to find on pins 2 and 3, but the remainders are
renumbered as described on the attach interrupt page of the Arduino documentation: numbers 2 (pin 21), 3
(pin 20), 4 (pin 19), and 5 (pin 18).
We've chosen to put the radio on interrupt 4, pin 19. The Atmel 1280 documentation and seeeduino schematic
all refer to int4 as being on pin PE4, or PWM2, but Arduino has renumbered this to be int0. If you use the
Arduino library attach interrupt function to attach the interrupt you must use their numbering scheme! If the
interrupt is not wired properly then it will hang during the radio init code on the attach interrupt call. This
wiring took some work to figure out initially.
Finally, the Vcc connection for the radio has been placed onto digital pin 48. This allows the radio to be reset
through software at the start of setup. This is to enable the radio to be reset at the same time as the software
is reset in case the radio has found itself in an unstable state and stopped working. (Note the highest current
draw for the radio is listed as 12.3 mA which is far less than the 40mA maximum current per pin allowable in
the Atmel1280 datasheet, although the working load lists 20mA as the recommended maximum.)
To make these changes, there are a couple of places in radio.cpp in the radio driver code that must be
changed. First, the pins except for the interrupt are defined at the top of radio.cpp:
#define CE_PIN 43
#define CSN_PIN 42
#define VCC_PIN 48
To change the IRQ, you must go change the hard-coded value in Radio_Init:
// Enable radio interrupt. This interrupt is triggered when data are
received and when a transmission completes.
/*DDRE &= ~_BV(PORTE7);
EICRB |= _BV(ISC71);
EICRB &= ~_BV(ISC70);
EIMSK |= _BV(INT7);*/
attachInterrupt(4, int0handler, LOW);
Notice Neil's use of Arduino library calls instead of the more cryptic direct register sets that are there for the
AT90.
17
Finally, in the transmit code that we started from there was a line in the IRQhandler at the bottom that must
be uncommented:
if (status & _BV(RX_DR)) {
pipe_number = (status & 0xE) >> 1;
radio_rxhandler(pipe_number);
}
The radio_rxhandler line was commented out and must be called in order to receive any packets.
If you feel compulsive you can change the name of the IRQ0handler as well to reflect the new IRQ you put the
radio on, but we didn't bother. Also, if there are many radios operating nearby changing the channel the radio
is operating on could help alleviate conflicts, but we didn't see a need to do this yet.
Finally we'll talk about the client code. This is all in radioclient.cpp, in either the base station project or the
remote project. Aside from the message receive handler, the only difference between the two is which is
identified as the rx_addr and which the tx_addr. These should be complements of each other, of course, so
that the radios talk to each other and not some other station. Although the use of Neil's base station during
testing was quite useful.
uint8_t rx_addr[RADIO_ADDRESS_LENGTH] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x88 };
uint8_t tx_addr[RADIO_ADDRESS_LENGTH] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x77 };
As long as the radios are set up to use the same channel, power, and transmit rate, the only code that will
require much attention is the receive message handler. The transmit code is used from Neil's sample virtually
unchanged, and we keep the same packet definition so that all of our messages are passed as strings of up to
20 bytes. One additional thing that we have done is to set messages originating from the base station as
message type 41, and ones from the remote station as message type 42. This allows us to avoid doing any
processing of the automatic reply messages.
On the remote side, the following code identifies a message as coming from the base station and breaks it's
string into two int variables containing the state of the two flight controller axes:
Radio_Receive(&packet);
if (packet.type == MESSAGE && packet.payload.message.messageid == 41) {
sscanf((char*) &packet.payload.message.messagecontent, "%d/%d",
&servoPos, &fanPos);
}
The explicit cast to a pointer to a char helps the system interpret the uint8_t data type of the packet data, and
the two numbers are placed into the appropriate variables. From this point on the code in the rxhandler has
already been discussed in the sections for the motor and servo.
It is worth noting that the Radio_Receive call MUST be made, even if you do not intend to do anything with the
data. If the packet is not picked up then after a few received packets (usually 3) the radio will be unable to
receive anything else until its buffer is cleared by picking up the data already received.
Joystick Our joystick is a Virtual Pilot made by CH Products. This joystick differs from most others in that it has 3 axes
instead of 2. The black cylinder sliding in and out constitutes one axis, the “steering wheel” rotating clockwise
18
and counter-clockwise is the second axis, while the black throttle is the third. These axes correspond to pins 6,
3, and 11 respectively as seen in Illustration 8. We chose to use the red button on the right, using pin 2, to turn
on the LED on the Seeeduino board when held down. The x-axis (pin 3) is used to control the servo motor
position, while the y-axis (pin 6) is used to control the fan speed and direction.
The joystick pins are connected to the analog inputs of the Seeeduino board, which send the board between 0
and 5 volts depending on the positioning of the axes. These values can then be accessed by calling the
analogRead function. The board will then convert the analog voltage signal to a digital signal by mapping the
voltage to a number between 0 and 1023. However, due to a problem with our joystick our x-axis only registers
values up to 920. We found that while the buttons could be used as a digital input, it was easier to use the pins
as analog inputs. This way whenever a button is pressed down, the analogRead function will return 0, and
when the button is not pressed down the value could have a wide range from 10-700. This avoids the bouncing
problem because low voltage drops will not be read in as a low instead of as a high after the button has been
released.
There were a couple of minor problems concerning the setup of the joystick. First of all, the connector had
most of its wires missing when we got it, so we had to solder the connectors on. The next challenge was
determining which axes belonged to which pins. This was determined by using the Serial interface and printing
out the values we received from the pins when we moved the axes on the joystick. We then had to calibrate
the joystick to find the center of the axes. The values of the axes at their default positions were determined,
and then the minimum and maximum values of the axes were determined.
However, we had a setback when we started getting erratic results from the joystick. This was observed from
the fact that the motors were stuttering when they would normally not have been. After verifying all of our
hardware connections, we determined that the problem was inside of the joystick itself. Therefore, we opened
it up and found that there were faulty connections inside of the joystick. We repaired the fault and everything
went back to working order.
19
Figure 11 - Joystick Pins
20
MaxSonar-EZ1 Sonar Sensor
Figure 12 - MaxSonar-EZ1 Sonar Sensor
The Hardware Basics
Because the primary purpose of this project is to get familiar with all the hardware components, a sonar sensor
is a must have. The MaxSonar-EZ1 offers very short to long-range detection and ranging, in an incredibly small
package, with ultra low power consumption. The MaxSonar-EZ1 detects objects from 0-inches, even objects
pressing against the front sensor face, to 254-inches (6.45 meters), and provides sonar range information from
6-inches to 254 inches, with 1-inch resolution. Objects between 0-inches and 6-inches range as 6-inches.
Obtaining the Sensor Data
This sonar sensor offers several ways to return the range value, such as Pulse Width train, analog output or
plain ASCII text. In the first project, pulse width is chosen as the method of getting the sensor data because it
offers very high reliability whereas text parsing is not as easy and A/D conversion is not as accurate.
In order to measure the pulse width, a hardware timer is needed. The microprocessor on the Seeeduino board
comes with several hardware timers of varying size, either 8-bit or 16-bit. For this project, hardware timer 3
was chosen because Timer 1 is used by the Pulse Width Modulation library functions from Arduino and Timer 2
is only 8-bit which is not large enough to hold the range value without overflow. Several macros are made to
make setting up the Timer a little easier; they are shown here:
/*
* Macros to change Timer 3 settings when used for
* input capture and measure the pulse width of
* the PW pin from the sonar
*/
#define SET_RISING_EDGE() (TCCR3B |= _BV(ICES3))
#define SET_FALLING_EDGE() (TCCR3B &= ~(_BV(ICES3)))
#define IS_RISING_EDGE() (TCCR3B & _BV(ICES3))
#define IS_FALLING_EDGE() ~(TCCR3B & _BV(ICES3))
#define SET_IC_ENABLE() (TIMSK3 |= _BV(ICIE3))
#define SET_IC_DISABLE() (TIMSK3 &= ~_BV(ICIE3))
#define CLEAR_IC_FLAG() (TIFR3 |= _BV(ICF3))
21
How It Works
When the RX pin on the sonar sensor is pulled HIGH for 20 us, a sound wave pulse is fired by the sonar and the
PW pin will go high and then it will go low once the echo is received, which gives an accurate representation of
the distance that is proportional to the width of the pulse on the PW pin with 147 us for every one inch of
distance.
The key in this implementation is to measure both the rising edge and the falling edge of the pulse on the PW
pin accurately. This is achieved by using the Input Capture Unit that comes with the hardware Timer 3 on the
microprocessor.
The Input Capture Unit is capable of monitoring specific input pins for either a rising edge or a falling edge and
it will copy the value of Timer 3 into its own register once the appropriate edge is detected.
As a result, the ISR in the code must be able to monitor the rising edge first, and change the register settings to
look for the falling edge once the rising edge on PW pin is detected.
The Code Structure
The sonar handling code is divided into 2 main functions and an ISR. The sonarInit() function initializes the
sonar such as setting the pins to either INPUT or OUTPUT and wait for 250 ms for the sonar to power up and do
the initial calibration. After that is all done, drive the RX pin low so the sonar does not continuously sending out
sound wave pulses.
static int SonarRX = 4; // Sonar RX pin is connected to pin 4, Pin4 is PE2
static int SonarPW = 9; // Sonar PW pin is connected to pin 9, ICP3 is on PE7
void sonarInit() {
// initialize the pins as inputs:
pinMode(SonarRX, OUTPUT);
pinMode(SonarPW, INPUT);
/*
* Assuming sonarInit() gets called immediately after
* power up, then a period of 250 ms must be passed
* before the RX pin is ready to receive command.
*/
delay(250);
//Disable sonar echo firing when it is first initialized
digitalWrite(SonarRX, LOW);
return;
}
The sonarEcho() function is very simple because it is only responsible of setting up the Input Capture Unit to
look for a rising edge and then driving the RX pin HIGH.
/**
* Set Input Capture to look for a rising edge, clear
* the interrupt flag and then enable Input Capture.
* After that, set RX to HIGH to enable the sonar.
*/
void sonarEcho() {
SET_RISING_EDGE();
22
CLEAR_IC_FLAG();
SET_IC_ENABLE();
//Enable Sonar
digitalWrite(SonarRX, HIGH);
return;
}
Note that the RX pin only needs to stay high for 20 us for the sonar to fire a sound wave pulse but here the RX
pin set to HIGH and never brought back down to LOW. This is because the ISR is actually responsible of setting
the RX to LOW.
ISR(TIMER3_CAPT_vect)
{
// Disable global interrupt
Disable_Interrupt();
/*
* Once the rising edge of PW is detected, it means
* RX has been staying HIGH long enough. Set it to
* LOW now to disable sonar.
*/
digitalWrite(SonarRX, LOW);
/*
* Reset Timer 3 when the rising edge of PW is
* detected, then change the Input Capture configuration
* to detect the falling edge and clear the interrupt flag.
*/
if (IS_RISING_EDGE()) {
TCNT3 = 0;
SET_FALLING_EDGE();
CLEAR_IC_FLAG();
} else {
/*
* Store the ICR3 value and disable Input Capture
* so it does not interfere with other components.
*/
timerTickCount = ICR3;
SET_RISING_EDGE();
CLEAR_IC_FLAG();
SET_IC_DISABLE();
}
// Enable global interrupt
Enable_Interrupt();
}
Inside this Interrupt Service Routine, the first thing that happens is that interrupts are disabled globally. That is
because the timing measurements must be very accurate so during this period of time, nothing else should be
able to halt the execution of the code. Because the Input Capture Unit is originally set to monitor a rising edge,
it is required to clear the Timer 3 and set the Input Capture Unit to look for a falling edge the next time around.
It is also important to clear the Input Capture flag too. When this ISR gets triggered again, by the falling edge,
the value stored in the ICR3 register is the value of the hardware Timer 3 when the falling edge is detected. It is
also important to note that the global interrupt must be re-enabled before leaving this ISR.
23
Problems and Solutions
The sonar sensor is easy to use. There is only one problem that caused some confusion when calibrating and
testing the sonar sensor. The sensor is capable of measuring a maximum distance of 256 inches and the
hardware Timer 3 is a 16-bit timer with different clock frequency prescalers such as 8 and 64. Initially, the
prescaler is set at 8 because the microprocessor has a 16MHz crystal. With an 8 prescaler, each timer tick
represents a nice and easy 0.5 us. However, this is not enough because each inch of distance is represented by
a 147 us pulse and in order to measure 256 inches, the timer must be able to handle a pulse of 37632 us, times
that by 2 gives 75264, a number that is just slightly larger than the maximum value that can be represented by
a 16-bit counter, which is 65536.
In order to accommodate this, the clock frequency prescaler of the hardware Timer 3 is changed to 64. This
means that each timer tick is now 4 us. Given that one inch is 147 us, it results in a 147 / 4 = 36.75 us of pulse
width for each tick.
BlinkM RGB LED
Figure 13 - BlinkM RGB LED
The Hardware Basics
BlinkM is a networkable and programmable full-color RGB LED. It is designed to allow the easy addition of
dynamic indicators, displays, and lighting to existing or new projects. BlinkM uses a high quality, high power
RGB LED and a small AVR microcontroller to allow a user to digitally control an RGB LED over a simple I2C
interface.
How It Works
The BlinkM RGB LED is much easier to use than the sonar because it comes with an AVR microcontroller and a
ready to use library of functions. In a sense, Arduino library really makes embedded programming on AVR
microcontrollers a much easier thing to do, but also not good for students to really learn the protocols since
the library functions mask the implementation details.
The BlinkM RGB LED uses an I2C interface to allow the user to control it. It is capable of accepting RGB color
range from 0 to 255 as well as HSB settings from 0 to 255. Since all the library functions are all readily available,
it is only necessary to create the appropriate functions to set the address of the BlinkM LED over the I2C BUS as
well as functions to change its color and fade speed. The fade speed controls how fast the LED changes its
current color to the new color once it receives the command.
24
The Code Structure /*
* First transmit to the broadcast address, then
* set the device with the given address.
*/
void LEDSetAddress(uint8_t address) {
Wire.beginTransmission(0x00);
Wire.send('A');
Wire.send(address);
Wire.send(0xD0);
Wire.send(0x0D);
Wire.send(address);
Wire.endTransmission();
delay(50);
return;
}
Before setting up an address for the device, the message needs to be broadcasted to the default address of 0,
which is an address that every device on the I2C BUS listens. An ASCII ‘A’ indicates that this is an address
assignment command followed by the actual address the device is assigned to. The 0xD0 and 0x0D arguments
are used as a check against inadvertent address changing. They are only used when first assigning the device an
address on the I2C BUS.
/*
* Set the RGB color of the LED at the given address. It also sets
* the color transition type and whether or not to hold the color
* since if that bit is not set, the LED will reset to default after
* a short period of time.
*/
void LEDSetColor(uint8_t address, uint8_t red, uint8_t green, uint8_t blue,
bool fade, bool holdColor) {
Wire.beginTransmission(address);
if (fade) {
Wire.send('c');
} else {
Wire.send('n');
}
Wire.send(red);
Wire.send(green);
Wire.send(blue);
if (holdColor) {
Wire.send('o');
}
Wire.endTransmission();
delay(50);
return;
}
This LEDSetColor() function takes parameters such as holdColor and fade on top of the address and RGB values. The fade boolean value indicates whether to change to the new color immediately upon receiving the command, or fade to the new color according to the interally set fade speed. Because the BlinkM LED is run by an AVR microcontroller, it will reset to its default behavior after running for an extended period of time. The Wire.send('o') command is used to stop the interal script so the color would stay the same indefinitely. Lastly, a function is created to set the fade speed of the BlinkM LED.
25
/*
* Set the speed of fading between colors.
* Higher numbers mean fading faster.
* 255 = instant fading
*/
void LEDSetFadeSpeed(uint8_t address, uint8_t fadeSpeed) {
Wire.beginTransmission(address);
Wire.send('f');
Wire.send(fadeSpeed);
Wire.endTransmission();
}
There many other commands available but for the purpose of this project, these three functions are enough.
26
Reference "ATMega 1280 Datasheet." Atmel. 01 Feburary 2010
<http://www.atmel.com/dyn/resources/prod_documents/doc2549.pdf>.
"BlinkM Datasheet." ThingM. 01 Feburary 2010
<http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf>.
"Compact Motor Driver Kit." Solarbotics. 01 Feburary 2010
<http://www.solarbotics.com/assets/datasheets/solarbotics_l298_compact_motor_driver_kit.pdf>.
"Language Reference." Arduino. 01 Feburary 2010 <http://arduino.cc/en/Reference/HomePage>.
"MaxSonar-EZ1 Datasheet." MaxBotix Inc. 01 Feburary 2010 <http://www.maxbotix.com/uploads/LV-
MaxSonar-EZ1-Datasheet.pdf>.
"nRF24L01+ Product Specification." Nordic Semiconductor. 01 Feburary 2010
<http://www.nordicsemi.com/files/Product/data_sheet/nRF24L01P_Product_Specification_1_0.pdf>.