Table of Contents
In this blog, we will try to demystify the process of flashing ATmega328P, an 8-bit AVR microcontroller, using serial programming algorithm.
Why should you know about serial programming?
Our interest in any field is sparked when some question pops up in our minds regarding that field. A kid sitting on the top of the roof staring at the sky might get interested in the classification of various stars when he or she asks the question “Why do some stars have a blue color, while others have orangish color?”. The kid will study about it from various sources and might become the next Jocelyn Bell.
Most of the embedded system designers and programmers got interested in this field when they asked questions like these:
“How can I make my own digital clock?”
“How can I program a microcontroller and build something?”
“How can I interface between various devices?”
and so on.
At the heart of answers to each of these and other similar questions lies the statement “You can do so by programming.”. Many people are proficient in writing code, however, how the system actually gets programmed, or how it stores various bytes which carry the essence of our code into memory is still not known to some. It is important that hobbyists and other people working in the field of embedded systems at least have an idea of what the dude named “avrdude” actually does or the bootloader does when they program their Arduino board. This knowledge will help them in knowing how their Arduino boards are being programmed, or what could the error possibly be when avrdude throws an error “SCK period not defined”.
What happens when you click on the “Verify” button in Arduino IDE?
When you click on “Verify” button, the one with the “tick mark” symbol, in Arduino IDE a series of steps take place which checks your program for any errors, links it with required library files, and finally dishes out a hex file with .hex extension. This series of steps are collectively called “Compilation”. Compilation is done by compilers and the end result of a successful compilation is a file containing hexadecimal equivalents of 1’s and 0’s, called “Machine Code” or “Executable code”. This is the file which the processor of our computers can understand and execute.
So, when you write a simple code to blink an LED on Arduino Uno, do not think that the microcontroller on Uno, ATmega328p, is storing your code with all its statements like the sentences in a book. It is storing the machine code equivalent of your code into its memory.
You can relate it to you scribbling notes on pages while your instructor is teaching you something. Your notes are understandable to you only, but that doesn’t mean that their interpretation is different than what your instructor has taught you (Well, sometimes it might be, but that is what compilers do too. They add or remove a few segments so as to optimize a code for a specific processor.)
NOTE 1: For the sake of consistency, from this point onwards we will restrict our discussion to ATmega328P microcontroller only. It is the microcontroller plugged into the DIP socket of Arduino Uno or soldered to your Arduino Nano board. Even with this constraint on our choice of microcontroller, the discussion we will be having here will apply to most of the microcontrollers in AVR 8-bit microcontroller family and the reader will find that most of the microcontrollers supporting serial programming have a similar programming algorithm.
NOTE 2: As we will be referring to the datasheet of ATmega328P again and again, here is the link to download its latest datasheet:
NOTE 3: Parenthesis containing a number prefixed by “Pg” refers to the pages in the datasheet of ATmega328P that should be read in order to understand a specific topic in detail.
Memory Structure of ATmega328P
ATmega328P has three different types of memories built into it. These are:
- Flash/Program Memory: It is the memory where the machine code equivalent of our source code resides. It is to be used only for programming purposes and not for storing variables (Pg 26).
- SRAM: Random Access Memory is the memory where we store our variables. Be it arrays, or characters to be sent serially to our laptop, they are all stored here (Pg 28).
- EEPROM: Electrically Erasable Programmable Read-Only Memory or EEPROM for short is the memory space where we store constant variables (Pg 29).
If we remove the power supply from our microcontroller, the contents of flash and EEPROM are preserved but those of SRAM are erased.
Let us first look at the structure of SRAM (Pg 28). It is also called Data Memory.
As can be seen from its memory map, SRAM is divided into 4 locations:
- 32 Registers: Which are used by the processor in the microcontroller for storing values of operands.
- 64 I/O Registers: These are the registers by means of which the microcontroller reads input and sends output. These registers also control various other properties of the microcontroller, like its timers and counters.
- 160 Ext. I/O Registers: These registers map to the 160 locations of the extended I/O memory.
- SRAM: The next 2048 locations are what make up our 2K of SRAM for storing variables.
Now we will talk about the structure of flash and EEPROM. It is here we can extend our analogy of notes scribbled out on a piece of paper. Strokes of the pen are what make up a single letter or a symbol. A meaningful combination of these symbols makes a word. A collection of words makes sentences, which are clubbed together to make a paragraph and it is the collection of paragraphs that makes up our notes which carry the information we have learned. In computer programming, 0’s and 1’s, known as bits, are our “pen-strokes”. A collection of eight such bits make up a byte (which also maps to ASCII). Two bytes clubbed together to form a sequence of 16 bits which we call a word. A word is constituted of high byte and low byte. In memory, each byte is stored at a memory location. These bytes could be looked at in groups of two i.e., as words. These words make up a page of memory.
ATmega328P’s flash memory has a total size of 32K bytes or 16K words. This memory is divided into 256 pages with each page having a capacity to hold 64 words (Pg 294).
Similarly, the EEPROM of this microcontroller has a total size of 1K bytes. It is divided into 256 pages each having a capacity to hold 4 bytes (Pg 294).
It is evident that flash memory is arranged and dealt with in terms of words, whereas EEPROM is arranged and dealt with in terms of bytes. The diagram below shows the organization of pages and words in the flash of ATmega328P.
The question now is, how can we access the memory locations in the flash and EEPROM? For this, we dive back into the datasheet. In table 28-11, under PCWORD it is written PC[5:0], and under PCPAGE is written PC[13:6]. What this means is that in a single 16-bit address of a word, bits [5:0] make up the address of the word in a single page, and bits [13:6] make up the address of the page in memory. We, therefore, have the following template for writing the address of a word in memory.
Here all bits marked w will make up the address of a word in a page and all the bits marked p will make up the address of a page.
For example, the complete address of a word stored at 34th location of page 78 is given by
0001 0011 1010 0010
As an exercise, the reader can look at table 28-12 and figure out the template for addressing bytes in the EEPROM of ATmega328P.
Setting up our microcontroller for serial programming
We will now come to the main topic of this blog, which is the serial programming of ATmega328P. Here, by the word ‘serial’ we mean that the data will be sent in synchronization with a clock signal. ATmega328P is the microcontroller to be programmed. It is our target device. In embedded systems terminology, it is called slave. The system which will program this microcontroller is called master.
In serial programming of ATmega328P (and other AVR microcontrollers in general), we will be needing 6 lines for making a connection between the master and the slave. These lines are (Pg 304):
- Master In Slave Out (MISO)(PB4): Data is sent along this line serially by the slave to the master.
- Master Out Slave In (MOSI)(PB3): Data is sent along this line serially by the master to the slave.
- SCK(PB5): This is the clock pulse line. When a clock pulse is sent along this line, a single bit is sent or read by either the master or the slave.
- RESET: This is an active-low unidirectional line that is used by the master to reset the slave. When RESET = 0, the slave becomes ready to receive and send instructions and data via the serial channels mentioned above.
- Vcc: This is our common power bus. For ATmega328P, it should be 5V.
- GND: This our ground or 0V bus. It is a good practice to put a capacitor in between the power bus and ground bus so as to filter out any ripples in supply.
We will connect the slave to these six lines as shown in figure 28-7 (Pg 303).
The question now is: who is the master? Who is at the other end of these lines? Well, in this case, we are the master and we will be sending pulses along MOSI and SCK lines via some ideal tactile switches which don’t have any switch bounce. Of course, such switches don’t exist in the real world. In the real world, we will be using a regular tactile switch. However, we can eliminate switch bounce by using Schmitt triggers. The tactile switch will be connected to Vcc using 1k pull-up resistors, from where a wire will connect our tactile switch’s output to a hex inverter Schmitt trigger. So, when we will depress the switch, the output of the Schmitt trigger will go high, else it will remain low. This will make our “ideal switch”.
We can attach a toggle switch to the RESET pin of the microcontroller. This will allow us to put the microcontroller into RESET state or normal state as and when required.
Another thing that we can do is to attach LEDs with our MOSI, MISO, and SCK lines. This will give a visual indication of the bit being sent across a particular line. We will connect a blue LED to our SCK line, and a red and green one to our MOSI and MISO lines respectively.
With all this said and done, we end up with a setup shown below.
Now it is time to program our microcontroller serially.
Serial Programming Algorithm
This is the algorithm we need to follow to communicate with our microcontroller and program it serially (Pg 304).
- Step 1: Power Up Sequence
Power is applied between Vcc and GND. The output of SCK and MOSI is low. RESET is given a single pulse.
- Step 2: Wait for 20 ms.
- Step 3: Program Enable Instruction: Send “program enable” instruction to the slave. Note that the MOSI line will be read at the rising edge of the SCK and MISO will be sent at the falling edge of SCK. So, the procedure of sending a bit to the slave should be:
Step a: If bit = 0, don’t press MOSI. If bit = 1, press MOSI.
Step b: Press SCK.
Step c: Release SCK.
Step d: If MOSI was pressed, release it.
- Step 4: If the communication between the master and slave is synchronised, 0x53 will be returned or say echoed by the slave when sending the third byte of the program enable instruction. Send all four bytes of the instruction. If the echo is incorrect, go back to Step 1 and start again.
- Step 5: Perform chip erase: If you wish to erase the contents of the flash and EEPROM (which you should in case your program didn’t work correctly the last time), send the chip erase instruction serially to the slave.
- Step 6: Programming the Flash: Flash is programmed one page at a time. Machine code for a single page is first loaded into the page buffer. When we will load our words into the page buffer, we will supply along with the load program memory page command the high/low byte of the machine code equivalent of that instruction and the address at which that instruction is to be stored. Each instruction is either 16-bit wide or 32-bit wide. Accordingly, we can decide on how many instructions can be fitted into a single page. Note that the low byte of the instruction is to be loaded first before loading the high byte of the instruction into the buffer. Once the buffer is loaded, you can write it using the “write program memory page” instruction given in Table 28.8.3, supplying it with the 7 Most Significant bits (MSB) of the page address. Then the machine code for the next page is loaded into the page buffer. EEPROM can be programmed in a similar fashion.
It is getting too complicated, right? Well, this datasheet has a beautiful diagram, Figure 28-8 which will visually explain how the flash and EEPROM of the slave get programmed (Pg 306).
This is shown below:
A Small Example of Serial Programming
The serial programming algorithm will become clear with the help of an example. In this example, we will program our ATmega328P microcontroller with a pretty simple code which will make pin 0 of port D go high. If there is an LED whose anode is connected to pin 0 and cathode to ground via a resistor, it will glow. We will write this code in assembly language, manually assemble it, and then serially program the microcontroller using it.
Now before we begin, we will introduce a weak syntax highlighting for bits which will make it easier to understand and follow the reasoning in this example.
|COLOR OF THE BIT||DESCRIPTION|
|0 or 1||8 such bits make up the low byte of a word|
|0 or 1||8 such bits make up the high byte of a word|
|0 or 1||6 such bits make up the address of a word in a page|
|0 or 1||8 such bits make up the address of a page in the flash|
Part 1: Writing the code
We have to make the pin 0 of port D go high. For this, we first need to set bit 0 of the DDRD register. This will configure pin 0 as an input pin. Next, we will set bit 0 of the PORTD register so as to set pin 0 of our microcontroller high. Here’s the assembly code to achieve this:
LDI R1, 0x01 ;store 0x01 in register 1 OUT 0x0A, R1 ;store the value of register 1 in DDRD register (I/O register no. 0x0A) OUT 0x0B, R1 ;store the value of register 1 in PORTD register (I/O register no. 0x0B)
Part 2: Manually assembling the code
Assembling is the procedure of converting assembly language code into machine language code. Each assembly instruction is translated into a sequence of bits (which can be written as a hexadecimal number) which is the only thing a machine understands. For assembling, we will look into the AVR instruction set manual (Download link: http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set- manual.pdf) for our instructions and convert them to their corresponding 16-bit opcode.
Let’s convert LDI R1, 0x01 to its 16-bit opcode. The template for the same is given below (Pg 115 of the Instruction-set manual):
The immediate number 0x01 = 0000 0001. So, KKKK KKKK = 0000 0001.
Then the required 16-bit opcode is:
LDI R1, 0x01 → 1110 0000 0001 0001
In a similar fashion we assemble OUT instructions using the template below (Pg 134 of the Instruction-set manual):
OUT 0x0A, R1 → 1011 1000 0001 1010
OUT 0x0B, R1 → 1011 1000 0001 1011
Our complete assembled code will become
1110 0000 0001 0001
1011 1000 0001 1010
1011 1000 0001 1011
Part 3: Enabling our chip for serial programming
Once the code has been assembled, all that we now have to do is to serially program the microcontroller using the serial programming algorithm we have discussed. We will apply power to Vcc and GND, keep SCK and MOSI low, and give a pulse to RESET pin via our toggle switch, and set it back to 0 (it will be helpful to mark which direction is high and which is low on the toggle switch). After waiting for at least 20ms, we will send the program enable instruction to the microcontroller which basically tells it “Hey! We are now going to program you serially”.
This instruction is given below (Pg 305):
These hexadecimal codes’ binary version is given below (search on Google on how to convert hexadecimal numbers to binary numbers and vice versa. It’s pretty easy):
1010 1100 0101 0011 0000 0000 0000 0000
As we have mentioned before, the state of MOSI pin is read on the rising edge of the clock pulse. If you wish the microcontroller to read the 0, simply press SCK tactile switch. If you wish the microcontroller to read 1, first press the MOSI switch and then press SCK. Release MOSI and then release SCK. If MISO is high, the LED on MISO line will glow when SCK will be released. It is getting confusing, right? Well, the following timing diagrams will clear things up. The first one shows how the first two bytes of program enable instruction are sent.
The next one shows how the last two bytes of the instruction are sent. This time 0x53 is echoed back on the falling edge of the clock signal.
If 0x53 is not echoed back it means that our master and slave are not synchronized with each other. In this case, apply a pulse to the RESET pin and send the instruction again. A few possible reasons for unsynchronised master and slave are:
- Power supply ripple
Solution: Connect a capacitor between Vcc and GND rails
- Incorrect pressing of SCK and MOSI switches.
Solution: Follow the algorithm as given. Press MOSI and then press SCK.
- Your skin is touching the wires of the circuit.
Solution: Make sure that you are not touching any wires.
Once 0x53 is echoed back on the third byte of the program enable instruction, send the fourth byte. Our slave is now ready to be programmed serially.
Part 4: Chip Erase
Erase the contents of flash and EEPROM by sending “chip erase” instruction. It is given below:
Its binary equivalent is 1010 1100 1000 0000 0000 0000 0000 0000. This is what we have to send across MOSI.
Part 5: Load Instructions into the Page Buffer
Now we have to load our assembled program into the page buffer. First, the low byte of an instruction is loaded and then its high byte is loaded.
Our assembled program is given below.
1110 0000 0001 0001
1011 1000 0001 1010
1011 1000 0001 1011
We will first load the first line, then the next line, and then the third line of the assembled program.
To upload the first line, the load instruction command is used. The template for this instruction is given below:
According to this, if we send the following bits serially,
0100 0000 0000 0000 0000 0000 0001 0001
0100 1000 0000 0000 0000 0000 1110 0000
the page buffer will get loaded with the first instruction.
To load the second instruction, we need to update the LSB of the address by 1 and send the following bits serially to MOSI,
0100 0000 0000 0000 0000 0001 0001 1010
0100 1000 0000 0000 0000 0001 1011 1000
To load the third instruction, we will again increment the LSB of the address by 1 and send the following bits serially,
0100 0000 0000 0000 0000 0010 0001 1011
0100 1000 0000 0000 0000 0010 1011 1000
This loads our program into the page buffer of ATmega328P.
Part 5: Write Program Memory Page
Finally, we need to write the contents of the page buffer to a specific page, in this case, page 0. For this, we have the following serial programming instruction:
As per this, sending the following bits along MOSI will write the contents of page buffer into the first page of the flash memory:
0100 1100 0000 0000 0000 0000 0000 0000
If we were to write page 1 of memory we would have used:
0100 1100 0000 0000 0100 0000 0000 0000
If we were to write page 63 of memory we would have used:
0100 1100 0000 1111 1100 0000 0000 0000
Part 6: Start normal operation
With our program finally written to the flash memory, we can now flip the toggle switch which will set the RESET pin high. This will make the microcontroller run our program. If there is an LED connected to pin 0 of port D as described before, it will glow.
This is what avrdude and various programmers like USBasp do when they program an 8-bit microcontroller from AVR family.
Most of the other microcontrollers in the market have a similar programming methodology along with a few other intricacies which vary from manufacturer to manufacturer. So, the next time the verbose output window shows the word “avrdude”, you know what is happening.
Praneet Kapoor is an electronics hobbyist currently pursuing B.Tech in Electrical and Electronics Engineering from Guru Gobind Singh Indraprastha University, Delhi, India. His interests are in the fields of analog and digital electronics, x86 assembly language, and embedded system development.