You are on page 1of 20

EE 476 Final Project RC Car Controller

Blair "Iceman" Lee John "Diesel" McDonald

[ Overview | The Car | Communication | Controller | Receiver | Steering Control | Speed Control | Results | Appendix ] Overview: We decided to build transmitter and receiver modules for a radio-controlled (RC) car, as well as implement variable-speed motor control and a continuous steering function. The simple speed controls included in most RC kits seldom offer more than three forward speeds and one reverse speed; furthermore, steering controls in most Radio Shack toys only offer binary steering: Either the car is turning left, right or not at all. We felt that overcoming these limitations would make any RC car more realistic to handle and ultimately more fun to drive.

The Car

A Tamiya RC model kit was bought from a Maryland hobby store, along with a rudimentary radio control unit to verify that original units operation. After making sure all parts were assembled correctly, the mechanical speed control, speed control servo, and radio receiver module were all removed. The car we selected already implements continuous steering with a servo attached to the steering linkage; however, because the radio receiver was no longer being used, we decided to leave the servo in and reverse engineer its interface. The original implementation of speed control in the car consists of a servo which mechanically moves the arm of a simple high-power potentiometer. While the motion of the servo is continuous, the circuit only produces six discrete levels: three forward, two reverse, and neutral. Hobbyists in RC cars often replace this assembly with an electronic speed control (ESC), which uses pulse width modulation to provide a smooth speed control. We chose to simulate the function of these commercially available ESCs by using the microprocessor to modulate pulse width across the motor. Communication An RC car is of little value if the controls are tethered to the vehicle. However, we determined during the design process that the radio system was mostly irrelevant to the actual project, and that the time required to build the system would seriously impede on the rest of the project. We decided instead to use a pair of commercially available radio modems. The devices, made by National Semiconductor and used by Laplink under the AirShare label, claim to establish a 115 kbps serial connection at a distance of up to 30 feet with clear line-of-sight. While this range is inferior to that of commercially available radio control units, it is more than adequate to prove that the rest of the design works. Command Forward Reverse Left Right Start Stop Opcode 0000 0001 0010 0011 0100 0101 Magnitude Range 0000 - 1111 0000 - 1111 0000 - 1111 0000 - 1111 n.a. n.a.

A simple communication protocol was established to send messages from the controller to the car. Because the connection is serial, we encode each command into a byte-long packet. The top nibble denotes the command, and the lower nibble represents the level at which the command is to be executed. For discrete operations (like headlights), the second nibble determines which functions are to be toggled. Communication travels one way from the controller to the car, which cuts down on the hardware required for either unit.

Controller

The original controller used spring centered potentiometers to produce analog signals which controlled the speed and direction of the car. The analog signals were transmitted to the car via a 75 MHz AM radio link. In order to emulate the true RC car experience, we decided that we needed something more visually intuitive than two potentiometers stuck into a breadboard. We purchased a simple PC joystick from Radio Shack and removed the "turbo" button circuitry. Using the existing interface cable, we were able to connect the internal potentiometers and pushbuttons to the receiver board without cosmetically altering the joystick.

Our design for the controller uses National Semiconductor serial ADCs to convert the analog waveform from the potentiometers to a digital signal. For example, the 8-bit value obtained from the forward/reverse potentiometer is compared against a known value for that potentiometers centered value; we can therefore determine whether the user intends to go forward or backward, and the rate at which she plans to do so. These data are used to generate the opcode and value to be sent to the car over the serial connection. After encoding, the character is sent to the car. The controller polls the user inputs one after another, sending out data regardless of a change in state of the input. Receiver In many ways, the receiver unit acts like a conventional microprocessor: Instructions are fetched from the input byte stream, decoded into an operation and magnitude, and dispatched to the proper control unit. Depending on the instruction, most of the work is done by either the speed control or the turning control; all other instructions control simple on/off devices and can be controlled directly in the receivers main loop. Steering Control

As stated above, steering is controlled by a servo. Rather than supplying a simple voltage, servos operate on a fixed voltage source and a control line that is pulsed to dictate the turn angle. Previous work on the servo left us with a DAC circuit, and we could have added a 555-based timer circuit for pulse width modulation; however, we decided to simplify the design by driving the servo off the mcu. We used the Timer0 interrupt subroutine in conjunction with user input from the transmitter to determine the pulse width to be applied and sent through the mcu's port pins.

Speed Control The received four-bit digital signal denotes the desired speed of the motor. Instead of using a digital-to-analog converter, we decided to drive the motor at full voltage and use pulse width modulation to control speed. Several products exist which accomplish this task; however, it is a simple matter to implement PWM on the microcontroller. A timer interrupt is used to count 16 ticks, and the given magnitude determined how many of those ticks the motor will be driven.

Due to the motors excessive power, it is impossible to drive the motor directly from the microcontroller. The speed control circuit solves this problem by amplifying the signals generated by the Atmel 4414 chip to allow large power transistors to drive the motor. The motor can draw a significant amount of current, roughly 7-8 amps, when turned on. The amplifier uses 200W rated BJTs in a class B push-pull configuration to drive this load. Since the base current to the power BJTs is still on the order of several hundred mAs, intermediate transistors were used to drive the base currents, which turn on and turn off the large BJTs. The intermediate transistors are wired together with the power BJTs in pairs so that the gain of each pair is significant enough to allow it to be driven by the output ports on the microcontroller.

Results In short, the system works. The joystick is able to move the car forwards and back, and turn left and right. However, it was a long road getting to this point. The pulse width modulation, as it was originally conceived, was far too fast for the motor to turn at all. Slowing down the period to approximately one second, up from 1 millisecond, solved the problem; however, there is a noticeable "jerk" to the forward and backward motions of the car. It may be possible to reduce the period, perhaps in half or even smaller; this was not tested in time for the project deadline. There was some initial irregularity in the pulse itself; instead of regular intervals, it seemed as though the pulses were being interrupted by some external stimulus. Careful programming to avoid register clobbering solved much of the problem, but irregularities occasionally appear at unpredictable times. Turning the car is nicely variable but not particularly smooth. The same irregularities found in the speed control manifest themselves to a greater extent in the servo, causing the wheels to jerk slightly left and right of the desired turn angle. We suspect the problem to be extremely difficult to solve with the Atmel mcu, as pulse widths for the servo vary from 1 to 2 milliseconds; at those small periods, it is difficult to get very accurate timing with the Timer0 interrupt. Perhaps a 555 circuit would have solved this problem. The software portion of the project was easy to design, and the implementation is quite simple. By avoiding complicated code, we are able to concentrate on hardware issues. Because most of this project relies on carefully designed circuitry, the reliability of the program greatly simplifies the debugging process. Easily the most contentious and dangerous piece of hardware in the project, the motor control circuitry is both particularly frustrating to design and test. The lack of high power, high current handling MOSFETs in Ithaca made the task significantly more challenging. Many combinations of high power BJTs were used, and a few can drive the car forward. Throughout the testing process we experienced excessive current draw in any number of the BJTs despite transistor biasing and current limiting attempts. Several high power transistors were rendered unusable after a couple seconds of exposure in these circuits. Several fingers bear the mark of instantaneous thermal transfer from contact with the now defunct BJTs. I even saw a 16 gauge wire solder itself in a few seconds when a previous attempt with a 30 watt soldering iron for 20 min was unsuccessful. Prior to the development of the working push-pull amplifier, none of the circuits was able to drive the car in both the forward and reverse directions. Many disastrous results were experienced before a successful combination of components was discovered. Sometimes the behavior of the circuits defied even Pspice. One of the circuits can be pulse width modulated between fast and very fast, but could not be turned off. The lesson learned is that the trick to working with BJTs is having a very tight control over the base currents. Again, power MOSFETs would have made this significantly easier.
;Blair Lee ;John McDonald

;EE 476 Final Project ;RC Car - TRANSMITTER ;PORTA ;PORTB ;PORTC ;PORTD is is is is used used used used for for for for speed control input. pushbutton input. steering control input. serial output.

.include "4414def.inc" .def .def .def .def .def .def .def .def .def .def .def .def .def .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ .equ savSREG =r16 TXbusy =r17 TXflash =r18 command =r19 temp =r20 Volt =r21 Count =r22 LEDReg =r23 debounce =r24 DBcnt =r25 status =r26 release =r27 temp2 =r28 baud96 =25 SpdMid =0x0c StrMid =0x0c FwdOffset=0xfd RevOffset=0xfc LftOffset=0xfd RgtOffset=0xfd OFF =0x00 ON =0xff AADCDO AADCCLK AADCCS AADCDI CADCDO CADCCLK CADCCS CADCDI =1 =2 =3 =4 =1 =2 =3 =4 ;save the status register ;transmit busy flag ;text to be sent is in flash if <> 0 ;command register ;general-purpose temporary register ;ADC sample value ;a counter ;register to store LED output (debug) ;debounce ON or OFF ;debounce counter ;running and lights status ;button release register ;another temporary register ;9600 baud constant for 4Mhz crystal ;midpoint for fwd-back control ;midpoint for left-right control ;forward offset ;reverse offset ;left offset ;right offset ;off ;on ;ADC ;ADC ;ADC ;ADC ;ADC ;ADC ;ADC ;ADC data out clock chip-select data in data out clock chip-select data in

TOP =4 TRIG =5 SWmask =0x30 Maxlights=0x04 fwdcmd bakcmd lftcmd rgtcmd gocmd stpcmd =0x00 =0x10 =0x20 =0x30 =0x40 =0x50

;top hat switch ;trigger switch ;switch mask ;nuumber of light modes ;forward prefix ;backward prefix ;left prefix ;right prefix ;lights prefix ;other prefix

.equ .equ .equ .equ .equ .equ .equ .equ

But1 But2 But3 But4 But5 But6 But7 But8

=0x01; =0x02; =0x04; =0x08; =0x10; =0x20; =0x40; =0x80;

;======================================================== ; Toggle ADC Clock - PORTA ;ADC reads and outputs data on falling edge of the clock .MACRO A_CLOCKPULSE sbi PORTA, AADCCLK nop cbi PORTA, AADCCLK nop .ENDMACRO ;CLK high ;CLK low

;======================================================== ; Toggle ADC Clock - PORTC ;ADC reads and outputs data on falling edge of the clock .MACRO C_CLOCKPULSE sbi PORTC, CADCCLK nop cbi PORTC, CADCCLK nop .ENDMACRO ;CLK high ;CLK low

;======================================================== ;************************ .dseg ;define variable strings to be tranmitted from RAM cntstr: .byte 2 ;************************ .cseg .org $0000 rjmp reti reti reti reti reti reti reti reti reti rjmp rjmp reti RESET: Reset ;reset entry vector

TXempty ;UART buffer empty TXdone ;UART transmit done

cli ldi out ldi out

temp, LOW(RAMEND) ;setup stack pointer SPL, temp temp, HIGH(RAMEND) SPH, temp ;start out not busy on TX

;initial conditions clr TXbusy

;setup UART -- enable TXempty & RXdone int, and RX, TX pins ldi temp, 0b00101000 out UCR, temp ;set baud rate to 9600 ldi temp, baud96 out UBRR, temp ;initialize ADCs on ports A and C ldi temp, 0b00011101 out DDRA, temp ldi temp, 0b00001001 out PORTA, temp ldi temp, 0b00011101 out DDRC, temp ldi temp, 0b00001001 out PORTC, temp ;set up leds on port B ldi temp, 0b00000011 joystick out DDRB, temp out PORTB, temp ;debounce setup clr debounce clr DBcnt clr status sei ;*********************Setup and Read Speed********************* Getspd: clr Volt ldi Count, 5 ;get top 5 bits A_CLOCKPULSE cbi PORTA, A_CLOCKPULSE sbi PORTA, A_CLOCKPULSE sbi PORTA, A_CLOCKPULSE cbi PORTA, A_CLOCKPULSE ReadA: A_CLOCKPULSE clc AADCCS AADCDI AADCDI AADCDI ;initiate conversion ;startbit ;single ended ;channel 0 ;power ADC and set AADCCS ;power ADC and set AADCCS ;power ADC and set CADCCS ;power ADC and set CADCCS

;input from switches on

