/** ****************************************************************************** * @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>= 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