Scorpion XL Custom Firmware

This is just a placeholder for some ideas percolating in my brain. In fact, it gets more and more disorganized as I go through this, so if you're looking for coherent information, it ain't here. Once I get things figured out I'll re-organize into a tutorial.

For (so far) all of my 3-pound combat robots -- and most of those my students are building -- we're using the Scorpion XL speed controller. They've served us well.

It turns out these use an ATmega168 microprocessor, meaning they're in the same architecture family as the Arduino microcontroller and should be programmable by the same tools. So here's my thinking:

  1. Save the proprietary Scorpion XL firmware on my computer, in case something goes wrong. The avrdude utility (which can be found in the \hardware\tools\avr\bin directory of an Arduino installation) can do this.
  2. Figure out how to upload custom firmware on it. The Tx/Rx pins, as well as the ICSP pins, are exposed on the board, so this shouldn't be a huge problem. It probably won't have an Arduino-style bootloader on it, however, so I'll likely need something like the Pololu USB AVR Programmer. (Apparently the UsbTiny programmer has issues with Windows 7.)
  3. Determine the mapping of solder points, header pins, and from-the-radio-receiver signal (yellow) wires to Arduino pins (or ATmega168 pins, which have a different name).
  4. Use the XL as both a speed controller and a robot brain for (semi-)autonomous tasks. At the least, I should be able to
    1. communicate with I2C devices or, alternately,
    2. with analog sensors, since the I2c pins double as analog inputs,
    3. and/or TTL serial devices (through the Tx/Rx pins)
    4. and/or "Arduino" pins 10, 11, 12, 13 (these are the ICSP pins)
    5. change the speed-response curve (XL only allows linear and exponential in stock firmware)
    6. monkey around with controlled braking vs. free-speed braking (?)

I'm curious if any of the Scorpion's jumper pins are just exposed digital I/O pins from the ATmega168.

Useful modifications

  • Connect an Xbee directly to the Scorpion for a custom Xbee-based radio control scheme (using the Tx/Rx pins). This would save the weight and hassle of using an Arduino Nano or Mini on the 'bot just to run the Xbee.
  • If enough analog inputs are exposed, use them to monitor LiPo battery voltage through the balance connector. (Requires one analog input per LiPo cell, as each input measures from 0V to +5V.)
  • Control an LED light show. For bling.
  • A quick look at the on-board LEDs suggests they're probably programmable...and may affect current/voltage on other pins depending on how they're connected. Be cautious!

Thoughts on pin-outs

