Кто любит RISC в жизни, заходим, не стесняемся.
Ответить

ili9341 и STM32F4Discovery

Ср ноя 22, 2017 12:15:25

Добрый день. Пытаюсь подключить экран с контроллером ili9341 к stm32f4-discovery.
У модуля с экраном 9 ног для подключения по SPI (VCC, GND, CS, RESET, D/C, MOSI, SCK, LED, MISO)
К STM подключил так:
Код:
TFT               STM32
----------
VCC                    5v
GND               GND
CS               PB11
RESET            PB12
DC               PB10
MOSI            PA7
SCK               PA5
LED               3v
MISO            PA6


При включении подсветка включается, экран равномерно белый.

Для проверки работы, пытаюсь прочитать из экрана его ID. Согласно datasheet, надо отправить команду 0x4, и 4 раза прочитать из экрана. В последних трех ответах будет ID (стр. 91 даташита). Команду шлю и читаю ответ в бесконечном цикле.
Для отладки, полученные значения шлю в UART. В ответ приходит 0, 0x3F и дальше все ответы 0xFF.
Для проверки работы SPI я слал с SPI1 на плате на SPI2 и обратно. Все работает, отправляется и принимает.
Буду очень благодарен, если подскажете, что делаю не правильно. Во вложении проект из Keil5.
Весь код специально написал прямо в main(), чтоб не искать по функциям. Убрал только работу с uart.
Пробовал устанавливать LSBFIRST (порядок бит) и менять скорость baute rate, но результат тот же.
Код:
#include "main.h"

/*
Подключение экрана:
TFT               STM32
----------
VCC            5v
GND               GND
CS               PB11
RESET            PB12
DC               PB10
MOSI            PA7
SCK               PA5
LED               3v
MISO            PA6
*/



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;

              sprintf(str_buffer, "d: %02X f: %02X g: %02X \r\n",d,f,g);
         uart_send_string(str_buffer);
         
         
      };
};


Добавлено after 19 minutes 33 seconds:
Извиняюсь, случайно создал тему не в том разделе.
Можно перенести в ARM?
Вложения
TFT_22_ili9341.rar
Проект в Keil5
(736.14 KiB) Скачиваний: 111

Re: ili9341 и STM32F4Discovery

Ср ноя 22, 2017 14:40:49

Добрый день. Пытаюсь подключить экран с контроллером ili9341 к stm32f4-discovery.
Для проверки работы, пытаюсь прочитать из экрана его ID. Согласно datasheet, надо отправить команду 0x4, и 4 раза прочитать из экрана. В последних трех ответах будет ID (стр. 91 даташита). Команду шлю и читаю ответ в бесконечном цикле.

Те LCD на ILI934x, что у меня есть, ни один не отвечает на команды чтения. И сколько я читал в форумах - и у других людей аналогично. Так что - забейте на чтение.
Инициализируйте и не будет белого экрана. Примеров инициализации в инете можно найти массу.
И на моей последней плате, на которой использую ILI934x, он подключен по совмещённому MOSI/MISO. И режим SPI тогда нужно программировать соответствующий. Хотя и на обычный SPI можно подключить.
В качестве примера подключения поищите даташит на отладочную плату STM32F429-DISCOVERY - на ней как раз стоит этот самый ILI9341 на SPI.

Можно перенести в ARM?

А какое отношение чип контроллера LCD имеет к ARM? Если его подключить к другому МК, его работа разве как-то изменится?

Re: ili9341 и STM32F4Discovery

Ср ноя 22, 2017 20:39:28

После инициализации надо что то записать в экран - иначе ничего не происходит. Например очистить.
У меня на ili9341 отлично читает из дисплея. Вот код для F103:

Для F4 чуть подправить работу с SPI. Я запускал с дискавери, но код не сохранился.

Re: ili9341 и STM32F4Discovery

Пт ноя 24, 2017 13:41:47

Вроде удалось победить дисплей, вывод работает. Подключил 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:
Код:
void LCD_SendCommand(uint8_t data) {
  LCD_DC_set(0);
  spi1_send(data);
}


void LCD_SendFastData(uint8_t data) {
      while((SPI1->SR & SPI_SR_TXE) == 0){};
       SPI1->DR = data;
}

