** Arduino and Avr programming using mainly Assembler This booklet contains info about my experiments with the arduino boards, such as duemilenove, pro mini, eleven etc. This book is about programming the avr microcontroller probably with an arduino type board. Its purpose is to investigate the avr architecture from an assembly programming and c language perpective. Generally my interest is focussed on coding rather than hardware, or circuit design or robotics or what-not. So I am largely content to experiment over a serial connection. SETUP * install arduino ide >> apt-get install arduino Arduino has to be run as sudo on my computer!! to be able to edit sketches. eg sudo arduino * install avrdude and avra to compile , upload from command line. (not ide) >> apt-get install avrdude avra * install fritzing to make pretty pictures of arduino circuits >> apt-get install fritzing Connect usb to uart connector.. rx:tx tx:rx gnd:gnd 3v3:vcc dtr:dtr dont connect 5v (for a 3v3 pro mini for example) In the ide choose tools->board->pro mini 328 3v3 (for example) choose serial port (usb for example) Load an example sketch from ide, eg "blink" and upload. what led on pin 13 blink. * install arduino-mk for command line stuff with arduino * environment vars for mk ------- ARDUINO_DIR = /usr/share/arduino ARDMK_DIR = /usr AVR_TOOLS_DIR = /usr ,,, * make file for arduino-mk ------- BOARD_TAG = pro328 # for the pro mini 3v3 atmega328 # ARDUINO_PORT = /dev/cu.usb* ARDUINO_PORT = /dev/ttyUSB0 # include libraries here ARDUINO_LIBS = Ethernet Ethernet/utility SPI include /usr/share/arduino/Arduino.mk ,,, * show valid values for the boardtag variable. >> make show_boards http://www.mjoldfield.com/atelier/2009/02/arduino-cli.html info on how to do it. make sketch file, eg "first.ino" and copy to new dir, cd there >> make * upload sketch >> make upload It worked!!! MAC OSX SETUP .... * install command line utilities on mac osx >> brew install avra avrdude picocom Avra does not seem to allow anything on the same line as a label in mac osx. This may be a bug. "abort trap: 6" error message THE SIMPLEST PROGRAM I think the simplest possible working code example on an arduino board is to just turn on the on-board led (at portb bit 5). At the very least this can be used to indicate some boolean result (true/false, 0/1 etc). The advantage of this is that no extra hardware is required to run the code. * turn on the on-board led (at pin 13 or portb,5) on an arduino board ------- .include "m328Pdef.inc" sbi DDRB, 5 ; set the data direction to output of bit 5 on PORTB sbi portb, 5 ; turn on the output pin ,,,, * turn off the on-board led on an arduino board ------- .include "m328Pdef.inc" sbi DDRB, 5 cbi portb, 5 ; turn off the output pin ,,,, * the same code as above but with a better framework ------- .nolist .include "m328Pdef.inc" .list .dseg ; create data buffers here in sram .cseg .org 0 jmp start ; put interrupt service routine vectors here. ; maybe create flash memory buffers here, or functions, or else put ; them at the end of the code. which maybe better because they dont ; get in the way of the ISR vector jumps. start: sbi DDRB, 5 ; set the data direction to output of bit 5 on PORTB sbi portb, 5 ; turn on the output pin halt: rjmp halt ,,,, SKETCHES Programs written in the arduino ide are called sketches and are written in a modified version of the c language. This is much simpler than using avr assembly, especially because of the numerous libraries available to drive devices (servos, etc) and read sensors. But I like assembly because it allows me to think about language and minimalism. * basic sketch with serial ------------ void setup() { Serial.begin(9600); Serial.println("Hello !!!"); } void loop() { } ,,, BLINK .... Blink is the quintessential starting sketch on the arduino. It requires no hardware because all or most arduinos have a LED on the board. * blink the onboard led ------------ // on some arduino boards the on-board led may be // on a different pin. int led = 13; int milli = 200; void setup() { pinMode(led, OUTPUT); } void loop() { digitalWrite(led, HIGH); // turn LED on delay(milli); // wait some milliseconds digitalWrite(led, LOW); // turn LED off delay(milli); // wait } ,,, BOARDS pro mini - based on atmega328. connect with ftdi cable to usb 32K flash (program) - 2K bootload 2K sram (data). 1K eeprom 3.3v and 8 megaherz 5v and 16 megaherz External interrupts. pin 2,3 uno - atmega328, 5v 16megaherz? BOOKS The mazidi book looks a good and simple resource for programming the arduino in assembler. But the examples must be modified for the atmega328 http://www.microdigitaled.com/AVR/Code/AVR_codes.htm all the mazidi books examples as plain text source code are available here which is an amazing resource. Since all the examples are very carefully and well written. * wget -c --user-agent=Mozilla --no-directories --accept='Example*.*' -r -l 1 http://www.microdigitaled.com/AVR/Code/AVR_codes.htm This wget got most of the chapters but not all. -c continue, -r -l1 recursive but only one level, --accept file name pattern. The examples need to be adapted because they are for the m32def device eg txen -> txen0, ucsrb -> ucsr0b. Also, many "out" instructions need to be replaced by "sts" since we are dealing with extended i/o space. I think The examples are all uppercase and use tabs between mnemonics and register names. * make names better >> rename 's/\.TXT$/.txt/' *.TXT * insert an example using vim >> :r mazidi/Example11_5.asm ARDUINO C BOOKS .... "Arduino, circuits and projects guide" by Gunter Spanner has lots of good stuff in it. Eg sin wave fast pwm for nice "ding" tones etc. GOALS To learn how to call code using indirect addressing (ie using the x, y, z registers) so we can do something like >> call bx ; x86 architecture To develop a simple repl loop, "read, evaluate, print, loop" So a simple read-only forth style interface over a serial connection- what used to be known as a Forth "Home Brew" play chess on an atmega328 microcontroller over a serial connection. Develop a byte code system. Ie, a simple interpreter of byte code for the atmega328 TOOLS == tools .. avra - an assembler to use with arduino .. avrdude - a tool to program the arduino board .. BUTTONS We can connect a button to provide some input to the microcontroller. We either use an internal pull-up/down resistor or an external one not working!! light always on... * turn on the on-board led when a button is pressed --------------- /* The circuit: * LED attached from pin 13 to ground * pushbutton attached to pin 2 from +3v3 * 10K resistor attached to pin 2 from ground or internal pullup */ const int buttonPin = 2; const int ledPin = 13; int buttonState = 0; void setup() { pinMode(13, OUTPUT); // dispense with external resistor pinMode(buttonPin, INPUT_PULLUP); } void loop(){ buttonState = digitalRead(buttonPin); if (buttonState == HIGH) { // turn LED on: digitalWrite(13, LOW); } else { // turn LED off: digitalWrite(13, HIGH); } } ,,,, DISPLAYS SEVEN SEGMENT DISPLAY .... This is the old calculator number display. NOKIA SCREEN .... nokia 5510, based on phillips pd.... is commonly used with arduino. runs on 3v3 so its easiest to use it with a 3v3 pro mini arduino. Otherwise, voltage dividers/ level shifters are needed for power and for all digital pins... LCD .... Hitachi HD44780 is the most common control chip of liquid crystal displays, and is supported by an arduino library. The arduino-mk program has a good example of using an lcd with and arduino and the lcd library. >> /usr/share/doc/arduino-mk/examples/HelloWorld/HelloWorld.ino The circuit: * LCD RS pin to digital pin 12 * LCD Enable pin to digital pin 11 * LCD D4 pin to digital pin 5 * LCD D5 pin to digital pin 4 * LCD D6 pin to digital pin 3 * LCD D7 pin to digital pin 2 * LCD R/W pin to ground * 10K resistor: * ends to +5V and ground * wiper to LCD VO pin (pin 3) OLED SCREEN .... The Oled screen is a more recent technology compared to lcd screens with high contrast and 2 wire interface. ULTRASONIC Simple ultrasonic boards can find range based on the speed of sound. Ultrasonic Sensor HC-SR04. This needs 5v. Emit at 40KHz and receive an echo. Speed is dependant on temperature of air. connections: trigger -> pin 9, echo -> 10, gnd:gnd, vcc:5v I used the 5 volt wire from the ftdi board to power the ultrasonic sensor with a pro mini 3v3 and it worked, but this fried the promini 3v3 after leaving it running for a while * simple sketch, assuming speed of sound 340 m/s, no resistors ---------------- // pin numbers or use "define" const int trigPin = 9; const int echoPin = 10; const int ledPin = 13; long duration; int distance; void setup() { pinMode(trigPin, OUTPUT); // trigger fires sound pulse pinMode(echoPin, INPUT); // echo receives sound pulse Serial.begin(9600); // Starts the serial communication } void loop() { digitalWrite(trigPin, LOW); delayMicroseconds(2); // trigger HIGH for 10 micro secs sends sound pulse digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // returns the sound wave travel time in microseconds duration = pulseIn(echoPin, HIGH); // distance in centimeters, assumes speed of sound 340 m/s distance = duration*0.034/2; Serial.print("Echo duration: "); Serial.print(duration); Serial.println(" microseconds "); Serial.print("Distance: "); Serial.print(distance); Serial.println(" cm "); if (distance < 40) { digitalWrite(ledPin, HIGH); Serial.println("< 40 cm !!"); } else { digitalWrite(ledPin, LOW); } delay(800); } ,,, BRUSHLESS MOTORS AND ESC Brushless motors with an "esc" unit are driven in exactly the same way as servo motors, that is, with a 50 herz wave, with a pulse width varying between roughly 1ms and 2 ms. At 50 hz the wave length will be 20ms. Brushless motors and electronic speed control units are used on many remoted control model aircraft. This means that the propeller can be easily controlled with an arduino. STEPPER MOTORS Use phase correct pwm mode for stepper motors. SERVO MOTORS https://sites.google.com/site/qeewiki/books/avr-guide/timers-on-the-atmega328 Good atmega info. Also has a hint on motors. Servo motors should be driven with pwm at a frequency of 50Hz. Not sure yet whether Servo motors need a phase correct PWM or not... You can also use pulse width modulation to control the angle of a servo motor attached to something mechanical like a robot arm. Servos have a shaft that turns to specific position based on its control line. Our servo motors have a range of about 180 degrees. Comment from "sparkfun" site: "Frequency/period are specific to controlling a specific servo. A typical servo motor expects to be updated every 20 ms with a pulse between 1 ms and 2 ms, or in other words, between a 5 and 10% duty cycle on a 50 Hz waveform. With a 1.5 ms pulse, the servo motor will be at the natural 90 degree position. With a 1 ms pulse, the servo will be at the 0 degree position, and with a 2 ms pulse, the servo will be at 180 degrees. You can obtain the full range of motion by updating the servo with an value in between." The text above gives hints how to control a servo with avr assembly and pwm. DRIVING MULTIPLE SERVO MOTORS .... Is is possible to drive 2 or 3 servos with timer1, not sure about timers 0 and 2. The other alternative is 'bit banging'. * a comment ------- First note that in OCRxn, the x defines the Timer number and n defines which servo you are controlling. Most timers can control multiply servos. For example on Timer1 you can set OCR1A, OCR1B, and sometimes OCR1C (read the data sheet to find out how many servos a particular timer can support). ,,, The "servo" c library available for arduino is capable of driving multiple (12) servos at once. For doing the same thing in avr assembly language here is some advice from Chuck Baird on the avrfreaks.net site: "... driving only 6 servos is fairly simple. You need to put out a pulse to each servo every 20 ms, and the pulse width (which determines the servo's position) is a maximum of 2 ms wide. Therefore, use a timer to generate an interrupt every 20/6 ms, and on each interrupt attend to one servo, round robin. In the ISR, turn the bit for the "current" servo on, then start a second timer for up to 2 ms (depending on that servo's position). When that interrupt fires, turn that same bit off. And that's one (easy) way to do it, if you have 2 timers to spare." The above comment provides a template for driving multiple servo motors in avr assembly language SERVO MOTORS IN C The arduino libraries include the servo library which can drive many servo motors at once, which is very convenient. positions ?? 45: extreme left 90: centre 135: extreme right tested on freetronics "uno" and promini 3v3 circuit: 3v3: red wire, gnd: black wire, other to pin 9, no resistor * control a servo motor ------------ #include Servo servo; //int pos = 0; void setup() { Serial.begin(9600); servo.attach(9); } void loop() { Serial.println("sweep..."); for (int pos = 0; pos <= 180; pos += 1) { servo.write(pos); delay(15); } Serial.println("and back..."); for (int pos = 180; pos >= 0; pos -= 1) { servo.write(pos); delay(15); } } ,,, We can communicate to the program below with ... >> sudo picocom -b 9600 --echo /dev/ttyUSB0 There is a gotcha here. Many serial terminals send line feeds cr lf etc which makes Serial.available return true even when there is nothing to read. So I am using a hack in the code below to get rid of the lf if any. * control a servo motor with the keyboard and a serial connection ----------------- #include Servo servo; int v = 0; void setup() { pinMode(9,OUTPUT); servo.attach(9); //analog pin 0 //servo.setMaximumPulse(2000); //servo.setMinimumPulse(700); Serial.begin(9600); Serial.println("Enter position 0 to 180 ?"); } void loop() { //static int v = 0; if (Serial.available()) { int v = Serial.parseInt(); servo.write(v); Serial.print("To position:"); Serial.println(v); v = 0; // get rid of line feed if terminal is sending one // this is not working... maybe need to disable in terminal // software. if (Serial.available()) { Serial.read(); } if (Serial.available()) { Serial.read(); } } } ,,, BLUETOOTH WITH HC05 The HC-05 is a very inexpensive and widely used bluetooth chip which is compatible and easy to use from an Arduino board The default baud rate is 38400 (not 9600), 8 data bits, 1 stop bit, no parity bit. Once the hc05 is connected to the rx and tx pins of an arduino board, the serial communication coding is exactly the same as any other serial connection, which is very handy. https://www.itead.cc/wiki/Serial_Port_Bluetooth_Module_%28Master/Slave%29_:_HC-05 Lots of good info about the HC=05 http://www.instructables.com/id/Modify-The-HC-05-Bluetooth-Module-Defaults-Using-A/ Modify hc05 with at commands The hc-05 has an onboard led which is good for knowing what is going on with it. So ... == hc-05 onboard led information led not lit: no power! put 3v3 on vcc and gnd to gnd led fast flash: power but no signal led slow single flash: connected to serial but not paired led slow double flash: connected and paired!! Using the hc05 bluetooth board we can connect to an arduino microcontroller using a linux computer with no wires!! http://pi19404.github.io/pyVision/2015/04/03/22/ this page was very usefull for getting it all working and giving command line incantations. It is possible blueman does this for you, below * in /etc/bluetooth/rfcomm.conf put, changing the mac address ------------ rfcomm0 { bind no; device 98:D3:31:XX:XX:XX; # change this to your HC05 mac address channel 1; comment "Serial Port"; } ,,, * install cutecom a nice gui serial terminal >> sudo apt-get install cutecom Now pair the device using 'blueman', your device should appear with an option to connect via serial * Start the cutecom serial terminal >> sudo cutecom Note!! On my system cutecom has to be started as root or you wont be able to connect to open /dev/rfcomm0 and in the device box type /dev/rfcomm0 (etc). Set the baud rate to 57600, and click open device. Now type your commands and see the robot move (if its a robot you are controlling). Of course this assumes you have code on the arduino that receives serial data and executes commands. Baud Rate: in my experiments the robot moved even when the baud rate was not correct (??). * or use picocom with hc-05 connected to usb port >> sudo picocom -b 38400 --echo /dev/ttyUSB0 Unusually the default baud rate for HC-05 bluetooth is 38400 when connected via usb, but 9600 when connected via bluetooth... Type control-a control-x to exit picocom * minicom is another alternative >> sudo minicom -b 38400 -D /dev/ttyUSB0 Then turn off hardware flow control (control-a o) and turn echo on Minicom allows scripts which could be useful. Turning hardware flow control off only seems necessary when connected via usb AT COMMANDS TO CONFIGURE THE HC05 .... We can use the venerable old "AT" modem commands to configure an HC-05 chip. I thought those days were over ... I use the graphical "cutecom" serial monitor with baud=38400, line-ending=cr,lf device=/dev/ttyUSB0 (when the HC-05 is plugged into a usb port via a usb-to-uart converter). It appears that it is not possible to configure an HC-05 via bluetooth which makes sense because it would invalidate the security of the device. Connect HC-05 to the usb to uart converter board as follows >> rx to tx, tx:rx, 3v3 to vcc, gnd to gnd, easy... The CR+LF line ending is important, it wont work otherwise When connecting the HC-05, hold down the little reset button (which it hopefully has, otherwise proceedure is different). The led will blink very slowly Type "AT" to receive "OK" for confirmation of communication * see if communication is happening with HC-05 bluetooth chip >> AT * display the module working state >> AT+STATE? (eg: "INITIALIZED" "READY" "PAIRABLE" "PAIRED" "INQUIRING" "CONNECTING" "CONNECTED" "DISCONNECTED" "NUKNOW" ) * show the bluetooth password or pin code >> AT+PSWD or AT+PSWD? * change the bluetooth connection pin code >> AT+PSWD= * check the current baud rate, stop bit and parity >> AT+UART? * set the baud rate to 38400, no stop bit, no parity >> AT+UART=38400,0,0 * set the baud rate to 115200, stop bit, with parity >> AT+UART=115200,1,2 * get the hc-05 bluetooth address (not mac address) >> AT+ADDR? eg: +ADDR:1234:56:abcdef (NAP: UAP : LAP) These addresses are used when binding master (server) and slave (client). See the next sub-section for more information. Its important to change the name to something memorable, so that pairing will be easy. This name is what appears in the blue tooth connection screen (eg for android/linux/windows) * set module name (default hc-05) >> AT+NAME= * get bluetooth device name (address eg: 00:02:72:OD:22:24) >> AT+RNAME? 0002,72,od2224\r\n * check connect mode (0=fixed address, 1=any address, 2=slave loop) >> AT+ CMODE? * set fixed address 98:D3:31:20:93:CB >> AT+BIND=98d3,31,2093cb MASTER SLAVE .... In the master slave mode, the HC-05 can automatically connect to another HC-05 bluetooth chip. This obviates the need to have to manually connect via bluetooth in order to control a robot, for example. The second HC-05 board can be connected to computer with a usb-to-uart board (ftdi) When I typed at+init the 2 modules bound together. But... there is disagreement about cmode=0/1 which is fixed address mode??? I had to configure the master HC-05 (which is plugged into the usb port of my laptop via a usb-to-uart board) at 38400 baud even though the slave (on the arduino robot) is apparently running at 57600. This works! I get coherent responses from the robot serial connection. If I set the master at 57600 I get gibberish responses from the robot (which indicates a baud mismatch). It seems that when the HC-05 is plugged into the usb port via the usb-to-uart converter it always runs at 38400 no matter what its configured baud rate is!!!??? On my HC-05 at+cmode=0 is fixed address and must be used with at+bind On my HC-05 at+cmode=1 binds to any address. The key to all this is the at+init command which actually binds the "slave" hc-05 to the "master" hc-05 (which is connected to the computer or some other controlling device. When cmode=1 (connect to any device) after at+init the hc-05 blinks slowly while it search for another device in range and then binds to it. But when cmode=0 and bind=addr the hc-05 connects quickly after at+init (2 short flashes) * set an HC-05 to master mode, and bind it to the slave (on the robot) ------ AT+ORGL // factory reset AT+RMAAD // clear all paired devices AT+UART=38400,0,0 // baud rate, stop, parity AT+ROLE=1 // master mode AT+CMODE=0 // fixed address connection mode, yes (or AT+CMODE=1 connect to any device in transmission range) AT+BIND=98D3,31,3069B0 // address of slave AT+INIT ,,, The slave usually doesnt have to be configured at all, it appears. * set an HC-05 to slave mode >> AT+ROLE=0 GOTCHAS .... If you connect tx->tx and rx->rx Wrong! (hc05 to arduino) the hc-05 will pair and connect but not do anything. Sometimes picocom will say >> FATAL: cannot open /dev/rfcomm5: Device or resource busy but why??? maybe the device is being used by some other process (cutecom ...) Make sure you are using >> sudo !!! >> sudo minicom or sudo picocom or sudo cutecom Or make sure that you are using the last /device Eg if you try /dev/rfcomm3 when blueman has assigned /dev/rfcomm4 then rfcomm3 will be "busy" STACK The avr chips usually have a hardware stack which is used for procedure calls (call, rcall) and can be used by the programmer explicitly with "push" and "pop". However the stack is 8 bit, obviously, so it is a bit clumsy to use it to pass pointers to code or data space. The stack pointer registers can (should?) be initialized before using the stack (ie calling a procedure), but this is done automatically on chips since about 2006. * initialize the stack, should be done before any "call/rcall" ----------- .include "m328Pdef.inc" ldi r21, high(ramend) out sph, r2 ldi r20, low(ramend) out spl, r21 ,,, WINDOWS install driver cp2102 to use the usb to uart device. Then putty at baud rate 38400 maybe com70 INTERPRETER The following is the bare bones code for running an interpreter over a serial connection. Here the mcu receives data via serial and executes the appropriate command. A more sofisticated system could read the serial stream for a whole line and modify commands with parameters etc, after that, if/loop/ etc. A tiny command interpreter is what is needed, maybe an adaption of python, forth, lua or tcl This is 'arduino' c... * a simple interpreting loop getting commands from serial ------------------- void setup() { Serial.begin(57600); // ... } void loop() { if (Serial.available() > 0) { data = Serial.read(); Serial.print("rx:"); Serial.print(data); } if (data == 'a') { // execute command for 'a' } else if (data == 'b') { } else {} } SYNTAX FOR ARDUINO C * print as ascii >> Serial.write(a); Many arduino boards use the atmega328 avr chip which has 32K of program memory * turn on the led on arduino pin 13 ---- ;hello.asm ; turns on an LED which is connected to PB5 (digital out 13) .include "m328Pdef.inc" ldi r16,0b00100000 out DDRB, r16 ; DDRB == data direction register B out PortB, r16 ; send a 'high' to PB5 only Start: rjmp Start ; hang in an infinite loop ,,, * upload this hex file to the arduino (uno ?) with >> avrdude -p m328p -c stk500v1 -b 57600 -P /dev/ttyUSB0 -U flash:w:hello.hex This is not working. The problem is the 'stk500' which may be correct for the 'uno' arduino but not for the duemilenovo. see below for a working example. The problem seems to be the -c stk500v1 option * also try for arduino uno s etc >> -b 115200 and the port -P /dev/ttyACM0 ARCHITECTURE .... The atmel atmega chips are 8 bit risc processors. They have a stack and a register file. Data memory and program memory are separate (program memory is stored in flash). BYTE CODE One step on the path to implementing a portable forth system, may be to develop a "byte-code" system. This means that opcode (1,2,3...) will execute specific functions which mimic a virtual machine. We can use the avr instruction 'ijmp' or 'icall' to implement this byte code system. The icalls are working, now we need load the correct optable offset and then load correct jump address into Z register. The basic procedure for bytecode execution is... ( the opcode execution loop. ) ; set PC pointer=Z register (saved in r23:r22) ; load next opcode into reg and increment ip ; compare to zero? and halt? ; double the opcode (word pointer) ; add offset to optable. ; do ijmp/icall to address The example with a 'jump' instruction is more complete. * a basic byte code system in avr assembly ------ .include "m328Pdef.inc" .equ DUP = 1 .equ EMIT = DUP+1 .equ KEY = EMIT+1 .equ STAR = KEY+1 .dseg ; ram data segment .cseg ; start of flash "rom" code segment .org 0x0000 jmp wakeup ; pointers to opcode functions. optable: .dw 0, dupx, emitx, keyx, starx duph: dupx: nop ret emith: .dw 0 .db "emit", 4 emitx: transmit: ; sbis doesnt work because ucsr0a is not in standard ; i/o space so it cant be used with sbis (skip if bit in i/o is set) lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp transmit ; if not, then just wait ; this should be got from the stack ldi r17, '#' sts UDR0, r17 ; if empty tx next character ret ; ret makes it easier to test these functions ; jmp nextopcode keyh: .dw emitx .db "key", 3 keyx: wait: lds r16, UCSR0A ; get usart status info sbrs r16, RXC0 ; has a byte been received? rjmp wait ; if not just wait for one. lds r17, udr0 ; get received byte ; now push the char onto the stack ret ; ret makes it easier to test these functions ; jmp nextopcode starh: .dw 0 .db "star", 4 starx: waithere: lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp waithere ; if not, then just wait ldi r17, '*' sts UDR0, r17 ; if empty tx next character ret serialx: ldi r16,(1<> https://sites.google.com/site/avrasmintro/ ORGANISATION .... An avra assembly file has a standard format and organisation. Code should be able to be on the same line as a label, but a bug in the Mac OSX version of avra means that it cant. * an example file with a reasonable organisation. ------- .nolist ; the include file provides named registers for the given avr chip .include "m328Pdef.inc" .list .eseg ; define eeprom data buffers here ; There is 1024 bytes of eeprom in the atmega328 MCU ebuffer: .db 5, "hello" .dseg ; create data buffers here in sram counter: .byte 2 .cseg .org 0 jmp start .org 0x02 jmp external0isr ; external interrupt 0 vector ; put interrupt service routines vectors here. ; maybe create flash memory buffers here, or functions, or else put ; them at the end of the code. which maybe better because they dont ; get in the way of the ISR vector jumps. start: ; initialization code here. main: ; the main code loop here rjmp main ; procedures can go here sleep: ret ; Interrupt service routines. external0isr: ; service the interrupt. reti ,,,, ERRORS .... >> Error : I/O out of range (0 <= P <= 63) This error may mean that you are trying to use OUT with a memory mapped control register... Try usign STS instead! * use sts not out >> ; out TIMSK0, temp ; enable compare/match interrupt >> sts TIMSK0, temp ; enable compare/match interrupt TRICKS .... define a register as null and then set it to zero. .def r2 = null REGISTERS .... r0 to r31 general purpose registers Z reg (r31:r30) used for indirect addressing with lpm, icall and ijmp instructions. This is important in a byte code system. Y reg (r29:r28) indirect addressing of ram memory X reg (r27:r26) indirect addressing of ram memory r25:r24 could be used to hold top-of-stack in a 16 bit forth system r23:r22 could be used for program counter PC MACROS .... Setting up the stack needs to be a macro and not a function because function calls with rcall etc, are not available until the stack pointer has been set! * macro example to set up the stack pointer ------------------ .include "m328Pdef.inc" .macro initstack ldi r21,high(ramend) out sph,r21 ldi r21,low(ramend) out spl,r21 .endmacro initstack ,,, DEF .... we can give registers more descriptive names with the .def assembler directive * eg --------- .def acc = r0 .def counter = r18 ,,, LABELS .... labels require a colon, eg buffer: not buffer labels cant start with dot (ie local labels in nasm) cant have dots in them... CONSTANTS .... * define constants ------------- .equ score = 100 .set score = 100 ; the same but can be changed later ,,, DATA SEGMENT .... In the atmel avr architecture, code and data space are separate. * specify the data segment >> .dseg * an example of creating a buffer in the data segment --------- .dseg counter .byte 2 .cseg ,,,, CODE SEGMENT .... With .db we can define string constants such as "test" but without any escaped characters (eg \n) * define some constant data in the code segment in flash memory ---------------- .include "m328Pdef.inc" .cseg greet: .db "hello", 0 greeting: .db "hello", '\n', 0 message: .db 'h','e','l','l','o',0 ; a harder way to do it ,,, MNEMONICS .... avr assembly, as with all microcontroller assemblies has a plethora of mnemonics or aliases for referring to ports and registers. Eg: tccr0b means "timer/counter 0 control register b". These are in addition to all the standard assembly mnemonics such as "ldi" etc. ARITHMETIC INSTRUCTIONS .... These instructions perform some simple arithmetic on an 8 bit register. add, sub, inc, dec JUMPS .... branches or jumps go to a particular instruction if the given condition is met. There are conditions which are specific to signed and unsigned numbers among others. * jump if reg16 is = or > than 20 (unsigned comparison) ------ cpi r16, 20 brsh higher ; ... higher: ,,, == common signed branches .. brge - branch if greater or equal, signed .. brlt - branch if less than, signed == common unsigned branches .. brlo - branch if less than .. brsh - branch if greater or equal .. brpl - if positive .. brmi - if negative .. brne - branch if not equal .. breq - branch if equal SKIPS .... skips are similar to jumps except that they only skip one (the next) instruction, if the condition is met. COMPARISONS .... See application note AVR202 for 16bit comparisons * compare 2 16 bit numbers for size -------- .include "m328Pdef.inc" .cseg sbi DDRB, 5 ; make portB.5 output ldi r17, 1 ldi r16, 0xFF ; load 0x01FF into r17:r16 ldi r19, 2 ldi r18, 0xFF cp r16, r18 ; compare low bytes cpc r17, r19 ; compare high bytes (with carry) brlo light rjmp here light: sbi PORTB, 5 ; turn on portB.5 (arduino pin13) here: jmp here ,,, LOOPS .... * the simplest loop construct ---- init: ldi r16, 100 again: dec r16 brne again ,,, * a delay loop ------- .include "m328Pdef.inc" .cseg .org 0 jmp start start: sbi DDRB, 5 ; make portb, 5 output (for the onboard led) again: sbi PORTB, 5 rcall sleep cbi PORTB, 5 rcall sleep rjmp start ; sleeps for approximately 200 milliseconds on a 16Mhz atmega328 sleep: clr r16 ; set reg16 := 0 clr r17 ldi r18, 40 here: ; a delay loop 40*256^2 instructions dec r16 ; brne here dec r17 brne here dec r18 brne here ret ,,, PROCEDURES .... call is 4 bytes and can call anywhere in 64K rcall is 2 bytes and can only call near proceedures icall uses the Z register (r31:r30 register pair) ijmp is not a procedure call, but can be used like one RETURNING VALUES FROM A FUNCTION OR PROCEDURE .... The return value can be left on the stack, but it must be placed underneath the top value (which is the return address for the procedure). Return values can just be left in a register A true/false return value could be indicated by setting or clearing the carry flag with CLC or SEC. USING THE STACK TO RETURN A VALUE .... * return a byte value on stack and juggle the return address. -------- pop r31 ; high byte of ret address into ZH pop r30 ; low byte of ret address in ZL push r24 ; put return value on stack push r30 push r31 ret ; return to caller ,,, The technique below returns a byte value on the stack, and uses a trick to shorten 3 instructions into 1. It relies on the fact that the IJMP instruction jumps to the address in the Z (r31:r30) word register. * use a trick to return to caller after putting ret value on stack -------- pop r31 ; high byte of ret address into ZH pop r30 ; low byte of ret address in ZL push r24 ; put return value on stack ijmp ; return to caller (instead of "push r30; push r31; ret;") ,,, RETURN VALUE IN REGISTER .... r25:r24 is a standard register pair for return values (not on the stack). But this is just a convention. RETURN VALUE IN CARRY FLAG .... The carry flag by convention is used to return a true/false value. ICALL .... Notice that loading the Z register for ICALL is different to loading the Z register for a table lookup with LPM. * call a procedure indirectly ---- .include "m328Pdef.inc" .cseg ldi zh, high(blink) ldi zl, low(blink) icall here: jmp here blink: ret ,,, Code below not working, needs to be debugged. * call the 'blink' function indirectly (blink pin13 on arduino uno) ---- .include "m328Pdef.inc" .cseg ;ldi r16,0b00100000 ;out DDRB,r16 ; DDRB == data direction register B sbi ddrb, 5 ldi zh, high(blink) ldi zl, low(blink) again: icall jmp again ; just does nothing for a while sleep: clr r16 ; set reg16 := 0 clr r17 ldi r18, 40 here: ; a delay loop 40*256^2 instructions dec r16 ; brne here dec r17 brne here dec r18 brne here ret blink: sbi PORTB, 5 ;out PortB,r16 ; send a 'high' to PB5 only rcall sleep sbi PORTB, 5 rcall sleep ret ,,, IJMP .... Only the Z register can be used for indirect jumps. Post increment is not relevant here, or not useful. * jump to a procedure indirectly ---- .include "m328Pdef.inc" .cseg ldi zh, high(do) ldi zl, low(do) ijmp here: jmp here do: sbi PORTB, 5 ;out PortB,r16 ; send a 'high' to PB5 only rcall sleep sbi PORTB, 5 rcall sleep ret ,,, LOGICAL INSTRUCTIONS .... ANDI .... andi - logical and with immediate (literal) value EOR .... exclusive or. This can be used for toggling bits, but the operand cant be immediate. eg "eor r20, 1" is an error and won't be compiled * toggle the arduino pin 13 led with exclusive or --------- .nolist .include "m328Pdef.inc" .list .def mask = r16 .cseg .org 0 sbi DDRB, 5 ; portb.5 = output (for LED feedback) ldi mask, 1<<5 ; toggle bit for portb,5 eor r17, mask ; xor toggle out PORTB, r17 ; halt: rjmp halt ,,,, BITS .... * toggle the least significant bit in r17 ----------- eor r17, 1 eor r17, 0x01 ; the same ,,, * toggle the most significant bit in r17 ----------- eor r17, 1<<8 eor r17, 0b10000000 ; this is the same, but more verbose ,,, MOVING DATA * copy data from flash ("rom" or "program") memory to sram data memory ------------- .nolist ; change this to the avr chip in use. .include "tn2313def.inc" .list .def temp = r16 .DSEG test: .byte 10 ; reserve space for array test[] in RAM .CSEG .org 0 rjmp start start: ldi r16, ramend ; set stack pointer out SPL, r16 ;initialize test (copy test_init_values into test[]) ldi zl, low(test_init_values*2) ldi zh, high(test_init_values*2) ldi yl, low(test) ldi yh, high(test) ldi r17, 10 ; copy 10 bytes copy: lpm temp, z+ st y+, temp dec r17 brne copy main: ldi temp, 20 sts test+2, temp ; test[2] = 20 lds temp, test+2 ; temp = test[2] rjmp main endmain: ;save init values in FLASH test_init_values: .db 1,2,3,4,5,6,7,8,9,10 ,,, TABLE READ INSTRUCTIONS LPM .... Reading data from the code segment (flash memory) is often referred to as table reading. The code segment data is often assumed to be "constant" since it is not usually altered during the execution of a program (although it can be with the spm instructions) There is usually much more code memory (eg 32K in atmega328) than sram data memory (2K in atmega328) so it is a good idea to store long strings here. lpm Rn, Z - load program memory lpm Rn, Z+ - load program memory and increment pointer * read a string stored in the program memory ------ .include "m328Pdef.inc" .def counter = r16 .dseg .cseg .org 0x0 rjmp main buffer: .db 5, "hello" main: ldi counter, 5 ; load string count into r16 ldi zh, high(buffer<<1) ldi zl, low(buffer<<1) inc zl ; better is ; lpm r16, z+ nextchar: lpm r20, z+ ; get contents of byte at z into r16 dec counter brne nextchar here: rjmp here ,,, IN .... in gets data from an i/o port LDI .... ldi - load immediate value It is not possible to use "ldi" with the file registers below r16 (ie "low registers"). But we can use CLR to load zero * load 5 in register 16 ----------------------- ldi r16, 0b00000101 ,,, * load 0 into register 3 ----------------------- clr r3 ,,, MOV .... The mov instruction can be used for moving data with the register file (r0 to r31) I think INDIRECT ADDRESSING WITH LD AND ST .... * load contents of sram location 0x0130 into r18 ------ .include "m328Pdef.inc" .eseg ; define eeprom data buffers here ; 1024 bytes of eeprom in atmega328 ebuffer: .db 5, "hello" .dseg ; define sram data buffers here. ; 2k bytes of sram in atmega328 dbuffer: .byte 30 .cseg ldi XL, 0x30 ldi XH, 0x01 ld r18, X here: rjmp here ; loop for ever ,,, * store 0 in 16 addresses starting at 0x0060 ------ .include "m328Pdef.inc" .dseg buffer: .byte 16 .cseg ldi r16, 16 ldi xl, 0x60 ldi xh, 0x00 ldi r20, 0x0 again: st X+, r20 dec r16 brne again here: rjmp here ; loop for ever ,,, STS .... store direct to data space ARITHMETIC IN ASSEMBLER SIGNED ARITHMETIC .... The description below shows how to add an 8bit register (interpreted as a signed number -128 to +127) to a 16bit register pair. Transform the 8-bit signed to 16 bit signed and then add the L.S.Bytes with carry and then add the carry (if exists)11, the 2 M.S. Bytes without the signs and put as sign the sign of the biggest. Beware there might be overflow. * add 8 bit signed register to 16bit register pair ------ clr 8bitvaluehighbyte ;New empty high byte for 8 bit value sbrc 8bitvaluelowbyte,7 ;Skip if 8 bit value is positive ser 8bitvaluehighbyte ;8 bit value is negative so set it's high byte ; or "com ..." for ones complement. add 16bitvaluelowbyte,8bitvaluelowbyte adc 16bitvaluehighbyte,8bitvaluehighbyte ,,, DIVISION .... There is no division instruction for the avr micros (attiny, atmega etc). Division can be coded with repeated subtraction but uses many clock cycles. Division by 2 or powers of two can be achieved easily with the LSR (logical shift right) instruction. The code for a simple 8 bit division is simple but can take a few clocks. * divide 250 by 60 --------------------- .include "m328Pdef.inc" ; the atmega328p, the usual arduino micro .def A = r16 ; an accumulator .def counter = r20 .dseg ; data in ram .cseg ; code/data in rom ldi A, 250 clr counter again: subi A, 60 inc counter cpi A, 60 brsh again ; the A register will have remainder and the counter will ; have the dividend here: rjmp here ,,, unsigned?? * divide 16bit value by 16bit value, repeated subtraction --------------------- .include "m328Pdef.inc" ; the atmega328p, the usual arduino micro .def divl = r16 ; low byte of dividend .def divh = r17 ; high byte of dividend .def divisorl = r18 .def divisorh = r19 .def quotient = r20 .equ D = 1000 .equ dd = 100 .cseg ; code/data in rom ldi divl, low(D) ldi divh, high(D) ldi divisorl, low(dd) ldi divisorh, high(dd) clr quotient again: sub divl, divisorl sbc divh, divisorh inc quotient ; check if divh:divl > divisorh:divisorl ; 16 bit comparison cp divl, divisorl cpc divh, divisorh brsh again finished: ; the divh:divl register pair will have the remainder ; and register "quotient" (r20) will have the quotient main: sbi DDRB, 5 ; portb.5 = output (for LED feedback) cpi quotient, 10 breq light rjmp here light: sbi PORTB, 5 here: rjmp here ,,, * proceedure to divide 2 8bit numbers on the stack. --------------------- .include "m328Pdef.inc" ; the atmega328p, the usual arduino micro .def A = r16 ; an accumulator .def divisor = r17 .def counter = r20 .def temp = r23 .dseg ; data in ram .cseg ; code/data in rom rjmp main ; stack ( div divisor -- quotient remainder ) divx: pop r21 ; juggle return address pop r22 pop divisor pop A clr counter again: sub A, divisor inc counter cp A, divisor brsh again push counter ; quotient push A ; remainder push r22 push r21 ret main: ldi temp, 20 ; dividend push temp ldi temp, 4 ; divisor push temp rcall divx ; quotient + remainder now on stack here: rjmp here ,,, APPROXIMATE DIVISION ... An interesting text about simplified division by 10 and 5 which is accurate for 1 byte. https://www.avrfreaks.net/forum/dis-asm-dirty-math-tricks-adventures-division-ten * approximate division by 10 and 5 for an 8 bit argument -------------- ; 8x8 division by ten, using a trick div10: ldi b, 26 mul a, b ;answer in r1 ; a longer version div10: rcall div5 ;r1=a / 5 lsr r1 ;r1=(a/5) / 2 = a/10 ret div5: ldi b,51 mul a,b inc r1 ;answer in r1 ret ,,, MULTIPLICATION .... The avr "mul" instruction multiplies 2 arguments, each 1 byte and leaves a 16 bit result in R1:R0. The multiplicand and multiplier can be any (high ?) register * multiply 42 by 10 leaving answer in r1:r0 --------- ldi r16, 42 ;load multiplicand ldi r18, 10 ;load multiplier mul r16, r18 ;multiply contents of a and b ;result 420 left in r1:r0 ,,, ADDITION .... There is no ADDI instruction, but there is a SUBI instruction but there is a trick * add an immediate value 4 to register r16 >> subi r16, -4 * you can even sub a negative character in avra!! >> subi temp, -'0' ; add '0' (48) to temp ADDING TWO BYTE NUMBERS .... use addc. DISPLAYING NUMBERS In any assembly language there is usually no immediately obvious way to display a number. This is always somewhat of a shock for a high level programmer. The usual strategy for displaying a number is to repeatedly divide by the current base (10 for decimal, 16 for hexadecimal), keep the remainders from each division, and then display those remainders in reverse order (each remainder corresponds to one "digit") In the case of hexadecimal numbers we can use a lookup table to convert the remainder (0-15) to a displayable digit (0-F). Also, division by 16 is considerably simpler than division by 10 in a microcontroller, because division by 16 can be accomplished with a series of right shifts of the number. Since most micros (including the avr chips) do not have a division opcode, division by 10 involves more hanky-panky. Because of this, our first attempt will be to display 1 byte in hexadecimal. DISPLAYING NUMBERS IN DECIMAL .... This is a little more complicated than hexadecimal because of the lack of a division opcode, but the conversion to a digit is simpler because we just add '0' to the value to get the asci value. * display one byte in decimal without division --------------------- .include "m328Pdef.inc" ; the atmega328p, the usual arduino micro .def temp = r20 .def A = r16 ; an accumulator .dseg ; ram data segment .cseg ; start of flash "rom" code segment .org 0x0000 jmp wakeup ; initialize an rs232 serial connection using onboard usart serialx: ldi temp, (1<> #define ubrr (FOSC/(16*BAUD))-1 SERIAL TERMINALS .... A serial terminal is a program which runs on a computer which allows us to communicate via rs232 with an arduino board that is connected in some way via serial. However, both USB and Bluetooth serial can also be used. This is very useful for testing programs and seeing what is actually happening on the MCU as the code is running. minicom, picocom, microcom, cutecom (easiest, graphical) If gibberish is returned from arduino, check that baud rates match. I used picocom because it seems to run well from the command line on mac osx and linux. REPL LOOP WITH SERIAL .... For example, change the frequency of a tone sent to a piezo speaker or a flashing led. The code below is a simple model for receiving single keypress commands over a serial connection and performing an action for each keypress. * turn on or off the onboard led depending on the key pressed ------ .nolist .include "m328Pdef.inc" .list .dseg ; ram data segment .cseg ; start of flash "rom" code segment .org 0 jmp start ; put isr vectors here ; receive a keystroke into the r17 register. key: wait: lds r16, UCSR0A ; get usart status info sbrs r16, RXC0 ; has a byte been received? rjmp wait ; if not just wait for one. lds r17, udr0 ; get received byte ret serial: ldi r16, 0 | 1<> Serial.begin(9600); baud rate is 9600. Serial monitor has to be the same or else gibberish is shown. Also make sure line endings are NL & CR serial.write() etc * basic sketch with serial ------------ void setup() { Serial.begin(9600); Serial.println("Hello !!!"); } void loop() { } ,,, * read one char from serial port and do something with it ------------------- void loop() { static int v = 0; if (Serial.available()) { char ch = Serial.read(); switch(ch) { case '0'...'9': v = ch - '0'; Serial.print("To position:"); Serial.println(v); break; case 's': break; case 'w': break; } } //Servo::refresh(); } ,,, TRANSMITTING .... By removing umsel0 code something positive happened. the little yellow tx light on the arduino uno lit up. good. wow its finally working, printing out yes yes yes the whole time. nice To test, open a serial terminal with device /dev/ttyACM0 or something similar and select baud 9600. See the vim section for a macro to run the 'picocom' serial monitor to test this code. * transmit a digit using usart ------------------ .include "m328Pdef.inc" .cseg ; initialise the stack at the top of ram ldi r21,high(ramend) out sph,r21 ldi r21,low(ramend) out spl,r21 ; initialise the rs232 serial connection ldi r16,(1< 0 here: rjmp here ; loop for ever buffer: .db 9, "hello itt" ; a counted string in the buffer ,,, RECEIVING RX .... This is not working 13 april 2020 and I am not sure why. * receive some chars from usart into a buffer ------------------ .include "m328Pdef.inc" .def counter = r20 .dseg buff: .byte 10 .cseg .org 0 jmp start start: ; set up stack ldi r21,high(ramend) out SPH,r21 ldi r21,low(ramend) out SPL,r21 rcall serialx ldi r17,'o' call transmit ldi r17,'k' call transmit rcall newline here: ; rcall accept ldi xl, low(buff) ; make x a pointer to buffer in sram ldi xh, high(buff) ; ldi r17, 'x' st X+, r17 ldi r17, 'y' st X+, r17 ldi r17, 'z' st X+, r17 ldi r17, 0 st X+, r17 rcall print halt: rjmp halt serialx: ldi r16, 0 | 1< 0 rcall newline ret newline: ldi r17, 10 call transmit ldi r17, 13 call transmit ret transmit: lds r16, UCSR0A ; usart control, status register, cant use sbis sbrs r16, UDRE0 ; is usart data register UDR empty? rjmp transmit ; if not, then just wait sts UDR0, r17 ; if empty tx next character ret ,,, * receiving using the usart serial interface ------------------ .include "m328Pdef.inc" .dseg .cseg message: .db "hello",0 ldi r21,high(ramend) out sph,r21 ldi r21,low(ramend) out spl,r21 ldi r16,(1< ICR1H ; Then the value in OCR1H/OCR1L is a percentage of 20000 ; ldi temp, high(20000) ; need a 50hz frequency with 16 Mhz ; sts ICR1H, temp ; ldi temp, low(20000) sts ICR1L, temp ; ; COM1A1 only one output ldi temp, 1< (eg OCR0). The timer/counter counts up to the compare value, then automatically resets to 0. Poll compare flag or write interrupt. pwm fast: "pulse width modulation, fast mode" put value in OCR0, OCR1, or OCR2. Start timer. The timer/counter counts up to the compare value, and toggles output pin automatically. Timer continues counting up until overflow. Resets to zero. Poll overflow flag or write ISR ("interrupt service routine"). Modify the OCR compare value to change duty cycle of square wave (time high versus time low). eg Can be used to emit sound samples or drive dc/servo motor pwm phase correct: "pulse width modulation, phase correct mode" Put a value in OCR Start the timer. Timer counts up to compare value, and toggles output pin automatically. Timer continues counting up until overflow. Timer starts to count *down*. Poll overflow flag or write ISR Modify OCR compare value to change duty cycle of square wave (time high versus time low). produces a symmetrical square wave GOTCHAS .... Big gotcha!! timer1 has memory mapped control registers, so you have to use STS to load data into them, but timer0 is does not, so use OUT!! Using STS with timer0 results in timer0 mysteriously doing nothing but using OUT with timer1 results in a compile error. The atmega32 (not 328) only has one control register TCCR0 but the atmega328 has 2. TCCR0A and TCCR0B. So the bits for selecting the different modes (normal, ctc, pwm) are different. This means that Mazidi code (written for atmega32) will not work without modification. Interrupt vector locations are different for different avrs. Need to use symbolic constant names for interrupt vectors and not numbers. TOGGLE MODE .... The simplest way to drive a piezo or blinking led is to use the output toggle with timer/counter 0,1,or2 in CTC (compare/match) mode. This reduces the code to 4 or 5 lines with no ISR because as soon as the timer/counter reaches compare/match value it clears the flag and toggles a certain bit. There are different toggle modes: toggle on match, set on match, and clear on match. The frequency of the wave generated below should be 39.800 kHz on a 16 MHz atmega328p. Frequency Calculation for code below: 200+1 clocks between timer matches (counts 0->200) Time(wave) = 2*201 (time-low + time-high) = 402 clocks Freq = waves/second Freq = 16000000/402 = 39800 Hz this is not working yet on arduino uno * generate a square wave on timer0 with a ctc mode toggle --------- .include "m328Pdef.inc" .def temp = r20 .cseg sbi ddrd, 3 ; ;sbi PORTD, 3 ldi r22, 200 out OCR0A, r22 ; set the match value ; in c TCCR0A = 1< Hz square wave on 16Mhz chip for Timer0 -------- .include "m328Pdef.inc" ; we use PORTB.5 which is arduino pin 13 with on board LED ; portb.0 is arduino pin 8 ; portd.0 is arduino pin 0 ; to create a 440 herz square wave we need to toggle at 880 herz ; (since a wave consists of a high and low part of equal duration) ; The calculation is: ; 16Mhz/1024 = 15625hz (frequency of timer with prescalar) ; (where 0 < x < 256, and x is an integer) ; .def zero = r2 .def temp = r18 .dseg .cseg .org 0x0 rjmp main .org OC0Aaddr ; timer0 compare match interrupt jmp timerMatch main: clr zero ; set register := 0 and dont change it! ldi temp, high(ramend) ; set up the stack (but not necessary since 2006) out SPH, temp ldi temp, low(ramend) out SPL, temp sbi DDRB, 5 ; make portB.5 output (arduino pin13 led) begin: ldi temp, 1< #include int main(void) { DDRB |= (1 << 5); // Set LED as output // Set the Timer Mode to CTC TCCR0A |= (1 << WGM01); // Set the value that you want to count to OCR0A = 0xF9; TIMSK0 |= (1 << OCIE0A); //Set the ISR COMPA vect sei(); //enable interrupts TCCR0B |= (1 << CS02); // set prescaler to 256 and start the timer while (1) { //main loop } } ISR (TIMER0_COMPA_vect) // timer0 overflow interrupt { //event to be exicuted every 4ms here PORTB ^= (1 << 0); // Toggle the LED PORTB ^= (1 << 5); // Toggle the LED } ,,, TOGGLE IN CTC MODE FOR TIMER0 .... OC0A = PD6, OC0B = PD5 So we connect our led here when toggling. In the automatic mode we can dispense with the interrupt altogether. So we dont need to enable interrupts, or write an ISR. In timer0 set automatic set/clear/toggle of output pin. COM0A1:COM0A0 bits in Timer0 control register TCCR1A I am not sure where to find output pins, but oc1a is arduino pin 9 output pin for OC0 = pb3 ?? * c code to initiate timer1 in output toggle mode >> TCCR0A |= (1 << COM0A0); * assembly code to enable timer0 output toggle ------- ldi temp, 1< 118 * Ctc output toggle with 262Hz wave on 16Mhz chip Timer0 -------- .include "m328Pdef.inc" ; connect LED/piezo to Arduino digital pin 6 ; look at pinout of atmega328 to see output pin of timer0 .def zero = r2 .def temp = r18 .dseg .cseg .org 0x0 rjmp main main: clr zero ; set register := 0 and dont change it! ldi temp, high(ramend) ; set up the stack out SPH, temp ldi temp, low(ramend) out SPL, temp sbi DDRD, 6 ; make portD.6 output (arduino pin6) begin: ldi temp, 118 ; gives Tone C4 (262 Hz) square wave on 16Mhz chip ; with /64 prescalar out OCR0A, temp ; load compare value (128) ldi temp, 1<> TCCR1A |= (1 << COM1A0); * assembly code to enable timer1 output toggle ------- ldi temp, 1<> CS12:11:10 = 0b101 (prescaler = 1024 the maximum) * drive a piezo at 400Hz with output toggle on timer1 -------- .include "m328Pdef.inc" ; working 2 august 2018 ; connect LED to PB1, arduino pin 9 ; portb.0 is arduino pin 8 ; portd.0 is arduino pin 0 .def zero = r2 .def temp = r18 .dseg .cseg .org 0x0 rjmp main main: clr zero ; set register := 0 and dont change it! ldi temp, high(ramend) ; set up the stack out SPH, temp ldi temp, low(ramend) out SPL, temp sbi DDRB, 1 ; make portB.1 output (arduino pin9) begin: ; actual delay time is 1/4 second. ldi temp, high(40-1) ; gives 2Hz square wave on 16Mhz chip ; with /64 prescalar sts OCR1AH, temp ; load compare value (62500) high byte ldi temp, low(40-1) sts OCR1AL, temp ; load compare value (62500) low byte ldi temp, 1<> sudo apt-get install arduino Then select the serial device and board (eg duemilenove) Then select file/preferences and check box for verbose info during upload * run arduino software with privelidges >> sudo arduino If you dont have the right priviledges the serial port will be greyed out. COMPILING WITH AVRA obtain a file called "m328Pdef.inc". This contains all the aliases or mnemonics for the atmega328. If you are using a different avr chip obtain the appropriate file. remove the semi colon from the line ;.device ATmega328P This allows the compiler to check things like memory ranges and allows use of high(ramend) etc, eg for initializing the stack. UPLOADING NOT IN SYNC THE DREADED ERROR .... The "not in sync" error prevents any new sketch or code being uploaded from the arduino ide or from avrdude etc. It can be caused by a multitude of things... eg To solve, first check the arduino board type. Then check usb cable connections. Then check rx/tx pins not connected. Then swap out usb cable for different one. Then swap out board for different. Etc. solution by elimination... * here is the dreaded not in sync error ----- avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 3 of 10: not in sync: resp=0x00 ,,,, !! causes of out of sync error: * serial port chosen badly. eg ttyS0 instead of ttyACM0 * bad usb cable: For example: for a "freetronics" uno compatible, a good usb cable will light on board led, turn power led blue, flash onboard led, then flash rx led once, then flash D13 onboard led. A bad cable may just turn on the power led blue, and turn on D13 led weakly. * bad ftdi cable connection * wrong "baud" rate (if manually set with avrdude) * wrong board type selected (eg uno when using pro mini) * something plugged into the rx/tx serial pins of the board, for example trying to upload a sketch by usb cable with an ftdi usb-to-uart connect plugged in * a "fried" board or atmega chip, the worst scenario. * ... COMMAND LINE UPLOAD .... The trick is to select verbose output in the arduino ide to see the appropriate switches for avrdude for a given arduino board etc. The following details how to upload code, eg a hex file to an arduino board from the unix command line. It doesnt seem to matter what usb socket the cable is plugged into at all. (at least not on my linux mint machine) * upload to duemilenove arduino >> sudo avrdude -v -v -v -v -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:hello.hex:i Notice here the device and baud rate are different. * upload to "uno" compatible freetronics arduino board >> sudo avrdude -p atmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:hello.hex:i If we use the wrong baud rate, avrdude gives messages, >> avrdude: stk500_recv(): programmer is not responding >> avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00 * the same without verbose output >> sudo avrdude -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:hello.hex:i * avrdude upload command for "uno" board connected via usb >> usr/share/arduino/hardware/tools/avrdude -C/usr/share/arduino/hardware/tools/avrdude.conf -v -v -v -v -patmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:/tmp/build4770305616360723053.tmp/Blink.cpp.hex:i * below is a typical avrdude command from the ide >> /usr/share/arduino/hardware/tools/avrdude -C/usr/share/arduino/hardware/tools/avrdude.conf -v -v -v -v -patmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:/tmp/build5695632546898703228.tmp/sketch_mar13a.cpp.hex:i == avrdude options .. -c the programmer .. -P the serial port NOP * code to make an avr microcontroller do nothing ------------- here: rjmp here ,,, SOUND http://avrprog.pbworks.com/w/page/9345379/AvrSound a good simple program to play midi file squeakily on a piezo buzzer ideas: sin wave calculation. ding sound using decaying amplitude hilo tech sound file. exponential decay of amplitude for nice ding. fast pwm. see arduino book "gunter" SAMPLED SOUND .... An internet comment "Just set up the AVR to do PWM at some multiple of the sample frequency and when the overlow interrupts occur reload the OCR for that timer's PWM with the next sample. As Bob suggests a 32K AVR will get you about 4 seconds of output (less because you need room for the player program itself)" The pcm format seems simple. 8 bit unsigned 8000 samples per second wav file hints: Use Timer1 and timer0 (timer0 for loading new samples?) use ctc compare/match with fast pwm. fast pulse width modulation 8 bit. prescaler clk/1=8MHz (?) PWM frequency = 8MHz / (255 + 1) = 31.25kHz Use interrupts In the isr(timer0_overflow.isr) just read the samples from the table and modify the "duty cycle" * use sox to convert to 8bit 8000 samples per second, mono >> sox old.wav -r 8000 -c 1 -b 8 new.wav The spoken word "microcontroller" is about 11K in 8bit 8000 samples per second. http://avrpcm.blogspot.com/2010/11/playing-8-bit-pcm-using-any-avr.html Good step by step for arduino pcm audio MUSIC .... unsigned int notes[] = /* Hap py Birth Day to you, Hap py birth day to C4 C4 D4 C4 F4 E4 C4 C4 D4 C4 G4 */ { 262, 262, 294, 262, 349, 330, 262, 262, 294, 262, 392, /* you, Hap py Birth Day dear xxxx Hap py birth F4 C4 C4 C5 A4 F4 E4 D4 B4b B4b A4 */ 349, 262, 262, 523, 440, 349, 330, 294, 466, 466, 440, /* day to you F4 G4 F4 */ 349, 392, 349 }; Multiply by 2 to get the octave above. Eg A880 is octave above A440 Use square waves (equal high low period) == some note frequencies in Herz .. D - 294 .. D# - 311 .. E - 330 .. F - 349 .. F# - 370 .. G - 392 .. G# - 415 .. A440 - 440 .. A# - 466 .. B - 494 .. OCR0A value for C4=262 Hz n+1 = 16MHz / (256 * 2 * T(freq) ) n+1 = 16000000 / (512 * 262) n <> 118 * make a Tone of frequency n using CTC toggle mode on Timer0 -------- .include "m328Pdef.inc" ; connect piezo to Arduino digital pin 6 ; look at pinout of atmega328 to see output pin of timer0, Channel A .def zero = r2 .def temp = r18 .def freq = r16 .dseg .cseg .org 0x0 rjmp main ; This procedure just sets up timer0 to toggle pinD.6 at the given ; frequency. It currently doesnt try to calculate the prescaler value, ; just the OC0A compare/match value. Frequency is in "freq" (r16) ; freq is less than 255 Hz (a limitation!) ; n+1 = 16000000 / (512 * Freq(Tone)) tone: ldi temp, 31250 ; 16000000/512 ; now divide temp by Frequency (16bit/8bit) or (16bit/16bit) ret main: clr zero ; set register := 0 and dont change it! ldi temp, high(ramend) ; set up the stack out SPH, temp ldi temp, low(ramend) out SPL, temp sbi DDRD, 6 ; make portD.6 output (arduino pin6) begin: ldi temp, 118 ; gives Tone C4 (262 Hz) square wave on 16Mhz chip ; with /64 prescalar out OCR0A, temp ; load compare value (128) ; wgm01:00=0b10 ("ctc" mode), CS02:00=0b100 (clock/256 prescaler) ldi temp, 1<100R(??)-->piezo-->Ground We can change the tone by changing the initial values loaded into r17 and r18 * buzz a piezo connected to arduino pin 13 then to ground ------------- .include "m328Pdef.inc" ldi r19,0b00100000 out DDRB,r19 again: ldi r19,0b00100000 out PortB,r19 clr r16 clr r17 ldi r17, 20 ldi r18, 1 here: dec r16 brne here dec r17 brne here dec r18 brne here ldi r19,0b00000000 out PortB,r19 ldi r18, 1 ldi r17, 20 far: dec r16 brne far dec r17 brne far dec r18 brne far rjmp again ,,, PIEZOS IN C LANGUAGE .... These may be modified Gunter Spanner examples pin10 -> piezo -> gnd (no resistor, polarity unimportant) * just stop the piezo !! its so tinny ---------- const int speaker = 10; void setup() { pinMode(speaker, OUTPUT); } void loop() { } ,,, * a simple alarm ---------- const int speaker = 10; void setup() { pinMode(speaker, OUTPUT); } void loop() { tone(speaker, 550, 450); delay(1000); } ,,, * science fiction sound ---------- const int speaker = 10; void setup() { pinMode(speaker, OUTPUT); } void loop() { for (int i = 200; i < 500; i += 10) { tone(speaker, i, 50); delay(20); } delay(1000); } ,,, * sin wav ---------- const int speaker = 9; const byte value[] = { 127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173, 176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240, 242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223, 221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78, 76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31, 33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124 }; void setup() { pinMode(speaker, OUTPUT); //tone(speaker, 550, 200); TCCR1A = 0b10000001; TCCR1B = 0b00001001; } void analogOut(byte val) { OCR1A = (val); } void loop() { for (unsigned int j = 0; j < 256; j++) { analogOut(value[j]); delayMicroseconds(10); } } ,,, POWER Batteries are rated by their mAh which means "milliamp hours" so an AA battery rated at 1000mAh could in theory produce 1 milli-amp for 1000 hours. If a project is powered by battery or solar panel, then its really a good idea to reduce power consumption. The arduino pro mini is good because it is lower power consumption than other arduinos. Arduino Uno draws 42mA. So would exhaust 1 AA battery in about 30 hours https://www.openhomeautomation.net/arduino-battery/ an excelent tutorial for powering an atmega328 on a breadboard with only 2 AA batteries and no voltage regulator. The atmega is powered at 3V with an external crystal. Also discusses ways to make the atmega328 use less power http://arduino.cc/en/main/standalone How to put the arduino on a bread-board powered with a ~9volt battery and using a 7805 voltage regulator to reduce voltage to 5V. Apparently this type of regulator wastes energy http://arduino.cc/en/Tutorial/ArduinoToBreadboard similar to above but without incircuit programming of the atmega chip. https://alanbmitchell.wordpress.com/2011/10/02/operate-arduino-for-year-from-batteries/ another low power article LOW POWER .... The atmega328 has 5 modes to reduce power consumption which can be activated through code. Ideas to reduce power consumption, use the on-chip oscillator and clock at 8Megahz. Use a 'switching' voltage regulator. run chip at 3V or 3.3V turn off unused stuff, like ADC etc CLOCK .... you can throttle the internal clock of the ATmega328 on the fly. see CLKPR. You can educe the internal 8MHz clock to 31.250kHz with two lines of code. SLEEP MODES .... http://www.engblaze.com/hush-little-microprocessor-avr-and-arduino-sleep-mode-basics/ a tutorial about avr sleep modes. BATTERY POWER .... CR2032 20mm coin cell battery runs at 3V and doesnt require a voltage regulator. but only has a rating of 200mAh so low power consumption is necessary. LR123 type lithium cell. high amp hours rating SOLAR POWER .... Powering an arduino or atmega with solar seems feasible put panels in series to increase voltage, and in parallel to increase amperage. http://www.instructables.com/id/Solar-powered-arduino-on-the-back-of-a-playing-car/ This is a very interesting blog on how to create a playing card sized solar panel to power an arduino CAR ROBOT BY BOB ELLIOTT The car robot is small, inexpensive and easy to modify. It uses an arduino pro mini, with a voltage regulator, hbridge and hc05 for bluetooth serial communication. On android, we are using blueterm 2 to drive the robot. First pair the robot in bluetooth settings (single slow flashing led on the hc05) and then connect in the blueterm screen (double slow flashing when successful) DEVICES OR BITS AND BOBS This section contains a list of handy stuff that can be used in arduino projects, with a short description of what the item does esp32 A complete board with wifi and bluetooth. more powerful than the atmega328 hc-05 connect to an arduino via bluetooth serial ws2811 12mm DC5V leds. Big individually addressable strips of leds, use fastled.io library cr2032 coin cell battery small battery with about 200mAH capacity DOCUMENTING FRITZING .... Fritzing is really great. It allows you to create great and pretty pictures of things you make, so that you will be able to make them again (when you unmake them and forget what you did). PROJECT IDEAS This is just a list of things that may be possible with an arduino analog meters using old voltage meters hsmag.cc/JBGDAz plant waterer hsmag.cc/HhpgXb Synthesizer using lookup tables for sin waves and connecting to speaker jack via aligator clips boldport pcb solder club AVRA avra is an assembler for use with avr chips. This is good if you dont like c or java. * to use register names you need an 'inc' file for the chip >> eg: /usr/share/avra/tn13def.inc avra doesnt seem to come with an inc file for the atmega328 but one should be findable * compile a file to a hex file >> avra hello.asm VIM AND AVR PROGRAMMING Vim can be used to compile, and upload to the arduino board code in a text file, or even code contained within another type of text document. Also, how to compile arduino c...? from the command line. Since I like to write notes to myself when I am learning about the avr chips (and other topics) I like to put code snippets inside of a document (such as this booklet). Having done that, I like to check that the code snippets actually work. Using vim, avra, avrdude, and picocom, this can be done with very few keystrokes (eg ",a" or ",u") * a mac osx style usb serial port >> /dev/tty.usbserial-A8008Ghy * map the key sequence ';av' to compile the whole file with avra >> map ;as :!avra % or >> map ;as :!avra % -o %:r.hex The second example is not necessary since by default avra creates a file called name.hex where the source file is 'name.asm' In the examples below, the complete assembly or c program is supposed to be within 2 'markers' within a document. The markers are '---' on a line by itself and ',,,' on a line by itself. These 2 markers mark the beginning and end of the assembly program within the document. Also the cursor needs to be between these 2 markers. * compile an avr assembly program within a document to 'test.hex' >> map ,a :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; * compile source and upload to the arduino 'duemilanove' board >> map ,u :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; sudo avrdude -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:test.hex:i * compile source and upload to the arduino 'uno' board >> map ,v :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; sudo avrdude -p atmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:test.hex:i * open a serial terminal to communicate with an arduino >> map ,s :! sudo picocom -b 9600 --echo /dev/ttyACM0 * and adjust the serial device according to the operating system >> map ,S :! sudo picocom -b 9600 --echo /dev/ttyUSB0 >> map ,S :! sudo picocom -b 9600 --echo /dev/tty.usbserial-A8008Ghy # eg: mac osx >> Type control-a control-x to exit picocom The following is useful for determining how much space is left within a boot file (which is limited to 512 bytes) * see how big a compiled file is without running it >> map ;ab :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; ls -la VIM WITH ARDUINO C .... We use arduino-mk with a Makefile in a subdirectory to automatically compile and upload a sketch which is written in arduino c with the "vim" text editor. The only tricky bit is automatically getting the arduino libraries into the Makefile. We may need to hand edit that file in some cases. the arduino-mk needs to be configured, see above in "setup" * extract an arduino c prog in a vim doc to a subdirectory "sketch" >> map ;ac :?^ *---?+1,/,,,/-1w ! ( cat - ) > sketch/test/test.ino; ls -la sketch/test * compile an arduino c prog in a vim doc to a subdirectory "sketch/test" >> map ;ac :?^ *---?+1,/,,,/-1w ! ( cat - ) > sketch/test/test.ino; cd sketch/test; make if there are other ".ino" files in sketch/test this will fail * compile an arduino c prog and upload to the board >> map ;au :?^ *---?+1,/,,,/-1w ! ( cat - ) > sketch/test/test.ino; cd sketch/test; make; make upload This last mapping is not quite working, programmer out of sync etc only small problem I think. The Makefile in the sketch/test subfolder has a tag that determines the type of arduino board (pro mini 328 3v3 etc) and the serial port. Adjust that makefile for different boards * look in doc folder for good examples of using arduino-mk >> /usr/share/doc/arduino-mk/examples/ * an example Makefile for arduino-mk bash uploading -------- #BOARD_TAG=pro328 BOARD_TAG=uno #ARDUINO_PORT = /dev/cu.usb* #ARDUINO_PORT = /dev/ttyUSB* # try a very general tty port, ttyUSB0, ttyS0 ARDUINO_PORT = /dev/tty* #ARDUINO_LIBS = Ethernet Ethernet/utility SPI #ARDUINO_LIBS = Servo include /usr/share/arduino/Arduino.mk ,,, DOCUMENT-NOTES: # this section contains "meta" information about the document # in what kind of state (good or bad) is this document document-quality: just begun doc-history: * 1 april 2020 revisiting this, with the idea to create more C and assembler examples, drive a servo with PWM in assembler and c, also maybe drive a brushless motor (which is the same as a servo with an ESC - electronic speed control). * 5 august 2018 discovered the timer0 bug. Use OUT not STS for Timer0 control registers... Trying to write a general "tone" proceedure. * 2 august 2018 wrote a working polling ctc timer1 example for the atmega328p * 1 august 2018 More work in assembly. Realised that best to look at C code for atmega328p and adapt to asm. Also, need to use bit names for control register bits to improve portability and readability. * 22 july 2018 struggling with jump to get the offset correct in the byte code section. Soon I will separate this into its own file and continue development there. This is a direct parallel to bumble.sf.net/books/osdev/os.asm (which is x86 code) * 20 july 2018 The opcode retrieval and indirect procedure call appear to be working. These are the essential elements for a bytecode system. using LPM and ICALL. Will convert to IJMP so that the data stack is easier to use * 19 July 2018 started writing a bytecode system. revisiting this to try to create a forth style bytecode system using avr assembly. I will use ideas from the x86 forth bytecode system at bumble.sf.net/books/osdev/os.asm * 8 November 2016 I combined the arduino book with the avr book, since my main focus now with the arduino will be programming in assembler. First I need to learn the avr architecture. * Wed 18 march 2015 document info and some vim and avr. Playing with the piezo speaker. * Mar 2015 started this book about programming avr microcontrollers such as the Atmega328 which is on some arduino boards. # who wrote this authors: mjbishop