Sunday, October 9, 2016

The five minute Fitbit hack



And this is what you get for bragging...



My 40-year running buddy JM, distressed and beleaguered former Iraq vet that he is, was recently beset with a Fitbit...corporate America has burdened him with the task of proving his manhood by walking at least 10,000 steps a day.

After listening to his combination of guilty-conscienctiousness and frustrated bitching for a few weeks, I begin to brag...'I can, in five minutes, write-a -program/wire-up-some-hardware that will obviate your physical participation in this ridiculous encumberation.'

And after 10,000 legitimate steps on the river walk, several beers on the porch, and a fantastically unusual Oregon Indian summer Sunday afternoon, I realized I would be forced to walk the walk.  Fair enough!  Results are below.  My hardware setup consisted of an Arduino Uno, three jumper wires to the servo (Radio Shack 'standard'), a 5" long 1/8" wood dowel, some cellophane tape...and the logger module from JM's fitness torture device.

And, voila...the quintessential 19-line Arduino program that will allow you to win that case of Gatorade at the company pool next week.

/* servo slew 1 */
//
//
#include <Servo.h>
Servo myservo;

void setup() {
  pinMode(13, OUTPUT);
  myservo.attach(3);
}

void loop() {
  int x, y;
  digitalWrite(13, HIGH);
  x = random(650, 1200);
  myservo.writeMicroseconds(x);
  y = random(350, 600);
  delay(y);
  digitalWrite(13, LOW);
  x = random(1000, 2050);
  myservo.writeMicroseconds(x);
  y = random(350, 600);
  delay(y);
}


The blinks of the LED on pin 13 aren't strictly necessary, just there for debugging purposes.  I randomized the delays as well as the right- and left- slew to simulate the uneven gait of most walkers.  My first attempt was even shorter, simply slewing between 20 and 120 degrees and back, once per second, but the Fitbit module didn't log near as many steps that way...

Forthwith, JM and I adjourned to the local brew pub for some quality hours with our favorite blend of mere Irishness and outright beligerence; two hours and ten minutes, four 22-ounce pints, and several thousand jokes in questionable taste later....

11,500 steps!

We do NOT feel guilty about this.  It is as if Everest were in our back yard, and Sir Edmund Hillary had asked us if we would like to tag along for a little hike.  JM ended up with 25,000 for the day and quite a bit of good will with the waitresses...

Tuesday, October 4, 2016

avra if you REALLY want it painless

Assembler wars continue...

At this point, if you are as frustrated with all assembler gyrations as I am, you would wonder if all this pain over the best and most pious AVR assembler is really worth the trouble.  avr-as and the GNU toolchian are all very well, but after wrangling with EEPROM programming for another few days, I was beginning to worry about my sanity.

While doing some more futile googling to figure out what I was doing wrong...I discovered that there were at least two more AVR assemblers for Linux beyond the GNU toolchain: 'Tom's AVR Assembler' and 'avra'.  They are both a bit old; 'tavrasm' was last maintained in 2005, and the last activity with 'avra' on Sourceforge was in 2010.  But the big difference: BOTH are intended specifically to assemble Atmel's avrasm-formatted files, and both include a superset of Atmel's assembler directives.  The latter of which, as it turns out, I am pretty familiar.

In my last post I documented my frustrations with the 'AVR-libc' header file gibberish, and demonstrated how to adapt Atmel's include files to work with avr-as, so that using the other garbage wasn't strictly necessary.  With a lot of trial-and-error, I managed to get almost everything to work...except a fully useful way to define EEPROM data and link addresses that would point to it.  I could handcode a .hex file (ech).  Or create a separate EEPROM file (faking in the data as if it was in program space), assemble it, then look at the list file and add constants to the main program file that refer to the right addresses (double ech).

Ugly.  Non-standard.  Not very portable.

Back in Atmel land...

And then I discovered tavrasm, and realized that I could return to a more sensible assembler syntax that was AVR-specific, use the standard Atmel *.inc files...errrrrr...sort of.  But then the honeymoon ended, the romance was over....

Yeah.  Tom Mortensen last worked on his assembler in 2005.  His 'macro in macro' expansion of the assembler directives was just a BIT undocumented.  I spent half a day looking vainly for code examples, experimenting with conditional assembly directives that didn't work, wondering why there weren't any command line options to generate a .list file even though there were documentation examples and directives for it.....The list of devices supported by tavrasm is/was VERY limited: it did NOT include any ATtiny chips, among other things...and there are a few instructions on the ATtiny (like  MOVW, LPM <register>, Z+ and, more critically, SPM) that are too good to give up.

To add the basic support for the ATtinys (sizes of the various memory spaces), I edited the device header file and recompiled, but there wasn't any easy way, short of learning yacc and lex again, then hacking through Tom's code and Atmel's part docs, to add support for the newer instructions.  Arrrrrrr....another afternoon wasted.

So I turned to avra, and all my problems went away, including the vast frustrations I was beginning to have with Tom's macros.

Here's the 'avra' version of Blink26.S:

--------------------------------------------------------
;
; blink26.S
;
; ** LOAD IO DEFS FOR THE RIGHT DEVICE
;
.nolist

.include "avrinct/avrcommon.inc"
.include "avrinct/tn26def.inc"
;
;    ** GET ME SOME HANDY MACROS **
;
.include "avrinct/macros.inc"
;
;  *** DEFINES HERE ***
;
.list
.equ F_CPU = 8000  ; 8Mhz clock, for msec_delay define in 'macro.inc'
.equ OUTPIN = PA7  ; for t26
.equ IO_OS = 0x20  ; for lds/sts with i/o ports, e.g 'lds r2, (IO_OS + TCNT0)'
;
;  *** SRAM RESERVES HERE ***
;
.dseg
;  FOR example:
;<datalabel>: .byte 1  ; to reserve 1 byte in SRAM for <datalabel>
;
;  *** RESETS, IVECTORS, AND MAIN PROGRAM ***
;
.cseg
.org 0x0000
reset:
    rjmp main;  *** tn26 interrupt vector names, just fer handy ***
    reti        ; 0001 INT0_vect
    reti        ; 0002 PCI0_vect
    reti        ; 0003 TIMER1_OC1A_vect
    reti        ; 0004 TIMER1_OC1B_vect   
    reti        ; 0005 TIMER1_OVF_vect
    reti        ; 0006 TIMER0_OVF_vect
    reti        ; 0007 USI_START_vect
    reti        ; 0008 USI_OVF_vect
    reti        ; 0009 EERDY_vect                EEPROM Ready
    reti        ; 000A ACOMP_vect             Analog Comparator
    reti        ; 000B ADC_vect                 ADC Conversion Complete   
;
;
main:
    spsetup r16, RAMEND  ; use the macro to set up the stack
    sbi DDRA, OUTPIN        ; set pin 'OUTPIN' on port A to high for OUTPUT
    ldi ZL, low(array * 2)  ; to calc address right for LPM later
    ldi ZH, high(array * 2) ; ditto
blink:
    sbi PORTA, OUTPIN        ; then drive it HIGH
    lpm r20, Z
b2:
    msec_delay 1, r17, r18, r19  ; 1 ms wait * r20 times
    dec r20
    brne b2
    cbi PORTA, OUTPIN        ; then LOW...
    lpm r20, Z
b3:
    msec_delay 1, r17, r18, r19  ; 1 ms wait * r20 times
    dec r20
    brne b3
    adiw Z, 1
    lpm r20, Z
    tst r20
    breq end
    rjmp blink;
;
end:
    rjmp end;
;  *** MAIN PROGRAM END ***
;
; PROGRAM MEMORY DATA HERE
;
array: .db 10,250,20,240,30,230,40,220,50,210,60,200,70,190,80,180,90,170,100,160,110,150,120,140,130,0
;
;
;

--------------------------------------------------------

'avra' has excellent documentation, particularly the macros; there are many, many code examples and the expansion from Atmel's basic functionality allows

Things in the above code are just clear, not near as much mud.  We have .dseg, .cseg, and .eseg directives; a very clear difference between register (.def) labels and symbolic constants (.equ); runtime constant expansions using .set, excellent conditional compilation directives; .list and .nolist for disassembly and automatic generation of Intel hex code, list and EEPROM hex files.

Now I can use the straight Atmel *.inc files with almost no customization, or even use the conditional directives to make them even more handy.  The inclusion of 'avrcommon.inc' takes care of some of the universal constants and macros, including stack initialization.

Macros are a bit easier, and a lot more powerful; here's part of my rewritten utility file:

 --------------------------------------------------------
;*
;* macros.inc file
;*
;*
;

...
...
 ;
;
; ***
; *** inline millisec delay loop
; ***
; *** max is about 11000
; *** !!and be sure to define F_CPU in main program for both like below!!
;
;.equ F_CPU, <CPU mhz * 1000>  (e.g. for 8Mhz use 8000)
;
;
; delay = @0, ra = @1, rb = @2, rc = @3
.macro msec_delay
    ldi @1, ((((@0 * F_CPU - 1) % 327680) % 1280) / 5)
    ldi @2, (((@0 * F_CPU - 1) % 327680) / 1280)
    ldi @3, ((@0 * F_CPU - 1) / 327680)
m_%:
    subi @1, 0x01
    sbci @2, 0x00
    sbci @3, 0x00
    brne m_%
    nop
.endmacro
; ***

 --------------------------------------------------------

Since avra is AVR-specific, the Makefile is REALLY streamlined.  avra doesn't need  the one-size-fits-all command-line options and it doesn't have to do any linking; directives in the assembled file handle almost everything:

<prompt>:avra -I <librarypath> test.S -o test.hex -l test.lst

...literally does the work of four separate functions in my previous Makefile, so you get all these files from one command:

test.hex     - the uploadable file, already in Intel hex
test.eep.hex - the EEPROM file, if needed
test.lst     - disassembled list file for sanity checking 
test.obj     - whether you need the object file or not

My current assembly Makefile, still generic but without C code support:
------------------------------------------------------------------------------------------------------
P=test
UC=t26
UPNUM=0
APRG=arduino
BUPATH=./back
VERSION=01
TS=`date +%H%M%S-%Y`
ifdef VER
    TS:=$(VER)
endif
ifdef PRG
    P:=$(PRG)
endif
ifdef MCU
    UC:=$(MCU)
endif
ifdef UPN
    UPNUM:=$(UPN)
endif
UPORT=/dev/ttyACM$(UPNUM)
AFLAGS=-I /home/zenas/mylib/
AS=avra
DUDE=avrdude

$(P).hex: $(P).S
    cp $(P).S $(BUPATH)/$(P)_$(TS).S; $(AS) $(AFLAGS) $(P).S -o $(P).hex -l $(P).lst

hex: $(P).hex

$(P): $(P).hex

toucha:
    touch *.S

clean:
    rm *.hex; rm *.obj; rm *.cof; rm *.lst
  
upload: $(P).hex
    $(DUDE) -P $(UPORT) -p $(UC) -c $(APRG) -b 19200 -U flash:w:$(P).hex


get:
    $(DUDE) -P $(UPORT) -p $(UC) -c $(APRG) -b 19200 -U flash:r:$(UC)-$(TS)-get.hex:i


--------------------------------------------------------

With all that streamlining, I went ahead and added the 'upload' and 'get' routines using avrdude.  Both of those functions assume you are using an Arduino connected to a USB port as a programmer.  You could probably also use it to put assembly code directly on an Arduino, if you really wanted to do that...

Since the whole idea isn't to go blind, or broke, or murder my cat before I get a real project finished...I think I've found something tolerable to use for those functions where assembly on the bare metal is the best solution.  avra meets all the qualifications; we can always use AVR-libc for C stuff, as it was intended, and look at the disassembly listings occasionally if we want to poach some of the tricks gcc might use to optimize AVR code; the 'msec_delay' macro above was specifically stolen from gcc output, for instance.