ws2812 0.0.2
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.
- data/.gitignore +3 -0
- data/LICENSE.txt +339 -0
- data/README.md +41 -0
- data/Rakefile +39 -0
- data/examples/basic.rb +45 -0
- data/examples/binaryclock.rb +161 -0
- data/examples/digiclock.rb +167 -0
- data/examples/gamma-vs-direct.rb +31 -0
- data/examples/unicornhat-test.rb +47 -0
- data/ext/ws2812/COMPILING.txt +4 -0
- data/ext/ws2812/SOURCES.txt +2 -0
- data/ext/ws2812/board_info.c +143 -0
- data/ext/ws2812/board_info.h +6 -0
- data/ext/ws2812/clk.h +60 -0
- data/ext/ws2812/dma.c +79 -0
- data/ext/ws2812/dma.h +126 -0
- data/ext/ws2812/extconf.rb +24 -0
- data/ext/ws2812/gamma.h +20 -0
- data/ext/ws2812/gpio.h +108 -0
- data/ext/ws2812/lowlevel.i +48 -0
- data/ext/ws2812/lowlevel_wrap.c +3189 -0
- data/ext/ws2812/mailbox.c +311 -0
- data/ext/ws2812/mailbox.h +53 -0
- data/ext/ws2812/pwm.c +112 -0
- data/ext/ws2812/pwm.h +123 -0
- data/ext/ws2812/ws2811.c +685 -0
- data/ext/ws2812/ws2811.h +69 -0
- data/lib/ws2812.rb +20 -0
- data/lib/ws2812/basic.rb +195 -0
- data/lib/ws2812/color.rb +33 -0
- data/lib/ws2812/gamma_correction.rb +72 -0
- data/lib/ws2812/unicornhat.rb +209 -0
- metadata +114 -0
data/ext/ws2812/pwm.h
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
/*
|
2
|
+
* pwm.h
|
3
|
+
*
|
4
|
+
* Copyright (c) 2014 Jeremy Garff <jer @ jers.net>
|
5
|
+
*
|
6
|
+
* All rights reserved.
|
7
|
+
*
|
8
|
+
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
9
|
+
* provided that the following conditions are met:
|
10
|
+
*
|
11
|
+
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
12
|
+
* conditions and the following disclaimer.
|
13
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
14
|
+
* of conditions and the following disclaimer in the documentation and/or other materials
|
15
|
+
* provided with the distribution.
|
16
|
+
* 3. Neither the name of the owner nor the names of its contributors may be used to endorse
|
17
|
+
* or promote products derived from this software without specific prior written permission.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
20
|
+
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
21
|
+
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
22
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
24
|
+
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
26
|
+
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
*
|
28
|
+
*/
|
29
|
+
|
30
|
+
#ifndef __PWM_H__
|
31
|
+
#define __PWM_H__
|
32
|
+
|
33
|
+
#include <stdint.h>
|
34
|
+
|
35
|
+
/*
|
36
|
+
*
|
37
|
+
* Pin mappint of alternate pin configuration for PWM
|
38
|
+
*
|
39
|
+
* GPIO ALT PWM0 ALT PWM1
|
40
|
+
*
|
41
|
+
* 12 0
|
42
|
+
* 13 0
|
43
|
+
* 18 5
|
44
|
+
* 19 5
|
45
|
+
* 40 0
|
46
|
+
* 41 0
|
47
|
+
* 45 0
|
48
|
+
* 52 1
|
49
|
+
* 53 1
|
50
|
+
*
|
51
|
+
*/
|
52
|
+
|
53
|
+
|
54
|
+
#define RPI_PWM_CHANNELS 2
|
55
|
+
|
56
|
+
|
57
|
+
typedef struct
|
58
|
+
{
|
59
|
+
uint32_t ctl;
|
60
|
+
#define RPI_PWM_CTL_MSEN2 (1 << 15)
|
61
|
+
#define RPI_PWM_CTL_USEF2 (1 << 13)
|
62
|
+
#define RPI_PWM_CTL_POLA2 (1 << 12)
|
63
|
+
#define RPI_PWM_CTL_SBIT2 (1 << 11)
|
64
|
+
#define RPI_PWM_CTL_RPTL2 (1 << 10)
|
65
|
+
#define RPI_PWM_CTL_MODE2 (1 << 9)
|
66
|
+
#define RPI_PWM_CTL_PWEN2 (1 << 8)
|
67
|
+
#define RPI_PWM_CTL_MSEN1 (1 << 7)
|
68
|
+
#define RPI_PWM_CTL_CLRF1 (1 << 6)
|
69
|
+
#define RPI_PWM_CTL_USEF1 (1 << 5)
|
70
|
+
#define RPI_PWM_CTL_POLA1 (1 << 4)
|
71
|
+
#define RPI_PWM_CTL_SBIT1 (1 << 3)
|
72
|
+
#define RPI_PWM_CTL_RPTL1 (1 << 2)
|
73
|
+
#define RPI_PWM_CTL_MODE1 (1 << 1)
|
74
|
+
#define RPI_PWM_CTL_PWEN1 (1 << 0)
|
75
|
+
uint32_t sta;
|
76
|
+
#define RPI_PWM_STA_STA4 (1 << 12)
|
77
|
+
#define RPI_PWM_STA_STA3 (1 << 11)
|
78
|
+
#define RPI_PWM_STA_STA2 (1 << 10)
|
79
|
+
#define RPI_PWM_STA_STA1 (1 << 9)
|
80
|
+
#define RPI_PWM_STA_BERR (1 << 8)
|
81
|
+
#define RPI_PWM_STA_GAP04 (1 << 7)
|
82
|
+
#define RPI_PWM_STA_GAP03 (1 << 6)
|
83
|
+
#define RPI_PWM_STA_GAP02 (1 << 5)
|
84
|
+
#define RPI_PWM_STA_GAP01 (1 << 4)
|
85
|
+
#define RPI_PWM_STA_RERR1 (1 << 3)
|
86
|
+
#define RPI_PWM_STA_WERR1 (1 << 2)
|
87
|
+
#define RPI_PWM_STA_EMPT1 (1 << 1)
|
88
|
+
#define RPI_PWM_STA_FULL1 (1 << 0)
|
89
|
+
uint32_t dmac;
|
90
|
+
#define RPI_PWM_DMAC_ENAB (1 << 31)
|
91
|
+
#define RPI_PWM_DMAC_PANIC(val) ((val & 0xff) << 8)
|
92
|
+
#define RPI_PWM_DMAC_DREQ(val) ((val & 0xff) << 0)
|
93
|
+
uint32_t resvd_0x0c;
|
94
|
+
uint32_t rng1;
|
95
|
+
uint32_t dat1;
|
96
|
+
uint32_t fif1;
|
97
|
+
uint32_t resvd_0x1c;
|
98
|
+
uint32_t rng2;
|
99
|
+
uint32_t dat2;
|
100
|
+
} __attribute__((packed)) pwm_t;
|
101
|
+
|
102
|
+
|
103
|
+
#define PWM_OFFSET (0x0020c000)
|
104
|
+
#define PWM_PERIPH_PHYS (0x7e20c000)
|
105
|
+
|
106
|
+
|
107
|
+
typedef struct
|
108
|
+
{
|
109
|
+
int pinnum;
|
110
|
+
int altnum;
|
111
|
+
} pwm_pin_table_t;
|
112
|
+
|
113
|
+
typedef struct
|
114
|
+
{
|
115
|
+
const int count;
|
116
|
+
const pwm_pin_table_t *pins;
|
117
|
+
} pwm_pin_tables_t;
|
118
|
+
|
119
|
+
|
120
|
+
int pwm_pin_alt(int chan, int pinnum);
|
121
|
+
|
122
|
+
|
123
|
+
#endif /* __PWM_H__ */
|
data/ext/ws2812/ws2811.c
ADDED
@@ -0,0 +1,685 @@
|
|
1
|
+
/*
|
2
|
+
* ws2811.c
|
3
|
+
*
|
4
|
+
* Copyright (c) 2014 Jeremy Garff <jer @ jers.net>
|
5
|
+
*
|
6
|
+
* All rights reserved.
|
7
|
+
*
|
8
|
+
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
9
|
+
* provided that the following conditions are met:
|
10
|
+
*
|
11
|
+
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
12
|
+
* conditions and the following disclaimer.
|
13
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
14
|
+
* of conditions and the following disclaimer in the documentation and/or other materials
|
15
|
+
* provided with the distribution.
|
16
|
+
* 3. Neither the name of the owner nor the names of its contributors may be used to endorse
|
17
|
+
* or promote products derived from this software without specific prior written permission.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
20
|
+
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
21
|
+
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
22
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
24
|
+
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
26
|
+
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
*
|
28
|
+
*/
|
29
|
+
|
30
|
+
|
31
|
+
#include <stdint.h>
|
32
|
+
#include <stdio.h>
|
33
|
+
#include <stdlib.h>
|
34
|
+
#include <string.h>
|
35
|
+
#include <unistd.h>
|
36
|
+
#include <sys/types.h>
|
37
|
+
#include <sys/stat.h>
|
38
|
+
#include <fcntl.h>
|
39
|
+
#include <sys/mman.h>
|
40
|
+
#include <signal.h>
|
41
|
+
|
42
|
+
#include "board_info.h"
|
43
|
+
#include "mailbox.h"
|
44
|
+
#include "clk.h"
|
45
|
+
#include "gpio.h"
|
46
|
+
#include "dma.h"
|
47
|
+
#include "pwm.h"
|
48
|
+
|
49
|
+
#include "gamma.h"
|
50
|
+
|
51
|
+
#include "ws2811.h"
|
52
|
+
|
53
|
+
#define BUS_TO_PHYS(x) ((x)&~0xC0000000)
|
54
|
+
|
55
|
+
#define OSC_FREQ 19200000 // crystal frequency
|
56
|
+
|
57
|
+
/* 3 colors, 8 bits per byte, 3 symbols per bit + 55uS low for reset signal */
|
58
|
+
#define LED_RESET_uS 55
|
59
|
+
#define LED_BIT_COUNT(leds, freq) ((leds * 3 * 8 * 3) + ((LED_RESET_uS * \
|
60
|
+
(freq * 3)) / 1000000))
|
61
|
+
|
62
|
+
// Pad out to the nearest uint32 + 32-bits for idle low/high times the number of channels
|
63
|
+
#define PWM_BYTE_COUNT(leds, freq) (((((LED_BIT_COUNT(leds, freq) >> 3) & ~0x7) + 4) + 4) * \
|
64
|
+
RPI_PWM_CHANNELS)
|
65
|
+
|
66
|
+
#define SYMBOL_HIGH 0x6 // 1 1 0
|
67
|
+
#define SYMBOL_LOW 0x4 // 1 0 0
|
68
|
+
|
69
|
+
#define ARRAY_SIZE(stuff) (sizeof(stuff) / sizeof(stuff[0]))
|
70
|
+
|
71
|
+
|
72
|
+
typedef struct ws2811_device
|
73
|
+
{
|
74
|
+
volatile uint8_t *pwm_raw;
|
75
|
+
volatile dma_t *dma;
|
76
|
+
volatile pwm_t *pwm;
|
77
|
+
volatile dma_cb_t *dma_cb;
|
78
|
+
uint32_t dma_cb_addr;
|
79
|
+
volatile gpio_t *gpio;
|
80
|
+
volatile cm_pwm_t *cm_pwm;
|
81
|
+
int max_count;
|
82
|
+
} ws2811_device_t;
|
83
|
+
|
84
|
+
// We use the mailbox interface to request memory from the VideoCore.
|
85
|
+
// This lets us request one physically contiguous chunk, find its
|
86
|
+
// physical address, and map it 'uncached' so that writes from this
|
87
|
+
// code are immediately visible to the DMA controller. This struct
|
88
|
+
// holds data relevant to the mailbox interface.
|
89
|
+
// TODO: Should we embed this in ws2811_device_t really?
|
90
|
+
static struct {
|
91
|
+
int handle; /* From mbox_open() */
|
92
|
+
unsigned mem_ref; /* From mem_alloc() */
|
93
|
+
unsigned bus_addr; /* From mem_lock() */
|
94
|
+
unsigned size; /* Size of allocation */
|
95
|
+
uint8_t *virt_addr; /* From mapmem() */
|
96
|
+
} mbox;
|
97
|
+
|
98
|
+
/**
|
99
|
+
* Iterate through the channels and find the largest led count.
|
100
|
+
*
|
101
|
+
* @param ws2811 ws2811 instance pointer.
|
102
|
+
*
|
103
|
+
* @returns Maximum number of LEDs in all channels.
|
104
|
+
*/
|
105
|
+
static int max_channel_led_count(ws2811_t *ws2811)
|
106
|
+
{
|
107
|
+
int chan, max = 0;
|
108
|
+
|
109
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
110
|
+
{
|
111
|
+
if (ws2811->channel[chan].count > max)
|
112
|
+
{
|
113
|
+
max = ws2811->channel[chan].count;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
return max;
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Map all devices into userspace memory.
|
122
|
+
*
|
123
|
+
* @param ws2811 ws2811 instance pointer.
|
124
|
+
*
|
125
|
+
* @returns 0 on success, -1 otherwise.
|
126
|
+
*/
|
127
|
+
static int map_registers(ws2811_t *ws2811)
|
128
|
+
{
|
129
|
+
ws2811_device_t *device = ws2811->device;
|
130
|
+
uint32_t dma_addr = dmanum_to_phys(ws2811->dmanum);
|
131
|
+
uint32_t base = board_info_peripheral_base_addr();
|
132
|
+
|
133
|
+
if (!dma_addr)
|
134
|
+
{
|
135
|
+
return -1;
|
136
|
+
}
|
137
|
+
|
138
|
+
device->dma = mapmem(dma_addr, sizeof(dma_t));
|
139
|
+
if (!device->dma)
|
140
|
+
{
|
141
|
+
return -1;
|
142
|
+
}
|
143
|
+
|
144
|
+
device->pwm = mapmem(PWM_OFFSET + base, sizeof(pwm_t));
|
145
|
+
if (!device->pwm)
|
146
|
+
{
|
147
|
+
return -1;
|
148
|
+
}
|
149
|
+
|
150
|
+
device->gpio = mapmem(GPIO_OFFSET + base, sizeof(gpio_t));
|
151
|
+
if (!device->gpio)
|
152
|
+
{
|
153
|
+
return -1;
|
154
|
+
}
|
155
|
+
|
156
|
+
device->cm_pwm = mapmem(CM_PWM_OFFSET + base, sizeof(cm_pwm_t));
|
157
|
+
if (!device->cm_pwm)
|
158
|
+
{
|
159
|
+
return -1;
|
160
|
+
}
|
161
|
+
|
162
|
+
return 0;
|
163
|
+
}
|
164
|
+
|
165
|
+
/**
|
166
|
+
* Unmap all devices from virtual memory.
|
167
|
+
*
|
168
|
+
* @param ws2811 ws2811 instance pointer.
|
169
|
+
*
|
170
|
+
* @returns None
|
171
|
+
*/
|
172
|
+
static void unmap_registers(ws2811_t *ws2811)
|
173
|
+
{
|
174
|
+
ws2811_device_t *device = ws2811->device;
|
175
|
+
|
176
|
+
if (device->dma)
|
177
|
+
{
|
178
|
+
unmapmem((void *)device->dma, sizeof(dma_t));
|
179
|
+
}
|
180
|
+
|
181
|
+
if (device->pwm)
|
182
|
+
{
|
183
|
+
unmapmem((void *)device->pwm, sizeof(pwm_t));
|
184
|
+
}
|
185
|
+
|
186
|
+
if (device->cm_pwm)
|
187
|
+
{
|
188
|
+
unmapmem((void *)device->cm_pwm, sizeof(cm_pwm_t));
|
189
|
+
}
|
190
|
+
|
191
|
+
if (device->gpio)
|
192
|
+
{
|
193
|
+
unmapmem((void *)device->gpio, sizeof(gpio_t));
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Given a userspace address pointer, return the matching bus address used by DMA.
|
199
|
+
* Note: The bus address is not the same as the CPU physical address.
|
200
|
+
*
|
201
|
+
* @param addr Userspace virtual address pointer.
|
202
|
+
*
|
203
|
+
* @returns Bus address for use by DMA.
|
204
|
+
*/
|
205
|
+
static uint32_t addr_to_bus(const volatile void *virt)
|
206
|
+
{
|
207
|
+
uint32_t offset = (uint8_t *)virt - mbox.virt_addr;
|
208
|
+
|
209
|
+
return mbox.bus_addr + offset;
|
210
|
+
}
|
211
|
+
|
212
|
+
/**
|
213
|
+
* Stop the PWM controller.
|
214
|
+
*
|
215
|
+
* @param ws2811 ws2811 instance pointer.
|
216
|
+
*
|
217
|
+
* @returns None
|
218
|
+
*/
|
219
|
+
static void stop_pwm(ws2811_t *ws2811)
|
220
|
+
{
|
221
|
+
ws2811_device_t *device = ws2811->device;
|
222
|
+
volatile pwm_t *pwm = device->pwm;
|
223
|
+
volatile cm_pwm_t *cm_pwm = device->cm_pwm;
|
224
|
+
|
225
|
+
// Turn off the PWM in case already running
|
226
|
+
pwm->ctl = 0;
|
227
|
+
usleep(10);
|
228
|
+
|
229
|
+
// Kill the clock if it was already running
|
230
|
+
cm_pwm->ctl = CM_PWM_CTL_PASSWD | CM_PWM_CTL_KILL;
|
231
|
+
usleep(10);
|
232
|
+
while (cm_pwm->ctl & CM_PWM_CTL_BUSY)
|
233
|
+
;
|
234
|
+
}
|
235
|
+
|
236
|
+
/**
|
237
|
+
* Setup the PWM controller in serial mode on both channels using DMA to feed the PWM FIFO.
|
238
|
+
*
|
239
|
+
* @param ws2811 ws2811 instance pointer.
|
240
|
+
*
|
241
|
+
* @returns None
|
242
|
+
*/
|
243
|
+
static int setup_pwm(ws2811_t *ws2811)
|
244
|
+
{
|
245
|
+
ws2811_device_t *device = ws2811->device;
|
246
|
+
volatile dma_t *dma = device->dma;
|
247
|
+
volatile dma_cb_t *dma_cb = device->dma_cb;
|
248
|
+
volatile pwm_t *pwm = device->pwm;
|
249
|
+
volatile cm_pwm_t *cm_pwm = device->cm_pwm;
|
250
|
+
int maxcount = max_channel_led_count(ws2811);
|
251
|
+
uint32_t freq = ws2811->freq;
|
252
|
+
int32_t byte_count;
|
253
|
+
|
254
|
+
stop_pwm(ws2811);
|
255
|
+
|
256
|
+
// Setup the PWM Clock - Use OSC @ 19.2Mhz w/ 3 clocks/tick
|
257
|
+
cm_pwm->div = CM_PWM_DIV_PASSWD | CM_PWM_DIV_DIVI(OSC_FREQ / (3 * freq));
|
258
|
+
cm_pwm->ctl = CM_PWM_CTL_PASSWD | CM_PWM_CTL_SRC_OSC;
|
259
|
+
cm_pwm->ctl = CM_PWM_CTL_PASSWD | CM_PWM_CTL_SRC_OSC | CM_PWM_CTL_ENAB;
|
260
|
+
usleep(10);
|
261
|
+
while (!(cm_pwm->ctl & CM_PWM_CTL_BUSY))
|
262
|
+
;
|
263
|
+
|
264
|
+
// Setup the PWM, use delays as the block is rumored to lock up without them. Make
|
265
|
+
// sure to use a high enough priority to avoid any FIFO underruns, especially if
|
266
|
+
// the CPU is busy doing lots of memory accesses, or another DMA controller is
|
267
|
+
// busy. The FIFO will clock out data at a much slower rate (2.6Mhz max), so
|
268
|
+
// the odds of a DMA priority boost are extremely low.
|
269
|
+
|
270
|
+
pwm->rng1 = 32; // 32-bits per word to serialize
|
271
|
+
usleep(10);
|
272
|
+
pwm->ctl = RPI_PWM_CTL_CLRF1;
|
273
|
+
usleep(10);
|
274
|
+
pwm->dmac = RPI_PWM_DMAC_ENAB | RPI_PWM_DMAC_PANIC(7) | RPI_PWM_DMAC_DREQ(3);
|
275
|
+
usleep(10);
|
276
|
+
pwm->ctl = RPI_PWM_CTL_USEF1 | RPI_PWM_CTL_MODE1 |
|
277
|
+
RPI_PWM_CTL_USEF2 | RPI_PWM_CTL_MODE2;
|
278
|
+
usleep(10);
|
279
|
+
pwm->ctl |= RPI_PWM_CTL_PWEN1 | RPI_PWM_CTL_PWEN2;
|
280
|
+
|
281
|
+
// Initialize the DMA control block
|
282
|
+
byte_count = PWM_BYTE_COUNT(maxcount, freq);
|
283
|
+
dma_cb->ti = RPI_DMA_TI_NO_WIDE_BURSTS | // 32-bit transfers
|
284
|
+
RPI_DMA_TI_WAIT_RESP | // wait for write complete
|
285
|
+
RPI_DMA_TI_DEST_DREQ | // user peripheral flow control
|
286
|
+
RPI_DMA_TI_PERMAP(5) | // PWM peripheral
|
287
|
+
RPI_DMA_TI_SRC_INC; // Increment src addr
|
288
|
+
|
289
|
+
dma_cb->source_ad = addr_to_bus(device->pwm_raw);
|
290
|
+
|
291
|
+
dma_cb->dest_ad = (uint32_t)&((pwm_t *)PWM_PERIPH_PHYS)->fif1;
|
292
|
+
dma_cb->txfr_len = byte_count;
|
293
|
+
dma_cb->stride = 0;
|
294
|
+
dma_cb->nextconbk = 0;
|
295
|
+
|
296
|
+
dma->cs = 0;
|
297
|
+
dma->txfr_len = 0;
|
298
|
+
|
299
|
+
return 0;
|
300
|
+
}
|
301
|
+
|
302
|
+
/**
|
303
|
+
* Start the DMA feeding the PWM FIFO. This will stream the entire DMA buffer out of both
|
304
|
+
* PWM channels.
|
305
|
+
*
|
306
|
+
* @param ws2811 ws2811 instance pointer.
|
307
|
+
*
|
308
|
+
* @returns None
|
309
|
+
*/
|
310
|
+
static void dma_start(ws2811_t *ws2811)
|
311
|
+
{
|
312
|
+
ws2811_device_t *device = ws2811->device;
|
313
|
+
volatile dma_t *dma = device->dma;
|
314
|
+
uint32_t dma_cb_addr = device->dma_cb_addr;
|
315
|
+
|
316
|
+
dma->cs = RPI_DMA_CS_RESET;
|
317
|
+
usleep(10);
|
318
|
+
dma->cs = RPI_DMA_CS_INT | RPI_DMA_CS_END;
|
319
|
+
dma->conblk_ad = dma_cb_addr;
|
320
|
+
dma->debug = 7; // clear debug error flags
|
321
|
+
dma->cs = RPI_DMA_CS_WAIT_OUTSTANDING_WRITES |
|
322
|
+
RPI_DMA_CS_PANIC_PRIORITY(15) |
|
323
|
+
RPI_DMA_CS_PRIORITY(15) |
|
324
|
+
RPI_DMA_CS_ACTIVE;
|
325
|
+
}
|
326
|
+
|
327
|
+
/**
|
328
|
+
* Initialize the application selected GPIO pins for PWM operation.
|
329
|
+
*
|
330
|
+
* @param ws2811 ws2811 instance pointer.
|
331
|
+
*
|
332
|
+
* @returns 0 on success, -1 on unsupported pin
|
333
|
+
*/
|
334
|
+
static int gpio_init(ws2811_t *ws2811)
|
335
|
+
{
|
336
|
+
volatile gpio_t *gpio = ws2811->device->gpio;
|
337
|
+
int chan;
|
338
|
+
|
339
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
340
|
+
{
|
341
|
+
int pinnum = ws2811->channel[chan].gpionum;
|
342
|
+
|
343
|
+
if (pinnum)
|
344
|
+
{
|
345
|
+
int altnum = pwm_pin_alt(chan, pinnum);
|
346
|
+
|
347
|
+
if (altnum < 0)
|
348
|
+
{
|
349
|
+
return -1;
|
350
|
+
}
|
351
|
+
|
352
|
+
gpio_function_set(gpio, pinnum, altnum);
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
return 0;
|
357
|
+
}
|
358
|
+
|
359
|
+
/**
|
360
|
+
* Initialize the PWM DMA buffer with all zeros for non-inverted operation, or
|
361
|
+
* ones for inverted operation. The DMA buffer length is assumed to be a word
|
362
|
+
* multiple.
|
363
|
+
*
|
364
|
+
* @param ws2811 ws2811 instance pointer.
|
365
|
+
*
|
366
|
+
* @returns None
|
367
|
+
*/
|
368
|
+
void pwm_raw_init(ws2811_t *ws2811)
|
369
|
+
{
|
370
|
+
volatile uint32_t *pwm_raw = (uint32_t *)ws2811->device->pwm_raw;
|
371
|
+
int maxcount = max_channel_led_count(ws2811);
|
372
|
+
int wordcount = (PWM_BYTE_COUNT(maxcount, ws2811->freq) / sizeof(uint32_t)) /
|
373
|
+
RPI_PWM_CHANNELS;
|
374
|
+
int chan;
|
375
|
+
|
376
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
377
|
+
{
|
378
|
+
ws2811_channel_t *channel = &ws2811->channel[chan];
|
379
|
+
int i, wordpos = chan;
|
380
|
+
|
381
|
+
for (i = 0; i < wordcount; i++)
|
382
|
+
{
|
383
|
+
if (channel->invert)
|
384
|
+
{
|
385
|
+
pwm_raw[wordpos] = ~0L;
|
386
|
+
}
|
387
|
+
else
|
388
|
+
{
|
389
|
+
pwm_raw[wordpos] = 0x0;
|
390
|
+
}
|
391
|
+
|
392
|
+
wordpos += 2;
|
393
|
+
}
|
394
|
+
}
|
395
|
+
}
|
396
|
+
|
397
|
+
/**
|
398
|
+
* Cleanup previously allocated device memory and buffers.
|
399
|
+
*
|
400
|
+
* @param ws2811 ws2811 instance pointer.
|
401
|
+
*
|
402
|
+
* @returns None
|
403
|
+
*/
|
404
|
+
void ws2811_cleanup(ws2811_t *ws2811)
|
405
|
+
{
|
406
|
+
int chan;
|
407
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
408
|
+
{
|
409
|
+
if (ws2811->channel[chan].leds)
|
410
|
+
{
|
411
|
+
free(ws2811->channel[chan].leds);
|
412
|
+
}
|
413
|
+
ws2811->channel[chan].leds = NULL;
|
414
|
+
}
|
415
|
+
|
416
|
+
if (mbox.virt_addr != NULL)
|
417
|
+
{
|
418
|
+
unmapmem(mbox.virt_addr, mbox.size);
|
419
|
+
mem_unlock(mbox.handle, mbox.mem_ref);
|
420
|
+
mem_free(mbox.handle, mbox.mem_ref);
|
421
|
+
if (mbox.handle >= 0)
|
422
|
+
mbox_close(mbox.handle);
|
423
|
+
memset(&mbox, 0, sizeof(mbox));
|
424
|
+
}
|
425
|
+
|
426
|
+
ws2811_device_t *device = ws2811->device;
|
427
|
+
if (device) {
|
428
|
+
free(device);
|
429
|
+
}
|
430
|
+
ws2811->device = NULL;
|
431
|
+
}
|
432
|
+
|
433
|
+
|
434
|
+
/*
|
435
|
+
*
|
436
|
+
* Application API Functions
|
437
|
+
*
|
438
|
+
*/
|
439
|
+
|
440
|
+
|
441
|
+
/**
|
442
|
+
* Allocate and initialize memory, buffers, pages, PWM, DMA, and GPIO.
|
443
|
+
*
|
444
|
+
* @param ws2811 ws2811 instance pointer.
|
445
|
+
*
|
446
|
+
* @returns 0 on success, -1 otherwise.
|
447
|
+
*/
|
448
|
+
int ws2811_init(ws2811_t *ws2811)
|
449
|
+
{
|
450
|
+
ws2811_device_t *device = NULL;
|
451
|
+
int chan;
|
452
|
+
|
453
|
+
// Zero mbox; non-zero values indicate action needed on cleanup
|
454
|
+
memset(&mbox, 0, sizeof(mbox));
|
455
|
+
|
456
|
+
ws2811->device = malloc(sizeof(*ws2811->device));
|
457
|
+
if (!ws2811->device)
|
458
|
+
{
|
459
|
+
return -1;
|
460
|
+
}
|
461
|
+
device = ws2811->device;
|
462
|
+
|
463
|
+
// Determine how much physical memory we need for DMA
|
464
|
+
mbox.size = PWM_BYTE_COUNT(max_channel_led_count(ws2811), ws2811->freq) +
|
465
|
+
+ sizeof(dma_cb_t);
|
466
|
+
// Round up to page size multiple
|
467
|
+
mbox.size = (mbox.size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
468
|
+
|
469
|
+
// Use the mailbox interface to request memory from the VideoCore
|
470
|
+
// We specifiy (-1) for the handle rather than calling mbox_open()
|
471
|
+
// so multiple users can share the resource.
|
472
|
+
mbox.handle = -1; // mbox_open();
|
473
|
+
mbox.mem_ref = mem_alloc(mbox.handle, mbox.size, PAGE_SIZE,
|
474
|
+
board_info_sdram_address() == 0x40000000 ? 0xC : 0x4);
|
475
|
+
if (mbox.mem_ref < 0)
|
476
|
+
{
|
477
|
+
return -1;
|
478
|
+
}
|
479
|
+
mbox.bus_addr = mem_lock(mbox.handle, mbox.mem_ref);
|
480
|
+
if (mbox.bus_addr == ~0)
|
481
|
+
{
|
482
|
+
mem_free(mbox.handle, mbox.size);
|
483
|
+
return -1;
|
484
|
+
}
|
485
|
+
mbox.virt_addr = mapmem(BUS_TO_PHYS(mbox.bus_addr), mbox.size);
|
486
|
+
|
487
|
+
// Initialize all pointers to NULL. Any non-NULL pointers will be freed on cleanup.
|
488
|
+
device->pwm_raw = NULL;
|
489
|
+
device->dma_cb = NULL;
|
490
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
491
|
+
{
|
492
|
+
ws2811->channel[chan].leds = NULL;
|
493
|
+
}
|
494
|
+
|
495
|
+
// Allocate the LED buffers
|
496
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
|
497
|
+
{
|
498
|
+
ws2811_channel_t *channel = &ws2811->channel[chan];
|
499
|
+
|
500
|
+
channel->leds = malloc(sizeof(ws2811_led_t) * channel->count);
|
501
|
+
if (!channel->leds)
|
502
|
+
{
|
503
|
+
goto err;
|
504
|
+
}
|
505
|
+
|
506
|
+
memset(channel->leds, 0, sizeof(ws2811_led_t) * channel->count);
|
507
|
+
}
|
508
|
+
|
509
|
+
device->dma_cb = (dma_cb_t *)mbox.virt_addr;
|
510
|
+
device->pwm_raw = (uint8_t *)mbox.virt_addr + sizeof(dma_cb_t);
|
511
|
+
|
512
|
+
pwm_raw_init(ws2811);
|
513
|
+
|
514
|
+
memset((dma_cb_t *)device->dma_cb, 0, sizeof(dma_cb_t));
|
515
|
+
|
516
|
+
// Cache the DMA control block bus address
|
517
|
+
device->dma_cb_addr = addr_to_bus(device->dma_cb);
|
518
|
+
|
519
|
+
// Map the physical registers into userspace
|
520
|
+
if (map_registers(ws2811))
|
521
|
+
{
|
522
|
+
goto err;
|
523
|
+
}
|
524
|
+
|
525
|
+
// Initialize the GPIO pins
|
526
|
+
if (gpio_init(ws2811))
|
527
|
+
{
|
528
|
+
unmap_registers(ws2811);
|
529
|
+
goto err;
|
530
|
+
}
|
531
|
+
|
532
|
+
// Setup the PWM, clocks, and DMA
|
533
|
+
if (setup_pwm(ws2811))
|
534
|
+
{
|
535
|
+
unmap_registers(ws2811);
|
536
|
+
goto err;
|
537
|
+
}
|
538
|
+
|
539
|
+
return 0;
|
540
|
+
|
541
|
+
err:
|
542
|
+
ws2811_cleanup(ws2811);
|
543
|
+
|
544
|
+
return -1;
|
545
|
+
}
|
546
|
+
|
547
|
+
/**
|
548
|
+
* Shut down DMA, PWM, and cleanup memory.
|
549
|
+
*
|
550
|
+
* @param ws2811 ws2811 instance pointer.
|
551
|
+
*
|
552
|
+
* @returns None
|
553
|
+
*/
|
554
|
+
void ws2811_fini(ws2811_t *ws2811)
|
555
|
+
{
|
556
|
+
ws2811_wait(ws2811);
|
557
|
+
stop_pwm(ws2811);
|
558
|
+
|
559
|
+
unmap_registers(ws2811);
|
560
|
+
|
561
|
+
ws2811_cleanup(ws2811);
|
562
|
+
}
|
563
|
+
|
564
|
+
/**
|
565
|
+
* Contains any errors from executing ws2811_wait
|
566
|
+
*/
|
567
|
+
uint32_t ws2811_dma_error = 0;
|
568
|
+
|
569
|
+
/**
|
570
|
+
* Wait for any executing DMA operation to complete before returning.
|
571
|
+
*
|
572
|
+
* @param ws2811 ws2811 instance pointer.
|
573
|
+
*
|
574
|
+
* @returns 0 on success, -1 on DMA competion error
|
575
|
+
*/
|
576
|
+
int ws2811_wait(ws2811_t *ws2811)
|
577
|
+
{
|
578
|
+
volatile dma_t *dma = ws2811->device->dma;
|
579
|
+
|
580
|
+
while ((dma->cs & RPI_DMA_CS_ACTIVE) &&
|
581
|
+
!(dma->cs & RPI_DMA_CS_ERROR))
|
582
|
+
{
|
583
|
+
usleep(10);
|
584
|
+
}
|
585
|
+
|
586
|
+
if (dma->cs & RPI_DMA_CS_ERROR)
|
587
|
+
{
|
588
|
+
// fprintf(stderr, "DMA Error: %08x\n", dma->debug);
|
589
|
+
ws2811_dma_error = dma->debug;
|
590
|
+
return -1;
|
591
|
+
}
|
592
|
+
|
593
|
+
return 0;
|
594
|
+
}
|
595
|
+
|
596
|
+
/**
|
597
|
+
* Setting to non-zero bypasses brightness
|
598
|
+
*/
|
599
|
+
uint8_t ws2811_direct_colors = 0;
|
600
|
+
|
601
|
+
/**
|
602
|
+
* Render the PWM DMA buffer from the user supplied LED arrays and start the DMA
|
603
|
+
* controller. This will update all LEDs on both PWM channels.
|
604
|
+
*
|
605
|
+
* @param ws2811 ws2811 instance pointer.
|
606
|
+
*
|
607
|
+
* @returns None
|
608
|
+
*/
|
609
|
+
int ws2811_render(ws2811_t *ws2811)
|
610
|
+
{
|
611
|
+
volatile uint8_t *pwm_raw = ws2811->device->pwm_raw;
|
612
|
+
int bitpos = 31;
|
613
|
+
int i, j, k, l, chan;
|
614
|
+
|
615
|
+
for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) // Channel
|
616
|
+
{
|
617
|
+
ws2811_channel_t *channel = &ws2811->channel[chan];
|
618
|
+
int wordpos = chan;
|
619
|
+
int scale = (channel->brightness & 0xff) + 1;
|
620
|
+
|
621
|
+
for (i = 0; i < channel->count; i++) // Led
|
622
|
+
{
|
623
|
+
uint8_t color[] = {0, 0, 0};
|
624
|
+
|
625
|
+
color[0] = (channel->leds[i] >> 8) & 0xff; // green
|
626
|
+
color[1] = (channel->leds[i] >> 16) & 0xff; // red
|
627
|
+
color[2] = (channel->leds[i] >> 0) & 0xff; // blue
|
628
|
+
if (ws2811_direct_colors == 0) {
|
629
|
+
// apply the gamma table
|
630
|
+
color[0] = (ws281x_gamma[color[0]] * scale) >> 8; // green
|
631
|
+
color[1] = (ws281x_gamma[color[1]] * scale) >> 8; // red
|
632
|
+
color[2] = (ws281x_gamma[color[2]] * scale) >> 8; // blue
|
633
|
+
}
|
634
|
+
|
635
|
+
for (j = 0; j < ARRAY_SIZE(color); j++) // Color
|
636
|
+
{
|
637
|
+
for (k = 7; k >= 0; k--) // Bit
|
638
|
+
{
|
639
|
+
uint8_t symbol = SYMBOL_LOW;
|
640
|
+
|
641
|
+
if (color[j] & (1 << k))
|
642
|
+
{
|
643
|
+
symbol = SYMBOL_HIGH;
|
644
|
+
}
|
645
|
+
|
646
|
+
if (channel->invert)
|
647
|
+
{
|
648
|
+
symbol = ~symbol & 0x7;
|
649
|
+
}
|
650
|
+
|
651
|
+
for (l = 2; l >= 0; l--) // Symbol
|
652
|
+
{
|
653
|
+
uint32_t *wordptr = &((uint32_t *)pwm_raw)[wordpos];
|
654
|
+
|
655
|
+
*wordptr &= ~(1 << bitpos);
|
656
|
+
if (symbol & (1 << l))
|
657
|
+
{
|
658
|
+
*wordptr |= (1 << bitpos);
|
659
|
+
}
|
660
|
+
|
661
|
+
bitpos--;
|
662
|
+
if (bitpos < 0)
|
663
|
+
{
|
664
|
+
// Every other word is on the same channel
|
665
|
+
wordpos += 2;
|
666
|
+
|
667
|
+
bitpos = 31;
|
668
|
+
}
|
669
|
+
}
|
670
|
+
}
|
671
|
+
}
|
672
|
+
}
|
673
|
+
}
|
674
|
+
|
675
|
+
// Wait for any previous DMA operation to complete.
|
676
|
+
if (ws2811_wait(ws2811))
|
677
|
+
{
|
678
|
+
return -1;
|
679
|
+
}
|
680
|
+
|
681
|
+
dma_start(ws2811);
|
682
|
+
|
683
|
+
return 0;
|
684
|
+
}
|
685
|
+
|