EE492 - Spring 1998

Ray Chang


Many of our Cornell Colleagues have to take bus to and from school. However, buses are not always on time. It is not their fault since the Ithaca weather and hence road condition can be highly unpredictable. On the consumer side, there is no choice except getting to the bus station on time despite having to stand in the chilly wind and heavy snow for perhaps 10 minutes or more.

What if we have an advanced warning system that gives out a signal at the approach of a bus? Suppose we have a transmitter on every bus that sends out the bus number code, and have a repeater on each bus stop repeat that signal when the bus passes by. To identify where the bus is at, the repeater may add in its stop number. This signal, when received and decoded by the user's receiver, gives out a warning signal when it matches the bus and stop number that the user set previously.

This project aim at building a prototype for a bus advanced warning system with two important differences. This prototype is based upon the Motorola MC68HC912B32 micro-controller. There shall be a compact DC power supply attached to the transmit and receive modules in the final product.

Since I am using a relatively powerful Micro-controller for this task, I added additional features to make this device more useful to the Cornell community. One of the features is a electronic planner that allows users to store telephone numbers of other students. Also, the users are able to store events in the organizer to remind them to do certain tasks. This is accomplished by building a digital clock.


Pin 7, the data pin, is connected to the micro-controller serial PORT S bit 0. This pin is used to transmit data into the micro-controller. The receiver gets the FM modulated signal through the antenna of 16 cm connected to pin 1, demodulates it into digital byte streams and sends it to pin 7. The micro-controller then takes this data and constantly matches it against the preset bus and stop code. Once matched, it activates a LED flashes to alert the user of the approach of bus.


Pin 5, the data pin, is connected to the other end of the micro-controller serial port pin 2. To clarify again, this should be the other end that is NOT connected to the micro-controller's serial port. Pin 2 on this end means the receiver pin and it is based on the data send from the transmit pin from the serial port connected to the board. The micro-controller, after accepted and encoded the bus and stop number that the user types in the terminal program, should send out the FM modulated bit stream continuously through the antenna connected to pin 2. The hardware has been set to a baud rate of 9600 and the carrier frequency to be 418 MHz. FM modulation is used to guard against loss and noise. 418 MHz is used to ensure transmission even in snow, which has a high absorption for frequencies at microwave frequency, namely 1-2 GHz.


I used the Seiko L1671 LCD display for the user interface. This 16 character dot matrix display is capable of displaying all of ASCI characters and the set of Japanese characters as well! It has its own decoder and controller on the back of the screen, so the user only has to connect the external pins to some I/Os on the MCU. The main problem of this LCD display, however, is the amount of time it requires for a character to be stored. In order to write to the display, I had to write a delay routine that loops for 800 cycles. At the very least, this would require around 2000 cycles which would be 250 us. Other than that, this display is relatively easily to program and has the capability to adjust contrast through the use of a variable resistor. The 8 data pins are connected to PORT A of the microcontroller. In addition, I am using PORT P to control the enable and read/write pins. Since there are many I/O pins on the board, the use of these pins does not pose a problem.

Seiko - LCD Specifications


The 16-Key Keypad only has 8-bit data pins. It uses a matrix decoding scheme to determine the button pressed. For instance, I had to drive the lower 4 bits of the data path with zeros and waiting to read the byte from the upper 4 bits, and vice versa. When a button is pressed, it effectively shorts out the two pins that interconnect at that point. If one presses the button A, then pin 0 and pin 4 are shorted together. This means if we had pull-up resistors for the upper 4 bits and drive a zero for the lower four bits, we should read in three '1's and one '0' for the upper 4 bits.

The motorola board comes in quite handy because we can set a bit to enable a pull-up resistor for all the pins. In addition, there is a direction register that controls whether the I/O is an input or output. Hence, we can easily drive the keypad and read the result. On the down side, however, is that matrix mode would require at least 4 cycles (2 reads/2 writes) for each button press whereas if we had 16-bit data path, it would have been one cycle. But, the microcontroller is running relatively quick, so this is not really a problem.

Keypad Specification (pdf)


This 16-bit Motorola microcontroller has a lot of features packed into a single IC. For instance, it has a 32-Kbyte Flash EEPROM in addition to 1K SRAM and another 768-bytes of EEPROM. It also has the standard features like:

1) Able to Connect 2 Motorola boards together
2) 8 Channel A/D
3) 8 Channel Timer
4) PWM
5) 16-bit pulse accumlator
6) Serial Interface
7) Interrupts/Exceptions

