get improved library files from LED clock
This commit is contained in:
parent
ca8b74c9ce
commit
81c40d86ff
@ -12,9 +12,12 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/* Copyright (c) 2016 King Kévin <kingkevin@cuvoodoo.info> */
|
||||
/* this library is used to drive a WS2812b LED chain */
|
||||
/* peripherals used: SPI , timer, DMA (check source for details) */
|
||||
/** library to drive a WS2812B LED chain (code)
|
||||
* @file led_ws2812b.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
* @note peripherals used: SPI @ref led_ws2812b_spi, timer @ref led_ws2812b_timer, DMA @ref led_ws2812b_dma
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
@ -29,45 +32,25 @@
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
|
||||
#include "led_ws2812b.h" // LED WS2812b library API
|
||||
#include "global.h" // global definitions
|
||||
#include "led_ws2812b.h" // LED WS2812B library API
|
||||
#include "global.h" // common methods
|
||||
|
||||
/* WS2812b peripheral configuration */
|
||||
#define WS2812B_SPI SPI1
|
||||
#define WS2812B_SPI_DR SPI1_DR
|
||||
#define WS2812B_SPI_RCC RCC_SPI1
|
||||
#define WS2812B_SPI_PORT GPIOA
|
||||
#define WS2812B_SPI_CLK GPIO_SPI1_SCK
|
||||
#define WS2812B_SPI_DOUT GPIO_SPI1_MISO
|
||||
#define WS2812B_TIMER TIM3
|
||||
#define WS2812B_TIMER_RCC RCC_TIM3
|
||||
#define WS2812B_TIMER_OC TIM_OC3
|
||||
#define WS2812B_CLK_RCC RCC_GPIOB
|
||||
#define WS2812B_CLK_PORT GPIOB
|
||||
#define WS2812B_CLK_PIN GPIO_TIM3_CH3
|
||||
#define WS2812B_DMA DMA1 // DMA1 supports SPI1_TX interrupt
|
||||
#define WS2812B_DMA_RCC RCC_DMA1 // follows previous definition
|
||||
#define WS2812B_DMA_CH DMA_CHANNEL3 // only DMA1 channel 3 supports SPI1_TX interrupt
|
||||
#define WS2812B_DMA_IRQ NVIC_DMA1_CHANNEL3_IRQ // follows previous definition
|
||||
#define WS2812B_DMA_ISR dma1_channel3_isr // follows previous definition
|
||||
/** bit template to encode one byte to be shifted out by SPI to the WS2812B LEDs
|
||||
* @details For each WS2812B bit which needs to be transfered we require to transfer 3 SPI bits.
|
||||
* The first SPI bit is the high start of the WS2812B bit frame.
|
||||
* The second SPI bit determines if the WS2812B bit is a 0 or 1.
|
||||
* The third SPI bit is the last part of the WS2812B bit frame, which is always low.
|
||||
* The binary pattern is 0b100100100100100100100100
|
||||
*/
|
||||
#define LED_WS2812B_SPI_TEMPLATE 0x924924
|
||||
|
||||
/* template to encode one byte
|
||||
* for each WS2812b bit which needs to be transfered we require to transfer 3 SPI bits
|
||||
* the first SPI bit is the high start of the WS2812b bit frame
|
||||
* the second SPI bit determines if the WS2812b bit is a 0 or 1
|
||||
* the third SPI bit is the last part of the WS2812b bit frame, which is always low
|
||||
* only the first 24 bits (3*8) are used */
|
||||
#define WS2812B_SPI_TEMPLATE 0b10010010010010010010010000000000
|
||||
uint8_t led_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) */
|
||||
static volatile bool transmit_flag = false; /**< flag set in software when transmission started, clear by interrupt when transmission completed */
|
||||
|
||||
uint8_t ws2812b_data[WS2812B_LEDS*3*3+40*3/8] = {0}; // SPI encode data to be shifted out for WS2812b + the 50us reset
|
||||
static volatile bool transmit_flag = false; // is transmission ongoing
|
||||
|
||||
/* set color of a single LED
|
||||
* transmission needs to be done separately */
|
||||
void ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
|
||||
void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
|
||||
{
|
||||
// verify the led exists
|
||||
if (led>=WS2812B_LEDS) {
|
||||
if (led>=LED_WS2812B_LEDS) {
|
||||
return;
|
||||
}
|
||||
// wait for transmission to complete before changing the color
|
||||
@ -75,101 +58,108 @@ void ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
|
||||
__WFI();
|
||||
}
|
||||
|
||||
uint8_t colors[] = {green, red, blue};
|
||||
|
||||
// generate the pattern
|
||||
for (uint8_t color=0; color<LENGTH(colors); color++) {
|
||||
uint32_t bits_color = WS2812B_SPI_TEMPLATE; // template to encode the bits in
|
||||
for (uint8_t bit=0; bit<8; bit++) {
|
||||
bits_color |= ((colors[color]>>bit)&0b1)<<(bit*3+9); // encode the data bits in the pattern
|
||||
const uint8_t colors[] = {green, red, blue}; // color order for the WS2812B
|
||||
const uint8_t pattern_bit[] = {0x02, 0x10, 0x80, 0x04, 0x20, 0x01, 0x08, 0x40}; // which bit to change in the pattern
|
||||
const uint8_t pattern_byte[] = {2,2,2,1,1,0,0,0}; // in which byte in the pattern to write the pattern bit
|
||||
for (uint8_t color=0; color<LENGTH(colors); color++) { // colors are encoded similarly
|
||||
// fill the middle bit (fixed is faster than calculating it)
|
||||
for (uint8_t bit=0; bit<8; bit++) { // bit from the color to set/clear
|
||||
if (colors[color]&(1<<bit)) { // setting bit
|
||||
led_ws2812b_data[led*3*3+color*3+pattern_byte[bit]] |= pattern_bit[bit]; // setting bit is pattern
|
||||
} else { // clear bit
|
||||
led_ws2812b_data[led*3*3+color*3+pattern_byte[bit]] &= ~pattern_bit[bit]; // clearing bit is pattern
|
||||
}
|
||||
}
|
||||
// store pattern
|
||||
ws2812b_data[led*3*3+color*3+0] = (bits_color>>24);
|
||||
ws2812b_data[led*3*3+color*3+1] = (bits_color>>16);
|
||||
ws2812b_data[led*3*3+color*3+2] = (bits_color>>8);
|
||||
}
|
||||
}
|
||||
|
||||
/* transmit colors to LEDs */
|
||||
void ws2812b_transmit(void)
|
||||
bool led_ws2812b_transmit(void)
|
||||
{
|
||||
while (transmit_flag) { // wait for previous transmission to complete
|
||||
__WFI();
|
||||
if (transmit_flag) { // a transmission is already ongoing
|
||||
return false;
|
||||
}
|
||||
transmit_flag = true; // remember transmission started
|
||||
dma_set_memory_address(WS2812B_DMA, WS2812B_DMA_CH, (uint32_t)ws2812b_data);
|
||||
dma_set_number_of_data(WS2812B_DMA, WS2812B_DMA_CH, LENGTH(ws2812b_data)); // set the size of the data to transmit
|
||||
dma_enable_transfer_complete_interrupt(WS2812B_DMA, WS2812B_DMA_CH); // warm when transfer is complete to stop transmission
|
||||
dma_enable_channel(WS2812B_DMA, WS2812B_DMA_CH); // enable DMA channel
|
||||
dma_set_memory_address(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, (uint32_t)led_ws2812b_data);
|
||||
dma_set_number_of_data(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, LENGTH(led_ws2812b_data)); // set the size of the data to transmit
|
||||
dma_enable_transfer_complete_interrupt(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // warm when transfer is complete to stop transmission
|
||||
dma_enable_channel(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // enable DMA channel
|
||||
|
||||
spi_enable_tx_dma(WS2812B_SPI); // use DMA to provide data stream to be transfered
|
||||
spi_enable_tx_dma(LED_WS2812B_SPI); // use DMA to provide data stream to be transfered
|
||||
|
||||
timer_set_counter(WS2812B_TIMER, 0); // reset timer counter fro clean clock
|
||||
timer_enable_counter(WS2812B_TIMER); // start timer to generate clock
|
||||
timer_set_counter(LED_WS2812B_TIMER, 0); // reset timer counter fro clean clock
|
||||
timer_enable_counter(LED_WS2812B_TIMER); // start timer to generate clock
|
||||
return true;
|
||||
}
|
||||
|
||||
/* setup WS2812b LED controller */
|
||||
void ws2812b_setup(void)
|
||||
void led_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
|
||||
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(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
|
||||
// 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
|
||||
// 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
|
||||
|
||||
// reset color
|
||||
for (uint16_t led=0; led<WS2812B_LEDS; led++) {
|
||||
ws2812b_set_rgb(led, 0x00, 0x00, 0x00); // switch off (set to black)
|
||||
// fill buffer with bit pattern
|
||||
for (uint16_t i=0; i<LED_WS2812B_LEDS*3; i++) {
|
||||
led_ws2812b_data[i*3+0] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>16);
|
||||
led_ws2812b_data[i*3+1] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>8);
|
||||
led_ws2812b_data[i*3+2] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>0);
|
||||
}
|
||||
ws2812b_transmit(); // set LEDs
|
||||
// fill remaining with with 0 to encode the reset code
|
||||
for (uint16_t i=LED_WS2812B_LEDS*3*3; i<LENGTH(led_ws2812b_data); i++) {
|
||||
led_ws2812b_data[i] = 0;
|
||||
}
|
||||
led_ws2812b_transmit(); // set LEDs
|
||||
}
|
||||
|
||||
/* data transmission finished */
|
||||
void WS2812B_DMA_ISR(void)
|
||||
/** DMA interrupt service routine to stop transmission after it finished */
|
||||
void LED_WS2812B_DMA_ISR(void)
|
||||
{
|
||||
if (dma_get_interrupt_flag(WS2812B_DMA, WS2812B_DMA_CH, DMA_TCIF)) { // transfer completed
|
||||
dma_clear_interrupt_flags(WS2812B_DMA, WS2812B_DMA_CH, DMA_TCIF); // clear flag
|
||||
dma_disable_transfer_complete_interrupt(WS2812B_DMA, WS2812B_DMA_CH); // stop warning transfer completed
|
||||
spi_disable_tx_dma(WS2812B_SPI); // stop SPI asking for more data
|
||||
while (SPI_SR(WS2812B_SPI) & SPI_SR_BSY); // wait for data to be shifted out
|
||||
timer_disable_counter(WS2812B_TIMER); // stop clock
|
||||
dma_disable_channel(WS2812B_DMA, WS2812B_DMA_CH); // stop using DMA
|
||||
if (dma_get_interrupt_flag(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, DMA_TCIF)) { // transfer completed
|
||||
dma_clear_interrupt_flags(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, DMA_TCIF); // clear flag
|
||||
dma_disable_transfer_complete_interrupt(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // stop warning transfer completed
|
||||
spi_disable_tx_dma(LED_WS2812B_SPI); // stop SPI asking for more data
|
||||
while (SPI_SR(LED_WS2812B_SPI) & SPI_SR_BSY); // wait for data to be shifted out
|
||||
timer_disable_counter(LED_WS2812B_TIMER); // stop clock
|
||||
dma_disable_channel(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // stop using DMA
|
||||
transmit_flag = false; // transmission completed
|
||||
}
|
||||
}
|
||||
|
@ -12,18 +12,60 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/* Copyright (c) 2016 King Kévin <kingkevin@cuvoodoo.info> */
|
||||
/* this library is used to drive a WS2812b LED chain */
|
||||
/* peripherals used: SPI , timer, DMA (check source for details) */
|
||||
/** library to drive a WS2812B LED chain (API)
|
||||
* @file led_ws2812b.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
* @note peripherals used: SPI @ref led_ws2812b_spi, timer @ref led_ws2812b_timer, DMA @ref led_ws2812b_dma
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/* number of LEDs */
|
||||
#define WS2812B_LEDS 60
|
||||
/** number of LEDs on the WS2812B strip */
|
||||
#define LED_WS2812B_LEDS 48
|
||||
|
||||
/* set color of a single LED
|
||||
* transmission needs to be done separately */
|
||||
void ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue);
|
||||
/* transmit colors to LEDs */
|
||||
void ws2812b_transmit(void);
|
||||
/* setup WS2812b LED controller */
|
||||
void ws2812b_setup(void);
|
||||
/** peripheral configuration */
|
||||
/** @defgroup led_ws2812b_spi SPI peripheral used to control the WS2812B LEDs
|
||||
* @{
|
||||
*/
|
||||
#define LED_WS2812B_SPI SPI1 /**< SPI peripheral */
|
||||
#define LED_WS2812B_SPI_DR SPI1_DR /**< SPI data register for the DMA */
|
||||
#define LED_WS2812B_SPI_RCC RCC_SPI1 /**< SPI peripheral clock */
|
||||
#define LED_WS2812B_SPI_PORT_RCC RCC_GPIOA /**< SPI I/O peripheral clock */
|
||||
#define LED_WS2812B_SPI_PORT GPIOA /**< SPI port */
|
||||
#define LED_WS2812B_SPI_CLK GPIO_SPI1_SCK /**< SPI clock pin (PA5), connect to PWM output */
|
||||
#define LED_WS2812B_SPI_DOUT GPIO_SPI1_MISO /**< SPI data pin (PA6), connect to WS2812B DIN */
|
||||
/** @} */
|
||||
/** @defgroup led_ws2812b_timer timer peripheral used to generate SPI clock
|
||||
* @{
|
||||
*/
|
||||
#define LED_WS2812B_TIMER TIM3 /**< timer peripheral */
|
||||
#define LED_WS2812B_TIMER_RCC RCC_TIM3 /**< timer peripheral clock */
|
||||
#define LED_WS2812B_TIMER_OC TIM_OC3 /**< timer output compare used to set PWM frequency */
|
||||
#define LED_WS2812B_CLK_RCC RCC_GPIOB /**< timer port peripheral clock */
|
||||
#define LED_WS2812B_CLK_PORT GPIOB /**< timer port */
|
||||
#define LED_WS2812B_CLK_PIN GPIO_TIM3_CH3 /**< timer pin to output PWM (PB0), connect to SPI clock input */
|
||||
/** @} */
|
||||
/** @defgroup led_ws2812b_dma DMA peripheral used to send the data
|
||||
* @{
|
||||
*/
|
||||
#define LED_WS2812B_DMA DMA1 /**< DMA peripheral to put data for WS2812B LED in SPI queue (only DMA1 supports SPI1_TX interrupt) */
|
||||
#define LED_WS2812B_DMA_RCC RCC_DMA1 /**< DMA peripheral clock */
|
||||
#define LED_WS2812B_DMA_CH DMA_CHANNEL3 /**< DMA channel (only DMA1 channel 3 supports SPI1_TX interrupt) */
|
||||
#define LED_WS2812B_DMA_IRQ NVIC_DMA1_CHANNEL3_IRQ /**< DMA channel interrupt signal */
|
||||
#define LED_WS2812B_DMA_ISR dma1_channel3_isr /**< DMA channel interrupt service routine */
|
||||
/** @} */
|
||||
|
||||
/** setup WS2812B LED driver */
|
||||
void led_ws2812b_setup(void);
|
||||
/** set color of a single LED
|
||||
* @param[in] led the LED number to set the color
|
||||
* @param[in] red the red color value to set on the LED
|
||||
* @param[in] green the green color value to set on the LED
|
||||
* @param[in] blue the blue color value to set on the LED
|
||||
* @note transmission needs to be done separately
|
||||
*/
|
||||
void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue);
|
||||
/** transmit color values to WS2812B LEDs
|
||||
* @return true if transmission started, false if another transmission is already ongoing
|
||||
*/
|
||||
bool led_ws2812b_transmit(void);
|
||||
|
66
lib/usart.c
66
lib/usart.c
@ -12,9 +12,12 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/* Copyright (c) 2016 King Kévin <kingkevin@cuvoodoo.info> */
|
||||
/* this library handles USART communication */
|
||||
/* peripherals used: USART (check source for details) */
|
||||
/** library for USART communication (code)
|
||||
* @file usart.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
* @note peripherals used: USART @ref usart
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
@ -30,33 +33,34 @@
|
||||
|
||||
#include "usart.h" // USART header and definitions
|
||||
|
||||
/* which USART to use */
|
||||
#define USART USART1
|
||||
#define USART_RCC RCC_USART1
|
||||
#define USART_IRQ NVIC_USART1_IRQ
|
||||
#define USART_PORT GPIOA
|
||||
#define USART_PIN_TX GPIO_USART1_TX
|
||||
#define USART_PIN_RX GPIO_USART1_RX
|
||||
/** @defgroup usart USART peripheral used for UART communication
|
||||
* @{
|
||||
*/
|
||||
#define USART USART1 /**< USART peripheral */
|
||||
#define USART_RCC RCC_USART1 /**< USART peripheral clock */
|
||||
#define USART_IRQ NVIC_USART1_IRQ /**< USART peripheral interrupt signal */
|
||||
#define USART_ISR usart1_isr /**< USART interrupt service routine */
|
||||
#define USART_PORT GPIOA /**< USART port */
|
||||
#define USART_PORT_RCC RCC_GPIOA /**< USART port peripheral clock */
|
||||
#define USART_PIN_TX GPIO_USART1_TX /**< USART transmit pin (PA9) */
|
||||
#define USART_PIN_RX GPIO_USART1_RX /**< USART receive pin (PA10) */
|
||||
/** @} */
|
||||
|
||||
/* serial baudrate, in bits per second (with 8N1 8 bits, no parity bit, 1 stop bit settings) */
|
||||
#define USART_BAUDRATE 115200
|
||||
/* RX and TX buffer sizes */
|
||||
#define USART_BUFFER 128
|
||||
#define USART_BAUDRATE 115200 /**< serial baudrate, in bits per second (with 8N1 8 bits, no parity bit, 1 stop bit settings) */
|
||||
|
||||
/* input and output ring buffer, indexes, and available memory */
|
||||
static uint8_t rx_buffer[USART_BUFFER] = {0};
|
||||
static volatile uint8_t rx_i = 0;
|
||||
static volatile uint8_t rx_used = 0;
|
||||
static uint8_t tx_buffer[USART_BUFFER] = {0};
|
||||
static volatile uint8_t tx_i = 0;
|
||||
static volatile uint8_t tx_used = 0;
|
||||
/* show the user how much data received over USART is ready */
|
||||
static uint8_t rx_buffer[USART_BUFFER] = {0}; /**< ring buffer for received data */
|
||||
static volatile uint8_t rx_i = 0; /**< current position of read received data */
|
||||
static volatile uint8_t rx_used = 0; /**< how much data has been received and not red */
|
||||
static uint8_t tx_buffer[USART_BUFFER] = {0}; /**< ring buffer for data to transmit */
|
||||
static volatile uint8_t tx_i = 0; /**< current position if transmitted data */
|
||||
static volatile uint8_t tx_used = 0; /**< how much data needs to be transmitted */
|
||||
volatile uint8_t usart_received = 0; // same as rx_used, but since the user can write this variable we don't rely on it
|
||||
|
||||
/* setup USART port */
|
||||
void usart_setup(void)
|
||||
{
|
||||
/* enable USART I/O peripheral */
|
||||
rcc_periph_clock_enable(USART_PORT_RCC); // enable clock for USART port peripheral
|
||||
rcc_periph_clock_enable(USART_RCC); // enable clock for USART peripheral
|
||||
gpio_set_mode(USART_PORT, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, USART_PIN_TX); // setup GPIO pin USART transmit
|
||||
gpio_set_mode(USART_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, USART_PIN_RX); // setup GPIO pin USART receive
|
||||
@ -82,14 +86,12 @@ void usart_setup(void)
|
||||
usart_received = 0;
|
||||
}
|
||||
|
||||
/* put character on USART (blocking) */
|
||||
void usart_putchar_blocking(char c)
|
||||
{
|
||||
usart_flush(); // empty buffer first
|
||||
usart_send_blocking(USART, c); // send character
|
||||
}
|
||||
|
||||
/* ensure all data has been transmitted (blocking) */
|
||||
void usart_flush(void)
|
||||
{
|
||||
while (tx_used) { // idle until buffer is empty
|
||||
@ -98,39 +100,35 @@ void usart_flush(void)
|
||||
usart_wait_send_ready(USART); // wait until transmit register is empty (transmission might not be complete)
|
||||
}
|
||||
|
||||
/* get character from USART (blocking) */
|
||||
char usart_getchar(void)
|
||||
{
|
||||
while (!rx_used) { // idle until data is available
|
||||
__WFI(); // sleep until interrupt;
|
||||
}
|
||||
char to_return = rx_buffer[rx_i]; // get the next available character
|
||||
usart_disable_rx_interrupt(USART); // disable receive interrupt to prevent index corruption
|
||||
rx_i = (rx_i+1)%sizeof(rx_buffer); // update used buffer
|
||||
rx_used--; // update used buffer
|
||||
usart_received = rx_used; // update available data
|
||||
usart_enable_rx_interrupt(USART); // enable receive interrupt
|
||||
return to_return;
|
||||
}
|
||||
|
||||
/* put character on USART (non-blocking until buffer is full) */
|
||||
void usart_putchar_nonblocking(char c)
|
||||
{
|
||||
while (tx_used>=sizeof(tx_buffer)) { // idle until buffer has some space
|
||||
usart_enable_tx_interrupt(USART); // enable transmit interrupt
|
||||
__WFI(); // sleep until something happened
|
||||
}
|
||||
usart_disable_tx_interrupt(USART); // disable transmit interrupt to prevent index corruption
|
||||
tx_buffer[(tx_i+tx_used)%sizeof(tx_buffer)] = c; // put character in buffer
|
||||
tx_used++; // update used buffer
|
||||
usart_enable_tx_interrupt(USART); // enable transmit interrupt
|
||||
}
|
||||
|
||||
#if (USART==USART1)
|
||||
void usart1_isr(void)
|
||||
#elif (USART==USART2)
|
||||
void usart2_isr(void)
|
||||
#elif (USART==USART3)
|
||||
void usart3_isr(void)
|
||||
#endif
|
||||
{ // USART interrupt
|
||||
/** USART interrupt service routine called when data has been transmitted or received */
|
||||
void USART_ISR(void)
|
||||
{
|
||||
if (usart_get_interrupt_source(USART, USART_SR_TXE)) { // data has been transmitted
|
||||
if (!tx_used) { // no data in the buffer to transmit
|
||||
usart_disable_tx_interrupt(USART); // disable transmit interrupt
|
||||
|
33
lib/usart.h
33
lib/usart.h
@ -12,21 +12,36 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/* Copyright (c) 2016 King Kévin <kingkevin@cuvoodoo.info> */
|
||||
/* this library handles USART communication */
|
||||
/* peripherals used: USART (check source for details) */
|
||||
/** library for USART communication (API)
|
||||
* @file usart.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
* @note peripherals used: USART @ref usart
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/* show the user how much received is available */
|
||||
/** transmit and receive buffer sizes */
|
||||
#define USART_BUFFER 128
|
||||
/** how many bytes available in the received buffer since last read */
|
||||
extern volatile uint8_t usart_received;
|
||||
|
||||
/* setup USART port */
|
||||
/** setup USART peripheral */
|
||||
void usart_setup(void);
|
||||
/* put character on USART (blocking) */
|
||||
/** send character over USART (blocking)
|
||||
* @param[in] c character to send
|
||||
* @note blocks until character transmission started */
|
||||
void usart_putchar_blocking(char c);
|
||||
/* ensure all data has been transmitted (blocking) */
|
||||
/** ensure all data has been transmitted (blocking)
|
||||
* @note block until all data has been transmitted
|
||||
*/
|
||||
void usart_flush(void);
|
||||
/* get character from USART (blocking) */
|
||||
/** get character received over USART (blocking)
|
||||
* @return character received over USART
|
||||
* @note blocks until character is received over USART when received buffer is empty
|
||||
*/
|
||||
char usart_getchar(void);
|
||||
/* put character on USART (non-blocking until buffer is full) */
|
||||
/** send character over USART (non-blocking)
|
||||
* @param[in] c character to send
|
||||
* @note blocks if transmit buffer is full, else puts in buffer and returns
|
||||
*/
|
||||
void usart_putchar_nonblocking(char c);
|
||||
|
194
lib/usb_cdcacm.c
194
lib/usb_cdcacm.c
@ -12,8 +12,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/* Copyright (c) 2016 King Kévin <kingkevin@cuvoodoo.info> */
|
||||
/* this library handles the USB CDC ACM */
|
||||
/** library for USB CDC ACM communication (code)
|
||||
* @file usb_cdcacm.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
@ -28,11 +31,13 @@
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/usb/usbd.h> // USB library
|
||||
#include <libopencm3/usb/cdc.h> // USB CDC library
|
||||
#include <libopencm3/cm3/sync.h> // synchronisation utilities
|
||||
|
||||
#include "global.h" // global utilities
|
||||
#include "usb_cdcacm.h" // USB CDC ACM header and definitions
|
||||
|
||||
/* USB devices descriptor
|
||||
* as defined in USB CDC specification section 5
|
||||
/** USB CDC ACM device descriptor
|
||||
* @note as defined in USB CDC specification section 5
|
||||
*/
|
||||
static const struct usb_device_descriptor device_descriptor = {
|
||||
.bLength = USB_DT_DEVICE_SIZE, // the size of this header in bytes, 18
|
||||
@ -51,6 +56,9 @@ static const struct usb_device_descriptor device_descriptor = {
|
||||
.bNumConfigurations = 1, // the number of possible configurations this device has
|
||||
};
|
||||
|
||||
/** USB CDC ACM data endpoints
|
||||
* @note as defined in USB CDC specification section 5
|
||||
*/
|
||||
static const struct usb_endpoint_descriptor data_endpoints[] = {{
|
||||
.bLength = USB_DT_ENDPOINT_SIZE, // the size of the endpoint descriptor in bytes
|
||||
.bDescriptorType = USB_DT_ENDPOINT, // a value of 5 indicates that this describes an endpoint
|
||||
@ -67,9 +75,8 @@ static const struct usb_endpoint_descriptor data_endpoints[] = {{
|
||||
.bInterval = 1, // the frequency, in number of frames, that we're going to be sending data
|
||||
}};
|
||||
|
||||
/* This notification endpoint isn't implemented. According to CDC spec its
|
||||
* optional, but its absence causes a NULL pointer dereference in Linux
|
||||
* cdc_acm driver.
|
||||
/** USB CDC ACM communication endpoints
|
||||
* @note This notification endpoint isn't implemented. According to CDC spec its optional, but its absence causes a NULL pointer dereference in Linux cdc_acm driver
|
||||
*/
|
||||
static const struct usb_endpoint_descriptor communication_endpoints[] = {{
|
||||
.bLength = USB_DT_ENDPOINT_SIZE, // the size of the endpoint descriptor in bytes
|
||||
@ -80,8 +87,9 @@ static const struct usb_endpoint_descriptor communication_endpoints[] = {{
|
||||
.bInterval = 255, // the frequency, in number of frames, that we're going to be sending data
|
||||
}};
|
||||
|
||||
/* functional descriptor
|
||||
* as defined in USB CDC specification section 5.2.3
|
||||
/** USB CDC ACM functional descriptor
|
||||
* @return
|
||||
* @note as defined in USB CDC specification section 5.2.3
|
||||
*/
|
||||
static const struct {
|
||||
struct usb_cdc_header_descriptor header;
|
||||
@ -117,8 +125,8 @@ static const struct {
|
||||
},
|
||||
};
|
||||
|
||||
/* communication class interface descriptor
|
||||
* as defined in USB CDC specification section 5.1.3
|
||||
/** USB CDC interface descriptor
|
||||
* @note as defined in USB CDC specification section 5.1.3
|
||||
*/
|
||||
static const struct usb_interface_descriptor communication_interface[] = {{
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
@ -137,8 +145,8 @@ static const struct usb_interface_descriptor communication_interface[] = {{
|
||||
.extralen = sizeof(cdcacm_functional_descriptors),
|
||||
}};
|
||||
|
||||
/* data class interface descriptor
|
||||
* as defined in USB CDC specification section 5.1.3
|
||||
/** USB CDC ACM data class interface descriptor
|
||||
* @note as defined in USB CDC specification section 5.1.3
|
||||
*/
|
||||
static const struct usb_interface_descriptor data_interface[] = {{
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
@ -154,6 +162,7 @@ static const struct usb_interface_descriptor data_interface[] = {{
|
||||
.endpoint = data_endpoints,
|
||||
}};
|
||||
|
||||
/** USB CDC ACM interface descriptor */
|
||||
static const struct usb_interface interfaces[] = {{
|
||||
.num_altsetting = 1,
|
||||
.altsetting = communication_interface,
|
||||
@ -162,6 +171,7 @@ static const struct usb_interface interfaces[] = {{
|
||||
.altsetting = data_interface,
|
||||
}};
|
||||
|
||||
/** USB CDC ACM configuration descriptor */
|
||||
static const struct usb_config_descriptor config = {
|
||||
.bLength = USB_DT_CONFIGURATION_SIZE, // the length of this header in bytes
|
||||
.bDescriptorType = USB_DT_CONFIGURATION, // a value of 2 indicates that this is a configuration descriptor
|
||||
@ -175,30 +185,31 @@ static const struct usb_config_descriptor config = {
|
||||
.interface = interfaces, // pointer to an array of interfaces
|
||||
};
|
||||
|
||||
/* string table (starting with index 1) */
|
||||
const char *usb_strings[] = {
|
||||
/** USB string table
|
||||
* @note starting with index 1
|
||||
*/
|
||||
static const char *usb_strings[] = {
|
||||
"CuVoodoo",
|
||||
"CDC-ACM",
|
||||
"STM32F1",
|
||||
};
|
||||
|
||||
/* buffer to be used for control requests */
|
||||
static uint8_t usbd_control_buffer[128];
|
||||
|
||||
/* structure holding all the info related to the USB device */
|
||||
static usbd_device *usb_device;
|
||||
static uint8_t usbd_control_buffer[128] = {0}; /**< buffer to be used for control requests */
|
||||
static usbd_device *usb_device = NULL; /**< structure holding all the info related to the USB device */
|
||||
static bool connected = false; /**< is the USB device is connected to a host */
|
||||
|
||||
/* input and output ring buffer, indexes, and available memory */
|
||||
static uint8_t rx_buffer[CDCACM_BUFFER] = {0};
|
||||
static volatile uint8_t rx_i = 0;
|
||||
static volatile uint8_t rx_used = 0;
|
||||
static uint8_t tx_buffer[CDCACM_BUFFER] = {0};
|
||||
static volatile uint8_t tx_i = 0;
|
||||
static volatile uint8_t tx_used = 0;
|
||||
/* show the user how much data received over USB is ready */
|
||||
static uint8_t rx_buffer[CDCACM_BUFFER] = {0}; /**< ring buffer for received data */
|
||||
static volatile uint8_t rx_i = 0; /**< current position of read received data */
|
||||
static volatile uint8_t rx_used = 0; /**< how much data has been received and not red */
|
||||
mutex_t rx_lock = MUTEX_UNLOCKED; /**< lock to update rx_i or rx_used */
|
||||
static uint8_t tx_buffer[CDCACM_BUFFER] = {0}; /**< ring buffer for data to transmit */
|
||||
static volatile uint8_t tx_i = 0; /**< current position if transmitted data */
|
||||
static volatile uint8_t tx_used = 0; /**< how much data needs to be transmitted */
|
||||
mutex_t tx_lock = MUTEX_UNLOCKED; /**< lock to update tx_i or tx_used */
|
||||
volatile uint8_t cdcacm_received = 0; // same as rx_used, but since the user can write this variable we don't rely on it
|
||||
|
||||
/* disconnect USB by pulling down D+ to for re-enumerate */
|
||||
/** disconnect USB by pulling down D+ to for re-enumerate */
|
||||
static void usb_disconnect(void)
|
||||
{
|
||||
/* short USB disconnect to force re-enumerate */
|
||||
@ -222,6 +233,15 @@ static void usb_disconnect(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
/** incoming USB CDC ACM control request
|
||||
* @param[in] usbd_dev USB device descriptor
|
||||
* @param[in] req control request information
|
||||
* @param[in] buf control request data
|
||||
* @param[in] len control request data length
|
||||
* @param[in] complete not used
|
||||
* @return 0 if succeeded, error else
|
||||
* @note resets device when configured with 5 bits
|
||||
*/
|
||||
static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req))
|
||||
{
|
||||
(void)complete;
|
||||
@ -229,18 +249,26 @@ static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *
|
||||
(void)usbd_dev;
|
||||
|
||||
switch (req->bRequest) {
|
||||
case USB_CDC_REQ_SET_CONTROL_LINE_STATE: {
|
||||
bool dtr = (req->wValue & (1 << 0)) ? true : false;
|
||||
bool rts = (req->wValue & (1 << 1)) ? true : false;
|
||||
if (dtr || rts) { // host opened serial port
|
||||
usbd_ep_write_packet(usb_device, 0x82, NULL, 0); // start transmitting
|
||||
}
|
||||
case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
|
||||
connected = req->wValue ? true : false; // check if terminal is open
|
||||
//bool dtr = (req->wValue & (1 << 0)) ? true : false;
|
||||
//bool rts = (req->wValue & (1 << 1)) ? true : false;
|
||||
/* this Linux cdc_acm driver requires this to be implemented
|
||||
* even though it's optional in the CDC spec, and we don't
|
||||
* advertise it in the ACM functional descriptor.
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
uint8_t reply[10] = {0};
|
||||
struct usb_cdc_notification *notif = (void *)reply;
|
||||
/* we echo signals back to host as notification. */
|
||||
notif->bmRequestType = 0xA1;
|
||||
notif->bNotification = USB_CDC_NOTIFY_SERIAL_STATE;
|
||||
notif->wValue = 0;
|
||||
notif->wIndex = 0;
|
||||
notif->wLength = 2;
|
||||
reply[8] = req->wValue & 3;
|
||||
reply[9] = 0;
|
||||
usbd_ep_write_packet(usbd_dev, 0x83, reply, LENGTH(reply));
|
||||
break;
|
||||
case USB_CDC_REQ_SET_LINE_CODING:
|
||||
// ignore if length is wrong
|
||||
if (*len < sizeof(struct usb_cdc_line_coding)) {
|
||||
@ -257,17 +285,20 @@ static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *
|
||||
scb_reset_system(); // reset device
|
||||
while (true); // wait for the reset to happen
|
||||
}
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** USB CDC ACM data received callback
|
||||
* @param[in] usbd_dev USB device descriptor
|
||||
* @param[in] ep endpoint where data came in
|
||||
*/
|
||||
static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
|
||||
{
|
||||
(void)ep;
|
||||
(void)usbd_dev;
|
||||
|
||||
char usb_data[64] = {0}; // buffer to read data
|
||||
uint16_t usb_length = 0; // length of incoming data
|
||||
@ -275,40 +306,46 @@ static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
|
||||
/* receive data */
|
||||
usb_length = usbd_ep_read_packet(usbd_dev, 0x01, usb_data, sizeof(usb_data));
|
||||
if (usb_length) { // copy received data
|
||||
for (uint16_t i=0; i<usb_length && rx_used<sizeof(rx_buffer); i++) { // only until buffer is full
|
||||
rx_buffer[(rx_i+rx_used)%sizeof(rx_buffer)] = usb_data[i]; // put character in buffer
|
||||
for (uint16_t i=0; i<usb_length && rx_used<LENGTH(rx_buffer); i++) { // only until buffer is full
|
||||
rx_buffer[(rx_i+rx_used)%LENGTH(rx_buffer)] = usb_data[i]; // put character in buffer
|
||||
rx_used++; // update used buffer
|
||||
}
|
||||
cdcacm_received = rx_used; // update available data
|
||||
}
|
||||
}
|
||||
|
||||
/** USB CDC ACM data transmitted callback
|
||||
* @param[in] usbd_dev USB device descriptor
|
||||
* @param[in] ep endpoint where data came in
|
||||
*/
|
||||
static void cdcacm_data_tx_cb(usbd_device *usbd_dev, uint8_t ep)
|
||||
{
|
||||
(void)ep;
|
||||
(void)usbd_dev;
|
||||
|
||||
char usb_data[64] = {0}; // buffer to send data
|
||||
uint16_t usb_length = 0; // length of transmitted data
|
||||
|
||||
/* transmit data */
|
||||
if (tx_used) { // copy received data
|
||||
for (usb_length=0; usb_length<sizeof(usb_data) && usb_length<tx_used; usb_length++) { // only until buffer is full
|
||||
usb_data[usb_length] = tx_buffer[(tx_i+usb_length)%sizeof(tx_buffer)]; // put data in transmit data
|
||||
}
|
||||
// this could lead to a lock down
|
||||
// while(usbd_ep_write_packet(usb_device, 0x82, usb_data, usb_length)==0);
|
||||
// this is less critical
|
||||
uint8_t transmitted = 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_t usb_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
|
||||
}
|
||||
|
||||
/** set USB CDC ACM configuration
|
||||
* @param[in] usbd_dev USB device descriptor
|
||||
* @param[in] wValue not used
|
||||
*/
|
||||
static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue)
|
||||
{
|
||||
(void)wValue;
|
||||
(void)usbd_dev;
|
||||
|
||||
usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, cdcacm_data_rx_cb);
|
||||
usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64, cdcacm_data_tx_cb);
|
||||
@ -319,55 +356,58 @@ static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue)
|
||||
|
||||
void cdcacm_setup(void)
|
||||
{
|
||||
usb_disconnect(); // force re-enumerate
|
||||
connected = false; // start with USB not connected
|
||||
usb_disconnect(); // force re-enumerate (useful after a restart or if there is a bootloader using another USB profile)
|
||||
|
||||
/* initialize USB */
|
||||
usb_device = usbd_init(&st_usbfs_v1_usb_driver, &device_descriptor, &config, usb_strings, 3, usbd_control_buffer, sizeof(usbd_control_buffer));
|
||||
usbd_register_set_config_callback(usb_device, cdcacm_set_config);
|
||||
|
||||
/* enable interrupts (to not have to poll all the time) */
|
||||
nvic_enable_irq(NVIC_USB_WAKEUP_IRQ);
|
||||
nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
|
||||
nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ); // without this USB isn't detected by the host
|
||||
|
||||
/* reset buffer states */
|
||||
rx_i = 0;
|
||||
rx_used = 0;
|
||||
mutex_unlock(&rx_lock);
|
||||
cdcacm_received = 0;
|
||||
|
||||
/* start sending */
|
||||
usbd_ep_write_packet(usb_device, 0x82, NULL, 0);
|
||||
tx_i = 0;
|
||||
tx_used = 0;
|
||||
mutex_unlock(&tx_lock);
|
||||
}
|
||||
|
||||
/* get character from USB CDC ACM (blocking) */
|
||||
char cdcacm_getchar(void)
|
||||
{
|
||||
while (!rx_used) { // idle until data is available
|
||||
__WFI(); // sleep until interrupt (not sure if it's a good idea here)
|
||||
}
|
||||
char to_return = rx_buffer[rx_i]; // get the next available character
|
||||
rx_i = (rx_i+1)%sizeof(rx_buffer); // update used buffer
|
||||
rx_i = (rx_i+1)%LENGTH(rx_buffer); // update used buffer
|
||||
rx_used--; // update used buffer
|
||||
cdcacm_received = rx_used; // update available data
|
||||
return to_return;
|
||||
}
|
||||
|
||||
/* put character on USB CDC ACM (blocking) */
|
||||
void cdcacm_putchar(char c)
|
||||
{
|
||||
if (tx_used<sizeof(tx_buffer)) { // buffer not full
|
||||
tx_buffer[(tx_i+tx_used)%sizeof(tx_buffer)] = c; // put character in buffer
|
||||
tx_used++; // update used buffer
|
||||
} else { // buffer full
|
||||
tx_i = (tx_i+1)%sizeof(tx_buffer); // shift start
|
||||
tx_buffer[(tx_i+tx_used)%sizeof(tx_buffer)] = c; // overwrite old data
|
||||
if (!usb_device || !connected) {
|
||||
return;
|
||||
}
|
||||
mutex_lock(&tx_lock); // get lock to prevent race condition
|
||||
if (tx_used<LENGTH(tx_buffer)) { // buffer not full
|
||||
tx_buffer[(tx_i+tx_used)%LENGTH(tx_buffer)] = c; // put character in buffer
|
||||
tx_used++; // update used buffer
|
||||
} else { // buffer full (might be that no terminal is connected to this serial)
|
||||
tx_i = (tx_i+1)%LENGTH(tx_buffer); // shift start
|
||||
tx_buffer[(tx_i+tx_used)%LENGTH(tx_buffer)] = c; // overwrite old data
|
||||
}
|
||||
mutex_unlock(&tx_lock); // release lock
|
||||
if (tx_used==1) { // to buffer is not empty anymore
|
||||
usbd_ep_write_packet(usb_device, 0x82, NULL, 0); // trigger tx callback
|
||||
}
|
||||
usbd_ep_write_packet(usb_device, 0x82, NULL, 0); // trigger tx (not sure why cdcacm_data_tx_cb doesn't work else)
|
||||
}
|
||||
|
||||
void usb_wakeup_isr(void) {
|
||||
/** USB interrupt service routine called when data is received */
|
||||
void usb_lp_can_rx0_isr(void) {
|
||||
usbd_poll(usb_device);
|
||||
}
|
||||
|
||||
void usb_lp_can_rx0_isr(void) {
|
||||
usbd_poll(usb_device);
|
||||
}
|
||||
|
@ -12,18 +12,27 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/* Copyright (c) 2016 King Kévin <kingkevin@cuvoodoo.info> */
|
||||
/* this library handles the USB CDC ACM */
|
||||
/** library for USB CDC ACM communication (API)
|
||||
* @file usb_cdcacm.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/* RX buffer size */
|
||||
#define CDCACM_BUFFER 128
|
||||
/* show the user how much received is available */
|
||||
/** transmit and receive buffer sizes */
|
||||
#define CDCACM_BUFFER 64
|
||||
/** how many bytes available in the received buffer since last read */
|
||||
extern volatile uint8_t cdcacm_received;
|
||||
|
||||
/* setup USB CDC ACM */
|
||||
/** setup USB CDC ACM peripheral */
|
||||
void cdcacm_setup(void);
|
||||
/* get character from USB CDC ACM (blocking) */
|
||||
/** get character received over USB (blocking)
|
||||
* @return character received over USB
|
||||
* @note blocks until character is received over USB when received buffer is empty
|
||||
*/
|
||||
char cdcacm_getchar(void);
|
||||
/* put character on USB CDC ACM (blocking) */
|
||||
/** send character over USB (non-blocking)
|
||||
* @param[in] c character to send
|
||||
* @note blocks if transmit buffer is full, else puts in buffer and returns
|
||||
*/
|
||||
void cdcacm_putchar(char c);
|
||||
|
Loading…
Reference in New Issue
Block a user