Wednesday, August 17, 2016

tiny45 Programming through an Arduino, Part 3

...and the last lap?  We would like to use an Arduino as a generic AVR programming device: leverage the low-end tools that come with Linux and the Arduino IDE to do stuff the gods of high-level programming never intended.

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

And there are other benefits of programming on the bare metal this way, if one's willing to do a little homework.  It is certainly cheaper:  a bare Mega328 costs $5 or less, as compared to $10 for the cheapest possible Arduino; and the tiny45 can be bought in volume at $0.99 a device!  And knowing a little bit more about how AVRs work, and how the programming tools do their job, can't hurt.

One of the problems with the Arduino IDE is it DOES bloat the code a bit; this isn't a problem on most Arduinos, which have a relatively large amount of flash and static RAM compared to the size of the bootloader and the more routine sample programs.  On the older and smaller devices, though, program and static RAM space is a major issue.

  /*
Blink for tiny45
 */
 //
 // NOTES for uploading:  use the ATtinyCore Universal library
 // (http://drazzy.com/package_drazzy.com_index.json)
 //   with ATtiny x5 series, then the chip, speed, etc. and use the
 //  Arduino/Leo as ISP (ATtiny) as the programmer.
 //
 //   You can also change the internal clock, B.O.D., voltage
 //  standard fuses by choosing the option
 //  you want and choosing 'Burn Bootloader'.  Be Careful!
 //
 //
#define BLINKPIN 1

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize ATtiny45 PB1 as an output.
  pinMode(BLINKPIN, OUTPUT);

}

