Skip to content

Instantly share code, notes, and snippets.

@vooon
Created March 13, 2016 22:19
Show Gist options
  • Save vooon/b4a7d36cadf995ffab54 to your computer and use it in GitHub Desktop.
Save vooon/b4a7d36cadf995ffab54 to your computer and use it in GitHub Desktop.
Code snippet of WS2812b smart led driver for STM32F373 & ChibiOS
#pragma once
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} rgb_t;
void wsInit(void);
/**
* Set pixel color
*
* @param[in] n pixel number
*/
void wsSet(size_t n, rgb_t rgb);
/**
* Send framebuffer to leds.
*
* Suspend thread until done.
*/
msg_t wsSync(void);
#include "ch.h"
#include "hal.h"
#include "ws2812b.h"
#include "fw_common.h"
#include <string.h>
/*
* DMA + TIM code has some problems on F373 since i enable other perith drivers.
* This is alternative driver wich uses USART3 module and one DMA channel.
*
* We use data inversion, pin inversion & Rx/Tx swap features of F3's USARTs.
* For best timings we need 7N1 mode, but STM32 do not support it. So we emulate 7N2 by 8N1 mode.
* WS2812 datasheet allow longer 0 state (+600 usec) so additional 400 usec should be ok.
*
* @code
* Timing:
* "0" "1" "0"
* S 0 1 2 3 4 5 6 7 P
* _ ___ _
* _! !___! !_! !_____
* @endcode
*/
#define NR_LEDS 8
#define NR_PORTS 2
#define FB_INIT 0b00100100 /* framebuffer initial pattern */
#define BYTES_PER_LED 8 /* 24 bit word / 3 bit per byte */
static uint8_t ws_fb[NR_PORTS][BYTES_PER_LED * (NR_LEDS / NR_PORTS)];
static thread_reference_t ws_thread = NULL;
/* PB8/PB9 - USART3 Tx/Rx */
static USART_TypeDef *ws_usart = USART3;
static const stm32_dma_stream_t *ws_dmatx = STM32_DMA_STREAM(STM32_UART_USART3_TX_DMA_STREAM);
static void ws_usart_stop(void)
{
dmaStreamDisable(ws_dmatx);
ws_usart->CR1 = 0;
ws_usart->CR2 = 0;
ws_usart->CR3 = 0;
}
/**
* USART3 IRQ handler. (we use TC to wake up therad)
*
* @isr
*/
OSAL_IRQ_HANDLER(STM32_USART3_HANDLER)
{
OSAL_IRQ_PROLOGUE();
uint32_t cr1 = ws_usart->CR1;
// Reading and clearing status
uint32_t isr = ws_usart->ISR;
ws_usart->ICR = isr;
// TC - we are done. stop all
if ((isr & USART_ISR_TC) && (cr1 & USART_CR1_TCIE)) {
// disable TC interrupt
ws_usart->CR1 = cr1 & ~USART_CR1_TCIE;
// Disconnect DOUT lines from USART, make it OUT PP
GPIOB->MODER = (GPIOB->MODER & 0xfff0ffff) | (0b0101 << 16);
// stop USART
ws_usart_stop();
// wake up thread
osalSysLockFromISR();
osalThreadResumeI(&ws_thread, MSG_OK);
osalSysUnlockFromISR();
}
OSAL_IRQ_EPILOGUE();
}
static void ws_sync_one(size_t port)
{
chDbgAssert(port < NR_PORTS, "wrong port");
osalSysLock();
ws_usart_stop();
/* Connect port to USART: PB8 & PB9
* Faster than regular PAL functions, but not portable.
*
* DOUT0 -> PB9 OUT : PB8 ALT(7)
* DOUT1 -> PB8 ALT(7) : PB8 OUT
*/
GPIOB->MODER = (GPIOB->MODER & 0xfff0ffff) | ((port == 1)? (0b1001 << 16) : (0b0110 << 16));
GPIOB->AFRH = (GPIOB->AFRH & 0xffffff00) | ((port == 1)? (7 << 4) : (7 << 0));
/* Setting baudrate to 2.5 Mbit => bit time is 400 usec
*
* Usual function [1] does not work, because BRR must be greater than 15.
* So we use OVER8 [2] mode and choose nearest USARTDIV = 28 -> 2571428 Bit/s or 388 usec.
*
* [1]: BRR = STM32_USART3CLK / 2500000UL
* [2]: USARTDIV = BRR[15:4] | BRR[2:0] << 1
*/
ws_usart->BRR = 0b10110;
// Reset any pending status flags
ws_usart->ICR = 0xffffffffU;
// Enable inversion, check port by Rx/Tx swap
ws_usart->CR2 = USART_CR2_DATAINV | USART_CR2_TXINV | USART_CR2_RXINV | ((port == 1)? USART_CR2_SWAP : 0);
ws_usart->CR3 = USART_CR3_DMAT;
// Enable USART, TC must be enabled later
ws_usart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_OVER8;
// Setup DMA transfer
dmaStreamSetMemory0(ws_dmatx, ws_fb[port]);
dmaStreamSetPeripheral(ws_dmatx, &ws_usart->TDR);
dmaStreamSetTransactionSize(ws_dmatx, sizeof(ws_fb[port]));
dmaStreamSetMode(ws_dmatx, STM32_DMA_CR_DIR_M2P | STM32_DMA_CR_MINC |
STM32_DMA_CR_MSIZE_BYTE | STM32_DMA_CR_PSIZE_BYTE |
STM32_DMA_CR_PL(STM32_UART_USART3_DMA_PRIORITY));
// enable TC
ws_usart->ICR = USART_ICR_TCCF;
ws_usart->CR1 |= USART_CR1_TCIE;
// Start transfer
dmaStreamEnable(ws_dmatx);
// wait for completion
osalThreadSuspendS(&ws_thread);
osalSysUnlock();
}
msg_t wsSync(void)
{
ws_sync_one(0);
ws_sync_one(1);
return MSG_OK;
}
static inline void fb_set(size_t p, size_t n, rgb_t rgb)
{
uint32_t grb = rgb.g << 16 | rgb.r << 8 | rgb.b;
for (size_t i = n * BYTES_PER_LED; i < (n + 1) * BYTES_PER_LED; i++, grb <<= 3) {
uint8_t b3 = FB_INIT; // 0b00100100
if (grb & 0x800000) b3 |= 0b00000001;
if (grb & 0x400000) b3 |= 0b00001000;
if (grb & 0x200000) b3 |= 0b01000000;
ws_fb[p][i] = b3;
}
}
void wsSet(size_t n, rgb_t rgb)
{
const size_t nsplit = NR_LEDS / NR_PORTS;
if (n < nsplit) // 0..3 -> WS_DOUT1
fb_set(0, n, rgb);
else if (n >= nsplit && n < NR_LEDS) // 4..7 -> WS_DOUT2
fb_set(1, n - nsplit, rgb);
}
void wsInit(void)
{
// Set GPIO to initial state
const uint16_t mask = (1 << GPIOB_WS_DOUT1) | (1 << GPIOB_WS_DOUT2);
palClearPort(GPIOB, mask);
palSetGroupMode(GPIOB, mask, 0, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST | PAL_STM32_PUPDR_FLOATING);
// Framebuffer initial state: three 0 bits
memset(ws_fb, FB_INIT, sizeof(ws_fb));
// alloc TX DMA stream
bool b = dmaStreamAllocate(ws_dmatx, STM32_UART_USART3_IRQ_PRIORITY, NULL, NULL);
osalDbgAssert(!b, "stream allocated");
rccEnableUSART3(false);
nvicEnableVector(STM32_USART3_NUMBER, STM32_UART_USART3_IRQ_PRIORITY);
// USART initial state
ws_usart_stop();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment