One problem with small robots is limited space. This makes sheilds, motor controllers and wires difficult to fit. For this reason I tried to incorporate a few useful devices onto a controller while keeping the cost low.
The end result is a controller 30mm x 60mm with built in dual 1A FET H bridge, 3-axis accelerometer and IR receiver. Up to 7 servos can be plugged directly into the PCB and powered directly from the battery.
The H bridge was a no brainer, most people want to add at least 2 DC motors to their robot for locomotion. The A3906 chip I've used has several advantages. It uses FETs instead of transistors so that more of your battery power goes to the motors. It can work on voltages from 2.5V - 9V. It has electronic braking and it has built in current limiting. I've set this to 900mA so that even if a motor stalls for an extended period of time no damage will be done to the controller.
I chose to include a 3-axis accelerometer because it can eliminate the need for bump switches. The magnitude and direction of an impact or collision can be easily determined and used to guide the robot away from any obstructions. The accelerometer can also warn the robot if it is going to fall over and be used as part of a self righting system.
I included the IR receiver because it provides a cheap and easy method of remotely controlling a robot using a common TV remote. This can also be used to locate a navigation beacon / docking station.
As small robots often run on small batteries this controller can run on voltages as low as 3.6V with a maximum input of 9V. The ability to run on 3.6V has required that the processor run at 3.3V so unfortunately the clock speed had to be reduced to 8MHz.
5V devices can be used with minimal interfacing. 5V digital I/O sensors should have a 4K7 resistor in series to limit the current in the MCU's clamping diodes. 5V analog sensors need a 2 resistor voltage divider (eg. 5K & 7.5K) to bring a 5V signal down to 3V.
Now, with the help of my fellow LMR members I am trying to write a library to suit the controller. This is where it gets difficult for me as programming is not my area of expertise. My first problem was that I have not written a library before and the tutorials I found tend to assume you are an experienced C language programmer. As I have programmed in basic most of my life and do not know a class from a data struct I needed help.
With the help of cr0c0, Maus and mogul (alphabetical order so as not to offend) and support from Grog, Rogue and RobotFreak I began creating my library.
There are a number of technical difficulties to overcome to make this controller work well. Firstly in the initial hardware design, pins had to be chosen carefully to allow maximum functionality. As PWM on pins D9 and D10 are disabled by the Servo library they could not be used for the H bridge. I left D2 free as it can be used for an external interrupt but was forced to use D3 as part of the H bridge control in order to use electronic braking.
Now I am running into timer restrictions.
Timer0 is used for time functions in the Arduino IDE such as delay(), micros() and millis() so that was strictly off-limits.
Timer1 is used by the Servo library. As servos are commonly used in small robots I wanted to leave Timer1 alone.
Timer2 is the only timer left.
I want to use Timer 2 for impact detection which requires regular monitoring of the accelerometer but it is also used for the tone() command and Ken Sherriff's IR receiver library.
As I would like my little robots to detect impacts, make noise and receive IR commands all at once I am now trying to write a timer interrupt based library fuction that does all 3 from timer 2 - Impossible? Out comes the digital oscilloscope.
Impact function goes on a diet:
My biggest problem is that reading a single analog input takes about 260uS and thus my Impact detection routine that must read 3 inputs takes almost a full mS.I have modified the code so that it alternates between reading the X, Y and Z axises. Because only one axis is read each time the function is called this reduces the function time to about 350uS.
As the robot vibrates upon impact which can lead to false triggering after an initial impact I have written the code so it will stop taking readings for about 500mS after an impact. This also helps a bit.
After doing some test I found that I can set the timer interrupt to call a function every 400uS and that the impact readings can be reduced to 1 in 5 (effectively taking a reading every 2mS) and still give reliable readings. I may be able to improve this later by changing the hardware filtering of the accelerometer outputs.
Minimum requirements for IR receiver decoding:
After studying the signals produced from a few different remotes I think I can decode them using the 400uS timer interrupt. The digital 0's had a width from about 600uS (Sony) to 450uS(Phillips) The protocols I looked at all had a large start bit 2-4mS in width. By checking the IR receiver pin for a low state every 400uS this can be easily detected and if necessary the impact detection can be temporarily disabled to allow a signal to be decoded.
With an interrupt frequency of only 400uS (2.5KHz) the robot can make beeps and general robot noise but music would not be possible. The simplest solution is to just use the tone() command (during which time impact detection and IR commands are temporarily disabled) and then re-configure Timer2 afterward.
After some thought, a flexible timer configuration:
Since my original attempt at writting this library I have now changed the timer mode. Originally the timer was configured for CTC mode. I have now changed it to overflow mode. This requires the ISR to reload the timer with a value and therefore my software can change the time interval between interrupts.
The reason I want a flexible time interval is that when no IR signal is being detected and only impact detection is running then the ISR only needs to be called about once every mS. This is enough to monitor the IR receiver when no signal is being decoded. The impact detection function can be configured to only take a reading every second function call.
When an IR signal is detected then the function can reduce the time interval between interrupts and delay ipact detection until after the IR signal has been decoded.
Before I went any further I decided to test what affects my timer interrupt had on servos. The answer: not good :(
I cannot read a single analog input without my servo doing a deranged dance on the table. Suddenly the interrupt flag from my earliest version of the software is looking much better.
Problem 1 soved:
Flags are the answer! For those new to programming, a flag is a bit, or in my case a byte that is set or reset when an event occurs. The reason flags have solved my problem is that during a interrupt service routine, other functions are suspended. If your ISR is very long as mine was it then interfers noticeably with other timed events.
Now my ISR which is normally triggered once every millisecond simply increments a byte which is used to determine how often the impact detection occurs and raises a flag (sets another byte to 1) if the IR receiver output is low.
ISR(TIMER2_OVF_vect) // timer compare interrupt service routine
TCNT2 = t2; // reload timer
tiflag++; // increment tiflag
if(digitalRead(4)==0) irflag=1; // set IR flag if signal detected
This takes very few clock cycles to complete. Even if does happen just before the servo timer interrupt was about to occur it would only delay the pulse by a few microseconds.
With the digital oscilloscope monitoring a servo output on D8 and my timer 2 interrupt which is pulsing D7 I am now getting smooth jitter free results while at the same time my impact detection function is reporting results via the serial monitor. Currently my impact detection routine is now called when my timer interrupt flag (tiflag) =2 after which it resets the flag back to 0. This causes the impact detection function to be called once every 2mS.
A bit more experimentation showed that I did not need a flexible timer system. I can now set my timer intervals to 100uS and simply change the value at which my tiflag (which simply counts the timer interrupts) calls my impact function.
With my timer interrupts at 100uS, decoding IR signals is much easier and music is possible. Admittedly anyone who tunes or plays musical instruments for a living might disagree, but the note frequencies should be close enough at lower octaves.
I have started with my IR decoding function. I thought I would try writing code that would measure pulse widths which is how the Sony IRC works. So far it is about 50% there. Lots of bugs to sort out but it is now 10:30pm and I need a break.
After going through my IR decoder code which uses a similar method to some of my previous robots I decided to scrap it and rewrite it. With the digital oscilloscope doing a lot of debugging for me I finally worked out my problems.
I decided to stick with the Sony protocol (same as picaxe uses) because it is based on pulse widths which are easy to measure using the timer interrupt. The entire IR decode function ended up as part of my timer ISR.
I checked the timing functions of a servo which was sweeping using a millis() command to control the speed (testing timer0 and timer1) and everything is running smoothly!
This is how my ISR looks now:
ISR(TIMER2_OVF_vect) // timer overflow service routine
TCNT2 = t2; // reload timer
tiflag++; // increment tiflag used to call Impact function
//----------------------------------------------------------- Sony IRC Decoder ------------------------------------------------------
if(digitalRead(irpin)==0) // check state of IR receiver output
irflag++; // increment irflag used to measure ir pulse widths
newbit=1; // recognizes a new bit being measured
else // IR receiver output high - end of bit
if(irflag>4) // bitwidth>4 = logic "1"
irdata+=irbitvalue; // increment data by bit value
irbitvalue*=2; // bitvalue updated for next bit (1,2,4,8,16,32,64,128)
irbitcount++; // count the bits
if(irbitcount>6) // only want first 7 bits
irbitvalue=0; // additional bits have a value of 0
ircommand=irdata; // make data available to user
if(irflag>8) // start bit resets decoder values
newbit=0; // addional interrupts not new bit
irflag=0; // reset irflag when IR pulse ends
Note: I have changed the timer settings to interrupt every 200uS (5KHz).
Currently I have focussed on functions that are dependant on a timer interrupt as these are the most difficult to implement.
For now I will forget about music. I'm fairly certain it is just easier to use the existing tone() library and then call microM.Setup() afterwards to re-initialize timer 2. In that case the IR and Impact functions would be briefly disabled while the tone() library is in use.
Testing showed that while the Tone() library did disable the IR and Impact commands the timer was easily re-initialized after a tune was played and then all functions were back to normal. I don't think this will be an issue in most cases.
Later I will add some simple functions for the "H" bridge and that should be about it.
I have made a large number of changes to my original program. The 2 biggest changes are:
Changing the structure to use flags in the ISR. This prevents the Impact() function from affecting other timer based events.
Addition of a SIRC decoder to read Sony IR controllers.
The attached folder "Impact_IR.zip" contains the working code that needs to be made into a library.
The attached folder "microM.zip" contains my attempt to convert the file into a library.
Judging from the errors I got when it tried to compile I think that the local variables for the ISR have not been implemented correctly in the microM.h file. As the ISR was not mentioned in the original sample I'm not sure how to fix it.
The original library was named Micro.M but the code used micro.M. I decided that the micro.M was easier on the eyes and changed all instances of Micro.M to micro.M. Perhaps this has also caused a problem?
I will attempt to work out where I went wrong but in the meantime I am hoping someone with more experience can have a look at where I went wrong.
Once it is all working again I need to find out is there a more user friendly way of utilizing the library? Currently the inputs and outputs are all global variables. Originally I was trying to avoid global variables but I'm not sure there is a better way that would be as flexible.
With the help of RobotFreak I got the new libray going. It wasn't perfect as we had to use global variables to get the ISR working but all functions were working. I then went to write a simple function for the "H" bridge.
AWW CRAP! - I forgot that timer 2 affects the PWM on pins 3 & 11 which are used for the left motor control. I think I am going to have to change my ISR again!
I suppose I should explain here that because the A3906 has electronic braking I wanted to make good use of it. Rather than just having the brakes slammed on hard I have connected it so that PWM can control the braking as well as the speed. This requires 2 PWM pins for each motor.
Pins 9 and 10 are not used because PWM on those pins are disabled by the servo library. Pins 5 and 6 are driven by timer 0 and are used to control the right motor which works perfectly.
As so many libraries use timer 2, I've decided to modify the PCB design so that only PWM pins 5&6 (Timer0) are used for motor control. This is not a big deal as I want to make some small modifications to the PCB anyway.
Electronic braking will still be available but it will be either full brake or no brake. As a bonus, D3 will now be made available as an external interrupt pin.
The fact is, it is almost impossible to get a product perfect first go. It usually takes about 3 revisions to perfect. At least when the Micro Magician does hit the shops you'll know that it has been put through it's paces and designed for maximum flexibility.
By the time the new PCB is ready for testing I will have the library finished and possibly some improved features if we can squeeze them in!