From deccc720e35198c9d9854789a58a59c4d4ebd38c Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 21 Dec 2021 16:27:46 +0100 Subject: [PATCH] cpu/stm32: add support for LTDC periph --- cpu/stm32/include/periph/cpu_ltdc.h | 96 +++++++++++++ cpu/stm32/include/periph_cpu.h | 1 + cpu/stm32/periph/Kconfig | 6 + cpu/stm32/periph/ltdc.c | 208 ++++++++++++++++++++++++++++ cpu/stm32/stmclk/stmclk_f2f4f7.c | 25 +++- kconfigs/Kconfig.features | 5 + 6 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 cpu/stm32/include/periph/cpu_ltdc.h create mode 100644 cpu/stm32/periph/ltdc.c diff --git a/cpu/stm32/include/periph/cpu_ltdc.h b/cpu/stm32/include/periph/cpu_ltdc.h new file mode 100644 index 0000000000..02c0c6b64c --- /dev/null +++ b/cpu/stm32/include/periph/cpu_ltdc.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_stm32 + * @{ + * + * @file + * @brief LTDC CPU specific definitions for the STM32 family + * + * @author Alexandre Abadie + */ + +#ifndef PERIPH_CPU_LTDC_H +#define PERIPH_CPU_LTDC_H + +#include + +#include "periph/cpu_gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LTDC GPIO configuration + */ +typedef struct { + gpio_t pin; /**< GPIO pin */ + gpio_af_t af; /**< Alternate function */ +} ltdc_gpio_t; + +/** + * @brief LTDC Peripheral configuration + */ +typedef struct { + uint8_t bus; /**< APB bus */ + uint32_t rcc_mask; /**< bit in clock enable register */ + ltdc_gpio_t clk_pin; /**< CLK pin */ + ltdc_gpio_t de_pin; /**< Data enable pin */ + ltdc_gpio_t hsync_pin; /**< Horizontal synchronization pin */ + ltdc_gpio_t vsync_pin; /**< Vertical synchronization pin */ + ltdc_gpio_t r_pin[8]; /**< Red color pins */ + ltdc_gpio_t g_pin[8]; /**< Green color pins */ + ltdc_gpio_t b_pin[8]; /**< Blue color pins */ + uint8_t hsync; /**< Horizontal synchronization */ + uint8_t vsync; /**< Vertical synchronization */ + uint8_t hbp; /**< Horizontal back porch */ + uint8_t hfp; /**< Horizontal front porch */ + uint8_t vbp; /**< Vertical back porch */ + uint8_t vfp; /**< Vertical front porch */ +} ltdc_conf_t; + +/** + * @brief Initialize the LTDC (LCD-TFT Display Controller) peripheral + */ +void ltdc_init(void); + +/** + * @brief Clear the LTDC display + */ +void ltdc_clear(void); + +/** + * @brief Map a buffer of RGB565 (16bit depth) colors to the display + * + * @param[in] x1 horizontal start position + * @param[in] x2 horizontal end position (included) + * @param[in] y1 vertical start position + * @param[in] y2 vertical end position (included) + * @param[in] color the color buffer + */ +void ltdc_map(uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2, const uint16_t *color); + +/** + * @brief Fill a region of the display with the same color + * + * @param[in] x1 horizontal start position + * @param[in] x2 horizontal end position (included) + * @param[in] y1 vertical start position + * @param[in] y2 vertical end position (included) + * @param[in] color the color value in RGB565 format (16bit depth) + */ +void ltdc_fill(uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2, const uint16_t color); + +#ifdef __cplusplus +} +#endif + +#endif /* PERIPH_CPU_LTDC_H */ +/** @} */ diff --git a/cpu/stm32/include/periph_cpu.h b/cpu/stm32/include/periph_cpu.h index c2c8568e5f..de340806c7 100644 --- a/cpu/stm32/include/periph_cpu.h +++ b/cpu/stm32/include/periph_cpu.h @@ -63,6 +63,7 @@ #include "periph/cpu_eth.h" #include "periph/cpu_gpio.h" #include "periph/cpu_i2c.h" +#include "periph/cpu_ltdc.h" #include "periph/cpu_pm.h" #include "periph/cpu_pwm.h" #include "periph/cpu_qdec.h" diff --git a/cpu/stm32/periph/Kconfig b/cpu/stm32/periph/Kconfig index 5cea7a9c59..e0d24cc87d 100644 --- a/cpu/stm32/periph/Kconfig +++ b/cpu/stm32/periph/Kconfig @@ -34,3 +34,9 @@ config MODULE_PERIPH_ADC select MODULE_ZTIMER if HAS_CPU_STM32F3 || HAS_CPU_STM32L4 || HAS_CPU_STM32WL select MODULE_ZTIMER_MSEC if HAS_CPU_STM32F3 || HAS_CPU_STM32L4 || HAS_CPU_STM32WL select MODULE_PERIPH_COMMON + +config MODULE_PERIPH_LTDC + bool "LTDC peripheral driver" + depends on HAS_PERIPH_LTDC + help + STM32 LCD-TFT Display controller diff --git a/cpu/stm32/periph/ltdc.c b/cpu/stm32/periph/ltdc.c new file mode 100644 index 0000000000..4b676cd11e --- /dev/null +++ b/cpu/stm32/periph/ltdc.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_stm32 + * @{ + * + * @file + * @brief Low-level LTDC (LCD-TFT Display controller) driver implementation + * + * @author Alexandre Abadie + * + * @} + */ + +#include +#include +#include +#include + +#include "cpu.h" +#include "board.h" +#include "periph/gpio.h" +#include "periph_conf.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#ifndef LCD_SCREEN_WIDTH +#define LCD_SCREEN_WIDTH 480 +#endif + +#ifndef LCD_SCREEN_HEIGHT +#define LCD_SCREEN_HEIGHT 272 +#endif + +#define LTDC_BLENDING_FACTOR1_CA 0x00000400U +#define LTDC_BLENDING_FACTOR2_CA 0x00000005U + +typedef struct { + uint32_t hsync; + uint32_t vsync; + uint32_t acc_hbp; + uint32_t acc_vbp; + uint32_t acc_active_h; + uint32_t acc_active_w; + uint32_t height; + uint32_t width; +} ltdc_display_conf_t; + +/* allocate array of 16bit pixels for the framebuffer (TODO: use external SRAM via FMC) */ +static uint16_t _ltdc_frame_buffer[LCD_SCREEN_WIDTH * LCD_SCREEN_HEIGHT]; + +static void _init_gpio(void) +{ + gpio_init(ltdc_config.clk_pin.pin, GPIO_OUT); + gpio_init_af(ltdc_config.clk_pin.pin, ltdc_config.clk_pin.af); + gpio_init(ltdc_config.de_pin.pin, GPIO_OUT); + gpio_init_af(ltdc_config.de_pin.pin, ltdc_config.de_pin.af); + gpio_init(ltdc_config.hsync_pin.pin, GPIO_OUT); + gpio_init_af(ltdc_config.hsync_pin.pin, ltdc_config.hsync_pin.af); + gpio_init(ltdc_config.vsync_pin.pin, GPIO_OUT); + gpio_init_af(ltdc_config.vsync_pin.pin, ltdc_config.vsync_pin.af); + + for (uint8_t pin = 0; pin < 8; ++pin) { + if (!gpio_is_valid(ltdc_config.r_pin[pin].pin)) { + continue; + } + gpio_init(ltdc_config.r_pin[pin].pin, GPIO_OUT); + gpio_init_af(ltdc_config.r_pin[pin].pin, ltdc_config.r_pin[pin].af); + gpio_init(ltdc_config.g_pin[pin].pin, GPIO_OUT); + gpio_init_af(ltdc_config.g_pin[pin].pin, ltdc_config.g_pin[pin].af); + gpio_init(ltdc_config.b_pin[pin].pin, GPIO_OUT); + gpio_init_af(ltdc_config.b_pin[pin].pin, ltdc_config.b_pin[pin].af); + } +} + +static void _configure_ltdc(void) +{ + uint32_t tmp; + + const ltdc_display_conf_t ltdc_display_config = { + .hsync = ltdc_config.hsync - 1, + .vsync = ltdc_config.vsync - 1, + .acc_hbp = ltdc_config.hsync + ltdc_config.hbp - 1, + .acc_vbp = ltdc_config.vsync + ltdc_config.vbp - 1, + .acc_active_h = LCD_SCREEN_HEIGHT + ltdc_config.vsync + ltdc_config.vbp - 1, + .acc_active_w = LCD_SCREEN_WIDTH + ltdc_config.hsync + ltdc_config.hbp - 1, + .height = LCD_SCREEN_HEIGHT + ltdc_config.vsync + ltdc_config.vbp + ltdc_config.vfp - 1, + .width = LCD_SCREEN_WIDTH + ltdc_config.hsync + ltdc_config.hbp + ltdc_config.hfp - 1, + }; + + /* Configure the HS, VS, DE and PC polarity: all active low*/ + LTDC->GCR &= ~(LTDC_GCR_HSPOL | LTDC_GCR_VSPOL | LTDC_GCR_DEPOL | LTDC_GCR_PCPOL); + + /* Set Synchronization size */ + LTDC->SSCR &= ~(LTDC_SSCR_VSH | LTDC_SSCR_HSW); + tmp = (ltdc_display_config.hsync << 16U); + LTDC->SSCR |= (tmp | ltdc_display_config.vsync); + + /* Set Accumulated Back porch */ + LTDC->BPCR &= ~(LTDC_BPCR_AVBP | LTDC_BPCR_AHBP); + tmp = (ltdc_display_config.acc_hbp << 16U); + LTDC->BPCR |= (tmp | ltdc_display_config.acc_vbp); + + /* Set Accumulated Active Width */ + LTDC->AWCR &= ~(LTDC_AWCR_AAH | LTDC_AWCR_AAW); + tmp = (ltdc_display_config.acc_active_w << 16U); + LTDC->AWCR |= (tmp | ltdc_display_config.acc_active_h); + + /* Set Total Width */ + LTDC->TWCR &= ~(LTDC_TWCR_TOTALH | LTDC_TWCR_TOTALW); + tmp = (ltdc_display_config.width << 16U); + LTDC->TWCR |= (tmp | ltdc_display_config.height); + + /* Set the background color value: black */ + LTDC->BCCR &= ~(LTDC_BCCR_BCBLUE | LTDC_BCCR_BCGREEN | LTDC_BCCR_BCRED); + + /* Enable LTDC */ + LTDC->GCR |= LTDC_GCR_LTDCEN; +} + +static void _configure_ltdc_layer(void) +{ + uint32_t tmp; + + /* Configure the horizontal start and stop position */ + tmp = ((LCD_SCREEN_WIDTH + ((LTDC->BPCR & LTDC_BPCR_AHBP) >> 16U)) << 16U); + LTDC_Layer1->WHPCR &= ~(LTDC_LxWHPCR_WHSTPOS | LTDC_LxWHPCR_WHSPPOS); + LTDC_Layer1->WHPCR = ((0 + ((LTDC->BPCR & LTDC_BPCR_AHBP) >> 16U) + 1U) | tmp); + + /* Configure the vertical start and stop position */ + tmp = ((LCD_SCREEN_HEIGHT + (LTDC->BPCR & LTDC_BPCR_AVBP)) << 16U); + LTDC_Layer1->WVPCR &= ~(LTDC_LxWVPCR_WVSTPOS | LTDC_LxWVPCR_WVSPPOS); + LTDC_Layer1->WVPCR = ((0 + (LTDC->BPCR & LTDC_BPCR_AVBP) + 1U) | tmp); + + /* Set the pixel format: RGB565 (16bit) */ + LTDC_Layer1->PFCR &= ~(LTDC_LxPFCR_PF); + LTDC_Layer1->PFCR = 2; + + /* Configure the default color values: all black */ + LTDC_Layer1->DCCR &= ~(LTDC_LxDCCR_DCBLUE | LTDC_LxDCCR_DCGREEN | LTDC_LxDCCR_DCRED | LTDC_LxDCCR_DCALPHA); + + /* Set the constant alpha value: fully opaque */ + LTDC_Layer1->CACR &= ~(LTDC_LxCACR_CONSTA); + LTDC_Layer1->CACR = 255; + + /* Set the blending factors */ + LTDC_Layer1->BFCR &= ~(LTDC_LxBFCR_BF2 | LTDC_LxBFCR_BF1); + LTDC_Layer1->BFCR = (LTDC_BLENDING_FACTOR1_CA | LTDC_BLENDING_FACTOR2_CA); + + /* Configure the color frame buffer start address */ + LTDC_Layer1->CFBAR &= ~(LTDC_LxCFBAR_CFBADD); + LTDC_Layer1->CFBAR = (uint32_t)_ltdc_frame_buffer; + + /* Configure the color frame buffer pitch in byte */ + LTDC_Layer1->CFBLR &= ~(LTDC_LxCFBLR_CFBLL | LTDC_LxCFBLR_CFBP); + LTDC_Layer1->CFBLR = (((LCD_SCREEN_WIDTH * 2) << 16U) | ((LCD_SCREEN_WIDTH * 2) + 3U)); + /* Configure the frame buffer line number */ + LTDC_Layer1->CFBLNR &= ~(LTDC_LxCFBLNR_CFBLNBR); + LTDC_Layer1->CFBLNR = LCD_SCREEN_HEIGHT; + + /* Enable LTDC_Layer by setting LEN bit */ + LTDC_Layer1->CR |= (uint32_t)LTDC_LxCR_LEN; +} + +void ltdc_init(void) +{ + DEBUG("[ltdc] init: initializing device\n"); + + periph_clk_en(ltdc_config.bus, ltdc_config.rcc_mask); + + _init_gpio(); + _configure_ltdc(); + _configure_ltdc_layer(); +} + +void ltdc_clear(void) +{ + memset(_ltdc_frame_buffer, 0, LCD_SCREEN_WIDTH * LCD_SCREEN_HEIGHT * sizeof(uint16_t)); + LTDC->SRCR = LTDC_SRCR_IMR; +} + +void ltdc_map(uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2, const uint16_t *color) +{ + for (uint16_t y = y1; y <= y2; y++) { + for (uint16_t x = x1; x <= x2; x++) { + *(_ltdc_frame_buffer + (x + y * LCD_SCREEN_WIDTH)) = *color++; + } + } + LTDC->SRCR = LTDC_SRCR_IMR; +} + +void ltdc_fill(uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2, const uint16_t color) +{ + for (uint16_t y = y1; y <= y2; y++) { + for (uint16_t x = x1; x <= x2; x++) { + *(_ltdc_frame_buffer + (x + y * LCD_SCREEN_WIDTH)) = color; + } + } + + LTDC->SRCR = LTDC_SRCR_IMR; +} diff --git a/cpu/stm32/stmclk/stmclk_f2f4f7.c b/cpu/stm32/stmclk/stmclk_f2f4f7.c index af2912a49a..64d05cd607 100644 --- a/cpu/stm32/stmclk/stmclk_f2f4f7.c +++ b/cpu/stm32/stmclk/stmclk_f2f4f7.c @@ -92,6 +92,13 @@ #error No suitable 48MHz found, USB will not work #endif +/* PLLSAI is enabled when LTDC is used */ +#if IS_USED(MODULE_PERIPH_LTDC) +#define CLOCK_REQUIRE_PLLSAIR 1 +#else +#define CLOCK_REQUIRE_PLLSAIR 0 +#endif + /* PLLI2S configuration: the following parameters configure a 48MHz I2S clock with HSE (8MHz) or HSI (16MHz) as PLL input clock */ #ifndef CONFIG_CLOCK_PLLI2S_M @@ -175,7 +182,7 @@ #define CONFIG_CLOCK_PLLSAI_Q (8) /* SAI clock, 48MHz by default */ #endif #ifndef CONFIG_CLOCK_PLLSAI_R -#define CONFIG_CLOCK_PLLSAI_R (8) /* LCD clock, 48MHz by default */ +#define CONFIG_CLOCK_PLLSAI_R (4) /* LCD clock, 48MHz by default */ #endif #if defined(RCC_PLLSAICFGR_PLLSAIM_Pos) @@ -455,7 +462,7 @@ #endif /* Check whether PLLSAI must be enabled */ -#if IS_ACTIVE(CLOCK_REQUIRE_PLLSAIP) +#if IS_ACTIVE(CLOCK_REQUIRE_PLLSAIP) || IS_ACTIVE(CLOCK_REQUIRE_PLLSAIR) #define CLOCK_ENABLE_PLLSAI 1 #else #define CLOCK_ENABLE_PLLSAI 0 @@ -556,6 +563,20 @@ void stmclk_init_sysclk(void) } #endif +#if defined(RCC_DCKCFGR1_PLLSAIDIVR) + if (IS_USED(MODULE_PERIPH_LTDC)) { + RCC->DCKCFGR1 &= ~RCC_DCKCFGR1_PLLSAIDIVR; + RCC->DCKCFGR1 |= RCC_DCKCFGR1_PLLSAIDIVR_0; /* Divide by 4 */ + } +#endif + +#if defined(RCC_DCKCFGR_PLLSAIDIVR) + if (IS_USED(MODULE_PERIPH_LTDC)) { + RCC->DCKCFGR &= ~RCC_DCKCFGR_PLLSAIDIVR; + RCC->DCKCFGR |= RCC_DCKCFGR_PLLSAIDIVR_0; /* Divide by 4 */ + } +#endif + #if defined(RCC_CR_PLLSAION) if (IS_ACTIVE(CLOCK_ENABLE_PLLSAI)) { RCC->PLLSAICFGR = (PLLSAI_M | PLLSAI_N | PLLSAI_P | PLLSAI_Q | PLLSAI_R); diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index 6743fae381..3ee21fb9ee 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -206,6 +206,11 @@ config HAS_PERIPH_LPUART help Indicates that a low-power UART peripheral is present. +config HAS_PERIPH_LTDC + bool + help + Indicates that a LTDC peripheral is present. + config HAS_PERIPH_MCG bool help