/** ****************************************************************************** * @addtogroup PIOS PIOS Core hardware abstraction layer * @{ * @addtogroup PIOS_VIDEO Code for OSD video generator * @brief OSD generator, Parts from CL-OSD and SUPEROSD project * @{ * * @file pios_video.c * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010. * @brief OSD generator, Parts from CL-OSD and SUPEROSD projects * @see The GNU Public License (GPL) Version 3 * ****************************************************************************** */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "pios.h" #ifdef PIOS_INCLUDE_VIDEO // Private methods static void configure_hsync_timers(); static void stop_hsync_timers(); static void reset_hsync_timers(); static void prepare_line(uint32_t line_num); static void flush_spi(); // Private variables extern xSemaphoreHandle osdSemaphore; static const struct pios_video_cfg *dev_cfg; // Define the buffers. // For 256x192 pixel mode: // buffer0_level/buffer0_mask becomes buffer_level; and // buffer1_level/buffer1_mask becomes buffer_mask; // For 192x128 pixel mode, allocations are as the names are written. // divide by 8 because two bytes to a word. // Must be allocated in one block, so it is in a struct. struct _buffers { uint8_t buffer0_level[GRAPHICS_HEIGHT * GRAPHICS_WIDTH]; uint8_t buffer0_mask[GRAPHICS_HEIGHT * GRAPHICS_WIDTH]; uint8_t buffer1_level[GRAPHICS_HEIGHT * GRAPHICS_WIDTH]; uint8_t buffer1_mask[GRAPHICS_HEIGHT * GRAPHICS_WIDTH]; } buffers; // Remove the struct definition (makes it easier to write for.) #define buffer0_level (buffers.buffer0_level) #define buffer0_mask (buffers.buffer0_mask) #define buffer1_level (buffers.buffer1_level) #define buffer1_mask (buffers.buffer1_mask) // We define pointers to each of these buffers. uint8_t *draw_buffer_level; uint8_t *draw_buffer_mask; uint8_t *disp_buffer_level; uint8_t *disp_buffer_mask; volatile uint8_t gLineType = LINE_TYPE_UNKNOWN; volatile uint16_t gActiveLine = 0; volatile uint16_t gActivePixmapLine = 0; volatile uint16_t line = 0; volatile uint16_t Vsync_update = 0; volatile uint16_t Hsync_update = 0; static int16_t m_osdLines = 0; /** * swap_buffers: Swaps the two buffers. Contents in the display * buffer is seen on the output and the display buffer becomes * the new draw buffer. */ void swap_buffers() { // While we could use XOR swap this is more reliable and // dependable and it's only called a few times per second. // Many compliers should optimise these to EXCH instructions. uint8_t *tmp; SWAP_BUFFS(tmp, disp_buffer_mask, draw_buffer_mask); SWAP_BUFFS(tmp, disp_buffer_level, draw_buffer_level); } bool PIOS_Hsync_ISR() { // On tenth line prepare data which will start clocking out on GRAPHICS_LINE+1 if (Hsync_update == GRAPHICS_LINE) { prepare_line(0); gActiveLine = 1; } Hsync_update++; return true; } bool PIOS_Vsync_ISR() { static portBASE_TYPE xHigherPriorityTaskWoken; xHigherPriorityTaskWoken = pdFALSE; m_osdLines = gActiveLine; stop_hsync_timers(); // Wait for previous word to clock out of each TIM_Cmd(dev_cfg->pixel_timer.timer, ENABLE); flush_spi(); TIM_Cmd(dev_cfg->pixel_timer.timer, DISABLE); gActiveLine = 0; Hsync_update = 0; Vsync_update++; if (Vsync_update >= 2) { // load second image buffer swap_buffers(); Vsync_update = 0; // trigger redraw every second field xHigherPriorityTaskWoken = xSemaphoreGiveFromISR(osdSemaphore, &xHigherPriorityTaskWoken); } portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); // portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); return xHigherPriorityTaskWoken == pdTRUE; } uint16_t PIOS_Video_GetOSDLines(void) { return m_osdLines; } /** * Stops the pixel clock and ensures it ignores the rising edge. To be used after a * vsync until the first line is to be displayed */ static void stop_hsync_timers() { // This removes the slave mode configuration TIM_Cmd(dev_cfg->pixel_timer.timer, DISABLE); TIM_InternalClockConfig(dev_cfg->pixel_timer.timer); } const struct pios_tim_callbacks px_callback = { .overflow = NULL, .edge = NULL, }; #ifdef PAL const uint32_t period = 10; const uint32_t dc = (10 / 2); #else const uint32_t period = 11; const uint32_t dc = (11 / 2); #endif /** * Reset the timer and configure for next call. Keeps them synced. Ideally this won't even be needed * since I don't think the slave mode gets lost, and this can simply be disable timer */ uint32_t failcount = 0; static void reset_hsync_timers() { // Stop both timers TIM_Cmd(dev_cfg->pixel_timer.timer, DISABLE); uint32_t tim_id; const struct pios_tim_channel *channels = &dev_cfg->hsync_capture; // BUG: This is nuts this line is needed. It simply results in allocating // all the memory but somehow leaving it out breaks the timer functionality. // I do not see how these can be related if (failcount == 0) { if (PIOS_TIM_InitChannels(&tim_id, channels, 1, &px_callback, 0) < 0) { failcount++; } } dev_cfg->pixel_timer.timer->CNT = 0xFFFF - 100; // dc; // Listen to Channel1 (HSYNC) switch (dev_cfg->hsync_capture.timer_chan) { case TIM_Channel_1: TIM_SelectInputTrigger(dev_cfg->pixel_timer.timer, TIM_TS_TI1FP1); break; case TIM_Channel_2: TIM_SelectInputTrigger(dev_cfg->pixel_timer.timer, TIM_TS_TI2FP2); break; default: PIOS_Assert(0); } TIM_SelectSlaveMode(dev_cfg->pixel_timer.timer, TIM_SlaveMode_Trigger); } static void configure_hsync_timers() { // Stop both timers TIM_Cmd(dev_cfg->pixel_timer.timer, DISABLE); // This is overkill but used for consistency. No interrupts used for pixel clock // but this function calls the GPIO_Remap uint32_t tim_id; const struct pios_tim_channel *channels; // Init the channel to output the pixel clock channels = &dev_cfg->pixel_timer; PIOS_TIM_InitChannels(&tim_id, channels, 1, &px_callback, 0); // Init the channel to capture the pulse channels = &dev_cfg->hsync_capture; PIOS_TIM_InitChannels(&tim_id, channels, 1, &px_callback, 0); // Configure the input capture channel TIM_ICInitTypeDef TIM_ICInitStructure; switch (dev_cfg->hsync_capture.timer_chan) { case TIM_Channel_1: TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; break; case TIM_Channel_2: TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; break; default: PIOS_Assert(0); } TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; // TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0; TIM_ICInit(dev_cfg->pixel_timer.timer, &TIM_ICInitStructure); // Set up the channel to output the pixel clock switch (dev_cfg->pixel_timer.timer_chan) { case TIM_Channel_1: TIM_OC1Init(dev_cfg->pixel_timer.timer, &dev_cfg->tim_oc_init); TIM_OC1PreloadConfig(dev_cfg->pixel_timer.timer, TIM_OCPreload_Enable); TIM_SetCompare1(dev_cfg->pixel_timer.timer, dc); break; case TIM_Channel_2: TIM_OC2Init(dev_cfg->pixel_timer.timer, &dev_cfg->tim_oc_init); TIM_OC2PreloadConfig(dev_cfg->pixel_timer.timer, TIM_OCPreload_Enable); TIM_SetCompare2(dev_cfg->pixel_timer.timer, dc); break; case TIM_Channel_3: TIM_OC3Init(dev_cfg->pixel_timer.timer, &dev_cfg->tim_oc_init); TIM_OC3PreloadConfig(dev_cfg->pixel_timer.timer, TIM_OCPreload_Enable); TIM_SetCompare3(dev_cfg->pixel_timer.timer, dc); break; case TIM_Channel_4: TIM_OC4Init(dev_cfg->pixel_timer.timer, &dev_cfg->tim_oc_init); TIM_OC4PreloadConfig(dev_cfg->pixel_timer.timer, TIM_OCPreload_Enable); TIM_SetCompare4(dev_cfg->pixel_timer.timer, dc); break; } TIM_ARRPreloadConfig(dev_cfg->pixel_timer.timer, ENABLE); TIM_CtrlPWMOutputs(dev_cfg->pixel_timer.timer, ENABLE); // This shouldn't be needed as it should come from the config struture. Something // is clobbering that TIM_PrescalerConfig(dev_cfg->pixel_timer.timer, 0, TIM_PSCReloadMode_Immediate); TIM_SetAutoreload(dev_cfg->pixel_timer.timer, period); } DMA_TypeDef *main_dma; DMA_TypeDef *mask_dma; DMA_Stream_TypeDef *main_stream; DMA_Stream_TypeDef *mask_stream; void PIOS_Video_Init(const struct pios_video_cfg *cfg) { dev_cfg = cfg; // store config before enabling interrupt configure_hsync_timers(); /* needed for HW hack */ const GPIO_InitTypeDef initStruct = { .GPIO_Pin = GPIO_Pin_12, .GPIO_Speed = GPIO_Speed_100MHz, .GPIO_Mode = GPIO_Mode_IN, .GPIO_OType = GPIO_OType_PP, .GPIO_PuPd = GPIO_PuPd_NOPULL }; GPIO_Init(GPIOC, &initStruct); /* SPI3 - MASKBUFFER */ GPIO_Init(cfg->mask.sclk.gpio, (GPIO_InitTypeDef *)&(cfg->mask.sclk.init)); GPIO_Init(cfg->mask.miso.gpio, (GPIO_InitTypeDef *)&(cfg->mask.miso.init)); /* SPI1 SLAVE FRAMEBUFFER */ GPIO_Init(cfg->level.sclk.gpio, (GPIO_InitTypeDef *)&(cfg->level.sclk.init)); GPIO_Init(cfg->level.miso.gpio, (GPIO_InitTypeDef *)&(cfg->level.miso.init)); if (cfg->mask.remap) { GPIO_PinAFConfig(cfg->mask.sclk.gpio, __builtin_ctz(cfg->mask.sclk.init.GPIO_Pin), cfg->mask.remap); GPIO_PinAFConfig(cfg->mask.miso.gpio, __builtin_ctz(cfg->mask.miso.init.GPIO_Pin), cfg->mask.remap); } if (cfg->level.remap) { GPIO_PinAFConfig(cfg->level.sclk.gpio, __builtin_ctz(cfg->level.sclk.init.GPIO_Pin), cfg->level.remap); GPIO_PinAFConfig(cfg->level.miso.gpio, __builtin_ctz(cfg->level.miso.init.GPIO_Pin), cfg->level.remap); } /* Initialize the SPI block */ SPI_Init(cfg->level.regs, (SPI_InitTypeDef *)&(cfg->level.init)); SPI_Init(cfg->mask.regs, (SPI_InitTypeDef *)&(cfg->mask.init)); /* Enable SPI */ SPI_Cmd(cfg->level.regs, ENABLE); SPI_Cmd(cfg->mask.regs, ENABLE); /* Configure DMA for SPI Tx SLAVE Maskbuffer */ DMA_Cmd(cfg->mask.dma.tx.channel, DISABLE); DMA_Init(cfg->mask.dma.tx.channel, (DMA_InitTypeDef *)&(cfg->mask.dma.tx.init)); /* Configure DMA for SPI Tx SLAVE Framebuffer*/ DMA_Cmd(cfg->level.dma.tx.channel, DISABLE); DMA_Init(cfg->level.dma.tx.channel, (DMA_InitTypeDef *)&(cfg->level.dma.tx.init)); /* Trigger interrupt when for half conversions too to indicate double buffer */ DMA_ITConfig(cfg->level.dma.tx.channel, DMA_IT_TC, ENABLE); /* Configure and clear buffers */ draw_buffer_level = buffer0_level; draw_buffer_mask = buffer0_mask; disp_buffer_level = buffer1_level; disp_buffer_mask = buffer1_mask; memset(disp_buffer_mask, 0, GRAPHICS_WIDTH * GRAPHICS_HEIGHT); memset(disp_buffer_level, 0, GRAPHICS_WIDTH * GRAPHICS_HEIGHT); memset(draw_buffer_mask, 0, GRAPHICS_WIDTH * GRAPHICS_HEIGHT); memset(draw_buffer_level, 0, GRAPHICS_WIDTH * GRAPHICS_HEIGHT); /* Configure DMA interrupt */ NVIC_Init(&cfg->level.dma.irq.init); /* Enable SPI interrupts to DMA */ SPI_I2S_DMACmd(cfg->mask.regs, SPI_I2S_DMAReq_Tx, ENABLE); SPI_I2S_DMACmd(cfg->level.regs, SPI_I2S_DMAReq_Tx, ENABLE); mask_dma = DMA1; main_dma = DMA2; main_stream = cfg->level.dma.tx.channel; mask_stream = cfg->mask.dma.tx.channel; /* Configure the Video Line interrupt */ PIOS_EXTI_Init(cfg->hsync); PIOS_EXTI_Init(cfg->vsync); // set levels to zero PIOS_Servo_Set(0, 0); PIOS_Servo_Set(1, 0); } /** * Prepare the system to watch for a HSYNC pulse to trigger the pixel * clock and clock out the next line */ static void prepare_line(uint32_t line_num) { if (line_num < GRAPHICS_HEIGHT) { uint32_t buf_offset = line_num * GRAPHICS_WIDTH; dev_cfg->pixel_timer.timer->CNT = dc; DMA_ClearFlag(dev_cfg->mask.dma.tx.channel, DMA_FLAG_TCIF7 | DMA_FLAG_HTIF7 | DMA_FLAG_FEIF7 | DMA_FLAG_TEIF7); DMA_ClearFlag(dev_cfg->level.dma.tx.channel, DMA_FLAG_FEIF5 | DMA_FLAG_TEIF5); // Load new line DMA_MemoryTargetConfig(dev_cfg->level.dma.tx.channel, (uint32_t)&disp_buffer_level[buf_offset], DMA_Memory_0); DMA_MemoryTargetConfig(dev_cfg->mask.dma.tx.channel, (uint32_t)&disp_buffer_mask[buf_offset], DMA_Memory_0); // Enable DMA, Slave first DMA_SetCurrDataCounter(dev_cfg->level.dma.tx.channel, BUFFER_LINE_LENGTH); DMA_SetCurrDataCounter(dev_cfg->mask.dma.tx.channel, BUFFER_LINE_LENGTH); SPI_Cmd(dev_cfg->level.regs, ENABLE); SPI_Cmd(dev_cfg->mask.regs, ENABLE); /* Enable SPI interrupts to DMA */ SPI_I2S_DMACmd(dev_cfg->mask.regs, SPI_I2S_DMAReq_Tx, ENABLE); SPI_I2S_DMACmd(dev_cfg->level.regs, SPI_I2S_DMAReq_Tx, ENABLE); DMA_Cmd(dev_cfg->level.dma.tx.channel, ENABLE); DMA_Cmd(dev_cfg->mask.dma.tx.channel, ENABLE); } reset_hsync_timers(); } void PIOS_VIDEO_DMA_Handler(void); void DMA1_Stream7_IRQHandler(void) __attribute__((alias("PIOS_VIDEO_DMA_Handler"))); void DMA2_Stream5_IRQHandler(void) __attribute__((alias("PIOS_VIDEO_DMA_Handler"))); /** * Check both SPI for the stop sequence before disabling them */ static void flush_spi() { bool level_empty = false; bool mask_empty = false; bool level_stopped = false; bool mask_stopped = false; // Can't flush if clock not running while ((dev_cfg->pixel_timer.timer->CR1 & 0x0001) && (!level_stopped | !mask_stopped)) { level_empty |= SPI_I2S_GetFlagStatus(dev_cfg->level.regs, SPI_I2S_FLAG_TXE) == SET; mask_empty |= SPI_I2S_GetFlagStatus(dev_cfg->mask.regs, SPI_I2S_FLAG_TXE) == SET; if (level_empty && !level_stopped) { // && SPI_I2S_GetFlagStatus(dev_cfg->level.regs ,SPI_I2S_FLAG_BSY) == RESET) { SPI_Cmd(dev_cfg->level.regs, DISABLE); level_stopped = true; } if (mask_empty && !mask_stopped) { // && SPI_I2S_GetFlagStatus(dev_cfg->mask.regs ,SPI_I2S_FLAG_BSY) == RESET) { SPI_Cmd(dev_cfg->mask.regs, DISABLE); mask_stopped = true; } } /* uint32_t i = 0; while(SPI_I2S_GetFlagStatus(dev_cfg->level.regs ,SPI_I2S_FLAG_TXE) == RESET && i < 30000) i++; while(SPI_I2S_GetFlagStatus(dev_cfg->mask.regs ,SPI_I2S_FLAG_TXE) == RESET && i < 30000) i++; while(SPI_I2S_GetFlagStatus(dev_cfg->level.regs ,SPI_I2S_FLAG_BSY) == SET && i < 30000) i++; while(SPI_I2S_GetFlagStatus(dev_cfg->mask.regs ,SPI_I2S_FLAG_BSY) == SET && i < 30000) i++;*/ SPI_Cmd(dev_cfg->mask.regs, DISABLE); SPI_Cmd(dev_cfg->level.regs, DISABLE); } /** * @brief Interrupt for half and full buffer transfer */ void PIOS_VIDEO_DMA_Handler(void) { // Handle flags from stream channel if (DMA_GetFlagStatus(dev_cfg->level.dma.tx.channel, DMA_FLAG_TCIF5)) { // whole double buffer filled DMA_ClearFlag(dev_cfg->level.dma.tx.channel, DMA_FLAG_TCIF5); if (gActiveLine < GRAPHICS_HEIGHT) { flush_spi(); stop_hsync_timers(); dev_cfg->pixel_timer.timer->CNT = dc; prepare_line(gActiveLine); } else if (gActiveLine >= GRAPHICS_HEIGHT) { // last line completed flush_spi(); stop_hsync_timers(); // STOP DMA, master first DMA_Cmd(dev_cfg->mask.dma.tx.channel, DISABLE); DMA_Cmd(dev_cfg->level.dma.tx.channel, DISABLE); } gActiveLine++; } else if (DMA_GetFlagStatus(dev_cfg->level.dma.tx.channel, DMA_FLAG_HTIF5)) { DMA_ClearFlag(dev_cfg->level.dma.tx.channel, DMA_FLAG_HTIF5); } else {} } #endif /* PIOS_INCLUDE_VIDEO */