As I've mentioned in IRC, I'm currently working on a QR Code generator for the z80-based monochrome TI calculators (focusing on the 84+ since that's what I have available, but I'll be try to keep it as portable as I can for reasons explained below).
On and off over the past 5 years, I've made some progress on a TI-Basic version (and the work I did with it contributed directly to my final project in my VisualBasic CS class in highschool), but the code is scattered around multiple, poorly named files, and TI-Basic doesn't lend itself well to writing inline documentation, so most of it is unreadable to me. Additionally, given that the underlying Reed–Solomon encoding is based on GF(256), many of the relevant algorithms appear to be well suited for 8-bit arithmetic. (And now that it's five years later and I've graduated as a math & physics major and I've taken both abstract algebra and logic design, I actually know what that means, rather than just knowing that it's some fancy math I'll need to get around.)
I also saw in the file archives that SopaXorzTaker has already made a limited TI-Basic version (according to the readme, not dissimilar in scope to the VB version I made in highschool: only the 21×21 size version, and only a constant masking code)—props to them, btw, bc this problem is not suited for TI-Basic. I hope that, with the lighter memory footprint that comes with using z80, I'll be able to generate larger-sized QR codes.
(For example, a number of GF(256) operations are aided by use of a table of discrete logarithms; each of 255 entries is an integer between 0 and 254 — an unsigned 8-bit integer. An appvar can store these as-is, taking 255 bytes + header, but a TI-Basic list interprets the entries as 9-byte reals, raising the memory footprint by an order of magnitude.)
I do expect to be reusing the front end I had made in Basic. It makes sense to have a front end in a language that is designed to be a front end, and a back end in a language designed to be a back end.
The reason I want to try to keep it as portable as I can is that if there's any use for this apart from the "coolness" factor, it's for transferring data from a monochrome calculator that has only a 2.5mm I/O port in situations where lugging the linking cable around is unfeasible. This project may make it possible to transfer data from your otherwise air-gapped calculator to the clipboard on your smartphone.
It's 3am right now, but expect an edit with code and links when I wake up
Edit: Code:
Code:
On and off over the past 5 years, I've made some progress on a TI-Basic version (and the work I did with it contributed directly to my final project in my VisualBasic CS class in highschool), but the code is scattered around multiple, poorly named files, and TI-Basic doesn't lend itself well to writing inline documentation, so most of it is unreadable to me. Additionally, given that the underlying Reed–Solomon encoding is based on GF(256), many of the relevant algorithms appear to be well suited for 8-bit arithmetic. (And now that it's five years later and I've graduated as a math & physics major and I've taken both abstract algebra and logic design, I actually know what that means, rather than just knowing that it's some fancy math I'll need to get around.)
I also saw in the file archives that SopaXorzTaker has already made a limited TI-Basic version (according to the readme, not dissimilar in scope to the VB version I made in highschool: only the 21×21 size version, and only a constant masking code)—props to them, btw, bc this problem is not suited for TI-Basic. I hope that, with the lighter memory footprint that comes with using z80, I'll be able to generate larger-sized QR codes.
(For example, a number of GF(256) operations are aided by use of a table of discrete logarithms; each of 255 entries is an integer between 0 and 254 — an unsigned 8-bit integer. An appvar can store these as-is, taking 255 bytes + header, but a TI-Basic list interprets the entries as 9-byte reals, raising the memory footprint by an order of magnitude.)
I do expect to be reusing the front end I had made in Basic. It makes sense to have a front end in a language that is designed to be a front end, and a back end in a language designed to be a back end.
The reason I want to try to keep it as portable as I can is that if there's any use for this apart from the "coolness" factor, it's for transferring data from a monochrome calculator that has only a 2.5mm I/O port in situations where lugging the linking cable around is unfeasible. This project may make it possible to transfer data from your otherwise air-gapped calculator to the clipboard on your smartphone.
It's 3am right now, but expect an edit with code and links when I wake up
Edit: Code:
Code:
; Program Name: QRAVMAK
; Author(s): ReGuess
; Description: AppVar Wrapper. TODO: Find a better name
; QR code AppVar Make? (QRAVMAK ?)
.nolist
#include "ti83plus.inc"
.list
.org userMem-2
.db $BB,$6D
; Program code goes here
;get variable name
ld HL, expName ; AppVarName
CALL findOrCreateAppVar
push HL ; EXP.sym -> Stack[0] ;Notation: Each 'element' of Stack is 2 Bytes
push DE ; EXP.data -> Stack[1] ;
CALL c, fillExpTbl ; CALL c, ...
ld HL, logName
CALL findOrCreateAppVar
ex (SP), HL ; EXP.data -> HL; LOG.sym -> Stack[1]
push HL ; EXP.data -> Stack[2]
push DE ; LOG.data -> Stack[3]
CALL c, fillLogTbl
pop DE ; I expect I'll eventually use these, so not getting rid of this yet
;call printTable
pop HL
pop DE
pop HL
RET
; Significantly modified from the TI-83+ Dev Guide Sys Routine Docs:
; https://education.ti.com/~/media/01E6AF2CADF84171B6F2E2039357BAAC
; page 12-3 (or p. 351) of 83psysroutines.pdf
; (the example for ChkFindSym)
;
; Inputs:
; HL: Pointer to variable name
; Outputs:
; OP1: Variable name
; HL: pointer to sym entry
; DE: pointer to (size bytes of) data
; BC: pointer to variable name
; Flags:
; Carry set if (and only if) variable was created
; Z set if variable found; otherwise, unknown?
; Destroyed:
; A
; OP2
; possibly OP3 and OP4 (if the variable was found archived)
findOrCreateAppVar:
push HL ; ptr to var name
rst rMov9ToOP1 ; OP1 = variable name
BCALL(_ChkFindSym) ; look up
jr nc, VarExists ; jump if it exists
ld HL, $00fe ; $00ff ; size to create (hard-coded as 254 bytes)
BCALL(_CreateAppVar) ; create it
push HL ; pointer to sym entry
push DE ; pointer to data
BCALL(_OP4ToOP1) ; OP1 = name
pop DE ; pointer to data
pop HL ; pointer to sym entry
scf ; set carry flag
jr done
VarExists:
ld A,B ; check for archived
or A ; in RAM? (also, clears carry flag)
jr z, done ; yes
BCALL(_Arc_Unarc) ; unarchive if enough RAM
pop HL ; pointer to variable name
jr findOrCreateAppVar ; look up pointers again in RAM now
done:
pop BC ; pointer to variable name
ret
; Uses the Russian Peasant algorithm for GF(256) multiplication,
; significantly optimized for multiplication by 2
; Inputs:
; DE: pointer to (size bytes of) data
; Destroys:
; A, B, C, D, E
fillExpTbl:
; putting $ff into B for the loop counter
; putting $1d into C because XOR C is smaller & faster than XOR $1d
; actually $1c now that I've optimized the sla A into an rlca
; (in general, it's faster to read a register
; than it is to read the next byte from program memory)
ld BC, $ff1c ; ld b, $ff
ld A, 1
inc DE ; move DE past the size bytes
peasantLoop:
inc DE
ld (DE), A
rlca ; sla A ; A <<= 1; (A.7 -> carry)
; this could probably be optimized with RLCA and some fancy math
jr nc, skip_xor_1d
xor C ; XOR $1d ; generator (0x11d)'s LSB
skip_xor_1d:
djnz peasantLoop
ret
; Inputs:
; HL: Pointer to EXP.data (Already filled; to be Read) [to be SEARCHED]
; DE: Pointer to LOG.data (Empty; to be Written to)
fillLogTbl:
inc DE ; move DE past the size bytes
;inc DE ; LOG.data+2
inc HL ; move HL past the size bytes
;inc HL ; EXP.data+2
ld A, 1 ; for A in range(1, 256):
log_loop:
push HL ; EXP.data+2 -> Stack(+1) ; Notation: Stack(+n) means w.r.t. start of call
ld BC, $00FF
cpir ; BC == C == 255(?) - index(A) (BC==C because B==0)
;HL is now pointer to location of value A in the variable, not that we care
ld H,B ;ld H,0
ld L,A
add HL,DE
ld B,A ; for storage
ld A,C ; for manipulation
cpl ;neg
dec A ;sub 2 ; might not need this if I play around w/ the `ld BC` above
ld (HL),A
pop HL
ld A,B
inc A
jr nz, log_loop
ret
;Inputs: DE: ptr to data
printTable:
ex de,hl
inc hl ; move DE past the size bytes ; data+2
ld B, $ff ; for A in range(1, 256):
print_loop:
inc hl
ld a, (hl)
push bc
push hl
ld h,0
ld l,a
bcall(_DispHL)
pop bc
push bc
bit 0,c
call z, wait
bcall(_NewLine) \ bcall(_NewLine)
pop hl
pop bc
djnz print_loop
ret
; Copied from https://www.plutiedev.com/z80-add-8bit-to-16bit#add-unsigned
; Inputs: A, HL: Addends
; Outputs: HL: sum
; Destroyed: A
addAtoHL:
add a, l ; A = A+L
ld l, a ; L = A+L
adc a, h ; A = A+L+H+carry
sub l ; A = H+carry
ld h, a ; H = H+carry
ret
; Props to Zeroko and Iambian, via Cemetech IRC
addMod255:
add a,b \ adc a,1 \ ret z \ dec a \ ret ; Iambian
;add a,b \ adc a,1 \ jp z,$+1 \ dec a ; Zeroko: in-line
;add A,B \ adc a,0 \ cp 255 \ ret nz \ xor a \ ret ; ReGuess
; there was another version that used inc a, but it left the chat before I had the chance to copy it
; A <- (A-B)mod 255
subMod255:
sub B
sbc A,0 ; ret nc \ dec A ; add a, 255
ret
; Inputs:
; B, C: multiplicands
; HL: Pointer to EXP.data
; DE: Pointer to LOG.data
; Outputs: A: Product
; Destroys: HL, DE
gf_mul:
xor A
cp B
ret z
cp C
ret z
; TODO
;push bc
inc hl \ inc hl ; todo: do this as soon as these pointers are found, instead of repeating it each time
inc de \ inc de
;push de
push hl
ld a, b
call addAtoHL
ld b,(hl)
pop hl
;push hl
ld a,c
call addAtoHL
ld a,(hl)
add a,b
jr nc, lbl_206
dec a ; modulo 255
lbl_206:
ex de,hl
call addAtoHL
ld a,(hl)
;pop hl
;pop de
;pop bc
ret
; Waits about 1 second. For debugging purposes.
; WARNING: DOES NOT CHECK IF MACHINE IS 84+ SERIES. DO NOT USE ON 83+
wait:
ld c, $45
in a,(45h)
waitloop:
in b,(c)
cp b
jr z, waitloop
ret
;data section
expName:
.db AppVarObj, "QREXPTBL"
logName:
.db AppVarObj, "QRLOGTBL" ; 8 bytes: no need for null termination?
string1: .db "Not yet implemented",0
; misc. code, not needed:
; comment out if we want (EXP.data - LOG.data)
;ex DE,HL ;if we want (LOG.data - EXP.data)
;xor A ; need to reset carry; might as well reset A, too.
;sbc HL,DE ; HL is now the difference (LOG.data - EXP.data)
;ex DE,HL ; now DE is the difference, and HL is now EXP.data+2
;inc A