Вроде удалось победить дисплей, вывод работает. Подключил 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, а на регистрах я не видел.