-
Notifications
You must be signed in to change notification settings - Fork 3
/
avrvm.html
1436 lines (1345 loc) · 37 KB
/
avrvm.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.6: http://docutils.sourceforge.net/" />
<title>Robot Firmware</title>
<style type="text/css">
@import url(html4css1.css);
pre.literal-block {
border: 1px solid #000;
background-color: #F0D1B2;
padding: 0.5em;
}
</style>
</head>
<body>
<div class="document" id="robot-firmware">
<h1 class="title">Robot Firmware</h1>
<blockquote>
<p>This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.</p>
<p>This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.</p>
<p>You should have received a copy of the GNU General Public License
along with this program. If not, see <<a class="reference external" href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>>.</p>
</blockquote>
<div class="section" id="introduction">
<h1>Introduction</h1>
<p>This is a simple sort-of Forth system for AVR microcontrollers. It's
written for the <a class="reference external" href="http://www.pololu.com/catalog/product/1220">Pololu Baby Orangutan robot controller</a></p>
<p>The system in general probably won't make much sense unless you're
already at least a little familiar with Forth. Two helpful sources are
<a class="reference external" href="http://www.bradrodriguez.com/papers/moving1.htm">Brad Rodriguez' "Moving Forth" series</a> and <a class="reference external" href="http://rwmj.wordpress.com/2010/08/07/jonesforth-git-repository/">Richard
Jones'</a> wonderful <a class="reference external" href="http://git.annexia.org/?p=jonesforth.git;a=summary">jonesforth "literate Forth"</a>.</p>
<p>There's no attempt to implement or adhere to any standard Forth. I'm just
noodling around and creating the easiest version of what seems like it
will work. It's also my first attempt at writing assembly code in over
a decade, and the first time using AVR assembly, so there are certainly
going to be some, uh, less-than-perfect code. Please bear with me.</p>
</div>
<div class="section" id="definitions">
<h1>Definitions</h1>
<p>The Pololu Baby Orangutan is (currently) built around the Amtel
ATmega328P, so let's start by including the definitions for that. (This
file comes with the AVR Studio 4 software from Amtel.):</p>
<pre class="literal-block">
.nolist
.include "m328Pdef.inc"
.list
.listmac
</pre>
<p>The AVR chip has a Harvard architecture with separate memories and buses for
program and data RAM. The data bus, SRAM, and registers are eight bits wide,
while the address bus and Flash memory (for persistent program storage)
are sixteen bits wide.</p>
<p>I've decided to try having the Top-Of-Stack (TOS) and the next 8-bit
"cell" underneath it (which I call TOSL below) in two registers with the
rest of the stack pointed to by another pair of registers.</p>
<p>Certain pairs of 8-bit registers (namely the "top" six registers r26 -
r31) can be used as 16-bit registers (namely X, Y, and Z) for addressing
and some math functions</p>
<p>If we use a pair of such registers as our TOS and TOSL it gives us the
ability to, say, put the low and high bytes of an address in program or
data RAM onto the stack and then efficiently use the 16-bit value to
fetch or store from that location.</p>
<p>Keep the top two items (bytes) on the stack in the X register:</p>
<pre class="literal-block">
.def TOS = r27 ; XH
.def TOSL = r26 ; XL
</pre>
<p>Y register is our Data Stack Pointer.
Z register will be used for diggin around in the dictionary.</p>
<p>We also use a working register:</p>
<pre class="literal-block">
.def Working = r16
</pre>
<p>The "word" word needs to track how many bytes it's read. This is also
reused by find:</p>
<pre class="literal-block">
.def word_counter = r17
</pre>
<p>Base (numeric base for converting digits to numbers):</p>
<pre class="literal-block">
.def Base = r8
</pre>
<p>Number keeps track of the digits it is comverting using this register:</p>
<pre class="literal-block">
.def number_pointer = r9
</pre>
<p>Registers used by FIND word:</p>
<pre class="literal-block">
.def find_buffer_char = r10
.def find_name_char = r11
</pre>
<p>Register used by Interpret:</p>
<pre class="literal-block">
.def temp_length = r12
</pre>
<p>Register used by the TWI/I2C driver to track handling of status codes:</p>
<pre class="literal-block">
.def twi = r18
</pre>
</div>
<div class="section" id="data-sram-organization">
<h1>Data (SRAM) Organization</h1>
<p>On the 328P the first 256 bytes of data space are actually the registers
and I/O ports (see the Datasheet fof details):</p>
<pre class="literal-block">
.dseg
.org SRAM_START
</pre>
<div class="section" id="word-buffer">
<h2>Word Buffer</h2>
<p>The "word" word reads the stream of characters returned by the "key" word
and fills this buffer until it reaches a space character. It's only 64
bytes because we're going to be using a single-byte length field and
packing two bits of meta-data into it, leaving six bits to specify the
word length, giving us a maximum possible name length of sixty-four:</p>
<pre class="literal-block">
buffer: .byte 0x40
</pre>
</div>
<div class="section" id="data-stack">
<h2>Data Stack</h2>
<p>The Parameter (Data) Stack grows upward
towards the Return Stack at the top of RAM. Note that the first two bytes
of stack are kept in the X register. Due to this the initial two bytes of
the data stack will be filled with whatever was in X before the first
push, unless you load X (i.e. TOS and Just-Under-TOS) "manually" before
dropping into the interpreter loop:</p>
<pre class="literal-block">
data_stack: .org 0x0140 ; SRAM_START + buffer
</pre>
</div>
</div>
<div class="section" id="code-flash-ram">
<h1>Code (Flash RAM)</h1>
<div class="section" id="macros">
<h2>Macros</h2>
<p>Some data stack manipulation macros to ease readability.</p>
<p>Pop from data stack to TOSL. Note that you are responsible for preserving
the previous value of TOSL if you still want it after using the macro.
(I.e. mov TOS, TOSL):</p>
<pre class="literal-block">
.MACRO popup
ld TOSL, -Y
.ENDMACRO
</pre>
<p>Make room on TOS and TOSL by pushing them onto the data stack:</p>
<pre class="literal-block">
.MACRO pushdownw
st Y+, TOSL
st Y+, TOS
.ENDMACRO
</pre>
<p>Essentially "drop drop":</p>
<pre class="literal-block">
.MACRO popupw
ld TOS, -Y
ld TOSL, -Y
.ENDMACRO
</pre>
</div>
<div class="section" id="begining-of-code-proper">
<h2>Begining of code proper</h2>
<pre class="literal-block">
.cseg
</pre>
</div>
<div class="section" id="interupt-vectors">
<h2>Interupt Vectors</h2>
<pre class="literal-block">
.org 0x0000
jmp RESET
jmp BAD_INTERUPT ; INT0 External Interrupt Request 0
jmp BAD_INTERUPT ; INT1 External Interrupt Request 1
jmp BAD_INTERUPT ; PCINT0 Pin Change Interrupt Request 0
jmp BAD_INTERUPT ; PCINT1 Pin Change Interrupt Request 1
jmp BAD_INTERUPT ; PCINT2 Pin Change Interrupt Request 2
jmp BAD_INTERUPT ; WDT Watchdog Time-out Interrupt
jmp BAD_INTERUPT ; TIMER2 COMPA Timer/Counter2 Compare Match A
jmp BAD_INTERUPT ; TIMER2 COMPB Timer/Counter2 Compare Match B
jmp BAD_INTERUPT ; TIMER2 OVF Timer/Counter2 Overflow
jmp BAD_INTERUPT ; TIMER1 CAPT Timer/Counter1 Capture Event
jmp BAD_INTERUPT ; TIMER1 COMPA Timer/Counter1 Compare Match A
jmp BAD_INTERUPT ; TIMER1 COMPB Timer/Coutner1 Compare Match B
jmp BAD_INTERUPT ; TIMER1 OVF Timer/Counter1 Overflow
jmp BAD_INTERUPT ; TIMER0 COMPA Timer/Counter0 Compare Match A
jmp BAD_INTERUPT ; TIMER0 COMPB Timer/Counter0 Compare Match B
jmp BAD_INTERUPT ; TIMER0 OVF Timer/Counter0 Overflow
jmp BAD_INTERUPT ; SPI, STC SPI Serial Transfer Complete
jmp BAD_INTERUPT ; USART, RX USART Rx Complete
jmp BAD_INTERUPT ; USART, UDRE USART, Data Register Empty
jmp BAD_INTERUPT ; USART, TX USART, Tx Complete
jmp BAD_INTERUPT ; ADC ADC Conversion Complete
jmp BAD_INTERUPT ; EE READY EEPROM Ready
jmp BAD_INTERUPT ; ANALOG COMP Analog Comparator
jmp BAD_INTERUPT ; TWI 2-wire Serial Interface
jmp BAD_INTERUPT ; SPM READY Store Program Memory Ready
BAD_INTERUPT:
jmp 0x0000
</pre>
</div>
<div class="section" id="initial-reset-vector">
<h2>Initial reset vector</h2>
<p>Disable interrupts and reset everything:</p>
<pre class="literal-block">
RESET:
cli
</pre>
<p>Set up the Return Stack:</p>
<pre class="literal-block">
ldi Working, low(RAMEND)
out SPL, Working
ldi Working, high(RAMEND)
out SPH, Working
</pre>
<p>Initialize Data Stack:</p>
<pre class="literal-block">
ldi YL, low(data_stack)
ldi YH, high(data_stack)
</pre>
<p>Set the UART to talk to a serial port:</p>
<pre class="literal-block">
rcall UART_INIT
</pre>
<p>Set up 100kHz freq for TWI/I2C peripheral:</p>
<pre class="literal-block">
ldi Working, 23
sts TWBR, Working ; set bitrate
ldi Working, 1
sts TWSR, Working ; set prescaler
</pre>
<p>Initialize Base:</p>
<pre class="literal-block">
ldi Working, 10
mov Base, Working
</pre>
<p>Re-enable interrupts:</p>
<pre class="literal-block">
sei
</pre>
<p>TODO: Set up a Stack Overflow Handler and put its address at RAMEND
and set initial stack pointer to RAMEND - 2 (or would it be 1?)
That way if we RET from somewhere and the stack is underflowed we'll
trigger the handler instead of just freaking out.</p>
</div>
<div class="section" id="main-loop">
<h2>Main Loop</h2>
<p>Our (very simple) main loop just calls "quit" over and over again:</p>
<pre class="literal-block">
MAIN:
rcall INTERPRET_PFA
rcall DOTESS_PFA
rjmp MAIN
</pre>
</div>
<div class="section" id="initialize-the-usart">
<h2>Initialize the USART</h2>
<pre class="literal-block">
UART_INIT:
ldi r17, high(520) ; 2400 baud w/ 20Mhz osc
ldi r16, low(520) ; See Datasheet
sts UBRR0H, r17
sts UBRR0L, r16
; The chip defaults to 8N1 so we won't set it here even though we
; should.
ldi r16, (1 << TXEN0) | (1 << RXEN0) ; Enable transmit/receive
sts UCSR0B, r16
ret
</pre>
</div>
</div>
<div class="section" id="words">
<h1>Words</h1>
<p>These are the basic commands of the system that work together to
implement the interpreter.</p>
<div class="section" id="key">
<h2>Key</h2>
<p>Read a character from the serial port and push it onto the stack:</p>
<pre class="literal-block">
KEY:
.dw 0x0000
.db 3, "key"
</pre>
<p>First, loop on the RXC0 bit of the UCSR0A register, which indicates that
a byte is available in the receive register:</p>
<pre class="literal-block">
KEY_PFA:
lds Working, UCSR0A
sbrs Working, RXC0
rjmp KEY_PFA
</pre>
<p>Make room on the stack and load the character onto it from the UART's data register:</p>
<pre class="literal-block">
rcall DUP_PFA
lds TOS, UDR0
</pre>
<p>Echo the char to the serial port:</p>
<pre class="literal-block">
rcall ECHO_PFA
ret
</pre>
</div>
<div class="section" id="dup">
<h2>Dup</h2>
<p>Duplicate the top value on the stack:</p>
<pre class="literal-block">
DUP:
.dw KEY
.db 3, "dup"
DUP_PFA:
st Y+, TOSL ; push TOSL onto data stack
mov TOSL, TOS
ret
</pre>
</div>
<div class="section" id="emit">
<h2>Emit</h2>
<p>Pop the top item from the stack and send it to the serial port:</p>
<pre class="literal-block">
EMIT:
.dw DUP
.db 4, "emit"
EMIT_PFA:
rcall ECHO_PFA
rcall DROP_PFA
ret
</pre>
</div>
<div class="section" id="echo">
<h2>Echo</h2>
<p>Write the top item on the stack to the serial port:</p>
<pre class="literal-block">
ECHO:
.dw EMIT
.db 4, "echo"
</pre>
<p>First, loop on the UDRE0 bit of the UCSR0A register, which indicates that
the data register is ready for a byte:</p>
<pre class="literal-block">
ECHO_PFA:
lds Working, UCSR0A
sbrs Working, UDRE0
rjmp ECHO_PFA
</pre>
<p>When it's ready, write the byte to the UART data register:</p>
<pre class="literal-block">
sts UDR0, TOS
ret
</pre>
</div>
<div class="section" id="drop">
<h2>Drop</h2>
<p>Drop the top item from the stack:</p>
<pre class="literal-block">
DROP:
.dw ECHO
.db 4, "drop"
DROP_PFA:
mov TOS, TOSL
popup
ret
</pre>
</div>
<div class="section" id="word">
<h2>Word</h2>
<p>Now that we can receive bytes from the serial port, the next step is a
"word" word that can parse space (hex 0x20) character-delimited words
from the stream of incoming chars.:</p>
<pre class="literal-block">
WORD:
.dw DROP
.db 4, "word"
WORD_PFA:
</pre>
<p>Get next char onto stack:</p>
<pre class="literal-block">
rcall KEY_PFA
</pre>
<p>Is it a space character?:</p>
<pre class="literal-block">
cpi TOS, ' '
brne _a_key
</pre>
<p>Then drop it from the stack and loop to get the next character:</p>
<pre class="literal-block">
rcall DROP_PFA
rjmp WORD_PFA
</pre>
<p>If it's not a space character then begin saving chars to the word buffer.
Set up the Z register to point to the buffer and reset the word_counter:</p>
<pre class="literal-block">
_a_key:
ldi ZL, low(buffer)
ldi ZH, high(buffer)
ldi word_counter, 0x00
</pre>
<p>First, check that we haven't overflowed the buffer. If we have, silently
"restart" the word, and just ditch whatever went before.:</p>
<pre class="literal-block">
_find_length:
cpi word_counter, 0x40
breq _a_key
</pre>
<p>Save the char to the buffer and clear it from the stack:</p>
<pre class="literal-block">
st Z+, TOS
rcall DROP_PFA
inc word_counter
</pre>
<p>Get the next character, breaking if it's a space character (hex 0x20):</p>
<pre class="literal-block">
rcall KEY_PFA
cpi TOS, ' '
brne _find_length
</pre>
<p>A space was found, copy length to TOS:</p>
<pre class="literal-block">
mov TOS, word_counter
ret
</pre>
</div>
<div class="section" id="number">
<h2>Number</h2>
<p>Parse a number from the word_buffer. The length of the word is in TOS.
Return the number of characters unconverted in TOS and the value, or
first unconverted character, in TOSL:</p>
<pre class="literal-block">
NUMBER:
.dw WORD
.db 6, "number"
NUMBER_PFA:
</pre>
<p>Point Z at the buffer:</p>
<pre class="literal-block">
ldi ZL, low(buffer)
ldi ZH, high(buffer)
</pre>
<p>We'll accumulate the number in Working. Set it to zero.
Then save the length to number_pointer and load the first character into
TOS:</p>
<pre class="literal-block">
mov number_pointer, TOS
ldi Working, 0x00
ld TOS, Z+
rjmp _convert
</pre>
<p>This is where we loop back in if there is more than one digit to convert.
We multiply the current accumulated value by the Base (the 16-bit result
is placed in r1:r0) and load the next digit into TOS:</p>
<pre class="literal-block">
_convert_again:
mul Working, Base
mov Working, r0
ld TOS, Z+
_convert:
</pre>
<p>If the character is between '0' and '9' go to _decimal:</p>
<pre class="literal-block">
cpi TOS, '0'
brlo _num_err
cpi TOS, ':' ; the char after '9'
brlo _decimal
rjmp _num_err
</pre>
<p>For a decimal digit, just subtract '0' from the char to get the value:</p>
<pre class="literal-block">
_decimal:
subi TOS, '0'
rjmp _converted
</pre>
<p>If we encounter an unknown digit put the number of remaining unconverted
digits into TOS and the unrecognized character in TOSL:</p>
<pre class="literal-block">
_num_err:
st Y+, TOSL
mov TOSL, TOS
mov TOS, number_pointer
ret
</pre>
<p>Once we have a digit in TOS we can add it to our accumulator and, if
there are more digits to convert, we loop back to keep converting them:</p>
<pre class="literal-block">
_converted:
add Working, TOS
dec number_pointer
brne _convert_again
</pre>
<p>We're done, move the result to TOSL and zero, signaling successful
conversion, in TOS:</p>
<pre class="literal-block">
st Y+, TOSL
mov TOSL, Working
mov TOS, number_pointer
ret
</pre>
</div>
<div class="section" id="left-shift-word-16-bit-value">
<h2>Left Shift Word (16-Bit) Value</h2>
<p>The AVR chip has a slight wrinkle when accessing program (flash) RAM.
Because it is organized in 16-bit words there are 16K addresses to
address the 32K of RAM. The architecture allows for reaching each byte
by means of left-shifting the address and using the least significant
bit to indicate low (0) or high (1) byte.</p>
<p>This means that if we get an address from e.g. the return stack and
we want to access data in program RAM with it we have to shift it one
bit left. This word "<<w" shifts a 16-bit value in TOS:TOSL one bit to
the left:</p>
<pre class="literal-block">
LEFT_SHIFT_WORD:
.dw NUMBER
.db 3, "<<w"
LEFT_SHIFT_WORD_PFA:
mov Working, TOS
clr TOS
lsl TOSL
</pre>
<p>If the carry bit is clear skip incrementing TOS:</p>
<pre class="literal-block">
brcc _lslw0
inc TOS ; copy carry flag to TOS[0]
_lslw0:
lsl Working
or TOS, Working
</pre>
<p>X now contains left-shifted word, and carry bit reflects TOS carry:</p>
<pre class="literal-block">
ret
</pre>
</div>
<div class="section" id="emithex">
<h2>Emithex</h2>
<p>I want to be able to emit values (from the stack or wherever) as hex
digits. This word pops the value on the stack and writes it to the serial
port as two hex digits (high byte first):</p>
<pre class="literal-block">
HEXDIGITS: .db "0123456789abcdef"
EMIT_HEX:
.dw LEFT_SHIFT_WORD
.db 7, "emithex"
EMIT_HEX_PFA:
</pre>
<p>Save Z register onto the return stack:</p>
<pre class="literal-block">
push ZH
push ZL
</pre>
<p>Dup TOS, emit the low byte, then the high byte:</p>
<pre class="literal-block">
rcall DUP_PFA
swap TOS
rcall emit_nibble ; high
rcall emit_nibble ; low
</pre>
<p>Restore Z from the return stack:</p>
<pre class="literal-block">
pop ZL
pop ZH
ret
</pre>
<p>So now to emit nybbles. This routine consumes TOS and clobbers Z:</p>
<pre class="literal-block">
emit_nibble:
</pre>
<p>Get the address of HEXDIGITS into Z:</p>
<pre class="literal-block">
pushdownw
ldi TOS, high(HEXDIGITS)
ldi TOSL, low(HEXDIGITS)
rcall LEFT_SHIFT_WORD_PFA
movw Z, X
popupw
</pre>
<p>mask high nibble:</p>
<pre class="literal-block">
andi TOS, 0x0f
</pre>
<p>Since there's no direct way to add the nibble to Z (I could define a
16-bit-plus-8-bit add word, and I probably will later) we'll use a loop
and the adiw instruction:</p>
<pre class="literal-block">
_eloop:
cpi TOS, 0x00
</pre>
<p>If nibble is not zero...:</p>
<pre class="literal-block">
breq _edone
dec TOS
</pre>
<p>Increment the HEXDIGITS pointer:</p>
<pre class="literal-block">
adiw Z, 1
rjmp _eloop
_edone:
</pre>
<p>Z points at correct char:</p>
<pre class="literal-block">
lpm TOS, Z
rcall EMIT_PFA
ret
</pre>
</div>
<div class="section" id="s">
<h2>.S</h2>
<p>Print out the stack:</p>
<pre class="literal-block">
DOTESS:
.dw EMIT_HEX
.db 2, ".s"
DOTESS_PFA:
</pre>
<p>Make room on the stack:</p>
<pre class="literal-block">
rcall DUP_PFA
</pre>
<p>Print out 'cr' 'lf' '[':</p>
<pre class="literal-block">
ldi TOS, 0x0d ; CR
rcall ECHO_PFA
ldi TOS, 0x0a ; LF
rcall ECHO_PFA
ldi TOS, '['
rcall ECHO_PFA
</pre>
<p>Print (as hex) TOS and TOSL. First copy TOSL to TOS to get the value back
but leave the stack at the same depth, then call emithex which will pop
a value:</p>
<pre class="literal-block">
mov TOS, TOSL
rcall EMIT_HEX_PFA
</pre>
<p>Now we're back to where we started.:</p>
<pre class="literal-block">
mov Working, TOSL
rcall DUP_PFA ; tos, tos, tosl
mov TOS, Working ; tosl, tos, tosl
rcall DUP_PFA ; tosl, tosl, tos, tosl
ldi TOS, '-' ; '-', tosl, tos, tosl
rcall EMIT_PFA ; tosl, tos, tosl
rcall EMIT_HEX_PFA ; tos, tosl
rcall DUP_PFA ; tos, tos, tosl
ldi TOS, ' ' ; ' ', tos, tosl
rcall EMIT_PFA ; tos, tosl
</pre>
<p>Point Z at the top of the stack (the part of the stack "under" TOS and
TOSL):</p>
<pre class="literal-block">
movw Z, Y
rcall DUP_PFA
_inny:
</pre>
<p>If the Z register is the same as or higher than data_stack print the
item at Z:</p>
<pre class="literal-block">
ldi Working, low(data_stack)
cp ZL, Working
ldi Working, high(data_stack)
cpc ZH, Working
brsh _itsok
</pre>
<p>Otherwise, we're done:</p>
<pre class="literal-block">
ldi TOS, ']'
rcall ECHO_PFA
ldi TOS, 0x0d ; CR
rcall ECHO_PFA
ldi TOS, 0x0a ; LF
rcall EMIT_PFA
ret
</pre>
<p>Load the value at (pre-decremented) Z and emit it as hex:</p>
<pre class="literal-block">
_itsok:
ld TOS, -Z
rcall EMIT_HEX_PFA
rcall DUP_PFA
ldi TOS, ' '
rcall ECHO_PFA
</pre>
<p>And go to the next one:</p>
<pre class="literal-block">
rjmp _inny
</pre>
</div>
<div class="section" id="find">
<h2>Find</h2>
<p>Given the length of a word in the word_buffer, find attempts to find that
word in the dictionary and return its LFA on the stack (in TOS:TOSL).
If the word can't be found, put 0xffff into TOS:TOSL:</p>
<pre class="literal-block">
FIND:
.dw DOTESS
.db 4, "find"
FIND_PFA:
</pre>
<p>Make room on the stack for address:</p>
<pre class="literal-block">
mov word_counter, TOS
st Y+, TOSL
ldi TOSL, low(READ_GYRO)
ldi TOS, high(READ_GYRO)
</pre>
<p>Check if TOS:TOSL == 0x0000:</p>
<pre class="literal-block">
_look_up_word:
cpi TOSL, 0x00
brne _non_zero
cpse TOSL, TOS
rjmp _non_zero
</pre>
<p>if TOS:TOSL == 0x0000 we're done:</p>
<pre class="literal-block">
ldi TOS, 0xff
ldi TOSL, 0xff
ret
</pre>
<p>While TOS:TOSL != 0x0000 check if this it the right word:</p>
<pre class="literal-block">
_non_zero:
</pre>
<p>Save current Link Field Address:</p>
<pre class="literal-block">
pushdownw
</pre>
<p>Load Link Field Address of next word in the dictionary into the X
register pair:</p>
<pre class="literal-block">
rcall LEFT_SHIFT_WORD_PFA
movw Z, X
lpm TOSL, Z+
lpm TOS, Z+
</pre>
<p>Now stack has ( - LFA_next, LFA_current) Load length-of-name byte into a register:</p>
<pre class="literal-block">
lpm Working, Z+
cp Working, word_counter
breq _same_length
</pre>
<p>Not the same length, ditch LFA_current and loop:</p>
<pre class="literal-block">
sbiw Y, 2
rjmp _look_up_word
</pre>
<p>If they're the same length walk through both and compare them character
by character.</p>
<p>Length is in Working and word_counter. Z holds current word's name's
first byte's address in program RAM. TOS:TOSL have the address of the
next word's LFA. So stack has ( - LFA_next, LFA_current)</p>
<p>Put address of search term in buffer into X (TOS:TOSL):</p>
<pre class="literal-block">
_same_length:
pushdownw
ldi TOS, high(buffer)
ldi TOSL, low(buffer)
</pre>
<p>stack ( - buffer, LFA_next, LFA_current):</p>
<pre class="literal-block">
_compare_name_and_target_byte:
ld find_buffer_char, X+ ; from buffer
lpm find_name_char, Z+ ; from program RAM
cp find_buffer_char, find_name_char
breq _okay_dokay
</pre>
<p>Not equal, clean up and go to next word:</p>
<pre class="literal-block">
popupw ; ditch search term address
sbiw Y, 2 ; ditch LFA_current
rjmp _look_up_word
</pre>
<p>The chars are the same:</p>
<pre class="literal-block">
_okay_dokay:
dec Working
brne _compare_name_and_target_byte
</pre>
<p>If we get here we've checked that every character in the name and the
target term match:</p>
<pre class="literal-block">
popupw ; ditch search term address
popupw ; ditch LFA_next
ret ; LFA_current
</pre>
</div>
<div class="section" id="to-pfa">
<h2>To PFA</h2>
<p>">pfa" Given a word's LFA (Link Field Address) in TOS:TOSL, find its PFA:</p>
<pre class="literal-block">
TPFA:
.dw FIND
.db 4, ">pfa"
TPFA_PFA:
</pre>
<p>Point to name length and adjust the address:</p>
<pre class="literal-block">
adiw X, 1
pushdownw ; save address
rcall LEFT_SHIFT_WORD_PFA
</pre>
<p>get the length:</p>
<pre class="literal-block">
movw Z, X
lpm Working, Z
popupw ; restore address
</pre>
<p>We need to map from length in bytes to length in words while allowing
for the padding bytes in even-length names:</p>
<pre class="literal-block">
lsr Working
inc Working ; n <- (n >> 1) + 1
add TOSL, Working ; Add the adjusted name length to our prog mem pointer.
brcc _done_adding
inc TOS ; Account for the carry bit if set.
_done_adding:
ret
</pre>
</div>
<div class="section" id="interpret">
<h2>interpret</h2>
<pre class="literal-block">
INTERPRET:
.dw TPFA
.db 9, "interpret"
INTERPRET_PFA:
</pre>
<p>get length of word in buffer:</p>
<pre class="literal-block">
rcall WORD_PFA
</pre>
<p>save length:</p>
<pre class="literal-block">
mov temp_length, TOS
</pre>
<p>Is it a number?:</p>
<pre class="literal-block">
rcall NUMBER_PFA
cpi TOS, 0x00 ; all chars converted?
brne _maybe_word
</pre>
<p>Then leave it on the stack:</p>
<pre class="literal-block">
mov TOS, TOSL
popup
ret
</pre>
<p>Otherwise, put length back on TOS and call find:</p>
<pre class="literal-block">
_maybe_word:
mov TOS, temp_length
popup
rcall FIND_PFA
</pre>
<p>Did we find the word?:</p>
<pre class="literal-block">
cpi TOS, 0xff
brne _is_word
</pre>
<p>No? Emit a '?' and be done with it:</p>
<pre class="literal-block">
popup
ldi TOS, '?'
rcall EMIT_PFA
ret
</pre>
<p>We found the word, execute it:</p>
<pre class="literal-block">
_is_word:
rcall TPFA_PFA
movw Z, X
popupw
ijmp
</pre>
</div>
</div>
<div class="section" id="conclusion">
<h1>Conclusion</h1>
<p>So that is a useful not-quite-Forth interpreter. I've burned this
program to my Pololu Baby Orangutan and it runs. I can connect to it
over a serial connection to pins PD0 and PD1 (I'm using the Pololu USB
AVR programmer and it's built in USB-to-TTL-compatible serial port.)</p>
<p>The following thirteen words are defined above:</p>
<ul class="simple">
<li>Key</li>
<li>Emit</li>
<li>Echo</li>
<li>Drop</li>
<li>Word</li>
<li>Number</li>
<li><<w (Left Shift 16-bit Word)</li>
<li>Emithex</li>
<li>.s</li>
<li>Find</li>
<li>>pfa (To PFA)</li>
<li>Interpret</li>
</ul>
<p>Not bad for 716 bytes of machine code.</p>
<p>To me it is exciting and even a bit incredible to be communicating to a
chip smaller than (for instance) the pupil of my eye using a simple but
effective command line interface that fits within one kilobyte of code.</p>
<div class="section" id="program-ability">
<h2>Program-ability</h2>
<p>The main difference between this engine and a real Forth is that AVRVM
can't compile new words.</p>
<p>In a more typical (or really, more original) Forth target architecture,
the data and program RAM are not separate, and you could easily lay down
new words in memory and immediately use them.</p>
<p>With the split Harvard architecture of the AVR the program RAM is flash
and can only be written to about a thousand times before risking
degradation. (There is a 1K block of EEPROM memory which can be
erased/written up to about 100,000 times. I'm ignoring it for now but
hope to use it somehow in the future.)</p>
<p>Since the data SRAM has only 2K, and since you can't directly execute
code bytes from it, there's not really a lot of room for compiling words
there.</p>
<p>We can compile words there and use the SPM instruction to copy them to
flash RAM, and I plan to write some words to enable that at some point,
but it makes a lot more sense to use the rest of the 32K program memory
to include "libraries" of additional routines (Forth words) written in
assembler (or C with proper interfacing) that can then be "driven" by
small "scripts" stored in SRAM.</p>
<p>The main drawback of this method could be the inability to debug commands
(words) as you write them. But with careful coding and use of the
simulator we should be able to develop stable commands without "burning
out" too many processors (with Flash rewrites.)</p>
</div>
</div>
<div class="section" id="additional-functionality">
<h1>Additional Functionality</h1>
<p>Now that we have a nice little kernal, let's add some interesting
commands to exercise our "robot brain".</p>
<div class="section" id="blinkenlights">
<h2>Blinkenlights</h2>
<p>The AVR's digital output lines can be used to drive LEDs. Here are some
commands to set up a pin (PB4) for output and toggle it to turn an LED
on and off:</p>
<pre class="literal-block">
PB4_OUT:
.dw INTERPRET
.db 4, "pb4o"
PB4_OUT_PFA:
</pre>
<p>Set the direction to output:</p>
<pre class="literal-block">
sbi DDRB, DDB4
</pre>
<p>Turn the port bit on:</p>
<pre class="literal-block">
sbi PORTB, PORTB4
ret
</pre>
<p>And a command to toggle the pin to turn the light on and off:</p>
<pre class="literal-block">
PB4_TOGGLE:
.dw PB4_OUT
.db 4, "pb4t"
PB4_TOGGLE_PFA:
sbi PINB, PINB4
ret
</pre>
</div>
<div class="section" id="motor-driver-i">
<h2>Motor Driver I</h2>
<p>The Pololu Baby Orangutan has two timers wired up to a motor controller.
These commands set up the timer0 to drive the motor1 outputs (see
<a class="reference external" href="http://www.pololu.com/docs/0J15/5">http://www.pololu.com/docs/0J15/5</a> ):</p>
<pre class="literal-block">
M1_ON:
.dw PB4_TOGGLE
.db 4, "m1on"
M1_ON_PFA:
ldi Working, 0b11110011
out TCCR0A, Working
ldi Working, 0b00000010
out TCCR0B, Working
clr Working
out OCR0A, Working
out OCR0B, Working
sbi DDRD, DDD5
sbi DDRD, DDD6
ret
M1_FORWARD:
.dw M1_ON
.db 3, "m1f"
M1_FORWARD_PFA: