// ======================================================================================== // Example code to drive the LCD F-51661GNCJU-MLW-AA from OPTREX CORPORATION (2002) // LCD Driver IC: Hitachi HD66766 // IM0=Low, IM1=High, 16-bit interface, 80-mcu-family // MCU: STM32 F407 VET6 // ========================================================(C) 2/2026 Ma_Baker============= // ======================================================================================== // Disclaimer: // This code can't be used directly as is, but has to be included in the STM32IDE framework // ======================================================================================== #include "main.h" #include "image_data.h" #define LCD_WIDTH 120 #define LCD_HEIGHT 160 #define INFOBAR_HEIGHT 16 // Using STM32 FSMC mode to communicate with the LCD in 16-bit mode // The FSMC (Flexible Static Memory Controller) is a hardware engine inside the STM32 // that allows the MCU to treat the LCD as if it were a piece of internal RAM. /* FSMC Address Calculation for 16-bit mode: * PD13 is FSMC_A18. * Hardware shifts address by 1 for 16-bit, so A18 pin corresponds to Bit 19 in code. * RS = 0 (Command): 0x60000000 * RS = 1 (Data): 0x60000000 + (1 << 19) = 0x60080000 */ #define LCD_REG_ADDR ((uint32_t)0x60000000) #define LCD_DATA_ADDR ((uint32_t)0x60080000) #define LCD_REG (*((volatile uint16_t *) LCD_REG_ADDR)) #define LCD_DATA (*((volatile uint16_t *) LCD_DATA_ADDR)) /* 16-bit RGB565 Color Definitions */ #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define WHITE 0xFFFF // ---------------------------------------------------------------------------- // Helper functions // ---------------------------------------------------------------------------- // Basic function to set Register-Values in the LCD --------------------------- // ---------------------------------------------------------------------------- // In STM32 FSMC mode it's just a write to a specific memory locations, all // LCD control lines are driven by the STM32. // To use other MCU one has to implement this function by setting the control // signals and write directly to the 16-data-bus of the LCD // ---------------------------------------------------------------------------- void LCD_WriteReg(uint16_t reg, uint16_t data) { LCD_REG = reg; LCD_DATA = data; } // Use the two LED D2 and D3 connected to PA6 and PA7 as debugging LEDs ---------- void Debug_Blink(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, uint8_t count) { for(uint8_t i = 0; i < count; i++) { HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET); // RESET = 0V = LED ON HAL_Delay(400); HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET); // SET = 3.3V = LED OFF HAL_Delay(400); } } // Initialize gray-scale palette ----------------------------------------------- void LCD_WriteAggressiveGamma(void) { // 32 levels, 6-bit each (0 to 63). // This curve reaches the "Max Brightness zone" (60+) much earlier. uint8_t gamma[32] = { 0, 2, 4, 7, 10, 14, 18, 22, // Dark to Mid-Dark 26, 30, 34, 38, 42, 46, 50, 53, // Mid-range 55, 57, 59, 60, 61, 62, 63, 63, // Bright range (reaching 63 early) 63, 63, 63, 63, 63, 63, 63, 63 // Absolute Max White }; for (uint16_t i = 0; i < 16; i++) { uint8_t low_val = gamma[i * 2]; uint8_t high_val = gamma[i * 2 + 1]; // Write to palette registers 0x30 - 0x3F LCD_WriteReg(0x30 + i, (high_val << 8) | low_val); } } // Fill GRAM with COLOR -------------------------------------------------------- void LCD_FillArea(uint16_t color, uint16_t from_line, uint16_t number_of_lines) { // Fill number_of_lines lines with color for (uint16_t y = from_line; y < number_of_lines; y++) { LCD_WriteReg(0x21, (y << 8) | 0); // Start of line LCD_REG = 0x22; for (uint16_t x = 0; x < 120; x++) { LCD_DATA = color; } } } // Load testimage in GRAM (Testimage binary data is stored in "image_data.h" --- void LCD_DrawImage(const uint16_t* pData) { uint32_t counter = 0; for (uint16_t y = 0; y < 160; y++) { // Set Address Counter to the start of the line (AD15-8 = Y, AD7-0 = X) LCD_WriteReg(0x21, (y << 8) | 0x00); // Prepare to write to GRAM LCD_REG = 0x22; for (uint16_t x = 0; x < 120; x++) { // Write the pixel data from flash LCD_DATA = pData[counter++]; } } } // Displaying 3-digit numbers in FG-color on a BG-color ----------------------------------- const uint8_t font8x16_digits[10][16] = { {0x00,0x3E,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x3E,0x00,0x00}, // 0 {0x00,0x06,0x0E,0x1E,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x3F,0x00,0x00}, // 1 {0x00,0x3E,0x61,0x01,0x01,0x01,0x02,0x0C,0x10,0x20,0x40,0x40,0x40,0x7F,0x00,0x00}, // 2 {0x00,0x3E,0x61,0x01,0x01,0x02,0x1C,0x02,0x01,0x01,0x01,0x61,0x3E,0x00,0x00,0x00}, // 3 {0x00,0x06,0x0E,0x16,0x26,0x46,0x46,0x7F,0x06,0x06,0x06,0x06,0x06,0x1F,0x00,0x00}, // 4 {0x00,0x7F,0x40,0x40,0x40,0x7C,0x02,0x01,0x01,0x01,0x01,0x61,0x3E,0x00,0x00,0x00}, // 5 {0x00,0x1C,0x22,0x40,0x40,0x7C,0x62,0x61,0x61,0x61,0x61,0x61,0x3E,0x00,0x00,0x00}, // 6 {0x00,0x7F,0x01,0x01,0x02,0x02,0x04,0x04,0x08,0x08,0x10,0x10,0x10,0x10,0x00,0x00}, // 7 {0x00,0x3E,0x61,0x61,0x61,0x3E,0x61,0x61,0x61,0x61,0x61,0x61,0x3E,0x00,0x00,0x00}, // 8 {0x00,0x3E,0x61,0x61,0x61,0x61,0x3F,0x01,0x01,0x01,0x22,0x1C,0x00,0x00,0x00,0x00} // 9 }; void LCD_DrawChar8x16(uint16_t x, uint16_t y, uint8_t num, uint16_t color, uint16_t bg) { if (num > 9) return; for (uint8_t i = 0; i < 16; i++) { // 16 rows // Set Address Counter to the start of this specific row // AD15-8 = Y, AD7-0 = X LCD_WriteReg(0x21, ((y + i) << 8) | (x & 0xFF)); LCD_REG = 0x22; // Prepare to write data uint8_t row_data = font8x16_digits[num][i]; for (int8_t j = 7; j >= 0; j--) { // 8 pixels wide if (row_data & (1 << j)) { LCD_DATA = color; // White pixel } else { LCD_DATA = bg; // Red background } } } } void LCD_PrintCT(uint16_t x, uint16_t y, int value, uint16_t color, uint16_t bg) { // Ensure value is 3 digits (000 - 999) if (value > 999) value = 999; uint8_t hundreds = value / 100; uint8_t tens = (value / 10) % 10; uint8_t units = value % 10; // Draw the three digits side by side LCD_DrawChar8x16(x, y, hundreds, color, bg); LCD_DrawChar8x16(x + 8, y, tens, color, bg); LCD_DrawChar8x16(x + 16, y, units, color, bg); } // ---------------------------------------------------------------------------- // Initializing the LCD // ---------------------------------------------------------------------------- void LCD_Init(void) { HAL_Delay(250); // Give LCD some time after MCU Reset // Reg. 0x00: Start Oscillation LCD_WriteReg(0x00, 0x0001); HAL_Delay(50); // Reg. 0x01: Driver Output Control (scan lines) LCD_WriteReg(0x01, 0x0013); // 1/160 duty as mentioned in the LCD tech-spec. // Reg. 0x02: LCD Driving Waveform LCD_WriteReg(0x02, 0x0000); // DC-mode (No inversion) (0x0000) // Reg. 0x03/0x0C: POWER Control 1 + 2 LCD_WriteReg(0x0C, 0x0000); // Bits 2-0 (VC bits) = 000 - Set regulator to 0.92xVcc (don't throttle power) LCD_WriteReg(0x03, 0x950C); // 0x950C=0b1001010100001100 // Bits 15-12 (1001): 1/12 Bias (as in the LCD tech-spec) // Bit 11 (0): Voltage Inverter OFF // Bits 10-8 (101): Booster Factor (results in 17.5V - 18V at the LCD when powered with 3.3V) // Bits 6-4 (000): DC Speed 0 // Bits 3-2 (11): AP Current 3 = Large // Bit 1 (0): no sleep mode // Bit 0 (0): standby mode canceled // Reg. 0x04: Contrast LCD_WriteReg(0x04, 0x0678); // setting initial contrast VR:6 CT:120=0x78 // Reg. 0x05: Entry mode LCD_WriteReg(0x05, 0x0030); // 0x0030: I/D1=1, I/D0=1 (Increment), AM=0 (Horizontal) // Reg. 0x14: Map RAM to physical lines 0-159 LCD_WriteReg(0x14, 0x9F00); // Reg. 0x16/17: Define Window (120x160) LCD_WriteReg(0x16, (119 << 8) | 0); LCD_WriteReg(0x17, (159 << 8) | 0); // Reg. 0x0B: Clock LCD_WriteReg(0x0B, 0x0000); // No clock division: Max. refresh speed } // ---------------------------------------------------------------------------- // main() : Testing the LCD (Testimage and Contrast Control) // ---------------------------------------------------------------------------- int main(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET); // Initialize Debug-LEDs HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET); Debug_Blink(GPIOA, GPIO_PIN_6, 3); // Start with 3 blinks on LED D3 HAL_Delay(1000); LCD_Init(); // Initialize LCD LCD_WriteGammaPalette(); // Set High Gamma Palette uint16_t test_colors[] = { // Define a color container 0xF800, // Pure Red 0x07E0, // Pure Green 0x001F, // Pure Blue 0xFFFF, // Pure White 0x0000 // Pure Black }; LCD_DrawImage(test_image); // Loading testimage uint8_t color_index = 0; // RED stripe at the top LCD_FillArea(test_colors[color_index], 0, INFOBAR_HEIGHT); uint8_t ct_val = 120; // Start value for contrast uint8_t vr_val = 6; uint16_t reg04_val = 0x0678; LCD_PrintCT(24, 0, (int)vr_val, 0xFFFF, 0xF800); // Print the current contrast values on the screen LCD_PrintCT(78, 0, (int)ct_val, 0xFFFF, 0xF800); LCD_WriteReg(0x07, 0x0103); // Gate Driver ON, Display ON HAL_Delay(3000); LCD_WriteReg(0x07, 0x0107); // Invert the screen for 3 sec and back HAL_Delay(3000); LCD_WriteReg(0x07, 0x0103); while (1) // Use the two buttons K0 abd K1 on the devboard for changing the LCD contrast { if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == GPIO_PIN_RESET) // Button K0 (PE4) -> INCREASE CT { if (ct_val < 127) ct_val++; reg04_val = (vr_val << 8) | (ct_val & 0x7F); LCD_WriteReg(0x04, reg04_val); color_index = 0; // Re-write RED strip LCD_FillArea(test_colors[color_index], 0, INFOBAR_HEIGHT); LCD_PrintCT(24, 0, (int)vr_val, 0xFFFF, 0xF800); // Print actual values LCD_PrintCT(78, 0, (int)ct_val, 0xFFFF, 0xF800); HAL_Delay(150); // Debounce and repeat speed } if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) == GPIO_PIN_RESET) // Button K1 (PE3) -> DECREASE CT { if (ct_val > 0) ct_val--; reg04_val = (vr_val << 8) | (ct_val & 0x7F); LCD_WriteReg(0x04, reg04_val); color_index = 0; // re-write RED strip LCD_FillArea(test_colors[color_index], 0, INFOBAR_HEIGHT); LCD_PrintCT(24, 0, (int)vr_val, 0xFFFF, 0xF800); // Print actual values LCD_PrintCT(78, 0, (int)ct_val, 0xFFFF, 0xF800); HAL_Delay(150); // Debounce and repeat speed } } }