A beginner’s guide to microcontrollers
Time after time I see beginners try to get started in embedded electronics, only to be overwhelmed and not know where to start. Some even make the mistake of trying to write their own code without first gaining a thorough understanding of the microcontroller/microprocessor they’re working with, the programming language they’re working with, or even basic programming concepts. Not to worry though…this article should pose as a good primer to get your feet wet in the world of embedded electronics.
This article does not attempt to teach about any specific microcontroller/microprocessor, but is more of a primer to explain the general concepts which apply to all microcontrollers/microprocessors.
First off…let’s ask ourselves a couple of questions. The first question –
What is a microcontroller?
A microcontroller is a tiny microcomputer on a chip. It has a CPU, RAM (Random Access Memory), special function registers, program ROM memory, data ROM memory, anywhere from one to several parallel I/O (Input/Output) ports, and can have a host of on chip peripherals including but not limited to Analog-to-Digital Converter (ADC), Digital-to-Analog Converter (DAC), Serial UART, one or several timers, comparators/on chip voltage reference, capture/compare/PWM (Pulse Width Modulation) module, Master Synchronous Serial Port for SPI (Serial Peripheral Interface)/I2C (Inter Integrated Circuit) communications, USB port, ethernet port, on chip oscillators, along with a host of other peripherals. For example: http://www.kynix.com/uploadfiles/pdf8798/PIC10F200T-E2fOT.pdf
What is a microprocessor (wait, you mean there’s actually a difference)?
A microprocessor is everything a microcontroller is but without the program ROM on chip. The program code resides off chip in a separate external EPROM chip. For example: http://www.kynix.com/uploadfiles/pdf8798/N80960SB-10.pdf
Program ROM and Data ROM
The on chip ROM memory (Read Only Memory) on a microcontroller is like the microcontroller’s hard drive. It has two partitions. One partition is reserved for the storage of the program code while the other partition is reserved for permanent storage of data that is used by the chip during normal program execution. On a given PIC microcontroller with say 8K of program space, the program space would occupy ROM addresses 0x0000–0x1FFF (or 0–8191 in decimal). The data space would start at program ROM address 0x2100. If the data ROM space were 256 bytes long, the data ROM space would occupy ROM addresses 0x2100–0x21FF (or 8448–8704 in decimal).
CPU stands for Central Processing Unit. It is basically the “brains” of the microcontroller. It is what fetches the instructions from the code memory and executes the instructions that it fetches.
The data RAM (Random Access Memory) is the data space that is used for temporarily storing constant and variable values that are used by the microcontroller during normal program execution. The amount of physical RAM space on a given microcontroller varies from one microcontroller to the next. The data RAM on a microcontroller is organized into several “registers”, each with its own unique “address”. A RAM register on an 8 bit microcontroller can hold a total of 8 bits, or one byte of data. A typical RAM space specification may specify that it is 256 x 8. This means that there are a total of 256 registers in the RAM, and those registers can hold 8 bits each.
A register is just a location in memory that you can write data to or read data from. Some of us refer to registers as “locations”.
Special Function Registers
The special function registers (or simply SFR’s) on a microcontroller are just like the registers in data RAM. You can write data to them as well as read data from them. Where they differ is that some SFR’s directly control the on chip hardware on the microcontroller while others are controlled by the on chip hardware on the microcontroller.
Each bit in an SFR is assigned to a function. In the SFR’s you have control bits and flag bits. Control bits are like “switches” that turn a function on or off depending on if you write a 1 or a 0 to that bit location in the SFR. Flag bits are like “indicator lights” that indicate whether or not a given condition exists depending on whether the flag bit is a 1 or a 0. Control bits directly control the hardware. Flag bits are controlled by the hardware. In any given program, we typically write to control bits while we read flag bits (some flag bits must be manually cleared by writing to them depending on the microcontroller…more on this later).
Each piece of hardware on the microcontroller will have at least 1 SFR assigned to it. Some hardware may have several SFR’s assigned to it. Consult your microcontroller’s data sheet to learn more about its specific SFR organization.
Most microcontrollers have special bits known as the “configuration bits”. These bits configure special options on the microcontroller including but not limited to –
* Oscillator Type
* Watchdog Timer On/Off
* Power Up Timer On/Off
* Brown Out Reset On/Off
* Low Voltage Programming On/Off
* Fail Safe Clock Monitor On/Off
* Internal/External Switchover On/Off
On a PIC microcontroller, there are even configuration bits for program code protection and data code protection. These bits prevent the program or data spaces from being read by external programming hardware to keep others from stealing your code. On an Atmel AT89S chip (8051 derivative), this is set by what are known as the “lock bits”.
Some refer to the configuration bits as the “fuse bits”. This comes from the old days of microprocessors that had actual “fuses” on chip that would get blown if certain fuse bit controlled functions were turned off. These fuses were “one time programmable”…once they were blown, there was no “unblowing” them. However, with the advent of flash memory available on modern microcontrollers, there are no more literal “fuses” on the chip. But the term itself carried over due to the configuration bits essentially providing the same control as the fuse bits did.
The ALU (Arithmetic and Logic Unit)
This piece of hardware is essentially responsible for all of the math and logic operations that are performed by the microcontroller. On most microcontrollers, the ALU will have 3 flag bits associated with it –
* The Zero Bit — This flag bit gets set to a 1 by the hardware whenever a math operation results in a zero result. It will be cleared to a 0 by the hardware whenever a math operation results in a non-zero result.
* The Carry/Borrow Bit — This flag bit works as a carry bit for addition operations while working as a borrow flag for subtraction operations. A “carry” occurs when the result of an addition operation results in a value larger than what the register is capable of holding. An 8 bit register can hold a maximum value of 255 (FF in hex or 11111111 in binary).
If an addition operation results in a result of greater than 255, the carry flag gets set to 1. If an addition operation results in a result of less than 255, no carry takes place so the carry flag gets cleared to 0.
For subtraction operations, the carry flag works as a borrow flag instead. The borrow flag works in reverse of the carry flag. If a subtraction operation results in a negative result, the borrow flag gets cleared to 0. If a subtraction operation results in a positive result, the borrow flag gets set to 1.
* The Digit Carry/Borrow Bit — This flag bit does the same thing as the carry/borrow flag, but it only works to indicate if a carry/borrow takes place between bits 3 and 4 only.
The ALU flag bits can be read at anytime to know whether the results of math operations were zero, positive/negative, or greater than/less than, etc etc.
The zero bit is a handy flag bit that allows us to compare two values to see if they are equal/not equal. If we take two numbers and subtract them, the result will be zero if equal while being non-zero if not equal. So to compare two values to see if they are equal/not equal, we subtract them, then read/check the zero bit to see if the bit is a 1 or a 0. If Zero Bit = 1, the result of the subtraction is zero, which means that the two values are equal. If Zero Bit = 0, the result of the subtraction is non-zero, which means that the two values are not equal.
The carry/borrow bit is a handy flag that allows us to compare two values to see if one value is greater than/less than the other value. Example…we have two values: VALUE1 and VALUE2. In code we perform this operation –
VALUE1 — VALUE2 = VALUE3
Once the subtract operation has been executed, we then read/check the high/low state of the carry/borrow bit.
If VALUE2 is greater than VALUE1, the result of the subtraction will be negative, which will clear the carry/borrow bit to 0. If VALUE2 is less than VALUE1, the result of the subtraction will be positive, which will set the carry/borrow bit to 1.
Consult the data sheet to learn which SFR contains these bits. On PIC microcontrollers, the ALU flag bits reside in the STATUS SFR. On MCS-51, they reside in the PSW SFR (Program Status Word).
The Program Counter
The Program Counter is an “address pointer” that tells the CPU where to find the next instruction to execute in program ROM. The CPU will fetch the instruction which resides at the program ROM address that is currently loaded in the program counter.
When the microcontroller resets, the program counter is initialized to 0x0000. The CPU will fetch the instruction which resides at program ROM address 0x0000. Once this instruction has been fetched, the program counter auto increments to the value of 0x0001. The program counter continuously auto-increments by the value of 1, which causes the CPU to access the contents of each register location in program ROM sequentially. It keeps doing so until the CPU has fetched and executed an instruction which modifies the value of the program counter. Such instructions that do this are jump instructions (ajmp and ljmp on MCS-51, goto on PIC), subroutine calls (acall and lcall on MCS-51, call on PIC), and any instructions which add or subtract a value to or from the program counter.
The stack on a microcontroller is primarily used during subroutine calls and jumps to an interrupt handler. It is a “Last In First Out” buffer that is used to store return addresses. During a subroutine call, the current program counter address is “pushed” onto the stack with a +1 offset added to it, then the program counter is modified with the address value where the subroutine being called resides. This makes the program counter jump to the subroutine code to execute the subroutine.
At the end of the subroutine, there will be a “return” instruction (ret on MCS-51, return on PIC). Upon the execution of the return instruction, the stack is “popped”, and the last ROM address value that was pushed onto the stack is popped off of the stack and back into the program counter. This makes the program counter jump back to the instruction that resides after the instruction that called the subroutine (hence the need for the +1 offset at the time that the PC address is pushed onto the stack), and program execution continues where it left off prior to the subroutine call.
Some microcontrollers have a “software stack” (MCS-51). A software stack uses some of the microcontroller’s internal RAM space as the stack space. Other microcontrollers have a hardware stack (PIC). With a hardware stack, the stack is its own dedicated space that is separate from all other on chip memory spaces.
On some microcontrollers, the stack is writable. This allows us to use the stack for temporarily backing up critical registers during subroutine calls and interrupt handler execution. Prior to executing the subroutine or interrupt handler, the contents of the registers to back up are pushed onto the stack. Then, just prior to returning from the subroutine or interrupt handler, the contents we pushed onto the stack at the beginning of the subroutine are popped off of the stack one at a time, then restored to their original locations in reverse order from how they were pushed onto the stack (remember…Last In First Out).
A good example of this would be how we back up the accumulator and the PSW registers on MCS-51 during the execution of an interrupt handler routine –
push ACC ;back up accumulator onto stack
push PSW ;back up program status word onto stack
;execute interrupt handler code here
pop PSW ;restore program status word
pop ACC ;restore accumulator
reti ;return to main code from interrupt
As you can see, we first push the accumulator contents onto the stack, then we push the contents of the PSW onto the stack after it. The interrupt handler code is then executed.
After execution of the interrupt handler code, the PSW is popped off the stack first, then the accumulator is popped off of the stack after it…in reverse order from how they were pushed.
A typical SFR
A typical SFR is set up as shown below.
| PORT 1 SFR |
— — — — — — — — — — — — — — — — — — —
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| P1.7 | P1.6 | P1.5 | P1.4 | P1.3 | P1.2 | P1.1 | P1.0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
This is the port latch SFR on an MCS-51 microcontroller for parallel port 1. Each port on the MCS-51 is an 8 bit parallel port and each of the bits in the port SFR are assigned to each pin on the port. P1.0 would be pin 0 on port 1, P1.1 would be pin 1 on port 1, P1.2 would be pin 2 on port 1, etc etc.
As shown we have all zeros written to each of the bits in the port 1 latch SFR. This will place all of the pins on port 1 in the low state (0 volts). If we were to write 1’s to any of the port SFR bits, this will set the pin associated with the bit position that we write the “1” to in the high state (+5V).
Example, let’s write the value 01010101 to the port 1 SFR –
| PORT 1 SFR |
— — — — — — — — — — — — — — — — — — —
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| P1.7 | P1.6 | P1.5 | P1.4 | P1.3 | P1.2 | P1.1 | P1.0 |
| 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
As shown, this will place pins P1.0, P1.2, P1.4 and P1.6 in the high state while placing pins P1.1, P1.3, P1.5 and P1.7 in the low state.
A word on datasheets…and why they’re so important
Not all microcontrollers are created equal. Each and every one is designed with specific hardware on the chip. Microcontrollers from different manufacturers all have different architectures. You will find that PIC microcontrollers differ greatly from MCS-51 microcontrollers just as MCS-51 differs greatly from say the Motorola 65xx in regards to how the SFR’s are implemented, how the data RAM is organized, the instruction set, configuration word, how the parallel ports work, etc etc.
The ONLY way to know exactly how to work with your microcontroller and its hardware is to consult its datasheet. The datasheet explains every SFR, every piece of on chip hardware, the absolute maximum electrical ratings, the program/data memory organization, how the parallel ports are wired and how they work, the instruction set summary (for those of you who code in assembly language), etc etc. Pretty much everything that you as the programmer will need to know about your microcontroller is in the microcontroller’s datasheet.
Most of them are freely available online with a simple Google search (I have yet to find one that isn’t). Stating that you couldn’t find the datasheet is not an acceptable excuse when it comes to this. The ONLY reason why anyone would refuse to look up the datasheet is either because they are too lazy OR they don’t understand them but don’t want others to know that they don’t. I will say right now though…most forum questions regarding microcontrollers could’ve been self answered had the person taken the time to look up the answer in the datasheet.
Datasheets are absolutely mandatory. You will not be able to write your own code without them.