Embedded Programming

This week we had to program a board to do something. I decided to program my board from week 6 to use its LED and its switch. Also, I wanted a 'hello world' program using FTDI serial communication. Here's the little chickling with a switch for the eye and a red LED for the beak:

We also had to read a microcontroller datasheet. The obvious choice for me was the ATtiny45 datasheet since it was what I had used in my board, and one of the three available microprocessors at the lab.

However, I was also interested in exploring the AVR128DB32 datasheet as it is what Neil has recommended to use in my final project as an instrumentation amplifier, and remains to be ordered.

The group assignment was to compare the performance and development workflows for various architectures, which can be found here.

Embedded programming: Ways to upload the code to the microcontroller

Before presenting the exact commands, I'd like to mention that there are various ways of uploading the code. One is through the terminal in Linux. Another is using the Arduino IDE whether on Linux, Windows, or Mac. Another way would be using Microchip Studio.

I tried to program the ATTiny45 microcontroller in two differet environments: programming in C and uploading it directly using a Linux terminal, and then trying to program it again in Windows using the Arduino IDE. The Arduino IDE uses C for its programming language as well, albeit with many libraries.

If using the Linux terminal to program, an in-system programmer (ISP) should be used to program the board. This is a separate board that is capable of connecting to the PC via USB and a FTDI serial connection and connect to the board with the 2x3 pin header and help to program it. FabTinyISP is one such DIY programmer for ATtiny45 and ATtiny44 that was created in week 4. Another relevant option would be the AVR programmer. If using board other than the ATtiny45 or ATtiny44, other programmers such as UPDI or JTAG may be needed.

If using the Arduino IDE, the Arduino board itself can be used as a programmer.

The program itself is saved in a .c file if Linux terminal is to be used. If using Arduino IDE, eventhough the code itself is still in C, many Arduino libraries are used, and the code itself is saved as .ino. To find out the difference between .c and .ino files, check out the Pre-Processing section on this page.

In order to compile the file according to the type of ISP that is used, a makefile is created (saved under .make).

Embedding the microprocessor in the Linux environment

