6502-QR-code-generator/QRCodeGenDemo.asm

742 lines
18 KiB
NASM
Raw Blame History

; 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!)
; + optimize single pattern for space
; x support multiple QR code versions
; o support multiple QR code levels
; x 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 ; 0 = PAL50
ATARI_2600 = 1 ; enable for Atari 2600 specific code
; 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_PADDING = 1 ; (+ 22 bytes) add padding bytes (optional)
QR_GENERATE = 0 ; (+~12 bytes) generates Reed-Solomon ECC generator polynomial on-the-fly
; else uses built-in table
; Atari 2600 specific QR settings (keep set to 0 for other platforms!)
IFCONST ATARI_2600
QR_OVERLAP = 1 ; overlaps input and output data to save RAM (defined for version 2 only!)
QR_SINGLE_MASK = 0 ; (-255 bytes) if 1 uses only 1 of the 8 mask pattern
QR_DIRECT_DRAW = 0 ; (+ 45 bytes) draw byte columns instead of individual pixel
ENDIF
IF QR_VERSION != 2
ECHO ""
ECHO "*** ERROR: Version", [QR_VERSION]d, "unsupported by demo code! ***"
ERR
ENDIF
IF QR_SINGLE_MASK = 1 && QR_DIRECT_DRAW = 0
ECHO ""
ECHO "*** ERROR: Unsupported assembler switches combination! ***"
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
IF QR_DIRECT_DRAW
tmpVars ds 6
ELSE
tmpVars ds 9
ENDIF
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
IF QR_DIRECT_DRAW
QR_NON_OVER = 6
ELSE
QR_NON_OVER = 8
ENDIF
qrCodeLst = data + QR_NON_OVER ; all but 6/8 bytes overlap (version 2 only!)
ds NUM_FIRST + QR_SIZE*3 - QR_TOTAL + QR_NON_OVER ; 38/40 bytes
ELSE
qrCodeLst ds NUM_FIRST + QR_SIZE*3 ; 76 bytes
ENDIF ; /QR_OVERLAP
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
IF QR_GENERATE
qrGenerator ds QR_DEGREE
ENDIF
;---------------------------------------
; 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
; Atari 2600 specific macros
IF QR_OVERLAP = 0
IF QR_DIRECT_DRAW
;-----------------------------------------------------------
MAC _BLACK_FUNC
;-----------------------------------------------------------
; blacks all function/alignment and timing pattern
ldx #CODE_LST_SIZE
.loopBlack
lda BlackGfx-1,x
sta qrCodeLst-1,x
dex
bne .loopBlack
; X = 0!
ENDM
ENDIF
ELSE ; QR_OVERLAP
;-----------------------------------------------------------
MAC _BLACK_LEFT
;-----------------------------------------------------------
; Blacks all function/alignment and timing pattern areas of the left sprite column
IF QR_DIRECT_DRAW
ldx #NUM_FIRST + QR_SIZE-1-8
.loopBlackLeft
lda LeftBlack+8,x
sta qrCodeLst+8,x
dex
bpl .loopBlackLeft
ELSE
; clear the bitmap column first...
ldx #NUM_FIRST + QR_SIZE-1-8
lda #0
.loopBlackLeft
sta qrCodeLst+8,x
dex
bpl .loopBlackLeft
; ...then draw the pattern
IF QR_DIRECT_DRAW
ldy #1 ; left top pattern/vertical timing line
ELSE
ldy #2
ENDIF
.loopPattern
lda #$ff ; fill pattern
jsr DrawPattern
bpl .loopPattern
ENDIF
ENDM
;-----------------------------------------------------------
MAC _BLACK_MIDDLE
;-----------------------------------------------------------
; Blacks all function/alignment and timing pattern areas of the middle sprite column
IF QR_DIRECT_DRAW
ldx #QR_SIZE-1
.loopBlackMiddle
lda GRP1Black,x
sta grp1Lst,x
dex
bpl .loopBlackMiddle
ELSE
; clear the bitmap column first...
ldx #QR_SIZE-1
lda #0
.loopBlackMiddle
sta grp1Lst,x
dex
bpl .loopBlackMiddle
; ...then draw the pattern
ldy #6 ; align pattern
.loopPattern
lda #$ff ; fill pattern
jsr DrawPattern
cpy #5
bcs .loopPattern
ENDIF
ENDM
;-----------------------------------------------------------
MAC _BLACK_RIGHT
;-----------------------------------------------------------
; Blacks all function/alignment and timing pattern areas of the right sprite column
IF QR_DIRECT_DRAW
ldx #QR_SIZE
.loopBlackRight
lda GRP0RBlack-1,x
sta grp0RLst-1,x
dex
bne .loopBlackRight
ELSE
; clear the bitmap column first...
ldx #QR_SIZE-1
lda #$00
.loopBlackRight
sta grp0RLst,x
dex
bpl .loopBlackRight
; ...then draw the pattern
ldy #4 ; right pattern
.loopPattern
lda #$ff ; fill pattern
jsr DrawPattern
cpy #3
bcs .loopPattern
ldx #0
ENDIF
; X = 0!
ENDM
ENDIF ; /QR_OVERLAP
IF QR_DIRECT_DRAW
;-----------------------------------------------------------
MAC _DRAW_FUNC
;-----------------------------------------------------------
; Draws all function/alignment and timing pattern over existing codewords
ldx #CODE_LST_SIZE-1
.loopBlack
lda qrCodeLst,x
ora BlackGfx,x
eor EorGfx,x
sta qrCodeLst,x
dex
bpl .loopBlack
IF QR_SINGLE_MASK && (QR_VERSION = 3 || QR_VERSION = 4)
; invert 1st pixel of 1st copy (bit 0 not set in formats Q and H)
ldy #0
ldx #QR_SIZE-9
jsr InvertPixel
ENDIF
ENDM
ENDIF
;===============================================================================
; 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 ; @53
lda FirstIdxTbl,x ; 4*
cmp #1 ; 2
bcs .newFirst ; 2/3
lsr .tmpFirst ; 5
bpl .endFirst ; 3 = 16 unconditional
.newFirst ; @62
; $bf/$3f | $01 | $fe
bne .enterLoop ; 2/3
lda firstMsl ; 3
.enterLoop
sta .tmpFirst ; 3 = 8
.endFirst ; @70/69
ldy #-2 ; 2
.loopBlock
sta WSYNC ; 3 @75/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 10 ;10 = 24
lda grp0RLst,x ; 4
iny ; 2
cpy #1 ; 2
bcc .isZero ; 2/3
BIT_B ; 1
.isZero
dex ; 2
sta GRP0 ; 3 = 16 @48
bcs .loopBlock ; 2/3
bpl .loopKernel ; 3/2=5/4
sta WSYNC
;---------------------------------------------------------------
sty ENAM1
sty GRP1
sty GRP0
ldx #2
.waitScreen:
lda INTIM
bne .waitScreen
sta WSYNC
stx VBLANK
rts
; DrawScreen
include QRCodeGen.inc
;---------------------------------------------------------------
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 #3
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 and version 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 & QR_DIRECT_DRAW
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 and version 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
IF QR_DIRECT_DRAW
IF QR_VERSION = 1
ERR ; TODO
ENDIF
IF QR_VERSION = 2
IF QR_SINGLE_MASK
include FuncDataV2S.inc ; special pattern
ELSE
include FuncDataV2.inc
ENDIF
ENDIF
IF QR_VERSION = 3
ERR ; TODO
ENDIF
ENDIF
FirstIdxTbl ; for 25 pixel
ds 7, 0
.byte $fe
ds 7, 0
.byte $01
ds 7, 0
IF QR_LEVEL = 0 || QR_LEVEL = 1
.byte $bf ; 1st format bit is 1
ELSE
.byte $3f ; 1st format bit is 0
ENDIF
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!
MessageTbl
Message0
.byte "2002 - Thrust+ Platinum"
Message1
.byte "2001 - Jammed"
Message2
.byte "2005 - SWOOPS!"
Message3
.byte "2017 - ZeroPage Homebrew"
Message4
.byte "2019 - Aardvark"
Message5
.byte "2012 - Boulder Dash"
Message6
.byte "2021 - VROOM!"
Message7
.byte "2020 - Robot City"
Message8
.byte "2015 - Star Castle Arcade"
Message9
.byte "2001 - AtariAge"
Message10
.byte "1998 - Atari 2600 Nexus"
Message11
.byte "2009 - RAM Pong"
Message12
.byte "1977 - Atari 2600"
Message13
.byte "2014 - Three<65>s"
Message14
.byte "1996 - Stella Mailing List"
Message15
.byte "1996 - Stella Emulator"
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", [QR_VERSION]d, ", Level (Degree)", [QR_LEVEL]d, "(", [QR_DEGREE]d, "), Capacity", [QR_CAPACITY]d, "bytes"
ECHO " -> Message Space", [QR_CAPACITY-QR_DEGREE-2]d, "bytes"