get improved library files from LED clock

This commit is contained in:
King Kévin 2016-08-14 18:37:30 +02:00
parent ca8b74c9ce
commit 81c40d86ff
6 changed files with 345 additions and 251 deletions

View File

@ -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
}
}

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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);