Goals for this module includes
I have already programmed my board using Arduino IDE. I spent some time setting up the Arduino environment, added the new 20MHz variant to the list of the boards (boards.txt), added
new bootloader and new makefile. I also setup the Udev rules and few other tweaks, you can read about those in my
Electronics Design page.
I didn't make the bootloader hex file or the makefile, I just sources it from various locations in Internet, so this is the time to learn about those too, means I will
be trying to compile my own Arduino bootloader and Makefile.
So I have downloaded the datasheet for the 8bit AVR microcontrollers,
ATmega48A/PA/88A/PA/168A/PA/328/P
. I will be going through this documentation as and when I require.
For those who are interested in the board I'm using; this is a 20MHz variant of the Fabduino I made during
Electronics Design.
So, Lets begin....
void setup() { // initialize digital pin 13 as an output. pinMode(13, OUTPUT); } void loop() { digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level) delay(1000); // wait for a second digitalWrite(13, LOW); // turn the LED off by making the voltage LOW delay(1000); // wait for a second }
#define F_CPU 20000000UL #include <avr/io.h> #include <util/delay.h> #define BLINK_DELAY_MS 1000 int main (void) { /* set (PB5) of PORTB for output*/ //DDRB = (1<<PB5); DDRB = 0b00100000; //does the same thing as above line while(1) { /* pin 5 high = turn led on */ PORTB = 0b00100000; _delay_ms(BLINK_DELAY_MS); /* pin 5 low = turn led off */ PORTB = 0b00000000; _delay_ms(BLINK_DELAY_MS); } }This code is written with the help of datasheet of the AtMega328p, specifically section 14.2 on Ports as General Digital I/O and my own experience.
rm out* avr-gcc -Os -DF_CPU=20000000UL -mmcu=atmega328p -c -o out.o blink0.1.c avr-gcc -mmcu=atmega328p out.o -o out avr-objcopy -O ihex -R .eeprom out out.hex avrdude -F -V -p ATMEGA328P -P usb -c usbtiny -b 115200 -U flash:w:out.hex
DEVICE = atmega328p F_CPU = 20000000 # in Hz FUSE_L = 0xFF # 0xE2 for internal 8Mhz 0xFF for external and 0X62 for internal 1MHz FUSE_H = 0xDF AVRDUDE = avrdude -c usbtiny -P usb -F -V -b 115200 -p $(DEVICE) # edit this line for your programmer CFLAGS = -Wall -Os OBJECTS = main.c COMPILE = avr-gcc -DF_CPU=$(F_CPU) $(CFLAGS) -mmcu=$(DEVICE) help: @echo "This Makefile has no default rule. Use one of the following:" @echo "make hex ....... to build main.hex" @echo "make program ... to flash fuses and firmware" @echo "make fuse ...... to flash the fuses" @echo "make flash ..... to flash the firmware (use this on metaboard)" @echo "make clean ..... to delete objects and hex file" hex: main.hex program: flash fuse # rule for programming fuse bits: fuse: @[ "$(FUSE_H)" != "" -a "$(FUSE_L)" != "" ] || \ { echo "*** Edit Makefile and choose values for FUSE_L and FUSE_H!"; exit 1; } $(AVRDUDE) -U hfuse:w:$(FUSE_H):m -U lfuse:w:$(FUSE_L):m # rule for uploading firmware: flash: main.hex $(AVRDUDE) -U flash:w:main.hex:i # rule for deleting dependent files (those which can be built by Make): clean: rm -f main.hex main.elf *.o main.elf: $(COMPILE) -o main.elf $(OBJECTS) main.hex: main.elf rm -f main.hex main.eep.hex avr-objcopy -j .text -j .data -O ihex main.elf main.hex avr-size main.hex # debugging targets: disasm: main.elf avr-objdump -d main.elf
The video has two boards, an Arduino UNO clone based on Atmega328p working at 16MHz and my board which is also based on the same chip but supposedly working at 20MHz. Both
the boards are running the same code for blinking the LED, one second on and one second off.
As you can see, my board is clearly not pulsing with one second delay, it's way above one second. Something is wrong, and I guess it's the clock speed. I defined 20MHz
clock in program and during compilation and the chip is not running at a lower clock. So if the chip was indeed working at 20MHz I wouldn't face such an error. So I suspect
three things,
For normal operations the clock is not necessary, for example you want your board to control some appliances based on some user inputs, such as turn on the lights when the sun goes
down, or a solar tracker using a few motors(not servos or steppers) and light sensors.
But for anything where the timing is important, such as communication, servo/stepper/BLDC motor control etc. or even the simple blinking LED program require precise timing.
The Microcontroller should work at a precise known clock speed, else we will have no control over the 'time'.
From the datasheet, I know that the chip has an internal clock and independent watchdog timers (I will explore this later). The internal clock is limited to 8MHz, you may
be overclock it and get higher clock speed, but that's not good, because the internal clock generator is unreliable above 8Mhz, so time critical functions such as communication
will fail. For any value above 8MHz, upto a limit of 20MHz we can use external clock.
First thing I did is to see what happens if I compile the program with -DF_CPU=8000000UL
instead of-DF_CPU=20000000UL
.
As expected the LED starts blinking with one Second delay; so the problem is what is suspected, chip running at lower clock.
First I tried too read the FUSE bits.
sibu@manjaro emb % avrdude -P usb -b 19200 -c usbtiny -p m328p -v avrdude: Version 6.3, compiled on Feb 21 2016 at 13:33:25 Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/ Copyright (c) 2007-2014 Joerg Wunsch System wide configuration file is "/etc/avrdude.conf" User configuration file is "/home/sibu/.avrduderc" User configuration file does not exist or is not a regular file, skipping Using Port : usb Using Programmer : usbtiny Overriding Baud Rate : 19200 avrdude: usbdev_open(): Found USBtinyISP, bus:device: 001:011 AVR Part : ATmega328P Chip Erase delay : 9000 us PAGEL : PD7 BS2 : PC2 RESET disposition : dedicated RETRY pulse : SCK serial program mode : yes parallel program mode : yes Timeout : 200 StabDelay : 100 CmdexeDelay : 25 SyncLoops : 32 ByteDelay : 0 PollIndex : 3 PollValue : 0x53 Memory Detail : Block Poll Page Polled Memory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack ----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- --------- eeprom 65 20 4 0 no 1024 4 0 3600 3600 0xff 0xff flash 65 6 128 0 yes 32768 128 256 4500 4500 0xff 0xff lfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00 hfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00 efuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00 lock 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00 calibration 0 0 0 0 no 1 0 0 0 0 0x00 0x00 signature 0 0 0 0 no 3 0 0 0 0 0x00 0x00 Programmer Type : USBtiny Description : USBtiny simple USB programmer, http://www.ladyada.net/make/usbtinyisp/ avrdude: programmer operation not supported avrdude: Using SCK period of 10 usec avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.00s avrdude: Device signature = 0x1e950f (probably m328p) avrdude: safemode: hfuse reads as DE avrdude: safemode: efuse reads as FD avrdude: safemode: hfuse reads as DE avrdude: safemode: efuse reads as FD avrdude: safemode: Fuses OK (E:FD, H:DE, L:FF) avrdude done. Thank you.Okay,
_delay_ms();
in C or delay();
in Arduino IDE. One method is to waste clock cycles, just like the functions _delay_ms();
in C or delay();
in Arduino does. But we will have to
write our own routine for this. We can waste clock cycles by making the chip loop through a few calculations or something, since we can calculate how many clock cycles are
required by each of those instructions and we know the time period of the clock, we can calculate the length of the loop so that we get a desired delay by executing this
routine.
I found a few sources which would help me do this
.equ DDRB , 0x04 .equ PORTB , 0x05 .org 0 jmp 16 .org 16 wdr main: ldi r16, 0b00100000 out DDRB,r16 ; Set PB5 to output out PORTB,r16 ; Set PB5 high loop: call delay sbi PORTB, 5 call delay cbi PORTB, 5 rjmp loop delay: ldi r18, 102 ldi r19, 118 ldi r20, 194 L1: dec r20 brne L1 dec r19 brne L1 dec r18 brne L1 ret
wasting the clock cycles to blink LED is not an ideal solution, in fact this is a waste of time. The chip is actually capable of doing much more and blinking an LED shouldn't consume all the resources. The solution is to use Internal/External Timers and interrupts. There are three timers in Atmeg328P, out of which one, Timer1 is a 16bit timer/counter. The other two, Timer0 and Timer2 are 8bit/timer/counter. There is a separate 128KHz watchdog timer too these timers/counters can be used to trigger corresponding interrupts and the interrupt routine can have the instructions to toggle the LEDs.
// Arduino timer CTC interrupt example #include <avr/io.h> #include <avr/interrupt.h> #define LEDPIN 13 void setup() { pinMode(LEDPIN, OUTPUT); // initialize Timer1 cli(); // disable global interrupts TCCR1A = 0; // set entire TCCR1A register to 0 TCCR1B = 0; // same for TCCR1B // set compare match register to desired timer count: OCR1A = 19530; // turn on CTC mode: TCCR1B |= (1 << WGM12); // Set CS10 and CS12 bits for 1024 prescaler: TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12); // enable timer compare interrupt: TIMSK1 |= (1 << OCIE1A); // enable global interrupts: sei(); } void loop() { // main program } ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }The program is from https://arduinodiy.wordpress.com/2012/02/28/timer-interrupts/
x
and when the counter reaches x
it triggers an interrupt. This mode is called
Clear Timer on Compare Match, or
CTC. The interrupt routine has the code to toggle the LED.
WGM12
bit high to enable CTC mode, CS10
and CS12
bits are set high to enable 1024 prescaler, this means for
every 1024 clock cycles the counter will increase by one.
OCR1A
is a 16bit register to store the target count, and when the counter reaches this number it will trigger an interrupt vectored to 0x0016
or
TIMER1_COMPA_vect
. We can keep our 'toggle LED' program here.
OCIE1A
bit of TIMSK1
register should be set high to enable the Timer1 compare interrupt, interrupt when the timer reaches the values set at OCR1A
.
OCR1A
???
We know that the Timer1 increases by one for every 1024 system clock. Suppose the system clock is at F Hz
, the period or the time resolution is 1/F
seconds. So the Timer one takes 1024 * 1/F
or 1024/F
seconds per increment. So for t
seconds of delay, we will have t*F/1024
counts in the Timer1. So this is the value we need to keep at OCR1A
for a t
second delay.
To be very precise the calculation is actually OCR1A = (t*F/prescale) - 1
. The extra -1
is to compensate the extra clock cycle required for resetting
the Timer1 back to Zero when it reaches the target.
For 20MHz clock frequency, and 1 second delay this number would be 19530
.
//timer CTC interrupt test #define F_CPU 20000000UL #include <avr/io.h> #include <avr/interrupt.h> int temp=0; int delay=1; int main(void) { DDRB = 0b00100000; PORTB = (0b00100000); // initialize Timer1 the 16bit timer cli(); // disable global interrupts TCCR1A = 0; TCCR1B = 0; // same for TCCR1* // set compare match register to desired timer count: OCR1A = 19530; // turn on CTC mode: TCCR1B |= (1 << WGM12); // Set CS10 and CS12 bits for 1024 prescaler: TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12); // enable timer compare interrupt: TIMSK1 |= (1 << OCIE1A); sei(); // enable global interrupts: while(1) { // my program } } ISR(TIMER1_COMPA_vect) { if (temp==1) { PORTB = (0b00100000); temp=0; } else { PORTB = (0b00000000); temp=1; } }
I tried translating the same code to assembly, though I couldn't find whats wrong with the program, it's not working, it's not blinking the LED. Anyway the code can be seen below.
; DEFINING MACROS .equ PORTB , 0x05 .equ DDRB , 0x04 .equ TCCR1A , 0x80 ; MEMORY MAPPED .equ TCCR1B , 0x81 ; MEMORY MAPPED .equ OCR1AL , 0x88 ; MEMORY MAPPED .equ OCR1AH , 0x89 ; MEMORY MAPPED .equ TIMSK1 , 0x6f ; MEMORY MAPPED ; TCCR1B - Timer/Counter1 Control Register B .equ CS10 , 0 ; Prescaler source of Timer/Counter 1 .equ CS11 , 1 ; Prescaler source of Timer/Counter 1 .equ CS12 , 2 ; Prescaler source of Timer/Counter 1 .equ WGM12 , 3 ; Waveform Generation Mode .equ WGM13 , 4 ; Waveform Generation Mode .equ ICES1 , 6 ; Input Capture 1 Edge Select .equ ICNC1 , 7 ; Input Capture 1 Noise Canceler ; ***** TIMER_COUNTER_1 ************** ; TIMSK1 - Timer/Counter Interrupt Mask Register .equ TOIE1 , 0 ; Timer/Counter1 Overflow Interrupt Enable .equ OCIE1A , 1 ; Timer/Counter1 Output CompareA Match Interrupt Enable .equ OCIE1B , 2 ; Timer/Counter1 Output CompareB Match Interrupt Enable .equ ICIE1 , 5 ; Timer/Counter1 Input Capture Interrupt Enable ; ***** INTERRUPT VECTORS ************************************************ .equ INT0addr , 0x0002 ; External Interrupt Request 0 .equ INT1addr , 0x0004 ; External Interrupt Request 1 .equ PCI0addr , 0x0006 ; Pin Change Interrupt Request 0 .equ PCI1addr , 0x0008 ; Pin Change Interrupt Request 0 .equ PCI2addr , 0x000a ; Pin Change Interrupt Request 1 .equ WDTaddr , 0x000c ; Watchdog Time-out Interrupt .equ OC2Aaddr , 0x000e ; Timer/Counter2 Compare Match A .equ OC2Baddr , 0x0010 ; Timer/Counter2 Compare Match A .equ OVF2addr , 0x0012 ; Timer/Counter2 Overflow .equ ICP1addr , 0x0014 ; Timer/Counter1 Capture Event .equ OC1Aaddr , 0x0016 ; Timer/Counter1 Compare Match A**************************************************************** .equ OC1Baddr , 0x0018 ; Timer/Counter1 Compare Match B .equ OVF1addr , 0x001a ; Timer/Counter1 Overflow .equ OC0Aaddr , 0x001c ; TimerCounter0 Compare Match A .equ OC0Baddr , 0x001e ; TimerCounter0 Compare Match B .equ OVF0addr , 0x0020 ; Timer/Couner0 Overflow .equ SPIaddr , 0x0022 ; SPI Serial Transfer Complete .equ URXCaddr , 0x0024 ; USART Rx Complete .equ UDREaddr , 0x0026 ; USART, Data Register Empty .equ UTXCaddr , 0x0028 ; USART Tx Complete .equ ADCCaddr , 0x002a ; ADC Conversion Complete .equ ERDYaddr , 0x002c ; EEPROM Ready .equ ACIaddr , 0x002e ; Analog Comparator .equ TWIaddr , 0x0030 ; Two-wire Serial Interface .equ SPMRaddr , 0x0032 ; Store Program Memory Read ;BEGIN ACTUAL PROGRAM .equ INT_VECTORS_SIZE , 52 ; size in words .org 0 rjmp main .org 0x0016 rjmp blink .org 52 main: ldi r16, 0b00100000 out DDRB,r16 ; Set PB5 to output ; out PORTB,r16 ; Set PB5 high cli ;disable global interrupts ldi r16, 0x00 sts TCCR1A, r16 sts TCCR1B, r16 ;set entire TCCR1* registers to 0 ; writing initial value to OCR1A ;16 bit write operation, high byte first ldi r17, 0x4c ldi r16, 0x4a sts OCR1AH, r17 sts OCR1AL, r16 clr r16 ;sbi r16, WGM12 ;turn on CTC mode ;sbi r16, CS10 ;Set CS10 and CS12 bits for 1024 prescaler ;sbi r16, CS12 ldi r16, 0b00001101 sts TCCR1B, r16 ;enable timer compare interrupt clr r16 ldi r16, 0b00000010 sts TIMSK1, r16 sei ;enable global interrupts ldi r20, 0x00 loop: ;main rjmp loop blink: cpi r20, 0 breq dim rjmp light light: ldi r20, 0x00 sbi PORTB, 5 rjmp loop dim: ldi r20, 0xff cbi PORTB, 5 rjmp loop
I had struggled a bit before I could 'interpret' this code into hex. I use the following commands to make the hex and flash it to the chip. I'm using the avr-as
as the interpreter.
avr-as -mmcu=atmega328p -o t.o blink-tim.asm avr-ld -o t t.o avr-objcopy -O ihex -R .eeprom t t.hex avrdude -F -V -p ATMEGA328P -P usb -c usbtiny -b 115200 -U flash:w:t.hexI got many errors and I had to change the mnemonics to fix the errors, almost all the time I was getting the error
Error: operand out of range:XXX
, where XXX can be some number.
out
cannot be used to write to registers
TCCR1A
TCCR1B
OCR1AH
OCR1AL
TIMSK1
sts
. There might be other instructions too, but I haven't explored them all. Also I couldn't find the equivalent command of sbi
,
to set individual bits, that works on these memory mapped registers. I have to load the values to a temporary register like r17
and then use sts
to load these values to the final 'Memory mapped' register.
From the datasheet of the micro-controllers I got the following details and many more.