I've been wanting to get back into some interesting embedded systems and electronics work - so I figured I'd dust off my old Arduino. In the process I ended up having to re-learn how to program it using C without using the Arduino IDE (it's not my favourite thing), which is what I'm going to go into here.
I downloaded CrossPack which contains GCC cross compiler for the AVR architecture, as well as a handful of other useful tools (like avrdude, which we'll use to flash the Arduino). Once that's setup we can start off with a pretty simple program which rapidly blinks the arduino's onboard LED, which is sort of the Hello World of the Arduino community:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <avr/io.h> | |
#include <util/delay.h> | |
#define LED_ON _BV(PORTB5) | |
#define LED_OFF ~_BV(PORTB5); | |
int main () { | |
DDRB |= _BV(DDB5); | |
while(1) { | |
PORTB |= LED_ON; | |
_delay_ms(300); | |
PORTB &= LED_OFF; | |
_delay_ms(300); | |
} | |
} |
To build this using GCC we just need to call avr-gcc, making sure to specify the processor (using -mmcu), define the processor frequency (the F_CPU macro)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p -o blink blink.c |
This creates an ELF binary. One odd thing is that pretty much everyone building C code for Arduino seems to include the "-R .eeprom" switch which removes the ".eeprom" section from the output binary. However I've not ever seen this section does not exist in my ELF binary - see below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ avr-objdump -h blink | |
blink: file format elf32-avr | |
Sections: | |
Idx Name Size VMA LMA File off Algn | |
0 .data 00000000 00800100 000000b0 00000124 2**0 | |
CONTENTS, ALLOC, LOAD, DATA | |
1 .text 000000b0 00000000 00000000 00000074 2**1 | |
CONTENTS, ALLOC, LOAD, READONLY, CODE | |
2 .comment 00000011 00000000 00000000 00000124 2**0 | |
CONTENTS, READONLY | |
3 .debug_aranges 00000020 00000000 00000000 00000138 2**3 | |
CONTENTS, READONLY, DEBUGGING | |
4 .debug_info 000000be 00000000 00000000 00000158 2**0 | |
CONTENTS, READONLY, DEBUGGING | |
5 .debug_abbrev 00000014 00000000 00000000 00000216 2**0 | |
CONTENTS, READONLY, DEBUGGING | |
6 .debug_line 00000058 00000000 00000000 0000022a 2**0 | |
CONTENTS, READONLY, DEBUGGING |
Anyway, perhaps there's something else at play that I've not understood so I've kept this switch in (I'm making a mental note to explore this later). Again many other places seem to insist on using the avr-objcopy utility to produce intel hex file before uploading to the Arduino - so often you'll see the below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
avr-objcopy -O ihex -R .eeprom blink blink.hex |
However again this is not a necessary step it seems, since the utility we use to upload the binary to the Arduino - avrdude - supports ELF so you can skip it. When we want to upload the binary to the Arduino we'll use avrdude, but first we need to know the programming device. For me this is just the USB type A to type B cable that comes with the arduino, which in the example below is /dev/tty.usbmodem1421:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
avrdude -F -V -c ATMEGA328P -p arduino -P /dev/tty.usbmodem1421 -b 115200 -U flash:w:blink:e | |
# note: most guides will tell you to do the below, but it's not necessary | |
# avrdude -F -V -c ATMEGA328P -p arduino -P /dev/tty.usbmodem1421 -b 115200 -U flash:w:blink.hex |
I've found it useful to create a little Makefile to tie these steps all together:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ cat Makefile | |
CC = avr-gcc | |
PROCESSOR = atmega328p | |
FREQ = 16000000 | |
AVRDUDE_CONFIG = arduino | |
AVRDUDE_PART = ATMEGA328P | |
AVRDUDE_PROGRAMMING_DEVICE = /dev/tty.usbmodem1411 | |
all: build | |
clean: | |
rm *.o *.hex blink | |
compile: | |
$(CC) -Os -DF_CPU=$(FREQ)UL -mmcu=$(PROCESSOR) -c -o blink.o blink.c | |
build: compile | |
$(CC) -mmcu=$(PROCESSOR) blink.o -o blink | |
flash: build | |
avrdude -F -V -c $(AVRDUDE_CONFIG) -p $(AVRDUDE_PART) -P $(AVRDUDE_PROGRAMMING_DEVICE) -b 115200 -U flash:w:blink:e | |
$ make flash | |
avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o blink.o blink.c | |
avr-gcc -mmcu=atmega328p blink.o -o blink | |
avr-objcopy -O ihex -R .eeprom blink blink.hex | |
avrdude -F -V -c arduino -p ATMEGA328P -P /dev/tty.usbmodem1411 -b 115200 -U flash:w:blink.hex | |
avrdude: AVR device initialized and ready to accept instructions | |
Reading | ################################################## | 100% 0.00s | |
avrdude: Device signature = 0x1e950f | |
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed | |
To disable this feature, specify the -D option. | |
avrdude: erasing chip | |
avrdude: reading input file "blink.hex" | |
avrdude: input file blink.hex auto detected as Intel Hex | |
avrdude: writing flash (176 bytes): | |
Writing | ################################################## | 100% 0.04s | |
avrdude: 176 bytes of flash written | |
avrdude: safemode: Fuses OK (H:00, E:00, L:00) | |
avrdude done. Thank you. |
Finally to tie this together in a slightly more existing example than blink.c, I've created a little program that will flash the LED attached to pin 13 with a message encoded in morse - "hello world" or ".... . .-.. .-.. --- / .-- --- .-. .-.. -..":
The code is on github at https://github.com/smcl/arduino_morse and only requires a bare Arduino and USB cable.