What I can see:

  • 6 pins: ICSP header ("Arduino" pins 10-13, plus a power (+5?) and ground). For purposes of orienting the AVR programmer cable, VDD is upper-left, GND is upper-right. Red conductor should be to the left when connected to Scorpion)
  • 8 7 pins: Jumper header (note: the upper-left jumper header pin is connected to the upper-left ICSP header pin). 3 of these are probably either ground or power as they are 1/2 of a 2-pin jumper pair (my guess is power -- that way the ATmega168's internal pull-up resistors are used, eliminating the need for on-board resistors).
  • 2 pins: Tx/Rx pins ("Arduino" pins 1 and 0, respectively)
  • 3 pins: signal wires of the FLIP channel servo-connectors
    • the solder point to the immediate left of Tx *seems* to be connected to the FLIP channel signal wire
  • 1 pin: additional solder point immediately beside Rx
  • Many break-out holes for ground and/or power

From the above, I count 14 available pins.

Assumptions:

  • motor LEDs are tied to the PWM pins used as enables for the motors (i.e., they don't have a dedicated pin)
  • the overall status LED has its own pin
  • I'm guessing it's using four of these as half-bridges, so 4 pins reserved for the IN inputs and 2 pins to supply PWM to the INH inputs.

At most, I should have 13 pins available, and no more than 4 of them PWM-ready. So, off-by-one from the visible pin count. I bet that beside-Rx-solder-point is just a break-out for something else (judging from the traces, I suspect it's a +5v tap). And we know at least 2 of the available pins (the I2C pins) are analog.

Arduino IDE Modifications

Using a Scorpion XL with Pololu AVR programmer, add the sections below to the boards.txt and programmers.txt files.

In boards.txt:

scorpionxlAVR.name=Scorpion XL (ATmega168, 5v) AVR
scorpionxlAVR.using=pololu
scorpionxlAVR.upload.maximum_size=14336
scorpionxlAVR.upload.speed=19200
# scorpion xl does not have an external clock like Arduino, so
# fuse bits must reflect using the internal 8 MHz oscillator!
scorpionxlAVR.bootloader.low_fuses=0xe2
scorpionxlAVR.bootloader.high_fuses=0xdd
scorpionxlAVR.bootloader.extended_fuses=0xf9
scorpionxlAVR.bootloader.path=atmega
scorpionxlAVR.bootloader.file=ATmegaBOOT_168_diecimila.hex
scorpionxlAVR.bootloader.unlock_bits=0x3F
scorpionxlAVR.bootloader.lock_bits=0x0F
scorpionxlAVR.build.mcu=atmega168
# using internal atmega168 oscillator @ 8 MHz
scorpionxlAVR.build.f_cpu=8000000L
scorpionxlAVR.build.core=arduino
scorpionxlAVR.build.variant=standard

In programmers.txt:

######################################################################
# Orginal from b.cook ...
pololu.name=Pololu USB AVR Programmer
pololu.communication=serial
pololu.protocol=avrispv2

Commands (Assuming Pololu AVR Programmer)

  • Backup the stock firmware: avrdude -C ../etc/avrdude.conf -p m168 -P COM5 -c avrispv2 -U flash:r:"e:/User Files/Lewis/project ideas/scorpion xl reprogram/factorycode.bin":r
    • -C ../etc/avrdude.conf (I'm running this from the Arduino-installed AVR tools directory and it needs to know the location of the conf file)
    • -p m168 (specifies ATmega168)
    • -P COM5 (the COM port my AVR programmer is using)
    • -c avrispv2 (this is the programmer-id for the Pololu AVR Programmer)
    • -U "...stuff here..." says read from the target device's flash memory and save to a local file
  • Restore the stock firmware: avrdude -C ../etc/avrdude.conf -p m168 -P COM3 -c avrispv2 -U flash:w:file.bin:r

[My "good" scorpion and the "broken" one from work have identical firmwares.]

Verbose output (stock firmware)

  • D:\arduino-1.0\hardware\tools\avr\bin>avrdude -C ../etc/avrdude.conf -p m168 -P COM5 -c avrispv2 -v
avrdude: Version 5.11, compiled on Sep  2 2011 at 19:38:36
         Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
         Copyright (c) 2007-2009 Joerg Wunsch
         System wide configuration file is "../etc/avrdude.conf"
         Using Port                    : COM5
         Using Programmer              : avrispv2
         AVR Part                      : ATMEGA168
         Chip Erase delay              : 9000 us
         PAGEL                         : PD7
         BS2                           : PC2
         RESET disposition             : dedicated
         RETRY pulse                   : SCK
         serial program mode           : yes
         parallel program mode         : yes
         Timeout                       : 200
         StabDelay                     : 100
         CmdexeDelay                   : 25
         SyncLoops                     : 32
         ByteDelay                     : 0
         PollIndex                     : 3
         PollValue                     : 0x53
         Memory Detail                 :
                                  Block Poll               Page
      Polled
           Memory Type Mode Delay Size  Indx Paged  Size   Size #Pages MinW  MaxW   ReadBack
           ----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
           eeprom        65    20     4    0 no        512    4      0  3600  3600 0xff 0xff
           flash         65     6   128    0 yes     16384  128    128  4500  4500 0xff 0xff
           lfuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           hfuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           efuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           lock           0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           calibration    0     0     0    0 no          1    0      0     0     0 0x00 0x00
           signature      0     0     0    0 no          3    0      0     0     0 0x00 0x00
         Programmer Type : STK500V2
         Description     : Atmel AVR ISP V2
         Programmer Model: STK500
         Hardware Version: 15
         Firmware Version Master : 2.10
         Topcard         : Unknown
         Vtarget         : 4.6 V
         SCK period      : 3.3 us
         Varef           : 0.0 V
         Oscillator      : 3.686 MHz
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.02s
avrdude: Device signature = 0x1e9406
avrdude: safemode: lfuse reads as E2
avrdude: safemode: hfuse reads as DD
avrdude: safemode: efuse reads as 7
avrdude: safemode: lfuse reads as E2
avrdude: safemode: hfuse reads as DD
avrdude: safemode: efuse reads as 7
avrdude: safemode: Fuses OK
avrdude done.  Thank you.

Fuse Setting, deconstructed

Note that a "1" means "not programmed" and a "0" means "programmed"

  • From the low fuse
    • CDIV9=1
    • CKOUT=1
    • SUT1=1, SUT0=0
    • CKSEL3=0, CKSEL2=0, CKSEL1=1, CKSEL0=0 (8 MHz internal clock used)
  • From the high fuse
    • RSTDISBL=1 (reset on PINC6 is enabled)
    • DWEN=1
    • SPIEN=0 (SPI pins are enabled -- we know these are exposed on the board because we can connect an AVR programmer)
    • WTON=1
    • EESAVE=1
    • BODLEVEL2=1, BODLEVEL1=0, BODLEVEL0=1 (brownout detection at Vcc=2.7 V)
  • Extended fuse
    • BOOTSZ1=1, BOTTSZ0=1
    • BOOTRST=1

Disassembly

  • (Convert .bin to .atmel_generic format using the srec_cat command): srec_cat scorpionxl-flash-factory.bin -binary -Output scorpionxl-flash-factory.atmel_generic
  • (*Potential* disassembly -- this is a "forced" disassembly from the .bin file, meaning some data was probably mis-interpreted as asm): avr-objdump -D --target=binary --architecture=avr scorpionxl-flash-factory.bin > flash-factory.avr.asm
    • First instruction is a jumpt to 0x68, which appears to be a valid instruction. All instructions between 0x0 and 0x68 are alternating pairs of RETI and NOP; according to the datasheet, the first 0x32 of these are supposed to be interrupt vectors, so all seems well (with just some extra space for good measure?)

Digging Through ASM to Find Pin References

ATmega168 pins are organized into 8-bit registers called ports such that one port can store the I/O bits for up to 8 pins (one pin per bit). For example, Port B (PB7:0) contains eight general-purpose I/O pins. I'm searching through the ASM to find references to the memory addresses which map to ports (hacked up a Ruby script to do the job):

*The first value is used when accessing the register directly (IN, OUT, SBI, CBI instructions); the parenthetical value is if accessed as a direct SRAM memory location (ST, LD instructions).

On an I/O pin port, the following rules apply:

  • DDR bit for that pin = 1 implies the pin is output; input otherwise
  • PORT bit is the logic state of the pin; writing a 1 to it when it is an input, however, will set the pull-up resistor (see the messiness that was pre-Arduino 1.0 and setting the pullups)

In interpreting the above table, if I found a 1 in a DDR register, I call that an output. If I find a 1 in a PORT register that is not already and output, I call that an input with the pullup resistor enabled. Anything else is marked as a potential input, i.e., it is definitely not an output but could simply be an unconnected input.

Scanning through the code I found writes to the following PWM-configuration registers:

I'm going to make a guess that either of OC0A or OC0B is one motor-enable output and either of OC2A or OC2B is the other. Reasoning: Take the 0x24 register as an example. What we know is that one or both of the PWM outputs was maybe configured, but we don't know if it's both or either specific one. The same goes for the 0xB0 register. Since we only have two motors, best guess is each register is only configuring one of its associated PWM pins.

Now, try to resolve that with what we know about outputs (two tables back)...and OUCH: all four of the PWM pins in question are configured as outputs. Some of them act as ICSP pins -- will the presence of an ICSP signal automatically override the PWM functions or is software required to differentiate?

After adding more support for arithmetic instructions, I got 241 written to 0x24 and 0 to 0x25. According to the chip specs, this says Arduino pins 5 and 6 are definitely set for PWM. Gonna make a big guess those are the motor enable outputs. Revised guess: 6 may be a PWM enable, but I'm thinking 5 may be an INH for the right motor. Updated Revised Guess: But what if all four INH inputs are PWM (which would allow LED brightness based on speed) and the IN inputs are binary????!?!?!? Yet another update: yes, the motor LEDs have brightness based on speed; I checked on my functional 'bot.

* ENC/rc refers to the 4-column-by-2-row pin header labeled "ENC", where r is the row and c is the column (zero-based)

**ISP/rc refers to the 3-column-by-2-row pin header labeled "ISP", where r is the row and c is the column (zero-based)

Half-Bridge Pin Mapping

The ESC has four half-bridges (two per channel), probably from this family of devices. (Reasoning: he lists the BTN7960B half-bridge on the downloads page as a "key component", though he is not specific as to which device (Scorpion XL, XXL, or OSMC) to which it applies. Visual inspection of the chips on the board, and tests of certain pins, support that the XL uses this component.

The key pins for the half-bridge are shown below:

Pin descriptions:

  • INH (inhibit): When zero, the half-bridge is "off", regardless of the state of IN. When one, the half-bridge's state is determined by IN. This would be connected to a PWM input on the Arduino -- paralleled to the companion half-bridge's INH. (On the physical device, this is pin 2, second from the left if the small pins are facing down.)
  • IN (input): the input signal for the half-bridge. When 1, the high-side switch (HSS) is active, otherwise the low-side switch (LSS) is used. In combination with the IN pin on the companion half-bridge, this is used to set the direction of the motor, as well as select between powered braking and free-spin braking. (pin 3 on the physical device)
  • IS (current sense): TODO (pin 6 on the physical device)
  • OUT: the powered output of the half-bridge, connected directly to the drive motors. Speed of the motors is controlled by the duty-cycle of the powered output (effectively, OUT is a power-amplified version of the logic-level PWM on the INH pin). (pins 4 and 8 on the physical device, the latter being the huge tab at the top of the device.)

I'm guessing that the motor LED's are in parallel with the motor power tabs with green oriented one way and red the other...and not driven directly by their own I/O pins.

It is possible to handle the forward, reverse, left turn, and right turn maneuvers with just two bits: one for the left IN's and one for the right IN's, assuming the dual IN's on each motor are the complement of each other. Since I seem to be running out of output-configured Arduino pins, I'm going to guess that A2 and A3 (or whichever of 3, 5, and 6 is not a PWM channel) is being used for this.

Useful Links