EE 109 - Spring 2024 Introduction to Embedded Systems

Spring 2024 Project

Speed Trap

Introduction

This semester’s class project is to build a device that measures how fast an object is moving as it passes two sensors and display the speed on a dial-type indicator. It also relays that speed to a remote device.

Note: This is a non-trivial project that requires you to not only use the concepts and skills you’ve learned from past labs but to extend that knowledge and use the higher-level understanding of how embedded systems and microcontrollers work to include use of new components and modules.

Getting a full score is not a guarantee. It will require time and intelligent work (not just guess, but verifying on paper and using some of the approaches outlined below). But it is definitely doable. We have broken the project into 3 checkpoints (1 due each week) to help limit the scope of your work to more manageable deliverables. Also, note that while we will still provide help in terms of concept and approach, we will leave it to you to do most of the debugging. When you do need help debugging you should have already used the tools at your disposal: the DMM or scope to verify basic signals are as you expect, LCD print statements to see where your code is executing, whether you are getting into an ISR at all, or what the value of various readings/variables is. Test your code little by little (this is the reason we do checkpoints) and do not implement EVERYTHING and only then try to compile. In addition, when something doesn’t work, back out any changes you’ve made (comment certain portions, etc.) to get to a point where you can indicate what does work and then slowly reintroduce the new changes to see where an fissue might lie. If you have not done your own debugging work and just ask for help, we will direct you to go back and perform some of these tasks.

To start, please read the overall project a few times before you even consider thinking about to many details. Get the big picture, hear the terms we use, look for the requirements and/or limitations we specify. Only then consider starting on your project.

For a copy of the project grading sheet, click here

Speed Trap Overview

In its simplest form the speed trap measures how long it takes for an object to pass two sensors and calculates the speed it was travelling. The speed in cm/sec is shown on the LCD display and also on an indicator dial similar to an analog speedometer used on many cars where a pointer rotates to point to the speed. A block diagram of our speed trap is shown below and it will have the following features.

Image

Operation of the Speed Trap

The following is a description of the operation of the speed trap.

Getting Started

We suggest starting with a copy of your code from Lab 8 since this project uses the rotary encoder and PWM signals. Download from the class web site the file project.zip that contains the Project_Answers.txt file with information about what needs to be demonstrated to show the project is working.

Most of the components used in this project have been used in previous labs, and your C code from the other labs can be reused if that helps. The project involves the use of three new elements:

All three are described below.

Which Port Bits to Use

Since the CPs grading the project may wish to try running the code on their project board, it is very important (and required) that all students use the port connections shown below to connect their buttons, rotary encoder, LEDs, etc. If other port bits are used the project will not operate when the program is run on another board.

Port BitFunction
PORTB, bit 3 (PB3)PWM signal to servo motor
PORTB, bit 4 (PB4)Tri-state buffer enable
PORTB, bit 5 (PB5)LED for indicating timing in progress
PORTC, bit 1 (PC1)Buzzer
PORTC, bit 2 (PC2)RGB LED's red segment
PORTC, bit 3 (PC3)RGB LED's blue segment
PORTC, bit 4 (PC4)Start sensor
PORTC, bit 5 (PC5)Stop sensor
PORTD, bit 0 (PD0)RS-232 Serial Rx input
PORTD, bit 1 (PD1)RS-232 Serial Tx output
PORTD, bit 2 (PD2)Rotary encoder
PORTD, bit 3 (PD3)Rotary encoder

Hardware Construction Tips

The buttons, LEDs, rotary encoder, buzzer and phototransistors, and the various resistors should all be mounted on your breadboard. It’s strongly recommended that you try to wire them in a clean and orderly fashion. Don’t use long wires that loop all over the place to make connections. You will have about 12 wires going from the Arduino to the breadboard so don’t make matters worse by having a rat’s nest of other wires running around on the breadboard. Feel free to cut off the leads of the LEDs and resistors so they fit down close to the board when installed.

Make use of the bus strips along each side of the breadboard for your ground and +5V connections. Use the red for power, blue for ground. There should only be one wire for ground and one for +5V coming from your Arduino to the breadboard. All the components that need ground and/or +5V connections on the breadboard should make connections to the bus strips, not wired back to the Arduino.

Object Detector

The two detectors are made from two white LEDs and two phototransistors. The LEDs and phototransistors are positioned so the light from the LED is illuminating the phototransistor unless some object blocks the path as shown below. The LEDs don’t have to be controlled by the Arduino and can be wired to be on all the time. The phototransistors should each be connected to a digital input port bit and monitored using the pin change interrupts similar to how you handle the rotary encoder inputs. When the phototransistor can “see” the light from the LED the digital input will be a one. When an object blocks the light path, the input will go to a zero.

Image

The LEDs should be installed on one side of the center channel of the breadboard, and the phototransistors opposite them on the other side of the channel. For the LED the longer lead (on side of the case that is rounded) should go to the positive voltage. For the phototransistor, the shorter lead should go to the positive voltage. The curved top of the LEDs should be pointing at the phototransistors, and their curved tops should be pointing at the LEDS. Both components should be positioned so that some small object about the size of a credit card can be slid along the channel past both sensors without striking them.

Image

The distance between the two sensors will be used in the speed calculation. You can put them wherever you want on the board depending on where your other components are, but the sensors should be at least 1.5 inches apart.

Test It!!!

When writing the software to implement the object detector, it’s a big waste of time to do any software debugging until you are certain the hardware providing the input signal is working correctly. We strongly recommend testing the output of the phototransistor using an oscilloscope to make sure it changes when an object moves between it and the LED.

The scope probe can be connected to the phototransistor where it shows in the above diagram to connect it to the Arduino. With the LED on and illuminating the phototransistor the scope should show about 5V. If you block the light from hitting the phototransistor, the scope trace should drop to close to zero volts. The test can also be made using the DMM.

Measuring Time and Speed

For measuring the time between when the start sensor is blocked and when the stop sensor is blocked, it is required that you use the 16-bit TIMER1 in the manner described here. The TIMER1 prescalar should be selected that allows the timer to count for over 4 seconds before reaching its maximum 16-bit value. When the start sensor is actuated, clear the timer’s count register (TCNT1) to zero and start the timer so it is counting. When the stop sensor is actuated, stop the timer and the 16-bit value of the counter can be read from the TCNT1 register. Remember that the counting action of the timer can be turned on and off by setting or clearing the prescalar bits. Hint: The sensing of the start and stop inputs, turning TIMER1 on and off, and retrieving the count value can all be done in the Pin Change Interrupt ISR for the two sensors.

Since the count value, and the frequency that the counter was counting at are now both known, your program can calculate the time that elapsed between the start and stop events. It’s recommended that this time be calculated in milliseconds since the time value should be displayed in that form. Once the time is known, use the distance between the start and stop sensors to calculate the speed the object was moving. The speed should be calculated in mm/sec since this value will be used in the serial link to another speed trap.

WARNING: The above calculations can be a bit tricky. All the calculations in the program must be done in fixed-point arithmetic. No floating point operations are allowed. When doing the calculations, always be aware of the size of the numbers the program will be working with and use large enough variables to avoid overflows.

HINT Work out the calculation that needs to be done by hand before writing it as code in the program. See if there are terms in the numerator and denominator that cancel out and simplify the calculation. Always keep in mind the maximum values that a variable can contain. If you are multiplying two numbers together, can the result exceed the maximum value the variable can hold?

Terminating a Timing Operation

Your project needs to have a way to terminate the timing operation if it is taking too long. This might occur if the start sensor is activated but the stop sensor never is. This can done by using TIMER1 which is counting up during the timing event. TIMER1 should be configured to invoke its ISR after 4 seconds has gone by.

Note that for a successful timing event, the 4 second limit will never be reached so the ISR will never be run. The ISR will only run if the stop sensor is never activated and the 4 second limit is reached. The ISR routine can terminate the timing and set a flag to indicate that the main program should print a message on the LCD stating that the timing expired.

Timing Indicator

The LED on PORTB, bit 5 is used to indicate that a timing event is in progress. Your code should turn this LED on when the “start” sensor is blocked, and then turn it off after the “stop” sensor is blocked. It should also go off if the timing event has been terminated due to it going past the 4 second limit.

Speed Threshold

The rotary encoder is used to set a speed threshold from 1 cm/sec to 99 cm/sec as an integer value. As the user rotates the knob the speed threshold should be shown on the LCD display. The software must ensure that the setting is never adjusted to be outside those limits. If the user adjusts the speed to 99 and continues to rotate the knob, the speed should stay at 99. The same thing should happen if the speed is adjusted to 1, it should stay at 1 even if the knob is rotated. Whenever a new threshold value is set, it is stored in the non-volatile EEPROM (see below).

The speed threshold value and the results of a speed measurement are compared to determine whether or not to sound an alarm using the buzzer. If the speed measured is greater than the speed threshold the alarm should sound as described below. When doing the comparison, only the integer part of the speed need be used, the fractional part can be ignored.

Buzzer

In Lab 7 you worked with producing tones of different frequencies from the buzzer. Those tones were done with code that used delays of half the desired output period between operations to make the output signal go high or low. The result was a squarewave signal at the desired frequency.

The problem with this method is that the program is locked into the delay routines while they measure out the selected delay time. A better way to create the tones is by using a timer to generate interrupts at the desired rate. Timer/Counter0, an 8-bit timer, should be used to generate a one half second tone of whatever frequency you choose. When the program wants to sound the buzzer it can start the timer running. Each time an interrupt occurs the ISR changes the state of the output bit driving the buzzer. After the required number of transitions have occured, the ISR can shut the timer off to end the output. Do not use the delay functions to drive the buzzer.

Servo Motor Indicator Dial

Besides displaying the speed on the LCD, the speed is also shown on a round dial by using a servo motor to rotate an indicator to point to the speed. The servo is controlled by Timer/Counter2 since this is the only timer with an output signal that is not blocked by the LCD panel. Servos normally operate with PWM signals with a period of 20ms, but with Timer2 the longest period that can be achieved is 16.4ms and the servos will work properly with this period PWM signal.

The width of the PWM pulse must vary between 0.75ms and 2.25ms. Assuming Timer2 is using the largest prescalar so as to have the maximum period, use that to determine the values that must go in the OCR2A register for the minimum pulse width of 0.75ms, and the value for the maximum pulse width of 2.25ms. Note: The calculation of these numbers was done in review question 2 of Lab 8.

Once you have determined the two OCR2A values for the minimum and maximum pulse width, you can use those to develop an equation to map the speed values to servo positions. The number that goes in the OCR2A register has a linear relationship with the speed value. Linear equations are based on two points, and you have the two points:

Using these two points, determine a linear equation for mapping the speed values to the OCR2A values. Once you have figured out the equation, write code to implement it, but remember to only use fixed point variables and operations. Do not use floating point operations or variables. Make sure the calculations do not result in overflows of the fixed point variables.

Every time the speed to be displayed changes, a new value for OCR2A should be calculated using the equation you developed above and stored in that register.

Reminder: Any use of Floating Point variables OR constants will result in large deductions in the visual grading.a

Installing the Dial

At the the instructor’s podium you can find sheets of small paper indicator dials that show the speed from 0 to 100 cm/sec. These can be cut out and taped to the bottom of the servo. To align the servo motor’s pointer properly we suggest the following procedure. Temporarily change your program to show fixed speed of 50 cm/sec, the midpoint of the rotation, and have the servo point there. Pull the indicator arm off the servo and reinstall it so it is pointing parallel to the two long sides of the servo which should be at the 50 cm/sec speed. After doing this it should correctly show the speeds from 0 to 100 cm/sec.

Non-volatile EEPROM Memory

The ATmega328P microcontroller contains 1024 bytes of memory that retains its data when the power is removed from the chip. It’s similar to the FLASH memory where your program is stored but intended to be written to and read from your program rather than just during the “make flash” operation. In this project we will be using the EEPROM to store the speed threshold value. In this way when the device is turned on it will have the same setting as it had previously.

The avr-gcc software includes several routines that you can use for accessing the EEPROM. To use these functions, your program must have this “include” statement at the beginning of the file that uses the routines.

#include <avr/eeprom.h>

The two routines that you should use for your program are described below.

Your code should use the above routines to store the speed threshold in the EEPROM whenever it has been changed. You can assume the threshold will always be in the range of 1 to 99 cm/sec. The number can be stored in an 8-bit variable and only requires writing a single “unsigned char” variable to the EEPROM. You can choose any address in the EEPROM address range (0 to 1023) to store the value. When your program starts up it should read the value from the EEPROM, but it must then test the value to see if it is a valid threshold value. If the EEPROM has never been programmed, it contains all 0xFF values. If you read the EEPROM data and the value is not in the range 1 to 99, then your program should ignore this number and revert to using a default threshold value that is defined in your source code.

Warning! The EEPROM on the microcontroller can be written to about 100,000 times and after that it will probably stop working. This limit should be well beyond anything we need for this project but it’s very important that you make sure you don’t have the above EEPROM writing routines in some sort of loop that might go out of control and do 100,000 writes before you realize the program isn’t working right.

Serial Interface

Here is a video lecture/tutorial from previous semesters’ labs that describes the operation of RS-232 serial and to the slides that accompany it.

The serial interface between speed trap devices will use an RS-232 link to send the speed data between the units. The serial input and output of the Arduino uses voltages in the range of 0 to +5 volts. These are usually called “TTL compatible” signal levels since this range was standardized in the transistor-transistor logic (TTL) family of integrated circuits. Normally for RS-232 communications these voltages must be converted to positive and negative RS-232 voltages levels in order for it to be compatible with another RS-232 device. However for this project we will skip using the voltage converters and simply connect the TTL level signals between the project boards.

Locating the RX and TX signals

The Ardunio’s transmitted (TX) and received (RX) data appears at two places on the LCD shield as shown in the figure below. The black connector along the top of the LCD shield has two holes marked “TX” and “RX” that can be used by inserting wires into these holes as has been done in previous labs. Alternatively, you can attach the wire jumpers to the D1 pin for the TX signal, or the D0 pin for RX signal.

Image

For the RX and TX signals, there is no advantage or difference in using one connection point over the other. The D1 pin is connected to “TX”, and D0 pin is connected to “RX” on the LCD board so you can use either one.

Tri-State Buffer to Eliminate Contention on RX Line

When using the the serial signals to communicate with another device there is an issue with programming the Arduino that needs to be resolved. If the received data coming from another Arduino (or the TX data being looped back) is connected directly to the RX input (Arduino port D0) it creates a conflict with a signal used to program the Arduino’s microcontroller. Both the RX data and the programming hardware try to put an active logic level on the D0 input and this can prevent the programming from succeeding. When this happens you will get error messages like these.

avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00
avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 2 of 10: not in sync: resp=0x00

The solution for this is to use a tri-state gate to isolate the incoming data from the RX input until after the programming is finished. The gate you will using is a 74HCT125 that contains four non-inverting tri-state buffers (see below).

Each of the four buffers have one input, one output and an enable input that controls the buffer’s tri-state output. In the diagram below on the left, the enable inputs are the connections going to the little circles on the side of the gate. The circle indicates that it is an “active low enable”, meaning that the gate is enabled when the enable input is a logical 0. In that state, it simply copies the input logic level to the output (0 → 0, 1 → 1). However when the gate is disabled (enable input = 1) its output is in the tri-state or hi-Z state regardless of the input. In that condition, the gate output has no effect on any other signal also attached to the output.

The serial communications circuit needs to include the 74HCT125 tri-state buffer in the path of the received data as shown below. The received data from the other Arduino should go to one of the buffer inputs, and the output of the buffer should be connected to the RX port of the Arduino. Remember to also connect pin 14 of the 74HCT125 to +5V, and pin 7 to ground.

Image

The tri-state buffer’s enable signal is connected to a digital I/O port bit. For this project, use I/O port bit PB4 (Port B, bit 4) to control the tri-state buffer. When the Arduino is being programmed all the I/O lines become inputs. With no signal coming from the Arduino, the pull-up resistor will cause the PB4 line go high and put a logic one on the tri-state buffer’s enable line. This disables the output (puts it in the hi-Z state) and the programming can take place. Once your program starts, all you have to do is make that I/O line an output and put a zero in the corresponding PORT bit. This will make the enable line a logical zero, that enables the buffer, and now the received data will pass through the buffer to the RX serial port.

Note that the tri-state buffer and pull-up resistor is necessary even if you have configured the Arduino and project board to use a loopback where the transmitted data from the TX port is connected to the RX port. The TX and RX being connected together will interfere with the programming if the tri-state buffer is not used. The TX data must be connected to the buffer input so the it can be isolated from the RX port until the programming is completed.

Format of the Serial Signals

For all the speed trap devices to be capable of communicating with others, use the following parameters for the USART0 module in the Arduino. Refer to the slides from the lecture on serial interfaces for information on how to configure the USART0 module for these settings.

Serial Interface Protocol

The serial data link between two speed traps uses a simple protocol:

In many devices that use serial links these features are implemented using relatively complex data structures and interrupt service routines so the processor does not have to spend much time doing polling of the receiver and transmitter. We’ll do it in a simpler manner that should work fine for this application.

The protocol for communicating the measured speed between two speed traps will consist of a string of up to six bytes in this order:

For example, to send a speed of 35.7 cm/sec degrees, the transmission should consist of {357}.

Transmitting Data

When your software determines that the speed measuring event has completed, it should call a routine that sends the characters for the speed to the remote unit. The serial link is much slower than the processor so the program has to poll the transmitter to see when it can put the next character to be sent in the transmitter’s data register for sending. The UCSR0A register contains a bit (UDRE0 - USART Data Register Empty) that tells when it’s ready to be given the next character to send. While this bit is a zero, the transmitter is busy sending a previous character and the program must wait for it to become a one. Once the UDRE0 bit becomes a one, the program can store the next character to be sent in the UDR0 register.

while ((UCSR0A & (1 << UDRE0)) == 0) { }
UDR0 = next_character;

While your program is waiting for all the characters to be transmitted it should still respond to interrupts from modules with interrupts enabled, but it does not have to reflect any changes on the display until after all the data has been sent and it’s back in the main program loop.

Receiving Data

Receiving the speed data from the remote unit is a bit more complicated since you have no idea when the remote unit will send the data. One simple way to implement this is to have your program check for a received character each time through the main loop. If one has been received, then call a function that waits for the rest of the characters and when complete displays the speed on the LCD. Unfortunately this method of receiving characters has one very bad design flaw in it. If for some reason the string is incomplete, maybe only the first half of the string was sent, the device will sit in the receiver subroutine forever waiting for the rest of the data and the ’}’ that marks the end of the transmission.

A better solution, and one that should be implemented in your program, is to use interrupts for the received data. Receiver interrupts are enabled by setting the RXCIE0 bit to a one in the UCSR0B register. When a character is received the hardware executes the ISR with the vector name “USART_RX_vect”.

For reading the incoming speed data, each time a character is received, an interrupt is generated and the ISR determines what to do with the character. After all the characters have been received, the ISR sets a global variable to indicate that a complete remote speed value has been received and is available. When the main part of the program sees this variable has been set, it gets the value and displays it. By using the interrupts, the program is never stuck waiting for a character that might never come.

It is also important to consider all the possible errors that might occur in the transmission of the date, such as missing start (‘{‘) character, missing or corrupted speed characters, missing end (‘}’) character, etc. The software must make sure all of these situations are handled cleanly and don’t leave the device in an inoperable state.

To handle possible errors in the received data, the speed trap should operate on the principle that if an error of any kind is detected in the data being received, it discards the current remote speed data that it was in the midst of receiving and goes back to waiting for the next speed data to start coming in. It does not have to notify the main program that an error was encountered, or try to correct the error, or guess what the correct data was. Simply throw it away and go back to waiting for a new data packet.

To implement this, use the following variables to properly receive the data:

If the ISR receives a ’{’, this indicates the start of a new speed data sequence even if the previous sequence was incomplete. Set the data start variable to a one, and clear buffer count to 0. Also set the the valid data flag to zero to indicate that you now have incomplete data in the buffer. This sets up the ISR to wait for the next transmission.

For the next characters that arrive, your code must check to see if they are valid.

The main program can check the data valid variable each time through the main loop. When it sees it has been set to a one, it can call a function to convert the speed data from from a string of ASCII characters to a single fixed-point number (see below). It should probably also clear the data valid variable to a zero so it doesn’t re-read the same data the next time through the loop.

Using sscanf to Convert Numbers

In Lab 5 you learned how to use the “snprintf” function to convert a number into a string of ASCII characters (e.g. 123 → “123”). Now we need to go the other way, from a string of ASCII characaters into single fixed-point number (e.g. “456” → 456). For this we can use the “sscanf” function that is part of the the standard C library.

Important: As with using snprintf, in order to use sscanf you must have the following line at the top of the program with the other #include statements.

#include <stdio.h>

The sscanf function is called in the following manner

sscanf(buffer, format, arg1, arg2, arg3, ...);

where the arguments are

The format argument tells sscanf how to format the output string and has a vast number of different formatting codes that can be used. The codes all start with a percent sign and for now we will only be working with one of them: &d. This is used to format decimal integer numbers. When this appears in the format string, the characters in the input string will be interpreted as representing a decimal integer number, and they will be converted to the corresponding value. The result will be stored in the variable that the corresponding argument points to.

The format string must have the same number of formatting codes as there are arguments that follow it in the function call. Each formatting code tells how to convert something in the input string into its corresponding argument. The first code tells how to convert something that is stored where “arg1” points, the second code is for “arg2”, etc.

Example: Assume you have a char array containing the characters representing three numbers. The code below would convert them into the three int variables.

char buf[] = "12 543 865";
int num1, num2, num3;
sscanf(buf, "%d %d %d", &num1, &num2, &num3);

The arguments are pointers to where the three values are to be stored by using the form “&num1” which makes the argument a pointer to num1 rather than the value of num1. After the function has executed, the variables “num1”, “num2” and “num3” will contain the values 12, 543 and 865.

Important: The “%d” formatting code tells sscanf that the corresponding argument is a pointer to an int variable. When it converts the characters to a fixed-point value it will store it in the correct number of bytes for that variable type. If you wish to store a value in a “short”, or a “char” variable, you must modify the format code. The formatting code “%hd” tells it to store a 2 byte short, and “%hhd” tells it to store a 1 byte char.

Here’s the above example but modified to use three different variable types.

char buf[] = "12 543 865"; char num1;
short num2;
int num3;
sscanf(buf, "%hhd %hd %d", &num1, &num2, &num3);

Speed Comparison LEDs

An RGB LED is used to show the comparison between the most recent local and remote speeds. This is the same RGB LED that was used in Lab 9 and should be hooked up the same way. The RGB LED is a “common anode” device. The anode (positive side) of all three LED segments are tied together and should be connected to the +5V supply. The cathodes (negative side) of the individual segments are connected to I/O pins or logic through a current limiting resistor. A low voltage on the segment’s cathode is used to turn the segment on. It’s recommended that the same current limiting resistor values be used here as were used in Lab 9.

Due to a lack of I/O pins on the microcontroller, only the red and blue segments are controlled directly by the microcontroller on ports PC2 and PC3 respectively. The green segment has to be controlled by these red and blue signals and some additional logic added to the circuit. The logic should be designed so when the red and blue segments are off, the green segment goes on. The circuit does not have to have the ability to turn all three segments off at the same time.

The local and remote speeds should be compared to an accuracy of 0.1 cm/sec (1 mm/sec). The results of the comparison are shown on the LED segments as follows.

Software Issues

Multiple Source Code Files

Your software should be designed in a way that makes testing the components of the project easy and efficient. In previous labs we worked on putting all the LCD routines in a separate file and this practice should be continued here. Consider having a separate file for the encoder routines and its ISR. The serial interface routines and ISR can be in another file. Code to handle the timing LED, the RGB LED and timers can either be in separate files or in the main program. All separate code files must be listed on the OBJECTS line of the Makefile to make sure everything gets linked together properly.

To get full credit for this project, your source code must be in at least four “.c” files. This can include your main file, lcd.c, and a minimum of two others.

Accessing Global Variables

In the project you may need to use global variables that are accessed in multiple source files. A global variable can be used in multiple files but it must be defined in one place. We define variables with lines like

char a, b;
int x, y;

Global variables must be defined outside of any function, such as at the start of a file before any of the functions (like “main()”). Variables can only be defined once since when a function is defined, the compiler allocates space for the variable and you can’t have a variable stored in multiple places.

If you want to use that global variable in another source code file, the compiler needs to be told about that variable when compiling the other file. You can’t put another definition of the variable in the file for the reason given above. Instead we use a variable declaration. The declaration of a global variable (one that’s defined in a different file) is done using the “extern” keyword. The “extern” declaration tells the compiler the name of the variable and what type it is but does not cause any space to be allocated when doing the compilation of the file.

For example, let’s say the global variable “result” is accessed in two files, project.c and stuff.c, and you decide to define it in the project.c file. The project.c file would contain the line defining the variable

int result;

and the stuff.c file would need to have the line

extern int result;

declaring it in order to use the variable. If the “extern” keyword was omitted, both files would compile correctly, but when they are linked together to create one executable, you would get an error about “result” being multiply defined.

If you have global variables that need to be accessed in multiple files it’s recommended that you put the declarations in a “.h” file that can be included in all the places where they may be needed. For the example above, create a “project.h” file that contains the line

extern int result;

and then in both project.c and stuff.c add the line

#include "project.h"

It doesn’t cause problems to have the declaration of a variable, like from an “.h” file, included in the file that also contains the definition of the same variable. The compiler knows how to handle this correctly.

Improving Your Makefile

In class we discussed how the “make” program uses the data in the “Makefile” various modules that make up a program. This project may require several source code files, some with accompanying “.h” header files, so the generic Makefile should be modified to describe this. For example, let’s say you have five C files for the project and five header files:

Let’s also say that project.h is “included” in the encoder.c file, and the header files for the LCD, encoder, serial and timer routines are included in the project.c file. In this situation, the following lines should be added to the Makefile after the “all: main.hex” and before the “.c.o” line as shown below.

all:    main.hex

project.o:   project.c project.h lcd.h encoder.h serial.h timers.h
encoder.o:   encoder.c encoder.h project.h
serial.o:    serial.c serial.h
timers.o:    timers.c timers.h
lcd.o:       lcd.c lcd.h

.c.o

Adding all the dependencies to the Makefile will make sure that any time a file is edited, all the affected files will be recompiled the next time you type make.

Building Your Design

It’s important that you test the hardware and software components individually before expecting them to all work together. Here’s a one possible plan for putting it together and testing it.

The project should be built in two stages with it checked off at each of the two stages.

For checkpoint 1:

  1. Install the LCD shield and write code to put a splash screen on the LCD. This will confirm that you can write messages on both lines of the display. Use your file of LCD routines from the previous labs to implement this.

  2. Build the two sensors by installing the two LEDs and the two phototransistors. Write code to test the sensors. If the first sensor shows the light it blocked write “Start” somewhere on the LCD. If the second sensor is blocked write “Stop” on the LCD.

  3. Write code to start the timing when the first sensor is activated, and stop when the second is activated. Write the timer count value to the LCD and check that it is larger for longer lenghts of time between blocking the start and stop sensor.

  4. Install the timing LED and add code to make it go on when timing starts and go off when it stops.

  5. Add code to the timer ISR to handle the case of the timing event exceeding the specified time limit. Confirm that the timing stops on its own after the correct amount of time.

  6. Convert the timer count value to milliseconds and to speed in cm/sec. This must be done without using any floating point arithmetic. Write these values to the LCD after each timing event.

For checkpoint 2:

  1. Install the rotary encoder. Define the speed threshold variable and use your encoder routines from Lab 7 to change this number up and down. Display the number on the LCD. Add code to make sure the number stays between 1 and 99 cm/sec. Trying to go beyond these limits should keep the value at limit.

  2. Install the buzzer and add code to sound the buzzer if the local speed measurement is above the threshold set with the rotary encoder. Use the concept of how to generate a tone from the encoder lab but use TIMER0 to generate the tone on the buzzer. Don’t use delay functions like you did in the encoder lab. Make the tone play whenever the speed exceeds the maximum speed setting and confirm that this is working.

  3. Add the servo motor. Write code to convert the measured speed to the value to generate the correct PWM pulse.</li>

  4. Write code to store the maximum speed value as set with the rotary encoder in the EEPROM, and read the EEPROM value when the program starts. Confirm that this is working by adjusting the speed value and cycling the power on the project. It should start up and display the speed you had set before. Make sure to add code that checks that the speed you read from the EEPROM is a valid value.

For checkpoint 3:

  1. Install the 74HCT125 tri-state buffer. Write code to enable the the buffer after the program starts. Check that you can program the Arduino with the serial connections in place. This means the tri-state buffer is doing what it is supposed to do.

  2. Write test code that continually sends data out the serial interface and use the oscilloscope to examine the output signal from the D1 port of the Arduino (transmitted data). For 9600 baud, the width of each bit should be 1/9600 sec. or 104μsec.

  3. Write code to send the speed value out the serial interface in the format specified above and check with the scope that it’s being transmitted after the speed measurement. Check that the transmitted packet matches the specified protocol with the start and end characters present.

  4. Write code to receive the remote speed and display it on the LCD. This also should be done without using any floating point routines.

  5. Do a “loopback” test of your serial interface. Connect the D1 (TX) pin to the input pin on the 74HCT125 so you are sending data to yourself. Try doing a speed measurement and see if the remote speed is the same as the one you displayed on the LCD as the local speed.

  6. Add the RGB LED and the three resistors. Add code to turn the appropriate segment on based on the comparison between the local and remote speed measurements. To test this you will need to hook your board to another unit.

Results

Getting your project checked off can be done in multiple checkpoints to ensure you earn partial credit for parts that are working. At each checkpoint we will confirm operation of all the features from the previous checkpoint plus the new features added in the current checkpoint.

Checkpoint 1:

  1. Splash screen with your name shown at start.
  2. Speed measurement working and displaying speed in cm/sec on the LCD.
  3. Timing LED goes on and off properly.
  4. Measurement times out after 4 seconds.

Checkpoint 2:

Demonstrate items from the previous checkpoint. Then demonstrate:

  1. Rotary encoder can adjust speed threshold.
  2. Buzzer sounds warning if speed too high.
  3. Servo motor positions pointer to correct position for the measured speed.
  4. Threshold stored in EEPROM and retrieved when Arduino restarted (power cycled).

Checkpoint 3:

Demonstrate items from the previous checkpoint. Then demonstrate:

  1. Serial interface can transmit local speed to remote device.
  2. Serial interface can receive remote speed and display on LCD.
  3. RGB LED segments indicate comparison between the speeds.

Review Questions

Be sure to answer the two review questions in Project_Answers.txt and reprinted below:

  1. Cost Analysis: Assume we are building 1000 units of this system. Use the provided part numbers (see the webpage) and the digikey.com or jameco.com website to find the total cost per unit (again assume we build a quantity of 1000 units) for these speed traps. Itemize the part costs (list each part and its unit cost when bought in units of 1000) and then show the final cost per unit below. Note: You only need to price the components used in the project (not all the components in your lab kit. Also, you do not need to account for the cost of the circuit board or wires. In a real environment we would manufacture a PCB (Printed Circuit Board). As an optional task, you can search online for PCB manufacturers and what the costs and options are in creating a PCB.

  2. Reliability, Health, Safety: Assume this system was to be sold to consumers for use at their home.

    • What scenarios might you suggest testing (considering both HW and SW) before releasing the product for use?

    • How might you make your design more reliable? By reliability we don’t just mean keeping it running correctly (though you should consider that), but also how you can detect that a connected component has failed to gracefully handle such situations. You can consider both HW and SW points of failure, issues related to the operational environment, etc. and how to mitigate those cases.

Submission

Make sure to comment your code with enough information to convey your approach and intentions. Try to organize your code in a coherent fashion.

The Project_Answers.txt file and all source code (all .c and .h files and the Makefile) must be uploaded to the Vocareum web site by the due date. Make sure you have included all files since the graders may download your submission, compile it on their computer and download it to their board for checking the operation. See the Assignments page of the class web site for a link for uploading.

Please make sure to save all the parts used in this lab in your project box. All the contents of the project box and components will need to be returned at the end the semester.