ac6502 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -32
- package/dist/components/IO/ACIA.d.ts +76 -0
- package/dist/components/IO/ACIA.js +282 -0
- package/dist/components/IO/ACIA.js.map +1 -0
- package/dist/components/IO/Attachments/Attachment.d.ts +112 -0
- package/dist/components/IO/Attachments/Attachment.js +71 -0
- package/dist/components/IO/Attachments/Attachment.js.map +1 -0
- package/dist/components/IO/Attachments/JoystickAttachment.d.ts +53 -0
- package/dist/components/IO/Attachments/JoystickAttachment.js +90 -0
- package/dist/components/IO/Attachments/JoystickAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.d.ts +63 -0
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +489 -0
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/KeyboardMatrixAttachment.d.ts +44 -0
- package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js +274 -0
- package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/KeypadAttachment.d.ts +47 -0
- package/dist/components/IO/Attachments/KeypadAttachment.js +141 -0
- package/dist/components/IO/Attachments/KeypadAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/LCDAttachment.d.ts +110 -0
- package/dist/components/IO/Attachments/LCDAttachment.js +716 -0
- package/dist/components/IO/Attachments/LCDAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/SNESAttachment.d.ts +85 -0
- package/dist/components/IO/Attachments/SNESAttachment.js +184 -0
- package/dist/components/IO/Attachments/SNESAttachment.js.map +1 -0
- package/dist/components/IO/Empty.d.ts +9 -0
- package/dist/components/IO/Empty.js +5 -7
- package/dist/components/IO/Empty.js.map +1 -1
- package/dist/components/IO/GPIOCard.d.ts +5 -5
- package/dist/components/IO/GPIOCard.js.map +1 -1
- package/dist/components/IO/RAMBank.d.ts +37 -0
- package/dist/components/IO/RAMBank.js +63 -0
- package/dist/components/IO/RAMBank.js.map +1 -0
- package/dist/components/IO/RTC.d.ts +107 -0
- package/dist/components/IO/RTC.js +483 -0
- package/dist/components/IO/RTC.js.map +1 -0
- package/dist/components/IO/Sound.d.ts +120 -0
- package/dist/components/IO/Sound.js +622 -0
- package/dist/components/IO/Sound.js.map +1 -0
- package/dist/components/IO/Storage.d.ts +74 -0
- package/dist/components/IO/Storage.js +409 -0
- package/dist/components/IO/Storage.js.map +1 -0
- package/dist/components/IO/Terminal.d.ts +19 -0
- package/dist/components/IO/Terminal.js +33 -0
- package/dist/components/IO/Terminal.js.map +1 -0
- package/dist/components/IO/VIA.d.ts +105 -0
- package/dist/components/IO/VIA.js +597 -0
- package/dist/components/IO/VIA.js.map +1 -0
- package/dist/components/IO/Video.d.ts +141 -0
- package/dist/components/IO/Video.js +630 -0
- package/dist/components/IO/Video.js.map +1 -0
- package/dist/components/Machine.d.ts +20 -24
- package/dist/components/Machine.js +249 -166
- package/dist/components/Machine.js.map +1 -1
- package/dist/index.js +28 -14
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +16 -16
- package/dist/lib.js +32 -32
- package/dist/lib.js.map +1 -1
- package/dist/tests/IO/ACIA.test.d.ts +1 -0
- package/dist/tests/IO/ACIA.test.js +423 -0
- package/dist/tests/IO/ACIA.test.js.map +1 -0
- package/dist/tests/IO/Attachments/Attachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/Attachment.test.js +339 -0
- package/dist/tests/IO/Attachments/Attachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/JoystickAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/JoystickAttachment.test.js +126 -0
- package/dist/tests/IO/Attachments/JoystickAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +779 -0
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js +355 -0
- package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/KeypadAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/KeypadAttachment.test.js +323 -0
- package/dist/tests/IO/Attachments/KeypadAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/LCDAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/LCDAttachment.test.js +627 -0
- package/dist/tests/IO/Attachments/LCDAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/SNESAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/SNESAttachment.test.js +331 -0
- package/dist/tests/IO/Attachments/SNESAttachment.test.js.map +1 -0
- package/dist/tests/IO/Empty.test.d.ts +1 -0
- package/dist/tests/IO/Empty.test.js +121 -0
- package/dist/tests/IO/Empty.test.js.map +1 -0
- package/dist/tests/IO/GPIOCard.test.js.map +1 -1
- package/dist/tests/IO/RAMBank.test.d.ts +1 -0
- package/dist/tests/IO/RAMBank.test.js +229 -0
- package/dist/tests/IO/RAMBank.test.js.map +1 -0
- package/dist/tests/IO/RTC.test.d.ts +1 -0
- package/dist/tests/IO/RTC.test.js +177 -0
- package/dist/tests/IO/RTC.test.js.map +1 -0
- package/dist/tests/IO/Sound.test.d.ts +1 -0
- package/dist/tests/IO/Sound.test.js +528 -0
- package/dist/tests/IO/Sound.test.js.map +1 -0
- package/dist/tests/IO/Storage.test.d.ts +1 -0
- package/dist/tests/IO/Storage.test.js +656 -0
- package/dist/tests/IO/Storage.test.js.map +1 -0
- package/dist/tests/IO/VIA.test.d.ts +1 -0
- package/dist/tests/IO/VIA.test.js +503 -0
- package/dist/tests/IO/VIA.test.js.map +1 -0
- package/dist/tests/IO/Video.test.d.ts +1 -0
- package/dist/tests/IO/Video.test.js +549 -0
- package/dist/tests/IO/Video.test.js.map +1 -0
- package/dist/tests/Machine.test.js +27 -42
- package/dist/tests/Machine.test.js.map +1 -1
- package/package.json +1 -1
- package/src/components/IO/{SerialCard.ts → ACIA.ts} +2 -2
- package/src/components/IO/{GPIOAttachments/GPIOAttachment.ts → Attachments/Attachment.ts} +2 -2
- package/src/components/IO/{GPIOAttachments/GPIOJoystickAttachment.ts → Attachments/JoystickAttachment.ts} +3 -3
- package/src/components/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.ts → Attachments/KeyboardEncoderAttachment.ts} +3 -3
- package/src/components/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.ts → Attachments/KeyboardMatrixAttachment.ts} +5 -5
- package/src/components/IO/{GPIOAttachments/GPIOKeypadAttachment.ts → Attachments/KeypadAttachment.ts} +3 -3
- package/src/components/IO/{GPIOAttachments/GPIOLCDAttachment.ts → Attachments/LCDAttachment.ts} +7 -7
- package/src/components/IO/{EmptyCard.ts → Empty.ts} +1 -1
- package/src/components/IO/{RAMCard.ts → RAMBank.ts} +8 -8
- package/src/components/IO/{RTCCard.ts → RTC.ts} +1 -1
- package/src/components/IO/{SoundCard.ts → Sound.ts} +2 -2
- package/src/components/IO/{StorageCard.ts → Storage.ts} +70 -73
- package/src/components/IO/{DevOutputBoard.ts → Terminal.ts} +2 -2
- package/src/components/IO/{GPIOCard.ts → VIA.ts} +64 -64
- package/src/components/IO/{VideoCard.ts → Video.ts} +1 -1
- package/src/components/Machine.ts +276 -176
- package/src/index.ts +34 -21
- package/src/lib.ts +16 -16
- package/src/tests/IO/{SerialCard.test.ts → ACIA.test.ts} +5 -5
- package/src/tests/IO/{GPIOAttachments/GPIOAttachment.test.ts → Attachments/Attachment.test.ts} +12 -12
- package/src/tests/IO/{GPIOAttachments/GPIOJoystickAttachment.test.ts → Attachments/JoystickAttachment.test.ts} +23 -23
- package/src/tests/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts → Attachments/KeyboardEncoderAttachment.test.ts} +4 -4
- package/src/tests/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts → Attachments/KeyboardMatrixAttachment.test.ts} +5 -5
- package/src/tests/IO/{GPIOAttachments/GPIOKeypadAttachment.test.ts → Attachments/KeypadAttachment.test.ts} +38 -38
- package/src/tests/IO/{GPIOAttachments/GPIOLCDAttachment.test.ts → Attachments/LCDAttachment.test.ts} +12 -12
- package/src/tests/IO/Empty.test.ts +143 -0
- package/src/tests/IO/{RAMCard.test.ts → RAMBank.test.ts} +33 -33
- package/src/tests/IO/{RTCCard.test.ts → RTC.test.ts} +6 -6
- package/src/tests/IO/{SoundCard.test.ts → Sound.test.ts} +6 -6
- package/src/tests/IO/{StorageCard.test.ts → Storage.test.ts} +34 -25
- package/src/tests/IO/{GPIOCard.test.ts → VIA.test.ts} +7 -7
- package/src/tests/IO/{VideoCard.test.ts → Video.test.ts} +13 -13
- package/src/tests/Machine.test.ts +31 -38
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const LCDAttachment_1 = require("../../../components/IO/Attachments/LCDAttachment");
|
|
4
|
+
// VIA Port A pin masks
|
|
5
|
+
const PIN_RS = 0x20;
|
|
6
|
+
const PIN_RW = 0x40;
|
|
7
|
+
const PIN_E = 0x80;
|
|
8
|
+
describe('LCDAttachment', () => {
|
|
9
|
+
let lcd;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
lcd = new LCDAttachment_1.LCDAttachment(16, 2);
|
|
12
|
+
});
|
|
13
|
+
// ── Helper: write a command via the GPIO bus ────────────────────
|
|
14
|
+
/** Simulate a command write: RS=0, RW=0 */
|
|
15
|
+
function writeCommand(lcd, cmd) {
|
|
16
|
+
lcd.sendCommand(cmd);
|
|
17
|
+
lcd.updatePixels();
|
|
18
|
+
}
|
|
19
|
+
/** Simulate a data byte write: RS=1, RW=0 */
|
|
20
|
+
function writeData(lcd, data) {
|
|
21
|
+
lcd.writeByte(data);
|
|
22
|
+
lcd.updatePixels();
|
|
23
|
+
}
|
|
24
|
+
/** Write a string to the LCD */
|
|
25
|
+
function writeString(lcd, str) {
|
|
26
|
+
for (let i = 0; i < str.length; i++) {
|
|
27
|
+
writeData(lcd, str.charCodeAt(i));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Simulate full GPIO bus cycle: set Port B data, set Port A control,
|
|
31
|
+
* raise E, then lower E (falling-edge latch) */
|
|
32
|
+
function gpioBusWrite(lcd, rs, data) {
|
|
33
|
+
const portAValue = (rs ? PIN_RS : 0); // RW = 0 (write)
|
|
34
|
+
const ddr = 0xFF; // all outputs
|
|
35
|
+
// Set data on Port B
|
|
36
|
+
lcd.writePortB(data, ddr);
|
|
37
|
+
// Raise E
|
|
38
|
+
lcd.writePortA(portAValue | PIN_E, ddr);
|
|
39
|
+
// Lower E (falling edge triggers latch)
|
|
40
|
+
lcd.writePortA(portAValue, ddr);
|
|
41
|
+
}
|
|
42
|
+
// ── Constructor & Reset ────────────────────────────────────────
|
|
43
|
+
describe('constructor and reset', () => {
|
|
44
|
+
it('should initialize with correct dimensions', () => {
|
|
45
|
+
expect(lcd.cols).toBe(16);
|
|
46
|
+
expect(lcd.rows).toBe(2);
|
|
47
|
+
});
|
|
48
|
+
it('should calculate pixel dimensions correctly', () => {
|
|
49
|
+
// 16 chars × (5+1) - 1 = 95 pixels wide
|
|
50
|
+
// 2 rows × (8+1) - 1 = 17 pixels high
|
|
51
|
+
expect(lcd.pixelsWidth).toBe(95);
|
|
52
|
+
expect(lcd.pixelsHeight).toBe(17);
|
|
53
|
+
});
|
|
54
|
+
it('should allocate pixel buffer', () => {
|
|
55
|
+
expect(lcd.buffer).toBeDefined();
|
|
56
|
+
expect(lcd.buffer.length).toBe(95 * 17);
|
|
57
|
+
});
|
|
58
|
+
it('should initialize DDRAM with spaces', () => {
|
|
59
|
+
const ddRam = lcd.getDDRam();
|
|
60
|
+
for (let i = 0; i < ddRam.length; i++) {
|
|
61
|
+
expect(ddRam[i]).toBe(0x20);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
it('should start with DDRAM address at 0', () => {
|
|
65
|
+
expect(lcd.getDDPtr()).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
it('should start with display off', () => {
|
|
68
|
+
expect(lcd.getDisplayFlags() & LCDAttachment_1.LCD_CMD_DISPLAY_ON).toBe(0);
|
|
69
|
+
});
|
|
70
|
+
it('should start with increment mode', () => {
|
|
71
|
+
expect(lcd.getEntryModeFlags() & LCDAttachment_1.LCD_CMD_ENTRY_MODE_INCREMENT).toBeTruthy();
|
|
72
|
+
});
|
|
73
|
+
it('should support different display sizes', () => {
|
|
74
|
+
const lcd20x4 = new LCDAttachment_1.LCDAttachment(20, 4);
|
|
75
|
+
expect(lcd20x4.cols).toBe(20);
|
|
76
|
+
expect(lcd20x4.rows).toBe(4);
|
|
77
|
+
// 20 × (5+1) - 1 = 119
|
|
78
|
+
// 4 × (8+1) - 1 = 35
|
|
79
|
+
expect(lcd20x4.pixelsWidth).toBe(119);
|
|
80
|
+
expect(lcd20x4.pixelsHeight).toBe(35);
|
|
81
|
+
});
|
|
82
|
+
it('should reset all state', () => {
|
|
83
|
+
// Modify state
|
|
84
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
85
|
+
writeData(lcd, 0x41); // 'A'
|
|
86
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x40);
|
|
87
|
+
// Reset
|
|
88
|
+
lcd.reset();
|
|
89
|
+
expect(lcd.getDDPtr()).toBe(0);
|
|
90
|
+
expect(lcd.getDisplayFlags()).toBe(0);
|
|
91
|
+
expect(lcd.getScrollOffset()).toBe(0);
|
|
92
|
+
expect(lcd.getDDRam()[0]).toBe(0x20); // space
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
// ── Command Processing ─────────────────────────────────────────
|
|
96
|
+
describe('clear display command', () => {
|
|
97
|
+
it('should clear DDRAM to spaces', () => {
|
|
98
|
+
writeData(lcd, 0x41); // 'A'
|
|
99
|
+
writeData(lcd, 0x42); // 'B'
|
|
100
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_CLEAR);
|
|
101
|
+
const ddRam = lcd.getDDRam();
|
|
102
|
+
expect(ddRam[0]).toBe(0x20);
|
|
103
|
+
expect(ddRam[1]).toBe(0x20);
|
|
104
|
+
});
|
|
105
|
+
it('should reset DDRAM pointer to 0', () => {
|
|
106
|
+
writeData(lcd, 0x41);
|
|
107
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_CLEAR);
|
|
108
|
+
expect(lcd.getDDPtr()).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
it('should reset scroll offset', () => {
|
|
111
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SHIFT | LCDAttachment_1.LCD_CMD_SHIFT_DISPLAY);
|
|
112
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_CLEAR);
|
|
113
|
+
expect(lcd.getScrollOffset()).toBe(0);
|
|
114
|
+
});
|
|
115
|
+
it('should reset entry mode to increment', () => {
|
|
116
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_ENTRY_MODE); // decrement mode
|
|
117
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_CLEAR);
|
|
118
|
+
expect(lcd.getEntryModeFlags() & LCDAttachment_1.LCD_CMD_ENTRY_MODE_INCREMENT).toBeTruthy();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('home command', () => {
|
|
122
|
+
it('should reset DDRAM pointer to 0', () => {
|
|
123
|
+
writeData(lcd, 0x41);
|
|
124
|
+
writeData(lcd, 0x42);
|
|
125
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_HOME);
|
|
126
|
+
expect(lcd.getDDPtr()).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
it('should reset scroll offset', () => {
|
|
129
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SHIFT | LCDAttachment_1.LCD_CMD_SHIFT_DISPLAY);
|
|
130
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_HOME);
|
|
131
|
+
expect(lcd.getScrollOffset()).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
it('should not clear DDRAM', () => {
|
|
134
|
+
writeData(lcd, 0x41);
|
|
135
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_HOME);
|
|
136
|
+
expect(lcd.getDDRam()[0]).toBe(0x41);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('entry mode command', () => {
|
|
140
|
+
it('should set increment mode', () => {
|
|
141
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_ENTRY_MODE | LCDAttachment_1.LCD_CMD_ENTRY_MODE_INCREMENT);
|
|
142
|
+
expect(lcd.getEntryModeFlags() & LCDAttachment_1.LCD_CMD_ENTRY_MODE_INCREMENT).toBeTruthy();
|
|
143
|
+
});
|
|
144
|
+
it('should set decrement mode', () => {
|
|
145
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_ENTRY_MODE); // no INCREMENT bit
|
|
146
|
+
expect(lcd.getEntryModeFlags() & LCDAttachment_1.LCD_CMD_ENTRY_MODE_INCREMENT).toBeFalsy();
|
|
147
|
+
});
|
|
148
|
+
it('should enable display shift on write', () => {
|
|
149
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_ENTRY_MODE | LCDAttachment_1.LCD_CMD_ENTRY_MODE_INCREMENT | LCDAttachment_1.LCD_CMD_ENTRY_MODE_SHIFT);
|
|
150
|
+
expect(lcd.getEntryModeFlags() & LCDAttachment_1.LCD_CMD_ENTRY_MODE_SHIFT).toBeTruthy();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('display control command', () => {
|
|
154
|
+
it('should turn display on', () => {
|
|
155
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
156
|
+
expect(lcd.getDisplayFlags() & LCDAttachment_1.LCD_CMD_DISPLAY_ON).toBeTruthy();
|
|
157
|
+
});
|
|
158
|
+
it('should turn display off', () => {
|
|
159
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
160
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY); // display off
|
|
161
|
+
expect(lcd.getDisplayFlags() & LCDAttachment_1.LCD_CMD_DISPLAY_ON).toBeFalsy();
|
|
162
|
+
});
|
|
163
|
+
it('should enable cursor', () => {
|
|
164
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON | LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR);
|
|
165
|
+
expect(lcd.getDisplayFlags() & LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR).toBeTruthy();
|
|
166
|
+
});
|
|
167
|
+
it('should enable cursor blink', () => {
|
|
168
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON | LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR_BLINK);
|
|
169
|
+
expect(lcd.getDisplayFlags() & LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR_BLINK).toBeTruthy();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
describe('set DDRAM address command', () => {
|
|
173
|
+
it('should set DDRAM address', () => {
|
|
174
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x05);
|
|
175
|
+
expect(lcd.getDDPtr()).toBe(0x05);
|
|
176
|
+
});
|
|
177
|
+
it('should set second row address', () => {
|
|
178
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x40);
|
|
179
|
+
expect(lcd.getDDPtr()).toBe(0x40);
|
|
180
|
+
});
|
|
181
|
+
it('should clear CGRAM pointer', () => {
|
|
182
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_CGRAM_ADDR | 0x00);
|
|
183
|
+
expect(lcd.getCGPtr()).not.toBeNull();
|
|
184
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x00);
|
|
185
|
+
expect(lcd.getCGPtr()).toBeNull();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('set CGRAM address command', () => {
|
|
189
|
+
it('should set CGRAM address', () => {
|
|
190
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_CGRAM_ADDR | 0x10);
|
|
191
|
+
expect(lcd.getCGPtr()).toBe(0x10);
|
|
192
|
+
});
|
|
193
|
+
it('should mask to 6 bits', () => {
|
|
194
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_CGRAM_ADDR | 0x3F);
|
|
195
|
+
expect(lcd.getCGPtr()).toBe(0x3F);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('shift command', () => {
|
|
199
|
+
it('should shift cursor right', () => {
|
|
200
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SHIFT | LCDAttachment_1.LCD_CMD_SHIFT_RIGHT);
|
|
201
|
+
expect(lcd.getDDPtr()).toBe(1);
|
|
202
|
+
});
|
|
203
|
+
it('should shift cursor left', () => {
|
|
204
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x05);
|
|
205
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SHIFT); // left
|
|
206
|
+
expect(lcd.getDDPtr()).toBe(4);
|
|
207
|
+
});
|
|
208
|
+
it('should shift display right', () => {
|
|
209
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SHIFT | LCDAttachment_1.LCD_CMD_SHIFT_DISPLAY | LCDAttachment_1.LCD_CMD_SHIFT_RIGHT);
|
|
210
|
+
expect(lcd.getScrollOffset()).toBe(-1);
|
|
211
|
+
});
|
|
212
|
+
it('should shift display left', () => {
|
|
213
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SHIFT | LCDAttachment_1.LCD_CMD_SHIFT_DISPLAY);
|
|
214
|
+
expect(lcd.getScrollOffset()).toBe(1);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
// ── Data Write ─────────────────────────────────────────────────
|
|
218
|
+
describe('data write to DDRAM', () => {
|
|
219
|
+
it('should write a byte to DDRAM', () => {
|
|
220
|
+
writeData(lcd, 0x41); // 'A'
|
|
221
|
+
expect(lcd.getDDRam()[0]).toBe(0x41);
|
|
222
|
+
});
|
|
223
|
+
it('should auto-increment address in increment mode', () => {
|
|
224
|
+
writeData(lcd, 0x41);
|
|
225
|
+
expect(lcd.getDDPtr()).toBe(1);
|
|
226
|
+
writeData(lcd, 0x42);
|
|
227
|
+
expect(lcd.getDDPtr()).toBe(2);
|
|
228
|
+
});
|
|
229
|
+
it('should auto-decrement address in decrement mode', () => {
|
|
230
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x05);
|
|
231
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_ENTRY_MODE); // decrement
|
|
232
|
+
writeData(lcd, 0x41);
|
|
233
|
+
expect(lcd.getDDPtr()).toBe(4);
|
|
234
|
+
});
|
|
235
|
+
it('should write a string to the first row', () => {
|
|
236
|
+
writeString(lcd, 'Hello');
|
|
237
|
+
expect(lcd.getRowText(0).substring(0, 5)).toBe('Hello');
|
|
238
|
+
});
|
|
239
|
+
it('should write to second row', () => {
|
|
240
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x40);
|
|
241
|
+
writeString(lcd, 'World');
|
|
242
|
+
expect(lcd.getRowText(1).substring(0, 5)).toBe('World');
|
|
243
|
+
});
|
|
244
|
+
it('should wrap from end of row 1 to row 2', () => {
|
|
245
|
+
// Write 40 characters to fill first row of DDRAM
|
|
246
|
+
for (let i = 0; i < 40; i++) {
|
|
247
|
+
writeData(lcd, 0x41 + (i % 26));
|
|
248
|
+
}
|
|
249
|
+
// Pointer should now be at 0x40 (second row)
|
|
250
|
+
expect(lcd.getDDPtr()).toBe(0x40);
|
|
251
|
+
});
|
|
252
|
+
it('should wrap from end of row 2 back to start', () => {
|
|
253
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x40);
|
|
254
|
+
// Write 40 characters to fill second row
|
|
255
|
+
for (let i = 0; i < 40; i++) {
|
|
256
|
+
writeData(lcd, 0x41 + (i % 26));
|
|
257
|
+
}
|
|
258
|
+
// Should wrap to beginning
|
|
259
|
+
expect(lcd.getDDPtr()).toBe(0x00);
|
|
260
|
+
});
|
|
261
|
+
it('should shift display when entry mode shift is enabled', () => {
|
|
262
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_ENTRY_MODE | LCDAttachment_1.LCD_CMD_ENTRY_MODE_INCREMENT | LCDAttachment_1.LCD_CMD_ENTRY_MODE_SHIFT);
|
|
263
|
+
writeData(lcd, 0x41);
|
|
264
|
+
expect(lcd.getScrollOffset()).toBe(1);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
// ── Data Read ──────────────────────────────────────────────────
|
|
268
|
+
describe('data read from DDRAM', () => {
|
|
269
|
+
it('should read back written data via readAddress', () => {
|
|
270
|
+
writeData(lcd, 0x41);
|
|
271
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x00);
|
|
272
|
+
expect(lcd.readAddress()).toBe(0x00);
|
|
273
|
+
});
|
|
274
|
+
it('should read DDRAM address via readAddress', () => {
|
|
275
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x15);
|
|
276
|
+
expect(lcd.readAddress()).toBe(0x15);
|
|
277
|
+
});
|
|
278
|
+
it('should read CGRAM address when in CGRAM mode', () => {
|
|
279
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_CGRAM_ADDR | 0x18);
|
|
280
|
+
expect(lcd.readAddress()).toBe(0x18);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
// ── CGRAM ──────────────────────────────────────────────────────
|
|
284
|
+
describe('CGRAM operations', () => {
|
|
285
|
+
it('should write custom character data to CGRAM', () => {
|
|
286
|
+
// Set CGRAM address for character 0, row 0
|
|
287
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_CGRAM_ADDR | 0x00);
|
|
288
|
+
// Write 8 rows of pattern (smile face)
|
|
289
|
+
const pattern = [0x00, 0x0A, 0x00, 0x00, 0x11, 0x0E, 0x00, 0x00];
|
|
290
|
+
for (const row of pattern) {
|
|
291
|
+
writeData(lcd, row);
|
|
292
|
+
}
|
|
293
|
+
// CGRAM pointer should have advanced
|
|
294
|
+
expect(lcd.getCGPtr()).toBe(8);
|
|
295
|
+
});
|
|
296
|
+
it('should render CGRAM character on display', () => {
|
|
297
|
+
// Define character 0 with a simple pattern (all pixels on in first row)
|
|
298
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_CGRAM_ADDR | 0x00);
|
|
299
|
+
writeData(lcd, 0x1F); // row 0: all 5 bits on
|
|
300
|
+
for (let i = 1; i < 8; i++) {
|
|
301
|
+
writeData(lcd, 0x00); // rows 1-7: all off
|
|
302
|
+
}
|
|
303
|
+
// Now write character 0 to DDRAM and enable display
|
|
304
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x00);
|
|
305
|
+
writeData(lcd, 0x00); // character 0 from CGRAM
|
|
306
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
307
|
+
lcd.updatePixels();
|
|
308
|
+
// Character 0 should be at position (0,0) - check first row pixels are on
|
|
309
|
+
// The pixel at (0,0) should be on since we set bit 4 (MSB of 5-bit) in row 0
|
|
310
|
+
const topLeftPixel = lcd.pixelState(0, 0);
|
|
311
|
+
expect(topLeftPixel).toBe(1);
|
|
312
|
+
});
|
|
313
|
+
it('should wrap CGRAM pointer in increment mode', () => {
|
|
314
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_CGRAM_ADDR | 0x00);
|
|
315
|
+
// Write 128 bytes (16 chars × 8 rows = full CGRAM)
|
|
316
|
+
for (let i = 0; i < 128; i++) {
|
|
317
|
+
writeData(lcd, 0x00);
|
|
318
|
+
}
|
|
319
|
+
// Should wrap back to 0
|
|
320
|
+
expect(lcd.getCGPtr()).toBe(0);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
// ── Pixel Output ───────────────────────────────────────────────
|
|
324
|
+
describe('pixel output', () => {
|
|
325
|
+
it('should render all pixels off when display is off', () => {
|
|
326
|
+
writeData(lcd, 0x41); // 'A'
|
|
327
|
+
// Display is off by default
|
|
328
|
+
lcd.updatePixels();
|
|
329
|
+
for (let y = 0; y < lcd.pixelsHeight; y++) {
|
|
330
|
+
for (let x = 0; x < lcd.pixelsWidth; x++) {
|
|
331
|
+
const state = lcd.pixelState(x, y);
|
|
332
|
+
// All character pixels should be 0 (off), gaps are -1
|
|
333
|
+
if (state !== -1) {
|
|
334
|
+
expect(state).toBe(0);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
it('should render character pixels when display is on', () => {
|
|
340
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
341
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x00);
|
|
342
|
+
writeData(lcd, 0xFF); // char 255 — all pixels on in A00 font
|
|
343
|
+
lcd.updatePixels();
|
|
344
|
+
// Check that some pixels are on in the first character cell
|
|
345
|
+
let hasOnPixel = false;
|
|
346
|
+
for (let y = 0; y < 8; y++) {
|
|
347
|
+
for (let x = 0; x < 5; x++) {
|
|
348
|
+
if (lcd.pixelState(x, y) === 1) {
|
|
349
|
+
hasOnPixel = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
expect(hasOnPixel).toBe(true);
|
|
354
|
+
});
|
|
355
|
+
it('should have gap pixels between characters', () => {
|
|
356
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
357
|
+
lcd.updatePixels();
|
|
358
|
+
// Column 5 (between char 0 and char 1) should be gap (-1)
|
|
359
|
+
const gapPixel = lcd.pixelState(5, 0);
|
|
360
|
+
expect(gapPixel).toBe(-1);
|
|
361
|
+
});
|
|
362
|
+
it('should have gap pixels between rows', () => {
|
|
363
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
364
|
+
lcd.updatePixels();
|
|
365
|
+
// Row 8 (between row 0 and row 1) should be gap (-1)
|
|
366
|
+
const gapPixel = lcd.pixelState(0, 8);
|
|
367
|
+
expect(gapPixel).toBe(-1);
|
|
368
|
+
});
|
|
369
|
+
it('should return -1 for out-of-bounds coordinates', () => {
|
|
370
|
+
expect(lcd.pixelState(-1, 0)).toBe(-1);
|
|
371
|
+
expect(lcd.pixelState(0, -1)).toBe(-1);
|
|
372
|
+
expect(lcd.pixelState(lcd.pixelsWidth, lcd.pixelsHeight)).toBe(-1);
|
|
373
|
+
});
|
|
374
|
+
it('should render spaces as all-off pixels', () => {
|
|
375
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
376
|
+
// DDRAM is initialized to spaces
|
|
377
|
+
lcd.updatePixels();
|
|
378
|
+
// All pixels in space characters should be off
|
|
379
|
+
for (let y = 0; y < 8; y++) {
|
|
380
|
+
for (let x = 0; x < 5; x++) {
|
|
381
|
+
expect(lcd.pixelState(x, y)).toBe(0);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
// ── Cursor Display ─────────────────────────────────────────────
|
|
387
|
+
describe('cursor display', () => {
|
|
388
|
+
it('should show underline cursor on bottom row of character', () => {
|
|
389
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON | LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR);
|
|
390
|
+
lcd.updatePixels();
|
|
391
|
+
// Cursor at position 0: bottom row (y=7) should have pixels on
|
|
392
|
+
for (let x = 0; x < 5; x++) {
|
|
393
|
+
expect(lcd.pixelState(x, 7)).toBe(1);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
it('should not show cursor when cursor flag is off', () => {
|
|
397
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
398
|
+
lcd.updatePixels();
|
|
399
|
+
// Bottom row of first char should be off (space + no cursor)
|
|
400
|
+
for (let x = 0; x < 5; x++) {
|
|
401
|
+
expect(lcd.pixelState(x, 7)).toBe(0);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
it('should move cursor with DDRAM address', () => {
|
|
405
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON | LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR);
|
|
406
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x01);
|
|
407
|
+
lcd.updatePixels();
|
|
408
|
+
// Row 0, column 0 character should NOT have cursor
|
|
409
|
+
for (let x = 0; x < 5; x++) {
|
|
410
|
+
expect(lcd.pixelState(x, 7)).toBe(0);
|
|
411
|
+
}
|
|
412
|
+
// Row 0, column 1 character should have cursor (x offset = 6)
|
|
413
|
+
for (let x = 0; x < 5; x++) {
|
|
414
|
+
expect(lcd.pixelState(6 + x, 7)).toBe(1);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
// ── GPIO Bus Interface ─────────────────────────────────────────
|
|
419
|
+
describe('GPIO bus interface', () => {
|
|
420
|
+
it('should latch command on E falling edge', () => {
|
|
421
|
+
const ddr = 0xFF;
|
|
422
|
+
// Write clear command via GPIO
|
|
423
|
+
lcd.writePortB(LCDAttachment_1.LCD_CMD_CLEAR, ddr);
|
|
424
|
+
// Set RS=0, RW=0, raise E
|
|
425
|
+
lcd.writePortA(PIN_E, ddr);
|
|
426
|
+
// Lower E
|
|
427
|
+
lcd.writePortA(0x00, ddr);
|
|
428
|
+
// DDRAM should be cleared (all spaces)
|
|
429
|
+
expect(lcd.getDDPtr()).toBe(0);
|
|
430
|
+
});
|
|
431
|
+
it('should write data when RS is high', () => {
|
|
432
|
+
// First turn on display via GPIO
|
|
433
|
+
gpioBusWrite(lcd, false, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
434
|
+
// Write 'A' with RS=1
|
|
435
|
+
gpioBusWrite(lcd, true, 0x41);
|
|
436
|
+
expect(lcd.getDDRam()[0]).toBe(0x41);
|
|
437
|
+
});
|
|
438
|
+
it('should not latch on E rising edge', () => {
|
|
439
|
+
const ddr = 0xFF;
|
|
440
|
+
// Write data to Port B
|
|
441
|
+
lcd.writePortB(0x41, ddr);
|
|
442
|
+
// Raise E (should NOT latch)
|
|
443
|
+
lcd.writePortA(PIN_RS | PIN_E, ddr);
|
|
444
|
+
// Data should not have been written yet
|
|
445
|
+
expect(lcd.getDDRam()[0]).toBe(0x20); // still space
|
|
446
|
+
});
|
|
447
|
+
it('should read DDRAM address via Port B when RW=1, RS=0', () => {
|
|
448
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x15);
|
|
449
|
+
// Set RW=1, RS=0 on Port A
|
|
450
|
+
const ddr = 0xFF;
|
|
451
|
+
lcd.writePortA(PIN_RW, ddr);
|
|
452
|
+
const result = lcd.readPortB(ddr, 0);
|
|
453
|
+
expect(result & 0x7F).toBe(0x15);
|
|
454
|
+
});
|
|
455
|
+
it('should read DDRAM data via Port B when RW=1, RS=1', () => {
|
|
456
|
+
// Write 'A' at address 0
|
|
457
|
+
writeData(lcd, 0x41);
|
|
458
|
+
// Set address back to 0
|
|
459
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x00);
|
|
460
|
+
// Set RW=1, RS=1 on Port A for data read
|
|
461
|
+
const ddr = 0xFF;
|
|
462
|
+
lcd.writePortA(PIN_RW | PIN_RS, ddr);
|
|
463
|
+
const result = lcd.readPortB(ddr, 0);
|
|
464
|
+
expect(result).toBe(0x41);
|
|
465
|
+
});
|
|
466
|
+
it('should return 0xFF from Port B when RW is low', () => {
|
|
467
|
+
const ddr = 0xFF;
|
|
468
|
+
lcd.writePortA(0x00, ddr); // RW=0
|
|
469
|
+
expect(lcd.readPortB(ddr, 0)).toBe(0xFF);
|
|
470
|
+
});
|
|
471
|
+
it('should always return 0xFF from Port A', () => {
|
|
472
|
+
expect(lcd.readPortA(0xFF, 0x00)).toBe(0xFF);
|
|
473
|
+
});
|
|
474
|
+
it('should perform a full write sequence via GPIO bus', () => {
|
|
475
|
+
// Step 1: Function set (8-bit, 2-line)
|
|
476
|
+
gpioBusWrite(lcd, false, LCDAttachment_1.LCD_CMD_FUNCTION | LCDAttachment_1.LCD_CMD_FUNCTION_LCD_2LINE | 0x10);
|
|
477
|
+
// Step 2: Display ON, cursor ON
|
|
478
|
+
gpioBusWrite(lcd, false, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON | LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR);
|
|
479
|
+
// Step 3: Clear display
|
|
480
|
+
gpioBusWrite(lcd, false, LCDAttachment_1.LCD_CMD_CLEAR);
|
|
481
|
+
// Step 4: Entry mode set — increment, no shift
|
|
482
|
+
gpioBusWrite(lcd, false, LCDAttachment_1.LCD_CMD_ENTRY_MODE | LCDAttachment_1.LCD_CMD_ENTRY_MODE_INCREMENT);
|
|
483
|
+
// Step 5: Write "Hi"
|
|
484
|
+
gpioBusWrite(lcd, true, 0x48); // 'H'
|
|
485
|
+
gpioBusWrite(lcd, true, 0x69); // 'i'
|
|
486
|
+
expect(lcd.getRowText(0).substring(0, 2)).toBe('Hi');
|
|
487
|
+
expect(lcd.getDDPtr()).toBe(2);
|
|
488
|
+
expect(lcd.getDisplayFlags() & LCDAttachment_1.LCD_CMD_DISPLAY_ON).toBeTruthy();
|
|
489
|
+
expect(lcd.getDisplayFlags() & LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR).toBeTruthy();
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
// ── DDRAM Pointer Wrapping ─────────────────────────────────────
|
|
493
|
+
describe('DDRAM pointer wrapping', () => {
|
|
494
|
+
it('should wrap from 0x27 to 0x40 in 2-row mode', () => {
|
|
495
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x27);
|
|
496
|
+
writeData(lcd, 0x41); // triggers increment
|
|
497
|
+
expect(lcd.getDDPtr()).toBe(0x40);
|
|
498
|
+
});
|
|
499
|
+
it('should wrap from 0x67 to 0x00 in 2-row mode', () => {
|
|
500
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x67);
|
|
501
|
+
writeData(lcd, 0x41);
|
|
502
|
+
expect(lcd.getDDPtr()).toBe(0x00);
|
|
503
|
+
});
|
|
504
|
+
it('should decrement from 0x00 to 0x67', () => {
|
|
505
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_ENTRY_MODE); // decrement mode
|
|
506
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x00);
|
|
507
|
+
writeData(lcd, 0x41); // triggers decrement
|
|
508
|
+
expect(lcd.getDDPtr()).toBe(0x67);
|
|
509
|
+
});
|
|
510
|
+
it('should decrement from 0x40 to 0x27', () => {
|
|
511
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_ENTRY_MODE); // decrement mode
|
|
512
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x40);
|
|
513
|
+
writeData(lcd, 0x41); // triggers decrement
|
|
514
|
+
expect(lcd.getDDPtr()).toBe(0x27);
|
|
515
|
+
});
|
|
516
|
+
describe('1-row mode', () => {
|
|
517
|
+
let lcd1;
|
|
518
|
+
beforeEach(() => {
|
|
519
|
+
lcd1 = new LCDAttachment_1.LCDAttachment(16, 1);
|
|
520
|
+
});
|
|
521
|
+
it('should wrap from position 79 to 0', () => {
|
|
522
|
+
writeCommand(lcd1, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 79);
|
|
523
|
+
writeData(lcd1, 0x41);
|
|
524
|
+
expect(lcd1.getDDPtr()).toBe(0);
|
|
525
|
+
});
|
|
526
|
+
it('should wrap from position 0 to 79 when decrementing', () => {
|
|
527
|
+
writeCommand(lcd1, LCDAttachment_1.LCD_CMD_ENTRY_MODE); // decrement mode
|
|
528
|
+
writeCommand(lcd1, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x00);
|
|
529
|
+
writeData(lcd1, 0x41);
|
|
530
|
+
expect(lcd1.getDDPtr()).toBe(79);
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
// ── Display Shift ──────────────────────────────────────────────
|
|
535
|
+
describe('display shift', () => {
|
|
536
|
+
it('should shift displayed content left', () => {
|
|
537
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
538
|
+
// Write "AB" starting at position 0
|
|
539
|
+
writeData(lcd, 0x41); // A
|
|
540
|
+
writeData(lcd, 0x42); // B
|
|
541
|
+
// Shift display left
|
|
542
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SHIFT | LCDAttachment_1.LCD_CMD_SHIFT_DISPLAY);
|
|
543
|
+
// After shifting, the second character should now appear as first visible
|
|
544
|
+
expect(lcd.getScrollOffset()).toBe(1);
|
|
545
|
+
});
|
|
546
|
+
it('should shift displayed content right', () => {
|
|
547
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
548
|
+
writeData(lcd, 0x41);
|
|
549
|
+
// Shift display right
|
|
550
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SHIFT | LCDAttachment_1.LCD_CMD_SHIFT_DISPLAY | LCDAttachment_1.LCD_CMD_SHIFT_RIGHT);
|
|
551
|
+
expect(lcd.getScrollOffset()).toBe(-1);
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
// ── Tick (Cursor Blink) ────────────────────────────────────────
|
|
555
|
+
describe('tick and blink', () => {
|
|
556
|
+
it('should toggle blink state after enough ticks', () => {
|
|
557
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON | LCDAttachment_1.LCD_CMD_DISPLAY_CURSOR_BLINK);
|
|
558
|
+
const cpuFrequency = 1000000; // 1 MHz
|
|
559
|
+
// Each tick = 128 cycles at 1 MHz = 0.128 ms
|
|
560
|
+
// Need 350ms / 0.128ms ≈ 2734 ticks
|
|
561
|
+
for (let i = 0; i < 3000; i++) {
|
|
562
|
+
lcd.tick(cpuFrequency);
|
|
563
|
+
}
|
|
564
|
+
// Blink state should have toggled
|
|
565
|
+
lcd.updatePixels();
|
|
566
|
+
// We can verify by checking that the test doesn't crash and pixels update
|
|
567
|
+
});
|
|
568
|
+
it('should not crash with high frequency', () => {
|
|
569
|
+
lcd.tick(10000000); // 10 MHz
|
|
570
|
+
lcd.tick(10000000);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
// ── Row Text Helper ────────────────────────────────────────────
|
|
574
|
+
describe('getRowText', () => {
|
|
575
|
+
it('should return text for row 0', () => {
|
|
576
|
+
writeString(lcd, 'Hello, World!');
|
|
577
|
+
expect(lcd.getRowText(0).substring(0, 13)).toBe('Hello, World!');
|
|
578
|
+
});
|
|
579
|
+
it('should return text for row 1', () => {
|
|
580
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x40);
|
|
581
|
+
writeString(lcd, 'Line Two');
|
|
582
|
+
expect(lcd.getRowText(1).substring(0, 8)).toBe('Line Two');
|
|
583
|
+
});
|
|
584
|
+
it('should return spaces for empty row', () => {
|
|
585
|
+
const text = lcd.getRowText(0);
|
|
586
|
+
expect(text.length).toBe(16);
|
|
587
|
+
expect(text.trim()).toBe('');
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
// ── Edge Cases ─────────────────────────────────────────────────
|
|
591
|
+
describe('edge cases', () => {
|
|
592
|
+
it('should handle multiple E transitions without crash', () => {
|
|
593
|
+
const ddr = 0xFF;
|
|
594
|
+
// Rapid E toggling
|
|
595
|
+
for (let i = 0; i < 100; i++) {
|
|
596
|
+
lcd.writePortB(0x20, ddr);
|
|
597
|
+
lcd.writePortA(PIN_E, ddr);
|
|
598
|
+
lcd.writePortA(0x00, ddr);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
it('should handle function set command', () => {
|
|
602
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_FUNCTION | LCDAttachment_1.LCD_CMD_FUNCTION_LCD_2LINE | 0x10);
|
|
603
|
+
// Should not crash
|
|
604
|
+
});
|
|
605
|
+
it('should handle writing all character codes', () => {
|
|
606
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_DISPLAY | LCDAttachment_1.LCD_CMD_DISPLAY_ON);
|
|
607
|
+
for (let i = 0; i < 256; i++) {
|
|
608
|
+
writeCommand(lcd, LCDAttachment_1.LCD_CMD_SET_DRAM_ADDR | 0x00);
|
|
609
|
+
writeData(lcd, i);
|
|
610
|
+
lcd.updatePixels();
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
it('should handle isEnabled and getPriority', () => {
|
|
614
|
+
expect(lcd.isEnabled()).toBe(true);
|
|
615
|
+
expect(lcd.getPriority()).toBe(0);
|
|
616
|
+
});
|
|
617
|
+
it('should handle interrupt methods', () => {
|
|
618
|
+
expect(lcd.hasCA1Interrupt()).toBe(false);
|
|
619
|
+
expect(lcd.hasCA2Interrupt()).toBe(false);
|
|
620
|
+
expect(lcd.hasCB1Interrupt()).toBe(false);
|
|
621
|
+
expect(lcd.hasCB2Interrupt()).toBe(false);
|
|
622
|
+
lcd.clearInterrupts(true, true, true, true);
|
|
623
|
+
lcd.updateControlLines(false, false, false, false);
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
//# sourceMappingURL=LCDAttachment.test.js.map
|