6502-QR-code-generator/QRCodeGenDemo.asm

779 lines
20 KiB
NASM

; QR Code generator demo V0.3
; (C) 2021 Thomas Jentzsch
; TODOs
; + get it working!
; + reduce RAM usage
; + reverse data
; + overlap data with QR code
; + multiple pattern formats
; + apply pattern
; x evaluate pattern (very slow!)
; - support multiple QR code versions
; o support multiple QR code levels
; - try to optimize function pattern (SetPixel)
; x add logo (does NOT work for such small sizes)
;---------------------------------------------------------------
; QR code data bytes (version 2):
; - 13 into right sprite column
; - 17 until horizontal timer line
; - 36 into right and middle sprite column
; - 8 in left sprite column
processor 6502
LIST OFF
include vcs.h
LIST ON
;===============================================================================
; A S S E M B L E R - S W I T C H E S
;===============================================================================
BASE_ADR = $f000
NTSC = 1
; QR Code Generator Switches
QR_VERSION = 2 ; 1, 2 or 3 (TODO 1 and 3)
QR_LEVEL = 1 ; 0 (L), 1 (M), 2 (Q) and 3 (H)
QR_OVERLAP = 1 ; overlaps input and output data to save RAM (0 not tested!)
QR_SINGLE_MASK = 0 ; (-156) if 1 uses only 1 of the 8 mask pattern
QR_PADDING = 1 ; (+22) add padding bytes
IF QR_VERSION = 1 || QR_VERSION = 3
ECHO ""
ECHO "ERROR: Version", [QR_VERSION]d, "unsupported by demo code"
ERR
ENDIF
;===============================================================================
; C O N S T A N T S
;===============================================================================
NUM_FIRST = 1 ; left top 9 and bottom 8 bits are fixed!
RND_EOR_VAL = $b4
_QR_TOTAL SET 0
;===============================================================================
; Z P - V A R I A B L E S
;===============================================================================
SEG.U variables
ORG $80
random .byte
;---------------------------------------
; QR code variables
; all byte counts based on version 2, level M QR code
tmpVars ds 6
msgIdx = tmpVars + 3
IF QR_SINGLE_MASK = 0
qrPattern .byte
ENDIF
;---------------------------------------
data ds QR_TOTAL ; 44 bytes
remainder = data ; (QR_DEGREE = 16 bytes)
msgData = data + QR_DEGREE ; (QR_MAX_DATA = 28 bytes)
;- - - - - - - - - - - - - - - - - - - -
; The QR code overlaps the data! It overwrites the data while being drawn.
IF QR_OVERLAP
qrCodeLst = data + 6 ; all but 6 bytes overlap (version 2 only!)
ds NUM_FIRST + QR_SIZE*3 - QR_TOTAL + 6 ; 38 bytes
ELSE
qrCodeLst ds NUM_FIRST + QR_SIZE*3 ; 76 bytes
ENDIF
grp0LLst = qrCodeLst + QR_SIZE * 0
firstMsl = qrCodeLst + QR_SIZE * 1
grp1Lst = qrCodeLst + NUM_FIRST + QR_SIZE * 1
grp0RLst = qrCodeLst + NUM_FIRST + QR_SIZE * 2
CODE_LST_SIZE = . - qrCodeLst
;---------------------------------------
; QR code total = 89/127 bytes
ECHO "RAM:", [$100 - .]d, "bytes free"
ECHO ""
;===============================================================================
; M A C R O S
;===============================================================================
MAC BIT_B
.byte $24
ENDM
MAC BIT_W
.byte $2c
ENDM
MAC SLEEP
IF {1} = 1
ECHO "ERROR: SLEEP 1 not allowed !"
END
ENDIF
IF {1} & 1
nop $00
REPEAT ({1}-3)/2
nop
REPEND
ELSE
REPEAT ({1})/2
nop
REPEND
ENDIF
ENDM
;-----------------------------------------------------------
MAC NEXT_RANDOM
;-----------------------------------------------------------
; update random value:
lda random ; 3
lsr ; 2
bcc .skipEOR ; 2/3
eor #RND_EOR_VAL ; 2
.skipEOR
sta random ; 3 = 14/19
ENDM
; Platform specific macros
IF QR_OVERLAP = 0
;-----------------------------------------------------------
MAC _BLACK_FUNC
;-----------------------------------------------------------
ldx #CODE_LST_SIZE-1
.loopBlack
lda BlackGfx,x
sta qrCodeLst,x
dex
bpl .loopBlack
ENDM
ELSE
;-----------------------------------------------------------
MAC _BLACK_LEFT
;-----------------------------------------------------------
ldx #NUM_FIRST + QR_SIZE-1-8
.loopBlackLeft
lda LeftBlack+8,x
sta qrCodeLst+8,x
dex
bpl .loopBlackLeft
ENDM
;-----------------------------------------------------------
MAC _BLACK_MIDDLE
;-----------------------------------------------------------
ldx #QR_SIZE-1
.loopBlackMiddle
lda GRP1Black,x
sta grp1Lst,x
dex
bpl .loopBlackMiddle
ENDM
;-----------------------------------------------------------
MAC _BLACK_RIGHT
;-----------------------------------------------------------
ldx #QR_SIZE
.loopBlackRight
lda GRP0RBlack-1,x
sta grp0RLst-1,x
dex
bne .loopBlackRight
ENDM
ENDIF ; /QR_OVERLAP
;-----------------------------------------------------------
MAC _DRAW_FUNC
;-----------------------------------------------------------
ldx #CODE_LST_SIZE-1
.loopBlack
lda qrCodeLst,x
ora BlackGfx,x
eor EorGfx,x
sta qrCodeLst,x
dex
bpl .loopBlack
ENDM
include QRCodeGen.inc
;===============================================================================
; R O M - C O D E
;===============================================================================
SEG Bank0
ORG BASE_ADR
;---------------------------------------------------------------
DrawScreen SUBROUTINE
;---------------------------------------------------------------
ldx #227
.waitTim:
lda INTIM
bne .waitTim
sta WSYNC
sta VBLANK
stx TIM64T
;---------------------------------------------------------------
ldx #3
bit SWCHB
bvs .skipCentering
; some vertical centering
ldx #(192-QR_SIZE*2)/2
.skipCentering
.waitTop
sta WSYNC
dex
bne .waitTop
ldx #QR_SIZE-1
lda #%1 ; 1st top left fixed pixel
bne .enterLoop
.tmpFirst = tmpVars
; the QR code kernel
.loopKernel ; @55
lda FirstIdxTbl,x ; 4*
bne .newFirst ; 2/3
lsr .tmpFirst ; 5
bpl .endFirst ; 3 = 14 unconditional
.newFirst ; @62
; $bf | $01 | $fe
bmi .enterLoop ; 2/3
lda firstMsl ; 3
.enterLoop
sta .tmpFirst ; 3 = 7
.endFirst ; @69
ldy #2 ; 2
.loopBlock
sta WSYNC ; 3 @74
;---------------------------------------
;M1-P0-P1-P0
lda .tmpFirst ; 3
asl ; 2
sta ENAM1 ; 3 = 8
lda grp1Lst,x ; 4
sta GRP1 ; 3
lda grp0LLst,x ; 4
sta GRP0 ; 3
SLEEP 17 ;17
lda grp0RLst,x ; 4
dey ; 2
sta GRP0 ; 3 = 40 @48
bne .loopBlock ; 3/2
dex ; 2
bpl .loopKernel ; 3/2=7/6
sta WSYNC
;---------------------------------------------------------------
sty ENAM1
sty GRP1
sty GRP0
ldx #2
.waitScreen:
lda INTIM
bne .waitScreen
sta WSYNC
stx VBLANK
rts
; DrawScreen
;---------------------------------------------------------------
Start SUBROUTINE
;---------------------------------------------------------------
cld ; Clear BCD math bit.
lda #0
tax
dex
txs
.clearLoop:
tsx
pha
bne .clearLoop
lda INTIM
ora #$10
sta random
jsr InitDemo
.mainLoop:
jsr VerticalBlank
jsr DrawScreen
jsr OverScan
jmp .mainLoop
;---------------------------------------------------------------
InitDemo SUBROUTINE
;---------------------------------------------------------------
sta WSYNC
;---------------------------------------
lda #$0e
sta COLUBK
lda #$00
sta COLUP0
sta COLUP1
lda #%001
sta NUSIZ0
sta VDELP1
ldx #$3f
stx HMP0
inx
stx HMP1
lda #$a0
sta HMM1
SLEEP 3
sta RESM1
sta RESP0
sta RESP1
sta WSYNC
;---------------------------------------
sta HMOVE
jmp GenerateQR
; GameInit
;---------------------------------------------------------------
VerticalBlank SUBROUTINE
;---------------------------------------------------------------
lda #%00001110
.loopVSync:
sta WSYNC
sta VSYNC
lsr
bne .loopVSync
IF NTSC
lda #44
ELSE
lda #77
ENDIF
sta TIM64T
bit INPT4
bmi .skipRegen
jsr GenerateQR
.skipRegen
NEXT_RANDOM
rts
; VerticalBlank
;---------------------------------------------------------------
OverScan SUBROUTINE
;---------------------------------------------------------------
IF NTSC
lda #36
ELSE
lda #63
ENDIF
sta TIM64T
.waitTim:
lda INTIM
bne .waitTim
rts
; OverScan
;---------------------------------------------------------------
GenerateQR SUBROUTINE
;---------------------------------------------------------------
; *** Generate QR code from message ***
IF QR_SINGLE_MASK = 0
lda random
and #$07
; lda #0
sta qrPattern
ENDIF
MessageCode
; convert the message into a data stream
.msgLen = tmpVars
.msgPtr = tmpVars+1
lda random
lsr
lsr
lsr
and #$0f
tay
; ldy #0
lda MessagePtrLo,y
sta .msgPtr
lda MessagePtrHi,y
sta .msgPtr+1
lda MessagePtrLo+1,y
sec
sbc .msgPtr
sta .msgLen
START_MSG
ldy #0
.loopMsg
lda (.msgPtr),y
ADD_MSG_BYTE
iny
cpy .msgLen
bcc .loopMsg
STOP_MSG
ECHO "QR Code message code:", [. - MessageCode]d, "bytes"
_QR_TOTAL SET _QR_TOTAL + . - MessageCode
GEN_QR_CODE
rts
BitMapCode
;---------------------------------------------------------------
CheckPixel SUBROUTINE
;---------------------------------------------------------------
; Platform specific code. Must NOT change X and Y registers!
; X = y; Y = x
; determine 8 bit column (0..2) or missile columns
tya
bne .notMissile
; check if single missile byte is affected
cpx #8
bcc .alwaysSet
cpx #8*2
bcs .alwaysSet
lda firstMsl
and BitMask-8,x
rts
.alwaysSet
lda #1
rts
.notMissile
cpy #1+8
bcs .notGRP0L
IF QR_OVERLAP
cpx #8 ; bottom left eye (partially) shared with data!
bcc .alwaysSet
ENDIF
lda grp0LLst,x
and BitMask-1,y
rts
.notGRP0L
cpy #1+8*2
bcs .notGRP1
lda grp1Lst,x
and BitMask-1-8,y
rts
.notGRP1
; must be GRP0R then
lda grp0RLst,x
and BitMask-1-8*2,y
rts
;---------------------------------------------------------------
InvertPixel SUBROUTINE
;---------------------------------------------------------------
; Platform specific code. Must NOT change X and Y registers!
; X = y; Y = x
; determine 8 bit column (0..2) or missile column
tya
bne .notMissile
; check if single missile byte is affected
cpx #8
bcc .ignore
cpx #8*2
bcs .ignore
lda BitMask-8,x
eor firstMsl
sta firstMsl
.ignore
rts
.notMissile
cpy #1+8
bcs .notGRP0L
lda grp0LLst,x
eor BitMask-1,y
sta grp0LLst,x
rts
.notGRP0L
cpy #1+8*2
bcs .notGRP1
lda grp1Lst,x
eor BitMask-1-8,y
sta grp1Lst,x
rts
.notGRP1
; must be GRP0R then
lda grp0RLst,x
eor BitMask-1-8*2,y
sta grp0RLst,x
rts
ECHO "QR Code bitmap code:", [. - BitMapCode]d, "bytes"
_QR_TOTAL SET _QR_TOTAL + . - BitMapCode
;===============================================================================
; R O M - T A B L E S (Bank 0)
;===============================================================================
org BASE_ADR + $600
FunctionModulesData
; Platform and version specific function module data definition
BlackGfx
LeftBlack
GRP0LBlack
.byte %11111111 ; constant, bit 0 of 2nd format copy, level
.byte %11111111 ; constant, bit 1 of 2nd format copy, level
.byte %11111111 ; constant, bit 2 of 2nd format copy, pattern
.byte %11111111 ; constant, bit 3 of 2nd format copy, pattern
.byte %11111111 ; constant, bit 4 of 2nd format copy, pattern
.byte %11111111 ; constant, bit 5 of 2nd format copy, ECC
.byte %11111111 ; constant, bit 6 of 2nd format copy, ECC
.byte %11111111 ; constant, 1 (dark module)
.byte %00000100
.byte %00000100
.byte %00000100
.byte %00000100
.byte %00000100
.byte %00000100
.byte %00000100
.byte %00000100
.byte %11111111 ; constant, bits 1..7 of 1st format copy
.byte %11111111 ; constant, bit 8 of 1st format copy, ECC
.byte %11111111 ; constant, 1 (timing bit)
.byte %11111111 ; constant, bit 9 of 1st format copy, ECC
.byte %11111111 ; constant, bit 10 of 1st format copy, ECC
.byte %11111111 ; constant, bit 11 of 1st format copy, ECC
.byte %11111111 ; constant, bit 12 of 1st format copy, ECC
.byte %11111111 ; constant, bit 13 of 1st format copy, ECC
.byte %11111111 ; constant, bit 14 of 1st format copy, ECC
;FirstBlack
.byte %00000000
GRP1Black
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000001
.byte %00000001
.byte %00000001
.byte %00000001
.byte %00000001
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %11111111
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
GRP0RBlack
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %11110000
.byte %11110000
.byte %11110000
.byte %11110000
.byte %11110000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %11111111 ; constant, bits 7..14 of 2nd format copy
.byte %11111111 ; constant
.byte %11111111 ; constant
.byte %11111111 ; constant
.byte %11111111 ; constant
.byte %11111111 ; constant
.byte %11111111 ; constant
.byte %11111111 ; constant
.byte %11111111 ; constant
EorGfx
;GRP0LEor
.byte %00000011 ; constant, bit 0 of 2nd format copy, level
.byte %11111011 ; constant, bit 1 of 2nd format copy, level
.byte %10001011 ; constant, bit 2 of 2nd format copy, pattern
.byte %10001011 ; constant, bit 3 of 2nd format copy, pattern
.byte %10001011 ; constant, bit 4 of 2nd format copy, pattern
.byte %11111011 ; constant, bit 5 of 2nd format copy, ECC
.byte %00000011 ; constant, bit 6 of 2nd format copy, ECC
.byte %11111110 ; constant, 1 (dark module)
.byte %00000000
.byte %00000100
.byte %00000000
.byte %00000100
.byte %00000000
.byte %00000100
.byte %00000000
.byte %00000100
.byte %11111011 ; constant, bits 1..7 of 1st format copy
.byte %11111111 ; constant, bit 8 of 1st format copy, ECC
.byte %00000010 ; constant, 1 (timing bit)
.byte %11111011 ; constant, bit 9 of 1st format copy, ECC
.byte %10001011 ; constant, bit 10 of 1st format copy, ECC
.byte %10001011 ; constant, bit 11 of 1st format copy, ECC
.byte %10001011 ; constant, bit 12 of 1st format copy, ECC
.byte %11111011 ; constant, bit 13 of 1st format copy, ECC
.byte %00000011 ; constant, bit 14 of 1st format copy, ECC
;FirstEor
.byte %00000000
;GRP1Eor
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %10101010
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
;GRP0REor
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %11100000
.byte %10100000
.byte %11100000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %00000000
.byte %11111111 ; constant, bits 7..14 of 2nd format copy
.byte %11111111 ; constant
.byte %10000000 ; constant
.byte %10111110 ; constant
.byte %10100010 ; constant
.byte %10100010 ; constant
.byte %10100010 ; constant
.byte %10111110 ; constant
.byte %10000000 ; constant
FirstIdxTbl ; for 25 pixel
ds 7, 0
.byte $fe
ds 7, 0
.byte $01
ds 7, 0
.byte $bf
ECHO "QR Code function modules data:", [. - FunctionModulesData]d, "bytes"
_QR_TOTAL SET _QR_TOTAL + . - FunctionModulesData
QR_CODE_DATA
.byte " QR Code Generator Demo V0.3 - (C)2021 Thomas Jentzsch "
; messages MUST not be longer than 26 bytes for version 2, level M!
; Galadriel:
MessageTbl
Message0
.byte "It began with the forging"
; .byte "AtariAge/?s=_1X<|>[]*#"
Message1
.byte "of the Great Rings. Three"
Message2
.byte "were given to the Elves,"
Message3
.byte "immortal, wisest and"
Message4
.byte "fairest of all beings."
Message5
.byte "Seven to the Dwarf lords,"
Message6
.byte "great miners and craftsmen"
Message7
.byte "of the mountain halls. And"
Message8
.byte "nine, nine rings were"
Message9
.byte "gifted to the race of men,"
Message10
.byte "who, above all else,"
Message11
.byte "desire power. But they"
Message12
.byte "were, all of them,"
Message13
.byte "deceived, for another Ring"
Message14
.byte "was made. In the land of"
Message15
.byte "Mordor, in the fires of..."
MessageEnd
; .byte "..the single hardest thing"
MessagePtrLo
.byte <Message0, <Message1, <Message2, <Message3
.byte <Message4, <Message5, <Message6, <Message7
.byte <Message8, <Message9, <Message10, <Message11
.byte <Message12, <Message13, <Message14, <Message15
.byte <MessageEnd
MessagePtrHi
.byte >Message0, >Message1, >Message2, >Message3
.byte >Message4, >Message5, >Message6, >Message7
.byte >Message8, >Message9, >Message10, >Message11
.byte >Message12, >Message13, >Message14, >Message15
.byte "JTZ"
org BASE_ADR + $ffc
.word Start
.word Start
ECHO "----------------------------------------"
ECHO "QR Code total:", [_QR_TOTAL]d, "bytes"
ECHO ""
ECHO "QR Code Version, Level (Degree): ", [QR_VERSION]d, ",", [QR_LEVEL]d, "(", [DEGREE]d, ")"