Skip to content

9. Embedded Programming

Assignment: Read the datasheet for your microcontroller, Use your programmer to program your board to do something

In previous weeks I’ve used my programmer and my new button blinky board to make an LED light up using a button or a delay function. During that time, I used the Arduino IDE and normal C ++ code. For this week, my instructor Mr. Dubick told us to directly manipulate the registers on microcontroller.

Preparing for Embedded Programming

I thought I had a solid understanding of how and Arduino works, I realized after this week I did not. Mr. Dubick shared the videos below to help familiarize this kind of programming with us. These videos were very helpful for getting a solid foundation of an Arduino and the registers we can control in the microcontroller. When first trying to program my ATTIny412 in the Arduino IDE I realized that the port commands are completely different. For example, DDRB for an Arduino UNO would be the same thing as PORTA.DIR for the ATTiny412, which is something I learned from reading the datasheet.

Here are the videos that were helpful

Reading Datasheet of ATTiny412

After watching the videos mentioned earlier, I started reading the ATTiny 412 datasheet. I skimmed it and to be honest, it felt like I was reading a different language. I was familiar with some of the concepts such as different register storage and the pin configuration (which is what I would use to program my board). A lot of my understanding of the datasheet came from the videos but also just slowly reading carefully at some moments. It is easy to skim through a lot of the information, especially the info that is important to the project. I would end up reading the same paragraphs on pull up resistors and outputs 6-7 times before I finally understood it.

The information I found most important was the pinout image, the detailed pin image, and finally the information of configuring a pin to be an output, input, etc. Most of the information I used was located in section 16.

image

image image image

Platform IO

After watching Dr. Gerstenfeld’s lecture I wanted to try Platform IO. After watching the video below and looking at Drew Griggs’s and Teddy Warner’s documentation (two FAB students from the previous year) I realized that I could embed this IDE into VSCode. The instillation process was simple after watching this video and I was ready to use my programmer. I first used an Arduino UNO to test the built in LED. Configuring a board on Platform IO in my opinion was much easier than Arduino IDE for a couple of reasons.

  • Platform IO Auto detects my COMS ports
  • Platform IO allows you to just type the board into the software and will download the necessary material to program it
  • Having Platform IO integrated into VSCode (one of the best text editors) after watching a simple tutorial video, makes coding much easier.

I was able to program the LED on the Arduino UNO with ease and the next step was to use my programmer to code the ATTiny412 chip on my binky board. I typed the microcontroller into the software Platform IO did the rest. I used a simple blinky code to test it.

image

However, I ran into a major issue. When I tried to program the ATTiny412 with my SAMD11C programmer (which I made during PCB Production week), it would reset my programmer. This meant I had to reconfigure my programmer to be a programmer. I received this error right before my board was reset

image

I thought it was something simple like the COMS port was disconnected or a wire was disconnected. I used my Atmel ICE made from a previous week, to reprogram the SAMD11C. I double checked everything and ran the code but still received the same error and my board reset again. I scoured the internet for a reason as to why this was happening to no eval. I tried about 3 more times to program the ATTiny412, but it never worked. Keep in mind that reprogramming the SAMD11C to act as a programmer is not a simple process; it takes about 5 minutes to reconfigure it, just to get back to where I was previously. I was confused because the Arduino IDE was able to use my programmer easily, but not Platform IO. I gave up on trying to use my programmer and used my Arduino as a UPDI programmer. I used my earlier documentation to upload the jtagupdi bootloader. After this process was complete, I plugged in the ATTiny412 to the Arduino UNO and the code worked using Platform IO. Even after all of this, I still enjoyed using Platform IO and I will probably continue to use this.

Embedded Programming

Learning Binary and Decimal Coding

My instructior Tom Dubick presented this slide show on bare metal programming. These two images were most helpful

image

image

The presentation is in terms of an arduino but the concept is the same for an ATTiny412. Each pin is connected to a register which can be directly controlled using binary or decimal coding. In the top example PORTB on the arduino has 8 bits (or 1 byte). The pins can be toggled to either 1 or 0. In this case, 1 means a high signal while a 0 means a low signal. the port number corresponds to the decimal value. So the internal LED pin on an arduino (pin 13) is located at port register B bit 5 or PB5 or PORTB bit 5. to toggle this on, I would have to write a line of code to set this bit to a value of 1. I have two options that equate to the same thing. The first option is to directly type binary by saying

PORTB = B0010000

This means that for PORTB, bit 5 is toggled on. The decimal equivilant of bit 5 is 32, so I could replace this line with a simpler one being.

PORTB = 32

To control more bits I could either add a zero using binary or add the decimal equivilant of all the pins to turn them on.

Configuring Pins

To make this process easier for me I would use my code from my previous documentation and individually change each function. For example I would look at the digitalWrite(ledpin, HIGH) and only change this into PORTA.OUT = 128. 128 is the decimal number of bit 7, so writing this equates to a 1 for that bit which turns on bit 7 of PORTA. it would be the same as typing PORTA.OUT = B10000000". Before using the button, I simply wanted to make the LED blinky using embedded programming.

Blinking Code

As I mentioned earlier, I thought that setting a pin as an output would be the same thing as and Arduino UNO, it was not. I recevied this error while uploading.

image

After multiple hours of rereading the datasheet, I realized that the command was PORT.DIR with a “1” equaling an output and conversely a “0” would equal and Input. I would use PORT.DIRSET to configure an output and PORT.DIRCLR to configure an input. The datasheet said that these commands were useful if we wanted to directly configure a pin without affecting the others. I thought that this would be the best option and continued with it. I would soon realize that it would be hard to read inputs later through the process. Regardless, until my final piece of code I would use these commands. The blinky code I used for my board is below.

BLINKY code

void setup()
    {
    PORTA.DIRSET = 128;
    }
void loop()
    {
  PORTA.OUTSET = 128;
  delay(500);
  PORTA.OUTCLR = 128;
  delay(500);      
    }

Activating Pullup Resistor

The next step was to add a button and for that I used the code from week 7 PCB Design and directly configure this. Configuring the digitalWrite and the output were simple. A problem that I ran into was trying to activate the pullup resistor because when it wasn’t turned on the LED would be inconsistent when trying to turn it on.

I tried multiple combinations of different commands (I think some of them weren’t even meant for an ATTiny412) and nothing worked. The datasheet told me that the command PORTA.PIN3CTRL was used to activate pullup, but what I eventually figured out with assistance from Drew Griggs that the pull up resistor was a bitmask. I thought that just simply typing out PORTA.PIN3CTRL would activate it, but I had to use a bitmask or command to make the input pin register as a “1” for 5V. I did this by typing PORTA.PIN3CTRL = PORTA.PIN3CTRL | PORT_PULLUPEN_bm;.

After doing this I didn’t have any problems with the pull up resistor

Here is my code for this part of the process

int ledPin = 1;

void setup()
{
    PORTA.DIRSET = 128; //Sets pin 1 as an output while not affecting the other pins
    PORTA.DIRCLR = 8; //Sets pin 4 as an input while not affecting the other pins
    PORTA.PIN3CTRL = PORTA.PIN3CTRL | PORT_PULLUPEN_bm; //Activates the PUllUP resistor for PA3
}

void loop()
{
    if (digitalRead(4) == HIGH) // Button Isn't Pushed
    {
       PORTA.OUTSET = 128; // LED will stay off
    }
    else
    {
        PORTA.OUTCLR = 128; // LED turns on
    }
}

Changing All of My Code

The final step was to add the digitalRead. Up till this point I was still using that function to test the functionality of my board. Again, I spent hours trying to figure out how to find a way to incorporate the pin read. I sometimes the LED would stay on or stay off regardless of the button input. I thought I was close to figuring it out, but I never did. The following day, I consulted with some fellow fab students, Nhidie Dhiman and Alaric Pan, I realized that because I was using PORTA.DIRSET and PORTA.DIRCTRL, it would be harder to incorporate the input signal. Ms. Dhiman referenced youtuber Mitch Davis to help me with this problem. Ironically, this was the same person that created one of the first videos I watched at the beginning of the week. The YouTube video mentioned earlier was the first video of an eight-part series in microcontrollers. These were helpful, and I highly recommend watching them. The most helpful video is below.

Here is his entire playlist on using Bare-metal programming. I recommend watching the all 9 videos because he does such a great job of explaining and breaking down each aspect of his code.

After watching the video, I went back to VSCode with a new understanding of what I was doing and exactly what I had to do next. I got back to the point where I was previously, where I had everything except for the digitalRead. At this point I had replaced the “SET” commands with basic commands. Which is shown here

//Button is PA3
//LED is PA7

int ledPin = 1;

void setup()
{
    PORTA_DIR = B10000000;  // Defines OUTPUT as PA7 and INPUT as PA3
    PORTA.PIN3CTRL |= PORT_PULLUPEN_bm; // Makes the button pin read as HIGH if button isn't pushed
    PORTA.OUT = B10000000;
}

void loop()
{
    If (digitalRead(4) == HIGH) // Button Is Pushed
    {
       PORTA.OUT &= ~(1 << 7); // LED will stay on using B01111111
    }
    else
    {
        PORTA.OUT |= (1 << 7); // LED turns off
    }
}
  • PORT.DIR: Sets the output and input pins (0 being input, 1 being output)
  • PORT_OUT: Sets the state of the output pin (5v or 0v)
  • PORTA.PIN3CTRL |= PORT_PULLUPEN_bm : Activates the pullup resistor in PINnCTRL

Before I set each port individually using PORTA.OUTSET and PORTA.OUTCLR. This time around I used bit masking to change the individual bit value for PORTA.OUT. After watching the videos, I had more of a conceptual understanding of all of this, along with spending hours researching and reading the datasheet. This change would be imperative in order to configure the input pin.

Reading the Button

My final task was to incorporate the button. I read in the data sheet that the command I would need would be a PORTA.IN which configures the pin to read incoming data. I knew I needed to add more and figure out that I had to use bit masking so the value that would be able to change between a 0 and 1 when the input value was changing. Initially I thought I had to use an or bitwise operation but running this cause my led to only stay. This was because I was downshifting a 1 so using an or operation would cause the value to always stay at 1. I changed the operation to an and bitwise operation and my board WORKED (but not the way I wanted it to). The LED would turn off when the button was pushed. I knew that I could simply switch the statements, but I wanted to challenge myself and only change the if statement. Thanks to Alaric Pan I could simply use a “not” or “!” to inverse my if statement. I added this and my board worked. My final code is below

//Button is PA3
//LED is PA7

int ledPin = 1;

void setup()
{
    PORTA_DIR = B10000000;  // Defines OUTPUT as PA7 and INPUT as PA3
    PORTA.PIN3CTRL |= PORT_PULLUPEN_bm; // Makes the button pin read as HIGH if button isn't pushed
    PORTA.OUT = B10000000;
    PORTA.IN = B00001000;
}

void loop()
{
    if (!(PORTA.IN &= (1 <<3))) // Button Is Pushed
    {
       PORTA.OUT &= ~(1 << 7); // LED will stay on B01111111
    }
    else
    {
        PORTA.OUT |= (1 << 7); // LED turns off
    }
}

Using Memory Registers

I was also challenged to use pointers to memory addresses to code the buttons. The videos mentioned above help explained pointers and how to talk to registers using this. After watching them I went back VSCode and began typing. I went back to the datasheet and found the memory location for the output register.

image

I typed out this #define output *((volatile byte *)(0x04)) as a variable to PORT.OUT. I ran this and my code didn’t worked. I noted that the memory location was labeled with offset and I did some research and came accross this image from this Youtube video

image

I realized that I had to encoporate the normal location and the offset location for the pointer. I looked for the PORTA configuration register and came accross this image

image

To fix the issue I typed #define output *((volatile byte *)(0x0400 + 0x04)), this would define my variable output to the exact register for output configuration. The 0x0400 is the PORTA register while the 0x04 is the output offset. I ran the code only changing out the output variables for this and my code worked. Using the same concept I changed all of corresponding registers to pointers. My final code for this is below

int ledPin = 1;

#define output *((volatile byte *)(0x0400 + 0x04))
#define input *((volatile byte*)(0x0400 + 0x08))
#define DataDirection *((volatile byte*) (0x0400 + 0x00))
#define Pincontrol *((volatile byte*) (0x0400 + 0x10 + 3*0x01))


void setup()
{
  DataDirection = B10000000;              // Defines OUTPUT as PA7 and INPUT as PA3
  Pincontrol |= PORT_PULLUPEN_bm; // Makes the button pin read as HIGH if button isn't pushed
  output = B10000000;
  input = B00001000;
}

void loop()
{
  if (!(input &= (1 << 3))) // Button Is Pushed
  {
    output &= ~(1 << 7); // LED will stay on B01111111
  }
  else
  {
    output |= (1 << 7); // LED turns off
  }
}

Problems I Ran Into

  • Directly setting the bits instead of using normal bit masking
  • Platform IO resetting my programmer
  • My programmer no longer works
  • Some of my files became corrupt so I had to redownload them
  • Not fully understanding what each command did until the end

What I Learned

WOW, this week was a stressful one. Who knew simply making an LED would take hours upon hours of research and reading? The knowledge I gained this week, with bare-metal programming, integrating a new IDE, and learning how to read a datasheet is important. However, I feel the most important lesson from this week is that I realized what makes an Arduino work. Terms such as registers, reset, DDR, and ports were completely unknown to me until this week. I learned what makes an Arduino function when I type in the terms digitalWrite and digitalRead. I learned how memory works, and even with my little experience with C++, I feel like I understand conceptually how some of it works. I learned that there is an easier way to code an Arduino with Platform IO in VSCode. The best way to describe this experience is comparing it to what I did during week 5 Electronics Production. That week I created a board with a microcontroller and figured out how to work it. After this week, I feel like I understand why it works.

Group Assignment

This weeks group assignment was to compare the performance and development workflows for other architechtures. Myself and Alaric Pan decided to work on the Jetson Nano. The group page is located here

Files

My final code file is here


Last update: February 27, 2023