#include "led.h" #include "stm32f030x6.h" LED_Colour_t LED_PixelData[LED_COUNT] = {{0}}; volatile bool LED_FrameFlag = false; volatile bool LED_SuspendFlag = false; #define LED_ODR_MASK ((1 << PIN_LED_R_0) | (1 << PIN_LED_G_0) \ | (1 << PIN_LED_B_0) | (1 << PIN_LED_R_1) \ | (1 << PIN_LED_G_1) | (1 << PIN_LED_B_1) \ | (1 << PIN_LED_R_2) | (1 << PIN_LED_G_2) \ | (1 << PIN_LED_B_2) | (1 << PIN_LED_R_3) \ | (1 << PIN_LED_G_3) | (1 << PIN_LED_B_3)) #define LED_MODER_MASK ((3 << PIN_LED_R_0 * 2) | (3 << PIN_LED_G_0 * 2) \ | (3 << PIN_LED_B_0 * 2) | (3 << PIN_LED_R_1 * 2) \ | (3 << PIN_LED_G_1 * 2) | (3 << PIN_LED_B_1 * 2) \ | (3 << PIN_LED_R_2 * 2) | (3 << PIN_LED_G_2 * 2) \ | (3 << PIN_LED_B_2 * 2) | (3 << PIN_LED_R_3 * 2) \ | (3 << PIN_LED_G_3 * 2) | (3 << PIN_LED_B_3 * 2)) #define LED_MODER ((1 << PIN_LED_R_0 * 2) | (1 << PIN_LED_G_0 * 2) \ | (1 << PIN_LED_B_0 * 2) | (1 << PIN_LED_R_1 * 2) \ | (1 << PIN_LED_G_1 * 2) | (1 << PIN_LED_B_1 * 2) \ | (1 << PIN_LED_R_2 * 2) | (1 << PIN_LED_G_2 * 2) \ | (1 << PIN_LED_B_2 * 2) | (1 << PIN_LED_R_3 * 2) \ | (1 << PIN_LED_G_3 * 2) | (1 << PIN_LED_B_3 * 2)) // TIM3 is clocked by APB1 and thus receives only half the system clock. The 4 // LSBs have bit lengths 2, 4, 8, and 16 cycles and are generated blocking from // an inline assembly block and are thus not in this table. static const uint16_t LED_BitLengths[LED_BITS - 4] = { 16, 32, 64, 128, 256, 512, 1024, 2048 }; static const int LED_Pins[LED_COLUMNS] = { PIN_LED_R_0, PIN_LED_G_0, PIN_LED_B_0, PIN_LED_R_1, PIN_LED_G_1, PIN_LED_B_1, PIN_LED_R_2, PIN_LED_G_2, PIN_LED_B_2, PIN_LED_R_3, PIN_LED_G_3, PIN_LED_B_3 }; // Number of 16-bit values to be transferred to the GPIO's output register via // DMA. Each value contains output values for all columns. For each row, a // value for each bit is needed, plus a constant last transfer to turn all LEDs // off during the processing time. #define LED_DMA_BUFFER_LENGTH (LED_ROWS * (LED_BITS + 1)) // Define buffers for double buffering static uint16_t LED_DMABuffer1[LED_DMA_BUFFER_LENGTH]; static uint16_t LED_DMABuffer2[LED_DMA_BUFFER_LENGTH]; static uint16_t *volatile LED_FrontBuffer = LED_DMABuffer1; static uint16_t *volatile LED_BackBuffer = LED_DMABuffer2; // If true, front and back buffers will be swapped after the current frame static volatile bool LED_QueuePageFlip = false; void LED_Commit(void) { // Wait for the current data to be displayed in case LED_Commit was called // more than one time during a single frame. Otherwise, a race condition // might occur. while(LED_QueuePageFlip); for(int r = 0; r < LED_ROWS; r++) { for(int i = 0; i < LED_COLUMNS; i++) { // Use pixel data as a raw byte buffer to get R, G, B in order uint8_t colour_value = ((uint8_t*)LED_PixelData)[r * LED_COLUMNS + i]; uint16_t gamma_corrected = (uint16_t)colour_value; gamma_corrected *= gamma_corrected; gamma_corrected >>= 16 - LED_BITS; for(int j = 0; j < LED_BITS; j++) { if(gamma_corrected & (1 << j)) { LED_BackBuffer[r * (LED_BITS + 1) + j] &= ~(1 << LED_Pins[i]); } else { LED_BackBuffer[r * (LED_BITS + 1) + j] |= 1 << LED_Pins[i]; } } } // Data to reset outputs after all data bits are sent LED_BackBuffer[r * (LED_BITS + 1) + LED_BITS] = LED_ODR_MASK; } LED_QueuePageFlip = true; } static void LED_StartBCM(int row) { // Reset DMA and timer TIM3->CR1 = 0x0000; TIM3->DIER = 0x0000; TIM3->CNT = 0; DMA1_Channel3->CCR = 0x0000; DMA1_Channel4->CCR = 0x0000; TIM3->ARR = 1; // Delay before everything starts with the first DMA request TIM3->CR1 = TIM_CR1_ARPE; // Since ARPE is set, this write goes to ARR's shadow register TIM3->ARR = LED_BitLengths[0]; TIM3->DIER = TIM_DIER_UDE | TIM_DIER_CC1DE; // DMA channel 3: Output data to port a on TIM3 update DMA1_Channel3->CMAR = (uint32_t)&(LED_FrontBuffer[row * (LED_BITS + 1) + 4]); // One transfer for each bit plus one to set the outputs to zero again. // The first 4 are sent out with assembly before the first DMA transfer. DMA1_Channel3->CNDTR = LED_BITS + 1 - 4; // Highest priority, 16 bits, increment memory, memory to peripheral DMA1_Channel3->CCR = DMA_CCR_PL | DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 | DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_EN | DMA_CCR_TCIE; // DMA channel 3: Output data to port a on TIM3 update // The bit lengths table is offset because the first value is already in // the timer's ARR shadow register. DMA1_Channel4->CMAR = (uint32_t)&(LED_BitLengths[1]); // The first four bits are sent out with the assembly code below, so only // LED_BITS - 5 bit lengths are needed. DMA1_Channel4->CNDTR = LED_BITS - 1 - 4; // 16 bits, memory increment mode, memory to peripheral DMA1_Channel4->CCR = DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 | DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_EN; // Send the 4 LSBs, set to zero at the end (so the timing is independent // from the delay introduced by the DMA) __asm__ volatile(".syntax unified\n" "str %[d0], [%[odr]];" "str %[d1], [%[odr]];" "nop;" "nop;" "str %[d2], [%[odr]];" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "str %[d3], [%[odr]];" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "nop;" "str %[off], [%[odr]];" : : [odr] "l" ((uint32_t)&(GPIOA->ODR)), [d0] "r" (LED_FrontBuffer[row * (LED_BITS + 1) + 0]), [d1] "r" (LED_FrontBuffer[row * (LED_BITS + 1) + 1]), [d2] "r" (LED_FrontBuffer[row * (LED_BITS + 1) + 2]), [d3] "r" (LED_FrontBuffer[row * (LED_BITS + 1) + 3]), [off] "r" (LED_ODR_MASK) :); // Enable TIM3 for the rest of the bits TIM3->CR1 = TIM_CR1_ARPE | TIM_CR1_CEN; } static inline void LED_PulseRowClock(void) { __asm__ volatile("nop"); GPIOF->BSRR = (1 << PIN_ROW_SCK); __asm__ volatile("nop"); GPIOF->BRR = (1 << PIN_ROW_SCK); } static inline void LED_PageFlip(void) { uint16_t *volatile tmp; tmp = LED_FrontBuffer; LED_FrontBuffer = LED_BackBuffer; LED_BackBuffer = tmp; LED_QueuePageFlip = false; } void LED_InitShiftRegister(void) { RCC->AHBENR |= RCC_AHBENR_GPIOFEN; GPIOF->ODR &= ~(1 << PIN_ROW_SCK) & ~(1 << PIN_ROW_DATA); GPIOF->MODER = (GPIOF->MODER & ~(0x3 << PIN_ROW_SCK * 2) & ~(0x3 << PIN_ROW_DATA * 2)) | (0x1 << PIN_ROW_SCK * 2) | (0x1 << PIN_ROW_DATA * 2); // Reset the shift register. Since RCK and SCK are shorted together, one // additional clock cycle is needed. GPIOF->BSRR = (1 << PIN_ROW_DATA); for(int i = 0; i < LED_ROWS + 1; i++) { LED_PulseRowClock(); } // All shift register outputs are now '1'. Because the rows are driven with // external transistors, this means all rows are off. } void LED_Init(void) { RCC->AHBENR |= RCC_AHBENR_GPIOAEN; RCC->AHBENR |= RCC_AHBENR_DMA1EN; RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // Fill both DMA buffers LED_QueuePageFlip = false; LED_Commit(); LED_PageFlip(); LED_Commit(); LED_PageFlip(); GPIOA->ODR |= LED_ODR_MASK; GPIOA->PUPDR &= ~LED_MODER_MASK; GPIOA->OTYPER |= LED_ODR_MASK; GPIOA->OSPEEDR |= LED_MODER_MASK; GPIOA->MODER = (GPIOA->MODER & ~LED_MODER_MASK) | LED_MODER; TIM3->CR1 = 0x0000; TIM3->PSC = 0; // CH1 will be used for DMA requests updating ARR TIM3->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1; TIM3->CCER = TIM_CCER_CC1E; TIM3->CCR1 = 1; // DMA channel 3: Output data to port a on TIM3 update DMA1_Channel3->CPAR = (uint32_t)&(GPIOA->ODR); // DMA channel 4: Update TIM3 ARR on TIM3 CH1 (which is set to 0) DMA1_Channel4->CPAR = (uint32_t)&(TIM3->ARR); NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); LED_StartBCM(0); } void LED_Suspend(void) { LED_SuspendFlag = true; while(LED_SuspendFlag); // Disable timer and DMA channels TIM3->CR1 = 0x0000; TIM3->DIER = 0x0000; TIM3->CNT = 0; DMA1->IFCR = DMA_IFCR_CTCIF3; DMA1_Channel3->CCR = 0x0000; DMA1_Channel4->CCR = 0x0000; NVIC_DisableIRQ(DMA1_Channel2_3_IRQn); NVIC_ClearPendingIRQ(DMA1_Channel2_3_IRQn); // Deactivate all rows and columns GPIOA->BSRR = LED_ODR_MASK; GPIOF->BSRR = (1 << PIN_ROW_DATA); for(int i = 0; i < LED_ROWS + 1; i++) { LED_PulseRowClock(); } // Disable timer and DMA clocks RCC->AHBENR &= ~RCC_AHBENR_DMA1EN; RCC->APB1ENR &= ~RCC_APB1ENR_TIM3EN; } void LED_WakeUp(void) { LED_Init(); } void DMA1_Channel2_3_IRQHandler(void) { // Interrupt when all bits have been sent DMA1->IFCR = DMA_IFCR_CTCIF3; static int current_row = 0; current_row++; if(current_row >= LED_ROWS) { GPIOF->BRR = 1 << PIN_ROW_DATA; current_row = 0; LED_PulseRowClock(); GPIOF->BSRR = 1 << PIN_ROW_DATA; if(LED_QueuePageFlip) { LED_PageFlip(); } LED_FrameFlag = true; } else { LED_PulseRowClock(); } if(LED_SuspendFlag) { LED_SuspendFlag = false; return; } // Start sending bits out again. The row offset caused by the shift // register "lagging" one clock cycle behind because RCK and SCK are // connected to the same signal doesn't matter here: we're not paying much // attention to which rows are which anyway. LED_StartBCM(current_row); }