Home
Weekly Assignments
Final Project

7. Embedded programming

The time had come! The brain of the project will be growing from now on, no excuses left!

Combining all the possible instructions of the microcontroller to achieve the desired behaviour can be tough. That's why the goal of this week is to learn the basics about microcontroller programming.

In order to do that, I read the full datasheet of the ATmega168A. There I learned about the architecture and its possibilities. Obviously I didn't memorize everything because I know I can take a look at that document in case I need any of the described features.

I had worked with PIC microcontrollers previously, but reading about all its features didn't come as a surprise.

Serial echoing

board

To start a program from scratch is always a tough job, but if you are not 100% sure that the hardware that is going to support that program is working as expected, the process can become frustrating. Where's the error? Is it on the board? Could it be in the software?, or... just everywhere?!

That's why I decided to start programming the board using the echo example, as Neil suggested. Everything behaved as expected. In a few minutes the board was answering to the keystrokes through the serial port.

Loading the program was as easy as executing the makefile with the "program-usbtiny" parameter and waiting until it was loaded in the microcontroller.

To take it one step further, I analyzed the code of the serial example with interruptions. Once I understood the configuration section and how everything worked, I started playing around with the instructions.

Now, each time a keystroke is sent to the microcontroller through the serial port, it sends the whole buffer and the led blinks. If the board button is pressed, the led blinks, a "Pressed" word is sent through the serial port, and the buffer is erased. From then on, the user can send a new sentence to the microcontroller without overlapping it with the one before.

Initially everything seemed to work fine, but after a few keystrokes I realized that some strange characters were being sent and received through the serial port. After some research I got to the conclusion that my program was not being correctly synchronized with the serial port of my computer. Changing and reordering some instructions solved the problem, or at least that's what I thought.

That solution (without having to change the model of the microcontroller or the pins of the components) made me realize that the synchronization issue was not a trivial problem to solve; if I changed the instruction order, the serial port may read/write incorrect values.

Anyhow I didn't like the solution. It should work regardless of the instruction order.

Finally, I got to the conclusion that the serial buffer was not empty. I changed the code to send an empty char (0) before sending the whole array. That worked perfectly, the program works even changing the order of some of the commands.

