diff --git a/GenPoly.asm b/GenPoly.asm new file mode 100644 index 0000000..1cf0129 --- /dev/null +++ b/GenPoly.asm @@ -0,0 +1,136 @@ + processor 6502 + +BASE_ADR = $f800 + +DEGREE = 26 +POLY = $11d ; GF(2^8) is based on 9 bit polynomial + ; x^8 + x^4 + x^3 + x^2 + 1 = 0x11d + + +;=============================================================================== +; Z P - V A R I A B L E S +;=============================================================================== + + SEG.U variables + ORG $80 + +tmpVars ds 4 + + ALIGN 16 + +result ds DEGREE + + +;=============================================================================== +; R O M - C O D E +;=============================================================================== + SEG Bank0 + ORG BASE_ADR + +;--------------------------------------------------------------- +Start SUBROUTINE +;--------------------------------------------------------------- + cld ; Clear BCD math bit. + lda #0 + tax + dex + txs +.clearLoop: + tsx + pha + bne .clearLoop + + RS_DIVISOR +.wait + jmp .wait + + +; Computes a Reed-Solomon ECC generator polynomial for degree 16, storing in result[0 : 16-1]. +; This is now implemented as a lookup table (Generator) +; g(x)=(x+1)(x+?)(x+?^2)(x+?^3)...(x+?^15) +; = x^16+3Bx^15+0Dx^14+68x^13+BDx^12+44x^11+d1x^10+1e^x9+08x^8 +; +A3x^7+41x^6+29x^5+E5x^4+62x^3+32x^2+24x+3B + MAC RS_DIVISOR +.root = tmpVars+2 +.i = tmpVars+3 + +; memset(result, 0, 16); + ldx #DEGREE-2 + ldy #0 +.loopClear + sty result,x + dex + bpl .loopClear +; result[16 - 1] = 1; // Start off with the monomial x^0 + iny + sty result + DEGREE - 1 +; uint8_t root = 1; + sty .root +; for (int i = 0; i < 16; i++) { + lda #DEGREE-1 ; just loop 16 times + sta .i +.loopI +; // Multiply the current product by (x - r^i) +; for (int j = 0; j < 16; j++) { + ldx #0 +.loopJ +; result[j] = reedSolomonMultiply(result[j], root); + lda result,x + ldy .root +; RS_MULT + jsr RSMult +; if (j != 16 - 1) + cpx #DEGREE - 1 + bcs .skipEor +; result[j] ^= result[j + 1]; + eor result+1,x +.skipEor + sta result,x + inx + cpx #DEGREE + bcc .loopJ +; root = reedSolomonMultiply(root, 0x02); + lda .root + ldy #$02 +; RS_MULT + jsr RSMult + sta .root + dec .i + bpl .loopI + ENDM + +; Returns the product of the two given field elements modulo GF(2^8/0x11D). +; All inputs are valid. +RSMult SUBROUTINE +; Russian peasant multiplication (x * y) +; Input: A = x, Y = y +; Result: A +.x = tmpVars +.y = tmpVars+1 + + sta .x + sty .y +; uint8_t z = 0; + lda #0 +; for (int i = 7; i >= 0; i--) { + ldy #7 +.loopI +; z = (uint8_t)((z << 1) ^ ((z >> 7) * 0x11D)); + asl + bcc .skipEorPoly + eor #> i) & 1) * x; + asl .y + bcc .skipEorX + eor .x +.skipEorX + dey + bpl .loopI + rts + + .byte "JTZ" + + org BASE_ADR + $7fc + .word Start + .word Start diff --git a/QRCodeGen.inc b/QRCodeGen.inc new file mode 100644 index 0000000..52c2045 --- /dev/null +++ b/QRCodeGen.inc @@ -0,0 +1,600 @@ +; The following code has been partially converted from the C code of the +; QR code generator found at https://github.com/nayuki/QR-Code-generator + +;----------------------------------------------------------- + MAC _RS_REMAINDER +;----------------------------------------------------------- +.i = tmpVars+2 +.factor = tmpVars+3 + +; memset(result, 0, 16); // done in START_TEXT +; for (int i = dataLen-1; i >= 0; i--) { // Polynomial division + ldx #MAX_DATA-1 +.loopI + stx .i +; uint8_t factor = data[i] ^ result[degree - 1]; + lda msgData,x + eor remainder + DEGREE - 1 + sta .factor +; memmove(&result[1], &result[0], (size_t)(16 - 1) * sizeof(result[0])); + ldx #DEGREE-1 +.loopMove + lda remainder-1,x + sta remainder,x + dex + bne .loopMove +; result[0] = 0; + lda #0 + sta remainder +; for (int j = 16-1; j >= 0; j--) + ldx #DEGREE-1 +.loopJ +; result[j] ^= reedSolomonMultiply(generator[j], factor); + lda Generator,x + ldy .factor + _RS_MULT + eor remainder,x + sta remainder,x +; } + dex + bpl .loopJ +; } + ldx .i + dex + bpl .loopI + ENDM + +;----------------------------------------------------------- +; Returns the product of the two given field elements modulo GF(2^8/0x11D). +; All inputs are valid. + MAC _RS_MULT +;----------------------------------------------------------- +; Russian peasant multiplication (x * y) +; Input: A = x, Y = y +; Result: A +.x = tmpVars +.y = tmpVars+1 + + sta .x + sty .y +; uint8_t z = 0; + lda #0 +; for (int i = 7; i >= 0; i--) { + ldy #7 +.loopI +; z = (uint8_t)((z << 1) ^ ((z >> 7) * 0x11D)); + asl + bcc .skipEorPoly + eor #> i) & 1) * x; + asl .y + bcc .skipEorX + eor .x +.skipEorX +; } + dey + bpl .loopI + ENDM + +;----------------------------------------------------------- +; Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of +; the QR Code to be black at function modules and white at codeword modules (including unused remainder bits). + MAC _DRAW_CODEWORDS +;----------------------------------------------------------- +; Note: This part has the maximum RAM usage +.right = tmpVars+0 +.vert = tmpVars+1 +.j = tmpVars+2 +.y = tmpVars+3 +.iBit = tmpVars+4 +.iByte = tmpVars+5 + +; blacken the (right) function modules in the bitmap + IF QR_OVERLAP + _BLACK_RIGHT ; returns with X = 0 + ELSE + _BLACK_FUNC ; returns with X = 0 + ENDIF +; int i = 0; // Bit index into the data +; 2600 code has data in reversed order + stx .iBit + lda #TOTAL_LEN-1 + sta .iByte +; // Do the funny zigzag scan +; for (int right = qrsize - 1; right >= 1; right -= 2) { // Index of right column in each column pair + ldy #QR_SIZE - 1 +.loopRight +; if (right == 6) + cpy #6 + bne .not6 +; right = 5; + dey ; skip the timing column +.not6 + sty .right + IF QR_OVERLAP +; overwrite shared data + cpy #16 + bne .skipBlackMiddle +; blacken the middle function modules in the bitmap + _BLACK_MIDDLE +.skipBlackMiddle + cpy #8 + bne .skipBlackLeft +; blacken the left function modules in the bitmap + _BLACK_LEFT +.skipBlackLeft + ENDIF +; for (int vert = 0; vert < qrsize; vert++) { // Vertical counter + ldy #QR_SIZE-1 +.loopVert + sty .vert +; for (int j = 0; j < 2; j++) { + ldy #-1 +.loopJ + iny + sty .j +; bool upward = ((right + 1) & 2) != 0; // 2600 code works in reverse + ldx .vert + lda .right + clc + adc #1 + and #$2 + bne .notUp +; int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate + lda #QR_SIZE-1 + sec + sbc .vert + tax +.notUp + stx .y +; int x = right - j; // Actual x coordinate + lda .right + sec + sbc .j +; sta .x + tay +; if (!getModule(qrcode, x, y) && i < dataLen * 8) { +; ldy .x +; ldx .y + jsr CheckPixel + bne .skipPixel + ldx .iByte + bmi .skipPixel +; bool black = getBit(data[i >> 3], 7 - (i & 7)); + lda data,x + ldx .iBit + and BitMask,x + beq .skipInv +; setModule(qrcode, x, y, black); +; ldy .x + ldx .y + jsr InvertPixel +.skipInv +; i++; + ldy .iBit + iny + tya + and #$07 + sta .iBit + bne .skipByte + dec .iByte +.skipByte +; } +.skipPixel + ldy .j + beq .loopJ +; } // for j + ldy .vert + dey + bpl .loopVert +; } // for vert + ldy .right + dey + dey + bpl .loopRight +; } // for right + ENDM + +;----------------------------------------------------------- + MAC _APPLY_MASK +;----------------------------------------------------------- + IF QR_SINGLE_MASK +.y = tmpVars + ldx #QR_SIZE - 1 +.loopY + stx .y + ldy #QR_SIZE - 1 +.loopX +; 0: (x + y) % 2 == 0 + tya + eor .y + lsr + bcs .skipInvert + jsr InvertPixel +.skipInvert + dey + bpl .loopX + dex + bpl .loopY + ELSE +.y = tmpVars +;.x = tmpVars+2 +.xMod3 = tmpVars+1 +.yMod3 = tmpVars+2 +.xDiv3 = tmpVars+3 +.tmp = tmpVars+4 + + lda #0 + sta .yMod3 + ldx #QR_SIZE - 1 +.loopY + stx .y + lda #0 + sta .xMod3 + sta .xDiv3 + ldy #QR_SIZE - 1 +.loopX +; sty .x + lda qrPattern + bne .not0 +; 0: (x + y) % 2 == 0 + tya + eor .y + bpl .checkMod2 + +.not0 + cmp #4 + bne .not4 +; 4: (x / 3 + y / 2) % 2 == 0 + lda .y + lsr + adc .xDiv3 + bpl .checkMod2 + +.not4 + bcs .above4 + + lsr + bcs .not2 +; 2: x % 3 == 0 + lda .xMod3 + beq .invert + bne .skipInvert + +.not2 + bne .is3 +; 1: y % 2 == 0 + txa + bpl .checkMod2 + +.is3 +; 3: (x + y) % 3 == 0 + lda .xMod3 +; sec + sbc .yMod3 + beq .invert + bne .skipInvert + +.above4 + cmp #6 + beq .is6 + bcs .is7 +.is6 + php +; 5: x * y % 2 + x * y % 3 == 0 +; 6: (x * y % 2 + x * y % 3) % 2 == 0 + lda .xMod3 + beq .modEven56 + lda .yMod3 + beq .modEven56 + clc + adc .xMod3 + BIT_W +.modEven56 + lda #0 + sta .tmp + tya + lsr + bcc .even56 + txa + lsr + bcc .even56 + inc .tmp +.even56 + plp + lda .tmp + bcs .checkMod2 + beq .invert + bne .skipInvert + +.is7 +; 7: ((x + y) % 2 + x * y % 3) % 2 == 0 + tya + eor .y + sta .tmp + lda .xMod3 + beq .modEven7 + lda .yMod3 + beq .modEven7 + clc + adc .xMod3 + adc .tmp + sta .tmp +.modEven7 + lda .tmp +.checkMod2 + lsr + bcs .skipInvert +.invert + jsr InvertPixel +.skipInvert +; next X + dec .xMod3 + bpl .xMod3OK + lda #2 + sta .xMod3 + inc .xDiv3 +.xMod3OK + dey + bpl .loopX +; next Y + dec .yMod3 + bpl .yMod3OK + lda #2 + sta .yMod3 +.yMod3OK + dex + bmi .exitLoopY + jmp .loopY + +.exitLoopY + ENDIF ; !QR_SINGLE_MASK + ENDM + +;----------------------------------------------------------- + MAC _DRAW_FORMAT +;----------------------------------------------------------- +.idx = tmpVars + + ldy #NUM_FORMAT-1 +.loopFormat + sty .idx + cpy #8 + IF QR_SINGLE_MASK + lda #%10101000 + and BitMask,y + bcc .lowFormat + lda #%00100100 + ELSE + ldx qrPattern + lda FormatLo,x + and BitMask,y + bcc .lowFormat + lda FormatHi,x + ENDIF + and BitMask-8,y +.lowFormat + beq .skipFormat + ldx FormatY1,y + lda FormatX1,y + tay + jsr InvertPixel + ldy .idx + ldx FormatY2,y + lda FormatX2,y + tay + jsr InvertPixel + ldy .idx +.skipFormat + dey + bpl .loopFormat + ENDM + +; ********** The user macros start here: ********** + +;----------------------------------------------------------- + MAC START_MSG +;----------------------------------------------------------- +; A = message length +; add mode and length to message data + tax + lsr + lsr + lsr + lsr + ora #(MODE << 4) +; (MODE << 4) | (MSG_LEN >> 4) + sta msgData + MAX_DATA - 1 + txa + asl + asl + asl + asl +; (MSG_LEN << 4) + sta msgData + MAX_DATA - 2 + lda #MAX_DATA - 3 + sta msgIdx +; clear the remaining data buffer + ldx #TOTAL_LEN-3 + lda #0 +.loopClear + sta data,x + dex + bpl .loopClear + ENDM + +;--------------------------------------------------------------- + MAC ADD_MSG_BYTE +;--------------------------------------------------------------- +; A = byte to add + ldx msgIdx + pha + lsr + lsr + lsr + lsr + ora msgData + 1,x + sta msgData + 1,x + pla + asl + asl + asl + asl + sta msgData,x + dec msgIdx + ENDM + +;----------------------------------------------------------- + MAC STOP_MSG +;----------------------------------------------------------- + IF QR_PADDING +.msgLen = tmpVars +; pad with optional filler bytes (QR code works without too) + lda #MAX_MSG - 1 + sec + sbc .msgLen + bcc .noPadding + tax +.loopPadding + lda #$ec ; defined by QR standard + sta msgData,x + dex + bmi .noPadding + lda #$11 ; defined by QR standard + sta msgData,x + dex + bpl .loopPadding +.noPadding + ENDIF + ENDM + +;----------------------------------------------------------- + MAC GEN_QR_CODE +;----------------------------------------------------------- +; This is the main macro to use! +QRCodeCode + +; calculate the ECC +RSRemainder + _RS_REMAINDER +; draw the code words onto the bitmap +DrawCodes + _DRAW_CODEWORDS +; apply the pattern mask +ApplyMask + _APPLY_MASK +; blacken the function modules in the bitmap again +; and draw the function modules in the bitmap +DrawFunc + _DRAW_FUNC +; draw the format bits +DrawFormat + _DRAW_FORMAT + + ECHO "QR Code encoding code:", [. - QRCodeCode]d, "bytes" +_QR_TOTAL SET _QR_TOTAL + . - QRCodeCode + ENDM + +;----------------------------------------------------------- + MAC QR_CODE_DATA +;----------------------------------------------------------- +; Add this to your code's data area +QRCodeData + +; Format Information Strings + IF QR_SINGLE_MASK = 0 + IF QR_LEVEL = 0 ; L +FormatLo + .byte %11101111 + .byte %11100101 + .byte %11111011 + .byte %11110001 + .byte %11001100 + .byte %11000110 + .byte %11011000 + .byte %11010010 +FormatHi + .byte %10001000 + .byte %11100110 + .byte %01010100 + .byte %00111010 + .byte %01011110 + .byte %00110000 + .byte %10000010 + .byte %11101100 + ENDIF + IF QR_LEVEL = 1 ; M +FormatLo + .byte %10101000 + .byte %10100010 + .byte %10111100 + .byte %10110110 + .byte %10001011 + .byte %10000001 + .byte %10011111 + .byte %10010101 +FormatHi + .byte %00100100 + .byte %01001010 + .byte %11111000 + .byte %10010110 + .byte %11110010 + .byte %10011100 + .byte %00101110 + .byte %01000000 + ENDIF +; TODO: levels Q and H + ENDIF + +; position of the 15 type information bits +FormatX1 + .byte 0, 1, 2, 3, 4, 5, 7, 8 + .byte 8, 8, 8, 8, 8, 8, 8 +FormatY2 + .byte 0, 1, 2, 3, 4, 5, 6 +; ds 8, QR_SIZE-9 ; shared +FormatY1 + ds 8, QR_SIZE-9 + .byte QR_SIZE-8, QR_SIZE-6, QR_SIZE-5, QR_SIZE-4 + .byte QR_SIZE-3, QR_SIZE-2, QR_SIZE-1 +FormatX2 + ds 7, 8 + .byte QR_SIZE-8, QR_SIZE-7, QR_SIZE-6, QR_SIZE-5 + .byte QR_SIZE-4, QR_SIZE-3, QR_SIZE-2, QR_SIZE-1 + +BitMask + .byte $80, $40, $20, $10, $8, $4, $2, $1 + +Generator ; data in reversed order! + IF DEGREE = 7 + .byte $75, $44, $0b, $a4, $9a, $7a, $7f + ENDIF + IF DEGREE = 10 + .byte $c1, $9d, $71, $5f, $5e, $c7, $6f, $9f + .byte $c2, $d8 + ENDIF + IF DEGREE = 15 + .byte $1a, $86, $20, $97, $84, $8b, $69, $69 + .byte $0a, $4a, $70, $a3, $6f, $c4, $1d + ENDIF + IF DEGREE = 16 +; Reed-Solomon ECC generator polynomial for degree 16 +; g(x)=(x+1)(x+?)(x+?^2)(x+?^3)...(x+?^15) +; = x^16+3bx^15+0dx^14+68x^13+bdx^12+44x^11+d1x^10+1e^x9+08x^8 +; +a3x^7+41x^6+29x^5+e5x^4+62x^3+32x^2+24x+3b + .byte $3b, $24, $32, $62, $e5, $29, $41, $a3 + .byte $08, $1e, $d1, $44, $bd, $68, $0d, $3b + ENDIF + IF DEGREE = 26 + .byte $5e, $2b, $4d, $92, $90, $46, $44, $87 + .byte $2a, $e9, $75, $d1, $28, $91, $18, $ce + .byte $38, $4d, $98, $c7, $62, $88, $04, $b7 + .byte $44, $f6 + ENDIF +DEGREE = . - Generator ; verify data + + ECHO "QR Code encoding data:", [. - QRCodeData]d, "bytes" +_QR_TOTAL SET _QR_TOTAL + . - QRCodeData + + ENDM \ No newline at end of file diff --git a/QRCodeGenDemo.asm b/QRCodeGenDemo.asm new file mode 100644 index 0000000..a7ec3fc --- /dev/null +++ b/QRCodeGenDemo.asm @@ -0,0 +1,817 @@ +; 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 Switches +QR_VERSION = 2 ; 1, 2 or 3 (TODO 1 and 3) +QR_LEVEL = 1 ; 0 (L) or 1 (M) (TODO Q and 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 + + +;=============================================================================== +; C O N S T A N T S +;=============================================================================== + +; QR code constants + IF QR_VERSION = 1 ;{ +QR_SIZE = 21 ; 21x21 QR code + IF QR_LEVEL = 0 +DEGREE = 7 ; for version 1, level L QR codes +MAX_DATA = 17+2 + ELSE +DEGREE = 10 ; for version 1, level M QR codes +MAX_DATA = 14+2 + ENDIF + ENDIF ;} + IF QR_VERSION = 2 +QR_SIZE = 25 ; 25x25 QR code + IF QR_LEVEL = 0 +DEGREE = 10 ; for version 2, level L QR codes +MAX_DATA = 32+2 + ELSE +DEGREE = 16 ; for version 2, level M QR codes +MAX_DATA = 26+2 + ENDIF + ENDIF + IF QR_VERSION = 3 ;{ +QR_SIZE = 29 ; 29x29 QR code + IF QR_LEVEL = 0 +DEGREE = 15 ; for version 3, level L QR codes +MAX_DATA = 53+2 + ELSE +DEGREE = 26 ; for version 3, level M QR codes +MAX_DATA = 42+2 + ENDIF + ENDIF ;} + + IF QR_VERSION = 1 || QR_VERSION = 3 + ECHO "" + ECHO "ERROR: Version", [QR_VERSION]d, "unsupported by demo code" + ERR + ENDIF + +MODE = %0100 ; byte mode +POLY = $11d ; GF(2^8) is based on 9 bit polynomial + ; x^8 + x^4 + x^3 + x^2 + 1 = 0x11d +NUM_FORMAT = 15 ; 15 type information bits + +MAX_MSG = MAX_DATA - 2 +TOTAL_LEN = MAX_DATA + DEGREE ; 44 + +NUM_FIRST = 1 ; left top 9 and bottom 8 bits are fixed! + +; other constants +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 TOTAL_LEN ; 44 bytes +remainder = data ; (DEGREE = 16 bytes) +msgData = data + DEGREE ; (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 - TOTAL_LEN + 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 +QR_LST_SIZE = . - qrCodeLst +;--------------------------------------- +; QR code total = 89/127 bytes + + ECHO "RAM:", [$100 - .]d, "bytes free" + ECHO "" + + +;=============================================================================== +; M A C R O S +;=============================================================================== + + 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 #QR_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 #QR_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 "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, ")"