Sometimes what should be simple isn't, and what shouldn't be simple is. Case in point: the humble on-off switch.
Before I started designing the current incarnation of the rover I had already started to build the control panel for the power switches. Pretty early on you'll learn that it can be simpler to keep the power supplies for logic and motors power separate; that way you can have different voltage levels for the two without spendy (and possibly inefficient) voltage regulators to cut the drive power levels down to something the logic can use. I also wanted a Run/Stop switch, so the rover could be powered up in auto-navigation mode, but hold it's position until told it was ok to start driving.
Turns out these switches, as beefy as they look, are only rated to ~20A at 12v (I think). At a possible draw of 15A each for the left and right side, I'd look pretty silly if a motor stall caused the switch to weld itself together and start a fire. So I put in one for logic (blue), and two for the left and right drive (red).
And of course there should be some LED's. Just because. (there are actually LED's here; three on the switches, and three just under the switches). The two larger holes under the switches were going to be for panel-mount fuses, but I decided that 'fuses are for sissies'. We'll see about that part... see my prior comments on 'fire'...
Fast-forward to my first drive test with the new rover, and after a round of steering tests I decided to measure the voltage on the drive battery, and it was something like 14 or 15 volts.
It's a 6s LiPO, so the operational range is 25.2v fresh, and 19.2v at minimum; maybe a little more for a safety margin. Below 19.2v discharge (lower than 3.2v per cell) and LiPO's become unhappy, and have shorter lives. The battery seems to have bounced back after a balance charge; time will tell if it really did get damaged.
I always had it in the back of my head to feed the drive and logic power to a pair of voltage dividers, and then measure that with one of the micro-controllers. So before I could continue with development I had to stop and complete that item on the checklist. And at about the same time I had just seen that doing the analog-to-digital conversions over long, noise inducing wires was a Bad Idea.
The parts bucket had a few Atmel Attiny's in it, and after some doodling some possible pinouts I settled on using the Attiny 84 on a small protoboard on the back of the panel to do the ADC and drive the LED's. Turns out that the missle switches I was using have to be wired in an interesting way to get control of the LED somewhat separately from the switch itself.
And since I had two PWM controlled fans mounted just under the panel, near the motor drive PCB's, and a handful of TMP36 temperature sensors looking for a home, I thought that I'd throw in some logic to control the fan speed based on temperature. For good measure I also wanted a servo to control some physical vents, so I could keep the rover body warm; at least relatively warm, as not everything is rated to -40C (or F, take your pick) and cold/hot cycles can be harsh on things.
So now I had a design to hold the voltage dividers, temperature sensor, NPN transistors for LED control, a button input for the Run/Stop switch, a fan PWM output, and a vent servo output. Add in an I2C interface, and every pin on the '84 had a job to do. It looks like this:
Hardware: AT Tiny 84
PORT SW HW | HW SW PORT
--- Vcc -- 1 | 14 -- GND ---
LED_VLOG GP PB0 0 2 | 13 10 PA0 ADC ADC_VLOG
SW_RS GP PB1 1 3 | 12 9 PA1 ADC ADC_VDRV
RST PB3 11 4 | 11 8 PA2 ADC ADC_TEMP
PWM_RS INT0 PB2 2 5 | 10 7 PA3 ADC LED_VDRV
PWM_FAN PWM PA7 3 6 | 9 6 PA4 I2C SCL
SDA I2C PA6 4 7 | 8 5 PA5 PWM VENT_PWM
I didn't realize it at the time, but there are some challenges to doing this with the Arduino IDE and the tiny-core, as good as they both are.
First, the pin names for analogRead are a bit whacked out, and need to use the analog pin names instead of the pin numbers. So analogRead(8) won't work, but analogRead(A2) does. I had tinkered with direct ADC register reads/writes, but analogRead works just fine. I found that I had to carefully build the statement to preserve the float math wrapped in a round() function to get the degrees C the way I wanted it, which is a single byte integer. I haven't tested low temperatures yet, but a byte should hold the range the TMP36 works in just fine, as +/-127C. It probably bottoms out at zero right now.... Hmmm... Anyway...
In my quest to nail down the conversion math (10mV/degree C) I added a 0.1uF cap across the TMP36, and the readings are now much more stable, even on wires long enough to reach down into the body nearer the driver IC's on the MD10C's. I guess I need to order another box-o-caps, since those were supposed to be for decoupling the microcontrollers.
The other trap I fell into was labeling the pin for servo control as 'PWM' and misleading myself. Servos don't use raw PWM, they use a 20ms recurring pulse between 500usec and 2500usec long. SoftwareServo works, but it looks like a hack; needing a call to ::refresh every 20ms or so. Yuck.
So I adapted the idea of using Timer1 interrupts on both match and overflow to control the pulse width and cycle time, respectively, and loop through 3 possible servos, one every 8ms, for a 24ms refresh rate. Better! But it took a couple of days to get it just right. It also means that any pin could do servo control, and you don't need to tie the physical design to where OCxx appears as raw PWM output... but it is handy to use similar pins since in my case OC1B gets messed up by my Timer1 takeover anyway. I stay well away from Timer0, since I need that for base functions like millis(). I think it would mess with I2C as well. Also, Timer1 is 16 bit, which is needed for getting the servo timing durations right with the '84 running on the 8MHz internal oscillator. Prescaler = 1/64, 10 bit fast PWM mode, if you care.
The last minor issue was getting TinyWire to do the I2C stuff just right. It works like a charm. The '84 is pretty busy, so it doesn't always respond to I2C, but it never misses two requests in a row, and only misses one or two every minute or two, if polled once per second.
There were a couple of items that turned out to be simple, in the end. At this point in the build, it was a pleasant surprise...
If anyone needs to know, I poll I2C from the ChipKit Max32, which is a 3.3v device, but unlike the ChipKit Uno it has I2C pins that are 5v tolerant, so I pull those up to 5v. No problems there.
Similarly simple, the fans, Arctic Cooler F80's, will take 'normal' PWM as generated by analogWrite, so you don't need to mess with timers or anything. I use OC0B, which is on Timer0, so it's not affected by my Timer1 use. They won't fully stop spinning at PWM 0, but that's ok. I suppose I could do something clever with the PWM driving a separate MOSFET to control the input voltage... next time.
The reason there are two fans is simple; they are PC case fans designed for 12v, a voltage I don't have, so I wired them in series and fed them from the ~24v drive power. They seem quiet enough, although I'd choose a fan with a lot more CFM next time, regardless of noise.
Oh, the key lock on the panel? That was simple too. The Run/Stop switch is wired through it; if the rover is ever out in public I can lock the R/S switch 'off', so if a small finger finds the glowy button the rover won't think it's starting a race and run over the poor kid, dog, or foot. Simple.
Here is the final protoboard, with only a few minor fix ups. On a side note, the Adafruit protoboards are terrific to work with - way better than wasting time with cruddy boards to save $2.
From top-left (at about the 11pm position), there is the Run/Stop header; I added a transistor there to boost the brightness a wee bit. No there are no limiting resistors. Yes, I'm rethinking that.
At the top-center (right above the AT84 chip) is the vent servo connector, and the fan pwm pin. The fan pwm comes directly off the AT84, so there is no visible jumper wire. Normally I like to arrange things so I can take advantage of a 5-pad protoboard, but it didn't work out in many places this time.
The four resistors are the voltage divider that take the 8.4v and 25.2v from the batteries and cut them down to just under 5v. If I had to do it again I'd use a 3.3v reference, so I wouldn't have to deal with trying to measure 5v with a questionable 5v reference.
The next batch of resistors and transistors on the right side are the LED drivers (sinks, actually...), all NPN's. Originally there were only two, but I realized the way I was using one transistor to light the LED's on two switches would probably be a Bad Idea, since it would bridge the power circuit on a jumper wire on this board, with no current limiting. Yikes!
The big 8 pin connector is the interface to all the wiring on the panel itself. This is so I can remove the protoboard without dragging the entire panel off the rover.
The two connectors on the bottom left (at about the 7 and 8 o'clock positions) are two I2C interfaces; one is an input to the board from the Max32, the other continues on to an RJ-45 connector on the panel itself, so I can plug in an I2C hand controller for testing and manual driving on the end of a standard ethernet cable. These connectors supply everything to the board; power, ground, I2C clock (SCL), I2C data (SDA), servo power, and the last pin is a passthru loop from the hand controller, so if it gets disconnected I can sense it right away. This layout is my standard interface for I2C things, although not all need to have servo power or the loopback wire connected.
The observant among you might notice there isn't a 0.1uF cap across the Attiny yet. I'll wedge it in there somehow.
All of this goop gets tied to a set of registers that control the logic, and are accessible via I2C. Here is the register map:
#define STA 0
#define VER 0 // EEPROM VER
#define I2C_ADDR 1 // This modules I2C ADDR
#define EEPROM_LEN 2 // Amount of reg to store in EEPROM
#define REG_END 3 // Save len for registers
#define VLOG_LOWLIM 4 // Scaled ADC warning level, 6.4v
#define VDRV_LOWLIM 5 // Scaled ADC warning level, 19.2v
#define TEMP_MAX 6 // DEGC to throw warning
#define FAN_ONVAL 7 // DEGC to start fan
#define FAN_OFFVAL 8 // DEGC to stop fan
#define FAN_CMD 9 // Fan prog: auto, on, off
#define VENT_SERVOLOLIM 10 // Servo limit
#define VENT_SERVOHILIM 11 // Servo limit
#define VENT_PWM_VAL 12 // Servo PWM Value
#define VENT_CMD 13 // Vent prog: auto, open, closed
#define FAN_PWM_VAL 14 // Fan PWM Value
#define WEB_DEGC 15 // Scaled ADC
#define RS_SWITCH_VAL 16 // Switch state
#define RS_LED_PWM 17 // RS LED 'Throbber'
#define RS_LED_CMD 18 // RS prog: see table
#define VLOG_VOLTS 19 // Scaled ADC
#define VDRV_VOLTS 20 // Scaled ADC
#define VLOG_LED 21 // On=ready, fast=low voltage
#define VDRV_LED 22 // On=ready, fast=low voltage
#define EXEC_STATUS 23 // Chassis EXEC thinks we are ready (or not)
In a (hopefully) final ironic twist I may add a regulator off the drive power so this little guy will always have at least a stable 5v in case the logic battery is low, and the ADC readings go wonky. I think to handle the step down from 24v it's going to be spendy, like $25 for a DE-SWADJ 3. ~95% efficient, though.
It needs a cooler name, too. I refer to it collectively as 'the panel' now. Boring. Maybe something that makes an acronym. Or not, the world has enough acronyms. Maybe 'Jeff'... "Hey, hit the red switches on the Jeff, m'kay?" Probably the whole rover would then get called Jeff though, as it doesn't have a name, it's just 'Rover'. I'll stick with 'Panel' for now.
What started out as a simple on-off switch sure grew legs, huh?