// the loop function runs over and over again forever
void loop() {
  delay(500);
  digitalWrite(
BLINKPIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(500);              // wait for a second
 
digitalWrite(BLINKPIN, LOW);
}

This is about as simple a program as you can write on an AVR, no matter how you do it....and with the IDE you still end up using 808 bytes of program space and 9 bytes of RAM.  I suspect that the delay() function takes up the bulk of that.
-----------
A few days earlier, I'd spent about half a day installing and cramming the WinAVR package on an old Dell Inspiron 7000 (Windows 2000, 128 MB of RAM, 4GB HD!), because it was one of the few PC's my junk pile furnished with a useful parallel port.  Up until I discovered the Arduino I had been using AVR assembler, Atmel's AVR Studio on a PC, and an old homebuilt SP12 programmer for all my ancient AVR devices.  I have several AT90S2313's and 8515's, a mega16, a mega8515, and a number of tiny26's (ancestor of the 261/461/861), all of which I wanted to continue to use.

I didn't have too much trouble working with WinAVR, and in the process managed to muddle through the rather complicated 'demo.c' program on the AVR-libc support site.....which example DOES (more or less), implement the 'fader' program in my previous post on this subject.  I modified the demo, working over the various macros embedded in the 'iocompat.h' file, and got it to work with the PWM stuff on the tiny26.  All good....

But...I didn't want to lug out that old clunk programmer and yet another computer when I wanted to use my old uC's, dammit!  And I wanted to use Linux as well...so what next?

While using the WinAVR stuff, I used the command line, as illustrated in the AVR-libc tutorials for 'demo.c' above, to do everything by hand, like so:

Insp7000 c:\code\> avr-gcc -g -Os -mmcu=attiny26 demo.c -o demo.o
Insp7000 c:\code\> avr-objcopy -j .text -j .data -O ihex demo.o demo.hex

This built me a 300+ byte file in 'demo.hex', and with the SP12 dongle I uploaded it like so:

Insp7000 c:\code\> avrdude -p t26 -c sp12 -U flash:w:demo.hex

(Notice that avr-gcc and AVR-libc know the ATtiny26 as 'atttiny26', while avrdude believes it is called 't26'.  Gotta keep our wits about us, kids...)

Which was probably no more that 70-80 bytes on the tiny26....Impressive!  And more to the point, it worked.  And note that the much simpler 'blink' program for the tiny45 above, letting the Arduino IDE do the heavy lifting, was 10 times the size.

Size matters; the tiny26 has 2K of flash, while the Mega328P (the core of the Arduino Uno) has 32K to waste.  We can do better, for not much in the way of pain.

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

So, this last example should give us some proper hackerly ideas, right?  Avr-gcc and avrdude are already there with the Arduino IDE installed; and unlike the IDE, avrdude has the full pin descriptions and functions defined for EVERY AVR clear back to the microcontroller stone age.  In theory, at least, if we have the ArduinoISP software loaded up on the Uno, we should be able to make the Arduino program ANY AVR device, as long as AVR-libc and avrdude know the device exists.

First, let's look in 'programmers.txt'  in the Arduino IDE install folder (<folder>/hardware/arduino/avr/) and see what kind of directives are sent to avrdude for the 'Arduino as ISP' programmer defined there:

arduinoasisp.name=Arduino as ISP
arduinoasisp.communication=serial
arduinoasisp.protocol=stk500v1
arduinoasisp.speed=19200
arduinoasisp.program.protocol=stk500v1
arduinoasisp.program.speed=19200
arduinoasisp.program.tool=avrdude
arduinoasisp.program.extra_params=-P{serial.port} -b{program.speed}


And so we see.  Unlike when hitting the Arduino directly through the IDE, we must talk DIRECTLY to the serial.port (not with the pseudodevice 'usb'), as well as define a baud rate to talk through the Arduino (as if it was a serial port itself rather than the destination for the code we want to write).

So that's one piece of the pie.  The other piece is....which programmer do we use on the avrdude command line if we aren't going out on the parallel port with the SP12 dongle?  Entering 'avrdude -c ?' gives us a list of programmers that it knows about:

Valid programmers are:
  2232HIO          = FT2232H based generic programmer
  4232h            = FT4232H based generic programmer
  89isp            = Atmel at89isp cable
  abcmini          = ABCmini Board, aka Dick Smith HOTCHIP
  alf              = Nightshade ALF-PgmAVR, http://nightshade.homeip.net/
  arduino          = Arduino
  arduino-ft232r   = Arduino: FT232R connected to ISP
  atisp            = AT-ISP V1.1 programming cable for AVR-SDK1 from <http://micro-research.co.th/>
  atmelice         = Atmel-ICE (ARM/AVR) in JTAG mode
  atmelice_dw      = Atmel-ICE (ARM/AVR) in debugWIRE mode
  atmelice_isp     = Atmel-ICE (ARM/AVR) in ISP mode
  atmelice_pdi     = Atmel-ICE (ARM/AVR) in PDI mode
  avr109           = Atmel AppNote AVR109 Boot Loader
  avr910           = Atmel Low Cost Serial Programmer
  avr911           = Atmel AppNote AVR911 AVROSP
  avrftdi          = FT2232D based generic programmer

...... (and so on for a page or two) ......

The only one the looks even close is, well 'arduino', so let's use the command line options we found above in combination with what we've got here, and just try talking to the gadget and see:

mymachine:$avrdude -p t45 -c arduino -P /dev/ttyACM0 -b 19200
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e9206
avrdude: safemode: Fuses OK (E:FF, H:DF, L:E2)
avrdude done.  Thank you.


So wow, it just works!  That is, indeed, the correct sig for the ATtiny45.  The Arduino Uno passes our request right through using the ArduinoISP software (with the jumpered on capacitor across the Reset line) to get to our 'legacy' device.

So now, how about a new version of the blink program?  Let's see here:

/*
   test blink program to make sure know what am doing with
avr-gcc and such.

   for tiny45 through Arduino as programmer?

*/
#include <inttypes.h>
#include <avr/io.h>

#define F_CPU 8000000UL   // 8Mhz on t45 - this needs to be defined BEFORE including util/delay.h

#include <util/delay.h>        //  for _delay_us(double) and _delay_ms(double)

        // handy stuff from Arduino.h
#define HIGH 0x1
#define LOW  0x0

#define INPUT 0x0
#define OUTPUT 0x1
#define INPUT_PULLUP 0x2
#define lowByte(w) ((uint8_t) ((w) & 0xff))
#define highByte(w) ((uint8_t) ((w) >> 8))

#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))

#define BLINKPIN 0x01   // assume port B for t45


    // sorta like setup()
void ioinit(void) {
    bitSet(DDRB, BLINKPIN);  // set B1 as OUTPUT  -- like DDRB |= (1 << BLINKPIN);
}

    // and can more or less pretend this is loop()
int main(void) {

    ioinit();

    for (;;) {
        _delay_ms(500.0);
        bitClear(PORTB, BLINKPIN);  //..or PORTB &= ~(1 << BLINKPIN);
        _delay_ms(500.0);
        bitSet(PORTB, BLINKPIN);    //..or PORTB |= (1 << BLINKPIN);
    }
}

// end testblink.c


Compile it with avr-gcc and use avr-objcopy to create the hex file, then avrdude can write it.  100 bytes used total!

So how did I concoct the above?  Among other things, I looked for (and at) the main header files that the IDE loads when you create a new program...i.e. at the Arduino.h file, which is stored at <IDE install folder>/hardware/arduino/avr/cores/.  LOTS of handy macros in there, and a nice list of other files that are included in all IDE builds by default.  Since we are trying to program on the bare metal, making as few assumptions as possible about the device, we can muck and pare at that list a bit, drill down through a few #includes to find what they do and what we need, guess what we don't need.  Eventually we can just go with:

#include <inttypes.h>
#include <avr/io.h>

#define F_CPU 8000000UL   // 8Mhz on t45 - this needs to be defined BEFORE including util/delay.h

#include <util/delay.h>        //  for _delay_us(double) and _delay_ms(double


There was one gotcha in my first attempt at this:  avr-gcc bitched incessantly that F_CPU was NOT something it knew about, thus the #define for the CPU frequency there....I tried just including util/delay.h without it, and immediate had a wake up call.  OBVIOUSLY (right?  right?) the delay functions wouldn't work right if they were based on the clock speed of the device, and didn't know what that number WAS.  An arbitrary AVR device can't be assumed to have an RTC or a PLL clock built-in, so the only other way to have a time base is to run it off the CPU clock, and....

IN any case...paring the #includes down to a bare minimum shows us what we can safely leave out, while still giving us some nice short cuts, and a handy template for building more complicated stuff later on.


No comments:

Post a Comment