mirror of
https://github.com/opentx/opentx.git
synced 2025-07-24 00:35:18 +03:00
366 lines
11 KiB
C++
366 lines
11 KiB
C++
/*
|
|
* Copyright (C) OpenTX
|
|
*
|
|
* Based on code named
|
|
* th9x - http://code.google.com/p/th9x
|
|
* er9x - http://code.google.com/p/er9x
|
|
* gruvin9x - http://code.google.com/p/gruvin9x
|
|
*
|
|
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "opentx.h"
|
|
|
|
#if defined(RADIO_T12) || defined(RADIO_TX12)
|
|
#define LCD_CONTRAST_OFFSET -10
|
|
#else
|
|
#define LCD_CONTRAST_OFFSET 160
|
|
#endif
|
|
#define RESET_WAIT_DELAY_MS 300 // Wait time after LCD reset before first command
|
|
#define WAIT_FOR_DMA_END() do { } while (lcd_busy)
|
|
|
|
#define LCD_NCS_HIGH() LCD_NCS_GPIO->BSRRL = LCD_NCS_GPIO_PIN
|
|
#define LCD_NCS_LOW() LCD_NCS_GPIO->BSRRH = LCD_NCS_GPIO_PIN
|
|
|
|
#define LCD_A0_HIGH() LCD_SPI_GPIO->BSRRL = LCD_A0_GPIO_PIN
|
|
#define LCD_A0_LOW() LCD_SPI_GPIO->BSRRH = LCD_A0_GPIO_PIN
|
|
|
|
#define LCD_RST_HIGH() LCD_RST_GPIO->BSRRL = LCD_RST_GPIO_PIN
|
|
#define LCD_RST_LOW() LCD_RST_GPIO->BSRRH = LCD_RST_GPIO_PIN
|
|
|
|
bool lcdInitFinished = false;
|
|
void lcdInitFinish();
|
|
|
|
void lcdWriteCommand(uint8_t byte)
|
|
{
|
|
LCD_A0_LOW();
|
|
LCD_NCS_LOW();
|
|
while ((SPI3->SR & SPI_SR_TXE) == 0) {
|
|
// Wait
|
|
}
|
|
(void)SPI3->DR; // Clear receive
|
|
LCD_SPI->DR = byte;
|
|
while ((SPI3->SR & SPI_SR_RXNE) == 0) {
|
|
// Wait
|
|
}
|
|
LCD_NCS_HIGH();
|
|
}
|
|
|
|
void lcdHardwareInit()
|
|
{
|
|
GPIO_InitTypeDef GPIO_InitStructure;
|
|
|
|
// APB1 clock / 2 = 133nS per clock
|
|
LCD_SPI->CR1 = 0; // Clear any mode error
|
|
LCD_SPI->CR1 = SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_CPOL | SPI_CR1_CPHA;
|
|
LCD_SPI->CR2 = 0;
|
|
LCD_SPI->CR1 |= SPI_CR1_MSTR; // Make sure in case SSM/SSI needed to be set first
|
|
LCD_SPI->CR1 |= SPI_CR1_SPE;
|
|
|
|
LCD_NCS_HIGH();
|
|
|
|
GPIO_InitStructure.GPIO_Pin = LCD_NCS_GPIO_PIN;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
|
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
|
GPIO_Init(LCD_NCS_GPIO, &GPIO_InitStructure);
|
|
|
|
GPIO_InitStructure.GPIO_Pin = LCD_RST_GPIO_PIN;
|
|
GPIO_Init(LCD_RST_GPIO, &GPIO_InitStructure);
|
|
|
|
GPIO_InitStructure.GPIO_Pin = LCD_A0_GPIO_PIN;
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
|
GPIO_Init(LCD_SPI_GPIO, &GPIO_InitStructure);
|
|
|
|
GPIO_InitStructure.GPIO_Pin = LCD_CLK_GPIO_PIN | LCD_MOSI_GPIO_PIN;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
|
GPIO_Init(LCD_SPI_GPIO, &GPIO_InitStructure);
|
|
|
|
GPIO_PinAFConfig(LCD_SPI_GPIO, LCD_MOSI_GPIO_PinSource, LCD_GPIO_AF);
|
|
GPIO_PinAFConfig(LCD_SPI_GPIO, LCD_CLK_GPIO_PinSource, LCD_GPIO_AF);
|
|
|
|
LCD_DMA_Stream->CR &= ~DMA_SxCR_EN; // Disable DMA
|
|
LCD_DMA->HIFCR = LCD_DMA_FLAGS; // Write ones to clear bits
|
|
LCD_DMA_Stream->CR = DMA_SxCR_PL_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0;
|
|
LCD_DMA_Stream->PAR = (uint32_t)&LCD_SPI->DR;
|
|
#if LCD_W == 128
|
|
LCD_DMA_Stream->NDTR = LCD_W;
|
|
#else
|
|
LCD_DMA_Stream->M0AR = (uint32_t)displayBuf;
|
|
LCD_DMA_Stream->NDTR = LCD_W*LCD_H/8*4;
|
|
#endif
|
|
LCD_DMA_Stream->FCR = 0x05; // DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0;
|
|
|
|
NVIC_EnableIRQ(LCD_DMA_Stream_IRQn);
|
|
}
|
|
|
|
#if LCD_W == 128
|
|
void lcdStart()
|
|
{
|
|
#if defined(LCD_VERTICAL_INVERT)
|
|
// T12 and TX12 have the screen inverted.
|
|
lcdWriteCommand(0xe2); // (14) Soft reset
|
|
lcdWriteCommand(0xa0); // Set seg
|
|
lcdWriteCommand(0xc8); // Set com
|
|
lcdWriteCommand(0xf8); // Set booster
|
|
lcdWriteCommand(0x00); // 5x
|
|
lcdWriteCommand(0xa3); // Set bias=1/6
|
|
lcdWriteCommand(0x22); // Set internal rb/ra=5.0
|
|
lcdWriteCommand(0x2f); // All built-in power circuits on
|
|
lcdWriteCommand(0x24); // Power control set
|
|
lcdWriteCommand(0x81); // Set contrast
|
|
lcdWriteCommand(0x0A); // Set Vop
|
|
lcdWriteCommand(0xa6); // Set display mode
|
|
#else
|
|
lcdWriteCommand(0xe2); // (14) Soft reset
|
|
lcdWriteCommand(0xa1); // Set seg
|
|
lcdWriteCommand(0xc0); // Set com
|
|
lcdWriteCommand(0xf8); // Set booster
|
|
lcdWriteCommand(0x00); // 5x
|
|
lcdWriteCommand(0xa3); // Set bias=1/6
|
|
lcdWriteCommand(0x22); // Set internal rb/ra=5.0
|
|
lcdWriteCommand(0x2f); // All built-in power circuits on
|
|
lcdWriteCommand(0x81); // Set contrast
|
|
lcdWriteCommand(0x36); // Set Vop
|
|
lcdWriteCommand(0xa6); // Set display mode
|
|
#endif
|
|
}
|
|
#else
|
|
void lcdStart()
|
|
{
|
|
lcdWriteCommand(0x2F); // Internal pump control
|
|
delay_ms(20);
|
|
lcdWriteCommand(0x24); // Temperature compensation
|
|
lcdWriteCommand(0xE9); // Set bias=1/10
|
|
lcdWriteCommand(0x81); // Set Vop
|
|
#if defined(BOOT)
|
|
lcdWriteCommand(LCD_CONTRAST_OFFSET + LCD_CONTRAST_DEFAULT);
|
|
#else
|
|
lcdWriteCommand(LCD_CONTRAST_OFFSET + g_eeGeneral.contrast);
|
|
#endif
|
|
lcdWriteCommand(0xA2); // Set line rate: 28KLPS
|
|
lcdWriteCommand(0x28); // Set panel loading
|
|
lcdWriteCommand(0x40); // Scroll line LSB
|
|
lcdWriteCommand(0x50); // Scroll line MSB
|
|
lcdWriteCommand(0x89); // RAM address control
|
|
lcdWriteCommand(0xC0); // LCD mapping control
|
|
lcdWriteCommand(0x04); // MX=0, MY=1
|
|
lcdWriteCommand(0xD0); // Display pattern = 16-SCALE GRAY
|
|
lcdWriteCommand(0xF1); // Set COM end
|
|
lcdWriteCommand(0x3F); // 64
|
|
|
|
lcdWriteCommand(0xF8); // Set Window Program Disable.
|
|
|
|
lcdWriteCommand(0xF5); // Start row address of RAM program window. PAGE1
|
|
lcdWriteCommand(0x00);
|
|
lcdWriteCommand(0xF7); // End row address of RAM program window. PAGE32
|
|
lcdWriteCommand(0x1F);
|
|
lcdWriteCommand(0xF4); // Start column address of RAM program window.
|
|
lcdWriteCommand(0x00);
|
|
lcdWriteCommand(0xF6); // End column address of RAM program window. SEG212
|
|
lcdWriteCommand(0xD3);
|
|
}
|
|
|
|
void lcdWriteAddress(uint8_t x, uint8_t y)
|
|
{
|
|
lcdWriteCommand(x & 0x0Fu); // Set Column Address LSB CA[3:0]
|
|
lcdWriteCommand((x >> 4u) | 0x10u); // Set Column Address MSB CA[7:4]
|
|
|
|
lcdWriteCommand((y & 0x0Fu) | 0x60u); // Set Row Address LSB RA [3:0]
|
|
lcdWriteCommand(((y >> 4u) & 0x0Fu) | 0x70u); // Set Row Address MSB RA [7:4]
|
|
}
|
|
#endif
|
|
|
|
volatile bool lcd_busy;
|
|
|
|
#if !defined(LCD_DUAL_BUFFER)
|
|
void lcdRefreshWait()
|
|
{
|
|
WAIT_FOR_DMA_END();
|
|
}
|
|
#endif
|
|
|
|
void lcdRefresh(bool wait)
|
|
{
|
|
if (!lcdInitFinished) {
|
|
lcdInitFinish();
|
|
}
|
|
|
|
#if LCD_W == 128
|
|
uint8_t * p = displayBuf;
|
|
for (uint8_t y=0; y < 8; y++, p+=LCD_W) {
|
|
lcdWriteCommand(0x10); // Column addr 0
|
|
lcdWriteCommand(0xB0 | y); // Page addr y
|
|
#if !defined(LCD_VERTICAL_INVERT)
|
|
lcdWriteCommand(0x04);
|
|
#endif
|
|
|
|
LCD_NCS_LOW();
|
|
LCD_A0_HIGH();
|
|
|
|
lcd_busy = true;
|
|
LCD_DMA_Stream->CR &= ~DMA_SxCR_EN; // Disable DMA
|
|
LCD_DMA->HIFCR = LCD_DMA_FLAGS; // Write ones to clear bits
|
|
LCD_DMA_Stream->M0AR = (uint32_t)p;
|
|
LCD_DMA_Stream->CR |= DMA_SxCR_EN | DMA_SxCR_TCIE; // Enable DMA & TC interrupts
|
|
LCD_SPI->CR2 |= SPI_CR2_TXDMAEN;
|
|
|
|
WAIT_FOR_DMA_END();
|
|
|
|
LCD_NCS_HIGH();
|
|
LCD_A0_HIGH();
|
|
}
|
|
#else
|
|
// Wait if previous DMA transfer still active
|
|
WAIT_FOR_DMA_END();
|
|
lcd_busy = true;
|
|
|
|
lcdWriteAddress(0, 0);
|
|
|
|
LCD_NCS_LOW();
|
|
LCD_A0_HIGH();
|
|
|
|
LCD_DMA_Stream->CR &= ~DMA_SxCR_EN; // Disable DMA
|
|
LCD_DMA->HIFCR = LCD_DMA_FLAGS; // Write ones to clear bits
|
|
|
|
#if defined(LCD_DUAL_BUFFER)
|
|
// Switch LCD buffer
|
|
LCD_DMA_Stream->M0AR = (uint32_t)displayBuf;
|
|
displayBuf = (displayBuf == displayBuf1) ? displayBuf2 : displayBuf1;
|
|
#endif
|
|
|
|
LCD_DMA_Stream->CR |= DMA_SxCR_EN | DMA_SxCR_TCIE; // Enable DMA & TC interrupts
|
|
LCD_SPI->CR2 |= SPI_CR2_TXDMAEN;
|
|
#endif
|
|
}
|
|
|
|
extern "C" void LCD_DMA_Stream_IRQHandler()
|
|
{
|
|
DEBUG_INTERRUPT(INT_LCD);
|
|
|
|
LCD_DMA_Stream->CR &= ~DMA_SxCR_TCIE; // Stop interrupt
|
|
LCD_DMA->HIFCR |= LCD_DMA_FLAG_INT; // Clear interrupt flag
|
|
LCD_SPI->CR2 &= ~SPI_CR2_TXDMAEN;
|
|
LCD_DMA_Stream->CR &= ~DMA_SxCR_EN; // Disable DMA
|
|
|
|
while (LCD_SPI->SR & SPI_SR_BSY) {
|
|
/* Wait for SPI to finish sending data
|
|
The DMA TX End interrupt comes two bytes before the end of SPI transmission,
|
|
therefore we have to wait here.
|
|
*/
|
|
}
|
|
LCD_NCS_HIGH();
|
|
lcd_busy = false;
|
|
}
|
|
|
|
/*
|
|
Proper method for turning of LCD module. It must be used,
|
|
otherwise we might damage LCD crystals in the long run!
|
|
*/
|
|
void lcdOff()
|
|
{
|
|
WAIT_FOR_DMA_END();
|
|
|
|
/*
|
|
LCD Sleep mode is also good for draining capacitors and enables us
|
|
to re-init LCD without any delay
|
|
*/
|
|
lcdWriteCommand(0xAE); // LCD sleep
|
|
delay_ms(3); // Wait for caps to drain
|
|
}
|
|
|
|
void lcdReset()
|
|
{
|
|
LCD_RST_LOW();
|
|
#if LCD_W == 128
|
|
delay_ms(150);
|
|
#else
|
|
delay_ms(1); // Only 3 us needed according to data-sheet, we use 1 ms
|
|
#endif
|
|
LCD_RST_HIGH();
|
|
}
|
|
|
|
/*
|
|
Starts LCD initialization routine. It should be called as
|
|
soon as possible after the reset because LCD takes a lot of
|
|
time to properly power-on.
|
|
|
|
Make sure that delay_ms() is functional before calling this function!
|
|
*/
|
|
void lcdInit()
|
|
{
|
|
lcdHardwareInit();
|
|
|
|
if (IS_LCD_RESET_NEEDED()) {
|
|
lcdReset();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Finishes LCD initialization. It is called auto-magically when first LCD command is
|
|
issued by the other parts of the code.
|
|
*/
|
|
|
|
#if defined(RADIO_X9DP2019) || defined(RADIO_X7ACCESS)
|
|
#define LCD_DELAY_NEEDED() true
|
|
#else
|
|
#define LCD_DELAY_NEEDED() (!WAS_RESET_BY_WATCHDOG_OR_SOFTWARE())
|
|
#endif
|
|
|
|
void lcdInitFinish()
|
|
{
|
|
lcdInitFinished = true;
|
|
|
|
/*
|
|
LCD needs longer time to initialize in low temperatures. The data-sheet
|
|
mentions a time of at least 150 ms. The delay of 1300 ms was obtained
|
|
experimentally. It was tested down to -10 deg Celsius.
|
|
|
|
The longer initialization time seems to only be needed for regular Taranis,
|
|
the Taranis Plus (9XE) has been tested to work without any problems at -18 deg Celsius.
|
|
Therefore the delay for T+ is lower.
|
|
|
|
If radio is reset by watchdog or boot-loader the wait is skipped, but the LCD
|
|
is initialized in any case.
|
|
|
|
This initialization is needed in case the user moved power switch to OFF and
|
|
then immediately to ON position, because lcdOff() was called. In any case the LCD
|
|
initialization (without reset) is also recommended by the data sheet.
|
|
*/
|
|
|
|
if (LCD_DELAY_NEEDED()) {
|
|
#if !defined(BOOT)
|
|
while (g_tmr10ms < (RESET_WAIT_DELAY_MS / 10)); // wait measured from the power-on
|
|
#else
|
|
delay_ms(RESET_WAIT_DELAY_MS);
|
|
#endif
|
|
}
|
|
|
|
lcdStart();
|
|
lcdWriteCommand(0xAF); // dc2=1, IC into exit SLEEP MODE, dc3=1 gray=ON, dc4=1 Green Enhanc mode disabled
|
|
delay_ms(20); // needed for internal DC-DC converter startup
|
|
}
|
|
|
|
void lcdSetRefVolt(uint8_t val)
|
|
{
|
|
if (!lcdInitFinished) {
|
|
lcdInitFinish();
|
|
}
|
|
|
|
#if LCD_W != 128
|
|
WAIT_FOR_DMA_END();
|
|
#endif
|
|
|
|
lcdWriteCommand(0x81); // Set Vop
|
|
lcdWriteCommand(val+LCD_CONTRAST_OFFSET); // 0-255
|
|
}
|