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