serial
  1. //
  2. //
  3. // hello.ftdi.44.echo.c
  4. //
  5. // 115200 baud FTDI character echo, with flash string
  6. //
  7. // set lfuse to 0x7E for 20 MHz xtal
  8. //
  9. // Neil Gershenfeld
  10. // 12/8/10
  11. //
  12. // (c) Massachusetts Institute of Technology 2010
  13. // Permission granted for experimental and personal use;
  14. // license for commercial sale available from MIT.
  15. //
  16. // Modified by Alejandro Escario
  17. // Mar 2015
  18.  
  19. #include <avr/io.h>
  20. #include <util/delay.h>
  21. #include <avr/interrupt.h>
  22. #include <avr/pgmspace.h>
  23.  
  24. #define output(directions,pin) (directions |= pin) // set port direction for output
  25. #define set(port,pin) (port |= pin) // set port pin
  26. #define clear(port,pin) (port &= (~pin)) // clear port pin
  27. #define pin_test(pins,pin) (pins & pin) // test for port pin
  28. #define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
  29. #define bit_delay_time 8.5 // bit delay for 115200 with overhead
  30. #define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
  31. #define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
  32. #define char_delay() _delay_ms(10) // char delay
  33.  
  34. #define serial_port PORTA
  35. #define serial_direction DDRA
  36. #define serial_pins PINA
  37. #define serial_pin_in (1 << PA0)
  38. #define serial_pin_out (1 << PA1)
  39.  
  40. #define max_buffer 25
  41.  
  42. void get_char(volatile unsigned char *pins, unsigned char pin, char *rxbyte) {
  43. //
  44. // read character into rxbyte on pins pin
  45. // assumes line driver (inverts bits)
  46. //
  47. *rxbyte = 0;
  48. //
  49. // delay to middle of first data bit
  50. //
  51. bit_delay();
  52. //
  53. // unrolled loop to read data bits
  54. //
  55. if pin_test(*pins,pin)
  56. *rxbyte |= (1 << 0);
  57. else
  58. *rxbyte |= (0 << 0);
  59. bit_delay();
  60. if pin_test(*pins,pin)
  61. *rxbyte |= (1 << 1);
  62. else
  63. *rxbyte |= (0 << 1);
  64. bit_delay();
  65. if pin_test(*pins,pin)
  66. *rxbyte |= (1 << 2);
  67. else
  68. *rxbyte |= (0 << 2);
  69. bit_delay();
  70. if pin_test(*pins,pin)
  71. *rxbyte |= (1 << 3);
  72. else
  73. *rxbyte |= (0 << 3);
  74. bit_delay();
  75. if pin_test(*pins,pin)
  76. *rxbyte |= (1 << 4);
  77. else
  78. *rxbyte |= (0 << 4);
  79. bit_delay();
  80. if pin_test(*pins,pin)
  81. *rxbyte |= (1 << 5);
  82. else
  83. *rxbyte |= (0 << 5);
  84. bit_delay();
  85. if pin_test(*pins,pin)
  86. *rxbyte |= (1 << 6);
  87. else
  88. *rxbyte |= (0 << 6);
  89. bit_delay();
  90. if pin_test(*pins,pin)
  91. *rxbyte |= (1 << 7);
  92. else
  93. *rxbyte |= (0 << 7);
  94. //
  95. // wait for stop bit
  96. //
  97. bit_delay();
  98. half_bit_delay();
  99. }
  100.  
  101. void put_char(volatile unsigned char *port, unsigned char pin, char txchar) {
  102. //
  103. // send character in txchar on port pin
  104. // assumes line driver (inverts bits)
  105. //
  106. // start bit
  107. //
  108. clear(*port,pin);
  109. bit_delay();
  110. //
  111. // unrolled loop to write data bits
  112. if bit_test(txchar,0)
  113. set(*port,pin);
  114. else
  115. clear(*port,pin);
  116. bit_delay();
  117. if bit_test(txchar,1)
  118. set(*port,pin);
  119. else
  120. clear(*port,pin);
  121. bit_delay();
  122. if bit_test(txchar,2)
  123. set(*port,pin);
  124. else
  125. clear(*port,pin);
  126. bit_delay();
  127. if bit_test(txchar,3)
  128. set(*port,pin);
  129. else
  130. clear(*port,pin);
  131. bit_delay();
  132. if bit_test(txchar,4)
  133. set(*port,pin);
  134. else
  135. clear(*port,pin);
  136. bit_delay();
  137. if bit_test(txchar,5)
  138. set(*port,pin);
  139. else
  140. clear(*port,pin);
  141. bit_delay();
  142. if bit_test(txchar,6)
  143. set(*port,pin);
  144. else
  145. clear(*port,pin);
  146. bit_delay();
  147. if bit_test(txchar,7)
  148. set(*port,pin);
  149. else
  150. clear(*port,pin);
  151. bit_delay();
  152. //
  153. // stop bit
  154. //
  155. set(*port,pin);
  156. bit_delay();
  157. //
  158. // char delay
  159. //
  160. char_delay();
  161. }
  162.  
  163. void put_string(volatile unsigned char *port, unsigned char pin, char *str) {
  164.  
  165. put_char(&serial_port, serial_pin_out, 0); // Hack that avoids sending strage characters
  166. int i = 0;
  167. do {
  168. put_char(port, pin, str[i]);
  169. } while (str[++i] != 0);
  170. }
  171.  
  172. void blink(void){
  173. // blinks de led
  174. PORTA |= (1 << PA7);
  175. _delay_ms(100);
  176. PORTA &= (0 << PA7);
  177. }
  178.  
  179. int erase(char *str){
  180. // cleans the buffer
  181. int i = 1;
  182. while(i < max_buffer) {
  183. str[i++]=0;
  184. }
  185. return 0;
  186. }
  187.  
  188. ISR(PCINT0_vect) {
  189. // interrupt function
  190. static char buffer[max_buffer] = {0};
  191. static int index;
  192. static char chr;
  193. if(~PINA & (1 << PA0)){ // character is prepared to be received
  194. get_char(&serial_pins, serial_pin_in, &chr);
  195. put_string(&serial_port, serial_pin_out, "echo: \"");
  196. buffer[index++] = chr;
  197. if (index == (max_buffer-1))
  198. index = 0;
  199. put_string(&serial_port, serial_pin_out, buffer);
  200. put_string(&serial_port, serial_pin_out, "\"\n");
  201. blink();
  202. } else{
  203. if(~PINA & (1 << PA3)){ // button pressed
  204. blink();
  205. put_string(&serial_port, serial_pin_out, "Reset\n");
  206. index = erase(buffer);
  207. }
  208. }
  209. }
  210.  
  211. int main(void) {
  212. //
  213. // main
  214. //
  215. // the clock divider to /1 has been set with the fuses
  216. //
  217. // initialize output pins
  218. //
  219. set(serial_port, serial_pin_out);
  220. output(serial_direction, serial_pin_out);
  221. DDRA &= ~(1 << PA3);
  222. DDRA |= (1 << PA7);
  223. //
  224. // set up pin change interrupt on input pin
  225. //
  226. set(GIMSK, (1 << PCIE0));
  227. set(PCMSK0, (1 << PCINT0));
  228. set(PCMSK0, (1 << PCINT3));
  229. sei(); // enable interruptions
  230. //
  231. // main loop
  232. //
  233. while (1);
  234. }

