Добрый день. Пытаюсь подключить экран с контроллером ili9341 к stm32f4-discovery. У модуля с экраном 9 ног для подключения по SPI (VCC, GND, CS, RESET, D/C, MOSI, SCK, LED, MISO) К STM подключил так:
При включении подсветка включается, экран равномерно белый.
Для проверки работы, пытаюсь прочитать из экрана его ID. Согласно datasheet, надо отправить команду 0x4, и 4 раза прочитать из экрана. В последних трех ответах будет ID (стр. 91 даташита). Команду шлю и читаю ответ в бесконечном цикле. Для отладки, полученные значения шлю в UART. В ответ приходит 0, 0x3F и дальше все ответы 0xFF. Для проверки работы SPI я слал с SPI1 на плате на SPI2 и обратно. Все работает, отправляется и принимает. Буду очень благодарен, если подскажете, что делаю не правильно. Во вложении проект из Keil5. Весь код специально написал прямо в main(), чтоб не искать по функциям. Убрал только работу с uart. Пробовал устанавливать LSBFIRST (порядок бит) и менять скорость baute rate, но результат тот же.
int main(void) { //Инициализация UART2, используется для вывода отладки uart_init();
//Включение тактирования порта B (для DC, RST, CS) RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Режим output для пинов 10, 11, 12 GPIOB->MODER |= GPIO_MODER_MODE10_0|GPIO_MODER_MODE11_0|GPIO_MODER_MODE12_0; //Скорость порта Very High speed для 10, 11, 12 GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEED10_0|GPIO_OSPEEDR_OSPEED10_1; GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEED11_0|GPIO_OSPEEDR_OSPEED11_1; GPIOB->OSPEEDR |= GPIO_OSPEEDR_OSPEED12_0|GPIO_OSPEEDR_OSPEED12_1; //Установка подтяжки: //Обнуление на всякий случай GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPD10_0|GPIO_PUPDR_PUPD10_1); GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPD11_0|GPIO_PUPDR_PUPD11_1); GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPD12_0|GPIO_PUPDR_PUPD12_1); //Установка подтяжки к GND (Pull-down) GPIOB->PUPDR |= GPIO_PUPDR_PUPD10_1|GPIO_PUPDR_PUPD11_1|GPIO_PUPDR_PUPD12_1;
//Перед инициализацией SPI, устанавливаю CS в HIGH, т.е. отключаю выбор (активное состояние LOW) GPIOB->BSRR |= GPIO_BSRR_BS11;
//Инициализация SPI //Включить тактирование SPI1 RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //Включить тактирование порта А RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Включение AF5 на пинах 5,6,7 GPIOA->AFR[0] |= GPIO_AFRL_AFSEL5_0|GPIO_AFRL_AFSEL5_2; GPIOA->AFR[0] |= GPIO_AFRL_AFSEL6_0|GPIO_AFRL_AFSEL6_2; GPIOA->AFR[0] |= GPIO_AFRL_AFSEL7_0|GPIO_AFRL_AFSEL7_2; //Режим альтернативной функция для PA5,PA6,PA7 GPIOA->MODER |= GPIO_MODER_MODE5_1|GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1; //Скорость High speed GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED5_0|GPIO_OSPEEDR_OSPEED5_1; GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED6_0|GPIO_OSPEEDR_OSPEED6_1; GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED7_0|GPIO_OSPEEDR_OSPEED7_1; //Режим push-pull GPIOA->OTYPER &= ~GPIO_OTYPER_OT5; GPIOA->OTYPER &= ~GPIO_OTYPER_OT6; GPIOA->OTYPER &= ~GPIO_OTYPER_OT7; //Отключение подтяжки на PA5, PA6, PA7 GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD5_0|GPIO_PUPDR_PUPD5_1); GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD6_0|GPIO_PUPDR_PUPD6_1); GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD7_0|GPIO_PUPDR_PUPD7_1); //Режим работы по двум линиям SPI1->CR1 &= ~SPI_CR1_BIDIMODE; //Отключение CRC SPI1->CR1 &= ~SPI_CR1_CRCEN; //8-bit передача SPI1->CR1 &= ~SPI_CR1_DFF; //Програмное управление SS SPI1->CR1 |= SPI_CR1_SSI|SPI_CR1_SSM; //Режим работы SPI 0 SPI1->CR1 &= ~SPI_CR1_CPHA; SPI1->CR1 &= ~SPI_CR1_CPOL; //Выбор скорости /8 SPI1->CR1 &= ~SPI_CR1_BR; SPI1->CR1 |= (SPI_CR1_BR_1); //Установка флага Master SPI1->CR1 |= SPI_CR1_MSTR; //Выбор формата фрейма, какой бит передавать первым //SPI1->CR1 |= SPI_CR1_LSBFIRST; //Включение SPI1 SPI1->CR1 |= SPI_CR1_SPE;
//Установка CS в LOW, для выбора slave GPIOB->BSRR |= GPIO_BSRR_BR11;
//Судя по описанию ili9341, RST активен кошда LOW. //Т.е. для работы, нужно установить в HIGH GPIOB->BSRR |= GPIO_BSRR_BS12;
//Экрану буду слать только команду на чтение, поэтому DC в LOW GPIOB->BSRR |= GPIO_BSRR_BR10;
//Перед началом работы надо отправить команду SOFWARE RESET (0x1) //Пока бит TXE не будет установлен, просто ждать while(!(SPI1->SR & SPI_SR_TXE)){}; //Команда для чтения из экрана SPI1->DR=0x1; //Дисплею надо 5ms, чтобы выполнить reset, тупая пауза с запасом for(int i=0; i < 10000000; i++);
while(1) { uint8_t d,f,g=0; //переменные, чтобы складывать ответы экрана
//Пока бит TXE не будет установлен, просто ждать while(!(SPI1->SR & SPI_SR_TXE)){}; //Команда для чтения из экрана 0x4 //В ответ вернет 4 байта. В первом мусор, остальные 3 с ID SPI1->DR=0x4; //Сразу после отправки команды, в ответ сначала придет мусор d=SPI1->DR;
//Чтобы получить ответ на команду, надо продолжить тактировать //передавая 0 (NOP) while(!(SPI1->SR & SPI_SR_TXE)){}; SPI1->DR=0; // 1 из 3 байт ответа d=SPI1->DR;
//Чтобы получить ответ на команду, надо продолжить тактировать //передавая 0 while(!(SPI1->SR & SPI_SR_TXE)){}; SPI1->DR=0; // 2 из 3 байт ответа f=SPI1->DR;
//Чтобы получить ответ на команду, надо продолжить тактировать //передавая 0 while(!(SPI1->SR & SPI_SR_TXE)){}; SPI1->DR=0; // 3 из 3 байт ответа g=SPI1->DR;
Добрый день. Пытаюсь подключить экран с контроллером ili9341 к stm32f4-discovery. Для проверки работы, пытаюсь прочитать из экрана его ID. Согласно datasheet, надо отправить команду 0x4, и 4 раза прочитать из экрана. В последних трех ответах будет ID (стр. 91 даташита). Команду шлю и читаю ответ в бесконечном цикле.
Те LCD на ILI934x, что у меня есть, ни один не отвечает на команды чтения. И сколько я читал в форумах - и у других людей аналогично. Так что - забейте на чтение. Инициализируйте и не будет белого экрана. Примеров инициализации в инете можно найти массу. И на моей последней плате, на которой использую ILI934x, он подключен по совмещённому MOSI/MISO. И режим SPI тогда нужно программировать соответствующий. Хотя и на обычный SPI можно подключить. В качестве примера подключения поищите даташит на отладочную плату STM32F429-DISCOVERY - на ней как раз стоит этот самый ILI9341 на SPI.
После инициализации надо что то записать в экран - иначе ничего не происходит. Например очистить. У меня на ili9341 отлично читает из дисплея. Вот код для F103: Спойлер
Вроде удалось победить дисплей, вывод работает. Подключил DMA, получилось примерно так: заливка экрана с DMA - 6 раз в секунду, заливка экрана просто по SPI 3 раза в секунду. Использую SPI1, BR стоит 0, т.е. /2 Делал без использования STL и HAL, вдруг кому будет пример интересен, прикладываю проект в keil5.
Ну и немного кода с комментариями, буду благодарен за критику, как можно улучшить и ускорить.
Инициализация SPI1 (у SPI1 частота шины больше, чем у SPI2, соответственно будет побыстрее)
Код:
//Инициализация SPI1 на пинах PA5, PA6, PA7 //Включить тактирование SPI1 RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //Включить тактирование порта А RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Включение AF5 на пинах 5,6,7 GPIOA->AFR[0] |= GPIO_AFRL_AFSEL5_0|GPIO_AFRL_AFSEL5_2; GPIOA->AFR[0] |= GPIO_AFRL_AFSEL6_0|GPIO_AFRL_AFSEL6_2; GPIOA->AFR[0] |= GPIO_AFRL_AFSEL7_0|GPIO_AFRL_AFSEL7_2; //Режим альтернативной функция для PA5,PA6,PA7 GPIOA->MODER |= GPIO_MODER_MODE5_1|GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1; //Скорость High speed GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED5_0|GPIO_OSPEEDR_OSPEED5_1; GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED6_0|GPIO_OSPEEDR_OSPEED6_1; GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED7_0|GPIO_OSPEEDR_OSPEED7_1; //Режим push-pull GPIOA->OTYPER &= ~GPIO_OTYPER_OT5; GPIOA->OTYPER &= ~GPIO_OTYPER_OT6; GPIOA->OTYPER &= ~GPIO_OTYPER_OT7; //Отключение подтяжки на PA5, PA6, PA7 GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD5_0|GPIO_PUPDR_PUPD5_1); GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD6_0|GPIO_PUPDR_PUPD6_1); GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD7_0|GPIO_PUPDR_PUPD7_1); //Режим работы по двум линиям SPI1->CR1 &= ~SPI_CR1_BIDIMODE; //Отключение CRC SPI1->CR1 &= ~SPI_CR1_CRCEN; //8-bit передача SPI1->CR1 &= ~SPI_CR1_DFF; //Программное управление SS SPI1->CR1 |= SPI_CR1_SSI|SPI_CR1_SSM; //Режим работы SPI 0 SPI1->CR1 &= ~SPI_CR1_CPHA; SPI1->CR1 &= ~SPI_CR1_CPOL; //Выбор скорости /2 SPI1->CR1 &= ~SPI_CR1_BR; //Установка флага Master SPI1->CR1 |= SPI_CR1_MSTR; //DMA на TX SPI1->CR2 |= SPI_CR2_TXDMAEN; //Выбор формата фрейма, какой бит передавать первым //SPI1->CR1 |= SPI_CR1_LSBFIRST; //Включение SPI1 SPI1->CR1 |= SPI_CR1_SPE;
Для управления линией DS функция:
Код:
void LCD_DC_set(uint8_t data){ while((SPI1->SR & SPI_SR_BSY) == 1){}; //Дождаться окончания передачи, перед изменением режима if(data == 0){ GPIOA->BSRR |= GPIO_BSRR_BR3; }else{ GPIOA->BSRR |= GPIO_BSRR_BS3; }; }
Основной момент тут в том, что перед тем как сменить режим работы с Data на Command надо дождаться окончания текущей передачи. Функции отправки данных в SPI:
В spi_send надо дождаться только флага TXE, что означает, что буфер свободен. BSY тут ждать не зачем, этот флаг проверяется только когда DC меняется, чтобы не порушить обмен. Для отправки две функции, LCD_SendData и LCD_SendFastData. Вторая появилась, когда экспериментировал со скоростью заливки всего экрана. Она вызывается 76800 раз, поэтому даже просто убрав лишний вызов LCD_DC_set(1) и перенеся запись в SPI-DR (без вызова spi1_send) удалось ускорить на заметную глазу величину. Соответственно, если перенести запись в SPI-DR прямо в функцию заливки экрана, можно еще чуть ускорить.
LCD_DC_set(1); //Цвета у экрана 16 бит и передавать их удобнее тоже по 16 бит. //Для этого надо переключить SPI1 в режим 16 бит //Сначала надо выключить передачу. Я не жду тут флага BSY, поскольку перед этим устанавливал DC и там этот //флаг проверяется. SPI1->CR1 &= ~SPI_CR1_SPE; //16бит SPI1->CR1 |= SPI_CR1_DFF; //Включаем SPI SPI1->CR1 |= SPI_CR1_SPE;
//NDTR 16 битный регистр и значение 76800 в него не влезет. Поэтому передаем двумя кусками по 38400 DMA2_Stream3 -> NDTR = LCD_PIXEL_COUNT/2; //Количество байт для передачи
DMA2_Stream3 -> PAR = (uint32_t)&(SPI1 -> DR); //Адрес переферии DMA2_Stream3 -> M0AR = (uint32_t)&color; //Адрес в памяти DMA2_Stream3 -> CR |= DMA_SxCR_CHSEL_0|DMA_SxCR_CHSEL_1; //Канал 3 DMA2_Stream3 -> CR &= ~DMA_SxCR_MINC; //Инкремент в памяти не нужен, заливка будет одним цветом DMA2_Stream3 -> CR |=DMA_SxCR_DIR_0; //Направление, из памяти в переферию DMA2_Stream3 -> CR |=DMA_SxCR_MSIZE_0; //16 бит DMA2_Stream3 -> CR |=DMA_SxCR_PSIZE_0; //16 бит
while((DMA2->LISR & DMA_LISR_TCIF3) == 0) {}; //подождать окончания копирования DMA2->LIFCR |= DMA_LIFCR_CTCIF3; //Сбросить флаг окончания
//Копирование 2 половины данных DMA2_Stream3 -> NDTR = LCD_PIXEL_COUNT/2; //Количество байт для передачи DMA2_Stream3 -> CR |= DMA_SxCR_EN; //Запустить копирование
while((DMA2->LISR & DMA_LISR_TCIF3) == 0) {}; //Подождать окончания копирования DMA2->LIFCR |= DMA_LIFCR_CTCIF3; //Сбросить флаг
//После заливки экрана надо вернуть режим работы SPI в 8 бит //Ждем пока закончится передача while((SPI1->SR & SPI_SR_BSY) == 1){}; //Выключаю SPI, устанавливаю 8 бит, включаю SPI SPI1->CR1 &= ~SPI_CR1_SPE; SPI1->CR1 &= ~SPI_CR1_DFF; SPI1->CR1 |= SPI_CR1_SPE;
get_ms возвращает нарастающее время в ms. Посчитал количество обновлений за 20 секунд с DMA и просто spi_send.
С DMA видел другой метод работы - цикличный режим и отслеживание количества запусков по прерыванию. Не думаю, что это сильно скажется на скорости, а кода больше Я решил просто запускать копирование еще раз.
Код инициализации дисплея я взял из чьего-то проекта (в IAR был). Оттуда же схема работы с экраном, но переделал работу с флагами SPI, поскольку она была реализована не правильно и работало медленно. Вообщем сейчас на глаз работает довольно шустро, хотя заливка экрана глазом видна.
Если есть идеи как еще ускорить вывод, или есть явные ошибки в реализации, буду рад услышать.
Проект во вложении, надеюсь кому-нибудь будет полезен. В сети много примеров на SPL и HAL, а на регистрах я не видел.
Вложения:
Комментарий к файлу: Проект в Keil5 TFT_22_ili9341.rar [990.6 KiB]
Скачиваний: 309
Качественное и безопасное устройство, работающее от аккумулятора, должно учитывать его физические и химические свойства, профили заряда и разряда, их изменение во времени и под влиянием различных условий, таких как температура и ток нагрузки. Мы расскажем о литий-ионных аккумуляторных батареях EVE и нескольких решениях от различных китайских компаний, рекомендуемых для разработок приложений с использованием этих АКБ. Представленные в статье китайские аналоги помогут заменить продукцию западных брендов с оптимизацией цены без потери качества.
По DMA не проверял, но если просто цветом заливать и больше ничего не делать то получается примерно 29 fps (заливок экрана в секунду). Скорость SPI 36 мГц. 36 000 000 / 320 / 240 / 16 ~ 29.3 Герц
Компания EVE выпустила новый аккумулятор серии PLM, сочетающий в себе высокую безопасность, длительный срок службы, широкий температурный диапазон и высокую токоотдачу даже при отрицательной температуре.
Эти аккумуляторы поддерживают заряд при температуре от -40/-20°С (сниженным значением тока), безопасны (не воспламеняются и не взрываются) при механическом повреждении (протыкание и сдавливание), устойчивы к вибрации. Они могут применяться как для автотранспорта (трекеры, маячки, сигнализация), так и для промышленных устройств мониторинга, IoT-устройств.
У Вас видимо CLK периферийной шины ==72МГц, как и частота ядра? И видимо STM32? И видимо SPI в режиме 16бит/слово? И FIFO на SPI у STM32 нету. При 2 тактах шины на 1 бит SPI на диаграмме SCLK нет пауз?
Да, действительно, работало медленно, потому что тактирование было оставлено по умолчанию. Вышеприведенный пример удалось "разогнать" до 34 в режиме DMA и 32 в SPI. Так что вроде экран не такой и тормоз
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 32
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения