471 lines
14 KiB
C++
471 lines
14 KiB
C++
/**
|
|
******************************************************************************
|
|
* @file Project/lcd/lcd.c
|
|
* @author FrSky Application Team
|
|
* @Hardware version V0.2
|
|
* @date 11-July-2012
|
|
* @brief This file provides LCD Init and botom drivers.
|
|
* *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "../../opentx.h"
|
|
|
|
#define WriteData(x) AspiData(x)
|
|
#define WriteCommand(x) AspiCmd(x)
|
|
|
|
#if defined(REVPLUS)
|
|
#define CONTRAST_OFS 160
|
|
#define RESET_WAIT_DELAY_MS 300 //wait time after LCD reset before first command
|
|
#define WAIT_FOR_DMA_END() { while(lcd_busy) {}; }
|
|
#else
|
|
#define CONTRAST_OFS 5
|
|
#define RESET_WAIT_DELAY_MS 1300 //wait time after LCD reset before first command
|
|
#define WAIT_FOR_DMA_END()
|
|
#endif
|
|
|
|
bool lcdInitFinished = false;
|
|
void lcdInitFinish();
|
|
|
|
/*
|
|
delaysInit() must be called before the first call to this function!
|
|
*/
|
|
static void Delay(uint32_t ms)
|
|
{
|
|
while(ms--) {
|
|
delay_01us(10000);
|
|
}
|
|
}
|
|
|
|
#if defined(REVPLUS)
|
|
|
|
// New hardware SPI driver for LCD
|
|
void initLcdSpi()
|
|
{
|
|
// uint16_t temp ;
|
|
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_LCD, ENABLE);
|
|
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_LCD_RST, ENABLE);
|
|
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_LCD_NCS, ENABLE);
|
|
|
|
RCC->APB1ENR |= RCC_APB1ENR_SPI3EN ; // Enable clock
|
|
// APB1 clock / 2 = 133nS per clock
|
|
SPI3->CR1 = 0 ; // Clear any mode error
|
|
SPI3->CR1 = SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_CPOL | SPI_CR1_CPHA ;
|
|
SPI3->CR2 = 0 ;
|
|
SPI3->CR1 |= SPI_CR1_MSTR ; // Make sure in case SSM/SSI needed to be set first
|
|
SPI3->CR1 |= SPI_CR1_SPE ;
|
|
|
|
configure_pins( PIN_LCD_NCS, PIN_OUTPUT | PIN_PORTA | PIN_OS25) ;
|
|
configure_pins( PIN_LCD_RST, PIN_OUTPUT | PIN_PORTD | PIN_OS25) ;
|
|
configure_pins( PIN_LCD_A0, PIN_OUTPUT | PIN_PORTC | PIN_OS50) ;
|
|
configure_pins( PIN_LCD_MOSI|PIN_LCD_CLK, PIN_PORTC | PIN_OS50 | PIN_PER_6 | PIN_PERIPHERAL ) ;
|
|
|
|
|
|
// NVIC_SetPriority( DMA1_Stream7_IRQn, 8 ) ;
|
|
NVIC_EnableIRQ(DMA1_Stream7_IRQn) ;
|
|
DMA1->HIFCR |= DMA_HIFCR_CTCIF7; //clear interrupt flag
|
|
DMA1->LISR |= DMA_HISR_TCIF7; //enable DMA TX end interrupt
|
|
|
|
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN ; // Enable DMA1 clock
|
|
|
|
DMA1_Stream7->CR &= ~DMA_SxCR_EN ; // Disable DMA
|
|
DMA1->HIFCR = DMA_HIFCR_CTCIF7 | DMA_HIFCR_CHTIF7 | DMA_HIFCR_CTEIF7 | DMA_HIFCR_CDMEIF7 | DMA_HIFCR_CFEIF7 ; // Write ones to clear bits
|
|
DMA1_Stream7->CR = DMA_SxCR_PL_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 ;
|
|
DMA1_Stream7->PAR = (uint32_t) &SPI3->DR ;
|
|
DMA1_Stream7->M0AR = (uint32_t)displayBuf;
|
|
DMA1_Stream7->FCR = 0x05 ; //DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0 ;
|
|
DMA1_Stream7->NDTR = LCD_W*LCD_H/8*4 ;
|
|
}
|
|
|
|
|
|
static void LCD_Init()
|
|
{
|
|
WriteCommand(0x2F); //Internal pump control
|
|
Delay(20);
|
|
WriteCommand(0x24); //Temperature compensation
|
|
WriteCommand(0xE9); //set bias=1/10
|
|
WriteCommand(0x81); //Set Vop
|
|
#if defined(BOOT)
|
|
AspiCmd(CONTRAST_OFS+25);
|
|
#else
|
|
AspiCmd(CONTRAST_OFS+g_eeGeneral.contrast);
|
|
#endif
|
|
WriteCommand(0xA2); //set line rate:28KLPS
|
|
WriteCommand(0x28); //set pannel loading
|
|
WriteCommand(0x40); //scroll line LSB
|
|
WriteCommand(0x50); //SCROLL LINE MSB
|
|
WriteCommand(0x89); //ram address control
|
|
WriteCommand(0xC0); //LCD mapping control
|
|
WriteCommand(0x04); //MX=0,MY=1
|
|
WriteCommand(0xD0); //DISPLAY PATTERN = 16-SCALE GRAY
|
|
WriteCommand(0xF1); //SET COM end
|
|
WriteCommand(0x3F); //64
|
|
|
|
WriteCommand(0xF8); //Set Window Program Disable.
|
|
|
|
WriteCommand(0xF5); //starting row address of RAM program window.PAGE1
|
|
WriteCommand(0x00);
|
|
WriteCommand(0xF7); //end row address of RAM program window.PAGE32
|
|
WriteCommand(0x1F);
|
|
WriteCommand(0xF4); //start column address of RAM program window.
|
|
WriteCommand(0x00);
|
|
WriteCommand(0xF6); //end column address of RAM program window.SEG212
|
|
WriteCommand(0xD3);
|
|
}
|
|
#else
|
|
static void LCD_Init()
|
|
{
|
|
AspiCmd(0x2B); //Panel loading set ,Internal VLCD.
|
|
Delay(20);
|
|
AspiCmd(0x25); //Temperature compensation curve definition: 0x25 = -0.05%/oC
|
|
AspiCmd(0xEA); //set bias=1/10 :Command table NO.27
|
|
AspiCmd(0x81); //Set Vop
|
|
#if defined(BOOT)
|
|
AspiCmd(CONTRAST_OFS+25);
|
|
#else
|
|
AspiCmd(CONTRAST_OFS+g_eeGeneral.contrast);
|
|
#endif
|
|
AspiCmd(0xA6); //inverse display off
|
|
AspiCmd(0xD1); //SET RGB:Command table NO.21 .SET RGB or BGR. D1=RGB
|
|
AspiCmd(0xD5); //set color mode 4K and 12bits :Command table NO.22
|
|
AspiCmd(0xA0); //line rates,25.2 Klps
|
|
AspiCmd(0xC8); //SET N-LINE INVERSION
|
|
AspiCmd(0x1D); //Disable NIV
|
|
AspiCmd(0xF1); //Set CEN
|
|
AspiCmd(0x3F); // 1/64DUTY
|
|
AspiCmd(0x84); //Disable Partial Display
|
|
AspiCmd(0xC4); //MY=1,MX=0
|
|
AspiCmd(0x89); //WA=1,column (CA) increment (+1) first until CA reaches CA boundary, then RA will increment by (+1).
|
|
|
|
AspiCmd(0xF8); //Set Window Program Enable ,inside modle
|
|
AspiCmd(0xF4); //starting column address of RAM program window.
|
|
AspiCmd(0x00);
|
|
AspiCmd(0xF5); //starting row address of RAM program window.
|
|
AspiCmd(0x60);
|
|
AspiCmd(0xF6); //ending column address of RAM program window.
|
|
AspiCmd(0x47);
|
|
AspiCmd(0xF7); //ending row address of RAM program window.
|
|
AspiCmd(0x9F);
|
|
}
|
|
#endif
|
|
|
|
void Set_Address(u8 x, u8 y)
|
|
{
|
|
WriteCommand(x&0x0F); //Set Column Address LSB CA[3:0]
|
|
WriteCommand((x>>4)|0x10); //Set Column Address MSB CA[7:4]
|
|
|
|
WriteCommand((y&0x0F)|0x60); //Set Row Address LSB RA [3:0]
|
|
WriteCommand(((y>>4)&0x0F)|0x70); //Set Row Address MSB RA [7:4]
|
|
}
|
|
|
|
#define LCD_WRITE_BIT(bit) \
|
|
if (bit) \
|
|
LCD_MOSI_HIGH(); \
|
|
else \
|
|
LCD_MOSI_LOW(); \
|
|
LCD_CLK_LOW(); \
|
|
LCD_CLK_LOW(); \
|
|
LCD_CLK_LOW(); \
|
|
LCD_CLK_HIGH(); \
|
|
LCD_CLK_HIGH();
|
|
|
|
#if defined(REVPLUS)
|
|
|
|
volatile bool lcd_busy;
|
|
|
|
#if !defined(LCD_DUAL_BUFFER)
|
|
void lcdRefreshWait()
|
|
{
|
|
WAIT_FOR_DMA_END();
|
|
}
|
|
#endif
|
|
|
|
void lcdRefresh(bool wait)
|
|
{
|
|
if (!lcdInitFinished) {
|
|
lcdInitFinish();
|
|
}
|
|
|
|
//wait if previous DMA transfer still active
|
|
WAIT_FOR_DMA_END();
|
|
lcd_busy = true;
|
|
|
|
Set_Address(0, 0);
|
|
|
|
LCD_NCS_LOW();
|
|
LCD_A0_HIGH();
|
|
|
|
DMA1_Stream7->CR &= ~DMA_SxCR_EN ; // Disable DMA
|
|
DMA1->HIFCR = DMA_HIFCR_CTCIF7 | DMA_HIFCR_CHTIF7 | DMA_HIFCR_CTEIF7 | DMA_HIFCR_CDMEIF7 | DMA_HIFCR_CFEIF7 ; // Write ones to clear bits
|
|
|
|
#if defined(LCD_DUAL_BUFFER)
|
|
//switch LCD buffer
|
|
DMA1_Stream7->M0AR = (uint32_t)displayBuf;
|
|
displayBuf = (displayBuf == displayBuf1) ? displayBuf2 : displayBuf1;
|
|
#endif
|
|
|
|
DMA1_Stream7->CR |= DMA_SxCR_EN | DMA_SxCR_TCIE; // Enable DMA & tXe interrupt
|
|
SPI3->CR2 |= SPI_CR2_TXDMAEN ;
|
|
}
|
|
|
|
extern "C" void DMA1_Stream7_IRQHandler()
|
|
{
|
|
//clear interrupt flag
|
|
DMA1_Stream7->CR &= ~DMA_SxCR_TCIE ; // Stop interrupt
|
|
DMA1->HIFCR |= DMA_HIFCR_CTCIF7; // Clear interrupt flag
|
|
SPI3->CR2 &= ~SPI_CR2_TXDMAEN ;
|
|
DMA1_Stream7->CR &= ~DMA_SxCR_EN ; // Disable DMA
|
|
|
|
while ( SPI3->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;
|
|
}
|
|
|
|
#else // #if defined(REVPLUS)
|
|
void lcdRefresh()
|
|
{
|
|
if (!lcdInitFinished) {
|
|
lcdInitFinish();
|
|
}
|
|
|
|
for (uint32_t y=0; y<LCD_H; y++) {
|
|
uint8_t *p = &displayBuf[y/2 * LCD_W];
|
|
|
|
Set_Address(0, y);
|
|
AspiCmd(0xAF);
|
|
|
|
LCD_CLK_HIGH();
|
|
LCD_A0_HIGH();
|
|
LCD_NCS_LOW();
|
|
|
|
for (uint32_t x=0; x<LCD_W; x++) {
|
|
uint8_t b = p[x];
|
|
if (y & 1)
|
|
b >>= 4;
|
|
LCD_WRITE_BIT(b & 0x08);
|
|
LCD_WRITE_BIT(b & 0x04);
|
|
LCD_WRITE_BIT(b & 0x02);
|
|
LCD_WRITE_BIT(b & 0x01);
|
|
}
|
|
|
|
LCD_NCS_HIGH();
|
|
LCD_A0_HIGH();
|
|
|
|
WriteData(0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**Init the Backlight GPIO */
|
|
static void LCD_BL_Config()
|
|
{
|
|
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOBL, ENABLE);
|
|
GPIO_InitTypeDef GPIO_InitStructure;
|
|
|
|
#if defined(REV9E)
|
|
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_BL|GPIO_Pin_BLW;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
|
GPIO_Init(GPIOBL, &GPIO_InitStructure);
|
|
GPIO_PinAFConfig(GPIOBL, GPIO_PinSource_BL, Pin_BL_AF);
|
|
GPIO_PinAFConfig(GPIOBL, GPIO_PinSource_BLW, Pin_BL_AF);
|
|
RCC->APB2ENR |= RCC_APB2ENR_TIM9EN ; // Enable clock
|
|
TIM9->ARR = 100 ;
|
|
TIM9->PSC = (PERI2_FREQUENCY * TIMER_MULT_APB2) / 50000 - 1; // 20us * 100 = 2ms => 500Hz
|
|
TIM9->CCMR1 = TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 ; // PWM
|
|
TIM9->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E ;
|
|
TIM9->CCR1 = 0 ;
|
|
TIM9->CCR2 = 80 ;
|
|
TIM9->EGR = 0 ;
|
|
TIM9->CR1 = TIM_CR1_CEN ; // Counter enable
|
|
#elif defined(REVPLUS)
|
|
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_BL|GPIO_Pin_BLW;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
|
GPIO_Init(GPIOBL, &GPIO_InitStructure);
|
|
GPIO_PinAFConfig(GPIOBL, GPIO_PinSource_BL, Pin_BL_AF);
|
|
GPIO_PinAFConfig(GPIOBL, GPIO_PinSource_BLW, Pin_BL_AF);
|
|
|
|
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN ; // Enable clock
|
|
TIM4->ARR = 100 ;
|
|
TIM4->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 50000 - 1; // 20us * 100 = 2ms => 500Hz
|
|
TIM4->CCMR1 = TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 ; // PWM
|
|
TIM4->CCMR2 = TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2 ; // PWM
|
|
TIM4->CCER = TIM_CCER_CC4E | TIM_CCER_CC2E ;
|
|
TIM4->CCR2 = 0 ;
|
|
TIM4->CCR4 = 80 ;
|
|
TIM4->EGR = 0 ;
|
|
TIM4->CR1 = TIM_CR1_CEN ; // Counter enable
|
|
#else
|
|
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_BL;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
|
GPIO_Init(GPIOBL, &GPIO_InitStructure);
|
|
GPIO_PinAFConfig(GPIOBL, GPIO_PinSource_BL, Pin_BL_AF);
|
|
|
|
RCC->APB2ENR |= RCC_APB2ENR_TIM10EN ; // Enable clock
|
|
TIM10->ARR = 100 ;
|
|
TIM10->PSC = (PERI2_FREQUENCY * TIMER_MULT_APB2) / 50000 - 1; // 20us * 100 = 2ms => 500Hz
|
|
TIM10->CCMR1 = 0x60 ; // PWM
|
|
TIM10->CCER = 1 ;
|
|
TIM10->CCR1 = 80;
|
|
TIM10->EGR = 0 ;
|
|
TIM10->CR1 = 1 ;
|
|
#endif
|
|
}
|
|
|
|
/** Init the analog SPI GPIO
|
|
*/
|
|
static void LCD_Hardware_Init()
|
|
{
|
|
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_LCD, ENABLE);
|
|
|
|
GPIO_InitTypeDef GPIO_InitStructure;
|
|
|
|
/*!< Configure lcd CLK\ MOSI\ A0pin in output push-pull mode *************/
|
|
GPIO_InitStructure.GPIO_Pin = PIN_LCD_MOSI | PIN_LCD_CLK | PIN_LCD_A0;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
|
GPIO_Init(GPIO_LCD_SPI, &GPIO_InitStructure);
|
|
|
|
LCD_NCS_HIGH();
|
|
|
|
/*!< Configure lcd NCS pin in output push-pull mode ,PULLUP *************/
|
|
GPIO_InitStructure.GPIO_Pin = PIN_LCD_NCS;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
|
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
|
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
|
|
GPIO_Init(GPIO_LCD_NCS, &GPIO_InitStructure);
|
|
|
|
/*!< Configure lcd RST pin in output pushpull mode ,PULLUP *************/
|
|
GPIO_InitStructure.GPIO_Pin = PIN_LCD_RST;
|
|
GPIO_Init(GPIO_LCD_RST, &GPIO_InitStructure);
|
|
}
|
|
|
|
/*
|
|
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
|
|
*/
|
|
AspiCmd(0xAE); //LCD sleep
|
|
Delay(3); //wait for caps to drain
|
|
}
|
|
|
|
/*
|
|
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() is functional before calling this function!
|
|
*/
|
|
void lcdInit()
|
|
{
|
|
LCD_BL_Config();
|
|
LCD_Hardware_Init();
|
|
|
|
if (WAS_RESET_BY_WATCHDOG()|WAS_RESET_BY_SOFTWARE()) return; //no need to reset LCD module
|
|
|
|
//reset LCD module
|
|
LCD_RST_LOW();
|
|
Delay(1); // only 3 us needed according to data-sheet, we use 1 ms
|
|
LCD_RST_HIGH();
|
|
}
|
|
|
|
/*
|
|
Finishes LCD initialization. It is called auto-magically when first LCD command is
|
|
issued by the other parts of the code.
|
|
*/
|
|
void lcdInitFinish()
|
|
{
|
|
lcdInitFinished = true;
|
|
|
|
#if defined(REVPLUS)
|
|
initLcdSpi();
|
|
#endif
|
|
|
|
/*
|
|
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 (!WAS_RESET_BY_WATCHDOG() && !WAS_RESET_BY_SOFTWARE()) {
|
|
#if !defined(BOOT)
|
|
while(g_tmr10ms < (RESET_WAIT_DELAY_MS/10)) {}; //wait measured from the power-on
|
|
#else
|
|
Delay(RESET_WAIT_DELAY_MS);
|
|
#endif
|
|
}
|
|
|
|
LCD_Init();
|
|
AspiCmd(0xAF); //dc2=1, IC into exit SLEEP MODE, dc3=1 gray=ON, dc4=1 Green Enhanc mode disabled
|
|
Delay(20); //needed for internal DC-DC converter startup
|
|
}
|
|
|
|
void lcdSetRefVolt(uint8_t val)
|
|
{
|
|
if (!lcdInitFinished) {
|
|
lcdInitFinish();
|
|
}
|
|
WAIT_FOR_DMA_END();
|
|
AspiCmd(0x81); //Set Vop
|
|
AspiCmd(val+CONTRAST_OFS); //0--255
|
|
}
|
|
|
|
#if defined(REV9E)
|
|
void turnBacklightOn(uint8_t level, uint8_t color)
|
|
{
|
|
TIM_BL->CCR1 = ((100-level)*(20-color))/20;
|
|
TIM_BL->CCR2 = ((100-level)*color)/20;
|
|
}
|
|
|
|
void turnBacklightOff(void)
|
|
{
|
|
TIM_BL->CCR1 = 0;
|
|
TIM_BL->CCR2 = 0;
|
|
}
|
|
#elif defined(REVPLUS)
|
|
void turnBacklightOn(uint8_t level, uint8_t color)
|
|
{
|
|
TIM_BL->CCR4 = ((100-level)*(20-color))/20;
|
|
TIM_BL->CCR2 = ((100-level)*color)/20;
|
|
}
|
|
|
|
void turnBacklightOff(void)
|
|
{
|
|
TIM_BL->CCR4 = 0;
|
|
TIM_BL->CCR2 = 0;
|
|
}
|
|
#endif
|