forked from osresearch/LEDscape
-
Notifications
You must be signed in to change notification settings - Fork 0
/
matrix.p
310 lines (257 loc) · 8.03 KB
/
matrix.p
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
// \file
/* PRU based 16x32 LED Matrix driver.
*
* Drives up to sixteen 16x32 matrices using the PRU hardware.
*
* Uses sixteen data pins in GPIO0 (one for each data line on each
* matrix) and six control pins in GPIO1 shared between all the matrices.
*
* The ARM writes a 24-bit color 512x16 into the shared RAM, sets the
* frame buffer pointer in the command structure and the PRU clocks it out
* to the sixteen matrices. Since there is no PWM in the matrix hardware,
* the PRU will cycle through various brightness levels. After each PWM
* cycle it rechecks the frame buffer pointer, allowing a glitch-free
* transition to a new frame.
*
* To pause the redraw loop, write a NULL to the buffer pointer.
* To shut down the PRU, write -1 to the buffer pointer.
*/
// Pins available in GPIO0
#define gpio0_r1 3
#define gpio0_g1 30
#define gpio0_b1 15
#define gpio0_r2 2
#define gpio0_b2 14
#define gpio0_g2 31
#define gpio0_r3 7
#define gpio0_b3 20
#define gpio0_g3 22
#define gpio0_r4 27
#define gpio0_b4 23
#define gpio0_g4 26
// Pins available in GPIO1
#define gpio1_sel0 12 /* 44, must be sequential with sel1 and sel2 */
#define gpio1_sel1 13 /* 45 */
#define gpio1_sel2 14 /* 46 */
#define gpio1_latch 28 /* 60 */
#define gpio1_oe 15 /* 47 */
#define gpio1_clock 19 /* 51 */
/** Generate a bitmask of which pins in GPIO0-3 are used.
*
* \todo wtf "parameter too long": only 128 chars allowed?
*/
#define GPIO0_LED_MASK (0\
|(1<<gpio0_r1)\
|(1<<gpio0_g1)\
|(1<<gpio0_b1)\
|(1<<gpio0_r2)\
|(1<<gpio0_g2)\
|(1<<gpio0_b2)\
|(1<<gpio0_r3)\
|(1<<gpio0_g3)\
|(1<<gpio0_b3)\
|(1<<gpio0_r4)\
|(1<<gpio0_g4)\
|(1<<gpio0_b4)\
)
#define GPIO1_SEL_MASK (0\
|(1<<gpio1_sel0)\
|(1<<gpio1_sel1)\
|(1<<gpio1_sel2)\
)
.origin 0
.entrypoint START
#include "ws281x.hp"
/** Mappings of the GPIO devices */
#define GPIO0 (0x44E07000 + 0x100)
#define GPIO1 (0x4804c000 + 0x100)
#define GPIO2 (0x481AC000 + 0x100)
#define GPIO3 (0x481AE000 + 0x100)
/** Offsets for the clear and set registers in the devices.
* Since the offsets can only be 0xFF, we deliberately add offsets
*/
#define GPIO_CLRDATAOUT (0x190 - 0x100)
#define GPIO_SETDATAOUT (0x194 - 0x100)
/** Register map */
#define data_addr r0
#define row_skip_bytes r1
#define gpio0_base r2
#define gpio1_base r3
#define row r4
#define offset r5
#define scan r6
#define display_width_bytes r7
#define pixel r8
#define out_clr r9 // must be one less than out_set
#define out_set r10
#define p2 r12
#define bright r13
#define gpio0_led_mask r14
#define gpio1_sel_mask r15
#define pix r16
#define clock_pin r17
#define latch_pin r18
#define row1_ptr r19
#define row2_ptr r20
#define row3_ptr r21
#define row4_ptr r22
#define BRIGHT_STEP 32
#define CLOCK_LO \
SBBO clock_pin, gpio1_base, GPIO_SETDATAOUT, 4; \
#define CLOCK_HI \
SBBO clock_pin, gpio1_base, GPIO_CLRDATAOUT, 4; \
#define LATCH_HI \
SBBO latch_pin, gpio1_base, GPIO_SETDATAOUT, 4; \
#define LATCH_LO \
SBBO latch_pin, gpio1_base, GPIO_CLRDATAOUT, 4; \
#define DISPLAY_OFF \
MOV out_set, 0; \
SET out_set, gpio1_oe; \
SBBO out_set, gpio1_base, GPIO_SETDATAOUT, 4; \
#define DISPLAY_ON \
MOV out_set, 0; \
SET out_set, gpio1_oe; \
SBBO out_set, gpio1_base, GPIO_CLRDATAOUT, 4; \
START:
// Enable OCP master port
// clear the STANDBY_INIT bit in the SYSCFG register,
// otherwise the PRU will not be able to write outside the
// PRU memory space and to the BeagleBon's pins.
LBCO r0, C4, 4, 4
CLR r0, r0, 4
SBCO r0, C4, 4, 4
// Configure the programmable pointer register for PRU0 by setting
// c28_pointer[15:0] field to 0x0120. This will make C28 point to
// 0x00012000 (PRU shared RAM).
MOV r0, 0x00000120
MOV r1, CTPPR_0
ST32 r0, r1
// Configure the programmable pointer register for PRU0 by setting
// c31_pointer[15:0] field to 0x0010. This will make C31 point to
// 0x80001000 (DDR memory).
MOV r0, 0x00100000
MOV r1, CTPPR_1
ST32 r0, r1
// Write a 0x1 into the response field so that they know we have started
MOV r2, #0x1
SBCO r2, CONST_PRUDRAM, 12, 4
// Wait for the start condition from the main program to indicate
// that we have a rendered frame ready to clock out. This also
// handles the exit case if an invalid value is written to the start
// start position.
#define DISPLAY_WIDTH 128
#define DISPLAYS 2
#define ROW_WIDTH (DISPLAYS * DISPLAY_WIDTH)
MOV bright, #0
MOV gpio0_base, GPIO0
MOV gpio0_led_mask, GPIO0_LED_MASK
MOV gpio1_base, GPIO1
MOV gpio1_sel_mask, GPIO1_SEL_MASK
MOV display_width_bytes, 4*DISPLAY_WIDTH
MOV row_skip_bytes, 4*8*ROW_WIDTH
MOV clock_pin, 0
MOV latch_pin, 0
SET clock_pin, gpio1_clock
SET latch_pin, gpio1_latch
PWM_LOOP:
// Load the pointer to the buffer from PRU DRAM into r0 and the
// length (in bytes-bit words) into r1.
// start command into r2
LBCO data_addr, CONST_PRUDRAM, 0, 4
// Wait for a non-zero command
QBEQ PWM_LOOP, data_addr, #0
// Command of 0xFF is the signal to exit
QBEQ EXIT, data_addr, #0xFF
MOV offset, 0
MOV row, 0
// Store the pointers to each of the four outputs
ADD row1_ptr, data_addr, 0
ADD row2_ptr, row1_ptr, row_skip_bytes
ADD row3_ptr, row1_ptr, display_width_bytes
ADD row4_ptr, row3_ptr, row_skip_bytes
ROW_LOOP:
// compute where we are in the image
MOV pixel, 0
PIXEL_LOOP:
MOV out_set, 0
CLOCK_HI
// read a pixel worth of data
// \todo: track the four pointers separately
#define OUTPUT_ROW(N) \
LBBO pix, row##N##_ptr, offset, 4; \
QBGE skip_r##N, pix.b0, bright; \
SET out_set, gpio0_r##N; \
skip_r##N:; \
QBGE skip_g##N, pix.b1, bright; \
SET out_set, gpio0_g##N; \
skip_g##N:; \
QBGE skip_b##N, pix.b2, bright; \
SET out_set, gpio0_b##N; \
skip_b##N:; \
OUTPUT_ROW(1)
OUTPUT_ROW(2)
OUTPUT_ROW(3)
OUTPUT_ROW(4)
// All bits are configured;
// the non-set ones will be cleared
// We write 8 bytes since CLR and DATA are contiguous,
// which will write both the 0 and 1 bits in the
// same instruction.
XOR out_clr, out_set, gpio0_led_mask
SBBO out_clr, gpio0_base, GPIO_CLRDATAOUT, 8
CLOCK_LO
// If the brightness is less than the pixel, turn off
// but keep in mind that this is the brightness of
// the previous row, not this one.
// \todo: Test turning OE on and off every other,
// every fourth, every eigth, etc pixel based on
// the current brightness.
#if 1
LSL p2, pixel, 1
QBLT no_blank, bright, p2
DISPLAY_OFF
no_blank:
#endif
ADD offset, offset, 4
ADD pixel, pixel, 1
#if DISPLAY_WIDTH > 0xFF
MOV p2, DISPLAY_WIDTH
QBNE PIXEL_LOOP, pixel, p2
#else
QBNE PIXEL_LOOP, pixel, DISPLAY_WIDTH
#endif
// Disable output before we latch and set the address
// Unless we've just done a full image, in which case
// we treat this as a dummy row and go back to the top
DISPLAY_OFF
QBEQ NEXT_ROW, row, 8
LATCH_HI
// set address; select pins in gpio1 are sequential
LSL out_set, row, gpio1_sel0
XOR out_clr, out_set, gpio1_sel_mask
SBBO out_clr, gpio1_base, GPIO_CLRDATAOUT, 8 // set both
// We have clocked out all of the pixels for
// this row and the one eigth rows later.
// Latch the display register and then turn the display on
LATCH_LO
DISPLAY_ON
// We have drawn half the image on each chain;
// skip the second half
ADD offset, offset, display_width_bytes
ADD row, row, 1
QBA ROW_LOOP
NEXT_ROW:
// We have clocked out all of the panels.
// Celebrate and go back to the PWM loop
// Limit brightness to 0..MAX_BRIGHT
ADD bright, bright, BRIGHT_STEP
AND bright, bright, 0xFF
QBA PWM_LOOP
EXIT:
#ifdef AM33XX
// Send notification to Host for program completion
MOV R31.b0, PRU0_ARM_INTERRUPT+16
#else
MOV R31.b0, PRU0_ARM_INTERRUPT
#endif
HALT