To test out the use of the new keypad encoder and LCD driver that is now
built into Tachyon I wrote a very small calculator app (316 bytes) in 78 lines including comment lines etc.
In the process I found that the lines that I shared between the LCD and the keypad were causing problems as the keypad was being scanned by the timer cog and the LCD could be written to anytime.
So I decided to buffer the LCD so the timer cog is now responsible for refreshing the LCD. This also means that the LCD can be called from another task without I/O conflicts or delays.
Anyhow, the calculator is setup on a 20x4 LCD along with a 4x4 keypad and the LCD displays the last three entries over 3 lines.
Next step is to interface F32, the floating point ROM built-into
Tachyon and add a larger keypad for a programmable scientific
calculator!
Tachyon Forth News
Tachyon Forth was developed for the Parallax Propeller 8 core microcontroller to not only provide an interactive development and debug environment but also as a means to create very compact yet fast code suitable for commercial projects. Tachyon offers a fast compile, fast and compact execution, a wide variety of devices drivers built-in and also offers FAT32 SD virtual file handling and Ethernet servers based on WIZnet chips.
Wednesday 11 October 2017
Saturday 7 October 2017
ARBITRARY MATRIX KEYPAD SCANNER
An arbitrary matrix keypad scanner? Sounds great, but what does it mean?
The other day I added a runtime configurable 4x4 keypad driver to EXTEND as a built-in device but as I was testing it on other hardware I had one device that had the row and columns scattered and not necessarily in order either. So I decided to redo the keypad driver so that I could specify a 32-bit mask for the rows and the same for the columns. These two longs are supplied to the keypad initialization routine !KEYPAD and the driver runs in the background (a timer cog task) only scanning and reading those bits that are specified in the masks.
Since we can have any combination of port pins for row and column this also means that it would be possible to scan a 16x16 matrix for 256 keys or even a 28x4 matrix etc. The driver generates a 10-bit code that corresponds to the row and column port pins and if a decode table is specified then the table is searched for a match but failing that it simply returns with the scancode.
How do you use it? Say if I had 4 columns on P0, P2, P4, P6, and 4 rows on P8,P9,P10,P11 then I would start up the scanner with:
The zero value is if we don't have a scancode table to convert scancodes to more friendly values such as ASCII etc. If you want to setup a table just point to a sequence of pairs of 16-bit values with the scancode and the result with the table terminate by a zero word like this:
TABLE K1144
$0100 || '1' || $0120 || '2' ||
$0140 || '3' || $0160 || 'A' ||
$0102 || '4' || $0122 || '5' ||
$0142 || '6' || $0162 || 'B' ||
$0104 || '7' || $0124 || '8' ||
$0144 || '9' || $0164 || 'C' ||
$0106 || '*' || $0126 || '0' ||
$0146 || '#' || $0166 || 'D' ||
0 ||
Then you can setup the keypad to use this table:
19 BEEPER
The other day I added a runtime configurable 4x4 keypad driver to EXTEND as a built-in device but as I was testing it on other hardware I had one device that had the row and columns scattered and not necessarily in order either. So I decided to redo the keypad driver so that I could specify a 32-bit mask for the rows and the same for the columns. These two longs are supplied to the keypad initialization routine !KEYPAD and the driver runs in the background (a timer cog task) only scanning and reading those bits that are specified in the masks.
Since we can have any combination of port pins for row and column this also means that it would be possible to scan a 16x16 matrix for 256 keys or even a 28x4 matrix etc. The driver generates a 10-bit code that corresponds to the row and column port pins and if a decode table is specified then the table is searched for a match but failing that it simply returns with the scancode.
How do you use it? Say if I had 4 columns on P0, P2, P4, P6, and 4 rows on P8,P9,P10,P11 then I would start up the scanner with:
0 %1111_00000000 %01010101 !KEYPAD
The zero value is if we don't have a scancode table to convert scancodes to more friendly values such as ASCII etc. If you want to setup a table just point to a sequence of pairs of 16-bit values with the scancode and the result with the table terminate by a zero word like this:
TABLE K1144
$0100 || '1' || $0120 || '2' ||
$0140 || '3' || $0160 || 'A' ||
$0102 || '4' || $0122 || '5' ||
$0142 || '6' || $0162 || 'B' ||
$0104 || '7' || $0124 || '8' ||
$0144 || '9' || $0164 || 'C' ||
$0106 || '*' || $0126 || '0' ||
$0146 || '#' || $0166 || 'D' ||
0 ||
Then you can setup the keypad to use this table:
K1144 %1111_00000000 %01010101 !KEYPAD
As with the earlier driver you can also specify a beeper and also redirect console input to come from the keypad too etc. 19 BEEPER
Tuesday 3 October 2017
To DO or not to DO - what NEXT?
Every Forther knows that if you want an indexed loop you use the DO LOOP or DO +LOOP words. In Tachyon I also had the ADO word which basically worked like a combined BOUNDS DO. Now there is no more DO, and with no more ADO then, what do we do?
The other way to loop without an index was simply to use FOR NEXT for a simple counted loop but that doesn't allow for an index or a variable step. Well, it doesn't normally. In order to squeeze more into the kernel I turned to enhancing FOR NEXT so that while it still works like the standard FOR NEXT, it also has indexing and variable step capabilities. If nothing else is specified the FOR NEXT loop maintains an index starting at zero and stepping by +1. Here is how it works:
.. 10 FOR I . SPACE NEXT 0 1 2 3 4 5 6 7 8 9 ok
Now what if we want to start from 100 instead of zero? Use 100 FROM and also if we want to step by 5 use 5 BY prior to the FOR like this:
.. CR 100 FROM 5 BY 10 FOR I . SPACE NEXT
100 105 110 115 120 125 130 135 140 145 ok
Another example is displaying the alphabet (just to keep it tight):
.. 'A' FROM 26 FOR I EMIT NEXT ABCDEFGHIJKLMNOPQRSTUVWXYZ ok
and back to front
.. 'Z' FROM -1 BY 26 FOR I EMIT NEXT ZYXWVUTSRQPONMLKJIHGFEDCBA ok
BTW, the FOR NEXT method also stacks a branch address so it is fast. Using LAP <code> LAP .LAP to measure:
.. 1,000,000 CR LAP FOR NEXT LAP .LAP
48000240 cycles at 80MHz = 600.003ms ok
Divide that down by a million and that is 600ns overhead each loop (plus 3us setup).
The other way to loop without an index was simply to use FOR NEXT for a simple counted loop but that doesn't allow for an index or a variable step. Well, it doesn't normally. In order to squeeze more into the kernel I turned to enhancing FOR NEXT so that while it still works like the standard FOR NEXT, it also has indexing and variable step capabilities. If nothing else is specified the FOR NEXT loop maintains an index starting at zero and stepping by +1. Here is how it works:
.. 10 FOR I . SPACE NEXT 0 1 2 3 4 5 6 7 8 9 ok
Now what if we want to start from 100 instead of zero? Use 100 FROM and also if we want to step by 5 use 5 BY prior to the FOR like this:
.. CR 100 FROM 5 BY 10 FOR I . SPACE NEXT
100 105 110 115 120 125 130 135 140 145 ok
Another example is displaying the alphabet (just to keep it tight):
.. 'A' FROM 26 FOR I EMIT NEXT ABCDEFGHIJKLMNOPQRSTUVWXYZ ok
and back to front
.. 'Z' FROM -1 BY 26 FOR I EMIT NEXT ZYXWVUTSRQPONMLKJIHGFEDCBA ok
BTW, the FOR NEXT method also stacks a branch address so it is fast. Using LAP <code> LAP .LAP to measure:
.. 1,000,000 CR LAP FOR NEXT LAP .LAP
48000240 cycles at 80MHz = 600.003ms ok
Divide that down by a million and that is 600ns overhead each loop (plus 3us setup).
V4.7 SWITCH CASE BREAK improvements
CASE structures are handy for when a list of values need to be compared against the supplied parameter and perform a specific action if there is a match.
For instance if we were outputting a character stream to an LCD display we would want to be able to interpret special control characters.Take for instance this cut-down but typical word that detects special control characters otherwise it handles the rest as data. The SWITCH word saves the parameter which all the CASE statements then use to compare to the supplied value. If there is a match it will execute everything up to the BREAK after which it will exit early. Once all the CASE words are exhausted then the parameter is recalled onto the stack again with SWITCH@ and handled as data.
pub LCDEMIT ( ch -- )
SWITCH
$0D CASE LCDCR BREAK
$0A CASE LCDLF BREAK
$0C CASE LCDCLS BREAK
SWITCH@ LCDCHAR
;
Previously the CASE word was compiling this sequence
SWITCH@ = IF
while BREAK would always compile an EXIT.
Then this was improved by having SWITCH compile
SWITCH= IF
while BREAK would test to see if it could the last word before BREAK into a jump rather than a call since Tachyon V4 wordcode allows for the lsb to be set to indicate a jump thus saving a EXIT in most cases (yeah, that was a pun).
How do we improve this again? This leads up to the next blog.
For instance if we were outputting a character stream to an LCD display we would want to be able to interpret special control characters.Take for instance this cut-down but typical word that detects special control characters otherwise it handles the rest as data. The SWITCH word saves the parameter which all the CASE statements then use to compare to the supplied value. If there is a match it will execute everything up to the BREAK after which it will exit early. Once all the CASE words are exhausted then the parameter is recalled onto the stack again with SWITCH@ and handled as data.
pub LCDEMIT ( ch -- )
SWITCH
$0D CASE LCDCR BREAK
$0A CASE LCDLF BREAK
$0C CASE LCDCLS BREAK
SWITCH@ LCDCHAR
;
Previously the CASE word was compiling this sequence
SWITCH@ = IF
while BREAK would always compile an EXIT.
Then this was improved by having SWITCH compile
SWITCH= IF
while BREAK would test to see if it could the last word before BREAK into a jump rather than a call since Tachyon V4 wordcode allows for the lsb to be set to indicate a jump thus saving a EXIT in most cases (yeah, that was a pun).
How do we improve this again? This leads up to the next blog.
Monday 2 October 2017
Multiple CASE vectors with SWITCHES
Previously we examined how the SWITCH CASE BREAK word works and was improved. In this post an extra construct is added that is simplicity itself, just the way I like it. What we do here is compile a sequence of values and actions after the control word so that it scans the list looking for a match.
This is the word SWITCHES which scans code words there were compiled after it in a word looking for 15-bit literal word since Tachyon V4 can encode a 15-bit literal into a word code.
pub SWITCHES ( val -- )
SWITCH R>
BEGIN DUP W@ $7FFF >
WHILE DUP W@ $7FFF AND SWITCH= IF 2+ W@ JUMP THEN 4+
REPEAT >R ;
Every literal is compared to the switch value and if there is a match then it will execute the next word in the list. Any non-literal word terminates the loop and returns to executing from that word inclusively. Now look at how it is used in a definition and note that there are no special constructs needed, just the list, and then code.
pub LCDEMIT
SWITCHES
$0D LCDCR $0A LCDLF $0C LCDCLS
$08 LCDBS $09 LCDTAB $15 LCDON
$16 LCDOFF $04 LCDHIDE
SWITCH@ LCDCHAR
;
Now there are only 2 memory words required for every condition rather than 5 words originally that was trimmed down to 3 or 4 words. Faster and more compact!
This is the word SWITCHES which scans code words there were compiled after it in a word looking for 15-bit literal word since Tachyon V4 can encode a 15-bit literal into a word code.
pub SWITCHES ( val -- )
SWITCH R>
BEGIN DUP W@ $7FFF >
WHILE DUP W@ $7FFF AND SWITCH= IF 2+ W@ JUMP THEN 4+
REPEAT >R ;
Every literal is compared to the switch value and if there is a match then it will execute the next word in the list. Any non-literal word terminates the loop and returns to executing from that word inclusively. Now look at how it is used in a definition and note that there are no special constructs needed, just the list, and then code.
pub LCDEMIT
SWITCHES
$0D LCDCR $0A LCDLF $0C LCDCLS
$08 LCDBS $09 LCDTAB $15 LCDON
$16 LCDOFF $04 LCDHIDE
SWITCH@ LCDCHAR
;
Now there are only 2 memory words required for every condition rather than 5 words originally that was trimmed down to 3 or 4 words. Faster and more compact!
Subscribe to:
Posts (Atom)