;debounce on or off ;debounce counter ;initialize car to off

;clear carry

sbic sec rol dec brne ldi

PINA, AADCDO ;set carry if high Volt Count ReadA Count, 8 ;more bits remaining

IgnoreA:A_CLOCKPULSE dec Count brne IgnoreA sbi PORTA, AADCCS

;ignore lsb first data ;conversion complete

;***********************Transmit Speed*********************** _bakCmd: cpi brlt cpi breq lsr subi ori mov rjmp _fwdCmd: ldi sub subi ori mov rjmp _stpCmd: ldi Sendspd: ; rcall rcall Volt, SpdMid _fwdCmd Volt, SpdMid _stpCmd Volt Volt, RevOffset Volt, bakcmd command, Volt Sendspd temp, SpdMid temp, Volt temp, FwdOffset temp, fwdcmd command, temp Sendspd command, 0x00 LEDSt SendMsg

;********************Setup and Read Steering******************** Getstr: clr Volt ldi Count, 5 ;get top 5 bits C_CLOCKPULSE cbi PORTC, C_CLOCKPULSE sbi PORTC, C_CLOCKPULSE sbi PORTC, C_CLOCKPULSE cbi PORTC, C_CLOCKPULSE CADCCS CADCDI CADCDI CADCDI ;initiate conversion ;startbit ;single ended ;channel 0

ReadC:

C_CLOCKPULSE clc sbic PINC, CADCDO sec rol Volt dec Count brne ReadC ldi Count, 8

;clear carry ;set carry if high ;more bits remaining

IgnoreC:C_CLOCKPULSE dec Count brne IgnoreC sbi PORTC, CADCCS

;ignore lsb first data ;conversion complete

;***********************Transmit Steering*********************** _rgtCmd: cpi brlt cpi breq lsr subi ori mov rjmp _lftCmd: ldi sub subi ori mov rjmp _ctrCmd: ldi ori Sendstr: ; rcall rcall Volt, StrMid _lftCmd Volt, StrMid _ctrCmd Volt Volt, RgtOffset Volt, rgtcmd command, Volt Sendstr temp, StrMid temp, Volt temp, LftOffset temp, lftcmd command, temp Sendstr command, 0x00 command, rgtcmd LEDSt SendMsg

;***************************Get Status*************************** Getstat: cpi breq in cpi breq debounce, ON _debounce temp, PORTB release, OFF _release ;check if debounce is on ;check if buttons have been released

;buttons have been debounced and released sbic PINB, TRIG rjmp _trig

_running: sbis rjmp mov com andi andi or rjmp _trig: mov andi inc cpi brge rjmp _lightsoff: ori rjmp _debounce: inc breq rjmp _DBdone: ldi rjmp _release: ori brne ldi

PINB, TOP _status temp, status temp temp, 0xf0 status, 0x0f status, temp _status temp2, status temp2, 0x0f temp2 temp, Maxlights _lightsoff _running status, 0xf0 _running DBcnt _DBdone _release debounce, OFF _release temp, 0x30 _status release, ON ;increment debounce counter

;turn debounce off

;************************Transmit Status************************* _status: mov ori breq _on: mov ori _off: mov ori rcall rjmp command, status command, 0xf0 _off command, status command, 0x5f command, status command, 0x4f SendMsg GetSpd

;***************************Return******************************* ;****************** ;Pause procedure ;******************

;_Wait: ; cli ; clr ;outerL: ; clr ;innerL: ; inc ; cpi ; brne ; inc ; cpi ; brne ; sei approx. 1 ms has ; ret

temp2 temp3 temp3 temp3, 0 innerL temp2 temp2, 255 outerL

;disable interrupts ;clear upper 8 bits of counter ;clear lower 8 bits of counter

;here the outer loop is complete; passed ;return from wait subroutine