Interrupt

In the example above I was using the PCINT0_vect to manage the interruption of the on-board button. Luckily, last week I added two extra sockets in order to use the free ports if needed. Using a true hole button and a protoboard, I wrote a program that controls the falling and/or rising edges of the button connected to the INT0 pin. Using that information, the program blinks a led.

  1. //
  2. //
  3. // hello.ftdi.44.interrupt.c
  4. //
  5. // Neil Gershenfeld
  6. // 12/8/10
  7. //
  8. // (c) Massachusetts Institute of Technology 2010
  9. // Permission granted for experimental and personal use;
  10. // license for commercial sale available from MIT.
  11. //
  12. // Modified by Alejandro Escario
  13. // Mar 2015
  14.  
  15. #include <avr/io.h>
  16. #include <util/delay.h>
  17. #include <avr/interrupt.h>
  18. #define set(port,pin) (port |= pin) // set port pin
  19.  
  20.  
  21. ISR(INT0_vect) {
  22. _delay_ms(10); // debounce timer
  23. toggle(PA7);
  24. }
  25.  
  26. void toggle(int port){
  27. PORTA ^= (1 << port);
  28. }
  29. int main(void) {
  30. //
  31. // main
  32. //
  33. // the clock divider to /8 has been set with the fuses
  34. //
  35. // initialize output pins
  36. //
  37. DDRA &= ~(1 << PA3); // button as input
  38. DDRA |= (1 << PA7); // in-board led as output
  39. DDRB &= ~(1 << INT0); // interruptor as input
  40. //
  41. // set up pin change interrupt on input pin
  42. //
  43. set(GIMSK, (1 << INT0)); // enabling PCINT interrupts
  44. MCUCR |= (1 << ISC00); // Any logical request
  45. //MCUCR |= (1 << ISC01); // falling edge
  46. //MCUCR |= (1 << ISC01) || (1 << ISC00); // rising edge
  47. sei(); // enable interruptions
  48. while(1);
  49. }

Timers

Generating a controlled output signal might be useful in a near future to control output devices such us stepper motors. Therefore, I decided to spend some time understanding the Attiny44 timers.

CTC

This program uses a CTC timer to make two leds blink. One connected to the OC0A and OC0B ports. Additionally, it triggers an interruption that increases the OCR0A counter. This way the time between blinks varies. Once OCR0A reaches the 0xFF value, its value is changed to 0.

Both leds were blinking too fast to be appreciated by a human eye. I changed the fuses to divide the clock by 8.

  1. //
  2. //
  3. // hello.ftdi.44.ctc.c
  4. //
  5. // Neil Gershenfeld
  6. // 12/8/10
  7. //
  8. // (c) Massachusetts Institute of Technology 2010
  9. // Permission granted for experimental and personal use;
  10. // license for commercial sale available from MIT.
  11. //
  12. // Modified by Alejandro Escario
  13. // Mar 2015
  14.  
  15. #include <avr/io.h>
  16. #include <util/delay.h>
  17. #include <avr/interrupt.h>
  18. #define set(port,pin) (port |= pin) // set port pin
  19.  
  20.  
  21. ISR(PCINT0_vect) {
  22. _delay_ms(10); // debounce timer
  23. if(~PINA & (1 << PA3)){ // falling edge
  24. OCR0A = 0x00;
  25. }
  26. }
  27.  
  28. void toggle(void){
  29. PORTA ^= (1 << PA7);
  30. }
  31.  
  32. ISR(TIM0_COMPA_vect) {
  33. if(OCR0A >= 0xFF){
  34. OCR0A = 0x00;
  35. }else{
  36. OCR0A += 0x01;
  37. }
  38. }
  39. int main(void) {
  40. //
  41. // main
  42. //
  43. // the clock divider to /8 has been set with the fuses
  44. //
  45. // initialize output pins
  46. //
  47. DDRA &= ~(1 << PA3); // button as input
  48. DDRA |= (1 << PA7) | (1 << PA2); // in-board led as output
  49. DDRB |= (1 << PB2); // led as output
  50.  
  51.  
  52. TIMSK0 |= (1 << OCF0A); // enable interrupt enable
  53. TCCR0A = (1 << COM0A0) | (1 << COM0B0) | (1 << WGM01); // CTC + Toggle on Compare Match
  54. OCR0A = 0x00; // initial pulse width
  55. TCCR0B = (1 << CS02) | (1 << CS00); // /1024 prescaling
  56. //
  57. // set up pin change interrupt on input pin
  58. //
  59. set(GIMSK, (1 << PCIE0)); // enabling PCINT interrupts
  60. set(PCMSK0, (1 << PCINT3)); // enabling the interrupts on PCINT3
  61. sei(); // enable interruptions
  62. PORTA &= (0 << PA2); // 0V on PA2
  63. while(1){
  64. };
  65. }

PWM

Using a PWM modulation, I wrote a program that changes the intensity of a led (connected to the OC0A pin) when a button is pressed.

  1. //
  2. //
  3. // hello.ftdi.44.pwm.c
  4. //
  5. // Neil Gershenfeld
  6. // 12/8/10
  7. //
  8. // (c) Massachusetts Institute of Technology 2010
  9. // Permission granted for experimental and personal use;
  10. // license for commercial sale available from MIT.
  11. //
  12. // Modified by Alejandro Escario
  13. // Mar 2015
  14.  
  15. #include <avr/io.h>
  16. #include <util/delay.h>
  17. #include <avr/interrupt.h>
  18.  
  19. #define set(port,pin) (port |= pin) // set port pin
  20.  
  21. ISR(PCINT0_vect) {
  22. _delay_ms(10); // debounce timer
  23. if(~PINA & (1 << PA3)){ // falling edge
  24. if(OCR0A >= 0xF0){
  25. OCR0A = 0x00;
  26. }else{
  27. OCR0A += 0x10;
  28. }
  29. }
  30. }
  31. int main(void) {
  32. //
  33. // main
  34. //
  35. // the clock divider to /1 has been set with the fuses
  36. //
  37. // initialize output pins
  38. //
  39. DDRA |= (1 << PA2); // to be set as gnd
  40. DDRA &= ~(1 << PA3); // button as input
  41. DDRB |= (1 << PB2); // led as output
  42. TCCR0A = (1 << COM0A1) | (1 << WGM00); // phase correct PWM + on compare match
  43. OCR0A = 0xFF; // initial pulse width
  44. TCCR0B = (1 << CS01) | (1 << CS00); // /64 prescaling
  45. //
  46. // set up pin change interrupt on input pin
  47. //
  48. set(GIMSK, (1 << PCIE0)); // enabling PCINT interrupts
  49. set(PCMSK0, (1 << PCINT3)); // enabling the interrupts on PCINT3
  50. sei(); // enable interruptions
  51. //
  52. PORTA &= (0 << PA2); // 0V on PA2
  53. while(1);
  54. }

Assembler

serial

Another interesting language to program the microcontroller is assembler.

Using assembler instructions the programmer can control and explode all the microcontroller possibilities, and optimize the code. On the other hand, the programming process can become tougher and frustrating.

In order to get familiar with this language and microprocessor I decided to create a very simple program that turns on and off a led while pressing the button. This can be done within a loop or using interruptions.

  1. ;
  2. ; hello.ftdi.blink.44.asm
  3. ;
  4. ; Led + button -
  5. ; Alejandro Escario - FabAcademy 2015
  6.  
  7.  
  8. .include "tn44def.inc"
  9.  
  10. .equ led_pin = PA7; led pin is PA7
  11. .equ button_pin = PA3;
  12. .equ led_port = PORTA; comm port
  13. .equ led_dir = DDRA; comm direction
  14.  
  15. ; program is in lower part of memory
  16. .cseg ; code segment init
  17. .org 0 ; sets the location counter to an absolute value
  18. rjmp reset
  19.  
  20.  
  21. ;
  22. ; main program
  23. ;
  24. reset:
  25. ; set pin to output
  26. sbi led_dir, led_pin
  27. ;
  28. ; start main loop
  29. ;
  30. loop:
  31. sbic PINA, button_pin
  32. cbi led_port, led_pin ; set led pin
  33. SBIS PINA, button_pin
  34. sbi led_port, led_pin
  35. rjmp loop

