Tiny-WS2812  1.0.0
A tiny cross-platform WS2812 LED Strip driver
ws2812_stm8s.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2022 Patrick Pedersen
3 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13 
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16  *
17  */
18 
30 #ifdef WS2812_TARGET_PLATFORM_STM8S
31 
32 #include <stm8s.h>
33 #include <stddef.h>
34 #include <stdbool.h>
35 
36 #include <ws2812_common.h>
37 #include <ws2812_stm8s.h>
38 
39 // Ensure that CPU clock runs at 16 MHz
40 #if F_CPU != 16000000UL
41 #error "F_CPU must be 16MHz!"
42 #endif
43 
44 // Helper constants for time critical stuff
45 #define TICKS_PER_LOOP 2
46 #define LOOPS_PER_US (F_CPU / TICKS_PER_LOOP / 1000000UL)
47 #define LDW_OVERHEAD 2
48 
49 // GPIO masks to quickly drive the WS2812 data line
50 volatile static uint16_t _port_odr_addr;
51 volatile static uint8_t _mask_hi;
52 volatile static uint8_t _mask_lo;
53 
54 static bool _prep = false;
55 
56 // Decrementing coutner for the delay_us function
57 volatile static uint16_t _us_loops_remaining;
58 
69 static void delay_us(uint8_t us)
70 {
71  _us_loops_remaining = (us * LOOPS_PER_US) - LDW_OVERHEAD;
72 
73  __asm
74  ldw x, __us_loops_remaining // 2 Cycles
75  0000$:
76  decw x // 1 Cycle
77  jrne 0000$ // 1-2 Cycles
78 
79  __endasm;
80 }
81 
82 // Refer to header for documentation
83 uint8_t ws2812_config(ws2812 *dev, ws2812_cfg *cfg)
84 {
85  dev->port_baseaddr = cfg->port_baseaddr;
86  dev->rst_time_us = cfg->rst_time_us;
87 
88  GPIO_TypeDef *port = (GPIO_TypeDef *)dev->port_baseaddr;
89 
90  uint8_t pin_msk = 0;
91 
92  for (uint8_t i = 0; i < cfg->n_dev; i++) {
93  pin_msk |= cfg->pins[i];
94  GPIO_Init(port, cfg->pins[i], GPIO_MODE_OUT_PP_LOW_FAST);
95  }
96 
97  dev->maskhi = pin_msk;
98  dev->masklo = ~pin_msk;
99 
100  _ws2812_get_rgbmap(&dev->rgbmap, cfg->order);
101 
102  return 0;
103 }
104 
105 // Refer to header for documentation
107 {
108  if (_prep == true)
109  return;
110 
111  _port_odr_addr = dev->port_baseaddr;
112  _mask_hi = dev->maskhi;
113  _mask_lo = dev->masklo;
114 
115  _prep = true;
116 }
117 
118 // Refer to header for documentation
120 {
121  if (dev->rst_time_us)
122  delay_us(dev->rst_time_us);
123 }
124 
151 static void ws2812_tx_bit_1() // DO NOT INLINE TO PREVENT PIPELINING
152 {
153  __asm
154  ldw x, __port_odr_addr // Load address of ODR register into X - 2 Cycles
155  ld a, (x) // Load content of ODR register into A - 1 Cycle
156  or a, __mask_hi // Set data line pin high using the pin mask - 1 Cycle
157  ld (x), a // Apply changes to ODR register - 1 Cycle
158  nop // Waste cycles until ~700ns have passed (nops are 1 cycle each)
159  nop
160  nop
161  nop
162  nop
163  nop
164  nop
165  nop
166  and a, __mask_lo // Set data line pin low using the pin mask - 1 Cycle
167  ld (x), a // Apply changes to ODR register - 1 Cycle
168  nop // Waste cycles until ~600ns have passed (nops are 1 cycle each)
169  nop
170  nop
171  nop
172  nop
173  nop
174  nop
175  __endasm;
176 }
177 
204 static void ws2812_tx_bit_0() // DO NOT INLINE TO PREVENT PIPELINING
205 {
206  __asm
207  ldw x, __port_odr_addr
208  ld a, (x)
209  or a, __mask_hi
210  ld (x), a
211  nop
212  nop
213  and a, __mask_lo
214  ld (x), a
215  nop
216  nop
217  nop
218  nop
219  nop
220  nop
221  nop
222  nop
223  nop
224  nop
225  __endasm;
226 }
227 
237 static inline void ws2812_tx_byte(uint8_t byte)
238 {
239  for (uint8_t i = 0; i < 8; i++)
240  {
241  disableInterrupts();
242  if (byte & 0b10000000) // Tests every bit in the byte
243  ws2812_tx_bit_1();
244  else
245  ws2812_tx_bit_0();
246  enableInterrupts();
247  byte <<= 1;
248  }
249 }
250 
251 // Refer to header for documentation
252 void ws2812_tx(ws2812 *dev, ws2812_rgb *leds, size_t n_leds)
253 {
254  for (size_t i = 0; i < n_leds; i++) {
255  for (uint8_t j = 0; j < sizeof(dev->rgbmap); j++) {
256  uint8_t *pxl = (uint8_t *) &(leds[i]);
257  ws2812_tx_byte(pxl[dev->rgbmap[j]]);
258  }
259  }
260 }
261 
262 // Refer to header for documentation
264 {
265  if (_prep == false)
266  return;
267 
268  _prep = false;
269  ws2812_wait_rst(dev);
270 }
271 
272 #endif
ALL PLATFORMS: Data structure to configure a WS2812 device struct.
Definition: ws2812_avr.h:64
uint8_t n_dev
Number of WS2812 device to drive.
Definition: ws2812_avr.h:78
uint8_t rst_time_us
Time required for the WS2812 device(s) to reset in us.
Definition: ws2812_avr.h:76
uint16_t port_baseaddr
Base address of the port used to drive WS2812 devices (ex. GPIOA_BASE, GPIOB_BASE,...
Definition: ws2812_stm8s.h:69
ws2812_order order
CoColor order of the WS2812 device(s) (ex. rgb, grb, bgr...)
Definition: ws2812_avr.h:77
uint8_t * pins
Array of pins used to program WS2812 devices (Must share the same PORT! (ex. PB0, PB1,...
Definition: ws2812_avr.h:69
Data structure to hold RGB color values.
Definition: ws2812_common.h:67
ALL PLATFORMS: WS2812 device struct to drive one or more WS2812 devices.
Definition: ws2812_avr.h:107
uint16_t port_baseaddr
Base address of the port used to drive WS2812 devices (ex. GPIOA_BASE, GPIOB_BASE,...
Definition: ws2812_stm8s.h:102
uint8_t rgbmap[3]
RGB map to map/convert RGB values to another color order.
Definition: ws2812_avr.h:112
uint8_t rst_time_us
Time required for WS2812 to reset in us.
Definition: ws2812_avr.h:109
uint8_t masklo
PORT masks to toggle the data pins low.
Definition: ws2812_avr.h:111
uint8_t maskhi
PORT masks to toggle the data pins high.
Definition: ws2812_avr.h:110
void delay_us(uint8_t us)
Halts the program for a given ammount of microseconds.
Definition: ws2812_avr.c:116
void ws2812_tx_byte(ws2812 *dev, uint8_t byte)
Transmits a byte of data to the WS2812 device.
Definition: ws2812_avr.c:192
void _ws2812_get_rgbmap(uint8_t(*rgbmap)[3], ws2812_order order)
Initializes a rgb map for a given color order.
Definition: ws2812_common.c:43
Definitions required by all platform specific headers.
void ws2812_close_tx(ws2812 *dev)
Closes a WS2812 transmission.
Definition: ws2812_stm8s.c:263
void ws2812_wait_rst(ws2812 *dev)
Waits for the WS2812 device to reset.
Definition: ws2812_stm8s.c:119
uint8_t ws2812_config(ws2812 *dev, ws2812_cfg *cfg)
Configures a WS2812 device struct.
Definition: ws2812_stm8s.c:83
void ws2812_prep_tx(ws2812 *dev)
Prepares the host device for data transmission.
Definition: ws2812_stm8s.c:106
void ws2812_tx(ws2812 *dev, ws2812_rgb *leds, size_t n_leds)
Transmits RGB values to the provided WS2812 device.
Definition: ws2812_stm8s.c:252
#define LOOPS_PER_US
Number for loops required for 1 us to pass.
Definition: ws2812_stm8s.c:46
#define LDW_OVERHEAD
Number of CPU ticks for the LDW instruction.
Definition: ws2812_stm8s.c:47
Provides STM8S platform specific definitions.