;******************* ;message sending sub ;******************* SendMsg: ldi ZL, LOW(cntstr) ;ptr to RAM ldi ZH, HIGH(cntstr) inc ZL st Z, command ld r0,Z out UDR, r0 ;fire off the UART transmit clr TXflash ;the string is in RAM ser TXbusy ;and set the TX busy flag sbi UCR, UDRIE ;enable the TXempty interrupt rcall TXwait ret ;****************** ;Debug procedure ;****************** LEDSt: mov LEDReg, command com LEDReg ;invert to send correct output to leds out PORTB, LEDReg ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;UART stuff follows. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; UART needs a character TXempty:in savSREG, SREG tst TXflash breq TXram inc ZL lpm rjmp inc ld tst TXfls ZL r0,Z r0 ;save processor status ;Is the string in flash memory? ;If not, it is in RAM ;get the next char from flash ;and put it in r0 ;get the next char from RAM ;if char is zero then exit

TXram: TXfls:

breq out rjmp TXend: clr cbi TXexit: out reti

TXend UDR, r0 TXexit TXbusy UCR, UDRIE SREG, savSREG

;otherwise transmit it ;exit until next char ;no more chars ;clear the TXempty interrupt ;restore proc status ;back to pgm

; TX done -- buffer is empty -- unused here TXdone: in savSREG, SREG ;save processor status out SREG, savSREG ;restore proc status reti ;back to pgm ;***************************** ;subroutine TXwait: tst brne ret TXbusy TXwait ;now wait for the tranmission to finish

;Blair Lee ;John McDonald ;EE 476 Final Project ;RC Car - RECEIVER .include "4414def.inc" .def .def .def .def .def .def .def .def .def .def .def .def .def .def .equ .equ .equ spdPulse=r16 spdPW =r17 strPulse=r18 strPW =r19 savSREG =r20 RXchar =r21 state =r22 Spdreg =r23 temp =r24 temp2 =r25 running =r26 timtemp =r27 lights =r28 temp3 =r29 baud96 read fwd =25 =0 =1 ;speed pulse counter for PWM (0..16) ;speed pulse width (0=stop, 15=full on) ;steering pulse counter ;steering pulse width (14=left, 30=right) ;save the status register ;a received character ;state machine variable ;speed register (Forward, Reverse) ;temporary register ;another temporary register ;flag to denote running or not running status ;timer temporary register ;lights status register ;9600 baud constant for 4Mhz crystal

.equ .equ .equ .equ .equ .equ

back left right go stop mask

=2 =3 =4 =5 =6 =0x0f

;command line prefixes, shifted right 5 bits .equ fwdcmd =0x00 ;forward prefix .equ bakcmd =0x01 ;backward prefix .equ lftcmd =0x02 ;left prefix .equ rgtcmd =0x03 ;right prefix .equ gocmd =0x04 ;go prefix .equ stpcmd =0x05 ;stop prefix .equ .equ .equ .equ .equ .equ .equ Center MaxWidth SpdOffset OFF ON Forward Reverse =0x16 =0x3f =0xfd =0x00 =0xff =0x0f =0xf0

;mask values for "other" commands .equ run_mask = 0x01 ;starts/stops car ;************************************** .dseg ;define variable strings to be transmitted from RAM cntstr: .byte 3 ;a two digit count + a zero terminate ;************************************** .cseg .org $0000 rjmp reti reti reti reti reti rjmp rjmp reti rjmp reti reti reti ; ; ; ; PORTA PORTC PORTB PORTD RESET ;reset entry vector

Timer1 Timer0 RXdone

;Timer1 overflow - speed control ;Timer0 overflow - steering control ;UART receive done

is for speed control is for steering control controls the lights is the serial receiver

;Timer0 speed and steering control interrupt Timer0:

cli inc brmi cp brge tst brmi ldi out sei reti StrOff: ldi out sei reti Timer1: cli inc cp brge out sei reti ShutOff: ldi out cpi breq sei reti TimerReset: ldi out sei reti

strPulse StrOff strPulse, strPW StrOff strPulse StrOff timtemp, ON PORTC, timtemp

;disable interrupts ;increment the steering Pulse counter ;turn steering pulse off ;compare strPulse to strPulseWidth ;turn steering pulse off ;check if counter is negative ;turn steering pulse off ;turn steering pulse on ;enable interrupts

timtemp, OFF PORTC, timtemp

;turn steering pulse off ;enable interrupts

spdPulse spdPulse, spdPW ShutOff PORTA, SpdReg

