├── .gitattributes ├── .github └── FUNDING.yml ├── Datasheets ├── SK6812.pdf ├── WS2812.pdf └── WS2812B.pdf ├── Instructions ├── EN_Instruction.pdf └── RU_Instruction.pdf ├── LICENSE ├── Library ├── ARGB.c ├── ARGB.h ├── example.c └── libs.h ├── README.md └── Resources ├── ARGB_Banner.png ├── ARGB_Scheme.png └── DMA_Gen_Order.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | # github: 3 | custom: ['https://crazygeeks.ru/donate/', 'https://paypal.me/yasnosos', 'https://www.donationalerts.com/r/yasnosos'] 4 | 5 | -------------------------------------------------------------------------------- /Datasheets/SK6812.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazy-Geeks/STM32-ARGB-DMA/a47062d56d2855750382ebedf017d0f58135fedd/Datasheets/SK6812.pdf -------------------------------------------------------------------------------- /Datasheets/WS2812.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazy-Geeks/STM32-ARGB-DMA/a47062d56d2855750382ebedf017d0f58135fedd/Datasheets/WS2812.pdf -------------------------------------------------------------------------------- /Datasheets/WS2812B.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazy-Geeks/STM32-ARGB-DMA/a47062d56d2855750382ebedf017d0f58135fedd/Datasheets/WS2812B.pdf -------------------------------------------------------------------------------- /Instructions/EN_Instruction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazy-Geeks/STM32-ARGB-DMA/a47062d56d2855750382ebedf017d0f58135fedd/Instructions/EN_Instruction.pdf -------------------------------------------------------------------------------- /Instructions/RU_Instruction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazy-Geeks/STM32-ARGB-DMA/a47062d56d2855750382ebedf017d0f58135fedd/Instructions/RU_Instruction.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dmitriy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Library/ARGB.c: -------------------------------------------------------------------------------- 1 | /** 2 | ******************************************* 3 | * @file ARGB.c 4 | * @author Dmitriy Semenov / Crazy_Geeks 5 | * @link https://crazygeeks.ru 6 | * @version 1.33 7 | * @date 17-May-2022 8 | * @brief Source file for ARGB Driver (Addressable RGB) 9 | ******************************************* 10 | * 11 | * @note Repo: https://github.com/Crazy-Geeks/STM32-ARGB-DMA 12 | * @note RU article: https://crazygeeks.ru/stm32-argb-lib 13 | */ 14 | 15 | /* WS2811 Timings 16 | * Tolerance: +/- 150ns <-> +/- 0.15us 17 | * RES: >50us 18 | * 19 | * Slow mode: 20 | * Period: 2.5us <-> 400 KHz 21 | * T0H: 0.5us 22 | * T1H: 1.2us 23 | * T0L: 2.0us 24 | * T1L: 1.3us 25 | * 26 | * Fast mode: 27 | * Period: 1.25us <-> 800 KHz 28 | * T0H: 0.25us - 20% 29 | * T1H: 0.6us - 48% 30 | * T0L: 1.0us 31 | * T1H: 0.65us 32 | * 33 | */ 34 | 35 | /* WS2811 Timings 36 | * Tolerance: +/- 150ns <-> +/- 0.15us 37 | * RES: >50us 38 | 39 | * Period: 1.25us <-> 800 KHz 40 | * T0H: 0.35us - 20% 41 | * T1H: 0.7us - 48% 42 | * T0L: 0.8us 43 | * T1H: 0.6us 44 | * 45 | */ 46 | 47 | #include "ARGB.h" // include header file 48 | #include "math.h" 49 | 50 | /** 51 | * @addtogroup ARGB_Driver 52 | * @{ 53 | */ 54 | 55 | /** 56 | * @addtogroup Private_entities 57 | * @brief Private methods and variables 58 | * @{ 59 | */ 60 | 61 | /// Timer handler 62 | #if TIM_NUM == 1 63 | #define TIM_HANDLE htim1 64 | #elif TIM_NUM == 2 65 | #define TIM_HANDLE htim2 66 | #elif TIM_NUM == 3 67 | #define TIM_HANDLE htim3 68 | #elif TIM_NUM == 4 69 | #define TIM_HANDLE htim4 70 | #elif TIM_NUM == 5 71 | #define TIM_HANDLE htim5 72 | #elif TIM_NUM == 8 73 | #define TIM_HANDLE htim8 74 | #else 75 | #error Wrong timer! Fix it in ARGB.h string 41 76 | #warning If you shure, set TIM_HANDLE and APB ring by yourself 77 | #endif 78 | 79 | /// Timer's RCC Bus 80 | #if TIM_NUM == 1 || (TIM_NUM >= 8 && TIM_NUM <= 11) 81 | #define APB1 82 | #else 83 | #define APB2 84 | #endif 85 | 86 | /// DMA Size 87 | #if defined(DMA_SIZE_BYTE) 88 | typedef u8_t dma_siz; 89 | #elif defined(DMA_SIZE_HWORD) 90 | typedef u16_t dma_siz; 91 | #elif defined(DMA_SIZE_WORD) 92 | typedef u32_t dma_siz; 93 | #endif 94 | 95 | extern TIM_HandleTypeDef (TIM_HANDLE); ///< Timer handler 96 | extern DMA_HandleTypeDef (DMA_HANDLE); ///< DMA handler 97 | 98 | volatile u8_t PWM_HI; ///< PWM Code HI Log.1 period 99 | volatile u8_t PWM_LO; ///< PWM Code LO Log.1 period 100 | 101 | #ifdef SK6812 102 | #define NUM_BYTES (4 * NUM_PIXELS) ///< Strip size in bytes 103 | #define PWM_BUF_LEN (4 * 8 * 2) ///< Pack len * 8 bit * 2 LEDs 104 | #else 105 | #define NUM_BYTES (3 * NUM_PIXELS) ///< Strip size in bytes 106 | #define PWM_BUF_LEN (3 * 8 * 2) ///< Pack len * 8 bit * 2 LEDs 107 | #endif 108 | 109 | /// Static LED buffer 110 | volatile u8_t RGB_BUF[NUM_BYTES] = {0,}; 111 | 112 | /// Timer PWM value buffer 113 | volatile dma_siz PWM_BUF[PWM_BUF_LEN] = {0,}; 114 | /// PWM buffer iterator 115 | volatile u16_t BUF_COUNTER = 0; 116 | 117 | volatile u8_t ARGB_BR = 255; ///< LED Global brightness 118 | volatile ARGB_STATE ARGB_LOC_ST; ///< Buffer send status 119 | 120 | static inline u8_t scale8(u8_t x, u8_t scale); // Gamma correction 121 | static void HSV2RGB(u8_t hue, u8_t sat, u8_t val, u8_t *_r, u8_t *_g, u8_t *_b); 122 | // Callbacks 123 | static void ARGB_TIM_DMADelayPulseCplt(DMA_HandleTypeDef *hdma); 124 | static void ARGB_TIM_DMADelayPulseHalfCplt(DMA_HandleTypeDef *hdma); 125 | /// @} //Private 126 | 127 | /** 128 | * @brief Init timer & prescalers 129 | * @param none 130 | */ 131 | void ARGB_Init(void) { 132 | /* Auto-calculation! */ 133 | u32_t APBfq; // Clock freq 134 | #ifdef APB1 135 | APBfq = HAL_RCC_GetPCLK1Freq(); 136 | APBfq *= (RCC->CFGR & RCC_CFGR_PPRE1) == 0 ? 1 : 2; 137 | #endif 138 | #ifdef APB2 139 | APBfq = HAL_RCC_GetPCLK2Freq(); 140 | APBfq *= (RCC->CFGR & RCC_CFGR_PPRE2) == 0 ? 1 : 2; 141 | #endif 142 | #ifdef WS2811S 143 | APBfq /= (uint32_t) (400 * 1000); // 400 KHz - 2.5us 144 | #else 145 | APBfq /= (uint32_t) (800 * 1000); // 800 KHz - 1.25us 146 | #endif 147 | TIM_HANDLE.Instance->PSC = 0; // dummy hardcode now 148 | TIM_HANDLE.Instance->ARR = (uint16_t) (APBfq - 1); // set timer prescaler 149 | TIM_HANDLE.Instance->EGR = 1; // update timer registers 150 | #if defined(WS2811F) || defined(WS2811S) 151 | PWM_HI = (u8_t) (APBfq * 0.48) - 1; // Log.1 - 48% - 0.60us/1.2us 152 | PWM_LO = (u8_t) (APBfq * 0.20) - 1; // Log.0 - 20% - 0.25us/0.5us 153 | #endif 154 | #ifdef WS2812 155 | PWM_HI = (u8_t) (APBfq * 0.56) - 1; // Log.1 - 56% - 0.70us 156 | PWM_LO = (u8_t) (APBfq * 0.28) - 1; // Log.0 - 28% - 0.35us 157 | #endif 158 | #ifdef SK6812 159 | PWM_HI = (u8_t) (APBfq * 0.48) - 1; // Log.1 - 48% - 0.60us 160 | PWM_LO = (u8_t) (APBfq * 0.24) - 1; // Log.0 - 24% - 0.30us 161 | #endif 162 | 163 | //#if INV_SIGNAL 164 | // TIM_POINTER->CCER |= TIM_CCER_CC2P; // set inv ch bit 165 | //#else 166 | // TIM_POINTER->CCER &= ~TIM_CCER_CC2P; 167 | //#endif 168 | ARGB_LOC_ST = ARGB_READY; // Set Ready Flag 169 | TIM_CCxChannelCmd(TIM_HANDLE.Instance, TIM_CH, TIM_CCx_ENABLE); // Enable GPIO to IDLE state 170 | HAL_Delay(1); // Make some delay 171 | } 172 | 173 | /** 174 | * @brief Fill ALL LEDs with (0,0,0) 175 | * @param none 176 | * @note Update strip after that 177 | */ 178 | void ARGB_Clear(void) { 179 | ARGB_FillRGB(0, 0, 0); 180 | #ifdef SK6812 181 | ARGB_FillWhite(0); 182 | #endif 183 | } 184 | 185 | /** 186 | * @brief Set GLOBAL LED brightness 187 | * @param[in] br Brightness [0..255] 188 | */ 189 | void ARGB_SetBrightness(u8_t br) { 190 | ARGB_BR = br; 191 | } 192 | 193 | /** 194 | * @brief Set LED with RGB color by index 195 | * @param[in] i LED position 196 | * @param[in] r Red component [0..255] 197 | * @param[in] g Green component [0..255] 198 | * @param[in] b Blue component [0..255] 199 | */ 200 | void ARGB_SetRGB(u16_t i, u8_t r, u8_t g, u8_t b) { 201 | // overflow protection 202 | if (i >= NUM_PIXELS) { 203 | u16_t _i = i / NUM_PIXELS; 204 | i -= _i * NUM_PIXELS; 205 | } 206 | // set brightness 207 | r /= 256 / ((u16_t) ARGB_BR + 1); 208 | g /= 256 / ((u16_t) ARGB_BR + 1); 209 | b /= 256 / ((u16_t) ARGB_BR + 1); 210 | #if USE_GAMMA_CORRECTION 211 | g = scale8(g, 0xB0); 212 | b = scale8(b, 0xF0); 213 | #endif 214 | // Subpixel chain order 215 | #if defined(SK6812) || defined(WS2811F) || defined(WS2811S) 216 | const u8_t subp1 = r; 217 | const u8_t subp2 = g; 218 | const u8_t subp3 = b; 219 | #else 220 | const u8_t subp1 = g; 221 | const u8_t subp2 = r; 222 | const u8_t subp3 = b; 223 | #endif 224 | // RGB or RGBW 225 | #ifdef SK6812 226 | RGB_BUF[4 * i] = subp1; // subpixel 1 227 | RGB_BUF[4 * i + 1] = subp2; // subpixel 2 228 | RGB_BUF[4 * i + 2] = subp3; // subpixel 3 229 | #else 230 | RGB_BUF[3 * i] = subp1; // subpixel 1 231 | RGB_BUF[3 * i + 1] = subp2; // subpixel 2 232 | RGB_BUF[3 * i + 2] = subp3; // subpixel 3 233 | #endif 234 | } 235 | 236 | /** 237 | * @brief Set LED with HSV color by index 238 | * @param[in] i LED position 239 | * @param[in] hue HUE (color) [0..255] 240 | * @param[in] sat Saturation [0..255] 241 | * @param[in] val Value (brightness) [0..255] 242 | */ 243 | void ARGB_SetHSV(u16_t i, u8_t hue, u8_t sat, u8_t val) { 244 | uint8_t _r, _g, _b; // init buffer color 245 | HSV2RGB(hue, sat, val, &_r, &_g, &_b); // get RGB color 246 | ARGB_SetRGB(i, _r, _g, _b); // set color 247 | } 248 | 249 | /** 250 | * @brief Set White component in strip by index 251 | * @param[in] i LED position 252 | * @param[in] w White component [0..255] 253 | */ 254 | void ARGB_SetWhite(u16_t i, u8_t w) { 255 | #ifdef RGB 256 | return; 257 | #endif 258 | w /= 256 / ((u16_t) ARGB_BR + 1); // set brightness 259 | RGB_BUF[4 * i + 3] = w; // set white part 260 | } 261 | 262 | /** 263 | * @brief Fill ALL LEDs with RGB color 264 | * @param[in] r Red component [0..255] 265 | * @param[in] g Green component [0..255] 266 | * @param[in] b Blue component [0..255] 267 | */ 268 | void ARGB_FillRGB(u8_t r, u8_t g, u8_t b) { 269 | for (volatile u16_t i = 0; i < NUM_PIXELS; i++) 270 | ARGB_SetRGB(i, r, g, b); 271 | } 272 | 273 | /** 274 | * @brief Fill ALL LEDs with HSV color 275 | * @param[in] hue HUE (color) [0..255] 276 | * @param[in] sat Saturation [0..255] 277 | * @param[in] val Value (brightness) [0..255] 278 | */ 279 | void ARGB_FillHSV(u8_t hue, u8_t sat, u8_t val) { 280 | uint8_t _r, _g, _b; // init buffer color 281 | HSV2RGB(hue, sat, val, &_r, &_g, &_b); // get color once (!) 282 | ARGB_FillRGB(_r, _g, _b); // set color 283 | } 284 | 285 | /** 286 | * @brief Set ALL White components in strip 287 | * @param[in] w White component [0..255] 288 | */ 289 | void ARGB_FillWhite(u8_t w) { 290 | for (volatile u16_t i = 0; i < NUM_PIXELS; i++) 291 | ARGB_SetWhite(i, w); 292 | } 293 | 294 | /** 295 | * @brief Get current DMA status 296 | * @param none 297 | * @return #ARGB_STATE enum 298 | */ 299 | ARGB_STATE ARGB_Ready(void) { 300 | return ARGB_LOC_ST; 301 | } 302 | 303 | /** 304 | * @brief Update strip 305 | * @param none 306 | * @return #ARGB_STATE enum 307 | */ 308 | ARGB_STATE ARGB_Show(void) { 309 | ARGB_LOC_ST = ARGB_BUSY; 310 | if (BUF_COUNTER != 0 || DMA_HANDLE.State != HAL_DMA_STATE_READY) { 311 | return ARGB_BUSY; 312 | } else { 313 | for (volatile u8_t i = 0; i < 8; i++) { 314 | // set first transfer from first values 315 | PWM_BUF[i] = (((RGB_BUF[0] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 316 | PWM_BUF[i + 8] = (((RGB_BUF[1] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 317 | PWM_BUF[i + 16] = (((RGB_BUF[2] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 318 | PWM_BUF[i + 24] = (((RGB_BUF[3] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 319 | PWM_BUF[i + 32] = (((RGB_BUF[4] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 320 | PWM_BUF[i + 40] = (((RGB_BUF[5] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 321 | #ifdef SK6812 322 | PWM_BUF[i + 48] = (((RGB_BUF[6] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 323 | PWM_BUF[i + 56] = (((RGB_BUF[7] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 324 | #endif 325 | } 326 | HAL_StatusTypeDef DMA_Send_Stat = HAL_ERROR; 327 | while (DMA_Send_Stat != HAL_OK) { 328 | if (TIM_CHANNEL_STATE_GET(&TIM_HANDLE, TIM_CH) == HAL_TIM_CHANNEL_STATE_BUSY) { 329 | DMA_Send_Stat = HAL_BUSY; 330 | continue; 331 | } else if (TIM_CHANNEL_STATE_GET(&TIM_HANDLE, TIM_CH) == HAL_TIM_CHANNEL_STATE_READY) { 332 | TIM_CHANNEL_STATE_SET(&TIM_HANDLE, TIM_CH, HAL_TIM_CHANNEL_STATE_BUSY); 333 | } else { 334 | DMA_Send_Stat = HAL_ERROR; 335 | continue; 336 | } 337 | #if TIM_CH == TIM_CHANNEL_1 338 | #define ARGB_TIM_DMA_ID TIM_DMA_ID_CC1 339 | #define ARGB_TIM_DMA_CC TIM_DMA_CC1 340 | #define ARGB_TIM_CCR CCR1 341 | #elif TIM_CH == TIM_CHANNEL_2 342 | #define ARGB_TIM_DMA_ID TIM_DMA_ID_CC2 343 | #define ARGB_TIM_DMA_CC TIM_DMA_CC2 344 | #define ARGB_TIM_CCR CCR2 345 | #elif TIM_CH == TIM_CHANNEL_3 346 | #define ARGB_TIM_DMA_ID TIM_DMA_ID_CC3 347 | #define ARGB_TIM_DMA_CC TIM_DMA_CC3 348 | #define ARGB_TIM_CCR CCR3 349 | #elif TIM_CH == TIM_CHANNEL_4 350 | #define ARGB_TIM_DMA_ID TIM_DMA_ID_CC4 351 | #define ARGB_TIM_DMA_CC TIM_DMA_CC4 352 | #define ARGB_TIM_CCR CCR4 353 | #endif 354 | TIM_HANDLE.hdma[ARGB_TIM_DMA_ID]->XferCpltCallback = ARGB_TIM_DMADelayPulseCplt; 355 | TIM_HANDLE.hdma[ARGB_TIM_DMA_ID]->XferHalfCpltCallback = ARGB_TIM_DMADelayPulseHalfCplt; 356 | TIM_HANDLE.hdma[ARGB_TIM_DMA_ID]->XferErrorCallback = TIM_DMAError; 357 | if (HAL_DMA_Start_IT(TIM_HANDLE.hdma[ARGB_TIM_DMA_ID], (u32_t) PWM_BUF, 358 | (u32_t) &TIM_HANDLE.Instance->ARGB_TIM_CCR, 359 | (u16_t) PWM_BUF_LEN) != HAL_OK) { 360 | DMA_Send_Stat = HAL_ERROR; 361 | continue; 362 | } 363 | __HAL_TIM_ENABLE_DMA(&TIM_HANDLE, ARGB_TIM_DMA_CC); 364 | if (IS_TIM_BREAK_INSTANCE(TIM_HANDLE.Instance) != RESET) 365 | __HAL_TIM_MOE_ENABLE(&TIM_HANDLE); 366 | if (IS_TIM_SLAVE_INSTANCE(TIM_HANDLE.Instance)) { 367 | u32_t tmpsmcr = TIM_HANDLE.Instance->SMCR & TIM_SMCR_SMS; 368 | if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr)) 369 | __HAL_TIM_ENABLE(&TIM_HANDLE); 370 | } else 371 | __HAL_TIM_ENABLE(&TIM_HANDLE); 372 | DMA_Send_Stat = HAL_OK; 373 | } 374 | BUF_COUNTER = 2; 375 | return ARGB_OK; 376 | } 377 | } 378 | 379 | /** 380 | * @addtogroup Private_entities 381 | * @{ */ 382 | 383 | /** 384 | * @brief Private method for gamma correction 385 | * @param[in] x Param to scale 386 | * @param[in] scale Scale coefficient 387 | * @return Scaled value 388 | */ 389 | static inline u8_t scale8(u8_t x, u8_t scale) { 390 | return ((uint16_t) x * scale) >> 8; 391 | } 392 | 393 | /** 394 | * @brief Convert color in HSV to RGB 395 | * @param[in] hue HUE (color) [0..255] 396 | * @param[in] sat Saturation [0..255] 397 | * @param[in] val Value (brightness) [0..255] 398 | * @param[out] _r Pointer to RED component value 399 | * @param[out] _g Pointer to GREEN component value 400 | * @param[out] _b Pointer to BLUE component value 401 | */ 402 | static void HSV2RGB(u8_t hue, u8_t sat, u8_t val, u8_t *_r, u8_t *_g, u8_t *_b) { 403 | if (sat == 0) { // if white color 404 | *_r = *_g = *_b = val; 405 | return; 406 | } 407 | // Float is smoother but check for FPU (Floating point unit) in your MCU 408 | // Otherwise it will take longer time in the code 409 | // FPU is in: F3/L3 and greater 410 | // Src: https://github.com/Inseckto/HSV-to-RGB 411 | float h = (float)hue / 255; 412 | float s = (float)sat / 255; 413 | float v = (float)val / 255; 414 | 415 | int i = (int)floorf(h * 6); 416 | float f = h * 6 - (float)i; 417 | u8_t p = (u8_t)(v * (1 - s) * 255.0); 418 | u8_t q = (u8_t)(v * (1 - f * s) * 255.0); 419 | u8_t t = (u8_t)(v * (1 - (1 - f) * s)*255.0); 420 | 421 | switch (i % 6) { 422 | // Src: https://stackoverflow.com/questions/3018313 423 | // uint8_t reg = hue / 43; 424 | // uint8_t rem = (hue - (reg * 43)) * 6; 425 | // uint8_t p = (val * (255 - sat)) >> 8; 426 | // uint8_t q = (val * (255 - ((sat * rem) >> 8))) >> 8; 427 | // uint8_t t = (val * (255 - ((sat * (255 - rem)) >> 8))) >> 8; 428 | // switch (reg) { 429 | case 0: *_r = val, *_g = t, *_b = p; break; 430 | case 1: *_r = q, *_g = val, *_b = p; break; 431 | case 2: *_r = p, *_g = val, *_b = t; break; 432 | case 3: *_r = p, *_g = q, *_b = val; break; 433 | case 4: *_r = t, *_g = p, *_b = val; break; 434 | default: *_r = val, *_g = p, *_b = q; break; 435 | } 436 | } 437 | 438 | /** 439 | * @brief TIM DMA Delay Pulse complete callback. 440 | * @param hdma pointer to DMA handle. 441 | * @retval None 442 | */ 443 | static void ARGB_TIM_DMADelayPulseCplt(DMA_HandleTypeDef *hdma) { 444 | TIM_HandleTypeDef *htim = (TIM_HandleTypeDef *) ((DMA_HandleTypeDef *) hdma)->Parent; 445 | // if wrong handlers 446 | if (hdma != &DMA_HANDLE || htim != &TIM_HANDLE) return; 447 | if (BUF_COUNTER == 0) return; // if no data to transmit - return 448 | if (hdma == htim->hdma[TIM_DMA_ID_CC1]) { 449 | htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1; 450 | if (hdma->Init.Mode == DMA_NORMAL) { 451 | TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_READY); 452 | } 453 | } else if (hdma == htim->hdma[TIM_DMA_ID_CC2]) { 454 | htim->Channel = HAL_TIM_ACTIVE_CHANNEL_2; 455 | if (hdma->Init.Mode == DMA_NORMAL) { 456 | TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_READY); 457 | } 458 | } else if (hdma == htim->hdma[TIM_DMA_ID_CC3]) { 459 | htim->Channel = HAL_TIM_ACTIVE_CHANNEL_3; 460 | if (hdma->Init.Mode == DMA_NORMAL) { 461 | TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_3, HAL_TIM_CHANNEL_STATE_READY); 462 | } 463 | } else if (hdma == htim->hdma[TIM_DMA_ID_CC4]) { 464 | htim->Channel = HAL_TIM_ACTIVE_CHANNEL_4; 465 | if (hdma->Init.Mode == DMA_NORMAL) { 466 | TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_4, HAL_TIM_CHANNEL_STATE_READY); 467 | } 468 | } else { 469 | /* nothing to do */ 470 | } 471 | // if data transfer 472 | if (BUF_COUNTER < NUM_PIXELS) { 473 | // fill second part of buffer 474 | for (volatile u8_t i = 0; i < 8; i++) { 475 | #ifdef SK6812 476 | PWM_BUF[i + 32] = (((RGB_BUF[4 * BUF_COUNTER] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 477 | PWM_BUF[i + 40] = (((RGB_BUF[4 * BUF_COUNTER + 1] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 478 | PWM_BUF[i + 48] = (((RGB_BUF[4 * BUF_COUNTER + 2] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 479 | PWM_BUF[i + 56] = (((RGB_BUF[4 * BUF_COUNTER + 3] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 480 | #else 481 | PWM_BUF[i + 24] = (((RGB_BUF[3 * BUF_COUNTER] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 482 | PWM_BUF[i + 32] = (((RGB_BUF[3 * BUF_COUNTER + 1] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 483 | PWM_BUF[i + 40] = (((RGB_BUF[3 * BUF_COUNTER + 2] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 484 | #endif 485 | } 486 | BUF_COUNTER++; 487 | } else if (BUF_COUNTER < NUM_PIXELS + 2) { // if RET transfer 488 | memset((dma_siz *) &PWM_BUF[PWM_BUF_LEN / 2], 0, (PWM_BUF_LEN / 2)*sizeof(dma_siz)); // second part 489 | BUF_COUNTER++; 490 | } else { // if END of transfer 491 | BUF_COUNTER = 0; 492 | // STOP DMA: 493 | #if TIM_CH == TIM_CHANNEL_1 494 | __HAL_TIM_DISABLE_DMA(htim, TIM_DMA_CC1); 495 | (void) HAL_DMA_Abort_IT(htim->hdma[TIM_DMA_ID_CC1]); 496 | #endif 497 | #if TIM_CH == TIM_CHANNEL_2 498 | __HAL_TIM_DISABLE_DMA(htim, TIM_DMA_CC2); 499 | (void) HAL_DMA_Abort_IT(htim->hdma[TIM_DMA_ID_CC2]); 500 | #endif 501 | #if TIM_CH == TIM_CHANNEL_3 502 | __HAL_TIM_DISABLE_DMA(htim, TIM_DMA_CC3); 503 | (void) HAL_DMA_Abort_IT(htim->hdma[TIM_DMA_ID_CC3]); 504 | #endif 505 | #if TIM_CH == TIM_CHANNEL_4 506 | __HAL_TIM_DISABLE_DMA(htim, TIM_DMA_CC4); 507 | (void) HAL_DMA_Abort_IT(htim->hdma[TIM_DMA_ID_CC4]); 508 | #endif 509 | if (IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET) { 510 | /* Disable the Main Output */ 511 | __HAL_TIM_MOE_DISABLE(htim); 512 | } 513 | /* Disable the Peripheral */ 514 | __HAL_TIM_DISABLE(htim); 515 | /* Set the TIM channel state */ 516 | TIM_CHANNEL_STATE_SET(htim, TIM_CH, HAL_TIM_CHANNEL_STATE_READY); 517 | ARGB_LOC_ST = ARGB_READY; 518 | } 519 | htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED; 520 | } 521 | 522 | /** 523 | * @brief TIM DMA Delay Pulse half complete callback. 524 | * @param hdma pointer to DMA handle. 525 | * @retval None 526 | */ 527 | static void ARGB_TIM_DMADelayPulseHalfCplt(DMA_HandleTypeDef *hdma) { 528 | TIM_HandleTypeDef *htim = (TIM_HandleTypeDef *) ((DMA_HandleTypeDef *) hdma)->Parent; 529 | // if wrong handlers 530 | if (hdma != &DMA_HANDLE || htim != &TIM_HANDLE) return; 531 | if (BUF_COUNTER == 0) return; // if no data to transmit - return 532 | // if data transfer 533 | if (BUF_COUNTER < NUM_PIXELS) { 534 | // fill first part of buffer 535 | for (volatile u8_t i = 0; i < 8; i++) { 536 | #ifdef SK6812 537 | PWM_BUF[i] = (((RGB_BUF[4 * BUF_COUNTER] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 538 | PWM_BUF[i + 8] = (((RGB_BUF[4 * BUF_COUNTER + 1] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 539 | PWM_BUF[i + 16] = (((RGB_BUF[4 * BUF_COUNTER + 2] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 540 | PWM_BUF[i + 24] = (((RGB_BUF[4 * BUF_COUNTER + 3] << i) & 0x80) > 0)? PWM_HI : PWM_LO; 541 | #else 542 | PWM_BUF[i] = (((RGB_BUF[3 * BUF_COUNTER] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 543 | PWM_BUF[i + 8] = (((RGB_BUF[3 * BUF_COUNTER + 1] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 544 | PWM_BUF[i + 16] = (((RGB_BUF[3 * BUF_COUNTER + 2] << i) & 0x80) > 0) ? PWM_HI : PWM_LO; 545 | #endif 546 | } 547 | BUF_COUNTER++; 548 | } else if (BUF_COUNTER < NUM_PIXELS + 2) { // if RET transfer 549 | memset((dma_siz *) &PWM_BUF[0], 0, (PWM_BUF_LEN / 2)*sizeof(dma_siz)); // first part 550 | BUF_COUNTER++; 551 | } 552 | } 553 | 554 | /** @} */ // Private 555 | 556 | /** @} */ // Driver 557 | 558 | // Check strip type 559 | #if !(defined(SK6812) || defined(WS2811F) || defined(WS2811S) || defined(WS2812)) 560 | #error INCORRECT LED TYPE 561 | #warning Set it from list in ARGB.h string 29 562 | #endif 563 | 564 | // Check channel 565 | #if !(TIM_CH == TIM_CHANNEL_1 || TIM_CH == TIM_CHANNEL_2 || TIM_CH == TIM_CHANNEL_3 || TIM_CH == TIM_CHANNEL_4) 566 | #error Wrong channel! Fix it in ARGB.h string 40 567 | #warning If you shure, search and set TIM_CHANNEL by yourself 568 | #endif 569 | 570 | // Check DMA Size 571 | #if !(defined(DMA_SIZE_BYTE) | defined(DMA_SIZE_HWORD) | defined(DMA_SIZE_WORD)) 572 | #error Wrong DMA Size! Fix it in ARGB.h string 42 573 | #endif 574 | -------------------------------------------------------------------------------- /Library/ARGB.h: -------------------------------------------------------------------------------- 1 | /** 2 | ******************************************* 3 | * @file ARGB.h 4 | * @author Dmitriy Semenov / Crazy_Geeks 5 | * @link https://crazygeeks.ru 6 | * @version 1.33 7 | * @date 17-May-2022 8 | * @brief Header file for ARGB Driver (Addressable RGB) 9 | ******************************************* 10 | * 11 | * @note Repo: https://github.com/Crazy-Geeks/STM32-ARGB-DMA 12 | * @note RU article: https://crazygeeks.ru/stm32-argb-lib 13 | */ 14 | 15 | #ifndef ARGB_H_ 16 | #define ARGB_H_ 17 | 18 | #include "libs.h" 19 | 20 | /** 21 | * @addtogroup ARGB_Driver 22 | * @brief Addressable RGB LED Driver 23 | * @{ 24 | * @addtogroup User_settings 25 | * @brief LED & Timer's settings 26 | * @{ 27 | */ 28 | 29 | #define WS2812 ///< Family: {WS2811S, WS2811F, WS2812, SK6812} 30 | // WS2811S — RGB, 400kHz; 31 | // WS2811F — RGB, 800kHz; 32 | // WS2812 — GRB, 800kHz; 33 | // SK6812 — RGBW, 800kHz 34 | 35 | #define NUM_PIXELS 5 ///< Pixel quantity 36 | 37 | #define USE_GAMMA_CORRECTION 1 ///< Gamma-correction should fix red&green, try for yourself 38 | 39 | #define TIM_NUM 2 ///< Timer number 40 | #define TIM_CH TIM_CHANNEL_2 ///< Timer's PWM channel 41 | #define DMA_HANDLE hdma_tim2_ch2_ch4 ///< DMA Channel 42 | #define DMA_SIZE_WORD ///< DMA Memory Data Width: {.._BYTE, .._HWORD, .._WORD} 43 | // DMA channel can be found in main.c / tim.c 44 | 45 | /// @} 46 | 47 | /** 48 | * @addtogroup Global_entities 49 | * @brief All driver's methods 50 | * @{ 51 | * @enum ARGB_STATE 52 | * @brief Driver's status enum 53 | */ 54 | typedef enum ARGB_STATE { 55 | ARGB_BUSY = 0, ///< DMA Transfer in progress 56 | ARGB_READY = 1, ///< DMA Ready to transfer 57 | ARGB_OK = 2, ///< Function execution success 58 | ARGB_PARAM_ERR = 3, ///< Error in input parameters 59 | } ARGB_STATE; 60 | 61 | void ARGB_Init(void); // Initialization 62 | void ARGB_Clear(void); // Clear strip 63 | 64 | void ARGB_SetBrightness(u8_t br); // Set global brightness 65 | 66 | void ARGB_SetRGB(u16_t i, u8_t r, u8_t g, u8_t b); // Set single LED by RGB 67 | void ARGB_SetHSV(u16_t i, u8_t hue, u8_t sat, u8_t val); // Set single LED by HSV 68 | void ARGB_SetWhite(u16_t i, u8_t w); // Set white component in LED (RGBW) 69 | 70 | void ARGB_FillRGB(u8_t r, u8_t g, u8_t b); // Fill all strip with RGB color 71 | void ARGB_FillHSV(u8_t hue, u8_t sat, u8_t val); // Fill all strip with HSV color 72 | void ARGB_FillWhite(u8_t w); // Fill all strip's white component (RGBW) 73 | 74 | ARGB_STATE ARGB_Ready(void); // Get DMA Ready state 75 | ARGB_STATE ARGB_Show(void); // Push data to the strip 76 | 77 | /// @} @} 78 | #endif /* ARGB_H_ */ -------------------------------------------------------------------------------- /Library/example.c: -------------------------------------------------------------------------------- 1 | #include "ARGB.h" 2 | 3 | void main(void){ 4 | ARGB_Init(); // Initialization 5 | 6 | ARGB_Clear(); // Clear stirp 7 | while (ARGB_Show() != ARGB_OK); // Update - Option 1 8 | 9 | ARGB_SetBrightness(100); // Set global brightness to 40% 10 | 11 | ARGB_SetRGB(2, 0, 255, 0); // Set LED №3 with 255 Green 12 | while (!ARGB_Show()); // Update - Option 2 13 | 14 | ARGB_SetHSV(0, 0, 255, 255); // Set LED №1 with Red 15 | while (!ARGB_Ready()); // Update - Option 3 16 | ARGB_Show(); 17 | 18 | ARGB_FillWhite(230); // Fill all white component with 230 19 | while (ARGB_Ready() == ARGB_BUSY); // Update - Option 4 20 | ARGB_Show(); 21 | 22 | ARGB_FillRGB(200, 0, 0); // Fill all the strip with Red 23 | while (!ARGB_Show()); 24 | } 25 | -------------------------------------------------------------------------------- /Library/libs.h: -------------------------------------------------------------------------------- 1 | /** 2 | ******************************************* 3 | * @file libs.h 4 | * @author Dmitriy Semenov / Crazy_Geeks 5 | * @brief Internal header for adding sys libs and defines 6 | ******************************************* 7 | */ 8 | 9 | #ifndef LIBS_H_ 10 | #define LIBS_H_ 11 | 12 | #include "main.h" ///< Main project file 13 | #include ///< Standard library 14 | #include ///< Std types 15 | #include ///< _Bool to bool 16 | #include ///< Lib for memcpy, strlen, etc 17 | #include ///< Lib for sprintf, printf, etc 18 | 19 | typedef uint8_t u8_t; ///< 8-bit unsigned 20 | typedef int8_t i8_t; ///< 8-bit signed 21 | typedef uint16_t u16_t; ///< 16-bit unsigned 22 | typedef int16_t i16_t; ///< 16-bit signed 23 | typedef uint32_t u32_t; ///< 32-bit unsigned 24 | typedef int32_t i32_t; ///< 32-bit signed 25 | typedef float fl_t; ///< float type 26 | 27 | 28 | #endif /* LIBS_H_ */ 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## STM32-ARGB-DMA 2 | **Fastest** and **simplest** library for **ARGB LEDs**: *WS28xx* and *SK68xx* Series *RGB* or *RGBW* for *STM32* Series. 3 |
Uses ***DMA Interrupts*** and ***PWM*** to control LED Strip 4 | 5 | ![Banner](Resources/ARGB_Banner.png) 6 | 7 | > ### [RU Description](https://crazygeeks.ru/stm32-argb-lib ) 8 | > ### [RU Habr](https://habr.com/ru/post/664934/ ) 9 | 10 | ### Features: 11 | - Can be used for **addressable RGB** and **RGBW LED** strips 12 | - Uses double-buffer and half-ready **DMA interrupts**, so RAM **consumption is small** 13 | - Uses standard neopixel's **800/400 KHz** protocol 14 | - Supports ***RGB*** and ***HSV*** color models 15 | - Timer frequency **auto-calculation** 16 | 17 | ### Limitations 18 | - Only supports **APBx frequency >32 MHz**. It's timers' limitations. 19 | 20 | ### Lib settings 21 | ```c 22 | #define WS2812 // Family: {WS2811S, WS2811F, WS2812, SK6812} 23 | // WS2811S — RGB, 400kHz; 24 | // WS2811F — RGB, 800kHz; 25 | // WS2812 — GRB, 800kHz; 26 | // SK6812 — RGBW, 800kHz 27 | 28 | #define NUM_PIXELS 5 // Pixel quantity 29 | 30 | #define USE_GAMMA_CORRECTION 1 // Gamma-correction should fix red&green, try for yourself 31 | 32 | #define TIM_NUM 2 // Timer number 33 | #define TIM_CH TIM_CHANNEL_2 // Timer's PWM channel 34 | #define DMA_HANDLE hdma_tim2_ch2_ch4 // DMA Channel 35 | #define DMA_SIZE_WORD // DMA Memory Data Width: {.._BYTE, .._HWORD, .._WORD} 36 | // DMA channel can be found in main.c / tim.c 37 | ``` 38 | 39 | ### Function reference (from .h file): 40 | ```c 41 | // API enum status 42 | typedef enum ARGB_STATE { 43 | ARGB_BUSY = 0, // DMA Transfer in progress 44 | ARGB_READY = 1, // DMA Ready to transfer 45 | ARGB_OK = 2, // Function execution success 46 | ARGB_PARAM_ERR = 3, // Error in input parameters 47 | } ARGB_STATE; 48 | 49 | void ARGB_Init(void); // Initialization 50 | void ARGB_Clear(void); // Clear strip 51 | 52 | void ARGB_SetBrightness(u8_t br); // Set global brightness 53 | 54 | void ARGB_SetRGB(u16_t i, u8_t r, u8_t g, u8_t b); // Set single LED by RGB 55 | void ARGB_SetHSV(u16_t i, u8_t hue, u8_t sat, u8_t val); // Set single LED by HSV 56 | void ARGB_SetWhite(u16_t i, u8_t w); // Set white component in LED (RGBW) 57 | 58 | void ARGB_FillRGB(u8_t r, u8_t g, u8_t b); // Fill all strip with RGB color 59 | void ARGB_FillHSV(u8_t hue, u8_t sat, u8_t val); // Fill all strip with HSV color 60 | void ARGB_FillWhite(u8_t w); // Fill all strip's white component (RGBW) 61 | 62 | ARGB_STATE ARGB_Ready(void); // Get DMA Ready state 63 | ARGB_STATE ARGB_Show(void); // Push data to the strip 64 | ``` 65 | 66 | ### Connection 67 | ![Connection](Resources/ARGB_Scheme.png) 68 | 69 | ### Instructions for use: 70 | > #### [Also available in PDF (RU/EN)](https://github.com/Crazy-Geeks/STM32-ARGB-DMA/tree/master/Instructions ) 71 | - Use *CubeMX* to configure clocks and peripheral. 72 | - Enable *PWM Generation* for your preferred timer channel. 73 | - ***PWM Mode 1***, ***OC Preload**: Enable*, ***Fast Mode**: Disable*, ***CH Polarity**: High* 74 | - Enable **DMA** for your timer channel with **"Memory To Peripheral"** direction. 75 | - Set *DMA* mode to **Circular**, *Data Width* to **Word**/**Byte**, *Increment Address* checkbox only for **Memory**. 76 | - Set *GPIO Speed* to the **Maximum**, use **Open Drain** or **Push Pull** Mode - details in **Troubleshooting**. 77 | - Save CubeMX .ioc file and generate code. 78 | - Add library to your source destination and add #include in your code. 79 | - In **main.c** file search for your DMA Handler 80 | ```c 81 | /* Private variables */ 82 | TIM_HandleTypeDef htim2; 83 | DMA_HandleTypeDef hdma_tim2_ch2_ch4; <-- THIS 84 | ``` 85 | - Add this handler in **ARGB.h** file in **DMA HANDLE** define. 86 | - Set other defines with your settings. 87 | - Now we're ready to go! 88 | 89 | ### TROUBLESHOOTING 90 | - **IF STRIP DOESN'T WORK** 91 | - You should **convert logic levels** 92 | - In **Push Pull** GPIO Mode use **SN74LVC** Translator 93 | - In **Open Drain** GPIO Mode use **1K PullUp** Resistor 94 | - Check HAL DMA Generation order. DMA_Init should be **higher** than TIM_Init 95 | ![DMA_Order](Resources/DMA_Gen_Order.png) 96 | 97 | - **COLOR NOISE** 98 | - Use _Logic Analyzer_ or _Oscilloscope_ to **measure** the signal, or just play with values 99 | - Correct timer values in **.c** file **152 string** 100 | - **ANY OTHER** 101 | - Write an [**issue**](https://github.com/Crazy-Geeks/STM32-ARGB-DMA/issues )! 102 | 103 | ### Suggestions 104 | - Write an [**issue**](https://github.com/Crazy-Geeks/STM32-ARGB-DMA/issues ) or use [**pull request**](https://github.com/Crazy-Geeks/STM32-ARGB-DMA/pulls ) 105 | 106 | ### Special thanks 107 | [**NarodStream**](https://narodstream.ru/stm-urok-119-ws2812b-lenta-na-umnyx-svetodiodax-rgb-chast-2 ), [**VFD**](https://www.thevfdcollective.com/blog/stm32-and-sk6812-rgbw-led ) 108 | 109 | ### Donate options 110 | - [My Site (RU)](https://crazygeeks.ru/donate/ ) 111 | - [PayPal](https://paypal.me/yasnosos ) 112 | - [DonationAlerts](https://www.donationalerts.com/r/yasnosos ) 113 | -------------------------------------------------------------------------------- /Resources/ARGB_Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazy-Geeks/STM32-ARGB-DMA/a47062d56d2855750382ebedf017d0f58135fedd/Resources/ARGB_Banner.png -------------------------------------------------------------------------------- /Resources/ARGB_Scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazy-Geeks/STM32-ARGB-DMA/a47062d56d2855750382ebedf017d0f58135fedd/Resources/ARGB_Scheme.png -------------------------------------------------------------------------------- /Resources/DMA_Gen_Order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazy-Geeks/STM32-ARGB-DMA/a47062d56d2855750382ebedf017d0f58135fedd/Resources/DMA_Gen_Order.png --------------------------------------------------------------------------------