diff --git a/lib/led_ws2812b.c b/lib/led_ws2812b.c index 12735ee..2d9c759 100644 --- a/lib/led_ws2812b.c +++ b/lib/led_ws2812b.c @@ -12,9 +12,12 @@ * along with this program. If not, see . * */ -/* Copyright (c) 2016 King Kévin */ -/* 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 + * @date 2016 + * @note peripherals used: SPI @ref led_ws2812b_spi, timer @ref led_ws2812b_timer, DMA @ref led_ws2812b_dma + */ /* standard libraries */ #include // standard integer types @@ -29,45 +32,25 @@ #include // interrupt handler #include // 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>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>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>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. * */ -/* Copyright (c) 2016 King Kévin */ -/* 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 + * @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); diff --git a/lib/usart.c b/lib/usart.c index 6a7abcf..c7dcf1a 100644 --- a/lib/usart.c +++ b/lib/usart.c @@ -12,9 +12,12 @@ * along with this program. If not, see . * */ -/* Copyright (c) 2016 King Kévin */ -/* this library handles USART communication */ -/* peripherals used: USART (check source for details) */ +/** library for USART communication (code) + * @file usart.c + * @author King Kévin + * @date 2016 + * @note peripherals used: USART @ref usart + */ /* standard libraries */ #include // 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 diff --git a/lib/usart.h b/lib/usart.h index 644827c..a48c9f0 100644 --- a/lib/usart.h +++ b/lib/usart.h @@ -12,21 +12,36 @@ * along with this program. If not, see . * */ -/* Copyright (c) 2016 King Kévin */ -/* this library handles USART communication */ -/* peripherals used: USART (check source for details) */ +/** library for USART communication (API) + * @file usart.h + * @author King Kévin + * @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); diff --git a/lib/usb_cdcacm.c b/lib/usb_cdcacm.c index 03ff37e..3201c36 100644 --- a/lib/usb_cdcacm.c +++ b/lib/usb_cdcacm.c @@ -12,8 +12,11 @@ * along with this program. If not, see . * */ -/* Copyright (c) 2016 King Kévin */ -/* this library handles the USB CDC ACM */ +/** library for USB CDC ACM communication (code) + * @file usb_cdcacm.c + * @author King Kévin + * @date 2016 + */ /* standard libraries */ #include // standard integer types @@ -28,11 +31,13 @@ #include // Cortex M3 utilities #include // USB library #include // USB CDC library +#include // 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 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. * */ -/* Copyright (c) 2016 King Kévin */ -/* this library handles the USB CDC ACM */ +/** library for USB CDC ACM communication (API) + * @file usb_cdcacm.h + * @author King Kévin + * @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);