;disable interrupts ;increment the speed Pulse counter ;compare spdPulse counter to spdPW ;branch to motor shutoff ;turn motor on ;enable interrupts ;return from timer0 interrupt

timtemp, OFF PORTA, timtemp spdPulse, MAXwidth TimerReset

;turn motor off ;compare spdPulse to MAXwidth ;branch to reset the timer ;enable interrupts ;return from timer0 interrupt ;reset spdPulse ;turn motor on ;enable interrupts ;return from timer0 interrupt

spdPulse, 0x00 PORTA, SpdReg

RESET: cli ldi out ldi out temp, LOW(RAMEND) ;setup stack pointer SPL, temp temp, HIGH(RAMEND) SPH, temp ;start out running

;initial conditions ldi RXchar, go

;setup UART -- enable TXempty & RXdone int, and RX, TX pins ldi temp, 0b10010000 out UCR, temp ;set baud rate to 9600

ldi out ;enable ldi out ldi out ldi out ldi out

temp, baud96 UBRR, temp Timer0 interrupts temp, 0b10000010 ;turn on timer0 interrupt only TIMSK, temp temp, 0x01 ;prescale timer to raw clock TCCR0, temp temp, 0x00 TCCR1A, temp temp, 0b00000001 TCCR1B, temp

;enable watchdog timer ldi temp, 0x0e out WDTCR, temp ;setup ports A and C to all output ldi temp, ON out DDRA, temp out DDRC, temp ldi temp, OFF out PORTA, temp out PORTC, temp ldi out temp, 0xff DDRB, temp

;initialize Speed ldi spdPW, OFF ldi spdPulse, OFF ;initialize Direction ldi strPW, Center ldi strPulse, OFF ldi ldi sei MainLoop: cpi breq cpi breq cpi breq cpi breq cpi breq cpi breq cpi breq rjmp _read: mov running, OFF state, read

state, read _read state, stop _stop2 state, go _go2 state, fwd _fwd state, back _back3 state, left _left3 state, right _right3 MainLoop temp, RXchar ;grab a byte from stream for analysis

mov lsr lsr lsr lsr cpi breq cpi breq cpi breq ldi rjmp _readchng: cpi breq cpi breq cpi breq cpi breq rjmp _readfd: ldi rjmp _readbk: ldi rjmp _readlt: ldi rjmp _readrt: ldi rjmp _readgo: ldi rjmp _readstp: ldi rjmp _left3: rjmp _right3: rjmp _stop2:

temp2, temp temp2 temp2 temp2 temp2

;don't want to clobber original temp ;now shift right 4 bits (bear with us...)

temp2, gocmd _readgo temp2, stpcmd _readstp running, ON ;check if input is to be used _readchng state, stop MainLoop ;if not running, ignore input temp2, fwdcmd _readfd temp2, bakcmd _readbk temp2, lftcmd _readlt temp2, rgtcmd _readrt MainLoop state, fwd MainLoop state, back MainLoop state, left MainLoop state, right MainLoop state, go MainLoop state, stop MainLoop _left2 _right2

RC Car Schematics

Project Overview The following is a high-level block diagram showing the data path through the system.

Joystick Input Controller These circuits were connected to the joystick to allow the microcontroller to read steering and speed input from it. The potentiometers R3 and R4 are part of the joystick and change values as the stick is moved around. The current mirrors maintain a constant current flow through the potentiometers so the ADCs can digitize the potential across them in a linear fashion.

Steering Control The steering is controlled by a servo that is powered off the main battery and is controlled through pulse width modulation. The microcontroller pulses the servo about 60 times per second. Pulses that are on for 1 ms turn the wheels all the way left, and pulses for 2 ms turn the wheels all the way right.

Speed Control The Speed control circuit is basically a large push-pull amplifier that can be driven off the port pins of the microcontroller, but can drive enough current to power the motor. The logic gates at the head of the circuit ensure that only one side of the push-pull configuration is active at a time. This protects the circuit from programmer error and from conflicting signals from the microcontroller. The power BJTs are controlled by a combination of the smaller transistors and the current limiting resistors. The diodes across the power BJTs protect them from back EMF when the motor is turned off.

You might also like