Embedded Programming

This week was a whirlwind tour back into the world of embedded programming. At one point I did this for a living, but it's been more than 5 years since I've had to touch a complier so things are a little hazy for me.


I had already set up the AVR toolchain on my laptop in the week we built the FabISPs. I also went ahead and bought my own FTDI cable so that I could do the programming and communication at home.

One Button Flashing Light

I figured I would start with my board with one button. Here you can see the light flashing every time it detects either the button being pushed or released.

I started with the base code located in the hello.ftdi.44.echo.c example.

There are a few important concepts for this week. Each individual pin on the microcontroller is associated with a port which also has an associated data direction register. We need to set the direction of an individual pin, and then we can either read values in or output values. As an example to configure my LED on PA7 to be an output and initially off:

// set port direction for output #define output(directions,pin) (directions |= pin) #define led_port PORTA #define led_direction DDRA #define led_pin (1 << PA7) // I want my LED to start off clear(led_port, led_pin); // set my pin to be an output output(led_direction, led_pin);

If I want to set an pin as input, I need to follow a similar process. The following is for a switch on PB2 that requires a pull up resistor. To turn on the pull-up resistor you simply have to write a logical 1 to the button_pin on button_port.

// set port direction for input #define input(directions,pin) (directions &= (~pin)) #define button_port PORTB #define button_direction DDRB #define button_pins PINB #define button_pin (1 << PB2) set(button_port, button_pin); // turn on pull up resistor input(button_direction, button_pin); // set button pin as input

Once a button is set as an input you can test the the proper pins register (PINB) variable for the current status of the pin of interest.

pin_test(button_pins,button_pin);

Interrupts

I wanted to refresh myself on the use of interrupts incase I had any time sensitive code I needed to run. Again there is the hello.ftdi.44.echo.interrupt.c code to start with.

There are some important flags that establish your interrupt environment. Each pin is associated with one of two interrupt vectors. PA0 to PA7 correspond to PCINT0 to PCINT7 that are handled by the PCINT0_vect ISR. PB0 to PB3 correspond to PCINT8 to PCINT10 and are handled by the PCINT_vect. PCMSK0 and PCMSK1 contain the enable flags for the previously mentioned interupts, and both a specific pin and it's corresponding PCIE0 or PCIE1 bit in the GIMSK register must be enabled for an interrupt to be able to be triggered. As an example, if I wanted to trigger an interrupt on a button chaning state:

#define button_port PORTB #define button_direction DDRB #define button_pins PINB #define button_pin (1 << PB2) #define button_interrupt (1 << PCIE1) #define button_interrupt_pin (1 << PCINT10) ISR(PCINT1_vect) { // // pin change interrupt handler // } // // down in main() { // // configue button as input set(button_port, button_pin); // turn on pull up resistor input(button_direction, button_pin); // set button pin as input // set up pin change interrupt // enable the PCIE1 ISR set(GIMSK, button_interrupt); // by default all of the PCMSK1 flags start as enabled; PCMSK1 = 0; // enable the interrupt pin associated with the button set(PCMSK1, button_interrupt_pin);

At this point it is important to note that the demo interrupt code has an undocumented feature that preventus you from extending the code until you make a slight edit.

In short:

When the serial input pin changes state, a flag is set that tells the processor to run the Interrupt Service Routine (ISR) at the next available opportunity. The ISR as written will then read in a character, and echo it back to you.

While an ISR can't be interrupted by another ISR, that flag can be set while an interrupt is running. In this case the action of receiving a character on the serial input pin will set the flag again (as the state is changing while the character is being received), and the processor will immediately launch into the ISR again after it finishes. Unfortunately this second time there is no character to receive, so the program will hang in the ISR waiting for another character to be transmitted, and this process will repeat itself so nothing will execute but that interrupt after the first time it is called.

To fix it add:

GIFR = (1 << PCIF0); // clears the interrupt so we don't hop right back in.

at the end of the ISR. It clears the flag associated with that interrupt so it will behave as expected in this case and you will not return to the ISR until the next time you are receiving a character.

This is not the appropriate fix in all cases, especially if you have other interrupts that you are trying to capture on PA0-PA7, but is a good place to start.

Two Button Board

As interrupts are a poor design choice for testing buttons I went back to a main loop implementation (still running an interrupt for the serial communication) to handle the buttons on my two button board from the circuit design assignment.


hello.ftdi.44.twobutton.interrupt.c
hello.ftdi.44.twobutton.interrupt.c.make

Bus Networking

To give bus networking a try I built the hello.bus.45 bridge and two nodes.

I think there is an issue with the tiny45s in the arch shop, as they are all just hanging out in a drawer, probably on the older side, and I had pretty bad luck with making working boards. I built one node board that didn't work at all, and one that was a little spotty. I don't think it was my soldering job - so I'm going to blame the processors.


Here you can see me telling node 0, node 1, and node 2 to do the thing where they flash twice. If the boards were behaving better I might have considered having them do something cool, but it was a good proof of concept to have under my belt.

Fabduino

To figure out what the heck an arduino was and why people have been so excited about them I decided to give the hello.arduino.328p board located on the embedded programing lecture page a try. This board is nice as it uses a slightly more poweful atmega328 chip with more ram and more pins.

After making and programming the board successfully with the example code on the website I spent way to much time figuring out how to run an arduino at 20 MHz. The easy way to use this board is to simply use the internal 8MHz clock when playing with the arduino programming environment and be happy with that. For some historical reasons the arduino bootloader is only designed to run at 16MHz. If you run at 20MHz nothing particularly breaks but some of timing functions will have a little error. It is possible to manually edit the code to fix some of the error, and some of them may now be fixed in the source.

I had a further issue, in that the arduino is built to run on the atmega328p processor, and it seems that the arch shop only has the atmega328. This would make for some fun trickery down the line.

20MHz atmega328 Fabduino

This is a bad idea. Just get a 16MHz resonator and a atmega328p.

Really

Ok... fine... I'll tell you

First you need to download the latest version of the arduino software. After doing that download the latest build of the optiboot bootloader (a snazzy bootloader that replaces the default) and replace the version of optiboot that came with the arduino software.

You can use optiboot to compile new bootloaders for the arduino. The following can be added to the optiboot makefile to add a profile for a 20MHz atmega328 device.

fabduino20: TARGET = fabduino20 fabduino20: MCU_TARGET = atmega328 fabduino20: CFLAGS += $(COMMON_OPTIONS) fabduino20: AVR_FREQ ?= 20000000L fabduino20: LDSECTIONS = -Wl,--section-start=.text=0x7e00 -Wl,--section-start=.version=0x7ffe fabduino20: $(PROGRAM)_fabduino20_atmega328.hex fabduino20: $(PROGRAM)_fabduino20_atmega328.lst fabduino20_isp: fabduino20 fabduino20_isp: TARGET = fabduino20 fabduino20_isp: MCU_TARGET = atmega328 # 512 byte boot, SPIEN fabduino20_isp: HFUSE ?= DE # full swing (20MHz) 16KCK/14CK+65ms fabduino20_isp: LFUSE ?= F7 # 2.7V brownout fabduino20_isp: EFUSE ?= 05 fabduino20_isp: isp

Unfortuniately optiboot is only written to work with a select family of microcontrolers. The atmega328 is not one of them. Fortuniately the 328 is the same at the 328p except for the pico-power addons (for the most part) so you can modify the source of optiboot to work with atmega328's. Open the pin_defs.h file and any time you find a line like:

#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega88) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__)

add the 328

#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega88) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__)

Then from a terminal (assuming you have the avr toolpath installed already) you can build the new bootloader with:

make fabduino20

You should now have a optiboot_fabduino_atmega328.hex file in your optiboot folder. Now that we have the bootloader that we want to use, we have to tell the arduino software how to use it. Figure out where the boards.txt file is on your OS and add a board profile for the 20MHz board on a 328 (the format has changed slightly since the file that is on the MAS.816 website)

fab.name=fabduino20 fab.upload.tool=avrdude fab.upload.protocol=arduino fab.upload.maximum_size=32256 fab.upload.maximum_data_size=2048 fab.upload.speed=115200 fab.bootloader.tool=avrdude fab.bootloader.low_fuses=0xF7 fab.bootloader.high_fuses=0xDE fab.bootloader.extended_fuses=0x05 fab.bootloader.unlock_bits=0x3F fab.bootloader.lock_bits=0x0F fab.bootloader.file=optiboot/optiboot_fabduino20_atmega328.hex fab.build.mcu=atmega328 fab.build.f_cpu=20000000L fab.build.core=arduino fab.build.variant=standard

and while you're at it add a profile for the 20MHz board on a 328p

fab20.name=fabduino20p fab20.upload.tool=avrdude fab20.upload.protocol=arduino fab20.upload.maximum_size=32256 fab20.upload.maximum_data_size=2048 fab20.upload.speed=115200 fab20.bootloader.tool=avrdude fab20.bootloader.low_fuses=0xF7 fab20.bootloader.high_fuses=0xDE fab20.bootloader.extended_fuses=0x05 fab20.bootloader.unlock_bits=0x3F fab20.bootloader.lock_bits=0x0F fab20.bootloader.file=optiboot/optiboot_fabduino20_atmega328.hex fab20.build.mcu=atmega328p fab20.build.f_cpu=20000000L fab20.build.core=arduino fab20.build.variant=standard

The reason for the two profiles is because for some as of yet unexplaied reason, while buring the bootloader the chip is properly detected as a atmega328, but when trying to use the arduino software to upload a sketch over ftdi the device signature is that of a atmega328p. I suspect that this has to do with the internals of optiboot. To install the sketch ensure the fabduino20 board is selected and the proper ISP, and select tools -> burn bootloader. After buring the bootloader change to the fabduino20p board profile and you can upload sketches as normal. As I mentioned - the timing functions will be ever so slightly off, so this is not an ideal setup for time sensitive operations.

8MHz atmega328 Fabduino

While we are at it we might as well make a profile for a 8MHz atmega328 device (using the internal oscillator). This will have proper timing. The addition tot he makefile for optiboot:

fabduino8: TARGET = fabduino8 fabduino8: MCU_TARGET = atmega328 fabduino8: CFLAGS += '-DLED_START_FLASHES=2' '-DBAUD_RATE=38400' fabduino8: AVR_FREQ ?= 8000000L fabduino8: LDSECTIONS = -Wl,--section-start=.text=0x7e00 -Wl,--section-start=.version=0x7ffe fabduino8: $(PROGRAM)_fabduino8_atmega328.hex fabduino8: $(PROGRAM)_fabduino8_atmega328.lst fabduino8_isp: fabduino8 fabduino8_isp: TARGET = fabduino8 fabduino8_isp: MCU_TARGET = atmega328 # 512 byte boot, SPIEN fabduino8_isp: HFUSE ?= DE # low power xtal (8MHz) 256CK/14CK+65ms fabduino8_isp: LFUSE ?= E2 # 2.7V brownout fabduino8_isp: EFUSE ?= 05 fabduino8_isp: isp

Then from a terminal (assuming you have the avr toolpath installed already) you can build the new bootloader with:

make fabduino8

You should now have a optiboot_fabduino8_atmega328.hex file in your optiboot folder. Now that we have the bootloader that we want to use, we have to tell the arduino software how to use it. Figure out where the boards.txt file is on your OS and add a board profile for the 8MHz board on a 328.

fab8.name=fabduino8 fab8.upload.tool=avrdude fab8.upload.protocol=arduino fab8.upload.maximum_size=32256 fab8.upload.maximum_data_size=2048 fab8.upload.speed=38400 fab8.bootloader.tool=avrdude fab8.bootloader.low_fuses=0xE2 fab8.bootloader.high_fuses=0xDE fab8.bootloader.extended_fuses=0x05 fab8.bootloader.unlock_bits=0x3F fab8.bootloader.lock_bits=0x0F fab8.bootloader.file=optiboot/optiboot_fabduino8_atmega328.hex fab8.build.mcu=atmega328 fab8.build.f_cpu=8000000L fab8.build.core=arduino fab8.build.variant=standard ################################################# fab8p.name=fabduino8p fab8p.upload.tool=avrdude fab8p.upload.protocol=arduino fab8p.upload.maximum_size=32256 fab8p.upload.maximum_data_size=2048 fab8p.upload.speed=38400 fab8p.bootloader.tool=avrdude fab8p.bootloader.low_fuses=0xE2 fab8p.bootloader.high_fuses=0xDE fab8p.bootloader.extended_fuses=0x05 fab8p.bootloader.unlock_bits=0x3F fab8p.bootloader.lock_bits=0x0F fab8p.bootloader.file=optiboot/optiboot_fabduino8_atmega328.hex fab8p.build.mcu=atmega328p fab8p.build.f_cpu=8000000L fab8p.build.core=arduino fab8p.build.variant=standard

The same strangeness with the bootloader exists, while buring the bootloader the chip is properly detected as a atmega328, but when trying to use the arduino software to upload a sketch over ftdi the device signature is that of a atmega328p. Install and burn the boards the same ways.

For the lazy:

optiboot_fabduino8_atmega328.hex
optiboot_fabduino20_atmega328.hex