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