blob: 7633b0122fd05bd44660f832bc143bef286d2610 [file] [edit]
#include <stdint.h>
#include <cstring>
#include "spi-st7272a.h"
#include "32blit.hpp"
#include "display.hpp"
extern char __ltdc_start, __ltdc_end;
extern char __fb_start, __fb_end;
void LTDC_IRQHandler() {
// check that interrupt triggered was line end event
if(LTDC->ISR & LTDC_ISR_LIF)
{
// disable line interrupt and clear flag
LTDC->IER &= ~LTDC_IT_LI;
LTDC->ICR = LTDC_FLAG_LI;
// flip the framebuffer to the ltdc buffer and request
// a new frame to be rendered
display::flip(blit::screen);
display::needs_render = true;
}
}
namespace display {
void update_ltdc_for_mode();
// lo and hi res screen back buffers
Surface __fb_hires((uint8_t *)&__fb_start, PixelFormat::RGB, Size(320, 240));
Surface __fb_hires_pal((uint8_t *)&__fb_start, PixelFormat::P, Size(320, 240));
Surface __fb_lores((uint8_t *)&__fb_start, PixelFormat::RGB, Size(160, 120));
Pen palette[256];
ScreenMode mode = ScreenMode::lores;
bool needs_render = false;
int palette_needs_update = 0;
uint8_t palette_update_delay = 0;
void init() {
__fb_hires_pal.palette = palette;
// TODO: replace interrupt setup with non HAL method
HAL_NVIC_SetPriority(LTDC_IRQn, 4, 4);
HAL_NVIC_EnableIRQ(LTDC_IRQn);
ltdc_init();
screen_init();
enable_vblank_interrupt();
}
void enable_vblank_interrupt() {
// trigger interrupt when screen refresh reaches the 252nd scanline
LTDC->LIPCR = 252;
// enable line interrupt
LTDC->IER |= LTDC_IT_LI;
display::needs_render = false;
}
Surface &set_screen_mode(ScreenMode new_mode) {
mode = new_mode;
switch(mode) {
case ScreenMode::lores:
screen = __fb_lores;
break;
case ScreenMode::hires:
screen = __fb_hires;
break;
case ScreenMode::hires_palette:
screen = __fb_hires_pal;
break;
}
update_ltdc_for_mode();
return screen;
}
void set_screen_palette(const Pen *colours, int num_cols) {
memcpy(palette, colours, num_cols * sizeof(blit::Pen));
palette_update_delay = 1;
palette_needs_update = num_cols;
}
void dma2d_hires_flip(const Surface &source) {
SCB_CleanInvalidateDCache_by_Addr((uint32_t *)(source.data), 320 * 240 * 3);
// set the transform type (clear bits 17..16 of control register)
DMA2D->CR &= 0xfcff;
// set target pixel format (clear bits 3..0 of foreground format register)
DMA2D->FGPFCCR &= 0xfff0;
// set source buffer address
DMA2D->FGMAR = (uintptr_t)source.data;
// set target pixel format (clear bits 3..0 of output format register)
DMA2D->OPFCCR &= 0xfff0;
// set target buffer address
DMA2D->OMAR = (uintptr_t)&__ltdc_start;
// set the number of pixels per line and number of lines
DMA2D->NLR = (320 << 16) | (240);
// set the source offset
DMA2D->FGOR = 0;
// set the output offset
DMA2D->OOR = 0;
// trigger start of dma2d transfer
DMA2D->CR |= DMA2D_CR_START;
// wait for transfer to complete
while(DMA2D->CR & DMA2D_CR_START) {
// never gets here!
}
}
void dma2d_lores_flip(const Surface &source) {
// this does not work... yet!
/*SCB_CleanInvalidateDCache_by_Addr((uint32_t *)(source.data), 320 * 240 * 3);
// set the transform type (clear bits 17..16 of control register)
DMA2D->CR &= 0xfcff;
// set target pixel format (clear bits 3..0 of foreground format register)
DMA2D->FGPFCCR &= 0xfff0;
// set source buffer address
DMA2D->FGMAR = source.data;
// set target pixel format (clear bits 3..0 of output format register)
DMA2D->OPFCCR &= 0xfff0;
// set target buffer address
DMA2D->OMAR = (&__ltdc_start) + (320 * 120 * 3) + 3; // halfway point
// set the number of pixels per line and number of lines
DMA2D->NLR = (1 << 16) | (320 * 240 * 2);
// set the source offset
DMA2D->FGOR = 1;
// set the output offset
DMA2D->OOR = 1;
// trigger start of dma2d transfer
DMA2D->CR |= DMA2D_CR_START;
// wait for transfer to complete
while(DMA2D->CR & DMA2D_CR_START) {
// never gets here!
}*/
}
void flip(const Surface &source) {
static uint32_t flip_time = 0;
// TODO: both flip implementations can we done via DMA2D which will save
// a heap of CPU time.
uint32_t ltdc_buffer_size = 320 * 240 * 3;
if(mode == ScreenMode::lores) {
//dma2d_lores_flip(source);
//screen.text(std::to_string(flip_time), minimal_font, Point(100,40));
uint32_t flip_start = DWT->CYCCNT;
uint8_t *s = (uint8_t *)source.data;
uint8_t *d = (uint8_t *)(&__ltdc_start);
// pixel double the framebuffer to the ltdc buffer
for(uint8_t y = 0; y < 120; y++) {
// pixel double the current row horizontally
for(uint8_t x = 0; x < 160; x++) {
*d++ = *(s + 0);
*d++ = *(s + 1);
*d++ = *(s + 2);
*d++ = *(s + 0);
*d++ = *(s + 1);
*d++ = *(s + 2);
s += 3;
}
// copy the previous row to pixel double vertically
uint32_t *cd = (uint32_t *)d;
uint32_t *cs = (uint32_t *)(d - (320 * 3));
while(cs < (uint32_t *)d) {
*cd++ = *cs++;
}
d += 320 * 3;
}
uint32_t flip_end = DWT->CYCCNT;
flip_time = ((flip_end - flip_start) / 1000) * 1000;
} else if(mode == ScreenMode::hires) {
//screen.text(std::to_string(flip_time), minimal_font, Point(140,40));
//uint32_t flip_start = DWT->CYCCNT;
// perform flip with dma2d transfer
dma2d_hires_flip(source);
/*
// alternative soft implementation
// copy the framebuffer data into the ltdc buffer, originally this
// was done via memcpy but implementing it as a 32-bit copy loop
// was much faster.
uint32_t *s = (uint32_t *)source.data;
uint32_t *d = (uint32_t *)(&__ltdc_start);
uint32_t c = ltdc_buffer_size >> 2;
while(c--) {
*d++ = *s++;
}
*/
//uint32_t flip_end = DWT->CYCCNT;
//flip_time = ((flip_end - flip_start) / 1000) * 1000;
} else {
// paletted
if(palette_needs_update && palette_update_delay-- == 0) {
for(int i = 0; i < palette_needs_update; i++) {
LTDC_Layer1->CLUTWR = (i << 24) | (palette[i].b << 16) | (palette[i].g << 8) | palette[i].r;
}
LTDC->SRCR = LTDC_SRCR_IMR;
palette_needs_update = 0;
}
uint32_t *s = (uint32_t *)source.data;
uint32_t *d = (uint32_t *)(&__ltdc_start + 320 * 240 * 2);
uint32_t c = (320 * 240) >> 2;
while(c--) {
*d++ = *s++;
}
}
// since the ltdc hardware pulls frame data directly over the memory bus
// without passing through the mcu's cache layer we must invalidate the
// affected area to ensure that all data has been committed into ram
SCB_CleanInvalidateDCache_by_Addr((uint32_t *)(&__ltdc_start), ltdc_buffer_size);
}
void screen_init() {
ST7272A_RESET();
// st7272a_set_bgr();
}
// configure ltdc peripheral setting up the clocks, pin states, panel
// parameters, and layers
void ltdc_init() {
// enable ltdc clock
__HAL_RCC_LTDC_CLK_ENABLE();
// configure the panel timings and signal polarity
LTDC->GCR &= ~LTDC_GCR_PCPOL; // synch signal polarity setting
LTDC->SSCR = (3 << 16) | 3; // hsync and vsync
LTDC->BPCR = (45 << 16) | 14; // accumulated horizonal and vertical back porch
LTDC->AWCR = (366 << 16) | 255; // accumulated active width and height
LTDC->TWCR = (374 << 16) | 257; // accumulated total width and height
// enable ltdc transfer and fifo underrun error interrupts
LTDC->IER = LTDC_IT_TE | LTDC_IT_FU;
// configure ltdc layer
LTDC_Layer1->WHPCR &= ~(LTDC_LxWHPCR_WHSTPOS | LTDC_LxWHPCR_WHSPPOS);
LTDC_Layer1->WHPCR = ((1 + ((LTDC->BPCR & LTDC_BPCR_AHBP) >> 16U) + 1U) | ((321 + ((LTDC->BPCR & LTDC_BPCR_AHBP) >> 16U)) << 16U));
LTDC_Layer1->WVPCR &= ~(LTDC_LxWVPCR_WVSTPOS | LTDC_LxWVPCR_WVSPPOS);
LTDC_Layer1->WVPCR = ((0 + (LTDC->BPCR & LTDC_BPCR_AVBP) + 1U) | ((241 + (LTDC->BPCR & LTDC_BPCR_AVBP)) << 16U));
LTDC_Layer1->PFCR = LTDC_PIXEL_FORMAT_RGB888;
LTDC_Layer1->DCCR = 0xff000000; // layer default color (back, 100% alpha)
LTDC_Layer1->CFBAR = (uint32_t)&__ltdc_start; // frame buffer start address
LTDC_Layer1->CFBLR = ((320 * 3) << LTDC_LxCFBLR_CFBP_Pos) | (((320 * 3) + 2) << LTDC_LxCFBLR_CFBLL_Pos); // frame buffer line length and pitch
LTDC_Layer1->CFBLNR = 240; // line count
LTDC_Layer1->CACR = 255; // alpha
LTDC_Layer1->CR |= LTDC_LxCR_LEN; // enable layer
// reload shadow registers
LTDC->SRCR = LTDC_SRCR_IMR;
// enable LTDC
LTDC->GCR |= LTDC_GCR_LTDCEN;
}
void update_ltdc_for_mode() {
if(mode == ScreenMode::hires_palette) {
LTDC_Layer1->PFCR = LTDC_PIXEL_FORMAT_L8;
LTDC_Layer1->CFBAR = (uint32_t)&__ltdc_start + 320 * 240 * 2; // frame buffer start address
LTDC_Layer1->CR |= LTDC_LxCR_CLUTEN;
} else {
LTDC_Layer1->PFCR = LTDC_PIXEL_FORMAT_RGB888;
LTDC_Layer1->CFBAR = (uint32_t)&__ltdc_start; // frame buffer start address
LTDC_Layer1->CR &= ~LTDC_LxCR_CLUTEN;
}
LTDC->SRCR = LTDC_SRCR_IMR;
}
}