9b. Group Assignment¶
This is the group assignment webpage for Kamp-Lintfort FabLab’s Kleve branch group assignment, week 09, Embedded Programming. Until hosted on the FabLab KaLi (or FabLab.blue) site, it can live here.
Architecture Comparisons¶
For this group assignment, we looked at two different ways to program our Arduino-compatible microcontroller boards and compared them regarding memory consumption and ease of use. We started out with a sketch provided by Roland blinking an LED on an ATtiny44 echo board in a morse code “SOS” pattern. Roland used this code in his Embedded Programming week task and we thought it might be a good example to rewrite. Here is the source code:
#include <avr/power.h>
int ledPin = 8;
int buttonPin = 7;
int ditMs = 100;
int dahMs = 3 * ditMs;
int delayBetweenCharacters = 2 * ditMs; //theoretically, this is 3 dit, but 1 dit is already included in the dit and dah functions
int delayBetweenSos = 6 * ditMs; //theoretically, this is 7 dit, but 1 dit is already included in the dit and dah functions
void setup() {
clock_prescale_set(0);
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT);
digitalWrite(buttonPin, HIGH); //Internal pullup on Button Pin
}
void loop() {
if(digitalRead(buttonPin) != LOW){
sosBlink(ledPin);
delay(delayBetweenSos);
}
}
void dit(int blinkPin){
digitalWrite(blinkPin, HIGH);
delay(ditMs);
digitalWrite(blinkPin, LOW);
delay(ditMs);
}
void dah(int blinkPin){
digitalWrite(blinkPin, HIGH);
delay(dahMs);
digitalWrite(blinkPin, LOW);
delay(ditMs);
}
void sosBlink(int blinkPin){
dit(blinkPin);
dit(blinkPin);
dit(blinkPin);
delay(delayBetweenCharacters);
dah(blinkPin);
dah(blinkPin);
dah(blinkPin);
delay(delayBetweenCharacters);
dit(blinkPin);
dit(blinkPin);
dit(blinkPin);
}
Following output of the Arduino IDE when compiling illustrates the memory usage of the program when written using the Arduino ecosystem:
Sketch uses 1056 bytes (30%) of program storage space. Maximum is 3456 bytes.
Global variables use 12 bytes (4%) of dynamic memory, leaving 244 bytes for local variables. Maximum is 256 bytes.
Rewriting the Code in C using AVR-libc¶
After going through a short introduction on C using AVR-libc , we then rewrote the same morse code using C and forgoing the high-level, Arduino programming language. This involves a lot less code overhead, at the cost of being less legible - we were all on the same page here. We roughly followed this German tutorial about AVR C programming and another one about bit math. In the end, rewriting was not very hard, just a matter of going through the code line by line and finding out the appropriate way to execute this specific line’s code using C and the AVR-libc library. In the end, we arrived at following code:
#define LED_PIN PB2
#define BUTTON_PIN PA7
#define LED_PORT PORTB
#define BUTTON_PORT PORTA
#define LED_DDR DDRB
#define BUTTON_DDR DDRA
#include <avr/io.h> //for general IO
#include <util/delay.h> //for time delaying
#include <avr/power.h> //for clock prescaler setting
const int ditMs = 100;
const int dahMs = 3 * ditMs;
const int delayBetweenLetters = 2 * ditMs; //theoretically, this is 3 dit, but 1 dit is already included in the dit and dah functions
const int delayBetweenWords = 6 * ditMs; //theoretically, this is 7 dit, but 1 dit is already included in the dit and dah functions
void setup(void){
clock_prescale_set(0); //set clock prescaler
LED_DDR |= (1 << LED_PIN); //set LED pin to be an output
BUTTON_DDR &= ~(1 << BUTTON_PIN); //set Button pin to be an input
BUTTON_PORT |= (1 << BUTTON_PIN); //activate pullup by setting button pin HIGH as input
}
int main(void){
setup(); //run setup function (only runs once due to while(1) being an infinite loop, setup could also be integrated here
while(1){
if(PINA & (1 << BUTTON_PIN)) { //check whether button is unpressed (unpressed -> HIGH)
dit();
dit();
dit();
interLetterDelay();
dah();
dah();
dah();
interLetterDelay();
dit();
dit();
dit();
interWordDelay();
}
}
return 0;
}
void dit(){
LED_PORT |= (1 << LED_PIN); //set LED_PIN in PORTB
_delay_ms(ditMs);
LED_PORT &= ~(1 << LED_PIN); //unset LED_PIN in PORTB
_delay_ms(ditMs);
}
void dah(){
LED_PORT |= (1 << LED_PIN); //set LED_PIN in PORTB
_delay_ms(dahMs);
LED_PORT &= ~(1 << LED_PIN); //unset LED_PIN in PORTB
_delay_ms(ditMs);
}
void interLetterDelay(){
_delay_ms(delayBetweenLetters);
}
void interWordDelay(){
_delay_ms(delayBetweenWords);
}
Following output of the Arduino IDE when compiling illustrates the memory usage of the program when written using C and AVR-libc:
Sketch uses 240 bytes (6%) of program storage space. Maximum is 3456 bytes.
Global variables use 0 bytes (0%) of dynamic memory, leaving 256 bytes for local variables. Maximum is 256 bytes.
We uploaded the code using the Arduino as ISP programmer setup described on Roland’s Embedded Programming week documentation. It immediately started blinking “SOS” and would halt to do so when the button was held - exactly the same as with the original Arduino code:
Comparison: Arduino Programming language vs. C using AVR-libc¶
When using the Arduino IDE and writing code in the high-level Arduino language, there is a lot of overhead bloating up the code. This can be especially critical when using smaller chips like the ATtiny’s, which don’t have much program memory to begin with. In comparison, the C version of the same code with AVR-libc uses only 22.7 % of the program memory that the Arduino one does, at identical functionality. Also, since all variables are actually constants here, there is no use of dynamic memory at all. Caveat: No care was taken to optimize the Arduino code at all, but this is a good example of what naturally tends to happen when programming in a very high-level language like that. Following table shows the exact memory usage numbers in comparison:
Memory | Arduino | AVR C | Relative Size (AVR C/Arduino) |
---|---|---|---|
Program Storage Space | 1056 bytes | 240 bytes | 22.7 % |
Dynamic Memory | 12 bytes | 0 bytes | 0 % |
When we talked about it, we came to the conclusion that - especially when not firm in programming - the memory savings really only make sense when memory is sparse. This can be either due to the chose chip just having very little memory available, it can also be a design goal though, when moving from prototyping to production. When deploying a product to production, one does usually not want to use microcontrollers with lots of unused functions or unused memory, as these things come at a cost, so at that stage it might make sense to have more compact code to save some cost. Another reason would be when outputs have to be switched absolutely in parallel, which is not possible when using the high-level language functions like digitalWrite() that only operate on a single pin. This could be implemented using bit math and direct port manipulation selectively when writing code using the Arduino Programming language though, too.