compare the performance and development workflows for other architectures
Read a microcontroller data sheet. Program your board to do something, with as many different programming languages and programming environments as possible.
I have a little bit of experience with programming in C, playing with Arduinos, etc. but this will largely be new for me.
I started where I left off in Week 06. I had already programmed my Hello board with a simple blink pattern sketch using the Arduino IDE. See Week 06 - Programming the Board to examine my code. Basically, this program has a set of pre-programmed blink patterns, and each time you push the button, it changes to the next one.
I wanted to update this code with a couple of improvements. Mainly, I wanted to improve the User Interface. The code above requires the user to press the button at the end of a blink cycle. Because the blink patterns are all implemented witht the delay()
function, the only time when the board is able to read the button push is at the end of a pattern cycle. This proved particularly troublesome for the end of the longer patterns, such as S.O.S. I wanted users to be able to push the button at any time to advance the blink pattern. So I got to work.
I found this tutorial particularly helpful, and basically followed it to burn my bootloader and get started programming my board. Here's my basic reproduction of those steps.
I downloaded and booted the Arduino IDE and followed the tutorial linked above to load in the ATtiny microcontroller board files.
These preceding steps allow the Arduino IDE to correctly set the fuses for the ATtiny44 using a 20 MHz external clock.
The FabISP is recognized by the Arduino IDE as USBtinyISP, so select that from the Tools > Programmer
menu. You are now ready to burn the bootloader onto the target board. Select Tools > Burn Bootloader
. If all goes well it will give you a "Bootloader Burned Successfully" message. NB: I first tried to use an Arduino Uno setup as an ArduinoISP and had no luck - it couldn't find my target board. It worked like a charm with the FabISP though.
I had already gotten my blink pattern program working using the delay()
function, so now my challenge was to implement "Blink without delay" for each of my blink patterns. I built it up slowly starting with a simple program that just blinked the green LED on and off based on the Arduino Blink Without Delay sketch. I adapted this for my own pin number (pin 7 is connected to my green LED).
/* Blink without Delay Modified from Arduino Example Code from: http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay */ // constants won't change. They're used here to set pin numbers: const int buttonPin = 3; // the number of the pushbutton pin const int redPin = 8; // the number of the red LED pin const int greenPin = 7; // the number of the green LED pin const long longInterval = 1000; // long interval at which to blink (milliseconds) const long shortInterval = 100; // short interval at which to blink (milliseconds) // variables will change: int redState = LOW; // redState used to set the red LED int greenState = LOW; // greenState used to set the red LED // Generally, you should use "unsigned long" for variables that hold time // The value will quickly become too large for an int to store unsigned long previousMillis = 0; // will store last time LED was updated unsigned long currentMillis = 0; // will store the current time // the setup function runs once when you power the board void setup() { // initialize digital greenPin as an output. pinMode(greenPin, OUTPUT); } void loop() { currentMillis = millis(); if (currentMillis - previousMillis > longInterval){ previousMillis = currentMillis; if (greenState == LOW) greenState = HIGH; else greenState = LOW; } digitalWrite(greenPin, greenState); }
Then I wanted to make sure I could read the button push, so I wrote a quick sketch to alternate which LED is lit via button push.
/* Blink back and forth on button press Modified from Arduino Example Code from: http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay */ // constants won't change. They're used here to set pin numbers: const int buttonPin = 3; // the number of the pushbutton pin const int redPin = 8; // the number of the red LED pin const int greenPin = 7; // the number of the green LED pin // variables will change: int redState = LOW; // redState used to set the red LED int greenState = LOW; // greenState used to set the red LED int buttonState = 0; // var to hold buttonState // the setup function runs once when you power the board void setup() { // initialize digital pins 7 & 8 as an output. pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); //initialize digital pin 3 as input. pinMode(buttonPin, INPUT); } void loop() { buttonState = digitalRead(buttonPin); if (buttonState == LOW){ delay(100); //allows user time to let go of button if (greenState == LOW){ greenState = HIGH; redState = LOW; } else { greenState = LOW; redState = HIGH; } } digitalWrite(greenPin, greenState); digitalWrite(redPin, redState); }
It worked pretty well:
Now that my button input was working, and my "blink w/o delay" was working, I wanted to put them together and rewrite my sketch from Week 06 - Programming to run without the delay()
function, allowing a button press to be read by the board at essentially any time. I built and tested each blink function individually, and eventually put together this sketch:
/* Blink Pattern without Delay Heavily Modified from Arduino Example Code from: http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay */ // constants won't change. They're used here to set pin numbers: const int buttonPin = 3; // the number of the pushbutton pin const int redPin = 8; // the number of the red LED pin const int greenPin = 7; // the number of the green LED pin const long long_interval = 1000; // long interval at which to blink (milliseconds) const long short_interval = 100; // short interval at which to blink (milliseconds) const long micro_interval = 50; // very short interval at which to blink (ms) // variables will change: int buttonState = 0; // variable for reading the pushbutton status int blinkNum = 0; // variable to store the current blink pattern int redState = LOW; // redState used to set the red LED int greenState = LOW; // greenState used to set the red LED int counter = 0; // a counter variable for blinkN() int sosCounter = 0; // a counter var for sos() // Generally, you should use "unsigned long" for variables that hold time // The value will quickly become too large for an int to store unsigned long previousMillis = 0; // will store last time LED was updated unsigned long currentMillis = 0; // will store the current time long timer = 0; // for incrementing the delay speed unsigned long default_timer = 1000; // stores default timer value unsigned long decrement = 100; // stores the timer decrement value // the setup function runs once when you power the board void setup() { // initialize digital pins 7 & 8 as an output. pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); //initialize digital pin 3 as input. pinMode(buttonPin, INPUT); } //turns both LEDs off void bothOff(){ greenState = LOW; redState = LOW; digitalWrite(greenPin, greenState); digitalWrite(redPin, redState); } //turns both LEDs on void bothOn(){ greenState = HIGH; redState = HIGH; digitalWrite(greenPin, greenState); digitalWrite(redPin, redState); } //blinks both LEDs together with interval t void blinkTogether(long t){ if (t <= 0){ // if given a negative or zero interval, turn off both LEDs and end the function call bothOff(); return; } currentMillis = millis(); if (currentMillis - previousMillis >= t) { previousMillis = currentMillis; if (redState == LOW) bothOn(); else bothOff(); } return; } //alternates the Red and Green LEDs with a time interval of t void alternate(long t){ if (t <= 0){ // if given a negative or zero interval, turn off both LEDs and end the function call bothOff(); return; } // check to see if it's time to blink the LED; that is, if the difference // between the current time and last time you blinked the LED is bigger than // the interval at which you want to blink the LED. currentMillis = millis(); if (currentMillis - previousMillis >= t) { // save the last time you blinked the LED previousMillis = currentMillis; // if the red LED is off turn it on and vice-versa: if (redState == LOW) { redState = HIGH; greenState = LOW; } else { redState = LOW; greenState = HIGH; } // set the LEDs with the states: digitalWrite(redPin, redState); digitalWrite(greenPin, greenState); } } // blinks green n times for t time and then blinks red once for t time void blinkN(int n, int t){ if (n <= 0){ // if given a negative or zero interval, turn off both LEDs and end the function call bothOff(); return; } currentMillis = millis(); //check if it's time to change the blink state if (currentMillis - previousMillis >= t){ previousMillis = currentMillis; //check if it's time to blink green or red, after blinking increment counter if (counter < n){ if (greenState == LOW){ greenState = HIGH; redState = LOW; counter++; } else{ greenState = LOW; redState = LOW; } } else{ if (greenState == HIGH && redState == LOW){ greenState = LOW; redState = LOW; } else if (greenState == LOW && redState == LOW){ greenState = LOW; redState = HIGH; } else{ greenState = LOW; redState = LOW; counter = 0; } } } digitalWrite(greenPin, greenState); digitalWrite(redPin, redState); } void sos(){ int s = 125; //short blink interval for s int l = 333; //long blink interval for o currentMillis = millis(); //if sosCounter is 0, 1, 2 || 6, 7, 8 if ((sosCounter >= 0 && sosCounter <= 2) || (sosCounter >= 6 && sosCounter <=8)){ if (currentMillis - previousMillis >= s){ previousMillis = currentMillis; if (greenState == LOW){ greenState = HIGH; redState = LOW; } else{ greenState = LOW; redState = LOW; sosCounter++; } } } else if (sosCounter >= 3 && sosCounter <= 5){ if (currentMillis - previousMillis >= l){ previousMillis = currentMillis; if (redState == LOW){ redState = HIGH; greenState = LOW; } else{ redState = LOW; greenState = LOW; sosCounter++; } } } else{ if (currentMillis - previousMillis >= 2*l){ previousMillis = currentMillis; sosCounter = 0; } } digitalWrite(greenPin, greenState); digitalWrite(redPin, redState); } // function to call blink functions dependent on blinkNum void blinkPattern(int n){ switch (n){ case 0: bothOff(); break; case 1: bothOn(); break; case 2: blinkTogether(long_interval); break; case 3: blinkTogether(short_interval); break; case 4: blinkTogether(micro_interval); break; case 5: alternate(long_interval); break; case 6: alternate(short_interval); break; case 7: alternate(micro_interval); break; case 8: blinkN(2, 200); break; case 9: blinkN(3, 150); break; case 10: sos(); break; default: bothOff(); blinkNum = 0; } } void loop() { //read the state of the pushbutton value buttonState = digitalRead(buttonPin); //check if the pushbutton is pressed, if it is, increment blinkNum by 1 if (buttonState == LOW){ bothOff(); //turn off both LEDs delay(250); // delay allows user to push button for up to 250ms and release if (blinkNum < 10){ blinkNum++; } else blinkNum = 0; } blinkPattern(blinkNum); }
Again, I'm pretty happy with how it works:
As a summary, there are 11 patterns that can be cycled through:
Each push of the button advances to the next pattern, and at the end cycles back to 0 (off).
Fairly happy with how programming via Arduino IDE went, I decided to try interfacing with the chip on a lower level, and writing C directly for the board. I decided to start with the FabAcademy Tutorials for this week.
I started by downloading the hello.ftdi.44.echo.c file and the corresponding Makefile. I plugged in my FabISP and connected my target board (as seen above) and navigated to the folder I downloaded the .c and Makefile into. I ran the command make program-usbtiny
and got the following output:
$ make program-usbtiny avr-gcc -mmcu=attiny44 -Wall -Os -DF_CPU=20000000 -I./ -o hello.ftdi.44.echo.out hello.ftdi.44.echo.c avr-objcopy -O ihex hello.ftdi.44.echo.out hello.ftdi.44.echo.c.hex;\ avr-size --mcu=attiny44 --format=avr hello.ftdi.44.echo.out AVR Memory Usage ---------------- Device: attiny44 Program: 758 bytes (18.5% Full) (.text + .data + .bootloader) Data: 64 bytes (25.0% Full) (.data + .bss + .noinit) avrdude -p t44 -P usb -c usbtiny -U flash:w:hello.ftdi.44.echo.c.hex avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.00s avrdude: Device signature = 0x1e9207 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 "hello.ftdi.44.echo.c.hex" avrdude: input file hello.ftdi.44.echo.c.hex auto detected as Intel Hex avrdude: writing flash (758 bytes): Writing | ################################################## | 100% 0.72s avrdude: 758 bytes of flash written avrdude: verifying flash memory against hello.ftdi.44.echo.c.hex: avrdude: load data flash data from input file hello.ftdi.44.echo.c.hex: avrdude: input file hello.ftdi.44.echo.c.hex auto detected as Intel Hex avrdude: input file hello.ftdi.44.echo.c.hex contains 758 bytes avrdude: reading on-chip flash data: Reading | ################################################## | 100% 0.83s avrdude: verifying ... avrdude: 758 bytes of flash verified avrdude: safemode: Fuses OK (H:FF, E:DF, L:FE) avrdude done. Thank you.
I was thrilled that this seemed to program the board successfully. Next I wanted to see it in action, so I followed the steps in this tutorial to start talking with the board over serial input. I booted up the Arduino IDE and tried to select Tools > Serial Monitor
but it gave me an error: Board at /dev/cu.usbmodem1421 is not available . I realized I probably needed to connect to my board via an FTDI cable. I didn't have an FTDI cable, but I decided to try this Instructable tutorial to setup an Arduino UNO as an FTDI cable.
The wire colors in the Instructable images were a little different from my wire colors - here's a translation:
Instructable | Function | My Color ============ | ======== | ======== WHITE | rts | BROWN YELLOW | tx | RED ORANGE | rx | ORANGE RED | vcc (5v) | YELLOW BLACK | gnd | GREEN unassigned | gnd | BLUE
Next, I realized I had to pop out the Atmega328p MCU in the Arduino. Luckily I have the DIP version and I could just pop it out with a small screwdriver
This should allow the built-in FTDI feature on the Arduino UNO to talk directly to the ATtiny44 on the Hello board.
This time, in the Arduino IDE I selected Tools > Serial Monitor
and established a viable serial connection. I set the baud rate to 152000 (as per the tutorial), and got the output: ch.c[7F˃+#&". Not sure what that means, I started sending characters. I sent "a" and got back: hello.ftdi.44.echo.c: you typed "⸮". Not promising. I tried "b" and "c" to no avail.
I tried numerals, punctuation marks, special characters, and always got back "⸮". The only character that I could send and get back correctly was "⸮" - and something told me that one wasn't actually working either ;¬D. On to try some debugging.
I wanted to see if I could write a simple sketch in Arduino and push it to the ATtiny44 (via the Arduino FTDI setup). It didn't work because in order to program the ATtiny44, the IDE tried to look for the USBtinyISP to program it with. Instead I wanted to just upload it directly with the IDE over the FTDI connection, but my hunch is that I would need to make a new board definition to make this connection work, and that's beyond my current skill. (I tried selecting Arduino/Genuino UNO as from the Tools > Boards
menu, but I got a bunch of errors: avrdude: stk500_getsync() attempt 10 of 10: not in sync: resp=0x69. I assume this means that it can't sync up to upload the sketch because it's trying to talk to the Atmega328p MCU, which is disconnected and laying on my living room table.
Though I wasn't able to program my ATtiny44 over the FTDI setup, I did discover something else interesting here. When I selected Tools > Boards > Arduino/Genuino UNO
for my board, I could then run the serial monitor, and get a different result.
This time the board would echo back a multi-character string which included my previous character inputs. For example, if I entered "I want to test the board" one character at a time, this is the output I would get back:
hello.ftdi.44.echo.c: you typed "I " hello.ftdi.44.echo.c: you typed "I " hello.ftdi.44.echo.c: you typed "I w " hello.ftdi.44.echo.c: you typed "I wa " hello.ftdi.44.echo.c: you typed "I wan " hello.ftdi.44.echo.c: you typed "I want " hello.ftdi.44.echo.c: you typed "I want " hello.ftdi.44.echo.c: you typed "I want t " hello.ftdi.44.echo.c: you typed "I want to " hello.ftdi.44.echo.c: you typed "I want to " hello.ftdi.44.echo.c: you typed "I want to t " hello.ftdi.44.echo.c: you typed "I want to te " hello.ftdi.44.echo.c: you typed "I want to tes " hello.ftdi.44.echo.c: you typed "I want to test " hello.ftdi.44.echo.c: you typed "I want to test " hello.ftdi.44.echo.c: you typed "I want to test t " hello.ftdi.44.echo.c: you typed "I want to test th " hello.ftdi.44.echo.c: you typed "I want to test the " hello.ftdi.44.echo.c: you typed "I want to test the " hello.ftdi.44.echo.c: you typed "I want to test the b " hello.ftdi.44.echo.c: you typed "I want to test the bo " hello.ftdi.44.echo.c: you typed "I want to test the boa " hello.ftdi.44.echo.c: you typed "I want to test the boar " hello.ftdi.44.echo.c: you typed "I want to test the board"
I also noticed that this pattern would wrap after 24 characters (see photos below).
This is some odd behavior, but my hunch is that this may be a problem in the C code. My challenge now is that I basically don't understand the C code well enough to debug it.
LOL. I just found this line in the tutorial which indicates that the code is working perfectly:
The characters you have typed so far are stored and echoed back along with the new character that you typed.
Guess I don't have to debug!
I'd still like to write a simple blink script in C code to teach myself how to write C for the microcontroller. It's quite different from writing within the Arduino IDE with the full suite of Arduino libraries and functions.
In order to explore writing more directly in C at a lower level, without the help of the Arduino libraries, I looked for tutorials for a simple blink sketch. I found one on youtube and went with it. This very simple program will blink my red LED every 500ms:
// simple program to blink an LED on the hello board // using ATtiny44 // // adapted from: https://www.youtube.com/watch?v=SxJZGqToqu4 // // red pin is PB2 #include <avr/io.h> //define F_CPU 1000000UL // 1 MHz - I don't think I need to include this #include <util/delay.h> int main(void){ DDRB |= (1<<DDB2); //make PB2 output while(1){ PINB = (1<<PINB2); //bit shift pin b2 _delay_ms(500); } }
Note: HTML doesn't seem to like when my code includes << even within the <pre> tag. Something to watch out for when pasting code... View page source here if you are confused about what I mean.
I saved this program as simpleBlink.c and made a new Makefile which set "simpleBlink" as the $PROJECT variable. I then hooked up my board, and entered make program-usbtiny
and got the following output:
$ make program-usbtiny avr-gcc -mmcu=attiny44 -Wall -Os -DF_CPU=20000000 -I./ -o simpleBlink.out simpleBlink.c avr-objcopy -O ihex simpleBlink.out simpleBlink.c.hex;\ avr-size --mcu=attiny44 --format=avr simpleBlink.out AVR Memory Usage ---------------- Device: attiny44 Program: 82 bytes (2.0% Full) (.text + .data + .bootloader) Data: 0 bytes (0.0% Full) (.data + .bss + .noinit) avrdude -p t44 -P usb -c usbtiny -U flash:w:simpleBlink.c.hex avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.00s avrdude: Device signature = 0x1e9207 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 "simpleBlink.c.hex" avrdude: input file simpleBlink.c.hex auto detected as Intel Hex avrdude: writing flash (82 bytes): Writing | ################################################## | 100% 0.12s avrdude: 82 bytes of flash written avrdude: verifying flash memory against simpleBlink.c.hex: avrdude: load data flash data from input file simpleBlink.c.hex: avrdude: input file simpleBlink.c.hex auto detected as Intel Hex avrdude: input file simpleBlink.c.hex contains 82 bytes avrdude: reading on-chip flash data: Reading | ################################################## | 100% 0.13s avrdude: verifying ... avrdude: 82 bytes of flash verified avrdude: safemode: Fuses OK (H:FF, E:DF, L:FE) avrdude done. Thank you.
Success! My board was simply blinking the red LED on and off every 0.5s!
This program took up 82 bytes of flash memory (2% of my available 4096 bytes). For comparison's sake, I wrote an equally simple sketch in the Arduino IDE to blink the green LED:
int greenPin = 7; void setup() { // put your setup code here, to run once: pinMode(greenPin, OUTPUT); } void loop() { // put your main code here, to run repeatedly: digitalWrite(greenPin, HIGH); delay(500); digitalWrite(greenPin, LOW); delay(500); }
The IDE told me the following:
Sketch uses 960 bytes (23%) of program storage space. Maximum is 4096 bytes.
Global variables use 9 bytes (3%) of dynamic memory, leaving 247 bytes for local variables. Maximum is 256 bytes.
Even though this arduino program is functionally equivalent to the c program, and uses a similar number of lines of code, it is more than 10x as large in memory.
As a further project, I'd like to figure out a) how to write this without using the delay function and b) how to read button pushes and c) translate my whole "Blink Pattern" program into lower-level c code, and see how much smaller it is. Projects for the future.
A quick note: I watched an interesting AVR microcontroller webcast by the author of the Make: AVR Programming book. He gives a good overview of microcontroller programming, including a good section on bitwise operators, memory registers, etc. You can check it out on YouTube.
I also found a great set of Atmel Programming Tutorials from Chris Dahms on YouTube. He uses the Atmel Studio IDE which I am not using, but otherwise, these seem like a great overview.
After watching these videos and absorbing some of their content, I went on to write some simple blink programs, including programs that use the pushbutton for input. Above I showed my first, simplest blink program. This is a slight modification to alternate between the red and green LEDs:
// simple program to blink an LED on the hello board // using ATtiny44 // // adapted from: https://www.youtube.com/watch?v=SxJZGqToqu4 // // green pin is PA7 // red pin is PB2 #include <avr/io.h> //define F_CPU 1000000UL // 1 MHz - I don't think I need to include this #define DELAY 100 #include <util/delay.h> int main(void){ DDRB |= (1<<DDB2); //make PB2 output DDRA |= (1<<DDA7); //make PA7 output while(1){ PINB = (1<<PINB2); //bit shift pin b2 _delay_ms(DELAY); PINA = (1<<PINA7); //bit shift pin a7 } }
Next, I wrote a program to turn the green LED on when the button is pushed in:
//program to use button on board to blink green LED // //based on https://github.com/MicrocontrollersAndMore/Atmel_Programming_Tutorial_3_Bit_Manipulation_and_Digital_Inputs/blob/master/DigitalInputs.c //assumes F_CPU is already setup correctly with fuses //green LED is on PA7 //red LED is on PB2 //button is on PA3 #include <avr/io.h> #define BIT_IS_SET(byte, bit) (byte & (1 << bit)) #define BIT_IS_CLEAR(byte, bit) (!(byte & (1 << bit))) int main (void){ DDRA |= (1 << PA7); //set PA7 to output DDRA &= ~(1 << PA3); //set PA3 to input while(1){ if(BIT_IS_CLEAR(PINA, PA3)) { //if button is pushed PORTA |= (1 << PA7); //light the green LED } else if(BIT_IS_SET(PINA, PA3)) { //else if button isn't pushed PORTA &= ~(1 << PA7); //turn off green LED } else{ //should never get here... not sure why included } } return(0); //should never get here either }