% ------------------------------------------- % latex generated by: booktolatex.cgi % from source file : ../htdocs/books/arduino/arduino-book.txt % on: 20 April 2024, 6:30am % querystring: books/arduino/arduino-book.txt % document-root: /var/www/html % script-name: /cgi-bin/booktolatex.cgi % Server-name: bumble.sourceforge.net % Sed-script: booktolatex.sed % ------------------------------------------- \documentclass[a4paper,12pt]{article} \usepackage[margin=0.4cm,noheadfoot]{geometry} \usepackage{color} %% to use colours, use "xcolor" for more \usepackage{multicol} %% for multiple columns \usepackage{keystroke} %% for keyboard key images \usepackage[toc]{multitoc} %% for multi column table of contents \usepackage{tocloft} %% to customize the table of contents \setcounter{tocdepth}{2} %% only display 2 levels in the contents \setlength{\cftbeforesecskip}{0cm} %% make the toc more compact \usepackage{listings} %% for nice code listings %\lstset{language={}, \lstset{language=, %% define special comment delimiters '##(' and ')' moredelim=[s][\color{grey}\itshape\footnotesize\ttfamily]{~(}{)}, basicstyle=\ttfamily, %% fixed pitch font xleftmargin=1cm, %% margin on the left outside the frames breaklines=true, %% break long code lines breakatwhitespace=false, %% break long code lines anywhere breakindent=10pt, %% reduce the indent from 20pt to 10 postbreak=\mbox{{\color{blue}\small$\Rightarrow$\space}}, %% mark with arrow showstringspaces=false, %% dont show spaces within strings framerule=5pt, %% thickness of the frames rulecolor=\color{lightgrey}, frame=l} %% source code settings \usepackage{graphicx} %% to include images \usepackage{fancybox} %% boxes with rounded corners \usepackage{wrapfig} %% flow text around tables, images \usepackage{tabularx} %% change width of tables \usepackage[table]{xcolor} %% alternate row colour tables \usepackage{booktabs} %% for heavier rules in tables \usepackage[small,compact]{titlesec} %% sections more compact, less space \usepackage{enumitem} %% more compact and better lists \setlist{noitemsep} %% reduce list item spacing \usepackage{hyperref} %% make urls into hyperlinks \hypersetup{ %% add "pdftex," if only pdf output is required colorlinks=true, %% set up the colours for the hyperlinks linkcolor=black, %% internal document links black urlcolor=black, %% url links black filecolor=red, citecolor=red, bookmarks=true, pdfpagemode=UseOutlines} % define some colours to use \definecolor{lightgrey}{gray}{0.70} \definecolor{grey}{gray}{0.30} \titleformat{\section}[frame] %% titlesec: create framed section headings {\normalfont} {\filleft \footnotesize \enspace Section \thesection\enspace\enspace} {3pt} {\bfseries\itshape\filright} \title{} \author{} \date{28 May 2021, 3:53am} \setlength{\parindent}{0pt} % \setlength{\parskip}{1ex} % label lists with stars \renewcommand{\labelitemi}{$\star$} \begin{document} \centerline{\Large \bf } \medskip \begin{center} {\huge ``}\textit{}{\huge ''} \textsc{} \end{center} % ----------------------------------- % the toc should be 2 columns because of the \multitoc package \tableofcontents \emph{ * Arduino and Avr programming using mainly Assembler } This booklet contains information about my experiments with the arduino boards, such as ``duemilenove'', ``pro mini'', ``eleven'', ``uno''. These boards often use the avr atmega328P microcontroller, which is an 8bit harvard architecture uC. Its purpose is to investigate the avr architecture from an assembly programming and c language perpective. Generally my interest is often 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. \section{Setup} \emph{ Install arduino ide } \begin{lstlisting} apt-get install arduino \end{lstlisting} Arduino has to be run as sudo on my computer!! to be able to edit sketches. eg sudo arduino \emph{ Install avrdude and avra to compile , upload from command line. (not ide) } \begin{lstlisting} apt-get install avrdude avra \end{lstlisting} \emph{ Install fritzing to make pretty pictures of arduino circuits } \begin{lstlisting} apt-get install fritzing \end{lstlisting} 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. \emph{ Install arduino-mk for command line stuff with arduino } \emph{ Environment vars for mk } ------- ARDUINO\_DIR = /usr/share/arduino ARDMK\_DIR = /usr AVR\_TOOLS\_DIR = /usr ,,, \emph{ 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 ,,, \emph{ Show valid values for the boardtag variable. } \begin{lstlisting} make show_boards \end{lstlisting} 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 \begin{lstlisting} make \end{lstlisting} \emph{ Upload sketch } \begin{lstlisting} make upload \end{lstlisting} It worked!!! \subsection{Mac Osx Setup} \emph{ Install command line utilities on mac osx } \begin{lstlisting} brew install avra avrdude picocom \end{lstlisting} 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 \section{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. \emph{ 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 ,,,, \emph{ Turn off the on-board led on an arduino board } ------- .include ``m328Pdef.inc'' sbi DDRB, 5 cbi portb, 5 ; turn off the output pin ,,,, \emph{ 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 ,,,, \section{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. \emph{ Basic sketch with serial } \begin{lstlisting} void setup() { Serial.begin(9600); Serial.println("Hello !!!"); } void loop() { } \end{lstlisting} \subsection{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. \emph{ Blink the onboard led } \begin{lstlisting} // 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 } \end{lstlisting} \section{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? \section{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. \emph{ 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. \emph{ Make names better } \begin{lstlisting} rename 's/\.TXT$/.txt/' *.TXT \end{lstlisting} \emph{ Insert an example using vim } \begin{lstlisting} :r mazidi/Example11_5.asm \end{lstlisting} \subsection{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. \section{Goals} To learn how to call code using indirect addressing (ie using the x, y, z registers) so we can do something like \begin{lstlisting} call bx ; x86 architecture \end{lstlisting} 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 \section{Tools} \arrayrulecolor{gray} \begin{center} \begin{tabular}{ |rl| } \multicolumn{2}{c}{\textbf{ tools }} \\ \hline \texttt{ avra } & An assembler to use with arduino \\ \texttt{ avrdude } & A tool to program the arduino board \\ \hline \end{tabular} \end{center} \section{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... \emph{ Turn on the on-board led when a button is pressed } \begin{lstlisting} /* 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 \begin{lstlisting} // 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); } \end{lstlisting} \section{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. \section{Stepper Motors} Use phase correct pwm mode for stepper motors. \section{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. \subsection{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'. \emph{ 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 \section{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 \emph{ Control a servo motor } \begin{lstlisting} #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); } } \end{lstlisting} We can communicate to the program below with ... \begin{lstlisting} sudo picocom -b 9600 --echo /dev/ttyUSB0 \end{lstlisting} 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. \emph{ Control a servo motor with the keyboard and a serial connection } \begin{lstlisting} #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(); } } } \end{lstlisting} 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 ... \arrayrulecolor{gray} \begin{center} \begin{tabular}{ |rl| } \multicolumn{2}{c}{\textbf{ hc-05 onboard led information }} \\ \hline 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!! \hline \end{tabular} \end{center} 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 \emph{ In /etc/bluetooth/rfcomm.conf put, changing the mac address } \begin{lstlisting} rfcomm0 { bind no; device 98:D3:31:XX:XX:XX; # change this to your HC05 mac address channel 1; comment "Serial Port"; } \end{lstlisting} \emph{ Install cutecom a nice gui serial terminal } \begin{lstlisting} sudo apt-get install cutecom \end{lstlisting} Now pair the device using 'blueman', your device should appear with an option to connect via serial \emph{ Start the cutecom serial terminal } \begin{lstlisting} sudo cutecom \end{lstlisting} 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 (??). \emph{ Or use picocom with hc-05 connected to usb port } \begin{lstlisting} sudo picocom -b 38400 --echo /dev/ttyUSB0 \end{lstlisting} 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 \emph{ Minicom is another alternative } \begin{lstlisting} sudo minicom -b 38400 -D /dev/ttyUSB0 \end{lstlisting} 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 \begin{lstlisting} rx to tx, tx:rx, 3v3 to vcc, gnd to gnd, easy... \end{lstlisting} 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 \emph{ See if communication is happening with HC-05 bluetooth chip } \begin{lstlisting} AT \end{lstlisting} \emph{ Display the module working state } \begin{lstlisting} AT+STATE? \end{lstlisting} (eg: ``INITIALIZED'' ``READY'' ``PAIRABLE'' ``PAIRED'' ``INQUIRING'' ``CONNECTING'' ``CONNECTED'' ``DISCONNECTED'' ``NUKNOW'' ) \emph{ Show the bluetooth password or pin code } \begin{lstlisting} AT+PSWD or AT+PSWD? \end{lstlisting} \emph{ Change the bluetooth connection pin code } \begin{lstlisting} AT+PSWD= \end{lstlisting} \emph{ Check the current baud rate, stop bit and parity } \begin{lstlisting} AT+UART? \end{lstlisting} \emph{ Set the baud rate to 38400, no stop bit, no parity } \begin{lstlisting} AT+UART=38400,0,0 \end{lstlisting} \emph{ Set the baud rate to 115200, stop bit, with parity } \begin{lstlisting} AT+UART=115200,1,2 \end{lstlisting} \emph{ Get the hc-05 bluetooth address (not mac address) } \begin{lstlisting} AT+ADDR? \end{lstlisting} 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) \emph{ Set module name (default hc-05) } \begin{lstlisting} AT+NAME= \end{lstlisting} \emph{ Get bluetooth device name (address eg: 00:02:72:OD:22:24) } \begin{lstlisting} AT+RNAME? 0002,72,od2224\r\n \end{lstlisting} \emph{ Check connect mode (0=fixed address, 1=any address, 2=slave loop) } \begin{lstlisting} AT+ CMODE? \end{lstlisting} \emph{ Set fixed address 98:D3:31:20:93:CB } \begin{lstlisting} AT+BIND=98d3,31,2093cb \end{lstlisting} \subsection{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) \emph{ 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. \emph{ Set an HC-05 to slave mode } \begin{lstlisting} AT+ROLE=0 \end{lstlisting} \subsection{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 \begin{lstlisting} FATAL: cannot open /dev/rfcomm5: Device or resource busy \end{lstlisting} but why??? maybe the device is being used by some other process (cutecom ...) Make sure you are using \begin{lstlisting} sudo !!! \end{lstlisting} \begin{lstlisting} sudo minicom or sudo picocom or sudo cutecom \end{lstlisting} 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'' \section{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. \emph{ Initialize the stack, should be done before any ``call/rcall'' } \begin{lstlisting} .include "m328Pdef.inc" ldi r21, high(ramend) out sph, r2 ldi r20, low(ramend) out spl, r21 \end{lstlisting} \subsection{Software Stack} sometimes you need more than one stack, so you can make your own using the X and Y registers \emph{ Initialiase and use an 8bit software stack with X } ------ .nolist .include ``m328Pdef.inc'' ; the atmega328p, the usual arduino uC .list sbi DDRB, 5 ; make the led 13 output ldi xh, high(RAMEND-128) ldi xl, low(RAMEND-128) ldi r17, 123 st X+, r17 ; push value in r17 onto stack ld r16, -X ; pop value from stack into r16 cpi r16, 123 sbi portb, 5 ; turn on the led ,,,, \emph{ An avra macro to push a 16 bit value onto a software stack } \begin{lstlisting} .nolist .include "m328Pdef.inc" .list .macro pushw .message "no parameters specified" .endm .def a = r16 .def b = r17 .def temp = r18 .macro pushw_16 st X+, @0 st X+, @1 .endm ; push a single byte/char onto 16bit stack .macro pushb_8 st X+, @0 clr temp st X+, temp .endm .dseg stack: .byte 128 .cseg pushw [a:b] ,,,, 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 \begin{lstlisting} 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 \end{lstlisting} \emph{ Upload this hex file to the arduino (uno ?) with } \begin{lstlisting} avrdude -p m328p -c stk500v1 -b 57600 -P /dev/ttyUSB0 -U flash:w:hello.hex \end{lstlisting} 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 \emph{ Also try for arduino uno s etc } \begin{lstlisting} -b 115200 and the port -P /dev/ttyACM0 \end{lstlisting} \subsection{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). \section{Forth Ideas} See also the ``bytecode'' chapter. One way to start to build a system using forth ideas is just to implement a data stack and 2 words 'key' and 'emit'. once we have these 2 words, we can start to test them and then add new words. There are a number of important forth ideas. In a minimalistic system we can implement only a subset of these ideas in order to create a usable interactive system. For example, it may not be necessary to allow the definition of new words, or even to have a stack. Reverse counted name strings allow decompilation of bytecode- because each opcode in the table contains a link to the execution address, not the header address. Ideas: \emph{ Dictionary: } a linked list dictionary containing procedure names and possibly a help string for each procedure \emph{ Interactive: } Communicate with the user over a serial connection. Accept one word, or multiple words, and look up the word in the dictionary and executing it. The word could be just a fixed number of characters, to make the logic simpler. \emph{ Stack or stacks: } passing all parameters on the stack, so that the output of a procedure can be the input of another (eg KEY EMIT KEY EMIT which gets a character from the keyboard, puts it on the stack. Then ``emit'' takes that value from the stack and displays it on some device. In Forth the stack cell size is at least 16bits, but for a minimal system 8bits may be useful) \emph{ A procedure to display available procedures (eg WORDS) } \emph{ A procedure to display available procedures and a short } help string. \emph{ Allow numbers to be pushed onto the stack. This is useful because } it allows the user to interactively provide parameters for a procedure (eg ``5 blink'' would push 5 onto the stack and then call the procedure ``blink'' to flash the on-board led 5 times) \subsection{Minimalistic System Using Forth Ideas} This section will try to implement some or all of the above ideas warning!: when the compiler puts in extra bytes to make .db sections even, this may ...? break the linked-list dictionary. The code below is very useful because it can become a framework for a minimalistic interactive system. Also, need to be careful when doing pointer arithmetic and counted strings, because the compiler puts in extra zeros to make all addresses even. Now need to search for a command ``find'' and execute it. compare works Once find is working we have a useful interactive 8bit system. We then write a loop using ``get then find''. Then we can think about parsing multiple commands (words). Then think about getting numbers on the stack. For a limited system, no command help will be available, so the struct should be \emph{ Structure of header with help string } --- commands: .db ``help for this command/word'', 0 .dw commands commandh: .dw backlink ; pointer to prev header ``nameh'' .db 5, ``command'' command: etc ,,, If no help is in the dict, the the .dw commands link will be zero. The ``help'' word can check for zero. This means that we can run a preprocessor to remove help strings where memory is very limited. The ``type'' command will also check for zero and terminate printing. I would like to convert the system below to using a 16bit software data stack using eg X or Y as the top-of-stack pointer. This stack will grow towards high memory (and towards the return stack). This means that we dont have to juggle data on the atmega hardware stack Its useful to have some commands/words which just do the same thing as another. Another idea: A name dictionary, that contains pointers to all the wordlists. This allows namespaces. \emph{ Structure of an alias } --- ; no help header somecommand: .dw prevword ; pointer to prev header .db `` command'', 8 ; notice the 1st char is a space somecommandx: .dw linktounalias ,,, \emph{ Some forth ideas implemented, using a software data stack } -------- .ifdef 0 This version uses a software 16bit datastack with X as pointer with a procedure header with name and help string. will try to search for a procedure name and execute, can print command names and help Modify to use a software datastack, using X reg as the top of stack pointer. This should make parameter passing easier (no juggling) and also allows proceedures to be used as opcodes in a bytecode system. Also, change the help header struct as above. In the code below, for avra, all the macro parameters, must have [] around them. The code is working upto ``words''. need to write ``help'', compare, find etc to have a simple working system. also can add a bytecode table. \emph{ The line below can be used to preprocess the code to remove } the help string from each word, thus reducing the size of the program greatly \begin{lstlisting} sed '/h:$/,/n:$/{/\.db/d;/\.dw/s/\.dw.*/.dw 0/}' test.asm \end{lstlisting} 10 may 2020: 1134 bytes with help strings, 838 bytes with no help Bug on macosx with avra (got via brew install) ldi XH, high(stack) ldi XL, low(stack) st X, r16 ld r16, X This corrupts the data in r16 because the X pointer is not valid. This is because of how the ``stack'' label in the dseg is compiled. using ``high(RAMEND-128)'' etc works instead. But better is put .org 0 at the top of dseg. This seems to solve the problem. 15 may 2020 size: 2118 bytes work to do: get a number on the stack, allow multiple commands in ``interpret'' allow search the help strings a welcome message with stats (system size, number of words etc) respond to ``hi'' and hello. words I would like now, $>$in nextword (parse/word), ``contains'' to see if a word contains a substring. This was, will be developed in the following order. This is a useful order for bringing up a system on new hardware because it allows the code to be tested as it is created. emit, key, dup, get/accept, words, help, .hexchar, .hex, .stack, compare, find, interpret. 14 may 2020 wrote ``find'' and ``do''/execute/perform. Fixed ``do'' by dividing the execution pointer by 2. 13 may 2020 this is now outgrowing the book format. needs to become a separate app. Find is working but ``do''/perform/execute not yet. need to make a ``nextword'' command which scans the input buffer for the next word (like ``parse'' or ``word'' in forth) and an ``in'' input pointer. Also, make an input buffer history, so that up arrow recalls a command. Also, make a ``keycode'' word, that shows keycodes for words. 12 may 2020 wrote .stack (hex), put a namelist dictionary in code now 1728 bytes, but a lot of that is help strings next tasks, make sure compare works, then write ``find'' and ``do'' which executes the address on the stack. .endif .nolist .include ``m328Pdef.inc'' ; the atmega328p, the usual arduino uC .list ; opcode numbers as used in the opcode table. These are not currently ; used here but included to show how this system is compatible with ; a byte code virtual machine. .equ DUPx = 1 .equ DROPx = DUPx+1 .equ BSWAPx = DROPx+1 ; bswap - swap bytes in top stack item .equ EMITx = BSWAPx+1 .equ KEYx = EMITx+1 .equ STARx = KEYx+1 .def temp = r20 ; short term calculations .def templ = r21 ; 16bit temporary values .def temph = r22 ; .def counter = r16 ; counter loops etc .def flag = r17 ; true false etc .def param = r24 ; used for local parameters .def zero = r0 ; just contains zero ; these macros should make the code more readable when ; pushing and popping onto/from the software stack .macro pushw .message ``no parameters specified'' .endm .macro pushw\_16 st X+, @1 ; push the low byte first st X+, @0 .endm .macro pushb .message ``no parameters specified'' .endm ; push a single byte/char onto 16bit stack .macro pushb\_8 st X+, @0 st X+, zero ; push zero as the high byte .endm .macro pushc .message ``no parameters specified'' .endm ; push an immediate 8bit char value ; use with ``pushc '*''' .macro pushc\_i ldi temp, @0 st X+, temp st X+, zero ; push zero as the high byte .endm .macro popb .message ``no parameters specified'' .endm ; pop a single char/byte from the software stack .macro popb\_8 ld @0, -X ; discard top byte ld @0, -X .endm .macro popw .message ``no parameters specified'' .endm ; pop a 16bit value from the stack ; could make this safer by checking for stack underflow .macro popw\_16 ;sbiw XH:XL, 0 ; check for stack underflow ;breq underflow ld @0, -X ; high byte ld @1, -X .endm .dseg .org 0x100 ; a buffer for typed commands input: .byte 32 stack: ; a 64 cell 16bit data stack .byte 128 .cseg ; rom program memory (flash) .org 0 rjmp start start: ; assume auto initialisation of stack ; set up X as a software data stack pointer ldi xh, high(stack) ldi xl, low(stack) rcall serial clr zero ; keep zero register=0 here: rcall interpret halt: rjmp halt code: ; some bytecode. need to write an ``execute'' procedure ; to test this. zero exits from the proc .db KEYx, EMITx, KEYx, EMITx, KEYx, EMITx, 0, 0 ; pointers to opcode functions. optable: .dw 0, dup, drop, bswap, emit, key data: .db 6, ``ABCabc'' test: .db ``testing typing and counting'',0 ; initialize an rs232 serial connection using onboard usart ; no header because it cant be used interactively. serial: ldi r16, 1$<$$<$TXEN0 | 1$<$$<$RXEN0 ; enable transmit receive sts UCSR0B, r16 ldi r16, 1$<$$<$UCSZ01 | 1$<$$<$UCSZ00 sts UCSR0C, r16 ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ret ;--------- ; This is a dictionary of namespaces ; if we have a lot of words/commands in the system we can ; divide them up into namespaces. namelisth: .db ``a list of namespaces.'', 0 .dw namelisth namelistn: .dw 0 ; this is the first namespace, so no backlink .db ``namelist'', 8, 0 namelistp: .dw 0 ; a pointer to last name in this list corelisth: .db "standard forthish words like pop, push etc.", 0 .dw corelisth corelistn: .dw namelistp ; this is the first namespace, so no backlink .db ``corelist'', 8, 0 corelistp: .dw last ; a pointer to last word in the standard dict ; end of the namespace dictionary ;----------------- ; duplicate the top (16bit) stack item duph: .db "op: duplicate top item of stack (n - n n)",0 ; help string .dw duph dupn: .dw 0 ; 1st procedure, so no backlink .db ``dup'',3 ; reverse counts for bytecode decompilation dup: popw [temph:templ] ;adiw XH:XL, 2 ; adjust stack pointer pushw [temph:templ] pushw [temph:templ] ret droph: .db "op: drop top item of stack ( n - )",0 .dw droph dropn: .dw dup ; 1st procedure, so no backlink .db ``drop'',4,0 ; reverse count drop: sbiw XH:XL, 2 ; adjust stack pointer ; or popw [temph:templ] ret bswaph: .db "op: swap bytes in top stack item (n - m)",0 .dw bswaph bswapn: .dw drop .db ``bswap'',5 bswap: ld temph, -X ld templ, -X st X+, temph st X+, templ ret keyh: .db "op: receive one character from keyboard ( - c)",0 ; help string .dw keyh keyn: .dw bswap ; back-link in linked-list dict to execution address .db ``key'',3 key: wait: lds temp, UCSR0A ; get usart status info sbrs temp, RXC0 ; has a byte been received? rjmp wait ; if not just wait for one. lds temp, udr0 ; get received byte ; push the char onto the stack, high byte zero pushb [temp] ret keycodeh: .db "show keycodes for keys pressed. 'q' to quit. ( - )",0 ; help string .dw keycodeh keycoden: .dw key ; prev word .db ``keycode'',7 keycode: ; print the help for this word ldi ZH, high(keycodeh$<$$<$1) ldi ZL, low(keycodeh$<$$<$1) pushw [ZH:ZL] pushc [200] ; type until 0 pushc [0] ; type rom not ram rcall type rcall newline kcagain: pushc ['$>$'] rcall emit rcall key pushc [' '] rcall emit rcall dothex rcall newline rjmp kcagain ret ; using the stack to pass parameters is like a protocol ; transmit 1 character from the stack over serial connection ; here is the header for the procedure emith: .db "print 8bit character on top of stack ( c - )",0 ; help string .dw emith emitn: .dw keycode ; a link to previous word .db ``emit'',4,0 ; procedure name with count emit: transmit: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? rjmp transmit ; if not, then just wait (block) ;ld temp, -X ; discard high byte (zero) ;ld temp, -X ; char to print popb [temp] sts UDR0, temp ; if usart buffer empty tx character ret ; note! reverse name counts allow decompilation of bytecode! ; this also has a flag variable for ram/rom ; stack: address+1 count rom/ram-flag counth: .db "put count on stack, increments pointer (A flag - A+1 c flag) ",0 .dw counth countn: .dw emit ; backlink .db ``count'',5 ; counted name count: popb [flag] ; rom or ram popw [ZH:ZL] ; pointer to counted string cpi flag, 0 ; check if rom or sram to print breq countrom ; 0=rom, non-zero=sram ld temp, Z+ ; get the count rjmp countagain countrom: lpm temp, Z+ ; get the count countagain: pushw [ZH:ZL] ; push new pointer pushb [temp] ; push count 0-255, high byte zero pushb [flag] ; the rom/ram flag (also used by type) ret ; type has a pointer and count on the stack ; this also terminates printing if a zero byte is found. ; needs to have a parameter for sram and rom because different ; instructions are used ``lpm'' and ``ld''. Put a flag on the stack for this. ; stack: (rom/sram-address count flag - ) typeh: .db ``print a counted string '',0 ; help string .dw typeh typen: .dw count ; backlink .db ``type'',4,0 ; counted name type: popb [flag] ; boolean flag for sram/rom memory popb [counter] ; how many chars to print popw [ZH:ZL] ; pointer to string cpi counter, 0 ; if count is zero, exit breq endtype nextchar: cpi flag, 0 ; check if rom or sram to print breq typerom ; 0=rom, non-zero=sram ld temp, Z+ rjmp continue typerom: lpm temp, Z+ ; get next character continue: ; here check for zero and terminate (so prints "c" style string too) cpi temp, 0 breq endtype pushb [temp] rcall emit ; print the character dec counter brne nextchar ; if character left, loop endtype: ret ; gets max count characters from the user and puts them in the ram buffer ; stack (ram-address count - ) ; this could be expanded to get into rom memory with the spm command geth: .db "get n characters into buffer b (B n - )",0 .dw geth getn: .dw type ; backlink .db ``get'',3 ; counted name get: ; here the logic is ; set up a counter with the max characters ; set Y = X ; start looping: ; key, ; check if key is $<$enter$>$, if so exit ; store in X; ; loop ; calculate and store count (max-counter) ; ; cant use XH:XL because that is the stack pointer popb [counter] ; maximum characters mov param, counter ; save max chars for count calculation popw [YH:YL] movw ZH:ZL, YH:YL ; save pointer for storing count adiw YH:YL, 1 ; first byte is count nextgetkey: rcall key popb [temp] cpi temp, 13 ; check if $<$enter$>$ pressed breq exitget ; if so, finish st Y+, temp dec counter brne nextgetkey exitget: sub param, counter ; calculate char count movw YH:YL, ZH:ZL ; restore pointer to count byte st Y, param ret dothexcharh: .db "print low byte of t-o-s as unsigned hex (c - )",0 ; help string .dw dothexcharh dothexcharn: .dw get ; backlink .db ``.hexchar'',8,0 ; reverse counted name dothexchar: ldi counter, 2 ; get this from the stack popb [param] next: swap param ; print high nibble digit first, low 2nd mov temp, param andi temp, 0x0F ; remove high nibble in temp cpi temp, 10 brlo nothex subi temp, -7 ; Hex digit so add 8 to get ABCDEF nothex: subi temp, -'0' ; add '0' to temp pushb [temp] rcall emit dec counter brne next ret dothexh: .db "print top-of-stack as unsigned hex (n - )",0 ; help string .dw dothexh dothexn: .dw dothexchar ; backlink .db ``.hex'',4,0 ; reverse counted name, with zero pad dothex: rcall dup rcall bswap ; print high byte first rcall dothexchar rcall dothexchar ret dotstackh: .db "print the stack in hexadecimal ( - )",0 ; help string .dw dotstackh dotstackn: .dw dothexchar ; previous word in dict .db ``.s'',2,0 ; reverse counted name, with padded 0 dotstack: ldi YH, high(stack) ldi YL, low(stack) ; print a stack prefix pushc ['s'] rcall emit pushc ['$\}$'] rcall emit pushc [' '] rcall emit cp YL, XL ; compare low bytes cpc YH, XH ; compare high bytes (with carry) breq stackend ; nothing in stack, so exit stacknext: ; print a hex prefix pushc ['0'] rcall emit pushc ['x'] rcall emit ld templ, Y+ ld temph, Y+ pushw [temph:templ] rcall dothex pushc [' '] rcall emit cp YL, XL ; compare low bytes cpc YH, XH ; compare high bytes (with carry) brlo stacknext stackend: rcall newline ret ; an alias! just does what words does .dw 0 ; aliases dont have help commandsn: .dw dotstack ; prev word in dict .db ``comms '',6,0 ; counted alias name commands: .dw words ; the code had a strange bug because I was not doubling the pointer ; value correctly (eg add YL, YL; adc YH, YH). When I got into a ; certain memory range the proc went into an infinite loop ; this works without doubling the pointer but is unseemly ; .db low(typeh$<$$<$1), high(typeh$<$$<$1) ; one tricky thing here is the reverse counts can be upset ; by avra padding zeros in uneven numbers of bytes, have to skip ; the zero byte wordsh: .db ``show available commands ( - )'',0 ; help string .dw wordsh wordsn: .dw commands ; dict back link to alias .db ``words'',5 ; counted name words: ; pointer to the last proc in dictionary ldi ZH, high(last$<$$<$1) ldi ZL, low(last$<$$<$1) nextword: ; this is a gotcha, have to double the link, but not for icall lpm YL, Z+ ; dereference the pointer in Z reg lpm YH, Z ; yes! a correct way to double a 16 bit register lsl YL rol YH ; another way to double the pointer ; add YL, YL ; adc YH, YH sbiw YH:YL, 0 ; check if pointer is zero (top of dict) breq endwords sbiw YH:YL, 1 ; point to count byte, or padded zero movw ZH:ZL, YH:YL ; better to use ``counter'' not temp lpm temp, Z ; get the count cpi temp, 0 brne wordscontinue sbiw ZH:ZL, 1 ; adjust the pointer over the padded zero lpm temp, Z ; get the count wordscontinue: ; probably would be better to use a trailing space ; as an alias marker, because we rely on the dict back link ; being just infront of the name. ; here check if last char of name is space (which signifies an alias) ;lpm param, Z ; get first char ;cpi param, ' ' ; check if 1st char is space (alias) ;brne wordsnotspace ;pushc ['$>$'] ; print an alias marker ;rcall emit wordsnotspace: sub ZL, temp ; adjust pointer to start of name sbc ZH, zero ; 16bit subtraction pushw [ZH:ZL] ; push address for ``type'' rcall dup ; save the pointer pushb [temp] ; count on stack pushc [0] ; rom flag (not sram) for type rcall type ; print the proc name pushc [' '] rcall emit popw [ZH:ZL] ; restore pointer sbiw ZH:ZL, 2 ; point to back-link rjmp nextword endwords: rcall newline ret newlineh: .db ``print a newline'',0 .dw newlineh ; or just '0' for no help newlinen: .dw words .db ``newline'',7 newline: pushc [13] ; \textbackslash n char rcall emit pushc [10] ; \textbackslash r char rcall emit ret ; change this to check for a zero backlink, which indicates ; absence of help string. Help string will be zero terminated ; Help header will go before name header ; and will have struct ; commandh: ; .db ``help string for command'', 0 ; .dw commandh ; link to help header, this can be zero helph: .db ``show help for commands ( - )'',0 .dw helph helpn: .dw newline .db ``help'',4,0 ; proc name with rev count help: ; pointer to the last proc in dictionary ldi ZH, high(last$<$$<$1) ldi ZL, low(last$<$$<$1) nexthelp: lpm YL, Z+ ; dereference the pointer in Z reg lpm YH, Z ; must double pointer add YL, YL adc YH, YH sbiw YH:YL, 0 ; check if pointer is zero (top of dict) breq endhelp sbiw YH:YL, 1 ; point to count byte, or padded zero movw ZH:ZL, YH:YL lpm counter, Z ; get the count cpi counter, 0 brne helpcontinue sbiw ZH:ZL, 1 ; adjust the pointer over the padded zero lpm counter, Z ; get the count helpcontinue: sub ZL, counter ; adjust pointer to start of name sbc ZH, zero ; 16bit subtraction pushw [ZH:ZL] ; push address for ``type'' to use rcall dup ; save the pointer pushb [counter] ; count on stack pushc [0] ; rom flag (not sram) for type rcall type ; print the proc name rcall newline popw [ZH:ZL] ; restore pointer sbiw ZH:ZL, 2 ; point to back-link pushw [ZH:ZL] ; save pointer again sbiw ZH:ZL, 2 ; point to help link lpm YL, Z+ ; dereference the pointer in Z reg lpm YH, Z ; ; must double pointer add YL, YL adc YH, YH sbiw YH:YL, 0 ; check if pointer is zero (no help header) breq nohelpheader pushw [YH:YL] ; push help header point for ``type'' to use pushc [200] ; help string is zero terminated pushc [0] ; type rom not sram flag pushc [' '] ; print 2 spaces before the help rcall emit pushc [' '] rcall emit rcall type nohelpheader: rcall newline popw [ZH:ZL] ; restore pointer to next command in dict rjmp nexthelp endhelp: ret ; this may need 2 more params on the stack ram/rom flags ? ; stack: ( A B flag flag ) ; compares a counted string is program memory with one in sram ; this will be used by find, pointers to strings are on the stack ; rom string on top of stack, ram string pointer underneath ; working. compareh: .db "compares n chars of 2 strings in rom and ram (A.sram n A.rom - flag)",0 .dw compareh comparen: .dw help .db ``compare'',7 compare: popw [ZH:ZL] ; pointer to counted string in rom popb [counter] ; how many chars to compare popw [YH:YL] ; pointer to string in sram ;dont compare count ! nextcompare: ; use string count to count the loop lpm temp, Z+ ld param, Y+ cp temp, param brne notequal dec counter ; loop until no more chars brne nextcompare equal: pushc [1] ; success flag on stack ret notequal: pushc [0] ; failure flag ret ; this word allows us to search names and the help strings to find ; a word/command. Or return the location and length or 0,0 containsh: .db "check if buffer A.prog contain string A.data (A.data n A.prog m - flag)",0 .dw containsh containsn: .dw compare .db ``contains'',8,0 ; reverse count with zero pad contains: ret ; finds a command and executes it ; this uses a similar process to ``help'' and words, looping ; through the dictionary. ; copy ``words'' code ; should take address and count as parameters findh: .db "find a command and execute it (A n - xt|0) ",13,10 .db "returns 0 if name not found, else exec address",0 .dw findh findn: .dw contains .db ``find'',4,0 ; procedure name with count find: ; pointer to the last proc in dictionary ldi ZH, high(last$<$$<$1) ldi ZL, low(last$<$$<$1) popb [param] ; get the name count popw [temph:templ] ; pointer to name buffer nextfind: lpm YL, Z+ ; dereference the pointer in Z reg lpm YH, Z ; must double pointer add YL, YL adc YH, YH sbiw YH:YL, 0 ; check if pointer is zero (top of dict) breq endfind pushw [YH:YL] ; save pointer to exec address in case sbiw YH:YL, 1 ; point to count byte, or padded zero movw ZH:ZL, YH:YL ; better to use ``counter'' not temp lpm temp, Z ; get the count cpi temp, 0 brne findcontinue sbiw ZH:ZL, 1 ; adjust the pointer over the padded zero lpm temp, Z ; get the count findcontinue: sub ZL, temp ; adjust pointer to start of name sbc ZH, zero ; 16bit subtraction cp temp, param ; compare 2 counts brne findagain ; if counts not equal, try next word in dict ; check for alias here findnotspace: pushw [ZH:ZL] ; save Z before compare, because it mods it pushb [param] ; save param too ; set up stack for ``compare'' (P.sram n P.rom - 0/1) pushw [temph:templ] pushb [temp] ; push the count pushw [ZH:ZL] ; pointer to name in rom ;pushw [temph:templ] ;rcall dotstack ;popw [temph:templ] rcall compare popb [temp] ; get result of compare popb [param] popw [ZH:ZL] cpi temp, 0 breq findagain ; if not same, try next word ; success! exec address already on stack ; pushc [1] ret findagain: rcall drop ; get rid of pointer to exec address sbiw ZH:ZL, 2 ; point to back-link rjmp nextfind endfind: pushc [0] ; word not found flag ret starh: .db ``print a star''",0 .dw starh starn: .dw find .db ``star'',4,0 star: pushc ['*'] rcall emit ret ; this is forth ``execute'' or ``perform'' ; working! after division by 2! doh: .db "execute address on stack (xp - )",0 .dw doh don: .dw star .db ``do'',2,0 do: popw [ZH:ZL] ; divide address by 2 for icall lsr ZH ror ZL icall ret interpreth: .db "accept 1 command and execute it ( - )",0 .dw interpreth interpretn: .dw do .db ``interpret'',9 interpret: interpagain: ; this is the skeleton code for ``interpret'' which ; just interprets 1 word. pushc ['$>$'] ; print a prompt rcall emit ldi ZH, high(input) ldi ZL, low(input) pushw [ZH:ZL] ; save for find pushw [ZH:ZL] ; where to put in sram pushc [32] ; maximum number of chars rcall get pushc [1] ; type sram not rom rcall count call drop ;rcall dotstack rcall find ;rcall dotstack rcall do rjmp interpagain ; never exits ret ; a pointer to the last word in the dictionary last: .dw do ,,, \emph{ An 8 bit implementation } -------- .ifdef 0 uses an 8 bit stack with a procedure header with name and help string. will try to search for a procedure name and execute, can print command names and help Modify to use a software datastack, using X reg as the top of stack pointer. This should make parameter passing easier (no juggling) and also allows proceedures to be used as opcodes in a bytecode system. Also, change the help header struct as above. .endif .nolist .include ``m328Pdef.inc'' ; the atmega328p, the usual arduino uC .list .def temp = r20 ; short term calculations .def counter = r16 ; counter loops etc .def param = r24 ; used for local parameters .def zero = r0 ; just contains zero .dseg ; a 20 byte buffer for typed commands input: .byte 30 stack: .byte 128 .cseg ; rom program memory (flash) .org 0 jmp start start: ; assume auto initialisation of stack ; test dup key and emit ; set up X as a software data stack pointer ldi xh, high(stack) ldi xl, low(stack) rcall serial here: clr zero ; keep zero register=0 ldi temp, '[' ; print a prompt push temp rcall emit rcall get rcall newline ; rjmp nope ; test compare ldi ZH, high(data$<$$<$1) ldi ZL, low(data$<$$<$1) ldi XH, high(input) ldi XL, low(input) push XL push XH push ZL push ZH rcall compare ; check the result pop temp cpi temp, 1 ; if not the same then skip brne nope ldi temp, 'Y' push temp rcall emit nope: rcall words rcall newline rcall help ldi temp, '.' push temp rcall emit rcall newline rjmp here data: .db 4, ``what'' ; initialize an rs232 serial connection using onboard usart ; no header because it cant be used interactively. serial: ldi r16, 1$<$$<$TXEN0 | 1$<$$<$RXEN0 ; enable transmit receive sts UCSR0B, r16 ldi r16, 1$<$$<$UCSZ01 | 1$<$$<$UCSZ00 sts UCSR0C, r16 ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ret ; duplicate the top (8bit) stack item duph: .dw 0 ; 1st procedure, so no backlink .db 3, ``dup'' ; command name dups: .db (dup-dups-1)*2 .db ``duplicate top item of stack'' ; help string dup: pop ZH ; return address into Z reg pop ZL pop temp ; top-of-stack push temp ; push t-o-s twice to duplicate push temp ijmp ; a trick! because ret address is in Z reg keyh: .dw duph ; a back-link in the linked-list dict .db 3, ``key'' ; keys: .db (key-keys-1)*2 .db "receive one character from keyboard" ; help string key: wait: lds temp, UCSR0A ; get usart status info sbrs temp, RXC0 ; has a byte been received? rjmp wait ; if not just wait for one. lds temp, udr0 ; get received byte pop ZH pop ZL ; push the char onto the stack push temp ijmp ; equivalent to ``push zl;push zh; ret'' ; using the stack to pass parameters is like a protocol ; transmit 1 character in r17 over serial connection ; here is the header for the procedure emith: .dw keyh ; a link to previous word .db 4, ``emit'' ; procedure name with count emits: ; !careful, the compiler pads extra zero in uneven addresses ; so this count arithmetic may not work. .db (emit-emits-1)*2 .db "print character on top of stack" ; help string emit: transmit: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? rjmp transmit ; if not, then just wait (block) ; maybe better not to use Z reg, because lpm and spm ; can only use Z and Z+ pop ZH ; high byte of ret address into r31 pop ZL ; low byte of ret address in r30 pop temp ; get character to transmit sts UDR0, temp ; if empty tx character ijmp ; return to caller (return address already in Z reg) counth: .dw emith ; backlink .db 5, ``count'' ; counted name counts: .db (count-counts-1)*2 .db "puts count on stack and increments pointer" count: pop YH ; return address of proc pop YL ; low byte pop ZH ; high byte of pointer to counted string pop ZL ; low byte lpm temp, Z+ ; get count push ZL push ZH push temp push YL ; restore return address push YH ; ret ; actually type doesnt work like this. It should have a count and a ; pointer on the stack. Then it prints ; Also, this type needs to also work for eeprom and sram. So it ; needs to check what memory it is dealing with. In sram it uses ; ``ld'' instruction but in rom it uses ``lpm'' etc. ; this also needs to terminate printing if a zero byte is found. typeh: .dw counth ; backlink .db 4, ``type'' ; counted name types: .db (type-types-1)*2 .db ``print a counted string'' ; help string type: pop YH ; return address of proc pop YL ; low byte pop ZH ; high byte of pointer to string pop ZL ; low byte push YL ; restore return address push YH ; lpm counter, Z+ ; get count cpi counter, 0 ; if count is zero, exit breq endtype nextchar: lpm temp, Z+ ; get next character ; here check for zero and terminate (so prints "c" style string too) cpi temp, 0 breq endtype push ZL ; emit uses Z reg so must be saved (or change emit) push ZH push temp rcall emit ; print the character pop ZH pop ZL dec counter brne nextchar ; if character left, loop endtype: ret ; gets max 10 characters from the user and puts them in the buffer geth: .dw typeh ; backlink .db 3, ``get'' ; counted name gets: .db (get-gets-1)*2 .db "get some characters into buffer" get: ; here the logic is ; set up a counter with the max characters ; set Y = X ; start looping: ; key, ; check if key is $<$enter$>$, if so exit ; store in X; ; loop ; calculate and store count (max-counter) ; ldi XL, low(input) ldi XH, high(input) movw YH:YL, XH:XL ; save pointer for storing count adiw XH:XL, 1 ; first byte is count ldi counter, 10 ; maximum characters nextgetkey: rcall key pop temp cpi temp, 13 ; check if $<$enter$>$ pressed breq exitget ; if so, finish st X+, temp dec counter brne nextgetkey exitget: ldi temp, 10 ; calculate and store the char count sub temp, counter st Y, temp ret ; the code had a strange bug because I was not doubling the pointer ; value correctly (eg add YL, YL; adc YH, YH). When I got into a ; certain memory range the proc went into an infinite loop ; this works without doubling the pointer but is unseemly ; .db low(typeh$<$$<$1), high(typeh$<$$<$1) wordsh: .dw geth ; back link .db 5,``words'' ; counted name wordss: .db (words-wordss-1)*2 ; calculated count of help string .db ``show available commands'' ; help string words: ; pointer to the last proc in dictionary ldi ZH, high(last$<$$<$1) ldi ZL, low(last$<$$<$1) nextword: ; this is a gotcha, have to double the link, but not for icall lpm YL, Z+ ; dereference the pointer in Z reg lpm YH, Z ; yes! a correct way to double a 16 bit register lsl YL rol YH ; another way to double the pointer ; add YL, YL ; adc YH, YH sbiw YH:YL, 0 ; check if pointer is zero (top of dict) breq endwords push YL ; save a copy of the pointer push YH adiw YH:YL, 2 ; point to counted name push YL ; put the pointer on the stack push YH rcall type ; print the proc name ldi temp, ' ' push temp rcall emit pop ZH ; restore pointer to next word in dict pop ZL rjmp nextword endwords: ret newlineh: .dw wordsh .db 7, ``newline'' newlines: .db (newline-newlines-1)*2 ; calculated count .db ``print a newline'' ; help string newline: ldi temp, 13 push temp rcall emit ldi temp, 10 push temp rcall emit ret ; change this to check for a zero backlink, which indicates ; absence of help string. Help string will be zero terminated ; Help header will go before name header ; and will have struct ; commands: ; .db ``help string for command'', 0 ; .dw commands ; link to help header, this can be zero helph: .dw newlineh .db 4, ``help'' ; procedure name with count helps: .db (help-helps-1)*2 .db ``show help for commands'' help: ; ``last'' is pointer to the last command in dictionary ldi ZH, high(last$<$$<$1) ldi ZL, low(last$<$$<$1) nexthelp: lpm YL, Z+ ; dereference the pointer in Z reg lpm YH, Z ; one way to double a 16 bit register. This has to be doubled ; because rom is word (16bit) addressed add YL, YL adc YH, YH sbiw YH:YL, 0 ; check if pointer is zero (top of dict) breq endhelp push YL ; save a copy of the pointer push YH adiw YH:YL, 2 ; point to counted name push YL ; put the pointer on the stack push YH push YL ; save another copy push YH rcall type ; print the proc name ldi temp, ':' ; print a : push temp rcall emit rcall newline ldi temp, ' ' ; print a space push temp rcall emit pop ZH ; get pointer to name count byte pop ZL lpm temp, Z ; get count inc temp ; +1 ; but here we need logic to see if we are at an odd ; numbered address, and if so, increment. Or we could check for zero. add ZL, temp ; add count to Z adc ZH, zero ; the method below is not really correct, should use an odd/even test ; check if zero byte (extra padded) lpm temp, Z ; if this is a zero byte, we assume its been padded cpi temp, 0 ; by the compiler brne notzero adiw ZH:ZL, 1 ; skip over the zero byte notzero: ; maybe another way, not working ; sbrc temp,0 ; if lsb is 0 skip next increment instruction ; adiw ZH:ZL, 1 ; skip over the padded zero byte push ZL push ZH ; push the pointer for ``type'' to use rcall type ; print the command help ldi temp, '.' push temp rcall emit rcall newline pop ZH ; restore back pointer to next word in dict pop ZL rjmp nexthelp endhelp: ;ldi temp, '\#' ;push temp ;rcall emit ret ; compares a counted string is program memory with one in sram ; this will be used by find, pointers to strings are on the stack ; rom string on top of stack, ram string pointer underneath ; working. compareh: .dw helph .db 7, ``compare'' compares: .db (compare-compares-1)*2 .db "compares 2 counted strings in rom and ram" compare: pop YH ; save ret address pop YL pop ZH ; pointer to counted string in rom pop ZL pop XH ; pointer to counted string in ram pop XL lpm temp, Z+ ; load string counts and compare ld counter, X+ ; use the string count to count the loop cp temp, counter brne notequal nextcompare: lpm temp, Z+ ld param, X+ cp temp, param brne notequal dec counter ; loop until no more chars brne nextcompare equal: ldi temp, 1 ; success flag on stack push temp push YL ; restore address push YH ret notequal: ldi temp, 0 ; failure flag push temp push YL ; restore address push YH ret ; finds a command an executes it ; this uses a similar process to ``help'' and words, looping ; through the dictionary. findh: .dw compareh .db 4, ``find'' ; procedure name with count finds: .db (find-finds-1)*2 .db ``find a comm and execute it'' find: ; ``last'' is pointer to the last command in dictionary ldi ZH, high(last$<$$<$1) ldi ZL, low(last$<$$<$1) nextfind: lpm YL, Z+ ; dereference the pointer in Z reg lpm YH, Z ; one way to double a 16 bit register. This has to be double ; because rom is word (16bit) addressed add YL, YL adc YH, YH sbiw YH:YL, 0 ; check if pointer is zero (top of dict) breq endfind push YL ; save a copy of the pointer push YH adiw YH:YL, 2 ; point to counted name push YL ; put the pointer on the stack push YH push YL ; save another copy push YH ; check here if name is the same as the buffer rcall type ; print the proc name pop ZH ; get pointer to name count byte pop ZL lpm temp, Z ; get count inc temp ; +1 add ZL, temp ; add count to Z adc ZH, zero ; but here we need logic to see if we are at an odd ; numbered address, and if so, increment. Or we could check for zero. ; the method below is not really correct, should use an odd/even test ; check if zero byte (extra padded) lpm temp, Z ; if this is a zero byte, we assume its been padded cpi temp, 0 ; by the compiler brne notazero adiw ZH:ZL, 1 ; skip over the zero byte notazero: ; maybe another way, not working ; sbrc temp,0 ; if lsb is 0 skip next increment instruction ; adiw ZH:ZL, 1 ; skip over the padded zero byte push ZL push ZH ; push the pointer for ``type'' to use rcall type ; print the command help ldi temp, '.' push temp rcall emit rcall newline pop ZH ; restore back pointer to next word in dict pop ZL rjmp nextfind endfind: ret ; a pointer to the last word in the dictionary last: .dw findh ,,, \section{Byte Code And Virtual Machines} An important idea!: use the atmega ``hardware'' stack for a ``return'' stack (in forth terminology) Then create a new, software stack (16bit) for the data stack. This has lots of advantages; we dont need to juggle data on the hardware stack, we can right commands/words as normal call/ret proceedures, and test them like normal procs. I can then convert that to a bytecode system. One step on the path to implementing a portable forth system, may be to develop a ``byte-code'' system. This means that opcodes (0-255) execute specific functions which create 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. A byte code forth-like virtual machine needs the following instructions - 2 stacks: a general data stack and a return stack, where procedure addresses are stored - stack manipulation eg + dup drop etc - ``exec'' or ``call'' to call a virtual (byte code) procedure equivalent to ``call'' or ``icall'' on the atmega328 - ``return'' or ``exit'' to exit from the virtual proceedure equivalent to ``ret'' on a real uC - unconditional jumps - conditional jumps eg ``jumpz'' , jump if the top stack item is zero equivalent to ``breq'' or ``brne'' on the 328P 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. The code below uses lots of ideas from the FORTH language, eg a linked-list ``dictionary'' of procedures where each item in the dictionary contains the procedure name and a link back to the previous procedure (or 0 if it is the 1st procedure). This is really useful because it allows procedures to be used interactively. But to make the system better, we need a procedure that looks up a name and executes it. Also, it would be nice to have a way to get numbers onto the stack, this means that we can run procedures with parameters. Also, it would be good to have a ``help'' function which displays a list of procedures and what they do. This would provide a minimal interactive read-only forth-like system and would be very handy for communicating with a microcontroller over a serial connection. \emph{ A basic byte code system in avr assembly } ------ .nolist .include ``m328Pdef.inc'' .list .def pch = r23 .def pcl = r22 .def temp = r16 .def zero = r1 .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 0 jmp start ; ISR vectors here start: ; assume stack auto initialised rcall serialx ; set up serial interface clr zero ; again: ldi zh, high(emitx) ldi zl, low(emitx) icall jmp again ; again: call keyx call emitx jmp again ; r23:r22 PC program counter ; put all this in an exec function. ; lpm=load program memory, must use Z register ldi r23, high(code$<$$<$1) ldi r22, low(code$<$$<$1) nextopcode: ; retrieve and save PC program counter to r23:r22 movw ZH:ZL, r23:r22 ; get ip lpm r20, Z+ movw r23:r22, ZH:ZL ; save ip rjmp checkzero ; just for debugging opcode retrieval digit: lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp digit ; if not, then just wait ldi r17, '0' ; convert opcode to asci digit mov r16, r20 ; assumes 0 $<$ opcode $<$ 10 add r16, r17 sts UDR0, r16 ; if empty tx next character checkzero: ; halt if opcode is zero cpi r20, 0 breq goodbye ldi ZH, high(optable$<$$<$1) ldi ZL, low(optable$<$$<$1) add ZL, r20 ; opcode number (need to double) adc ZH, zero add ZL, r20 ; adc ZH, zero lpm r16, Z+ lpm r17, Z+ movw ZH:ZL, r17:r16 ; ;ldi ZH, high(starx$<$$<$1) ;ldi ZL, low(starx$<$$<$1) icall rjmp nextopcode goodbye: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? rjmp goodbye ; if not, then just wait ldi temp, '!' sts UDR0, temp ; if empty tx next character here: rjmp here code: ; some bytecode to test the system .db STAR, KEY, EMIT, KEY, EMIT, KEY, EMIT, 0 ; pointers to opcode functions. optable: .dw 0, dupx, emitx, keyx, starx ; duplicates the top stack item duph: .dw 0 ; this is 1st proc, so backlink=0 .db 3, ``dup'' ; the proc name + count dupx: ; here need to juggle the stack because the top 2 items ; are the return address from the function pop ZH ; return address into Z reg pop ZL pop r16 ; low byte of top of stack pop r17 ; high byte, but reversed?? push r17 push r16 push r17 push r16 ijmp ; a trick! because ret address is in Z reg emith: .dw dupx ; a link back to previous word .db 4, ``emit'' ; the proc name + count 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 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 starh: .dw 0 .db 4, ``star'' 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 ; set up the usart serial interface for the atmega328 serialx: ldi r16, 1$<$$<$TXEN0 | 1$<$$<$RXEN0 ; enable transmit receive sts UCSR0B, r16 ldi r16, 1$<$$<$UCSZ01 | 1$<$$<$UCSZ00 sts UCSR0C, r16 ldi r16,0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L,r16 ldi r16,0x00 ; the baud rate, 9600 sts UBRR0H,r16 ret ,,, The purpose of using the JMP or IJMP instruction to run procedures is to free up the (8 bit) avr data stack for passing parameters to and from procedures. If we use CALL or ICALL, then we need to 'juggle' the procedure return address. Another improvement is to 'cache' the top (16 bit) element of the stack in a register pair. Eg the register pair just below the X register The code below really needs an ``fcall'' or ``exec'' function which can execute a series of byte codes. Also needs an ``exit/return'' to exit from a virtual proc. Still need ``call'', ``return'' \emph{ A bytecode system using jmp not call } ------ .nolist .include ``m328Pdef.inc'' .list .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 ; execx: jmp nextopcode ; duplicates the top stack item duph: dupx: pop r16 ; low byte of top of stack (reversed??) pop r17 ; high byte of top-of-stack push r17 push r16 push r17 push r16 jmp nextopcode emith: .dw dupx ; the classic dictionary backlink .db ``emit'', 4 emitx: waitagain: lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp waitagain ; if not, then just wait pop r17 ; low byte (character) pop r18 ; high byte (should be zero) sts UDR0, r17 ; if empty tx character 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 ldi r16, 0 push r16 ; high byte push r17 ; low byte (character) 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 jmp nextopcode ; initialize an rs232 serial connection using onboard usart ; this cant really be used interactively so is not a forth-style command ; with header etc serialx: ldi r16, 1$<$$<$TXEN0 | 1$<$$<$RXEN0 ; enable transmit receive sts UCSR0B, r16 ldi r16, 1$<$$<$UCSZ01 | 1$<$$<$UCSZ00 sts UCSR0C, r16 ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ret code: ; some bytecode to test the system .db STAR, STAR, KEY, DUP, EMIT, EMIT .db KEY, DUP, EMIT, EMIT, 0 wakeup: ; initialise the stack at the top of ram ldi r21, high(ramend) out sph, r21 ldi r21, low(ramend) out spl, r21 call serialx ; set up serial interface ; r25:r24 could hold top of stack ; r23:r22 PC program counter ; put all this in an exec function. ; lpm=load program memory, must use Z register ldi r23, high(code$<$$<$1) ldi r22, low(code$<$$<$1) nextopcode: ; retrieve and save PC program counter to r23:r22 movw ZH:ZL, r23:r22 ; get ip lpm r20, Z+ movw r23:r22, ZH:ZL ; save ip ; rcall digit ; checkzero: ; halt if opcode is zero cpi r20, 0 breq goodbye ; this looks wrong: check! need to do ``add'' then ``adc'' on ZH:ZL ldi ZH, high(optable$<$$<$1) ldi ZL, low(optable$<$$<$1) add ZL, r20 ; opcode number (need to double?) add ZL, r20 ; ; try the following to double opcode ; add ZL, r20 ; adc ZH, 0 ; add ZL, r20 ; adc ZH, 0 ; load the function address into Z via r17:r16 lpm r16, Z+ lpm r17, Z+ movw ZH:ZL, r17:r16 ; ijmp ; never executed I think ; jmp nextopcode goodbye: lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp goodbye ; if not, then just wait ldi r20, '.' sts UDR0, r20 ; if empty tx character here: rjmp here ; just for debugging opcode retrieval digit: lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp digit ; if not, then just wait ldi r17, '0' ; convert opcode to asci digit mov r16, r20 ; assumes 0 $<$ opcode $<$ 10 add r16, r17 sts UDR0, r16 ; if empty tx next character ret ,,, We can use DEFs to make this more readable. eg: .def pch = r23 ; (program counter high byte) .def pcl = r22 .def tosh = r25 ; (top-of-stack high byte) .def tosl = r24 The system below, if it starts to work, edges towards being a useful ``repl'' system, where the user can enter commands over the serial connection. But we really need an ``fcall'' opcode so that we can call proceedures. Added jumpnz jump if non zero \emph{ A bytecode system with ijmp and top-of-stack in r25:r24 } ------ .nolist .include ``m328Pdef.inc'' .list .ifdef 0 1 april 2020, tested and working on ``duemilanove'' I changed the code for calculating the program counter offset with 'jump' instructions. The code is structured as a linked list dictionary, which is the system used in all forth systems, but here the dictionary is not used because there is no ``lookup'' of words (just execution of 'opcodes'). This is the most complete bytecode example, but to be useful as a virtual machine, it needs opcodes ``call'', ``return'', ``jumpz'' 5 may 2020: about 450 bytes in size, with no headers or help strings. also, no ``lit'' and litw for pushing literal values on the stack .endif ; pch:pcl is the "Y" register which allows using sbiw and adiw instructions .def pch = r29 ; (program counter high byte) .def pcl = r28 .def tosh = r25 ; (top-of-stack high byte) .def tosl = r24 ; the data stack is the atmega stack ; the return stack is used by ``call/fcall/exec'' and ``return/exit'' ; the word ``call'' is a reserved word in many assemblers, hence ``fcall'' .def rsh = r23 ; return stack pointer, high byte .def rsl = r22 ; return stack pointer, low byte .def zero = r2 .def temp = r16 .def opcode = r20 .equ DUP = 1 .equ DROP = DUP+1 .equ NOT = DROP+1 .equ INCR = NOT+1 .equ DECR = INCR+1 .equ EMIT = DECR+1 .equ KEY = EMIT+1 .equ JUMP = KEY+1 .equ JUMPNZ = JUMP+1 .equ FCALL = JUMPNZ+1 .equ RETURN = FCALL+1 .equ STAR = RETURN+1 .dseg ; ram data segment .cseg ; start of flash ``rom'' code segment .org 0x0000 jmp start ; ISR jump vectors go here. start: ; initialise the data stack at the top of ram ; this is not really required ldi temp, high(RAMEND) out SPH, temp ldi temp, low(RAMEND) out SPL, temp ; set up the forth-like return stack, like data stack, it grows up towards ; the data stack. This leaves about 750 bytes of data memory ldi RSH, high(RAMEND-256) ldi RSL, low(RAMEND-256) clr zero ; set r2 = 0 call serialx ; set up serial interface ; lpm=load program memory, must use Z register ldi PCH, high(code$<$$<$1) ldi PCL, low(code$<$$<$1) nextopcode: ; retrieve and save PC program counter movw ZH:ZL, PCH:PCL ; get ip lpm opcode, Z+ movw PCH:PCL, ZH:ZL ; save ip ; call digitx checkzero: ; halt if opcode is zero cpi opcode, 0 breq goodbye ldi ZH, high(optable$<$$<$1) ldi ZL, low(optable$<$$<$1) ;add ZL, r20 ; opcode number (need to double ;add ZL, r20 ; ; double opcode because each address is 2 bytes add ZL, opcode adc ZH, zero add ZL, opcode adc ZH, zero lpm XL, Z+ lpm XH, Z+ movw ZH:ZL, XH:XL ; ijmp ; never executed I think ; jmp nextopcode goodbye: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? rjmp goodbye ; if not, then just wait ldi temp, '+' sts UDR0, temp ; if empty tx character halt: rjmp halt code: ; some bytecode to test the system, but the compiler will pad with zeros ; so be careful about jump targets if odd number of .dbs ; testing jumpnz ; .db STAR, STAR, KEY, DUP, EMIT, JUMPNZ, -5, 0 ; print ascii table .db STAR, STAR .db KEY, DUP, EMIT, DECR, DUP, JUMPNZ, -4, DROP, JUMP, -10, 0 ; word pointers to opcode functions. optable: .dw 0, dupx, dropx, notx, incrx, decrx, emitx, keyx .dw jumpx, jumpnzx, fcallx, returnx, starx ; an example help header dups: .db "duplicate top stack item (16bit)", 0 .dw dups duph: dupx: push tosl ; duplicate top stack item push tosh ; jmp nextopcode dropx: pop tosh ; discard top of stack pop tosl ; jmp nextopcode ; logical negation of top of stack ; eg: tos $<$$>$ 0, tos $<$- 0 and tos==0, tos $<$- 1 notx: sbiw tosh:tosl, 0 breq notiszero ldi tosh, 0 ldi tosl, 0 jmp nextopcode notiszero: ldi tosh, 0 ldi tosl, 1 jmp nextopcode ; increments by 1 the top stack item incrx: adiw tosh:tosl, 1 jmp nextopcode ; decrement by 1 decrx: sbiw tosh:tosl, 1 jmp nextopcode emith: .dw dupx ; the forth-style dictionary backlink .db 4, ``emit'' ; emitx: waitagain: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? rjmp waitagain ; if not, then just wait sts UDR0, tosl ; if empty tx character in TOS pop tosh ; high byte of TOS (should be zero) pop tosl ; low byte of TOS jmp nextopcode keyh: .dw emitx .db 3, ``key'' keyx: waitkey: push tosl ; low byte push tosh ; high byte lds temp, UCSR0A ; get usart status info sbrs temp, RXC0 ; has a byte been received? rjmp waitkey ; if not just wait for one. ldi tosh, 0 ; set high byte of TOS to 0 lds tosl, udr0 ; get key char into low byte of TOS jmp nextopcode jumph: jumpx: movw ZH:ZL, PCH:PCL ; get ip lpm r16, Z ; get jump ;just debug the digit ;mov r20, r16 ;call digitx ; add or subtract jump offset from PC program counter ; the code below is designed to sign extend from r16 to r17:r16 pair ; the idea is to make the subsequent addition or the program counter and offset ; work when the offset is negative (eg JUMP, -4) clr r17 sbrc r16,7 ; Skip if 8 bit value is positive com r17 ; 8 bit value is negative so set it's high byte ; or ser r17 add PCL, r16 adc PCH, r17 ; add with carry ; since pch:pcl is "Y``, we can use sbiw ''subtract immediate from word" sbiw PCH:PCL, 1 ; adjust offset to JUMP instruction jmp nextopcode ; a relative jump jumpnzx: movw XH:XL, TOSH:TOSL ; save top of stack pop tosh pop tosl ; drop top of stack sbiw XH:XL, 0 ; check if top of stack == 0 breq nojump ; if not update prog counter ; if == 0 jump to target movw ZH:ZL, PCH:PCL ; get ip lpm r16, Z ; get jump clr r17 sbrc r16,7 ; Skip if 8 bit value is positive com r17 ; 8 bit value is negative so set it's high byte add PCL, r16 adc PCH, r17 ; add with carry ; since pch:pcl is "Y``, we can use sbiw ''subtract immediate from word" sbiw PCH:PCL, 1 ; adjust offset to JUMP instruction jmp nextopcode nojump: adiw PCH:PCL, 1 ; skip over jump jmp nextopcode ; call a virtual proc and push the ret address onto the return stack ; untested!! need to check ; or make the ret stack grow towards high memory (ie toward top of ; data stack) and check for a clash fcallx: adiw pch:pcl, 1 ; push ret address onto ret stack movw XH:XL, rsh:rsl st X+, pcl st X+, pch movw rsh:rsl, xh:xl ; now adjust prog counter to jump sbiw pch:pcl, 1 movw ZH:ZL, PCH:PCL ; get ip ld PCL, Z+ ; ld PCH, Z+ ; jmp nextopcode returnx: ; todo write this ; just the reverse process for fcall. pop the ret address ; off the return stack and jump jmp nextopcode starh: .dw keyx .db 4, ``star'' 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 jmp nextopcode ; initialize an rs232 serial connection using onboard usart ; this cant really be used interactively so is not a forth-style command ; with header etc serialx: ldi r16, 1$<$$<$TXEN0 | 1$<$$<$RXEN0 ; enable transmit receive sts UCSR0B, r16 ldi r16, 1$<$$<$UCSZ01 | 1$<$$<$UCSZ00 sts UCSR0C, r16 ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ret ; a primitive method of debugging ; prints one digit in r20 digitx: push r16 push r17 repeat: lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp repeat ; if not, then just wait ldi r17, '0' ; convert opcode to asci digit mov r16, r20 ; assumes 0 $<$ opcode $<$ 10 add r16, r17 sts UDR0, r16 ; if empty tx next character pop r17 pop r16 ret ,,, \section{Avr Assembly} Avr assembly is not case sensitive. Looking at the include file (for example m328Pdef.inc) can give some information about what bits to manipulate to configure, timers, interrupts etc. \emph{ A so so avr assembly site } \begin{lstlisting} https://sites.google.com/site/avrasmintro/ \end{lstlisting} \subsection{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. \emph{ 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 ,,,, \subsection{Errors} \begin{lstlisting} Error : I/O out of range (0 <= P <= 63) \end{lstlisting} This error may mean that you are trying to use OUT with a memory mapped control register... Try usign STS instead! \emph{ Use sts not out } \begin{lstlisting} ; out TIMSK0, temp ; enable compare/match interrupt \end{lstlisting} \begin{lstlisting} sts TIMSK0, temp ; enable compare/match interrupt \end{lstlisting} \subsection{Tricks} define a register as null and then set it to zero. .def r2 = null \subsection{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 \subsection{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! \emph{ Macro example to set up the stack pointer } \begin{lstlisting} .include "m328Pdef.inc" .macro initstack ldi r21,high(ramend) out sph,r21 ldi r21,low(ramend) out spl,r21 .endmacro initstack \end{lstlisting} \subsection{Def} we can give registers more descriptive names with the .def assembler directive \emph{ Eg } --------- .def acc = r0 .def counter = r18 ,,, \subsection{Labels} labels require a colon, eg buffer: not buffer labels cant start with dot (ie local labels in nasm) cant have dots in them... \subsection{Constants} \emph{ Define constants } \begin{lstlisting} .equ score = 100 .set score = 100 ; the same but can be changed later \end{lstlisting} \subsection{Data Segment} In the atmel avr architecture, code and data space are separate. \emph{ Specify the data segment } \begin{lstlisting} .dseg \end{lstlisting} \emph{ An example of creating a buffer in the data segment } --------- .dseg counter: .byte 2 .cseg ,,,, \emph{ Copy from rom to ram } ----- .nolist .include ``m328Pdef.inc'' .list .def temp = r25 .def counter = r24 .dseg data: .byte 5 ; reserve 5 bytes (uninitialesed) .cseg sbi DDRB, 5 ; led on pin13 cbi PORTB, 5 ; turn off the output pin ldi ZL, low(romdata$<$$<$1) ldi ZH, high(romdata$<$$<$1) ldi XL, low(data) ldi XH, high(data) ldi counter, 5 rcall copy rcall check halt: rjmp halt copy: lpm temp, Z+ st X+, temp dec counter brne copy ret ; turn on the led if the buffer contains the letter check: ldi XL, low(data) ldi XH, high(data) ld temp, X cpi temp, 'a' brne endcheck ;sbi PINB, 5 ; apparently this toggles the led sbi PORTB, 5 ; turn on the output pin endcheck: ret ; ... romdata: .db ``aBCDE'' ,,, \subsection{Code Segment} With .db we can define string constants such as ``test'' but without any escaped characters (eg \textbackslash n) \emph{ Define some constant data in the code segment in flash memory } \begin{lstlisting} .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 \end{lstlisting} \subsection{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. \subsection{Arithmetic Instructions} These instructions perform some simple arithmetic on an 8 bit register. add, sub, inc, dec \subsection{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. \emph{ Jump if reg16 is = or $>$ than 20 (unsigned comparison) } ------ cpi r16, 20 brsh higher ; ... higher: ,,, \arrayrulecolor{gray} \begin{center} \begin{tabular}{ |rl| } \multicolumn{2}{c}{\textbf{ common signed branches }} \\ \hline \texttt{ brge } & Branch if greater or equal, signed \\ \texttt{ brlt } & Branch if less than, signed \\ \texttt{ brpl } & If positive \\ \texttt{ brmi } & If negative \\ \hline \end{tabular} \end{center} \arrayrulecolor{gray} \begin{center} \begin{tabular}{ |rl| } \multicolumn{2}{c}{\textbf{ common unsigned branches }} \\ \hline \texttt{ brlo } & Branch if less than \\ \texttt{ brsh } & Branch if greater or equal \\ \texttt{ brne } & Branch if not equal \\ \texttt{ breq } & Branch if equal \\ \hline \end{tabular} \end{center} \subsection{Nop And Halting} There are various ways to halt or stop the uC. We can put it to sleep, enter and infinite loop etc \emph{ An infinite loop that does nothing } \begin{lstlisting} here: rjmp here \end{lstlisting} \subsection{Skips} skips are similar to jumps except that they only skip one (the next) instruction, if the condition is met. sbic - ... \subsection{Comparisons} In assembly comparisons may be unsigned or signed (since a given bit pattern in a register can be interpreted as either a signed or unsigned number) \emph{ Branch if less than (unsigned?) } --- cpi A, 200 brlo under200 ,,,, \emph{ Compare a 16 bit value with an immediate value } ----- compare16: cpi r16, low(1234) ;Compare low byte ldi r18, high(1234) ; cpc r17, r18 ;Compare high byte ; the zero flag is set if equal ,,,, See Atmel application note AVR202 for 16bit comparisons \emph{ Compare two 16 bit numbers for size } -------- .include ``m328Pdef.inc'' .cseg sbi DDRB, 5 ; make portB.5 output, arduino pin 13 (on-board led) 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: rjmp here ,,, \subsection{Loops} \emph{ The simplest loop construct } ---- init: ldi r16, 100 again: dec r16 brne again ,,, \emph{ 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 sleep500 cbi PORTB, 5 rcall sleep500 rjmp start ; sleeps for approximately 500 milliseconds on a 16Mhz atmega328 sleep500: clr r16 ; set reg16 := 0 clr r17 ldi r18, 42 here: ; a delay loop 40*256\^{}2 instructions dec r16 ; brne here dec r17 brne here dec r18 brne here ret ,,, \subsection{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 both ``call'' and ``rcall'' put the same (absolute address) on the stack, so that ``ret'' works the same for both calls. \subsection{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. \subsection{Using The Stack To Return A Value} \emph{ 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 ; restore return address, otherwise ``ret'' wont work! 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. \emph{ 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;'') ,,, \subsection{Return Value In Register} r25:r24 is a standard register pair for return values (not on the stack). But this is just a convention. \subsection{Return Value In Carry Flag} The carry flag by convention is used to return a true/false value. \subsection{Icall} Notice that loading the Z register for ICALL is different to loading the Z register for a table lookup with LPM. \emph{ 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. \emph{ 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 ,,, \subsection{Ijmp} Only the Z register can be used for indirect jumps. Post increment is not relevant here, or not useful. \emph{ 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 ,,, \subsection{Logical Instructions} \subsection{Andi} andi - logical and with immediate (literal) value \subsection{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 another way to toggle might be to do ---- sbi ddrd, 5 sbi pind, 5 ; apparently this toggles the led??? ,,, \emph{ 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 ,,,, \subsection{Bits} \emph{ Toggle the least significant bit in r17 } \begin{lstlisting} eor r17, 1 eor r17, 0x01 ; the same \end{lstlisting} \emph{ Toggle the most significant bit in r17 } \begin{lstlisting} eor r17, 1<<8 eor r17, 0b10000000 ; this is the same, but more verbose \end{lstlisting} \section{Moving Data} \emph{ Copy data from flash (``rom'' or ``program'') memory to sram data memory } \begin{lstlisting} .nolist ; change this to the avr chip in use. .include "tn2313def.inc" .list .def temp = r16 .DSEG .org 0 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 \end{lstlisting} \subsection{Gotchas} If you use ``ld'' instead of ``lpm'' when reading rom (program memory) there will be no error, but you will probably just get zero ( because you are actually reading sram data memory.) Also, there is no ``lpm r20, Y+'' instruction, must use Z or Z+ Also, the minimum address for sram within the data segment (that is .dseg with .org) is 0x100 because addresses below that are registers and i/o ports.... \subsection{The Mac Bug With Avra} In some versions of avra on macosx you *must* use .org 0x100 or something similare at the top of the .dseg!!!!! Otherwise labels in the data segment dont work!. The bug appears to be the way that avra on macosx compiles dseg labels. When I set the X reg pointer to a data segment label with "ldi XH, high(buffer); ldi XL, low(buffer)" then the pointer is invalid. \emph{ The minimal code for the mac bug } ----- .nolist .include ``m328Pdef.inc'' .list .def temp = r16 .def param = r17 .dseg ; must use .org 0x100 here otherwise dseg labels dont work on macosx avra .org 0x100 ; sram data buffer, seems to be compiled incorrectly on ; mac osx input: .byte 30 .cseg ; initialize an rs232 serial connection using onboard usart serial: ldi r16, 1$<$$<$TXEN0 | 1$<$$<$RXEN0 ; enable transmit receive sts UCSR0B, r16 ldi r16, 1$<$$<$UCSZ01 | 1$<$$<$UCSZ00 sts UCSR0C, r16 ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ; load X pointer with address of buffer ;ldi xh, high(input) ; actually this seems to cause the problem ;ldi xl, low(input) ldi xh, high(RAMEND-128) ldi xl, low(RAMEND-128) ldi r17, '!' ; '!'=asci 33 clr r18 ; used for the count loop nextchar: tx: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? rjmp tx ; if not, then just wait (block) ;ldi r17, 'a' st X, r17 ; the problem lines, r17 gets corrupted ld r17, X ; because X pointer invalid if using high(input). sts UDR0, r17 ; if usart buffer empty tx character ; this prints '@', not 'a' ; in this case I think 6 lower bits are being zeroed inc r17 cpi r17, 126 ; '$\sim$' is last printable char brne nextchar halt: rjmp halt ,,,, \subsection{Pointers} The avr chips such as atmega328 have 3 register pairs xh:xl yh:yl zh:zl these can be used as 16bit registers with instructions like ``adiw'', ``sbiw'' etc. They can also be used as pointers. Pointers to code memory need to be put in the Z reg (ZH:ZL or r31:r30) Pointers to data memory can be in X,Y, or Z You need to double pointer reference when using lpm, but not icall!! see the forth ideas code section. \emph{ Check if a pointer register pair is zero } ------- sbiw XH:XL, 0 breq exit ; or brne to reverse the logic ; code if not zero exit: ,,,, \emph{ Example } ------ .include ``m328Pdef.inc'' .def temp = r0 .cseg ldi ZH, high(pointer$<$$<$1) ldi ZL, low(pointer$<$$<$1) lpm YL, Z+ ; dereference the pointer in Z reg lpm YH, Z add YL, YL ; need to double the pointer adc YH, YH ; or lsl YH movw ZH:ZL, YH:YL ; now Z reg points to buffer for lpm ; but if you were using ``icall'' you would not need to double ; the address bytes lpm temp, Z+ ;etc pointer: .dw buffer buffer: .db ``text'' ,,, If we write the pointer in an ugly way then \emph{ Another way to write the pointer } ----- .db low(typeh$<$$<$1), high(typeh$<$$<$1) ,,, \subsection{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 \emph{ Read a string stored in the program memory } ------ .include ``m328Pdef.inc'' .def counter = r16 .dseg .cseg .org 0 rjmp main buffer: .db 5, ``hello'' main: sbi DDRB, 5 ; portb.5 = output (for pin13 LED feedback) ldi zh, high(buffer$<$$<$1) ldi zl, low(buffer$<$$<$1) lpm counter, Z+ ; load string count into r16 nextchar: cbi PORTB, 5 ; turn off led lpm r20, Z+ ; get contents of byte at z into r16 cpi r20, 'o' brne continue sbi PORTB, 5 ; turn on led if letter found rjmp halt continue: dec counter brne nextchar halt: rjmp halt ,,, \subsection{In} in gets data from an i/o port \subsection{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 \emph{ Load 5 in register 16 } \begin{lstlisting} ldi r16, 0b00000101 \end{lstlisting} \emph{ Load 0 into register 3 } \begin{lstlisting} clr r3 \end{lstlisting} \subsection{Mov} The mov instruction can be used for moving data with the register file (r0 to r31) I think \subsection{Indirect Addressing With Ld And St} \emph{ 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 ,,, \emph{ 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 ,,, \subsection{Sts} store direct to data space \section{Arithmetic In Assembler} \emph{ Increment odd numbers by one } ------- sbrc r0,0 ; if lsb is 0 skip next increment instruction inc r0 ; bit 0 was a 1 (number is odd), increase r0 by 1 ,,, \subsection{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. \emph{ 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 ,,, \subsection{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. \emph{ Divide 250 by 60 } \begin{lstlisting} .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 \end{lstlisting} unsigned?? \emph{ Divide 16bit value by 16bit value, repeated subtraction } \begin{lstlisting} .list .include "m328Pdef.inc" ; the atmega328p, the usual arduino micro .nolist .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 .org 0 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) cbi PORTB, 5 cpi quotient, 10 breq light rjmp here light: sbi PORTB, 5 ; turn on led if quotient is 10 here: rjmp here \end{lstlisting} \emph{ Proceedure to divide 2 8bit numbers on the stack. } \begin{lstlisting} .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 \end{lstlisting} \subsection{Division By Powers Of Two} \emph{ Divide a 16 bit number (255) by 2 } \begin{lstlisting} ldi YH, 0x00 ldi YL, 0xFF ; LSR on upper register, Logical shift right lsr YH ; ROR on the lower. Rotate right through carry ror YL \end{lstlisting} \emph{ Multiply a 16 bit number (257) by 2 } \begin{lstlisting} ; LSL on lower register, Logical shift left ; ROL on the upper. Rotate left through carry ldi YH, 0x01 ldi YL, 0x01 lsl YL rol YH ; another way to double, just simple addition ; add YL, YL ; adc YH, YH \end{lstlisting} 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 \emph{ Approximate division by 10 and 5 for an 8 bit argument } \begin{lstlisting} ; 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 \end{lstlisting} \subsection{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 \emph{ 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 ,,, \emph{ Testing mul } ------- .nolist .include ``m328Pdef.inc'' .list .def multiplicand = r16 .def multiplier = r17 .cseg .org 0 jmp start start: sbi DDRB, 5 ; make PORTB,bit 5 an output, onboard led ldi multiplicand, 42 ; ldi multiplier, 10 ; ; result in r1:r0 mul multiplicand, multiplier ;multiply contents of a and b ; move word to use cpi mov r20, r0 mov r21, r1 ; compare result to 420 cpi r20, low(420) ;Compare low byte ldi r18, high(420) ; cpc r21, r18 ;Compare high byte brne off on: ; if equal turn on led sbi PORTB, 5 rjmp continue off: cbi PORTB, 5 ; if not turn off continue: rjmp continue ,,,, \emph{ Multiplication by 10 without using ``mul'' } ----- ; Bin1Mul10 ; ========= ; multiplies a 16-bit-binary by 10 ; Sub used by: AscToBin2, Asc5ToBin2, Bcd5ToBin2 ; In: 16-bit-binary in rBin1H:L ; Out: T-flag shows general result: ; T=0: Valid result, 16-bit-binary in rBin1H:L ok ; T=1: Overflow occurred, number too big ; Bin1Mul10: push rBin2H ; Save the register of 16-bit-binary 2 push rBin2L mov rBin2H,rBin1H ; Copy the number mov rBin2L,rBin1L add rBin1L,rBin1L ; Multiply by 2 adc rBin1H,rBin1H brcs Bin1Mul10b ; overflow, get out of here Bin1Mul10a: add rBin1L,rbin1L ; again multiply by 2 (4*number reached) adc rBin1H,rBin1H brcs Bin1Mul10b ; overflow, get out of here add rBin1L,rBin2L ; add the copied number (5*number reached) adc rBin1H,rBin2H brcs Bin1Mul10b ;overflow, get out of here add rBin1L,rBin1L ; again multiply by 2 (10*number reached) adc rBin1H,rBin1H brcc Bin1Mul10c ; no overflow occurred, don't set T-flag Bin1Mul10b: set ; an overflow occurred during multplication Bin1Mul10c: pop rBin2L ; Restore the registers of 16-bit-binary 2 pop rBin2H ret ; ,,, \subsection{Addition} There is no ADDI instruction, but there is a SUBI instruction but there is a trick \emph{ Add an immediate value 4 to register r16 } \begin{lstlisting} subi r16, -4 \end{lstlisting} \emph{ You can even sub a negative character in avra!! } \begin{lstlisting} subi temp, -'0' ; add '0' (48) to temp \end{lstlisting} \subsection{Adding Two Byte Numbers} use addc. \subsection{Converting Numbers To Ascii} \emph{ Convert 3 digits to integer } ----- .def ten = r16 .def temp = r19 .def zero = r17 .def counter = r18 toInt: push counter ; save counter ldi temp, 4 ; five chars, one too much mov counter, temp clr rBin1H ; Clear result clr rBin1L clt ; Clear T-Bit blankzero: dec counter ; all chars read? breq finished ; last char ld temp, Z+ ; read a char cpi temp, ' ' ; ignore blanks breq blankzero ; next char cpi temp, '0' ; ignore leading zeros breq blankzero ; next char nextchar: subi temp, '0' ; brcs invalidchar ; digit $<$ '0' cpi temp, 10 ; digit $>$ 9 brcc invalidchar ; here just use the normal 'mul' instruction. ; rcall Bin1Mul10 ; Multiply result by 10 brts invalidchar ; Overflow occurred add rBin1L, temp ; add the digit ld temp, Z+ brcc continue ; no overflow to MSB inc rBin1H ; Overflow to MSB breq invalidchar ; Overflow of MSB continue: dec counter ; downcount number of digits brne nextchar ; convert more chars finished: ; End of ASCII number reached ok sbiw ZL, 3 ; Restore start position of ASCII number pop counter ; Restore register R0 ; here put the 1 byte integer on the stack ret invalidchar: ; Last char was invalid sbiw ZL, 1 ; Point to invalid char pop counter ; Restore register R0 set ; Set T-Flag for error ret ; return with error condition set ; or put error on the stack ,,,, \emph{ 5 digit ASCII to binary } -------- ; Asc5ToBin2 ; ========== ; converts a 5-digit ASCII to a 16-bit-binary ; In: Z points to first decimal ASCII-digit, leading ; blanks and zeros are ok. Requires exact 5 digits. ; Result: T-Flag reports result: ; T=0: result in rBin1H:rBin1L, valid, Z points to ; first digit of the hex-ASCII-number ; T=1: error, Z points to the first illegal character ; or to the digit, where the overflow occurred ; Used registers: rBin1H:L (result), R0 (restored after ; use), rBin2H:L (restored after use), rmp ; Called subroutines: Bin1Mul10 ; Asc5ToBin2: push R0 ; R0 is used as counter, save it first ldi rmp,6 ; five chars, one too much mov R0,rmp clr rBin1H ; Clear result clr rBin1L clt ; Clear T-Bit Asc5ToBin2a: dec R0 ; all chars read? breq Asc5ToBin2d ; last char ld rmp,Z+ ; read a char cpi rmp,' ' ; ignore blanks breq Asc5ToBin2a ; next char cpi rmp,'0' ; ignore leading zeros breq Asc5ToBin2a ; next char Asc5ToBin2b: subi rmp,'0' ; treat digit brcs Asc5ToBin2e ; Last char was invalid cpi rmp,10 ; digit $>$ 9 brcc Asc5ToBin2e ; Last char invalid rcall Bin1Mul10 ; Multiply result by 10 brts Asc5ToBin2e ; Overflow occurred add rBin1L,rmp ; add the digit ld rmp,z+ brcc Asc5ToBin2c ; no overflow to MSB inc rBin1H ; Overflow to MSB breq Asc5ToBin2e ; Overflow of MSB Asc5ToBin2c: dec R0 ; downcount number of digits brne Asc5ToBin2b ; convert more chars Asc5ToBin2d: ; End of ASCII number reached ok sbiw ZL,5 ; Restore start position of ASCII number pop R0 ; Restore register R0 ret Asc5ToBin2e: ; Last char was invalid sbiw ZL,1 ; Point to invalid char pop R0 ; Restore register R0 set ; Set T-Flag for error ret ; and return with error condition set ; ,,, \section{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. \emph{ The procedure to display an integer } ---- set display string = "" again: divide by base. remainder is least significant digit. convert to ascii. append to beginning of display string if quotient = 0 break jump again print ,,,, \subsection{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. The process below is clever because it gets around the lack of a division instruction on the atmega328 uCs. If the number is just going to be displayed/printed once, then we dont need to use 3 registers, just one. \emph{ In pseudo code convert 8bit number to ascii base 10 } ---- ; free 3 registers, clr hundreds clr tens clr ones ldi temp, 0x30 ; convert to ascii hundred: if $>$= 100 inc hundreds subi bin, 100 loop hundred endif add hundreds, temp ten: if $>$= 10 inc tens subi bin, 10 loop ten endif add tens, temp one: if $>$= 1 inc ones subi bin, 1 loop one endif add ones, temp ,,, \emph{ Display 16 bit value in decimal without division } \begin{lstlisting} .nolist .include "m328Pdef.inc" ; the atmega328p, the usual arduino micro .list .def digit = r16 .def temp = r20 ; .def paraml = r24 ; low byte of value to display .def paramh = r25 ; high byte .equ value = 62123 .dseg ; ram data segment .cseg ; rom program memory (flash) .org 0 jmp start start: ; assume auto init stack rcall serial ldi digit, '[' rcall emit ldi paramh, high(value) ldi paraml, low(value) rcall printdecword ldi digit, ']' rcall emit halt: rjmp halt ; initialize an rs232 serial connection using onboard usart serial: ldi temp, 1<= 10000 cpi paraml, low(10000) ; compare low byte ldi temp, high(10000) cpc paramh, temp ; compare high byte brlo notenthousands inc digit subi paraml, low(10000) ldi temp, high(10000) sbc paramh, temp ; 16bit subtraction rjmp tenthousands notenthousands: subi digit, -'0' rcall emit clr digit thousands: ; if >= 1000 cpi paraml, low(1000) ; compare low byte ldi temp, high(1000) cpc paramh, temp ; compare high byte brlo nothousands inc digit subi paraml, low(1000) ldi temp, high(1000) sbc paramh, temp ; 16bit subtraction rjmp thousands nothousands: subi digit, -'0' rcall emit clr digit hundreds: ; if >= 100 cpi paraml, 100 brlo nohundreds inc digit subi paraml, 100 rjmp hundreds nohundreds: subi digit, -'0' rcall emit clr digit tens: ; if >= 10 cpi paraml, 10 brlo notens inc digit subi paraml, 10 rjmp tens notens: subi digit, -'0' ; convert to ascii rcall emit ; print the digit clr digit ones: ; if >= 1 cpi paraml, 1 brlo noones inc digit subi paraml, 1 rjmp ones noones: subi digit, -'0' ; convert to ascii rcall emit ; print the digit ret ; cant use ijmp because "emit" changes Z reg \end{lstlisting} \emph{ Display one byte in decimal without division } \begin{lstlisting} ; working 19 april 2020 .nolist .include "m328Pdef.inc" ; the atmega328p, the usual arduino micro .list .def temp = r20 ; .def param = r24 ; used for local parameters .dseg ; ram data segment .cseg ; rom program memory (flash) .org 0 jmp start start: ; initialise the stack at the top of ram ldi r21, high(ramend) out sph, r21 ldi r21, low(ramend) out spl, r21 ; test emit rcall serial ldi temp, '[' push temp rcall emit ldi temp, 234 push temp rcall printdec ldi temp, ']' push temp rcall emit here: rjmp here ; initialize an rs232 serial connection using onboard usart serial: ldi r16, 1<= 100 cpi param, 100 brlo nohundreds inc digit subi param, 100 rjmp hundreds nohundreds: subi digit, -'0' push digit ; function "emit" does not use "param" register, ; so we do not have to preserve it on the stack rcall emit clr digit tens: ; if >= 10 cpi param, 10 brlo notens inc digit subi param, 10 rjmp tens notens: subi digit, -'0' ; convert to ascii push digit rcall emit ; print the digit clr digit ones: ; if >= 1 cpi param, 1 brlo noones inc digit subi param, 1 rjmp ones noones: subi digit, -'0' ; convert to ascii push digit rcall emit ; print the digit ret ; cant use ijmp because "emit" changes Z reg \end{lstlisting} \subsection{Displaying Numbers In Binary} incomplete. The procedure for displaying a number in binary is also a general procedure for displaying a number in any base. See algorithm above We can also display binary numbers with a series of on/off leds, or even a series of blinks of a single led. \emph{ Display an 8 bit register in binary over a serial connection } -------- .nolist .include ``m328Pdef.inc'' ; the atmega328, the usual arduino micro .list .def temp = r18 .def counter = r22 .dseg ; this buffer will hold the binary display string. display: .byte 20 .cseg ; start of flash ``rom'' code segment .org 0x0000 jmp start start: ; assume stack auto initialised rcall serialx ; set up serial interface ldi r16, 9 rcall printbin ldi r16, 12 rcall printbin main: rjmp main ; another strategy would be to do ``lsl'' and save the most significant ; bit in the carry flag, then print it. This saves having to reverse ; the order of the digits. But it is not a general base procedure. ; print an 8 bit number in r16 register in binary format printbin: ldi counter, 8 next: ; incomplete!! mov temp, r16 ; divide by two. append to string ``display'' lsr temp ; temp = int(temp/2) ; convert the 1/0 to ascii subi temp, -'0' ; add '0' to temp dec counter brne next ret ; initialize an rs232 serial connection using onboard usart serialx: ldi r16, 1$<$$<$TXEN0 | 1$<$$<$RXEN0 ; enable transmit receive sts UCSR0B, r16 ldi r16, 1$<$$<$UCSZ01 | 1$<$$<$UCSZ00 sts UCSR0C, r16 ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ret ; transmit 1 character in r17 over serial connection emitx: transmit: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? (usart data register empty?) rjmp transmit ; if not, then just wait sts UDR0, r17 ; if empty tx next character ret ,,, \subsection{Displaying Numbers In Hexadecimal} Another technique is to add '0' or 'A' ? to the value to get a displayable digit. So we dont have to do any table lookup. \emph{ Display an 8 bit and 16bit values in hexadecimal with no lookup table } -------- ; 8 bit and 16bit working 2020 .nolist .include ``m328Pdef.inc'' ; the atmega328, the usual arduino micro .list .def temp = r17 .def char = r18 .def counter = r22 .def param = r23 .def paraml = r24 .def paramh = r25 .equ value = 0xAB12 .cseg .org 0 rjmp start start: ; assume auto initialise stack pointer rcall serialx ; set up serial interface ldi param, 0xFA rcall printhex ldi char, ' ' rcall emitx ldi paramh, high(value) ldi paraml, low(value) rcall printhexword halt: rjmp halt ; prints the hex value in paramh:paraml printhexword: mov param, paramh rcall printhex mov param, paraml rcall printhex ret ; prints the byte in ``temp'' in hex with no lookup table ; 13 instructions printhex: ldi counter, 2 next: swap param ; print high nibble digit first, low 2nd mov temp, param andi temp, 0x0F ; remove high nibble in temp cpi temp, 10 brlo nothex subi temp, -7 ; Hex digit so add 8 to get ABCDEF nothex: subi temp, -'0' ; add '0' to temp mov char, temp rcall emitx dec counter brne next ret ; initialize an rs232 serial connection using onboard usart serialx: ldi temp, 1$<$$<$txen0 | 1$<$$<$rxen0 ; enable transmit receive sts UCSR0B, temp ldi temp, 1$<$$<$ucsz01 | 1$<$$<$ucsz00 sts UCSR0C, temp ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ret ; transmit 1 character in r17 over serial connection emitx: transmit: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? (usart data register empty?) rjmp transmit ; if not, then just wait sts UDR0, char ; if empty tx next character ret ,,, \emph{ Display an 8 bit register in hexadecimal using a lookup table } -------- .include ``m328Pdef.inc'' ; the atmega328, the usual arduino micro .def temp = r18 .def counter = r22 .cseg ; start of flash ``rom'' code segment .org 0x0000 jmp wakeup ; a hex digit lookup table hextable: .db ``0123456789ABCDEF'' ; prints the byte in r16 in hex over a serial connection ; the printhex2 below is more succinct and doesnt require ; a lookup table. printhex: ldi counter, 2 clr r2 nextnibble: swap r16 ldi ZH, high(hextable$<$$<$1) ldi ZL, low(hextable$<$$<$1) mov temp, r16 andi temp, 0x0F ; remove high nibble in r18 add zl, temp ; get digit by offset adc zh, r2 ; propagate carry bit lpm r17, Z ; load hex digit from table rcall emitx dec counter brne nextnibble ret ; initialize an rs232 serial connection using onboard usart serialx: ldi r16, (1$<$$<$txen0)|(1$<$$<$rxen0) ; enable transmit receive sts UCSR0B, r16 ldi r16, (1$<$$<$ucsz01)|(1$<$$<$ucsz00) sts UCSR0C, r16 ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ret ; transmit 1 character in r17 over serial connection emitx: transmit: lds temp, UCSR0A ; usart control, status register sbrs temp, UDRE0 ; is UDR empty? (usart data register empty?) rjmp transmit ; if not, then just wait sts UDR0, r17 ; if empty tx next character ret wakeup: ; initialise the stack at the top of ram ldi r21, high(ramend) out sph, r21 ldi r21, low(ramend) out spl, r21 rcall serialx ; set up serial interface ldi r16, 0xFA rcall printhex2 ldi r16, 0x3B rcall printhex2 halt: rjmp halt ,,, \section{Strings} It appears that avra doesnt support special chars such as \textbackslash n \emph{ Define some ``constant'' strings in the code segment } \begin{lstlisting} .include "m328Pdef.inc" .cseg greet: .db "hello", 0 message: .db 'h','e','l','l','o',0 ; a harder way to do it \end{lstlisting} \emph{ Create a 20 byte string buffer in the data segment (sram) } \begin{lstlisting} .include "m328Pdef.inc" .dseg input: .byte 20 .cseg greet: .db "hello", 0 \end{lstlisting} \section{Memory} The atmega328 (the original ``arduino'' micro-controller) is designed with a ``harvard'' architecture. This may be unfamiliar for programmers coming from an Intel x86 environment (which is a ``Von Neuman'' architecture). Perhaps the main feature of the harvard architecture is the separation of program and data memory. These 2 types of memory have very different characteristics and different instructions for reading and writing them. \subsection{Eeprom Memory} eeprom is rewritable memory that is conserved when there is no power to the uC. I think it can be initialised in the .eseg section. The atmega328P has 1024 bytes of eeprom. \emph{ Write to eeprom } -------- ;Put 1 byte in r16 to address pointed by Z. put\_ieep\_byte: sbic EECR,EEWE ; Wait for completion of previous write rjmp put\_ieep\_byte out EEARH,zh ; Set up address (in Z ) in address register out EEARL,zl out EEDR,r16 ; Write data (r16) to data register sbi EECR,EEMWE ; Write logical one to EEMWE sbi EECR,EEWE ; Start eeprom write by setting EEWE ret ,,,, \subsection{Flash Program Memory} Flash memory is also known as ``rom'' (or read-only) memory, although this memory can be written to using the avr SPM instruction. However there are a number of hurdles to overcome in writing to flash memory. The program code is stored in this memory. In the Atmega328 there is 32K of flash memory (hence the ``32'' in the chip name) as opposed to only 8K of data memory (hence the "8" in the chip name). We can read from the atmega flash memory using the LPM (load program memory) instruction. The code below can't be run because it doesnt have any way to receive the string into sram. \emph{ Compare a string in flash memory to a string in sram } ------ .nolist .include ``m328Pdef.inc'' .list .def temp = r16 .DSEG test: .byte 10 ; reserve space for array test[] in RAM .CSEG .org 0 rjmp start start: ldi zl, low(2*buffer) ; Set a pointer to the table in program memory ldi zh, high(2*buffer); ldi yl, low(test) ; Set a pointer to the table in RAM ldi yh, high(test) rcall compare brcs ledoff ledon: ; turn on the led if strings are the same. rjmp start ledoff: ; turn on the led if strings are the not the same rjmp start ; compare zero terminated strings in sram and flash compare: loop: ld r16, Y+ ; Get byte character from RAM lpm r0, Z+ ; and from FLASH ; Z+ is better no? ; adiw r30:r31,1; cp r16, r0 ; Compare bytes brne notequal ; Branch if not equal tst r16 ; At the end of string brne loop ; Branch if NO clc ; Clear carry flag to mark comparison succes ret ; return from function notequal: sec ; Set carry flag in case of not equal strings ret buffer: .db ``cewek'', 0 ,,, \emph{ Receive characters via rs232 and compare to a string in flash } ------ .nolist .include ``m328Pdef.inc'' .list ; define register names just for readability .def temp = r16 .def counter = r20 .DSEG test: .byte 10 ; reserve space for array test[] in SRAM .CSEG .org 0 rjmp start start: ; initializing the stack pointer is normally not necessary. sbi DDRB, 5 ; make PORTB,bit 5 an output ldi YL, low(test) ; Set a pointer to the table in RAM ldi YH, high(test) rcall serial rcall key ; get key value into r17 st Y+, r17 rcall emit rcall key st Y+, r17 rcall emit ldi zl, low(2*buffer) ; Set a pointer to the table in program memory ldi zh, high(2*buffer); ldi yl, low(test) ; Set a pointer to the table in RAM ldi yh, high(test) rcall compare ; check if strings equal, result in carry flag brcs ledoff ledon: ; turn on the led if strings are the same. sbi PORTB, 5 rjmp start ledoff: ; turn off the led if strings are the not the same sbi PORTB, 5 rjmp start ; compare zero terminated strings in sram and flash compare: loop: ld r16, Y+ ; Get byte character from RAM lpm r0, Z+ ; and from FLASH ; Z+ is better no? ; adiw r30:r31,1; cp r16, r0 ; Compare bytes brne notequal ; Branch if not equal tst r16 ; At the end of string brne loop ; Branch if NO clc ; Clear carry flag to mark comparison succes ret ; return from function notequal: sec ; Set carry flag in case of not equal strings ret ; emit the character in r17 register. emit: waitagain: lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp waitagain ; if not, then just wait sts UDR0, r17 ; if empty tx character ret ; 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,(1$<$$<$txen0)|(1$<$$<$rxen0) ; enable transmit receive sts UCSR0B, r16 ldi r16,(1$<$$<$ucsz01)|(1$<$$<$ucsz00) sts UCSR0C, r16 ldi r16,0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L,r16 ldi r16,0x00 ; the baud rate, 9600 sts UBRR0H,r16 ret buffer: .db ``ce'', 0 ,,, In c structures can be put in Flash with the PROGMEM statement. \subsection{Sram Data Memory} There is 8K of sram data memory in the atmega328. It is simple to both read and write to this type of memory an unlimited amount of times. \section{Serial Interface} The avr atmega chips have a serial uart interface. This needs to be configured to a certain baud rate, parity and stop bit. The serial interface is very useful for communicating with the atmega from a computer, since the standard arduino usb connection can be treated as a serial connection. Also, in a forth-style system the serial connection is used to issue commands and ``program'' the chip. By combining these techniques with and hc05 bluetooth chip we have a simple way to wirelessly control and program an arduino. \emph{ Formula for value of ubrr register } \begin{lstlisting} #define ubrr (FOSC/(16*BAUD))-1 \end{lstlisting} \subsection{Gotchas} If you see the backtick ` coming back from a serial terminal, then this seems to indicate a stack overflow for some reason ..... If you cant see the correct device in /dev/tty* it is probably because the usb cable is Not a data cable!. \subsection{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. \subsection{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. \emph{ 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$<$$<$txen0 | 1$<$$<$rxen0 ; enable transmit receive sts UCSR0B, r16 ldi r16, 1$<$$<$ucsz01 | 1$<$$<$ucsz00 sts UCSR0C, r16 ldi r16, 0x66 ; the baud rate, 9600 on 16 megaherz chip sts UBRR0L, r16 ldi r16, 0x00 ; the baud rate, 9600 sts UBRR0H, r16 ret start: ; initialise the stack at the top of ram ldi r21, high(ramend) out SPH, r21 ldi r21, low(ramend) out SPL, r21 call serial ; set up serial interface sbi DDRB, 5 ; make PORTB,bit 5 an output ; the main program loop again: call key cpi r17, 'a' ; compare immediate breq ledon ; branch if r17 == 'a' cpi r17, 'b' ; compare immediate breq ledoff ; 'b' was pressed rjmp again ; handle keypresses here. ledon: sbi portb, 5 ; turn on the output pin rjmp again ledoff: cbi portb, 5 ; turn off the output pin rjmp again ,,, \emph{ Receive a keystroke over serial and handle specific keypresses } ------ .include ``m328Pdef.inc'' .dseg ; ram data segment .cseg ; start of flash ``rom'' code segment .org 0x0000 jmp wakeup ; emit the character in r17 register. emit: waitagain: lds r16, UCSR0A ; usart control, status register sbrs r16, UDRE0 ; is UDR empty? rjmp waitagain ; if not, then just wait sts UDR0, r17 ; if empty tx character ret ; 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,(1$<$$<$txen0)|(1$<$$<$rxen0) ; enable transmit receive sts ucsr0b, r16 ldi r16,(1$<$$<$ucsz01)|(1$<$$<$ucsz00) sts ucsr0c, r16 ldi r16,0x66 ; the baud rate, 9600 on 16 megaherz chip sts ubrr0l,r16 ldi r16,0x00 ; the baud rate, 9600 sts ubrr0h,r16 ret wakeup: ; initialise the stack at the top of ram ldi r21, high(ramend) out sph, r21 ldi r21, low(ramend) out spl, r21 call serial ; set up serial interface ; the main program loop again: ldi r17, '*' call emit call key cpi r17, 'a' ; compare immediate breq apressed ; branch if r17 == 'a' cpi r17, 'b' ; compare immediate breq bpressed ; branch if r17 == 'b' call emit rjmp again ; handle keypresses here. apressed: ldi r17, 'A' call emit rjmp again bpressed: ldi r17, 'B' call emit rjmp again ,,, Now we can develop the code above to change the fequency of a tone when a key is pressed. \subsection{Serial Communication In C} The serial interface is a great way to get information from an arduino about what it is doing or feeling. You can get this over the usb interface to the computer or via an hc05 bluetooth component for ``remote'' boards. Make sure the ``baud'' rate is the same on the serial monitor and in the sketch. \begin{lstlisting} Serial.begin(9600); \end{lstlisting} 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 \emph{ Basic sketch with serial } \begin{lstlisting} void setup() { Serial.begin(9600); Serial.println("Hello !!!"); } void loop() { } \end{lstlisting} \emph{ Read one char from serial port and do something with it } \begin{lstlisting} 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(); } \end{lstlisting} \subsection{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. \emph{ Transmit a digit using usart } \begin{lstlisting} .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 ,,, \subsection{Receiving Rx} This is not working 13 april 2020 and I am not sure why. \emph{ Receive some chars from usart into a buffer } \begin{lstlisting} .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 \end{lstlisting} \emph{ Receiving using the usart serial interface } \begin{lstlisting} .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(40000) ; need a 50hz frequency with 16 Mhz ; sts ICR1H, temp ; ldi temp, low(40000) sts ICR1L, temp ; ; COM1A1 only one output ldi temp, 1$<$$<$WGM11 | 1$<$$<$COM1A1 ; Fast PWM, wave form output sts TCCR1A, temp ; wgm bits for Fast PWM and starts timer with prescalar = 8 ; loading clock starts the timer ldi temp, 1$<$$<$WGM13 | 1$<$$<$WGM12 | 1$<$$<$CS11 sts TCCR1B, temp ; for servo motors the value should be between 5\% and 10\% of ICR1 value ; for dimming leds, the value should be between 0\% and 100\% of ICR1 value ; load OCR1A which determines pulse width of pwm wave ldi temp, high(3000) ; need a 1-2 ms pulse width sts OCR1AH, temp ; load compare value high byte ldi temp, low(3000) sts OCR1AL, temp ; load compare value low byte ; 50 Herz wave with variable pulse width main: ; sleep for eg 5ms and vary the ocr1a value here for ; dimming and led or servo.. rjmp main ,,,, An example in c to set up a pulse width modulated wave on pin 3 using an automatic toggle (no ISR is required, because the output pin toggle is part of the configuration). \emph{ 50 herz pwm wave with .512ms pulse width on pin 3 (uno board) } ------ pinMode(3, OUTPUT); pinMode(11, OUTPUT); TCCR2A = \_BV(COM2A0) | \_BV(COM2B1) | \_BV(WGM20); TCCR2B = \_BV(WGM22) | \_BV(CS22)| \_BV(CS21)| \_BV(CS20); // OCR2A = 156; OCR2B = 4; // ocr2b = 20 is 2.56ms pulse width // ocr2b = 10 is 1.28ms pulse width // ocr2b = 11 is 1.42ms pulse width // these values are important for driving servos and brushless motor // using an ESC, because the 180 degree servo arm rotates between // about 1ms and 2ms pulse width at 50herz ,,, \subsection{Bit Banging Pwm} The phrase 'bit banging pwm' refers to creating a pulse width modulation 'manually', by writing a high signal for a specified amount of time and then writing a low signal to the same pin for the remaining time of the duty cycle. There is more coding required to achieve this, compared to using the PWM mode of one of the timers, but it is useful when attempting to drive multiple servo motors, dimming leds, brushless motors, etc. Also, it is useful for learning about how pulse width modulation works. It is also handy when all of the uC timers are occupied doing something else. There are various options for 'bit banging' including using a timer or timers to generate the delays, or using a delay loop (which prevents the micro-controller from doing anything else). One technique for software PWM is to assign ``time windows'' to each servo, which are staggered over the length of the wave period. So, if the frequency of the wave is 50hz, then the period of the wave is 20ms. We can assign a 1ms time window for each servo. In that window, we switch on the pulse for the next servo at the beginning of the window and switch off the pulse for the previous servo, at some point in the window, to provide the correct pulse width. This technique allows for driving 20 servos from one timer. \emph{ Bit banging in arduino c } ------- void setup() $\{$ pinMode(13, OUTPUT); $\}$ void loop() $\{$ digitalWrite(13, HIGH); delayMicroseconds(100); // Approximately 10\% duty cycle @ 1KHz digitalWrite(13, LOW); delayMicroseconds(1000 - 100); $\}$ ,,,, \emph{ Bit bang pwm example } \begin{lstlisting} .include "m328Pdef.inc" ;atmega328 definitions .def temp = r16 ;general purpose accumulator .dseg .cseg ; 50 Herz wave with variable pulse width main: rjmp main \end{lstlisting} \subsection{Phase Correct Pwm} The timer counts up, toggles off the output when compare/match value is reached, continues counting to 0xFF, then starts to count down, then toggles again the output when compare/match value is reached on the down-count. This create a symetrical wave with the phase always aligned. \subsection{Fast Pwm} The timer/counter only counts up (as with other timer modes). In phase correct timer counts up and down. The timer counts up and toggles the output when it reaches the compare/match value. Then it *keeps* counting until it reaches overflow 0xFF or 0xFFFF. This is how the ``duty cycle'' (time high/ time low) is varied, by varying the value in the compare/match value. Using this technique we can create a sound signal. The pwm mode is different from the ctc mode because in ``ctc'' mode, when the compare/match value is reached, the timer is reset to zero, and then continues to count up. A table of sound samples (usually 8kHz 8 bits) is read and the duty cycle is varied in the overflow interrupt with the timer in PWM mode. This is very similar to the ctc (clear-timer-on-compare) mode The code below needs to be modified because it is unreadable, since it uses bitmaps instead of bit-names for configuration. \emph{ Attiny13/atmega328 pwm mode (toggle) timer0 } \begin{lstlisting} ; code seems incomplete, where is pulse width ;.include "tn13def.inc" ;(attiny13 definitions) .nolist .include "m328Pdef.inc" ;atmega328 definitions .def temp = r16 ;general purpose accumulator .dseg .cseg .org 0x00 sbi DDRB, 0 ;set oca0/portb.0 for output ldi A, 0b01000011 ;set to ctc mode out TCCR0A, temp ldi A, 0b00001101 ;set prescaler/divider to /1024 out TCCR0B, temp ldi A, 128 ;set the compare register out OCR0A, temp ;to 128 here: ; the micro can do something else here rjmp here \end{lstlisting} \subsection{Interrupts In Pwm Mode} Interrupts are also available in pwm mode, which is interesting. This means we can do pwm to any pin on the arduino In the TIMSK0 register there are bits for OCIE0A, OCIE0B, and TOIE0 so we can generate an interrupt on compare match and also overflow meaning that we can do pwm to any pin. This means we can experiment with dimming the led at pin13 \emph{ An example in c } ------ // enable the timer/counter match interrupt TIMSK2 |= (1$<$$<$OCIE2B); // Output compare B match // clear interrupt flag TIFR2 |= (1$<$$<$OCF2B); // enable global interrupts enabled in main startup code // sei(); ,,,, PULSE WIDTH MODULATION IN C ... \emph{ Pwm code for an atmega8 } \begin{lstlisting} #define fillrate OCR2A // main() PORTB=0x00; DDRB=0x08; //We use PORTB.3 as output, for OC2A, see the atmega8 reference manual // Mode: Phase correct PWM top=0xFF // OC2A output: Non-Inverted PWM TCCR2A=0x81; // Set the speed here, it will depend on your clock rate. TCCR2B=0x02; // for example, this will alternate between 75% and 42% PWM while(1) { fillrate = 191; // ca. 75% PWM delay_ms(2000); fillrate = 107; // ca. 42% PWM delay_ms(2000); } \end{lstlisting} \section{Square Waves} A square wave is a wave with a 50\% duty cycle, meaning that it spends equal time high and low. This can be achieve with an automatic ``toggle'' on a certain pin, so that no ISR interrupt service routine is necessary. However this means that a particular pin, must be used for output. We could use this square wave to drive a piezo speaker, for example and produce a tone. We can then do other things with the micro in the main loop. \emph{ Set up 38Khz square wave with timer 1 on uno, ctc on compare match, oc1a pin toggle } -------- // timer1 initialization noInterrupts(); // disable all interrupts TCCR1A = 0; TCCR1A |= (1 $<$$<$ COM1A0); // setup OC1A pin in toggle mode TCCR1B = 0; TCCR1B |= (1 $<$$<$ WGM12); // clear timer/counter 1 on compare match TCCR1B |= (1 $<$$<$ CS10); // no prescaler (timer clock = system clock) TCNT1 = 0; // reset counter OCR1A = 210; // set value on TCNT1 for given IR frequency (AVRCLOCK/IRFREQUENCY/2) // no dont enable interrupts here! // TIMSK1 |= (1 $<$$<$ OCIE1A); // enable timer compare interrupt TIFR1 = (1$<$$<$OCF1B); interrupts(); // enable all interrupts // main loop // .... ,,, \section{Time} The arduino C millis() function returns a long unsigned integer. This can deal with time periods up to approximately 50 days. \section{Wave Forms} ``wave'' is a combination of signal on and signal off. We normally think of a wave as curvy, but digital waves arent. ``Duty Cycle'' is time-high/time-low (as a percentage) ``Period'' is the time for one complete wave. ``Frequency'' number of waves per second (Herz) ``square wave'' wave (on/off) with duty cycle 50\%. That is equal time on and off. Mazidi page 518 (input capture and wave generation in avr) has an example of wave generation using a wave table. This appears to be a digital wave (0/1) which could imitate a sine wave by on/off time variation. The technique below could make a sine wave for music generation (or led dimming?) But this should be pwm, no? \emph{ Wave generation using timer0 compare/match toggle and interrupt } --------- ; untested 2020 .include ``m328Pdef.inc'' .def counter = r29 .def temp = r18 .org 0x0 rjmp main .org OC0Aaddr ; timer/counter0 compare match A vector rjmp timerMatch main: ; auto initialize stack sbi DDRB, 5 ; pb5 as output begin: ; Set the Timer0 Mode to CTC clear timer on compare mode ldi temp, 1$<$$<$WGM01 out TCCR0A, temp ldi temp, 69 out OCR0A, temp ; initial compare/match value = 69 ; set prescaler to 64 and start the timer ldi temp, 1$<$$<$CS01 | 1$<$$<$CS00 out TCCR0B, temp ldi temp, 1$<$$<$OCIE0A sts TIMSK0, temp ; enable compare/match interrupt sei ; global enable interupts here: ; the micro can do other work here rjmp here ; timer0 match ISR timerMatch: ;sbi PORTB, 5 sbic pinb, 5 ; cbi portb, 5 ; sbis pinb, 5 ; sbi portb, 5 ; dec counter ; brpl next ; if counter $<$ 0 reset table pointer Z to table(0) ldi zh, high(wavetable*2) ldi zl, low(wavetable*2) ldi counter, 4 ; length of table next: lpm r28, Z+ ; load program memory (wave table) out OCR0A, r28 ; update compare/match value for timer0 reti ; return from ISR ; the wave table has alternating high/low values for the wave ; period=value+1 eg 25 high, 50 low, 40 high wavetable: .db 24, 49, 39, 34 ,,, \section{Timers And Counters} https://sites.google.com/site/qeewiki/books/avr-guide/timers-on-the-atmega328 The best site. Examples in C, with tables and explanations. And prescaler lists. https://gist.github.com/Wollw/2381139 atmega328 timer examples in C. https://www.avrfreaks.net/forum/tut-c-newbies-guide-avr-timers?page=all a good guide in C. with ctc in interrupt mode. Looks like code for atmega328p Timer/Counters are used to generate delays (from microseconds up to several seconds) for doing things like blinking lights or driving piezo speakers. They are also used for generating square wave forms (with a given frequency and duty cycle). These wave forms can be used to drive speakers or motors. When used with an ISR (interrupt service routine), they can be used to ``multitask'' in the microcontroller. That is, the microcontroller can perform several tasks at once, such as driving a speaker and transfering data. Timers, when they use an external clock are referred to as ``counters''. In normal mode we load the timer/counter with an initial value and wait until it ``overflows'' (reaches 255 or 2\^{}16). We can check this overflow using the TOVn (Timer overflow flag). In CTC mode (Compare match mode) we load a match value and the timer/counter counts up from zero until the match value is reached. We wait for this by watching a different flag. (Or set an interrupt to fire when the value is matched). On the face of it, there is not much practical difference between the 2 modes. However, CTC mode is slightly simpler because we do not need to reload the counter with its start value after each overflow. And if an interrupt is used, we do not have to reset the compare/match flag after each match. page 346 mazidi, use timer1 to toggle led once per second Example 9.33 http://electronics.stackexchange.com/questions/2057/polyphonic-sounds-from-a-microcontroller some timer info timer (or counter) in normal mode, counts 0 to 255 then sets overflow. \subsection{Frequencies And Periods} frequency is in herz, periods in seconds or milliseconds. \emph{ Some frequencies in the world } ---- human pulse - 1-2hz a440 musical note - 440hz Amethyst wood-star hummingbird wing rate - 80hz Giant hummingbird wing rate - 10 - 15hz ,,, \subsection{Jargon Acronyms And Terminology} There is a lot of this \emph{ Terminology } ----- duty cycle - percentage of time high/time low for wav period - time taken for one wave (seconds) frequency - number of waves per second (herz) ,,,, ctc - ``clear timer on compare'' mode pwm - ``pulse width modulation'' mode \emph{ The meaning of the bit names } ----- WGMnx ``waveform generation mode'' bits select timer mode (eg fast pwm, ctc) n = timer number (0,1,2) x = bit number (0,1,2...) COM ``compare match output'' bits select/invert output pin CSnx ``clock select'' selects the prescalar and starts the timer. n = timer number (0,1,2) x = bit number (0,1,2...) ,,, \subsection{Stopping Timers} Load 0 into the clock select bits to stop any of the timers \subsection{Timer Modes} This is a quick summary of the different timer/counter modes All these modes can be used with an interrupt on overflow/compare-match or by polling flags. Timer0 and Timer2 are 8bit, Timer1 is 16bit normal: load counter with something (or 0). start counter. counter counts up to overflow (0xFF for 8 bit timers, 0xFFFF for 16 bit) Overflow triggers overflow flag in TIFR0/1/2 or triggers interrupt. Either poll the overflow flag, or write an interrupt service routine to do something. ctc: ``clear timer on compare''. Put the ``compare'' value in OCR$<$n$>$ (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$<$n$>$ 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$<$n$>$ 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 \subsection{Gotchas} Address arithmetic in avr assembler is tricky because the compiler puts in extra zero to pad out odd numbered addresses. 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. \emph{ Error, need to shift all bit names } \begin{lstlisting} ldi r16, 0 | WGM01 ; error! \end{lstlisting} \begin{lstlisting} ldi r16, 0 | 1<$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 \emph{ 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$<$$<$COM0A0 | 1$<$$<$WGM01; ldi r20, 1$<$$<$WGM01 ; out TCCR0B, r20 ; load Timer control reg ldi r20, 1$<$$<$CS00 out TCCR0B, r20 ; load Timer control reg and start counting/toggling ; TCCR0B = 1$<$$<$CS00; here: ; We can do other work with the micro here, while timer2 is ; busy toggling pin pd7 rjmp here ,,, Note: Timer2 prescalar values are different to Timer0 prescalar values (although both are 8 bit). \emph{ Generate a square wave on timer2 with normal mode toggle } --------- ; Not working... .include ``m328Pdef.inc'' sbi ddrd, 7 ; OC2 (PD7) as output ldi r22, 100 out OCR2, r22 ; set the match value ldi r22, 0x11 ; com21:20=toggle, mode=normal, no prescale out TCCR2, r22; load Timer control reg and start counting/toggling here: ; We can do other work with the micro here, while timer2 is ; busy toggling pin pd7 rjmp here ,,, Below is retrodan code, but this retrodan code doesnt seem to work on atmega328. Uses magic numbers. NO good \emph{ Attiny13/atmega328 ctc automatic wave-form mode (toggle) timer0 } \begin{lstlisting} ;.include "tn13def.inc" ;(attiny13 definitions) .include "m328Pdef.inc" ;atmega328 definitions .def A = r16 ;general purpose accumulator .org 0x00 sbi DDRB, 0 ;set oca0/portb.0 for output ldi A, 0b01000010 ;set to ctc mode out TCCR0A, A ldi A, 0b00000101 ;set prescaler/divider to /1024 out TCCR0B, A ldi A, 128 ;set the compare register out OCR0A, A ;to 128 ; also need to load the clock last I think here: ; the micro can do something else here rjmp here \end{lstlisting} \subsection{Prescaler For Timers} Prescaler, or divider, divides the system clock by a given value. Prescaler values are limited to the following. The higher the prescaler value, then the slower the timer with count up to 256 The prescaler values are selected with the CSn$\{$0,1,2$\}$ bits in the "B" control register for the 3 atmega328 timers. Timerkj The prescaler value is set in the ``Timer/Counter Control Register'' (TCCR0B in the atmega328 used in the many arduinos) \arrayrulecolor{gray} \begin{center} \begin{tabular}{ |rl| } \multicolumn{2}{c}{\textbf{ prescalar values for timer0/1 (n below is 0 or 1) }} \\ \hline \texttt{ 1 } & TCCRnB = 0b0000001 (1$<$$<$CSn0) eg: ldi temp, 1$<$$<$CS00 \\ \texttt{ 8 } & TCCRnB = 0b0000010 (1$<$$<$CSn1) \\ \texttt{ 64 } & TCCRnB = 0b0000011 (1$<$$<$CSn1) | (1$<$$<$CSn0) \\ \texttt{ 256 } & TCCRnB = 0b0000100 (1$<$$<$CSn2) \\ \texttt{ 1024 } & TCCRnB = 0b0000101 (1$<$$<$CSn2) | (1$<$$<$CSn0) \\ \hline \end{tabular} \end{center} Timer1 control register uses STS not OUT ! \emph{ Set prescalar to 1024 for timer1 } ----- ldi temp, 1$<$$<$CS12 | 1$<$$<$CS10 sts TCCR1B, temp ,,,, \emph{ Set prescalar to 64 for timer1 } ----- ldi temp, 1$<$$<$CS11 | 1$<$$<$CS10 sts TCCR1B, temp ,,,, Timer2 has more prescaler values which gives more flexibility for the frequencies generated. \arrayrulecolor{gray} \begin{center} \begin{tabular}{ |rl| } \multicolumn{2}{c}{\textbf{ prescalar values for timer2 }} \\ \hline \texttt{ 1 } & TCCR2B = 0b0000001 (1$<$$<$CS20) eg: ldi temp, 1$<$$<$CS20 \\ \texttt{ 8 } & TCCR2B = 0b0000010 (1$<$$<$CS21) \\ \texttt{ 32 } & TCCR2B = 0b0000011 (1$<$$<$CS21) | (1$<$$<$CS10) \\ \texttt{ 64 } & TCCR2B = 0b0000100 (1$<$$<$CS22) \\ \texttt{ 128 } & TCCR2B = 0b0000101 (1$<$$<$CS22) | (1$<$$<$CS10) \\ \texttt{ 256 } & TCCR2B = 0b0000110 \\ \texttt{ 1024 } & TCCR2B = 0b0000111 \\ \hline \end{tabular} \end{center} \emph{ Set the prescaler } \begin{lstlisting} ldi r16, 0b00000011 ; set timer prescaler = 64 out TCCR0B, r16 ; timer control register B \end{lstlisting} \subsection{Calculate Delays} This is confusing. Just use Freq=1/Period and the usual Freq formula Delays are easy! How many clocks per second? That clock frequency / prescalar. Now, how many clocks until overflow or compare/match? Divide the second by the first and that is the time delay. An the wave frequency is just 1/(2*time delay) 2* because square wave has high then low. \emph{ Calculate the delay generated by timer0 } for example: Time for each square wave = 2 (on/off state) * (ticks-until-overflow + instruction cycles) * prescalar / xtal (crystal frequency) ticks-until-overflow is 255-TCNT0+1 instruction cycles is sum of each instruction clock cycles For example 9.3 (TCNT0=0xf2) the delay calculation is (on atmega328 16 Mhz) T = 2 * (14 + 24) * 1 / 16 Megaherz = 2 * 38 / 16 Megaherz = 38 / 8000000 seconds = 38000000/8000000 microseconds = 38/8 useconds Frequency = 1/T = 8000000/38 Hz = 210.526 kHz In forth ``14 24 + 2 * 100 * 16 /'' gives the period in microseconds to 2 decimal places With prescaler=1024 and TCNT0=0xf2 the delay calculation is (atmega328 16 Mhz) in forth no this gives an overflow in a 16 bit forth!! 14 24 + 2 * 100 * 1024 * 16 / (30.72 microseconds) Freq= 1/T = 100000000 / 3072 = 32.552 kHz ?? no it should be 206 Herz (A440 is 440 herz) \subsection{Timer Two} Timer2 has more prescale values than timer0, but is 8 bit This would allow better approximations for some tones (eg: use prescale 128 for A440). BLINKING TIMERS ... \emph{ Blinks a led using the timer0 (counter) with prescaler } \begin{lstlisting} ; blinks an LED which is connected to PB5 (digital out 13) .include "m328Pdef.inc" .def temp = r16 .cseg .org 0 rjmp start start: ; freq = Fosc / (N*top) ; freq = 16000000/(1024*256) = 61.035 hz ; this is the minimum frequency for timer0 and timer2 ; find sleep time ; sleep time = period = 1/freq = 1/61,035 = 16ms sbi DDRB, 5 ; set portb, bit 5 for output ldi temp, 0 | 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) ; Better to use the minimum prescalar eg ; freq = Fosc/(2*N*top) ; 440 = 16000000/(2*256*top) ; top = 16000000/(2*256*440) = 71 ; "freq" refers to the frequency of the square wave and NOT the ; frequency of the interrupt (which is double) ; .def zero = r2 .def temp = r18 .dseg .cseg .org 0x0 rjmp main .org OC0Aaddr ; timer0 compare match interrupt vector jmp timerMatch main: ; assume auto stack init clr zero ; set register := 0 and dont change it! sbi DDRB, 5 ; make portB.5 output (arduino pin13 led) begin: ; wgm01 ctc "clear timer on compare" mode 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 } \end{lstlisting} 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. Output pins are fixed in toggle mode, see the table above oc1a is arduino pin 9 \emph{ C code to initiate timer1 in output toggle mode } \begin{lstlisting} TCCR0A |= (1 << COM0A0); \end{lstlisting} \emph{ Assembly code to enable timer0 output toggle } ------- ldi temp, 1$<$$<$COM0A0 ; enable Timer0 output pin toggle sts TCCR0A, temp ; or use out for the atmega328 out TCCR0A, temp ,,, The output toggle below must be connected to pin PD6 (PORTD, 6) because this is the hardware output pin for Timer0 channel A We can see this from the ``pin out'' diagram of an Atmega328p chip. The output pin of Timer0, channel A is labelled OC0A. The code below is the most succinct way to create a square wave or or drive a piezo speaker. It will probably not dim a led much, and the flashing of the LED may not be visible. Use PWM to dim leds. Use OUT, not STS for Timer0 (for timers 1 \& 2 used STS because they are memory mapped!) OCR0A value for C4=262 Hz n+1 = 16MHz / (256 * 2 * T(freq) ) n+1 = 16000000 / (512 * 262) n $<$$>$ 118 \emph{ Timer0 CTC mode output toggle with 262Hz wave on 16Mhz chip } -------- .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: ; assume stack pointer is auto initialised clr zero ; set register := 0 and dont change it! sbi DDRD, 6 ; make portD.6 output (arduino pin6) begin: ldi temp, 118 ; gives Tone C4 (262 Hz) square wave on 16Mhz chip ; with /256 prescalar. Tone C4= 261.63hz well-tempered out OCR0A, temp ; load compare value ldi temp, 1$<$$<$COM0A0 | 1$<$$<$WGM01 ; enable Timer0 output pin toggle ; and clear-timer-on-compare mode ``ctc'' out TCCR0A, temp ; no interrupts needed. ; bit names for portability and readability ; wgm01:00=0b10 (``ctc'' mode), CS02:00=0b100 (clock/256 prescaler) ldi temp, 1$<$$<$CS02 ; prescalar = 256 Clock Select bits out TCCR0B, temp ; loading the clock starts the timer ; stop the timer by loading 0 into CS bits here: ; micro can do other work here. Its not tied up! rjmp here ,,, \subsection{Timer One} The atmega328 has 3 timer/counters. Timer1 is the second and is a 16 bit counter, so can count from 0 to 64K. This is useful for producing ``visible'' delays, that is, delays that can be seen when toggling an led, for example. The 8 bit timer delays tend to be in the milliseconds even when using the maximum prescaler. \emph{ One millisecond delay with timer1 } -------- .include ``m328Pdef.inc'' .def zero = r19 .def temp = r20 .dseg .cseg .org 0 rjmp main main: clr zero ; set register to 0 and dont change ldi r16, high(ramend) out SPH, r16 ldi r16, low(ramend) out SPL, r16 sbi DDRB, 5 begin: sbi portb, 5 rcall delay1milli cbi portb, 5 rcall delay1milli rjmp begin delay1milli: ldi r20, high(65536-8000) sts TCNT1H, r20 ; timer1 counter reg (high byte) ldi r20, low(65536-8000) sts TCNT1L, r20 ; timer1 counter reg (low byte) ldi r20, 0x0 sts TCCR1A, r20 ldi r20, 0x1 sts TCCR1B, r20 again: in r20, TIFR1 ; check if overflow has occurred sbrs r20, TOV1 ; if so, exit loop rjmp again ; if not, wait and check ldi r20, 1$<$$<$TOV1 ; reset the overflow flag by setting to 1 out TIFR1, r20 sts TCCR1B, zero sts TCCR1A, zero ret ,,, example 9.33 from mazidi The actual delay time below is 0.25 of a second. So the square wave period is 0.5 seconds and frequency= 1/period = 2Hz This is ``Normal'' mode, so the timer does not reset when it gets to the compare value, but we can still poll the compare flag in normal mode (and maybe even spark an interrupt on compare?) So it is a kind of fake CTC mode (clear timer on compare) \emph{ Make a 2Hz square wave on 16Mhz chip with normal mode. Polls compare flag } -------- .include ``m328Pdef.inc'' ; ``zero'' should make code more readable ; we use PORTB.5 below because arduino boards often have ; an led on the board at that pin (pin 13) ; Code below working on arduino uno 16MHz but flashes twice ; per second .def zero = r2 .def temp = r18 .dseg .cseg 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, 5 ; make portB.5 output (arduino pin13) begin: sbi PORTB, 5 ; turn on LED (high signal) rcall delay cbi PORTB, 5 ; clear bit: turn off LED rcall delay rjmp begin delay: ; actually delay time is 1/4 second. ldi r20, high(62500-1) ; gives 2Hz square wave on 16Mhz chip ; with /64 prescalar sts OCR1AH, r20 ; load compare value (62500) high byte ldi r20, low(62500-1) sts OCR1AL, r20 ; load compare value (62500) low byte sts TCNT1H, zero ; set counter to 0 (high+low bytes) sts TCNT1L, zero sts TCCR1A, zero ; ; better to use bit names here ; wgm13:12=00, normal mode, CS11:10=0b11 (i.e clock/64) ldi r20, 0 | (1$<$$<$CS11) | (1$<$$<$CS10) sts TCCR1B, r20 ; loading the clock starts the timer checkflag: in r20, TIFR1 ; check timer compare/match flag in TIFR1 sbrs r20, OCF1A ; OCF1A is the compare/match flag rjmp checkflag ; keep polling ldi r20, 1$<$$<$OCF1A ; reset the match flag, by writing 1 out TIFR1, r20 ; write to the timer1 flag register sts TCCR1B, zero sts TCCR1A, zero ; stop the timer ret ,,, TIMER1 CTC CLEAR TIMER ON COMPARE MODE .... modify the code above to use CTC and then an interrupt The code below is simpler than the above because we do not have to reset the counter, nor do we have to stop the timer/counter. Even simpler is an interrupt. Using an interrupt the compare/match flag is automatically cleared. \emph{ 2Hz wave on 16Mhz chip Timer1 in CTC mode, polling compare/match flag } -------- .include ``m328Pdef.inc'' ; ``zero'' should make code more readable ; we use PORTB.5 below because arduino boards often have ; an led on the board at that pin (pin 13) ; Code below working on arduino uno 16MHz but flashes twice ; per second .def zero = r2 .def temp = r18 .dseg .cseg 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, 5 ; make portB.5 output (arduino pin13) begin: ; actually delay time is 1/4 second. ldi temp, high(62500-1) ; gives 2Hz square wave on 16Mhz chip ; with /64 prescalar sts OCR1AH, temp ; load compare value (62500) high byte ldi temp, low(62500-1) sts OCR1AL, temp ; load compare value (62500) low byte sts TCCR1A, zero ; no configuration ; use bit names for portability and readability ; wgm13:12=01 (ctc mode), CS11:10=0b11 (i.e clock/64) ldi temp, 0 | 1$<$$<$CS11 | 1$<$$<$CS10 | 1$<$$<$WGM12 sts TCCR1B, temp ; loading the clock starts the timer checkflag: in r20, TIFR1 ; check timer compare flag in TIFR1 sbrs r20, OCF1A ; OCF1A is the compare flag rjmp checkflag ; keep polling ldi r20, 1$<$$<$OCF1A ; reset the compare/match flag, by writing 1 out TIFR1, r20 ; write to the timer1 flag register sbic PINB, 5 ; toggle portb.5 (pin 13 arduino) cbi PORTB, 5 sbis PINB, 5 sbi PORTB, 5 rjmp checkflag ,,, Working 2 august 2018 \emph{ Ctc interrupts make 2Hz wave on 16Mhz chip Timer1 } -------- .include ``m328Pdef.inc'' ; we use PORTB.5 is arduino pin 13 with on board LED ; portb.0 is arduino pin 8 ; portd.0 is arduino pin 0 .def zero = r2 .def temp = r18 .dseg .cseg .org 0x0 rjmp main .org OC1Aaddr ; timer1 compare match interrupt jmp timer1match 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, 5 ; make portB.5 output (arduino pin13 led) begin: ; actually delay time is 1/4 second. ldi temp, high(62500-1) ; gives 2Hz square wave on 16Mhz chip ; with /64 prescalar sts OCR1AH, temp ; load compare value (62500) high byte ldi temp, low(62500-1) sts OCR1AL, temp ; load compare value (62500) low byte sts TCCR1A, zero ; no configuration ; enable the ctc interrupt, put the toggle in the isr ldi temp, 1$<$$<$OCIE1A sts TIMSK1, temp ; enable compare/match interrupt sei ; global enable interupts ; use bit names for portability and readability ; wgm13:12=01 (``ctc'' mode), CS11:10=0b11 (clock/64 prescaler) ldi temp, 0 | 1$<$$<$CS11 | 1$<$$<$CS10 | 1$<$$<$WGM12 sts TCCR1B, temp ; loading the clock starts the timer here: ; micro can do other work here. Its not tied up! rjmp here timer1match: ; interrupt automatically clears the compare/match flag OCF1A sbic PINB, 5 ; toggle portb.5 (pin 13 arduino) cbi PORTB, 5 ; turn off led/wave sbis PINB, 5 sbi PORTB, 5 ; turn on led/wave reti ,,, \emph{ C code for timer1 ctc mode with an interrupt } \begin{lstlisting} #include #include int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode ; but TIMSK is TIMSK1 in Assembly!! TIMSK |= (1 << OCIE1A); // Enable CTC interrupt sei(); // Enable global interrupts OCR1A = 15624; // Set CTC compare value to 1Hz at // 1MHz AVR clock, with a prescaler of 64 TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { // do other work here } } ISR(TIMER1_COMPA_vect) { PORTB ^= (1 << 0); // Toggle the LED } \end{lstlisting} TOGGLE IN CTC MODE FOR TIMER1 .... In the automatic mode we can dispense with the interrupt altogether. So we dont need to enable interrupts, or write an ISR. In timer one set automatic set/clear/toggle of output pin. COM1A1:COM1A0 bits in Timer1 control register TCCR1A I am not sure where to find output pins, but oc1a is arduino pin 9 = output pins for automatic toggle .. PB3 = OC0 - timer0 output pin (arduino pin ?) .. PD4 = OC1B - timer1 channel B pin (arduino pin ?) .. PD5 = OC1A - timer1 channel A outpin (arduino pin 9) .. \emph{ C code to initiate timer1 in output toggle mode } \begin{lstlisting} TCCR1A |= (1 << COM1A0); \end{lstlisting} \emph{ Assembly code to enable timer1 output toggle } ------- ldi temp, 1$<$$<$COM1A0 ; enable Timer1 output pin toggle (channel A) sts TCCR1A, temp ,,, The output toggle below must be connect to PB1 (arduino pin 9) because this is the hardware output pin for Timer1 channel A The code below is the most succinct way to create a square wave/ toggle an LED, or drive a piezo speaker. We could develop this sketch, by storing the match value in a data variable in dseg, and then reading the serial connection for keystrokes. When a keystroke is detected, the match value is altered, so that the tone emited by the piezo, or the led flash rate, is changed. This would create a very simple piezo ``keyboard''. \emph{ Ctc output toggle with 2Hz wave on 16Mhz chip 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(62500-1) ; gives 2Hz square wave on 16Mhz chip ; with /64 prescalar sts OCR1AH, temp ; load compare value (62500) high byte ldi temp, low(62500-1) sts OCR1AL, temp ; load compare value (62500) low byte ldi temp, 1$<$$<$COM1A0 ; enable Timer1 output pin toggle (channel A) sts TCCR1A, temp ; no interrupts needed. ; bit names for portability and readability ; wgm13:12=01 (``ctc'' mode), CS11:10=0b11 (clock/64 prescaler) ldi temp, 0 | 1$<$$<$CS11 | 1$<$$<$CS10 | 1$<$$<$WGM12 sts TCCR1B, temp ; loading the clock starts the timer here: ; micro can do other work here. Its not tied up! rjmp here ,,, Timer1 prescaler \begin{lstlisting} CS12:11:10 = 0b101 (prescaler = 1024 the maximum) \end{lstlisting} \emph{ 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$<$$<$COM1A0 ; enable Timer1 output pin toggle (channel A) sts TCCR1A, temp ; no interrupts needed. ; bit names for portability and readability ; wgm13:12=01 (``ctc'' mode), CS12:11:10=0b101 (clock/1024 prescaler) ; frequency = 16 MHz/ 1024 / 40 = approx 400 Hz (audible tone) ldi temp, 0 | 1$<$$<$CS12 | 1$<$$<$CS10 | 1$<$$<$WGM12 sts TCCR1B, temp ; loading the clock starts the timer here: ; micro can do other work here. Its not tied up! rjmp here ,,, \subsection{Ctc Clear Timer On Compare Mode} This is poor code because it doesnt use bit names for the control registers. \emph{ Clear timer on compare for attiny13 } \begin{lstlisting} .include "tn13def.inc" ;(attiny13 definitions) .def A = r16 ;general purpose accumulator .org 0x00 main: sbi DDRB, 0 ;set portb0 for output ldi A, 0b01000010 ;activate ctc mode out TCCR0A, A ;set flag on compare match ldi A, 0b00000101 ; out TCCR0B, A ;set prescaler to /1024 ldi A, 128 ;our compare value out OCR0A, A ;into the compare register here: ; this line seems dodgy! need to EOR the portB no?? sbi PINB, 0 ;flip the 0 bit rcall pause ;wait rjmp here ;go back and do it again pause: ploop: in a, TIFR0 ;wait for timer andi a, 0b00000100 ;(ocf0a) breq ploop ldi a, 0b00000100 ;clear flag out tifr0, a ;note: write a 1 (not 0) ret \end{lstlisting} TIMER1 INTERRUPTS .... compare/match a, compare/match b, overflow, capture event. \section{Delays} It is a surprisingly common task to do nothing for a short period of time. MCUs are often too fast (16Mhz !!) and need to be slowed down. \emph{ Blink on-board led using an approximate 1/2 second delay loop } \begin{lstlisting} ; turns on an LED which is connected to PB5 (digital out 13) ; testing ; with r18=40 flashed 20 times in about 18.73 seconds (too fast) ; with r18=45 flashed 30 times in about 32 seconds (too slow) ; with r18=42 flashed 20 times in about 19.52 seconds (not bad) .nolist .include "m328Pdef.inc" .list .dseg .cseg .org 0 sbi DDRB, 5 ; data direction register for portB, bit5 is output main: sbi PORTB, 5 ; turn on bit5 in port b (pin 13 arduino) rcall delay cbi PORTB, 5 ; clear bit5, turn off led rcall delay rjmp main ; An approximate half second delay delay: ; to calculate accurately the delay here involves looking ; at the number of cycles for each instruction. clr r16 ; set reg16 := 0 clr r17 ldi r18, 42 here: ; a delay loop 42*256^2 instructions dec r16 ; brne here dec r17 brne here dec r18 brne here ret \end{lstlisting} Apparently the arduino ``millis'' function uses timer0 \emph{ Use timer0 for 1millisecond delay } \begin{lstlisting} .nolist .include "m328Pdef.inc" .list .dseg .cseg .org 0 rjmp start start: sbi DDRB, 5 ; data direction register for portB, bit5 is output \end{lstlisting} \section{Ports} \subsection{Toggling Ports} A common passtime is to toggle the output on a port (although ``pwm'' and ``ctc'' modes of the timer/counters (0,1,2) can do this automatically) \emph{ Toggle value on a port } ------- in temp, PORTB ; not PINB ldi temp2, \$01 ; bit 0 eor temp, temp2 out PORTB, temp ,,, \emph{ Another way... } --------- sbic pinb, 0 ;1 or 2clocks if bit 0 clear, skip next line cbi portb, 0 ;2 if 1, execute this line sbis pinb, 0 ;1-2 clocks if bit 0 high, skip next line sbi portb, 0 ;2 if 0, execute this line ,,, \emph{ A cumbersome way to toggle, but maybe useful elsewhere } \begin{lstlisting} cpi flag, 0 breq zeroflag ; clr flag sbi PORTB, 5 ; turn on LED (high signal) rjmp checkflag zeroflag: cbi PORTB, 5 ; turn on LED (high signal) inc flag \end{lstlisting} \section{Arduino} \emph{ Install the arduino java software } \begin{lstlisting} sudo apt-get install arduino \end{lstlisting} Then select the serial device and board (eg duemilenove) Then select file/preferences and check box for verbose info during upload \emph{ Run arduino software with priviledges } \begin{lstlisting} sudo arduino \end{lstlisting} If you dont have the right priviledges the serial port will be greyed out. \section{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. \section{Uploading Code To The Arduino} The arduino, or any AVR chip with the arduino bootloader installed, contains a bootloader which checks for code to receive over a serial (usb...) connection when the MCU is reset. This is convenient because we do not need a ``ISP'' programmer to program the arduino. \subsection{Command Line Upload} avrdude is the command line tool used to upload code to an arduino board. One 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) \emph{ Upload to duemilenove arduino on linux } \begin{lstlisting} sudo avrdude -v -v -v -v -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:hello.hex:i \end{lstlisting} Notice here the device and baud rate are different. \emph{ Upload to ``uno'' compatible freetronics arduino board } \begin{lstlisting} sudo avrdude -p atmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:hello.hex:i \end{lstlisting} If we use the wrong baud rate, avrdude gives messages, \begin{lstlisting} avrdude: stk500_recv(): programmer is not responding \end{lstlisting} \begin{lstlisting} avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00 \end{lstlisting} \emph{ The same without verbose output } \begin{lstlisting} sudo avrdude -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:hello.hex:i \end{lstlisting} \emph{ Avrdude upload command for ``uno'' board connected via usb } \begin{lstlisting} 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 \end{lstlisting} \emph{ Below is a typical avrdude command from the ide } \begin{lstlisting} /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 \end{lstlisting} \arrayrulecolor{gray} \begin{center} \begin{tabular}{ |rl| } \multicolumn{2}{c}{\textbf{ avrdude options }} \\ \hline \texttt{ -c the programmer } \\ \texttt{ -P the serial port } \\ \hline \end{tabular} \end{center} \subsection{Problems Uploading} \subsection{Serial Buffers Flooded} Flooding of the serial buffers is another cause of the ``not in sync'' error for avrdude (as below). This should only occur when you have uploaded code to the arduino that reads/writes the usart serial connection. Perhaps first check other possible causes, such as a bad usb cable, arduino board wrongly specified, wrong baud rate etc etc. ---- avrdude: stk500\_recv(): programmer is not responding avrdude: stk500\_getsync() attempt 3 of 10: not in sync: resp=0x00 ,,,, Th techique below worked for me when all else had failed. John Wasser on forum.arduino.cc suggested the following solution for uploading a sketch/code when the serial buffers are flooded (by runaway serial usart read/write code) - Connect the arduino with a good usb cable to the computer - hold down the reset button on the arduino - keep holding down the reset button and disconnect the usb cable (this clears the computers usb buffers - reconnect the usb cable (still holding down the arduino reset button) - upload the sketch/code (for example to a duemilenova board \begin{lstlisting} sudo avrdude -p atmega328p -carduino -P/dev/tty.usbserial-A8008Ghy -b57600 -D -Uflash:w:test.hex:i \end{lstlisting} - as soon as the rx light on the arduino blinks, release the reset button. \subsection{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... \emph{ 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: \emph{ The serial port is flooded and cannot receive the sketch/code } to upload. This is usually caused by code that reads/writes to the serial usart. See above for the solution. \emph{ Serial port chosen badly. eg ttyS0 instead of ttyACM0 } \emph{ 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. \emph{ Bad ftdi cable connection } \emph{ Wrong ``baud'' rate (if manually set with avrdude) } \emph{ Wrong board type selected (eg uno when using pro mini) } \emph{ 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 \emph{ A ``fried'' board or atmega chip, the worst scenario. } \emph{ ... } \section{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'' \subsection{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/2=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'' \emph{ Use sox to convert to 8bit 8000 samples per second, mono } \begin{lstlisting} sox old.wav -r 8000 -c 1 -b 8 new.wav \end{lstlisting} 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 \subsection{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) \arrayrulecolor{gray} \begin{center} \begin{tabular}{ |rl| } \multicolumn{2}{c}{\textbf{ some note frequencies in Herz }} \\ \hline \texttt{ D } & 294 \\ \texttt{ D\# } & 311 \\ \texttt{ E } & 330 \\ \texttt{ F } & 349 \\ \texttt{ F\# } & 370 \\ \texttt{ G } & 392 \\ \texttt{ G\# } & 415 \\ \texttt{ A440 } & 440 \\ \texttt{ A\# } & 466 \\ \texttt{ B } & 494 \\ OCR0A value for C4=262 Hz n+1 = 16MHz / (256 * 2 * T(freq) ) n+1 = 16000000 / (512 * 262) n $<$$>$ 118 \emph{ 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$<$$<$COM0A0 | 1$<$$<$WGM01 ; enable Timer0 output pin toggle ; and clear-timer-on-compare mode ``ctc'' out TCCR0A, temp ; no interrupts needed. ldi temp, 1$<$$<$CS02 ; prescalar = 256 out TCCR0B, temp ; loading the clock starts the timer here: ; micro can do other work here. Its not tied up! rjmp here ,,, \section{Piezo Buzzers} This is a small device for playing sound. Apparently it can also operate as a sensor. It should be possible to make audible tones on piezo with all atmega328 timers (0,1,2). We can drive these audible tones with the output toggle of ``ctc'' mode of the timers. A piezo can be connected with either polarity. The piezo buzzer can make tunes by simply toggling the output on a pin at a certain frequency. We can also achieve this using ctc mode (clear timer on compare) in ``toggle'' mode, or also using an interrupt to toggle the output. Using an interrupt has the advantage of being able to use any output pin, but if we use the toggle mode in pwm or ctc then we must use the assigned output pins for that particular timer. The automatic toggle is more succinct from a code perspective. We could also use PWM (either ``fast'' or ``normal'') ``pulse width modulation'' mode with a 50\% duty cycle, but I cant think of any advantage over CTC mode. We can use any of the 3 atmega328 timers to drive a piezo speaker. Each timer can drive 2 output pins, but since the frequency on both outputs for a given timer must be the same, I cannot see the utility of this for making a tone on a piezo. circuit: pin13-$>$100R(??)--$>$piezo--$>$Ground We can change the tone by changing the initial values loaded into r17 and r18 \emph{ Buzz a piezo connected to arduino pin 13 then to ground } \begin{lstlisting} .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 \end{lstlisting} \subsection{Piezo Speakers With Timers} It does not seem useful to use PWM mode with timer0 or timer2 because it is not possible to change both the frequency and the duty cycle at the same time?? Using an atmega328 MCU running at 16Mhz, then the minimum frequency for a square wave using CTC mode is about 30Hz. The frequency calculation is given by the usual formula i.e. \begin{lstlisting} formula: freq = Fosc / (2*N*top) \end{lstlisting} Some people give the formula as \begin{lstlisting} formula: freq = Fosc / (2*N*(top+1) \end{lstlisting} where N is prescaler value, and top is the compare match value for the timer Checking with a piezo buzzer and a tuner app, the second formula seems more accurate. In general, it is best to use the minimum prescalar value, for a given frequency, because this will allow a more accurate approximation for the audible tone. When using the internal clock a variation of 1-2\% is normal. Note that timer2 has more prescaler values, which would allow better approximations for some tones (eg: use prescale 128 for A440). So, to obtain A440 (440hz) on a 16Mhz atmega328 (arduino) we use the formula \begin{lstlisting} 440hz = 16Mhz / (2*256*top) \end{lstlisting} \begin{lstlisting} top = 16000000 / (2*256*440) = 71 \end{lstlisting} or use \begin{lstlisting} 440hz = 16Mhz / (2*256*(top+1)) \end{lstlisting} So, the value 71 would be loaded into the OCR0A But if we try to use a prescalar of 64 we get \begin{lstlisting} top = 16000000 / (2*64*440) = 284 \end{lstlisting} and 284 is too big to fit into an 8 bit register (OCR0A). This means that 256 is the minimum prescalar which can achieve a frequency of 440 herz on an 8 bit timer. See the example in the section ``toggle in ctc mode for timer0'' Check this frequency with a guitar tuner \emph{ Timer0 CTC mode output toggle with 440Hz wave on 16Mhz chip } -------- .include ``m328Pdef.inc'' ; connect LED/piezo to Arduino digital pin 6 ; look at pinout of atmega328 to see output pin of timer0 .def temp = r18 ; Testing: ; Using fine tuner" iphone app, ; OCR0A = 71 gives tone freq 434hz ; OCR0A = 70 gives tone freq 440hz ; freq = Fosc/(2*N*(top+1)) ; top+1= 16000000/(2*256*440) ; top+1= 71.02 ; top = 70 (integer) .dseg .cseg .org 0x0 rjmp onreset onreset: ; assume stack pointer is auto initialised sbi DDRD, 6 ; make portD.6 output (arduino pin6) begin: ldi temp, 70 ; gives approx Tone A440 (440 Hz) square wave on 16Mhz chip ; with /256 prescalar. out OCR0A, temp ; load compare value ; WGM bits - waveform generation mode bits, select CTC mode ldi temp, 1$<$$<$COM0A0 | 1$<$$<$WGM01 ; enable Timer0 output pin toggle ; and clear-timer-on-compare mode ``ctc'' out TCCR0A, temp ; no interrupts needed. ; bit names for portability and readability ; CS02:00=0b100 (clock/256 prescaler) ldi temp, 1$<$$<$CS02 ; prescalar = 256 Clock Select bits out TCCR0B, temp ; loading the clock starts the timer ; stop the timer by loading 0 into CS bits here: ; micro can do other work here. rjmp here ,,, \emph{ Play 3 notes with timer0 } -------- .include ``m328Pdef.inc'' ; connect piezo speaker to Arduino digital pin 6 ; and other pin to ground through 100R resistor .def temp = r18 ; Testing: ; Using fine tuner" iphone app, prescalar 256 ; OCR0A = 71 gives tone freq 434hz ; OCR0A = 70 gives tone freq 440hz ; OCR0A = 59 gives tone freq 521hz (error 2hz) ; OCR0A = 60 gives tone freq 512hz ; freq = Fosc/(2*N*(top+1)) ; By this testing we can see that once OCR0A is below 60 the errors ; become large (about 8hz). Timer2 maybe a better option here, because ; of more prescale values (eg 128) .dseg .cseg .org 0x0 rjmp start start: ; assume stack pointer is auto initialised sbi DDRD, 6 ; make portD.6 output (arduino pin6) begin: ; WGM bits - waveform generation mode bits, select CTC mode ldi temp, 1$<$$<$COM0A0 | 1$<$$<$WGM01 ; enable Timer0 output pin toggle ; and clear-timer-on-compare mode ``ctc'' out TCCR0A, temp ; no interrupts needed. ldi temp, 1$<$$<$CS02 ; prescalar = 256 Clock Select bits out TCCR0B, temp ; loading the clock starts the timer ; stop the timer by loading 0 into CS bits main: ldi temp, 70 ; gives approx Tone A440 (440 Hz) square wave on 16Mhz chip ; with /256 prescalar. out OCR0A, temp ; load compare value rcall sleep500 ; play note B4 (approx 494hz) duration 1/2 second ; top = 16000000/(2*256*494) - 1 = int(62.256) = 62 ldi temp, 62 out OCR0A, temp ; load compare value rcall sleep500 ; for note C4 we could use a prescalar of 64 and top=238 ; play note C4 (approx 523hz) ; top = 16000000/(2*256*523) - 1 = int(58.751) = 59 ldi temp, 59 out OCR0A, temp ; load compare value rcall sleep500 ;rjmp main here: ; micro can do other work here. rjmp here ; sleeps for approximately 500 milliseconds on a 16Mhz atmega328 sleep500: clr r16 ; set reg16 := 0 clr r17 ldi r18, 40 again: ; a delay loop 40*256\^{}2 instructions dec r16 ; brne again dec r17 brne again dec r18 brne again ret ,,, \subsection{Piezos In C Language} These may be modified Gunter Spanner examples pin10 -$>$ piezo -$>$ gnd (no resistor, polarity unimportant) \emph{ Just stop the piezo !! its so tinny } \begin{lstlisting} const int speaker = 10; void setup() { pinMode(speaker, OUTPUT); } void loop() { } \end{lstlisting} \emph{ A simple alarm } \begin{lstlisting} const int speaker = 10; void setup() { pinMode(speaker, OUTPUT); } void loop() { tone(speaker, 550, 450); delay(1000); } \end{lstlisting} \emph{ Science fiction sound } \begin{lstlisting} 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); } \end{lstlisting} \emph{ Sin wav } \begin{lstlisting} 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); } } \end{lstlisting} \section{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 $\sim$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 \subsection{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 \subsection{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. \subsection{Sleep Modes} http://www.engblaze.com/hush-little-microprocessor-avr-and-arduino-sleep-mode-basics/ a tutorial about avr sleep modes. \subsection{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 \subsection{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 \section{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) \section{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 \section{Documenting} \subsection{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). \section{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 \section{Assemblers For Avr Mcus} ``avra'' is available on mac linux and windows, but the mac versions available with brew install seems to have various bugs and quirks. For example, you cant have a label and code on the same line! also, you need .org 0x100 in the data segment. It may be possible to fix this by compiling the code. gavrasm - is a compiler written in pascal which seems to be actively maintained (as of Feb 2020). avr-gcc - also contains a \section{Avra} avra is an assembler for use with avr chips. This is good if you dont like c or java. \emph{ To use register names you need an 'inc' file for the chip } \begin{lstlisting} eg: /usr/share/avra/tn13def.inc \end{lstlisting} avra doesnt seem to come with an inc file for the atmega328 but one should be findable \emph{ Compile a file to a hex file } \begin{lstlisting} avra hello.asm \end{lstlisting} \subsection{Macros} \emph{ A macro which shows a message if no parameters } ----- .macro load .message ``no parameters specified'' .endm .macro load\_16\_i ldi @0,high(@2) ldi @1,low(@2) .endm ,,,, \section{C Language Programming} Arduino uses a version of c++ for programming (or assembly language, as seen above). However, arduino c programs do not have a main() function and cannot access many functions from the c standard library (such as file functions) for reasons that should, by now, be pretty obvious (ok, I tell you, there is no operating system!!!). This section will experiment with some c language constructs, such as arrays, function pointers, splitting strings etc. I will be investigating implementing a system drawing ideas from forth, namely, having a dictionary (an array of structures) of commands, with the command name and function pointer. Also, a simple data stack (implemented as an array). The ``setup()'' and ``loop()'' functions seem to be required in arduino c. \emph{ Basic sketch with serial } \begin{lstlisting} void setup() { Serial.begin(9600); Serial.println("Hello !!!"); } void loop() { } \end{lstlisting} \subsection{Structures In C} \emph{ Define a structure and initialize an array of them. } ---- struct song $\{$ char title[100]; char singer[100]; $\}$ songlist[] = $\{$ $\{$``one too many mornings'', ``dylan''$\}$, $\{$``money for nothing'', ``knoffler''$\}$, $\{$``somewhere'', ``anonymous''$\}$ $\}$; void setup() $\{$ Serial.begin(9600); for (int ii = 0; ii $<$ 3; ii++) $\{$ Serial.print(``Title:''); Serial.println(songlist[ii].title); $\}$ $\}$ void loop() $\{$ $\}$ ,,,, \emph{ Define an array and structure array } ---- long stack[100] = $\{$1,2,3$\}$; struct command $\{$ char name[32]; char action[100]; $\}$ dict[] = $\{$ $\{$``help'', ``...''$\}$, $\{$``longBlink'', ``...''$\}$, $\{$``shortBlink'', ``...''$\}$ $\}$; void setup() $\{$ $\}$ void loop() $\{$ $\}$ ,,,, the F() macro stores strings in flash not RAM which is good because there is 32K flash \emph{ Storing an array of structures flash mem and reading them } -------- struct myStruct // this is the typedef $\{$ char label[20]; uint16\_t data; $\}$; const myStruct myArrayPROGMEM[] PROGMEM = $\{$ $\{$ ``String1'', 0xCFFF$\}$, $\{$ ``String2'', 0xFC12$\}$, $\}$; myStruct myArraySRAM; void setup() $\{$ Serial.begin( 9600); Serial.println(F( ``Started'')); for( int i=0; i$<$(sizeof(myArrayPROGMEM)/sizeof(myStruct)); i++) $\{$ memcpy\_P( \&myArraySRAM, \&myArrayPROGMEM[i], sizeof( myStruct)); Serial.println( myArraySRAM.label); Serial.println( myArraySRAM.data, HEX); $\}$ $\}$ void loop() $\{$ $\}$ ,,,,, \emph{ Put an array of structs into Flash. } \begin{lstlisting} https://forum.arduino.cc/t/storing-a-struct-array-in-progmem/512308/2 \end{lstlisting} \emph{ Putting an array of strings in flash, and reading it } \begin{lstlisting} const char string1[] PROGMEM = "String First"; const char string2[] PROGMEM = "String Second"; // Then set up a table to refer to your strings. const char *const table[] PROGMEM = {string1, string2}; char buffer[30]; void setup() { Serial.begin(9600); while (!Serial); Serial.println("OK"); } void loop() { for (int i = 0; i < 2; i++) { // special strcpy function (Flash->RAM) strcpy_P(buffer, (char *)pgm_read_word(&(table[i]))); // Necessary casts and dereferencing, just copy. Serial.println(buffer); delay(500); } } ,,,, * split serial input into words -------- #define INPUT_SIZE 30 // Get next command from Serial (add 1 for final 0) char input[INPUT_SIZE + 1]; void setup(){ Serial.begin(9600); Serial.setTimeout(1000); Serial.println("Enter words:"); } void loop() { if (Serial.available() > 0) { byte size = Serial.readBytes(input, INPUT_SIZE); if (input[strlen(input)-1] == '\r') { input[strlen(input)-1] = 0; } // Add the final 0 to end the C string input[size] = 0; // Read each word char* command = strtok(input, " "); while (command != 0) { Serial.print("["); Serial.print(command); Serial.println("]"); // Find the next command in input string command = strtok(0, " "); } } } ,,,, The system below already seems useful and friendly. The user can connect with a serial connection and find out the capabilities of the little arduino machine. We can make this even better by using a bluetooth HC05 module in serial mode to connect wirelessly. Then organise a battery power source, and the little machine become and independant talking sensor. The dictionary is stored in flash. using the F() macro to put print strings into flash and thus save SRAM memory. Also, we can put the whole dictionary into flash. See the link above for how to code it. * a forth-ish system in c ---- #define MAXSTACKLENGTH 20 #define MAXDICTLENGTH 2 #define DICTSIZE 15 // maybe make the dictionary into an object just like the stack? int led = 13; // on-board LED int index = 0; // index into dictionary long value = 0; // signed long integer to push on stack char input[50]; // user input // Make a simple stack implementation here. // convert code below to use the stack structure. typedef struct stacktype { long data[MAXSTACKLENGTH]; int top; } Stack; enum Wordlist { HELP, STACK, TIME, TEST, FEEDBACK, SOUND}; typedef struct command { char name[32]; char help[100]; Wordlist vocab; void (*action)(const struct command [], Stack *); } Command; Stack datastack = {{0,0},0}; struct command cc; int size(Stack * ss) { return ss->top; } int isEmpty(Stack * ss) { return size(ss) <= 0; } int isFull(Stack * ss) { return size(ss) >= MAXSTACKLENGTH-1; } void push(Stack * ss, long ll) { if (isFull(ss)) { Serial.println(F("Stack Full!")); return; } ss->data[ss->top] = ll; ss->top++; } long pop(Stack * ss) { if (isEmpty(ss)) { Serial.println(F("Stack Empty!")); return 0; } ss->top--; return ss->data[ss->top]; } void clear(Stack * ss) { if (isEmpty(ss)) { return; } ss->top = 0; } void drop(Stack * ss) { if (isEmpty(ss)) { return; } ss->top--; } void dup(Stack * ss) { if (isEmpty(ss)) { return; } if (isFull(ss)) { Serial.println(F("Stack Full!")); return; } ss->data[ss->top] = ss->data[ss->top - 1]; ss->top++; } void swap(Stack * ss) { if (size(ss) < 2) { return; } long ll = pop(ss); long mm = pop(ss); push(ss, ll); push(ss, mm); } void plus(Stack * ss) { if (size(ss) < 2) { return; } long ll = pop(ss); long mm = pop(ss); push(ss, ll+mm); } int findWord (const Command dd[], char * aword); void (*fp)(const Command [], Stack * ss); /* void listWords (const Command dd[], Stack * ss); void greeting (const Command dd[], Stack * ss); void help (const Command dd[], Stack * ss); void showStack (const Command dd[], Stack * ss); void printItem (const Command dd[], Stack * ss); void clearStack (const Command dd[], Stack * ss); void drop (const Command dd[], Stack * ss); void dup (const Command dd[], Stack * ss); void swap (const Command dd[], Stack * ss); void plus (const Command dd[], Stack * ss); void millis (const Command dd[], Stack * ss); void star(const Command dd[], Stack * ss); void blink(const Command dd[], Stack * ss); void blinkNum(const Command dd[], Stack * ss); void printUpTime(const Command dd[], Stack * ss); */ // do we really need this? A flat array seems to be sufficient typedef struct dictionary { struct command contents[MAXDICTLENGTH]; int size; } Dict; void greeting (const Command dd[], Stack * ss) { Serial.println(F("Hello, how are you?!")); Serial.println( F("This is a 'forth-like' system with a data stack running on arduino.")); Serial.println(F("Type 'words' to see all commands")); Serial.println(F("Type 'help' for a description of all commands. ")); Serial.println(F("Enter a number to push it onto the stack.")); } // returns -1 if word not found, otherwise the index. int findWord (const Command dd[], char * aword) { struct command cc; //int dictsize = sizeof(dd)/sizeof(struct command); for (int ii = 0; ii < DICTSIZE; ii++) { //memcpy_P( &myArraySRAM, &myArrayPROGMEM[i], sizeof(acommand)); memcpy_P( &cc, &dd[ii], sizeof(cc)); if (strcmp(cc.name, aword) == 0 ) { return ii; } } return -1; } void help (const Command dd[], Stack * ss) { struct command cc; Serial.println(F("Ok relax, help is coming:")); for (int ii = 0; ii < DICTSIZE; ii++) { memcpy_P( &cc, &dd[ii], sizeof(cc)); Serial.print(" ["); Serial.print(cc.name); Serial.print("] "); Serial.println(cc.help); } } void showStack (const Command dd[], Stack * ss) { Serial.print(F("Stack [")); for (int ii = 0; ii < size(ss); ii++) { Serial.print(ss->data[ii]); Serial.print(" "); } Serial.print(F("]")); } void printItem (const Command dd[], Stack * ss) { if (isEmpty(ss)) { return; } Serial.println(pop(ss)); } // duplicated to conform to the function pointer signature. void clearStack(const Command dd[], Stack * ss) { clear(ss); } void drop(const Command dd[], Stack * ss) { drop(ss); } void dup(const Command dd[], Stack * ss) { dup(ss); } void swap(const Command dd[], Stack * ss) { swap(ss); } void plus(const Command dd[], Stack * ss) { plus(ss); } void millis(const Command dd[], Stack * ss) { push(ss, millis()); } void star (const Command dd[], Stack * ss) { if (size(ss) < 1) { Serial.println(F("star needs one stack value")); return; } int value = pop(ss); for (int ii = 0; ii < value; ii++) { Serial.print("*"); } Serial.println(); } void blink(long duration, long times) { for (int ii = 0; ii < times; ii++) { digitalWrite(led, HIGH); delay(duration); digitalWrite(led, LOW); delay(duration); } } void blinkNum(const Command dd[], Stack * ss) { if (size(ss) < 1) { Serial.println(F("blinknum needs 1 stack value")); return; } long value = pop(ss); int tens = (value/10)%100; int ones = value % 10; blink(500, tens); blink(200, ones); delay(2000); blink(500, tens); blink(200, ones); } void blink(const Command dd[], Stack * ss) { if (size(ss) < 2) { Serial.println(F("blink needs 2 stack values")); return; } int times = pop(ss); int duration = pop(ss); Serial.print(F("Blinking led (")); Serial.print(led); Serial.print(F(") ")); Serial.print(times); Serial.print(F(" times with duration ")); Serial.print(duration); Serial.println(F(" milliseconds.")); for (int ii= 0; ii < times; ii++) { digitalWrite(led, HIGH); delay(duration); digitalWrite(led, LOW); delay(duration); } } void printUpTime(const Command dd[], Stack * ss) { unsigned long currentMillis = millis(); unsigned long seconds = currentMillis / 1000; unsigned long minutes = seconds / 60; unsigned long hours = minutes / 60; unsigned long days = hours / 24; currentMillis %= 1000; seconds %= 60; minutes %= 60; hours %= 24; Serial.print(F("Run-time: ")); if (hours < 10) { Serial.print(F("0")); } Serial.print(hours); Serial.print(F(":")); if (minutes < 10) { Serial.print(F("0")); } Serial.print(minutes); Serial.print(F(":")); if (seconds < 10) { Serial.print(F("0")); } Serial.print(seconds); Serial.println(F(" (h:m:s)")); } void listWords (const Command dd[], Stack * ss) { /* dd is actually pointer to first struct */ struct command cc; Serial.println("All commands:"); for (int ii = 0; ii < DICTSIZE; ii++) { memcpy_P( &cc, &dd[ii], sizeof(cc)); Serial.print(" "); Serial.print(cc.name); } Serial.println(" "); } const Command dict[] PROGMEM = { {"hi", "gives a greeting", HELP, greeting}, {"help", "show all words and help", HELP, help}, {"words", "just show words", HELP, listWords}, {".s", "show the stack", STACK, showStack}, {".", "print and drop top stack item", STACK, printItem}, {"clear", "clear the stack", STACK, clearStack}, {"drop", "drop top stack item", STACK, drop}, {"dup", "duplicate top stack item", STACK, dup}, {"swap", "swap top 2 stack items", STACK, swap}, {"+", "add top 2 stack items", STACK, plus}, {"ms", "push milliseconds run-time", TIME, millis}, {".time", "print current run-time in minutes+seconds", TIME, printUpTime}, {"star", "print n stars", TEST, star}, {"blinknum", "blink number 1-99 on led (tens slow blinks)", FEEDBACK, blinkNum}, {"blink", "(dur repeat --) blinks led on pin 13 'n' times duration d ms", FEEDBACK, blink} }; void setup() { pinMode(led, OUTPUT); Serial.setTimeout(60000); Serial.begin(9600); while (!Serial); // wait for serial port. Needed for native USB Serial.println("OK"); Serial.println("Talking to Ardy..."); } void loop() { char * end; showStack(dict, &datastack); Serial.print("#>"); while (Serial.available() == 0) { } // but it depends on serial monitor settings what is your // line ending. It could be \n etc // this blocks until \r received or timeout. Serial.readStringUntil('\r').toCharArray(input, 20); // remove \r char at end of string if (input[strlen(input)-1] == '\r') { input[strlen(input)-1] = 0; } //Serial.print("Got: ["); Serial.print(input); Serial.println("]"); //help(dict, &datastack); // Read each word char* oneword = strtok(input, " "); while (oneword != 0) { // Serial.print("["); Serial.print(oneword); Serial.println("]"); index = findWord(dict, oneword); if (index == -1) { // 'atol' cant distinguish between 0 and error // 10 is the base for the conversion. // convert word to long integer value = strtol(oneword, &end, 10); // but this is pushing zero on empty input if (!*end) { // word is an integer, so push it. push(&datastack, value); } else { Serial.print(F("Sorry, what does '")); Serial.print(oneword); Serial.print(F("' mean?")); Serial.println(" "); break; } } else { // the function pointer is in flash (in a command struct) // so has to be copied over to sram. memcpy_P( &cc, &dict[index], sizeof(cc)); // execute word as a function pointer (*cc.action)(dict, &datastack); } oneword = strtok(0, " "); } // while more words } // loop ,,,, 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. * extract and compile arduino c code and upload to 'uno' board >> map ,C :?^ *---?+1,/,,,/-1w ! ( cat - ) > sketch/test/test.ino; cd sketch/test; make; cd build-uno; sudo avrdude -p atmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:test.hex:i * compile an avr assembly program within a document to 'test.hex' >> map ,a :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; * compile assembler 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 asm code and upload to the arduino 'uno' compatible 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 It will probably be necessary to change the exact /dev/tty.usb* device below * compile source and upload to the arduino 'uno' compatible board on mac osx >> map ,V :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; sudo avrdude -p atmega328p -carduino -P/dev/tty.usbmodem14201 -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 not working! says filedes is not a tty * compile, upload and open a serial terminal to test on duemilenove with linux >> map ,x :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; sudo avrdude -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:test.hex:i; sudo picocom -b 9600 --echo /dev/ttyUSB0 * open a serial terminal for comms with an "uno" board on mac osx >> map ,t :! sudo picocom -b 9600 --echo /dev/tty.usbmodem14201 * 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" When 'make' is used, it actually puts the hex file in a subfolder 'build-uno' etc. * 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 2>&1 \| less Redirect stderr to less so that we can see what causes a compile to fail. 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 \end{lstlisting} \end{document}