#include "tft.h" #include #include #include #define TFT_SPI_TIMEOUT_MS 100U #define TFT_FONT_W 5U #define TFT_FONT_H 7U #define TFT_MAX_SCALE 4U #if (TFT_USE_HORIZONTAL == 0U) || (TFT_USE_HORIZONTAL == 1U) #define TFT_X_OFFSET 0U #define TFT_Y_OFFSET 20U #else #define TFT_X_OFFSET 20U #define TFT_Y_OFFSET 0U #endif typedef enum { TFT_GLYPH_SPACE = 0, TFT_GLYPH_DASH, TFT_GLYPH_DOT, TFT_GLYPH_COLON, TFT_GLYPH_0, TFT_GLYPH_A = TFT_GLYPH_0 + 10 } TFT_GlyphIndex_e; static bool tft_initialized = false; static bool tft_transfer_ok = true; static const uint8_t tft_font5x7[][TFT_FONT_H] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* space */ {0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00}, /* - */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C}, /* . */ {0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00}, /* : */ {0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, /* 0 */ {0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x0E}, /* 1 */ {0x0E, 0x11, 0x10, 0x08, 0x04, 0x02, 0x1F}, /* 2 */ {0x1F, 0x08, 0x04, 0x08, 0x10, 0x11, 0x0E}, /* 3 */ {0x08, 0x0C, 0x0A, 0x09, 0x1F, 0x08, 0x08}, /* 4 */ {0x1F, 0x01, 0x0F, 0x10, 0x10, 0x11, 0x0E}, /* 5 */ {0x0C, 0x02, 0x01, 0x0F, 0x11, 0x11, 0x0E}, /* 6 */ {0x1F, 0x10, 0x08, 0x04, 0x02, 0x02, 0x02}, /* 7 */ {0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, /* 8 */ {0x0E, 0x11, 0x11, 0x1E, 0x10, 0x08, 0x06}, /* 9 */ {0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, /* A */ {0x0F, 0x11, 0x11, 0x0F, 0x11, 0x11, 0x0F}, /* B */ {0x0E, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0E}, /* C */ {0x0F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0F}, /* D */ {0x1F, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x1F}, /* E */ {0x1F, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x01}, /* F */ {0x0E, 0x11, 0x01, 0x1D, 0x11, 0x11, 0x0E}, /* G */ {0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, /* H */ {0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, /* I */ {0x1C, 0x08, 0x08, 0x08, 0x08, 0x09, 0x06}, /* J */ {0x11, 0x09, 0x05, 0x03, 0x05, 0x09, 0x11}, /* K */ {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1F}, /* L */ {0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}, /* M */ {0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x11}, /* N */ {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, /* O */ {0x0F, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x01}, /* P */ {0x0E, 0x11, 0x11, 0x11, 0x15, 0x09, 0x16}, /* Q */ {0x0F, 0x11, 0x11, 0x0F, 0x05, 0x09, 0x11}, /* R */ {0x1E, 0x01, 0x01, 0x0E, 0x10, 0x10, 0x0F}, /* S */ {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}, /* T */ {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, /* U */ {0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04}, /* V */ {0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A}, /* W */ {0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11}, /* X */ {0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04}, /* Y */ {0x1F, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1F}, /* Z */ }; static void tft_cs_low(void) { HAL_GPIO_WritePin(TFT_CS_GPIO_Port, TFT_CS_Pin, GPIO_PIN_RESET); } static void tft_cs_high(void) { HAL_GPIO_WritePin(TFT_CS_GPIO_Port, TFT_CS_Pin, GPIO_PIN_SET); } static void tft_dc_command(void) { HAL_GPIO_WritePin(TFT_DC_GPIO_Port, TFT_DC_Pin, GPIO_PIN_RESET); } static void tft_dc_data(void) { HAL_GPIO_WritePin(TFT_DC_GPIO_Port, TFT_DC_Pin, GPIO_PIN_SET); } static void tft_reset_low(void) { HAL_GPIO_WritePin(TFT_RES_GPIO_Port, TFT_RES_Pin, GPIO_PIN_RESET); } static void tft_reset_high(void) { HAL_GPIO_WritePin(TFT_RES_GPIO_Port, TFT_RES_Pin, GPIO_PIN_SET); } static void tft_backlight_on(void) { HAL_GPIO_WritePin(TFT_BLK_GPIO_Port, TFT_BLK_Pin, GPIO_PIN_SET); } static void tft_bus_init(void) { #if TFT_USE_SOFT_SPI GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); GPIO_InitStruct.Pin = TFT_SCK_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(TFT_SCK_GPIO_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = TFT_MOSI_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(TFT_MOSI_GPIO_Port, &GPIO_InitStruct); HAL_GPIO_WritePin(TFT_SCK_GPIO_Port, TFT_SCK_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(TFT_MOSI_GPIO_Port, TFT_MOSI_Pin, GPIO_PIN_RESET); #endif } #if TFT_USE_SOFT_SPI static void tft_write_byte(uint8_t data) { for (uint8_t i = 0U; i < 8U; i++) { HAL_GPIO_WritePin(TFT_SCK_GPIO_Port, TFT_SCK_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(TFT_MOSI_GPIO_Port, TFT_MOSI_Pin, (data & 0x80U) != 0U ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(TFT_SCK_GPIO_Port, TFT_SCK_Pin, GPIO_PIN_SET); data <<= 1U; } } #endif static void tft_write_bytes(const uint8_t *data, uint32_t len) { #if TFT_USE_SOFT_SPI while (len > 0U) { tft_write_byte(*data); data++; len--; } #else while (len > 0U) { #if TFT_HW_SPI_BYTE_MODE && TFT_HW_SPI_PULSE_CS tft_cs_low(); if (HAL_SPI_Transmit(&TFT_SPI_UNIT, (uint8_t *) data, 1U, TFT_SPI_TIMEOUT_MS) != HAL_OK) { tft_transfer_ok = false; tft_cs_high(); return; } tft_cs_high(); data++; len--; #else #if TFT_HW_SPI_BYTE_MODE uint16_t chunk = 1U; #else uint16_t chunk = len > UINT16_MAX ? UINT16_MAX : (uint16_t) len; #endif if (HAL_SPI_Transmit(&TFT_SPI_UNIT, (uint8_t *) data, chunk, TFT_SPI_TIMEOUT_MS) != HAL_OK) { tft_transfer_ok = false; tft_cs_high(); return; } data += chunk; len -= chunk; #endif } #endif } static void tft_write_bytes_continuous(const uint8_t *data, uint32_t len) { #if TFT_USE_SOFT_SPI tft_write_bytes(data, len); #else while (len > 0U) { uint16_t chunk = len > UINT16_MAX ? UINT16_MAX : (uint16_t) len; if (HAL_SPI_Transmit(&TFT_SPI_UNIT, (uint8_t *) data, chunk, TFT_SPI_TIMEOUT_MS) != HAL_OK) { tft_transfer_ok = false; return; } data += chunk; len -= chunk; } #endif } static void lcd_write_reg(uint8_t reg) { tft_cs_low(); tft_dc_command(); tft_write_bytes(®, 1U); tft_cs_high(); tft_dc_data(); } static void lcd_write_data8(uint8_t data) { tft_cs_low(); tft_dc_data(); tft_write_bytes(&data, 1U); tft_cs_high(); } static void lcd_write_data16(uint16_t data) { uint8_t buf[2] = {(uint8_t) (data >> 8), (uint8_t) data}; tft_cs_low(); tft_dc_data(); tft_write_bytes(buf, sizeof(buf)); tft_cs_high(); } static void tft_write_color_block(uint16_t color, uint32_t count) { uint8_t buf[64]; uint8_t high = (uint8_t) (color >> 8); uint8_t low = (uint8_t) color; for (uint32_t i = 0U; i < sizeof(buf); i += 2U) { buf[i] = high; buf[i + 1U] = low; } tft_cs_low(); tft_dc_data(); while (count > 0U && tft_transfer_ok) { uint32_t pixels = count > (sizeof(buf) / 2U) ? (sizeof(buf) / 2U) : count; tft_write_bytes(buf, pixels * 2U); count -= pixels; } tft_cs_high(); } void TFT_WriteColorData(const uint8_t *data, uint32_t len) { if (data == NULL || len == 0U) { return; } tft_cs_low(); tft_dc_data(); #if TFT_HW_SPI_STREAM_COLOR tft_write_bytes_continuous(data, len); #else tft_write_bytes(data, len); #endif tft_cs_high(); } bool TFT_IsReady(void) { return tft_initialized && tft_transfer_ok; } void LCD_Address_Set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { uint16_t xs = x1 + TFT_X_OFFSET; uint16_t xe = x2 + TFT_X_OFFSET; uint16_t ys = y1 + TFT_Y_OFFSET; uint16_t ye = y2 + TFT_Y_OFFSET; lcd_write_reg(0x2A); lcd_write_data16(xs); lcd_write_data16(xe); lcd_write_reg(0x2B); lcd_write_data16(ys); lcd_write_data16(ye); lcd_write_reg(0x2C); } void TFT_FillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color) { if (x >= TFT_LCD_W || y >= TFT_LCD_H || width == 0U || height == 0U) { return; } if ((uint32_t) x + width > TFT_LCD_W) { width = (uint16_t) (TFT_LCD_W - x); } if ((uint32_t) y + height > TFT_LCD_H) { height = (uint16_t) (TFT_LCD_H - y); } LCD_Address_Set(x, y, (uint16_t) (x + width - 1U), (uint16_t) (y + height - 1U)); tft_write_color_block(color, (uint32_t) width * height); } void LCD_Fill(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color) { if (xend <= xsta || yend <= ysta) { return; } TFT_FillRect(xsta, ysta, (uint16_t) (xend - xsta), (uint16_t) (yend - ysta), color); } void TFT_DrawPoint(uint16_t x, uint16_t y, uint16_t color) { TFT_FillRect(x, y, 1U, 1U, color); } static const uint8_t *tft_get_glyph(char ch) { if (ch >= 'a' && ch <= 'z') { ch = (char) (ch - ('a' - 'A')); } if (ch >= '0' && ch <= '9') { return tft_font5x7[TFT_GLYPH_0 + (uint8_t) (ch - '0')]; } if (ch >= 'A' && ch <= 'Z') { return tft_font5x7[TFT_GLYPH_A + (uint8_t) (ch - 'A')]; } switch (ch) { case '-': return tft_font5x7[TFT_GLYPH_DASH]; case '.': return tft_font5x7[TFT_GLYPH_DOT]; case ':': return tft_font5x7[TFT_GLYPH_COLON]; default: return tft_font5x7[TFT_GLYPH_SPACE]; } } static void tft_draw_char(uint16_t x, uint16_t y, char ch, uint16_t fc, uint16_t bc, uint8_t scale) { uint8_t line[(TFT_FONT_W * TFT_MAX_SCALE) * 2U]; const uint8_t *glyph; uint16_t char_w; uint16_t char_h; if (scale == 0U) { scale = 1U; } if (scale > TFT_MAX_SCALE) { scale = TFT_MAX_SCALE; } char_w = TFT_FONT_W * scale; char_h = TFT_FONT_H * scale; if (x >= TFT_LCD_W || y >= TFT_LCD_H) { return; } glyph = tft_get_glyph(ch); LCD_Address_Set(x, y, (uint16_t) (x + char_w - 1U), (uint16_t) (y + char_h - 1U)); tft_cs_low(); tft_dc_data(); for (uint8_t row = 0U; row < TFT_FONT_H; row++) { for (uint8_t repeat_y = 0U; repeat_y < scale; repeat_y++) { uint32_t idx = 0U; for (uint8_t col = 0U; col < TFT_FONT_W; col++) { uint16_t color = (glyph[row] & (1U << col)) ? fc : bc; for (uint8_t repeat_x = 0U; repeat_x < scale; repeat_x++) { line[idx++] = (uint8_t) (color >> 8); line[idx++] = (uint8_t) color; } } tft_write_bytes(line, idx); } } tft_cs_high(); } void TFT_DrawString(uint16_t x, uint16_t y, const char *text, uint16_t fc, uint16_t bc, uint8_t scale) { uint16_t cursor = x; uint16_t advance; if (text == NULL) { return; } if (scale == 0U) { scale = 1U; } if (scale > TFT_MAX_SCALE) { scale = TFT_MAX_SCALE; } advance = (TFT_FONT_W + 1U) * scale; while (*text != '\0') { if (cursor + (TFT_FONT_W * scale) >= TFT_LCD_W) { break; } tft_draw_char(cursor, y, *text, fc, bc, scale); cursor = (uint16_t) (cursor + advance); text++; } } void TFT_Clear(uint16_t color) { LCD_Fill(0U, 0U, TFT_LCD_W, TFT_LCD_H, color); } void LCD_Init(void) { if (tft_initialized) { return; } tft_transfer_ok = true; tft_bus_init(); tft_cs_high(); tft_reset_low(); HAL_Delay(100); tft_reset_high(); HAL_Delay(100); tft_backlight_on(); HAL_Delay(100); lcd_write_reg(0x11); HAL_Delay(120); lcd_write_reg(0x36); #if TFT_USE_HORIZONTAL == 0U lcd_write_data8(0x00); #elif TFT_USE_HORIZONTAL == 1U lcd_write_data8(0xC0); #elif TFT_USE_HORIZONTAL == 2U lcd_write_data8(0x70); #else lcd_write_data8(0xA0); #endif lcd_write_reg(0x3A); lcd_write_data8(0x05); lcd_write_reg(0xB2); lcd_write_data8(0x0C); lcd_write_data8(0x0C); lcd_write_data8(0x00); lcd_write_data8(0x33); lcd_write_data8(0x33); lcd_write_reg(0xB7); lcd_write_data8(0x35); lcd_write_reg(0xBB); lcd_write_data8(0x32); lcd_write_reg(0xC2); lcd_write_data8(0x01); lcd_write_reg(0xC3); lcd_write_data8(0x15); lcd_write_reg(0xC4); lcd_write_data8(0x20); lcd_write_reg(0xC6); lcd_write_data8(0x0F); lcd_write_reg(0xD0); lcd_write_data8(0xA4); lcd_write_data8(0xA1); lcd_write_reg(0xE0); lcd_write_data8(0xD0); lcd_write_data8(0x08); lcd_write_data8(0x0E); lcd_write_data8(0x09); lcd_write_data8(0x09); lcd_write_data8(0x05); lcd_write_data8(0x31); lcd_write_data8(0x33); lcd_write_data8(0x48); lcd_write_data8(0x17); lcd_write_data8(0x14); lcd_write_data8(0x15); lcd_write_data8(0x31); lcd_write_data8(0x34); lcd_write_reg(0xE1); lcd_write_data8(0xD0); lcd_write_data8(0x08); lcd_write_data8(0x0E); lcd_write_data8(0x09); lcd_write_data8(0x09); lcd_write_data8(0x15); lcd_write_data8(0x31); lcd_write_data8(0x33); lcd_write_data8(0x48); lcd_write_data8(0x17); lcd_write_data8(0x14); lcd_write_data8(0x15); lcd_write_data8(0x31); lcd_write_data8(0x34); lcd_write_reg(0x21); lcd_write_reg(0x29); TFT_Clear(TFT_COLOR_BLACK); if (tft_transfer_ok) { tft_initialized = true; } } void TFT_Init(void) { LCD_Init(); } void TFT_ShowBootScreen(void) { if (!tft_initialized) { TFT_Init(); if (!tft_initialized) { return; } } tft_transfer_ok = true; uint16_t band_h = TFT_LCD_H / 3U; TFT_FillRect(0U, 0U, TFT_LCD_W, band_h, TFT_COLOR_BLUE); TFT_FillRect(0U, band_h, TFT_LCD_W, band_h, TFT_COLOR_GREEN); TFT_FillRect(0U, (uint16_t) (band_h * 2U), TFT_LCD_W, (uint16_t) (TFT_LCD_H - band_h * 2U), TFT_COLOR_RED); TFT_DrawString(16U, 22U, "DM LCD", TFT_COLOR_WHITE, TFT_COLOR_BLUE, 3U); TFT_DrawString(16U, (uint16_t) (band_h + 24U), "BOOT", TFT_COLOR_BLACK, TFT_COLOR_GREEN, 3U); TFT_DrawString(16U, (uint16_t) (band_h * 2U + 24U), "WAIT IMU", TFT_COLOR_WHITE, TFT_COLOR_RED, 2U); } const char *TFT_RobotModeText(RobotMode_t mode) { switch (mode) { case NORMAL_MODE: return "NORMAL"; case SYS_ERROR_OCCURRED: return "ERROR"; case REMOTE_NOT_CONNECTED: return "REMOTE OFF"; case REMOTE_CONNECTED: return "REMOTE ON"; case AUTO_SHOOTING_MODE: return "AUTO SHOOT"; case IMU_CALIBERATION_MODE: return "IMU CAL"; default: return "UNKNOWN"; } } static uint16_t tft_mode_color(RobotMode_t mode) { switch (mode) { case NORMAL_MODE: return TFT_COLOR_GREEN; case SYS_ERROR_OCCURRED: case REMOTE_NOT_CONNECTED: return TFT_COLOR_RED; case REMOTE_CONNECTED: return TFT_COLOR_YELLOW; case AUTO_SHOOTING_MODE: return TFT_COLOR_CYAN; case IMU_CALIBERATION_MODE: return TFT_COLOR_MAGENTA; default: return TFT_COLOR_LIGHT_GRAY; } } static int16_t tft_temperature_to_tenths(float temperature_c) { return (int16_t) ((temperature_c * 10.0f) + (temperature_c >= 0.0f ? 0.5f : -0.5f)); } static void tft_format_temperature(char *buf, size_t len, int16_t temperature_tenths) { uint16_t abs_temp; uint16_t integer; uint8_t decimal; size_t idx = 0U; char digits[5]; uint8_t digits_len = 0U; if (buf == NULL || len < 6U) { return; } if (temperature_tenths < 0) { abs_temp = (uint16_t) (-temperature_tenths); buf[idx++] = '-'; } else { abs_temp = (uint16_t) temperature_tenths; } integer = abs_temp / 10U; decimal = (uint8_t) (abs_temp % 10U); do { digits[digits_len++] = (char) ('0' + (integer % 10U)); integer /= 10U; } while (integer > 0U && digits_len < sizeof(digits)); while (digits_len > 0U && idx + 1U < len) { buf[idx++] = digits[--digits_len]; } if (idx + 4U < len) { buf[idx++] = '.'; buf[idx++] = (char) ('0' + decimal); buf[idx++] = ' '; buf[idx++] = 'C'; } buf[idx] = '\0'; } static void tft_draw_status_layout(void) { TFT_Clear(TFT_COLOR_BLACK); TFT_DrawString(16U, 16U, "DM LCD", TFT_COLOR_CYAN, TFT_COLOR_BLACK, 3U); TFT_FillRect(16U, 54U, 248U, 2U, TFT_COLOR_DARK_GRAY); TFT_DrawString(16U, 76U, "TEMP:", TFT_COLOR_YELLOW, TFT_COLOR_BLACK, 2U); TFT_DrawString(16U, 132U, "MODE:", TFT_COLOR_YELLOW, TFT_COLOR_BLACK, 2U); } void TFT_ShowStatus(float temperature_c, RobotMode_t mode) { static bool layout_ready = false; static int16_t last_temperature_tenths = INT16_MIN; static RobotMode_t last_mode = (RobotMode_t) 0xFF; int16_t temperature_tenths = tft_temperature_to_tenths(temperature_c); char temperature_text[16]; const char *mode_text = TFT_RobotModeText(mode); if (!tft_initialized) { TFT_Init(); if (!tft_initialized) { return; } } if (!layout_ready) { tft_transfer_ok = true; tft_draw_status_layout(); if (!tft_transfer_ok) { return; } layout_ready = true; } if (temperature_tenths != last_temperature_tenths) { tft_transfer_ok = true; uint16_t temperature_color = temperature_tenths >= 550 ? TFT_COLOR_RED : TFT_COLOR_WHITE; tft_format_temperature(temperature_text, sizeof(temperature_text), temperature_tenths); TFT_FillRect(96U, 68U, 168U, 32U, TFT_COLOR_BLACK); TFT_DrawString(100U, 70U, temperature_text, temperature_color, TFT_COLOR_BLACK, 3U); if (tft_transfer_ok) { last_temperature_tenths = temperature_tenths; } } if (mode != last_mode) { tft_transfer_ok = true; TFT_FillRect(96U, 124U, 184U, 32U, TFT_COLOR_BLACK); TFT_DrawString(100U, 126U, mode_text, tft_mode_color(mode), TFT_COLOR_BLACK, 3U); if (tft_transfer_ok) { last_mode = mode; } } }