uint8_tws2812b_data[WS2812B_LEDS*3*3+40*3/8]={0};// SPI encode data to be shifted out for WS2812b + the 50us reset
staticvolatilebooltransmit_flag=false;// is transmission ongoing
uint8_tled_ws2812b_data[LED_WS2812B_LEDS*3*3+40*3/8+1]={0};/**< data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset (~40 data bits) */
staticvolatilebooltransmit_flag=false;/**< flag set in software when transmission started, clear by interrupt when transmission completed */
timer_enable_counter(LED_WS2812B_TIMER);// start timer to generate clock
returntrue;
}
/* setup WS2812b LED controller */
voidws2812b_setup(void)
voidled_ws2812b_setup(void)
{
/* setup timer to generate clock of (using PWM): 800kHz*3 */
rcc_periph_clock_enable(WS2812B_CLK_RCC);// enable clock for GPIO peripheral
gpio_set_mode(WS2812B_CLK_PORT,GPIO_MODE_OUTPUT_10_MHZ,GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,WS2812B_CLK_PIN);// set pin a output
// setup timer to generate clock of (using PWM): 800kHz*3
rcc_periph_clock_enable(LED_WS2812B_CLK_RCC);// enable clock for GPIO peripheral
gpio_set_mode(LED_WS2812B_CLK_PORT,GPIO_MODE_OUTPUT_10_MHZ,GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,LED_WS2812B_CLK_PIN);// set pin as output
rcc_periph_clock_enable(RCC_AFIO);// enable clock for alternate function (PWM)
rcc_periph_clock_enable(WS2812B_TIMER_RCC);// enable clock for timer peripheral
timer_reset(WS2812B_TIMER);// reset timer state
timer_set_mode(WS2812B_TIMER,TIM_CR1_CKD_CK_INT,TIM_CR1_CMS_EDGE,TIM_CR1_DIR_UP);// set timer mode, use undivided timer clock, edge alignment (simple count), and count up
timer_set_prescaler(WS2812B_TIMER,0);// no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz)
timer_set_period(WS2812B_TIMER,rcc_ahb_frequency/800000/3-1);// set the clock frequency to 800kHz*3bit since we need to send 3 bits to output a 800kbps stream
timer_set_oc_value(WS2812B_TIMER,WS2812B_TIMER_OC,rcc_ahb_frequency/800000/3/2);// duty cycle to 50%
timer_set_oc_mode(WS2812B_TIMER,WS2812B_TIMER_OC,TIM_OCM_PWM1);// set timer to generate PWM (used as clock)
timer_enable_oc_output(WS2812B_TIMER,WS2812B_TIMER_OC);// enable output to generate the clock
/* setup SPI to transmit data (we are slave and the clock comes from the above PWM): 3 SPI bits for 1 WS2812b bit */
rcc_periph_clock_enable(WS2812B_SPI_RCC);// enable clock for SPI peripheral
gpio_set_mode(WS2812B_SPI_PORT,GPIO_MODE_INPUT,GPIO_CNF_INPUT_FLOAT,WS2812B_SPI_CLK);// set clock as input
gpio_set_mode(WS2812B_SPI_PORT,GPIO_MODE_OUTPUT_10_MHZ,GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,WS2812B_SPI_DOUT);// set MISO as output
spi_reset(WS2812B_SPI);// clear SPI values to default
spi_set_slave_mode(WS2812B_SPI);// set SPI as slave (since we use the clock as input)
spi_set_bidirectional_transmit_only_mode(WS2812B_SPI);// we won't receive data
spi_set_unidirectional_mode(WS2812B_SPI);// we only need to transmit data
spi_set_dff_8bit(WS2812B_SPI);// use 8 bits for simpler encoding (but there will be more interrupts)
spi_set_clock_polarity_1(WS2812B_SPI);// clock is high when idle
spi_set_clock_phase_1(WS2812B_SPI);// output data on second edge (rising)
spi_send_msb_first(WS2812B_SPI);// send least significant bit first
spi_enable_software_slave_management(WS2812B_SPI);// control the slave select in software (since there is no master)
spi_set_nss_low(WS2812B_SPI);// set NSS low so we can output
spi_enable(WS2812B_SPI);// enable SPI
rcc_periph_clock_enable(LED_WS2812B_TIMER_RCC);// enable clock for timer peripheral
timer_reset(LED_WS2812B_TIMER);// reset timer state
timer_set_mode(LED_WS2812B_TIMER,TIM_CR1_CKD_CK_INT,TIM_CR1_CMS_EDGE,TIM_CR1_DIR_UP);// set timer mode, use undivided timer clock, edge alignment (simple count), and count up
timer_set_prescaler(LED_WS2812B_TIMER,0);// no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz)
timer_set_period(LED_WS2812B_TIMER,rcc_ahb_frequency/800000/3-1);// set the clock frequency to 800kHz*3bit since we need to send 3 bits to output a 800kbps stream
timer_set_oc_value(LED_WS2812B_TIMER,LED_WS2812B_TIMER_OC,rcc_ahb_frequency/800000/3/2);// duty cycle to 50%
timer_set_oc_mode(LED_WS2812B_TIMER,LED_WS2812B_TIMER_OC,TIM_OCM_PWM1);// set timer to generate PWM (used as clock)
timer_enable_oc_output(LED_WS2812B_TIMER,LED_WS2812B_TIMER_OC);// enable output to generate the clock
// setup SPI to transmit data (we are slave and the clock comes from the above PWM): 3 SPI bits for 1 WS2812B bit
rcc_periph_clock_enable(LED_WS2812B_SPI_PORT_RCC);// enable clock for SPI IO peripheral
gpio_set_mode(LED_WS2812B_SPI_PORT,GPIO_MODE_INPUT,GPIO_CNF_INPUT_FLOAT,LED_WS2812B_SPI_CLK);// set clock as input
gpio_set_mode(LED_WS2812B_SPI_PORT,GPIO_MODE_OUTPUT_10_MHZ,GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,LED_WS2812B_SPI_DOUT);// set MISO as output
rcc_periph_clock_enable(RCC_AFIO);// enable clock for SPI alternate function
rcc_periph_clock_enable(LED_WS2812B_SPI_RCC);// enable clock for SPI peripheral
spi_reset(LED_WS2812B_SPI);// clear SPI values to default
spi_set_slave_mode(LED_WS2812B_SPI);// set SPI as slave (since we use the clock as input)
spi_set_bidirectional_transmit_only_mode(LED_WS2812B_SPI);// we won't receive data
spi_set_unidirectional_mode(LED_WS2812B_SPI);// we only need to transmit data
spi_set_dff_8bit(LED_WS2812B_SPI);// use 8 bits for simpler encoding (but there will be more interrupts)
spi_set_clock_polarity_1(LED_WS2812B_SPI);// clock is high when idle
spi_set_clock_phase_1(LED_WS2812B_SPI);// output data on second edge (rising)
spi_send_msb_first(LED_WS2812B_SPI);// send least significant bit first
spi_enable_software_slave_management(LED_WS2812B_SPI);// control the slave select in software (since there is no master)
spi_set_nss_low(LED_WS2812B_SPI);// set NSS low so we can output
spi_enable(LED_WS2812B_SPI);// enable SPI
// do not disable SPI or set NSS high since it will put MISO high, breaking the beginning of the next transmission
/* configure DMA to provide the pattern to be shifted out from SPI to the WS2812b LEDs */
rcc_periph_clock_enable(WS2812B_DMA_RCC);// enable clock for DMA peripheral
dma_channel_reset(WS2812B_DMA,WS2812B_DMA_CH);// start with fresh channel configuration
dma_set_memory_address(WS2812B_DMA,WS2812B_DMA_CH,(uint32_t)ws2812b_data);// set bit pattern as source address
dma_set_peripheral_address(WS2812B_DMA,WS2812B_DMA_CH,(uint32_t)&WS2812B_SPI_DR);// set SPI as peripheral destination address
dma_set_read_from_memory(WS2812B_DMA,WS2812B_DMA_CH);// set direction from memory to peripheral
dma_enable_memory_increment_mode(WS2812B_DMA,WS2812B_DMA_CH);// go through bit pattern
dma_set_memory_size(WS2812B_DMA,WS2812B_DMA_CH,DMA_CCR_MSIZE_8BIT);// read 8 bits from memory
dma_set_peripheral_size(WS2812B_DMA,WS2812B_DMA_CH,DMA_CCR_PSIZE_8BIT);// write 8 bits to peripheral
dma_set_priority(WS2812B_DMA,WS2812B_DMA_CH,DMA_CCR_PL_HIGH);// set priority to high since time is crucial for the peripheral
nvic_enable_irq(WS2812B_DMA_IRQ);// enable interrupts for this DMA channel
// reset color
for(uint16_tled=0;led<WS2812B_LEDS;led++){
ws2812b_set_rgb(led,0x00,0x00,0x00);// switch off (set to black)
// configure DMA to provide the pattern to be shifted out from SPI to the WS2812B LEDs
rcc_periph_clock_enable(LED_WS2812B_DMA_RCC);// enable clock for DMA peripheral
dma_channel_reset(LED_WS2812B_DMA,LED_WS2812B_DMA_CH);// start with fresh channel configuration
dma_set_memory_address(LED_WS2812B_DMA,LED_WS2812B_DMA_CH,(uint32_t)led_ws2812b_data);// set bit pattern as source address
dma_set_peripheral_address(LED_WS2812B_DMA,LED_WS2812B_DMA_CH,(uint32_t)&LED_WS2812B_SPI_DR);// set SPI as peripheral destination address
dma_set_read_from_memory(LED_WS2812B_DMA,LED_WS2812B_DMA_CH);// set direction from memory to peripheral
dma_enable_memory_increment_mode(LED_WS2812B_DMA,LED_WS2812B_DMA_CH);// go through bit pattern
dma_set_memory_size(LED_WS2812B_DMA,LED_WS2812B_DMA_CH,DMA_CCR_MSIZE_8BIT);// read 8 bits from memory
dma_set_peripheral_size(LED_WS2812B_DMA,LED_WS2812B_DMA_CH,DMA_CCR_PSIZE_8BIT);// write 8 bits to peripheral
dma_set_priority(LED_WS2812B_DMA,LED_WS2812B_DMA_CH,DMA_CCR_PL_HIGH);// set priority to high since time is crucial for the peripheral
nvic_enable_irq(LED_WS2812B_DMA_IRQ);// enable interrupts for this DMA channel
uint8_ttransmitted=usbd_ep_write_packet(usb_device,0x82,usb_data,usb_length);// try to transmit data
tx_i=(tx_i+transmitted)%sizeof(rx_buffer);// update location on buffer
tx_used-=transmitted;// update used size
if(!usbd_dev||!connected||!tx_used){// verify if we can send and there is something to send
return;
}
if(mutex_trylock(&tx_lock)){// try to get lock
uint8_tusb_length=(tx_used>64?64:tx_used);// length of data to be transmitted (respect max packet size)
usb_length=(usb_length>(LENGTH(tx_buffer)-tx_i)?LENGTH(tx_buffer)-tx_i:usb_length);// since here we use the source array not as ring buffer, only go up to the end
while(usb_length!=usbd_ep_write_packet(usb_device,0x82,(void*)(&tx_buffer[tx_i]),usb_length));// ensure data is written into transmit buffer
tx_i=(tx_i+usb_length)%LENGTH(tx_buffer);// update location on buffer
tx_used-=usb_length;// update used size
mutex_unlock(&tx_lock);// release lock
}else{
usbd_ep_write_packet(usb_device,0x82,NULL,0);// trigger empty tx for a later callback
}
usbd_poll(usb_device);// ensure the data gets sent