The only thing that was kind of troublesome was that using only one board, if one wants to debug the program code and it's too large to fit in the 768 bytes of EEPROM, one has to erase the on-board debug program that is stored in the FLASH EEPROM. This poses a problem because the FLASH EEPROM is only guaranteed for 100 erase and program cycles. So, effectively, one is only guaranteed for 50 debug sessions. Although I didn't pass the limit to figure out how bad the situation gets, I came really close to it....

Motorola HC12 Main Page


The Intel board is actually more powerful than the Motorola one. However, it is also more expensive and bulky. I am only using this board because we couldn't get another Motorola board since they ran out. This board is only used to send character bytes to the transmitter at 9600 bauds. The intel board is actually more useful in this setting since it has two communication ports unlike the Motorola one. This means we can change the code that we are sending out anytime rather than building an interface for it.

Intel 80196 Main Page

CODE - C++

I programmed this microcontroller using Microsoft C++ (even though it's mostly C coding). Once I have written the code, I use HIWARE TOOLS to translate the coding into the Motorola S-code. In the beginning, I had a lot of trouble figuring out what to do in between stages. Since there are like three to five stages one has to go through. I'll document them here just in case some of you would like to know... plus, I'll need to remember this myself.

1) Write Code in C/C++
2) Use the HC12 Compiler --> generates ELF object code for the linker
3) Create a customized linker file (*.prm) - Read below for more
3 1/2) One can use the decoder to generate the assembly code (type *.abs in menu) 4) Use the burner software to translate the (*.abs) file into S-record format
5) Upload to the board's FLASH EEPROM by connecting jumper W3,W4 to 1 and W7 to VPP. Remember to feed +12V to VPP pin (right under the power connector)


About the hardest part of all the steps is understanding how to link the code to assembly language. If you are only doing normal programming (i.e. no interrupt subroutines) then it should be relatively easy. All you have to do is use this template. Otherwise, refer to section on FLASH EEPROM

1) LINK command specifies the output .abs file name

2) NAMES command includes all the files that you want to link. Note that .o file is the output of the HC12 compiler. The strt12sp.o is the startup code that the Hiware Tools uses to initialize the microcontroller (needed if you are doing FLASH EEPROM programming!), and the ANSIS.LIB is just the standard C/C++ library file.

3) SECTIONS command let the user define where in memory the code goes. One should consult the original manual to figure out the memory locations. For the HC12 MCU, the EEPROM is located in 0x0D00 to 0x0FFF which is 768 bytes. And, the 1k RAM is located at location 0x0800 to 0x0BFF. If the user code can fit in this memory map (use the template configuration). You can see the size of your code in the HC12 Compiler, just read the output after compilation.

4) PLACEMENT command basically lets the user define where to put the functions in memory. If one just uses the default_ram and default_rom command, the startup code automatically put everything sequentially. However, if you want a particular function to start at a certain location, you need to specify a section in memory as indicated above, and say function__Fv into the section that you define.

5) INIT is defined by the startup code to be the entry point in your program. It automatically stores the location of your main function in memory and references it when needed.


When you program the Flash EEPROM, you have to erase the on-board debugger. This not only disables the SIMULATOR that came with Hiware tools, but you are also helpless on debugging. However, since the EEPROM is not big enough to hold code over about 700 bytes, this is the only way to go. My only suggestion is to test out one section of the code at a time (try to limit it down to 700 bytes) and then put everything together. Here's a copy of the debug program in case you need to reinstall it.

To do interrupt subroutines in C, you have to put this line before the function "#pragma TRAP_PROC". This is an internal back-end ANSI that the Hiware compiler understands. It basically specifies the compiler to use the RTI command rather than RTS. In addition, add "extern "C"" in front function definition. There's another way of doing this as well, but it's more Hiware internal methods (read it in the manual). This takes care of the compiler part, but one still needs to specify new commands in the .prm file. See this template.

Notice that I have defined the ROM section to be from 0x8000 to 0xFFFF. This is the 32k-byte region of the FLASH EEPROM in normal single chip mode. I have also defined a stack space in memory as well. To specify an interrupt subroutine location, just use the command "VECTOR ADDRESS interrupt_jump_table_location interrupt_subroutine_address". The interrupt_jump_table_location is specified in the appendix of the manual. The very last entry is the jump location for an external reset. So, specify the _startUp code at this location.

NOTE: You have to set the COPCTL register to 0x00 otherwise, the COP timer will timeout and you won't get anywhere! Took me more than 12 hours to figure out.... with the help of a Motorola engineer.


Like I have mentioned before, the transmitter is connected to the INTEL MCU because of its ease of use in debugging environment. So, in the assembly code, we just keep transmitting the code (which is encapsulated in 2 bytes of data; the higher byte indicates the stop number, and the lower byte indicates the bus number.) One potential problem that we avoided was the possibility of code collision. For instance, if the bus stop number is 8 and the bus number is also 8, then, we would be always listening for the code "0808". However, if the transmission from another bus stop has the bus number 8, and then the next transmission is from bus stop 8, but with a different bus approaching it, then it would set off a false alarm. This is caused by the fact that we are comparing a byte at a time. So, to bypass this problem, we offset the bus stop number by 128, which would always give the code a unique number since the value of each byte will never be the same. This seems to be appropriate in Ithaca because we do not have that many buses or bus stops.

Before we continue, it would be better if we explain how the serial interrupts work. We know from the hardware specs and the way we initialized the baud rate register that the serial line is communicating at the speed of 9600 BPS. At this rate, we are basically checking 1200 bytes every second (since 1 byte = 8 bits), or 600 pairs of code. The way the serial line works on the Motorola MCU is that everytime a byte of data arrives at the Rxd register, it buffers it in the register SC0RDL and calls an interrupt subroutine. So, when the interrupt occurs, we store the original lower byte in the high byte, and then the incoming byte in the lower byte of a 2 byte variable. Once we have captured the byte, we need to check whether the received code is the expected code. If it is, we set "bus_here" to 1 which would signal the main program that a match has been found and we can now set off the alarm. Since I didn't plan the time too well, I could only build a simple alarm. Using pin 5 of the PWM port, I drive a LED high whenever a bus_here is set. To connect the LED, I used a series 330 ohm resistor because the applied voltage is +5V.

One of the main debug problems I ran into was that the transmitter and receiver's RF ground was not at the same level. I discovered this after numerous failures at getting the receiver subroutine to work. What happens is that I am using 2 separate power supplies, one for the Motorola MCU and one for the Intel MCU. The first one supplies +5.12V while the other one only supplies +4.82V. Hence, there is a difference of +0.3V. According to the receiver specification, a digital low would have a voltage of 0.2V. However, with the ground different, a low is now driving 0.52V! This makes it impossible for the Motorola MCU to determine the incoming code. It would just read all ones (actually, it receives all zeros and assumes the line is idle).

I don't know if its my luck or not, just after I figured the above problem out, the Intel board decided to give up on me. I can reset the board, but I am unable to load my code into it. Hence, I have never truly tested whether the receiver interrupt is working. But, I have checked my codes many times according to the manual, so hopefully it works.


The digital clock basically keeps the time for the whole system, it is used in conjuction with the planner to alert user of important "to do" events. There is a build-in reset routine that lets the user reset time on the fly. It evens stores AM/PM. :)

The digital clock is working with respect to design specification, with a minor glitch. Since I am using the 16-bit timer overflow to count the seconds, the problem is that this is not exactly accurate to 1 second. Let's do the following calculation

8Mhz/32 = 250000 Hz/2^16 = 3.815 Hz/4 = 0.954 Hz = 1.05 seconds

The first division by 32 is a prescalar option that is build in to the timer control. What it does is to slow down the timer increment by a factor of 32. Next we divide by 2^16 to account for one timer overflow. However this is only going to be about 1/4 of a second, hence I wait for a total of 4 timer overflow before I increment the second variable.

While I am updating the variable however, we are running into a lot of wasted time as well. Since I am updating the whole clock in the timer interrupt, this adds additional time to the calculated 1.05 seconds. What also affected the result is probably the fact that I am writing to the LCD display in the interrupt as well. As I have mentioned before, each of those writes could take potentially up to 250 us, and we are writing up to 6 characters! After running the clock for about 3 hours, I have noticed a difference of around 6 minutes. This means we are off by at least 360 seconds.

3600sec*3/(3600*3-360) = 1.034 seconds

In conclusion, I might have had slightly better result if I took the write subroutine out into the main program instead of in the interrupt subroutine. I probably have to sneak in a second every 30 seconds or so in order to make the time more accurate.


The digital planner has two functions. One is to store telephone numbers of other people (up to 10 in this version), and the other is to store events happening on that day. The current version does not have a built-in calendar. So far, only the former has been implemented, the latter will have to be implemented in the future.

In order to enter alphabets, I am using a scheme very much like entering alphabets on the phone. Since each of the keys on the keypad has 3 letters, we can prompt the user to press two buttons for each letter. For instance, if one wants to enter the letter "A", one should type in 2 first (since ABC is on button 2) and then press 1 (since "A" is the first of the three). If one pays attention, letter Q and Z are missing. To compensate, I made button 1 into "QZ_" where _ means a blank space.

Basically, the phone numbers are stored inside a 2-D array that also uses the same index for the last name of the person. In future version, I would want to store the entries inside the EEPROM rather than the RAM because if I turn off the power everything goes. Other than that, this section is working for the most part.


wireless paging - on-campus paging service

smart id - use transmitter to send out the Cornell ID