FreeTRX/MDUV380_firmware/application/source/io/display.c
Marcus Kida 7f5f3ab06f Screen dimming: add a 15% idle-dim level
Add SCREEN_DIM_15 (Off / 100 / 50 / 30 / 15). The value now needs 3 bits, so
repurpose the spare BIT_UNUSED_2 as BIT_SCREEN_DIM_2; existing codeplugs (bit 2
clear) keep their level, no reset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 08:16:04 +02:00

480 lines
13 KiB
C

/*
* Copyright (C) 2019 Kai Ludwig, DG4KLU
* Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
* Daniel Caujolle-Bert, F1RMB
*
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
* incorporation in a commercial product or incorporation into a product or project which allows commercial use.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <FreeRTOS.h>
#include "hardware/HX8353E.h"
#include "io/display.h"
#include "functions/settings.h"
#include "interfaces/gpio.h"
#include "main.h"
#include "user_interface/uiGlobals.h"
uint8_t displayLCD_Type = 1;
static bool displayIsInverseVideo = false;
#if defined(PLATFORM_VARIANT_DM1701)
static void displayClearScreenBlankLines(bool isInverted);
#endif
void displayWriteCmd(uint8_t cmd)
{
*((volatile uint8_t*) LCD_FSMC_ADDR_COMMAND) = cmd;
}
void displayWriteData(uint8_t val)
{
*((volatile uint8_t*) LCD_FSMC_ADDR_DATA) = val;
}
void displayWriteCmds(uint8_t cmd, size_t len, uint8_t opts[])
{
*((volatile uint8_t*) LCD_FSMC_ADDR_COMMAND) = cmd;
for(volatile int x = 0; x < 1; x++);//Display seems to need a tiny delay to respond to the data
if (len)
{
for (size_t i = 0; i < len; i++)
{
*((volatile uint8_t*) LCD_FSMC_ADDR_DATA) = opts[i];
for(volatile int x = 0; x < 1; x++);//Display seems to need a tiny delay to respond to the data
}
}
}
void displaySetInvertedState(bool isInverted)
{
if (displayIsInverseVideo != isInverted)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
displayIsInverseVideo = isInverted;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
GPIO_InitStruct.Pin = LCD_D0_Pin | LCD_D1_Pin | LCD_D2_Pin | LCD_D3_Pin;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_D4_Pin | LCD_D5_Pin | LCD_D6_Pin | LCD_D7_Pin;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);
osDelay(10);
displayWriteCmd(displayIsInverseVideo ? HX8583_CMD_INVON : HX8583_CMD_INVOFF);
osDelay(10);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
*((volatile uint8_t*) LCD_FSMC_ADDR_DATA) = 0;// write 0 to the display pins , to pull them all low, so keyboard reads don't need to
#if defined(PLATFORM_VARIANT_DM1701)
displayClearScreenBlankLines(displayIsInverseVideo);
#endif
displaySetInverseVideo(displayIsInverseVideo);
}
}
void displayInit(bool isInverted, bool SPIFlashAvailable)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// Enable the FMC interface clock
__HAL_RCC_FSMC_CLK_ENABLE();
// was 0x10D9;
FSMC_Bank1->BTCR[0] =
FSMC_BCR1_MBKEN | // bit 0
FSMC_BCR1_MTYP_1 | // bit 3
FSMC_BCR1_MWID_0 | // bit 4
FSMC_BCR1_FACCEN | // bit 6
//((0x2UL << FSMC_BCR1_FACCEN_Pos)) | // bit 7: UNDOCUMENTED
FSMC_BCR1_WREN; // bit 12
// was 0x00100517;
FSMC_Bank1->BTCR[1] =
FSMC_BCR1_MBKEN | // bit 0
FSMC_BCR1_MUXEN | // bit 1
FSMC_BCR1_MTYP_0 | // bit 2
FSMC_BCR1_MWID_0 | // bit 4
FSMC_BCR1_BURSTEN | // bit 8
FSMC_BCR1_WRAPMOD | // bit 10
FSMC_BCR1_CBURSTRW; // bit 19, as bit 20 isn't documented
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
GPIO_InitStruct.Pin = LCD_D0_Pin | LCD_D1_Pin | LCD_D2_Pin | LCD_D3_Pin | LCD_RS_Pin | LCD_WR_Pin | LCD_RD_Pin;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_D4_Pin | LCD_D5_Pin | LCD_D6_Pin | LCD_D7_Pin;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = 0x00;
GPIO_InitStruct.Pin = LCD_CS_Pin | LCD_RST_Pin;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
// Reset the screen
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET);
osDelay(20);
HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);
// Init
if((DISPLAYLCD_GET_TYPE(displayLCD_Type) == 2) || (DISPLAYLCD_GET_TYPE(displayLCD_Type) == 3))
{
displayWriteCmd(0xfe);
displayWriteCmd(0xef);
{
uint8_t opts[] = { 0x00 };
displayWriteCmds(0xb4, 1, opts);
}
{
uint8_t opts[] = { 0x16 };
displayWriteCmds(0xff, 1, opts);
}
{
uint8_t opts[] = { ((DISPLAYLCD_GET_TYPE(displayLCD_Type) == 3) ? 0x40 : 0x4f) };
displayWriteCmds(0xfd, 1, opts);
}
{
uint8_t opts[] = { 0x70 };
displayWriteCmds(0xa4, 1, opts);
}
{
uint8_t opts[] = { 0x94, 0x88 };
displayWriteCmds(0xe7, 2, opts);
}
{
uint8_t opts[] = { 0x3a };
displayWriteCmds(0xea, 1, opts);
}
{
uint8_t opts[] = { 0x11 };
displayWriteCmds(0xed, 1, opts);
}
{
uint8_t opts[] = { 0xc5 };
displayWriteCmds(0xe4, 1, opts);
}
{
uint8_t opts[] = { 0x80 };
displayWriteCmds(0xe2, 1, opts);
}
{
uint8_t opts[] = { 0x12 };
displayWriteCmds(0xa3, 1, opts);
}
{
uint8_t opts[] = { 0x07 };
displayWriteCmds(0xe3, 1, opts);
}
{
uint8_t opts[] = { 0x10 };
displayWriteCmds(0xe5, 1, opts);
}
{
uint8_t opts[] = { 0x00 };
displayWriteCmds(0xf0, 1, opts);
}
{
uint8_t opts[] = { 0x55 };
displayWriteCmds(0xf1, 1, opts);
}
{
uint8_t opts[] = { 0x05 };
displayWriteCmds(0xf2, 1, opts);
}
{
uint8_t opts[] = { 0x53 };
displayWriteCmds(0xf3, 1, opts);
}
{
uint8_t opts[] = { 0x00 };
displayWriteCmds(0xf4, 1, opts);
}
{
uint8_t opts[] = { 0x00 };
displayWriteCmds(0xf5, 1, opts);
}
{
uint8_t opts[] = { 0x27 };
displayWriteCmds(0xf7, 1 , opts);
}
{
uint8_t opts[] = { 0x22 };
displayWriteCmds(0xf8, 1, opts);
}
{
uint8_t opts[] = { 0x77 };
displayWriteCmds(0xf9, 1, opts);
}
{
uint8_t opts[] = { 0x35 };
displayWriteCmds(0xfa, 1, opts);
}
{
uint8_t opts[] = { 0x00 };
displayWriteCmds(0xfb, 1, opts);
}
{
uint8_t opts[] = { 0x00 };
displayWriteCmds(0xfc, 1, opts);
}
displayWriteCmd(0xfe);
displayWriteCmd(0xef);
{
uint8_t opts[] = { 0x00 };
displayWriteCmds(0xe9, 1, opts);
}
osDelay(20);
}
else
{
displayWriteCmd(0x11);
osDelay(120);
{
uint8_t opts[] = {0x05, 0x3c, 0x3c };
displayWriteCmds(0xb1, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0x05, 0x3c, 0x3c };
displayWriteCmds(0xb2, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0x05, 0x3c, 0x3c, 0x05, 0x3c, 0x3c };
displayWriteCmds(0xb3, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0x03 };
displayWriteCmds(0xb4, 1, opts);
}
{
uint8_t opts[] = { 0x28, 0x08, 0x04 };
displayWriteCmds(0xc0, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0xc0 };
displayWriteCmds(0xc1, 1, opts);
}
{
uint8_t opts[] = { 0xd, 0x00};
displayWriteCmds(0xc2, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0x8d, 0x2a };
displayWriteCmds(0xc3, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0x8d, 0xee };
displayWriteCmds(0xc4, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0x1a };
displayWriteCmds(0xc5, 1 , opts);
}
{
uint8_t opts[] = { 0x08 };
displayWriteCmds(0x36, 1, opts);
}
{
uint8_t opts[] = { 0x04, 0x0c, 0x07, 0x0a, 0x2e, 0x30, 0x25, 0x2a, 0x28, 0x26, 0x2e, 0x3a, 0x00, 0x01, 0x03, 0x13 };
displayWriteCmds(0xe0, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0x04, 0x16, 0x06, 0x0d, 0x2d, 0x26, 0x23, 0x27, 0x27, 0x25, 0x2d, 0x3b, 0x00, 0x01, 0x04, 0x13 };
displayWriteCmds(0xE1, sizeof(opts), opts);
}
}
{
uint8_t opts[] = {
(DISPLAYLCD_GET_TYPE(displayLCD_Type) == 1) ? 0x60 :
(DISPLAYLCD_GET_TYPE(displayLCD_Type) == 2) ? 0xE0 :
0xA0
};
displayWriteCmds(HX8583_CMD_MADCTL, 1, opts);
}
{
uint8_t opts[] = { 0x00, 0x00, 0x00, DISPLAY_SIZE_X };
displayWriteCmds(HX8583_CMD_CASET, sizeof(opts), opts);
}
{
#if defined(PLATFORM_VARIANT_DM1701)
uint8_t opts[] = { 0x00, 0x00, 0x00, DISPLAY_SIZE_Y + DISPLAY_Y_OFFSET};
#else
uint8_t opts[] = { 0x00, 0x00, 0x00, DISPLAY_SIZE_Y};
#endif
displayWriteCmds(HX8583_CMD_RASET, sizeof(opts), opts);
}
{
uint8_t opts[] = { 0x05 }; // RGB565 16 bits per pixel
displayWriteCmds(HX8583_CMD_COLMOD, 1, opts);
}
//osDelay(10);// does not seem to be needed
displayWriteCmd(HX8583_CMD_SLPOUT); // Activate the display
//osDelay(120);// does not seem to be needed
displayWriteCmd(HX8583_CMD_DISPON);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
displayBegin(isInverted, SPIFlashAvailable);
displayClearBuf();
#if defined(PLATFORM_VARIANT_DM1701)
displayClearScreenBlankLines(isInverted);
#endif
displayRender();
}
#if defined(PLATFORM_VARIANT_DM1701)
static void displayClearScreenBlankLines(bool isInverted)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
memset(&GPIO_InitStruct, 0x00, sizeof(GPIO_InitTypeDef));
// Display shares its pins with the keypad, so the pins need to be put into alternate mode to work with the FSMC
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
GPIO_InitStruct.Pin = LCD_D0_Pin | LCD_D1_Pin | LCD_D2_Pin | LCD_D3_Pin;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_D4_Pin | LCD_D5_Pin | LCD_D6_Pin | LCD_D7_Pin;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);
uint8_t opts[] = { 0x00, 0, 0x00, DISPLAY_Y_OFFSET };
displayWriteCmds(HX8583_CMD_RASET, sizeof(opts), opts);
displayWriteCmd(HX8583_CMD_RAMWR);
uint8_t fillData = isInverted ? 0xFF: 0x00;
for(int y = 0; y < DISPLAY_Y_OFFSET * DISPLAY_SIZE_X * sizeof(uint16_t); y++)
{
*((volatile uint8_t*) LCD_FSMC_ADDR_DATA) = fillData;
}
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
*((volatile uint8_t*) LCD_FSMC_ADDR_DATA) = 0;// write 0 to the display pins , to pull them all low, so keyboard reads don't need to
}
#endif
void displayEnableBacklight(bool enable, int displayBacklightPercentageOff)
{
if (enable)
{
gpioSetDisplayBacklightIntensityPercentage(nonVolatileSettings.displayBacklightPercentage[DAYTIME_CURRENT]);
}
else
{
gpioSetDisplayBacklightIntensityPercentage(((nonVolatileSettings.backlightMode == BACKLIGHT_MODE_NONE) ? 0 : displayBacklightPercentageOff));
}
}
bool displayIsBacklightLit(void)
{
return (gpioGetDisplayBacklightIntensityPercentage() != nonVolatileSettings.displayBacklightPercentageOff);
}
// Backlight level to use when idle. If screen dimming is enabled (Auto mode only),
// dim to a fraction of the configured brightness; otherwise use the normal off level.
int displayGetIdleBacklightPercentage(void)
{
if (nonVolatileSettings.backlightMode == BACKLIGHT_MODE_AUTO)
{
static const uint8_t dimPercent[SCREEN_DIM_NUM] = { 0, 100, 50, 30, 15 };
uint8_t level = settingsGetScreenDimLevel();
if (level != SCREEN_DIM_OFF)
{
return ((nonVolatileSettings.displayBacklightPercentage[DAYTIME_CURRENT] * dimPercent[level]) / 100);
}
}
return nonVolatileSettings.displayBacklightPercentageOff;
}