
Подключение дисплея по 8-ми битной шине к stm32f103c8t6 без дополнительных библиотек.
Завалялся у меня TFT LCD дисплей 2.4 дюйма с сенсорным экраном. Никакой информации о нем я не знал. На модуле есть слот для sd карты, стабилизатор напряжения и пара LVC245A.
Судя по контактам управление по 8-битной шине. На помощь пришел поиск по картинкам. Нашел похожие дисплеи за исключением мелких деталей на плате, потом нашел и свой. Так толком и не понял что это за дисплей, но он запустился и работает по командам из Datasheet от ILI9341. Пишут, что это аналоги SPFD5408 и R61520. Я не сравнивал.
На дисплее есть резистивный сенсор. Управление по 4 проводам. Выводы подключены к выводам передачи данных. X+ на LCD_D1, X- на LCD_CS, Y+ на LCD_RS и Y- на LCD_D0. Я так понимаю этот модуль рассчитан на Arduino Uno. Это совершено не удобно, нужно использовать ногу где есть АЦП. А еще это большая потеря скорости отдачи данных на дисплей.
Я отпаял шлейф сенсора и подключил отдельно, но об этом в отдельной статье.
На МК STM32F103C8T6 нет FSMC. Я советую использовать подряд идущие пины одного порта, например A0-A7 для передачи данных на экран. Можно и не с нулевого пина, просто при записи делать побитовый сдвиг.
В моем случае я сделал плату для соединения экрана и модуля с МК STM32F103C8T6 по этому подключение будет таким.
#define DISP_WIDTH 320 // ширина дисплея в пикселях #define DISP_HEIGHT 240 // высота дисплея в пикселях #define TFT_DATA_PORT GPIOB #define TFT_DATA_PIN_START GPIO_PinSource8 #define TFT_DATA_PINS (GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15) #define TFT_PORT GPIOA #define TFT_RST GPIO_Pin_9 #define TFT_CS GPIO_Pin_10 #define TFT_RS GPIO_Pin_11 #define TFT_WR GPIO_Pin_12 #define TFT_RST_LOW() (TFT_PORT->BRR |= TFT_RST) #define TFT_RST_HI() (TFT_PORT->BSRR |= TFT_RST) #define TFT_RS_LOW() (TFT_PORT->BRR |= TFT_RS) #define TFT_RS_HI() (TFT_PORT->BSRR |= TFT_RS) #define TFT_WR_LOW() (TFT_PORT->BRR |= TFT_WR) #define TFT_WR_HI() (TFT_PORT->BSRR |= TFT_WR) #define TFT_CS_LOW() (TFT_PORT->BRR |= TFT_CS) #define TFT_CS_HI() (TFT_PORT->BSRR |= TFT_CS) #define TFT_STROBE() TFT_WR_LOW();TFT_WR_HI()
То есть B8 - B15 я подключил к портам LCD_D0 - LCD_D7. Выводы RST, CS, RS, WR я подключил к A9-A12. Вывод RD я не использую, так как читать с экрана данные мне не нужно, по этому я подключил его к питанию 3.3В.
Команды, которые нам пригодятся для теста дисплея
#define ILI9341_SWRESET 0x01 // программный сброс #define ILI9341_DISPOFF 0x28 // выключение дисплея #define ILI9341_DISPON 0x29 // включение дисплея #define ILI9341_CASET 0x2A // выбор первого и последнего пикселя по оси X #define ILI9341_PASET 0x2B // выбор первого и последнего пикселя по оси Y #define ILI9341_RAMWR 0x2C // запись пикселя в дисплей #define ILI9341_PWCTR1 0xC0 // настройка питания #define ILI9341_SLPIN 0x10 // перевод дисплея в режим сна #define ILI9341_SLPOUT 0x11 // пробуждение #define ILI9341_PIXFMT 0x3A // установка формата пикселя #define ILI9341_MADCTL 0x36 // настройки порядка вывода пикселей на экран
Вспомогательные функции для задержки
#define DWT_CYCCNT *(volatile unsigned long *)0xE0001004 #define DWT_CONTROL *(volatile unsigned long *)0xE0001000 #define SCB_DEMCR *(volatile unsigned long *)0xE000EDFC #define DWT_CTRL_CYCCNTENA_Msk (0x1UL << 0) void delay_us(uint32_t us) { int32_t us_count_tick = us * (SystemCoreClock/1000000); SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT_CYCCNT = 0; DWT_CONTROL |= DWT_CTRL_CYCCNTENA_Msk; while(DWT_CYCCNT < us_count_tick); DWT_CONTROL &= ~DWT_CTRL_CYCCNTENA_Msk; } void delay_ms(uint32_t ms) { int32_t ms_count_tick = ms * (SystemCoreClock/1000); SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT_CYCCNT = 0; DWT_CONTROL|= DWT_CTRL_CYCCNTENA_Msk; while(DWT_CYCCNT < ms_count_tick); DWT_CONTROL &= ~DWT_CTRL_CYCCNTENA_Msk; }
Обмен данными с дисплеем на базе ILI9341 и инициализация
// отправка нашего байта void TFTSend(uint8_t b) { // сбрасываем все пины в 0 TFT_DATA_PORT->BRR = TFT_DATA_PINS; // устанавливаем нужные пины в 1 // если у вас пины подключены начиная с 0-го, смещение можно убрать TFT_DATA_PORT->BSRR = b << TFT_DATA_PIN_START; // дергаем LCD_WR сообщая экрану, что можно читать байт TFT_STROBE(); } // отправка команды void TFTCmd(uint8_t b) { // начало обмена данными TFT_CS_LOW(); // прижимаем LCD_RS к нулю т.к. это команда TFT_RS_LOW(); // отправляем наш байт TFTSend(b); // обмен данными завершен TFT_CS_HI(); } // отправка данных void TFTData(uint8_t b) { // начало обмена данными TFT_CS_LOW(); // LCD_RS подтягиваем к питанию т.к. это данные TFT_RS_HI(); // отдаем байт TFTSend(b); // завершаем обмен TFT_CS_LOW(); } // сброс экрана void TFTReset() { TFT_RST_HI(); TFT_CS_HI(); TFT_RS_HI(); TFT_WR_HI(); delay_us(5); TFT_RST_LOW(); delay_us(15); TFT_RST_HI(); delay_us(15); } // инициализация экрана void TFTInit() { // сбрасываем экран TFTReset(); // шлем команду программного сброса // это сбросит настройки дисплея на значение по умолчанию TFTCmd(ILI9341_SWRESET); // задержка, в описании 5ms delay_ms(5); // программное выключение дисплея TFTCmd(ILI9341_DISPOFF); // обычный режим питания TFTCmd(ILI9341_PWCTR1); // выводим из спящего режима TFTCmd(ILI9341_SLPOUT); delay_us(1500); // включаем дисплей TFTCmd(ILI9341_DISPON); // задаем формат передачи пикселя TFTCmd(ILI9341_PIXFMT); // я не буду копировать всю таблицу из даташита // подробности о различных режимах можно посмотреть в ДШ на ILI9341 // указываем 16 битный формат пикселя TFTData(0x55); // Выключаем инверсию TFTCmd(ILI9341_INVOFF); // устанавливаем порядок выводов пикселя // таблица ниже TFTCmd(ILI9341_MADCTL); TFTData(0x80 | 0x40 | 0x08 | 0x20);
Таблица настроек вывода пикселя
Функции вывода пикселей на экран
// задаем размер блока в который будем слать пиксели void TFTRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { w = w + x - 1; h = h + y - 1; // адрес первого пикселя TFTCmd(ILI9341_CASET); TFTData(x >> 8); TFTData(x); TFTData(w >> 8); TFTData(w); // адрес последнего пикселя TFTCmd(ILI9341_PASET); TFTData(y >> 8); TFTData(y); TFTData(h >> 8); TFTData(h); } uint32_t px, px_count; // заливка области void TFTFill(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t c) { // устанавливаем размер области TFTRect(x, y, w, h); TFTCmd(ILI9341_RAMWR); px_count = w * h; for (px = 0; px < px_count; ++ px) { // шлем цвет пикселя // в моем случае 16 битный режим TFTData(c >> 8); TFTData(c); } }
Команда CASET (Column Address Set) - принимает два байта, номер первого пикселя в строке, по оси X и два байта номер последнего.
Команда PASET (Page Address Set) аналогично принимает принимает по Y.
Для удобства вместо номера конечного пикселя в качестве аргумента функции TFTRect я использую ширину и высоту, а уже в самой функции перевожу в адрес пикселя.
Команда RAMWR это запись наших пикселей. Если взять ширину 5 пикселей а высоту 2, то всего нужно будет передать 2 * 5 = 10 пикселей по 2 байта.
Некоторые цвета для теста
#define COLOR_BLACK 0x0000 #define COLOR_BLUE 0x001F #define COLOR_GREEN 0x07E0 #define COLOR_RED 0xF800 #define COLOR_YELLOW 0xFFE0 #define COLOR_WHITE 0xFFFF
Или можно воспользоваться макросом для вычисления цвета из RGB
#define RGB565(r, g, b) (((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3))
Остается настроить тактовую частоту и порты на вывод с подтяжкой к питанию
void SetSysClockTo72(void) { ErrorStatus HSEStartUpStatus; RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); HSEStartUpStatus = RCC_WaitForHSEStartUp(); if (HSEStartUpStatus == SUCCESS) { RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK2Config(RCC_HCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) { } RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() != 0x08) { } } else { while (1) { } } } void GPIOInit() { RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef gpio; gpio.GPIO_Pin = TFT_RST | TFT_CS | TFT_RS | TFT_WR; gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(TFT_PORT, &gpio); gpio.GPIO_Pin = TFT_DATA_PINS; gpio.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(TFT_DATA_PORT, &gpio); }
И можно что то выводить на дисплей
int main(void) { // настройки тактирования SetSysClockTo72(); // Настройки портов GPIOInit(); // Инициализация дисплея TFTInit(); // Закрасим все черным TFTFill(0, 0, DISP_WIDTH, DISP_HEIGHT, COLOR_BLACK); // Выведем вверху желтую панель TFTFill(0, 0, DISP_WIDTH, 20, COLOR_YELLOW); // синюю внизу TFTFill(0, DISP_HEIGHT - 20, DISP_WIDTH, 20, ILI9341_BLUE); while(1); }
Ну вот и все.
Комментарии к статье: Подключение сенсорного дисплея на ILI9341 (R61520, SPFD5408) к STM32
Здравствуйте! Интересно, но жалко не описана работа с резистивной панелью. Планируется ли в дальнейшем?
Я пользовался двумя статьями с easyelectronics.ru. Первая описывает как опрашивать экран, называется "Работа с резистивным сенсорным экраном". Вторая как откалибровать "Освоение STM32F103VE + TFT LCD + touch screen (часть 4)".
Сайт не дает ссылки запостить, по этому пишу названия. В гуле по названиям и имени сайта они легко находятся.