There were five programs that were embedded on the ATTiny45 microcontroller using the Linux terminal. All the programs were writtein in C; however, the last one (which was written by Neil) also had a terminal written in Python. They were as follows:

  • Program 1: The simplest blink program
  • Program 2: The simplest blink and switch program
  • Program 3: A more complex blink program
  • Program 4: Double-click blink program (didn't work)
  • Program 5: Echo Hello World program (written by Neil)

Program 1: The simplest blink program

Before programming the ATtiny45 microcontroller to be able to blink the LED and use the switch, the microcontroller datasheet should be studied.

First and foremost, let's note that all the io pins of the microcontroller belong to Port B (for comparison, in ATtiny44 there is a Port A and a Port B). Here's the pin configuration of ATtiny45:

Comparing the pinout to the chick board, it can be seen that PB3 (or Port B3) is connected to the LED, and PB4 is connected to the switch.

Creating the C code for the program, and later the makefile to compile it can be done through any text editor. In Linux, nano is one of the simplest text editors that can be used, and can be accessed through the command line of the Linux terminal:

Now, there are three important entities to be aware of when trying to program a microcontroller. These are bite-size memory allocations in the microcontroller that control input and output to and from the microcontroller. They are bite-sized (or contain 8 bits each) as the ATtiny45 microcontroller also contains 8 pins, so the value of each bit corresponds to its namesake pin.

  • Data Register: In case of ATtiny45, there's only Port B data register, or PORTB. This is where high and low values for each pin are saved. How they'll be used is defined by the next entity.
  • Data Direction Register: or DDRB in case of ATtiny45. Each bit here defines whether each pin of the microcontroller will be an input or an output pin. A value of 0 corresponds to 'read' (so the signal at the corresponding pin will be input)and a value of 1 corresponds to 'write' (or output).
  • Input Pins Address: or PINB in ATtiny45. This is where the input signal from each pin gets registered. This can be used to check whether a switch is pressed or any other relevant signal is present or not at a given pin.

This page helps a lot further in explainaing what each thing in a piece of embedded programming C code means. This is a great figure from that page:

And here's yet another very helpful page in explaining microcontroller code.

I found the simplest, most straight-forward piece of code to simply blink in Florian Schaefer's page from Kamplintfort. It was as following with added explanations (as comments, starting with //) -- note that pin 2 (PB3) of the microcontroller is connected to the beak LED.

    #include <avr/io.h> // this line includes the input/output C library io.h
    #include <util/delay.h> // this line includes the C delay library delay.h

    int main(void)
    {
      DDRB = 0x08; // This is setting pin 3 as an output port. 08 is a hex value (1000 in binary).
    // The 0x prefix means that the number that follows is in hexadecimal base, while the prefix 0b signifies binary base. More info.
      while (1) { // this would always be true so loop would go on forever
       PORTB = 0x00; _delay_ms(500); // set PORTB to 00000000, delay for 500 ms
       PORTB = 0x08; _delay_ms(500); // set PORTB to 00001000, delay for 500 ms
      }
    }

The function of each line of code has been explained in comments in front of the line itself. With some logic functions (AND, OR, NOT, XOR) a much more complicated piece of code can be developed to compare incoming signals and act accordingly. Here's just a shortlist of what's available and common formats of the C code to handle various functions:

  • &: This is the AND function is C.
  • |: The pipe character represents the OR function in C.
  • ^ : The hat symbol translates as XOR in logic.
  • ~: Tilda represents NOT.
  • Remember how in C++ you can write varA += varB; and it is equivalent to varA = varA + varB;? Well, here too, you can write DDRB |= 0b00000001; and it is equivalent to DDRB = DDRB | 0b00000001; This opens up a lot of possibilities for logical manipulation of data.
  • PINBx: This is equal to x. PINB0 is equal to 0, PINB4 is equal to 4.
  • Creating masks: Masks are bites that can be used for logical manipulation of registery or other values. They are usually created by shifting a 1 or a 0 a defined number of places to the left in a bite. E.g. 1 << PINB0 results in shifting the number 1 0 positions to the left in a bite, resulting in the mask 00000001. 1 << PIN1 results in 00000010, so with 1 shifted one place to the left. The mask is ready to be logically compared to another bite.

This code (and the second part including the switch) have been my favorite of this week due to their minimalism. I love minimalist code which looks simple and is easy to understand, yet performs exactly what is needed efficiently.

Now that we know what all that code does, let's actually upload the code unto the chick board's ATtiny45 microcontroller so that we can see whether the beak LED blinks because of it. For me, uploading the code was the hardest part of this assignment, as any slight deviation from the commands needed would result in some problem or another.

Emedding the code under Linux

In order to compile the file according to the type of ISP that is used, a makefile is created (saved under .make). Here's an example of what the makefile looks like with some added commentary (starting with #):

    PROJECT=blink #The name of the project
    SOURCES=$(PROJECT).c #Name of the C file to be compiled
    MMCU=attiny45 #Name of the microcontroller
    F_CPU = 8000000 #Frequency of the microcontroller in Hz

    CFLAGS=-mmcu=$(MMCU) -Wall -Os -DF_CPU=$(F_CPU)

    $(PROJECT).hex: $(PROJECT).out
      avr-objcopy -O ihex $(PROJECT).out $(PROJECT).c.hex;\
      avr-size --mcu=$(MMCU) --format=avr $(PROJECT).out

    $(PROJECT).out: $(SOURCES)
      avr-gcc $(CFLAGS) -I./ -o $(PROJECT).out $(SOURCES)

    program-avrisp2: $(PROJECT).hex # In case of an AVR programmer, use these 2 lines
      avrdude -p t45 -P usb -c avrisp2 -U flash:w:$(PROJECT).c.hex

    program-usbtiny: $(PROJECT).hex # In case of a FabTinyISP programmer, use these 2 lines
      avrdude -p t45 -P usb -c usbtiny -U flash:w:$(PROJECT).c.hex

This is how a typical makefile looks like opened with nano:

A lot more detail about the Fab Academy Makefile can be found here.

The makefile is saved with the extension .make and is run using the command make.

In order to program the board, the FabTinyISP is connected to the computer's USB port in order to receive the program. It is preferable that a USB cable is used so that the ISP does not put a lot of load on the computer's motherboard. An FTDI cable is connected from the computer to the FabTinyISP in order to transfer serial communication as well as power the board. And the ISP itself is connected to the board using 2x3 pin headers on both ends.

There is a fine point to be made here, and it is that care must be taken so that the 2x3 pinheaders are connected correctly:

One must take care that GND is connected to ground, and the same with all the other pins. The following diagram shows the chick board pinheader configuration with the chick right-side-up to the left, and the FabTinyISP pinheader configuration with the USB connection facing left:

Also, depending on the 6-wire cable and the direction in which the headers to connect to the pinheaders are installed, one may need to flip the cable or connect directly -- you can always check with a multimeter. Also, it is helpful to note that the physical 'dot' etched on the top of ATtiny45 microcontroller signified the RST pin.

A Linux terminal is opened, and the following command is used to compile the file:

make -f blink.make

If you're not logged in under an admin account, try the following instead (which allows the previlages of a superuser):

sudo make -f blink.make

The option -f is used to tell the command to use the filename mentioned (blink.make in this case) as the file being compiled, and not look for default files GNUmakefile, makefile, and Makefile, in that order.

Then the following command is used to upload the compiled file to the microcontroller:

make -f blink.make program-usbtiny

Again, if your account is not an admin one, try the following instead:

sudo make -f blink.make program-usbtiny

program-usbtiny option is specified in the program code itself: you just subsitute the programmer that you're using so it uploads accordingly.

More about the make command in Linux can be found out here.

The blink program was uploaded and worked well.

Program 2: The simplest blink and switch program

The code for the blink program had to be modified slightly to add functioning of the switch. The switch in my board, as can be seen, was connected to pin 3 -- PB4. That means the 4th bit (starting with 0) in the data registeration bit, or 0b00010000. In hexadecimal, that would be 0x10.

The following code blinks every 100 ms when the button is not pushed, and stops blinking when the button is pushed.

    #include <avr/io.h>
    #include <util/delay.h>

    while (1) {
      if((PINB&0x10)){

        PORTB = 0x00; _delay_ms(100);
        PORTB = 0x08; _delay_ms(100);
      }
      else {
      PORTB=0x00;
      }
    }

PINB&0x10 is comparing pin input values to 0b00010000, and entering the while loop if the value input from pin 4 is high, which would mean that the while loop is executed only when the switch is pressed.

The simple clean code worked like a charm:

The blink and switch C program can be downloaded at the bottom of this page.

Program 3: A more complex blink program

This blink program uses more features of the AVR libraries, so that instead of pushing hexadecimal values into the port variable to turn the LED on or off, they are predefined in the beginning. Here is how the program looked like:

    #include <avr/io.h>
    #include <util/delay.h>

    #define set(port,pin) (port |= pin) //set port pin
    #define clear(port,pin) (port &= (-pin)) //clear port pin

    #define LED_DIR VPORTB.DIR
    #define LED_OUT VPORTB.OUT
    #define LED_PIN PIN3_bm

    int main(void){
    //
    // initialize LED pin
    //
    CPU_CCP = CCP_IOREG_gc; // unprotect clock
    CLKCTRL.MCLKCTRLB = 0; // turn off prescalar (20MHz)
    set(LED_DIR,LED_PIN);
    //
    // main loop
    //
    while (1) {
    set(LED_OUT,LED_PIN);
    _delay_ms(100);
    clear(LED_OUT,LED_PIN);
    _delay_ms(100);
    }
    }

Program 4: Double-click blink program

Here I had another idea: Create a counter for the program which can differentiate between a double click of the switch button vs pushing it once, and do something different in each case, for example have a different delay time between the blinks. Vachik also got excited about the idea and we spent a lot of time trying various programs to make it work, and read quite a bit about the clock_t variable representing the processor time used by a process, as well as inteerupts. Below is one example of one such program we tried to accomplish that; however, none of them worked.

    #include <avr/io.h>
    #include <util/delay.h>
    #include <time.h>
    #include <stdio.h>

    int counter =0;
    double timed =200;

    clock_t start, end;

    int main(void) {     DDRB = 0x08;
      while (1){
       if(!(PINB&0x10))
        { counter++;
         start = clock();
         end = clock();
         while((end - start)/ CLOCKS_PER_SEC<timed)
         { if(!(PINB&0x10))
         counter++;
         end = clock();
        }
        if(counter==2){
         PORTB = 0x00; _delay_ms(100);
         PORTB = 0x08; _delay_ms(100);
        }
        else{
         PORTB = 0x00; _delay_ms(200);
         PORTB = 0x08; _delay_ms(400);
        }
      }
      else{
        PORTB = 0x00; _delay_ms(100);
       }
       counter=0;
      }
    }

The 'hello world' program

To test whether the FTDI serial connection works, it would be nice to have a 'hello world' program to send one character at a time to the microcontroller via FTDI and have the microcontroller echo back the character through the same connection.

For this I used Neil's 'hello world' program for ATtiny45. In my board Rx is connected to PB1 and and Tx is connected to PB2, just like Neil's, so the code could be applied to program my board unchanged.

Interfacing the board and computer and troubleshooting

Once the program has been uploaded to the board, there needs to be an interface to interact with the board, to be able to send and receive messages through serial comminication. This is accomplished by the Python program term.py written by Neil Gershenfeld. It provides a little window interface allowing for serial communication with any board connected with an FTDI connection to the USB port of the computer. From Linux terminal, the term.py program is summoned through the following command:

python term.py /dev/ttyUSB0 9600

Here ttyUSB0 is the computer port where the USB is connected. If in doubt, you can always check which it is using the List USB command in your Linux terminal:

lsusb

9600 is the baud rate at which data will be communicated serially through the port.

At this point, if you don't have pyserial (Python Serial Port Extension) installed, you'll get the error:
ImportError: No module named serial

In order to install pyserial, you can use pip, which is the package installer for Python. In my case, it seemed pip wasn't install on the computer, so I installed pip using the following command:

sudo apt install python-pip

Here I got the error: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem. Which is what I did:

Then I proceeded to install pip, and then use it to install pyserial as explained above:

Then I tried one more time to run the term.py interface program, but this time got an error related to tk:

ImportError: No module named _tkinter, please install the python-tk package

So I proceeded to install the tkinter package for Python:

Now the term.py program could actually open and run:

As can be seen, every character typed on the keyboard travels to the board and echos back, showing up on the serial monitor.

However, if one continues typing, the characters typed after the 24th one start replacing the string from the beginning:

It should be mentioned that after this point, every time I tried to run the serial monitor again, I would have the same jumbled text without being able to reset it as if it was permanently stored somewhere in the memory.

Programming the board in Windows

Arduino IDE

As mentioned before, when programming in the Windows environment, the Arduino IDE and the Microchip Studio are two of the programs that can be used. I tried the Arduino IDE this week.

In order to program the board using Arduino IDE, I followed the instructions here and used an Arduino Uno as a programmer for the chick board.

Here's how the setup turned out:

Arduino IDE uses C as well, but also include Arduino libraries. The resulting files have an .ino extension and are called sketched, but don't be fooled: they're just C.

The program that I used at this stage was one of the example programs (sketches) of Arduino IDE under File -> Examples -> 01. Basics -> Blink. I just had to redefine the microprocessor port bit that turns on the LED by using the code int LED_BUILTIN=3;. You can see the code here:

Once the program was uploaded and embedded onto the chick, it worked like a charm:

I tried this later with my minimalist blink code from before; however, eventhough the chick would function right, the light of the LED was a lot dimmer, and the clock was definitely off, showing something the Arduino libraries have implicitly taken care of. Though I still don't know what made the light dimmer.

Other attempts

MicroPython could not be used to program the board as it requires 256 KB of code space and 16 KB of RAM. ATtiny45 contains 4 KB flash memory for program storage and 256 B of SRAM.

For dabbling in the future

While working on this assignment, I came across this wristwatch using an ATtiny85 which is very similar to ATtiny45. I'd like to make this in the future, just need to order a couple components before doing so.