blob: 7e5894b392263f9c139d54c420b21d6adb48dc19 [file]
#include <stdint.h>
#include "spi-st7272a.h"
#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 {
// lo and hi res screen back buffers
Surface __fb_hires((uint8_t *)&__fb_start, PixelFormat::RGB, Size(320, 240));
Surface __fb_lores((uint8_t *)&__fb_start, PixelFormat::RGB, Size(160, 120));
ScreenMode mode = ScreenMode::lores;
bool needs_render = false;
uint32_t flip_cycle_count;
void init() {
// 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;
}
void set_screen_mode(ScreenMode new_mode) {
mode = new_mode;
screen = mode == ScreenMode::hires ? __fb_hires : __fb_lores;
}
void flip(const Surface &source) {
__disable_irq(); // Set PRIMASK
uint8_t *s = (uint8_t *)source.data;
uint32_t *d = (uint32_t *)(&__ltdc_start);
if(mode == ScreenMode::lores) {
// pixel double the framebuffer to the ltdc buffer
for(uint8_t y = 0; y < 120; y++) {
// pixel double the current row while converting from RGB to RGB565
for(uint8_t x = 0; x < 160; x++) {
uint16_t c = ((*s++ & 0b11111000) << 8) | ((*s++ & 0b11111100) << 3) | ((*s++ & 0b11111000) >> 3);
*(d) = c | (c << 16); *(d + 160) = c | (c << 16);
d++;
}
d += 160; // skip the doubled row
}
}else{
static uint8_t t = 0;
t++;
bool dither_spacial = false;
uint32_t scc = DWT->CYCCNT;
if(dither_spacial) {
int8_t dither[4][4] = {
{ 0, 8, 2, 10},
{12, 4, 14, 6},
{ 3, 11, 1, 9},
{15, 7, 13, 5}
};
for(uint16_t y = 0; y < 240; y++) {
uint8_t *pd = dither[y & 0b11];
for(uint16_t x = 0; x < 320; x+=4) {
int8_t dt = x;// + t;
uint32_t dither = 0;// (pd[(dt + 1) & 0b11] << 24) | (pd[(dt + 0) & 0b11] << 16) | (pd[(dt + 3) & 0b11] << 8) | (pd[(dt + 2) & 0b11]);
uint32_t red = ((*(s + 3)) << 24) | ((*(s + 0)) << 16) | ((*(s + 9) << 8)) | (*(s + 6));
uint32_t green = ((*(s + 4)) << 24) | ((*(s + 1)) << 16) | ((*(s + 10) << 8)) | (*(s + 7));
uint32_t blue = ((*(s + 5)) << 24) | ((*(s + 2)) << 16) | ((*(s + 11) << 8)) | (*(s + 8));
red = __UQADD8( red, dither);
green = __UQADD8(green, dither);
blue = __UQADD8( blue, dither);
uint32_t p12 =
((red & 0xf8000000) >> 0) | ((green & 0xfc000000) >> 5) | ((blue & 0xf8000000) >> 11) |
((red & 0x00f80000) >> 8) | ((green & 0x00fc0000) >> 13) | ((blue & 0x00f80000) >> 19);
uint32_t p34 =
((red & 0x0000f800) << 16) | ((green & 0x0000fc00) << 11) | ((blue & 0x0000f800) << 5) |
((red & 0x000000f8) << 8) | ((green & 0x000000fc) << 3) | ((blue & 0x000000f8) >> 3);
*d = p12;
d++;
*d = p34;
d++;
s += 12;
}
}
} else {
uint32_t c = (320 * 240) >> 1;
while(c--) {
uint16_t c1 = ((*s++ & 0b11111000) << 8) | ((*s++ & 0b11111100) << 3) | ((*s++ & 0b11111000) >> 3);
uint16_t c2 = ((*s++ & 0b11111000) << 8) | ((*s++ & 0b11111100) << 3) | ((*s++ & 0b11111000) >> 3);
*d++ = c2 << 16 | c1;
}
}
flip_cycle_count = DWT->CYCCNT - scc;
}
__enable_irq(); // Clear PRIMASK
// 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), 320 * 240 * 2);
}
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_RGB565;
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 * 2) << LTDC_LxCFBLR_CFBP_Pos) | (((320 * 2) + 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;
}
}