void LCD_SendData(uint8_t data) {
  LCD_DC_set(1);
  spi1_send(data);
}


void spi1_send(uint8_t data){
      while((SPI1->SR & SPI_SR_TXE) == 0){};
    SPI1->DR = data;
};


В spi_send надо дождаться только флага TXE, что означает, что буфер свободен. BSY тут ждать не зачем, этот флаг проверяется только когда DC меняется, чтобы не порушить обмен.
Для отправки две функции, LCD_SendData и LCD_SendFastData. Вторая появилась, когда экспериментировал со скоростью заливки всего экрана. Она вызывается 76800 раз, поэтому даже просто
убрав лишний вызов LCD_DC_set(1) и перенеся запись в SPI-DR (без вызова spi1_send) удалось ускорить на заметную глазу величину. Соответственно, если перенести запись в SPI-DR прямо в функцию заливки экрана, можно еще чуть ускорить.

Заливка экрана с помощью DMA:
Код:
void LCD_Fill_dma(uint16_t color) {
   LCD_SetCursorPosition(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);
   LCD_SendCommand(ILI9341_GRAM);
   
   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 бит

   DMA2_Stream3 -> CR |= DMA_SxCR_EN;   //Запустить копирование

   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;
   
}


Ну и собственно как проверял скорость обновления:

Код:
cur_time=get_ms();
while(get_ms() - cur_time < 20000){
   color=(color == 0x00F8 ? 0xF800 : 0x00F8);      
      LCD_Fill_dma(color);
   j++;
};
   
cur_time=get_ms();
while(get_ms() - cur_time < 20000){
   color=(color == 0x00F8 ? 0xF800 : 0x00F8);      
      LCD_Fill(color);
   l++;
};
   
j=j/20;
l=l/20;


get_ms возвращает нарастающее время в ms. Посчитал количество обновлений за 20 секунд с DMA и просто spi_send.

С DMA видел другой метод работы - цикличный режим и отслеживание количества запусков по прерыванию. Не думаю, что это сильно скажется на скорости, а кода больше :) Я решил просто запускать копирование еще раз.

Код инициализации дисплея я взял из чьего-то проекта (в IAR был). Оттуда же схема работы с экраном, но переделал работу с флагами SPI, поскольку она была реализована не правильно и работало медленно.
Вообщем сейчас на глаз работает довольно шустро, хотя заливка экрана глазом видна.

Если есть идеи как еще ускорить вывод, или есть явные ошибки в реализации, буду рад услышать.

Проект во вложении, надеюсь кому-нибудь будет полезен. В сети много примеров на SPL и HAL, а на регистрах я не видел.
Вложения
TFT_22_ili9341.rar
Проект в Keil5
(990.6 KiB) Скачиваний: 155

Re: ili9341 и STM32F4Discovery

Пт ноя 24, 2017 23:31:06

По DMA не проверял, но если просто цветом заливать и больше ничего не делать то получается примерно 29 fps (заливок экрана в секунду).
Скорость SPI 36 мГц.
36 000 000 / 320 / 240 / 16 ~ 29.3 Герц

Re: ili9341 и STM32F4Discovery

Сб ноя 25, 2017 00:17:24

Вроде удалось победить дисплей, вывод работает. Подключил DMA, получилось примерно так: заливка экрана с DMA - 6 раз в секунду

Очень медленно. У меня без проблем работает на SCLK=45МГц.

Добавлено after 5 minutes 7 seconds:
Скорость SPI 36 мГц

У Вас видимо CLK периферийной шины ==72МГц, как и частота ядра? И видимо STM32? И видимо SPI в режиме 16бит/слово?
И FIFO на SPI у STM32 нету.
При 2 тактах шины на 1 бит SPI на диаграмме SCLK нет пауз?

Re: ili9341 и STM32F4Discovery

Вт ноя 28, 2017 15:46:44

Да, действительно, работало медленно, потому что тактирование было оставлено по умолчанию.
Вышеприведенный пример удалось "разогнать" до 34 в режиме DMA и 32 в SPI.
Так что вроде экран не такой и тормоз :)
Ответить