My first approach was to use a loop, but I finally used an interrupt.

  1. ;
  2. ; hello.ftdi.44.blink.interrupt.asm
  3. ;
  4. ; Led + button (interrupt)
  5. ; Modified by Alejandro Escario - FabAcademy 2015
  6.  
  7.  
  8. .include "tn44def.inc" ; constant definitions
  9.  
  10. .equ led_pin = PA7; led pin is PA7
  11. .equ button_pin = PA3;
  12. .equ led_dir = DDRA; comm direction 1 => OUTPUT (PORT) 0 => INPUT (PIN)
  13.  
  14. .def temp = R16; temporary storage
  15.  
  16. .cseg ; code segment init
  17. .org $0000
  18. rjmp reset ; sets the location counter to an absolute value
  19. rjmp 0 ; interrupt 0
  20. rjmp pcint0_handler
  21.  
  22. ;
  23. ; main program
  24. ;
  25. reset:
  26. ; set pin to output
  27. sbi led_dir, led_pin
  28.  
  29. ;
  30. ;interrupt enable for PA3
  31. ;
  32. ldi temp, (1 << PCIE0)
  33. out GIMSK, temp
  34. ldi temp, (1 << PCINT3)
  35. out PCMSK0, temp
  36. sei ; set Global Interrupt Enable
  37. cbi PORTA, led_pin ; set led pin
  38. loop:
  39. sbic PINA, PA3 ; if the button is not pressed
  40. cbi PORTA, led_pin ; turn off the led
  41. sleep
  42. nop
  43. rjmp loop;
  44. pcint0_handler:
  45. sbi PORTA, led_pin ; set led pin
  46. reti

FabKit

At first sight, I knew that my project would need to use serial communications quite often. Taking into consideration the problems I faced with the echo example, the ATtiny44 might not be my best chance to develop the project.

The ATmega168 is. So let's build an Arduino board. I will be using the Fabkit model by now.

Once again the letters of the FabKit board were not milled as expected,... I will go on trying to solve that problem.

programming

To check that everything works as expected, I loaded a very simple program (based on Neil's one) that makes the led blink as many times as the iteration of the loop that is being run.

  1. //
  2. // hello.arduino.168.blink.c
  3. //
  4. // test blinking LED
  5. //
  6. // Neil Gershenfeld
  7. // 10/21/13
  8. //
  9. // Modified by Alejandro Escario - FabAcademy 2015
  10.  
  11. #include <avr/io.h>
  12. #include <util/delay.h>
  13.  
  14.  
  15. #define output(directions,pin) (directions |= pin) // set port direction for output
  16. #define set(port,pin) (port |= pin) // set port pin
  17. #define clear(port,pin) (port &= (~pin)) // clear port pin
  18. #define pin_test(pins,pin) (pins & pin) // test for port pin
  19. #define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
  20. #define led_delay() _delay_ms(100) // LED delay
  21. #define num_delay() _delay_ms(500) // Iteration delay
  22.  
  23. #define led_port PORTB
  24. #define led_direction DDRB
  25. #define led_pin (1 << PB5)
  26.  
  27. int main(void) {
  28. //
  29. // main
  30. //
  31. // set clock divider to /1
  32. //
  33. CLKPR = (1 << CLKPCE);
  34. CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0); // 8MHz (CLK) vs 8MHz (desired freq) (scaling factor 1)
  35. //
  36. // initialize LED pin
  37. //
  38. clear(led_port, led_pin);
  39. output(led_direction, led_pin);
  40. //
  41. // main loop
  42. //
  43. int i = 0;
  44. int j = 0;
  45. while (1) {
  46. if(++i >= 255){
  47. i = 0;
  48. }
  49. j = 0;
  50. while(j < i){
  51. set(led_port, led_pin);
  52. led_delay();
  53. clear(led_port, led_pin);
  54. led_delay();
  55. j++;
  56. }
  57. num_delay();
  58. }
  59. }

This program made me realize that there was a problem: the led was not blinking. After reading multiple times the code and making sure that everything was OK, I got to the conclusion that there must have been a hardware error: the led was not pointing to the right direction. Once I fixed that problem, it started blinking properly.

I wanted to install the Arduino Bootloader. After configuring the IDE as described in the FabKit tutorial the bootloader was correctly uploaded. But when I tried to load the Blink example... I got an error:

avrdude stk500_getsync() not in sync resp=0x00
sync error

This error was a complete nightmare. Searching the Internet, the "solutions" I found were not fixing the problem. After trying to load the Bootloader and the program in several computers and getting always the same error, I solved the problem by running the Arduino IDE as the system administrator. To do that in MAC OS X (I am currently using OS X 10.10 - Yosemite) the executed command is:

~$ sudo /Applications/Arduino.app/Contents/MacOS/JavaAppLauncher

Then, the Bootloader and the program were loaded correctly. The strangest thing is that I don't have to open the IDE as administrator any more. Since I did that once, everything seems to be working perfectly.

Files

Serial echoing

Interrupt

Timers

CTC

PWM

Assembler

FabKit