diff --git a/firmware/Makefile b/firmware/Makefile index 8c1d6c5..bb57a08 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -13,6 +13,7 @@ endif #Add Files and Folders below######################################################### CFILES = main.c syscalls/syscalls.c setup/system_init.c startup/startup_stm32f0xx.c CFILES += temp-adc.c +CFILES += dmx.c ASFILES = sk6812.S INCLUDEPATH = -Iinclude -Iinclude/cmsis @@ -37,7 +38,7 @@ SIZE=arm-none-eabi-size LFLAGS = -mlittle-endian -mthumb -mcpu=cortex-m0 -mthumb-interwork LFLAGS += -mfloat-abi=soft --disable-newlib-supplied-syscalls -nostartfiles -LFLAGS += -Tstartup/stm32f030.ld -Wl,-Map=$(mapfile).map -Wl,--gc-sections -g +LFLAGS += -Tstartup/stm32f030.ld -Wl,-Map=$(mapfile).map -Wl,--gc-sections -Wl,--print-memory-usage -g CFLAGS = -c -fmessage-length=0 -mlittle-endian -mthumb -mcpu=cortex-m0 -mthumb-interwork CFLAGS += -mfloat-abi=soft -nostartfiles -Wall -g3 -O0 diff --git a/firmware/dmx.c b/firmware/dmx.c new file mode 100644 index 0000000..82da070 --- /dev/null +++ b/firmware/dmx.c @@ -0,0 +1,136 @@ +#include +#include + +static uint32_t dmx_base_channel; + + +enum dmx_rx_state_enum { + DMX_RX_WAIT_FOR_BREAK = 0, + DMX_RX_DATA, +}; + +static volatile enum dmx_rx_state_enum dmx_state; + + +static volatile bool break_received; + +/** + * @brief DMX data received. Contains the whole DMX universe including the first 0 byte. + * The controller does check the first byte to be zero. + */ +static volatile uint8_t dmx_channel_data[DMX_UNIVERSE_SIZE + 1]; + +void dmx_init(uint32_t base_channel) +{ + int i; + volatile uint8_t *ptr; + + for (i = 0, ptr = dmx_channel_data; i < DMX_USED_CHANNEL_COUNT; i++, ptr++) { + *ptr = 0u; + } + + dmx_base_channel = base_channel; + break_received = false; + + /* Enable GPIOA and USART1 clock */ + RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_DMAEN; + RCC->APB2ENR |= RCC_APB2ENR_USART1EN; + + /* Switch RXTX pin low, activating permanent READ mode */ + GPIOA->MODER |= (0x1<<(2*5)); + GPIOA->BRR |= (1<<5); + + /* Switch PA10 to RX alternate function of USART1 (AF1) */ + GPIOA->MODER |= (0x2<<(2*10)); + GPIOA->AFR[1] |=(0x1<<(4*2)); + + /* Set baudrate: 48MHz / 250k = 129 */ + USART1->BRR = 192u; + USART1->CR3 = USART_CR3_EIE; + USART1->CR2 = USART_CR2_STOP_1; + USART1->CR1 = USART_CR1_RXNEIE | USART_CR1_RE | USART_CR1_UE; + + /* Map USART1 RX to DMA Channel 3 */ + SYSCFG->CFGR1 &= ~SYSCFG_CFGR1_USART1RX_DMA_RMP; + + DMA1_Channel3->CCR = DMA_CCR_PL_1 | DMA_CCR_MINC | DMA_CCR_TCIE; + + NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); + NVIC_EnableIRQ(USART1_IRQn); +} + + +const uint8_t *dmx_get_data() +{ + return (const uint8_t *)dmx_channel_data; +} + +void USART1_IRQHandler(void) +{ + uint32_t isr; + + isr = USART1->ISR; + USART1->ICR = USART_ICR_ORECF | USART_ICR_NCF | USART_ICR_FECF; + + if (isr & USART_ISR_FE) { + /* Frame error received. Start of DMX frame */ + /* Flush RX data */ + USART1->CR3 &= ~USART_CR3_DMAR; + USART1->RQR = USART_RQR_RXFRQ; + DMA1_Channel3->CCR &= ~DMA_CCR_EN; + while (DMA1_Channel3->CCR & DMA_CCR_EN); + DMA1_Channel3->CMAR = (uint32_t)dmx_channel_data; + DMA1_Channel3->CPAR = (uint32_t)&USART1->RDR; + DMA1_Channel3->CNDTR = DMX_UNIVERSE_SIZE + 1; + DMA1_Channel3->CCR |= DMA_CCR_EN; + USART1->RQR = USART_RQR_RXFRQ; + USART1->CR3 |= USART_CR3_DMAR; + break_received = true; + dmx_state = DMX_RX_DATA; + } else if (isr & USART_ISR_RXNE) { + if (dmx_state != DMX_RX_DATA) { + USART1->RQR = USART_RQR_RXFRQ; + } + } + + __DSB(); +} + +void DMA_CH2_3_DMA2_CH1_2_IRQHandler(void) +{ + uint32_t isr; + + isr = DMA1->ISR; + /* Only clear the interupts of channel 2 (bits 11:9) */ + DMA1->IFCR = isr & 0xF00; + + if (isr & DMA_ISR_TCIF3) { + DMA1->ISR; + dmx_state = DMX_RX_WAIT_FOR_BREAK; + } + __DSB(); +} + +bool dmx_poll_break_received(void) +{ + bool ret; + + /* Atomically reset the flag */ + __disable_irq(); + ret = break_received; + break_received = false; + __enable_irq(); + + return ret; +} + +bool dmx_enough_data_received() +{ + uint32_t received_count = (DMX_UNIVERSE_SIZE + 1) - DMA1_Channel3->CNDTR; + + if (received_count > (dmx_base_channel + DMX_USED_CHANNEL_COUNT)) { + return true; + } + + return false; +} diff --git a/firmware/include/ring-light/dmx.h b/firmware/include/ring-light/dmx.h new file mode 100644 index 0000000..6fdcbf1 --- /dev/null +++ b/firmware/include/ring-light/dmx.h @@ -0,0 +1,49 @@ +#ifndef _DMX_H_ +#define _DMX_H_ + + + +#include +#include + +#define DMX_UNIVERSE_SIZE (512u) +#define DMX_USED_CHANNEL_COUNT (129u) + +/** + * @brief Init DMX reception + * + * DMX data is received from the base channel onwards: + * - R LED1 + * - G LED1 + * - B LED1 + * - W LED1 + * - R LED2 + * ... + * - W LED32 + * - W DISCRETE + * + * In Sum: 129 8 bit channels + * + * @param base_channel Base channel the ring light will listen on + */ +void dmx_init(uint32_t base_channel); + +/** + * @brief Returns the array of the 129 DMX channels + * @return + */ +const uint8_t *dmx_get_data(void); + +/** + * @brief Check if a break was received. This resets the flag + * @return true if a break was received since the last time calling this function + */ +bool dmx_poll_break_received(void); + +/** + * @brief The DMX receiver has received all data for the ring light. It can be read + * @return + */ +bool dmx_enough_data_received(void); + +#endif /* _DMX_H_ */ diff --git a/firmware/main.c b/firmware/main.c index a42b06c..0cdc88e 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -2,6 +2,7 @@ #include #include #include +#include #define RING_MAX_LED 32u #define MAX_TEMP_CELSIUS 70 @@ -15,7 +16,9 @@ enum ring_modes { RING_MODE_ARC, /*!< SK6812 closing ring */ RING_MODE_QUARTER, /*!< SK6812 walking quarter */ RING_MODE_IN_FARBE_UND_BUNT, /*!< SK6812 color mix */ - RING_MODE_MAX /*!< end of list */ + RING_MODE_MAX, /*!< end of list */ + RING_MODE_WAIT_DMX, + RING_MODE_WAIT_DMX_BREAK }; volatile int32_t temperature; @@ -23,6 +26,7 @@ volatile int32_t temperature; extern void sk6812_send_led(uint32_t rgbw); volatile uint32_t wait_tick = 0; +volatile bool blink_tick = false; static void wait_for_ticks(uint32_t ticks) { @@ -33,11 +37,19 @@ static void wait_for_ticks(uint32_t ticks) int main(void) { uint32_t led_val = 0x00UL; + uint32_t last_led_val = 0x00UL; uint32_t led_calc_val[RING_MAX_LED] = {0x00UL}; uint8_t led_pwm_val = 0u; + const uint8_t *dmx_data; bool button_pressed = false; - enum ring_modes mode = RING_MODE_ALL; + bool force_led_update; + bool overtemp_flag = false; + enum ring_modes mode; + + /* Led value / mode before going to DMX */ + uint32_t led_val_before_dmx = 0u; + enum ring_modes mode_before_dmx = RING_MODE_RED; /* Init to save value */ RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; RCC->APB1ENR |= RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM14EN; @@ -83,17 +95,41 @@ int main(void) mode = RING_MODE_WHITE_DISCRETE; temperature_adc_init(); + dmx_init(0u); SysTick_Config(800000); while(1) { + force_led_update = false; temperature = temperature_adc_get_temp(); + if (led_val != last_led_val || button_pressed) { + force_led_update = true; + } + /*! -# Gradually dim down the LED brightness in case the temperature is too high */ - if (temperature > ((MAX_TEMP_CELSIUS) * 10)) { - if (led_val > 20) + if (overtemp_flag) { + if (temperature < (MAX_TEMP_CELSIUS-15) * 10) { + overtemp_flag = false; + } + } else { + overtemp_flag = temperature > ((MAX_TEMP_CELSIUS) * 10) ? true : false; + } + if (overtemp_flag) { + if (led_val > 2 && mode < RING_MODE_MAX) led_val--; } + if (dmx_poll_break_received()) { + /* DMX received. Go to DMX mode. + * Save old state + */ + if (mode < RING_MODE_MAX) { + led_val_before_dmx = led_val; + mode_before_dmx = mode; + } + mode = RING_MODE_WAIT_DMX; + } + led_pwm_val = 0u; switch (mode) { @@ -163,6 +199,32 @@ int main(void) } } break; + case RING_MODE_WAIT_DMX: + force_led_update = false; + if (dmx_enough_data_received() && !overtemp_flag) { + dmx_data = dmx_get_data(); + mode = RING_MODE_WAIT_DMX_BREAK; + if (dmx_data[0] != 0) + break; + for (int i = 0; i < RING_MAX_LED; i++) { + led_calc_val[i] = (dmx_data[1 + i*4 + 3]) | + (dmx_data[1 + i*4 + 2] << 8) | + (dmx_data[1 + i*4 + 0] << 16) | + (dmx_data[1 + i*4 + 1] << 24); + } + led_pwm_val = dmx_data[129]; + force_led_update = true; + } else if (overtemp_flag) { + force_led_update = true; + for (int i = 0; i < RING_MAX_LED; i++) { + led_calc_val[i] = 0ul; + } + led_pwm_val = 0; + } + break; + case RING_MODE_WAIT_DMX_BREAK: + force_led_update = false; + break; default: for(int i = 0; i < RING_MAX_LED; i ++) { led_calc_val[i] = 0x00000000UL; @@ -170,37 +232,68 @@ int main(void) break; } - TIM14->CCR1 = led_pwm_val; - __disable_irq(); - for(int i = 0; i < RING_MAX_LED; i ++) { - sk6812_send_led(led_calc_val[i]); + if (overtemp_flag) { + force_led_update = true; + led_calc_val[0] = blink_tick ? 0x00FF0000UL : 0UL; } - __enable_irq(); - wait_for_ticks(5); + + if (force_led_update) { + TIM14->CCR1 = led_pwm_val; + + for(int i = 0; i < RING_MAX_LED; i ++) { + /* Allow interrupts in between LEDs. + * They must not exceed the reset length of 80us of SK6812. + */ + __disable_irq(); + sk6812_send_led(led_calc_val[i]); + __enable_irq(); + } + last_led_val = led_val; + } + + /* Only wait in case of non-DMX mode */ + if (!(mode == RING_MODE_WAIT_DMX_BREAK || mode == RING_MODE_WAIT_DMX) || overtemp_flag) + wait_for_ticks(5); if((int16_t)TIM3->CNT > (int16_t)led_val) { led_val = 0u; - } - else if(((int16_t)led_val - (int16_t)TIM3->CNT) > UINT8_MAX) { + } else if(((int16_t)led_val - (int16_t)TIM3->CNT) > UINT8_MAX) { led_val = 255u; - } - else { + } else { led_val = (int16_t)led_val - (int16_t)TIM3->CNT; } + TIM3->CNT = 0u; + if(button_pressed) { if(GPIOA->IDR & GPIO_IDR_0) { button_pressed = false; } - } - else if(!(GPIOA->IDR & GPIO_IDR_0)) { - mode = (mode + 1) % RING_MODE_MAX; + } else if(!(GPIOA->IDR & GPIO_IDR_0)) { button_pressed = true; + /* Button pressed */ + if (mode > RING_MODE_MAX) { + /* In DMX mode. Abort DMX mode */ + mode = mode_before_dmx; + led_val = led_val_before_dmx; + } else { + /* Normal mode switching */ + mode = (mode + 1) % RING_MODE_MAX; + } } } } -void SysTick_Handler(void) { +void SysTick_Handler(void) +{ + static uint32_t tick = 10; + + if (!--tick) { + tick = 10; + blink_tick = !blink_tick; + } + + wait_tick++; } diff --git a/firmware/temp-adc.c b/firmware/temp-adc.c index a488a55..d3c019c 100644 --- a/firmware/temp-adc.c +++ b/firmware/temp-adc.c @@ -96,7 +96,7 @@ void DMA_CH1_IRQHandler(void) uint32_t isr; isr = DMA1->ISR; - DMA1->IFCR = isr; + DMA1->IFCR = isr & 0xF; if (isr & DMA_ISR_TCIF1) { process_adc_samples();