Как стать автором
Обновить

Комментарии 32

На этом контроллере stm32f103c8t6 максимальная скорость передачи по SPI как раз без DMA.
Да и экран маленький, не надо ему столько. Попробуйте лучше с ILI 9341 SPI. И вытянуть 30 ФПС.
Кстати да тоже столкнулся с таким что ногодрыгом получается быстрее, ну и на самом деле для этого дисплея дма точно без надобности.
Если нагрузить МК параллельно вычислениями и работой с другой периферией, то с DMA будет быстрее, а так да, можно и на прерываниях сделать, работать будет так же.
Насчет ILI 9341 давно думал, надо заказать китайцам, но думаю 30 фпс по spi при полном обновлении экрана врятли получится, а вот через параллельный интерфейс вполне возможно.

Не отрицаю. Но я так и оставил ногодрыг у себя, а всю остальную периферию на дма выкинул. Я сейчас не помню точно, но вышло что скорость по дма была ниже чем мне требовалось

подтверждаю и хочу добавить что порой хватает только процессора на всё:
Я на подобном OLED дисплее и stm32f100 cделал «умные» часы в корпусе обычных наручных круглых часов: вообще всё реализовал без DMA и питались они от обычной часовой CR2032 батарейки и даже 8мгц хватало чтоб и флешку читать и 30фпс обновлять и отрисовывать интерфейс с плавными свайпами вправо и влево, при этом, во время свайпа, оба окна продолжали динамически обновляться (экранного буфера не было из-за 2к озу — рисовал динамически).
аж прям ностальгия ) картинка рисовалась в двух буферах и шло переключение между буферами в одном рисуем второй показываем и оставалось еще много ресурсов
На этом контроллере stm32f103c8t6 максимальная скорость передачи по SPI как раз без DMA.

Как это возможно?
uint16_t i;
for(i=0;i<SSD1306_HEIGHT*SSD1306_WIDTH;i++)
{
if(color==WHITE)
displayBuff[i]=0xFF;
else if(color==BLACK)
displayBuff[i]=0;
}
Это а ) ужасно. вынесите условие из цикла
б) неверно. В 8 раз больше чем нужно.
Более того, SSD1306_HEIGHT*SSD1306_WIDTH нужно вынести в отдельную константу
Это необязательно. А вот переменную цикла безопасней сделать не uint16_t а просто int. Потому что при копипасте на чуть больший дисплей например 320х240х16бит она уже переполнится. А 32 двух разрядной архитектуре stm32 все равно как индексировать массив по 32 бита нативной переменной или по 16 битной. То есть выигрыша от 16битного индекса нет, а опасность есть.
То есть я бы написал просто " int i"
Конечно не всегда. Но речь идёт именно о stm32.
На других платформах она как правило как минимум 16 на 8 и 16 разрядных машинах и 32 на 32 и 64 разрядных машинах. Впрочем по стандарту должно быть что то вроде size_t для индексов. И даже на 8 и 16 разрядных машинах лучше использовать int, а не int32_t.
Данный фрагмент кода никак не специфицен для stm32, его можно перенести на любую другую платформу без какой либо переделки. Поэтому, если вы уж хотите сделать код действительно безопасным, о таком типе данных как int следует забыть, и использовать типы данных объявленные в файле stdint.h.
Ваше же предложение о замене uint16_t на просто int (кстати, почему именно на int, а не на unsigned int?) снижает безопасность кода. Более того, так как стандарт C не определяет размер данных, вы можете даже просто сменив компилятор — словить массу проблем за счет использования int'ов вместо uint32_t. И в теории такое может произойти банально за счет обновления версии компилятора.
The actual size of the integer types varies by implementation. The standard requires only size relations between the data types and minimum sizes for each data type:

The relation requirements are that the long long is not smaller than long, which is not smaller than int, which is not smaller than short. As char's size is always the minimum supported data type, no other data types (except bit-fields) can be smaller.

The minimum size for char is 8 bits, the minimum size for short and int is 16 bits, for long it is 32 bits and long long must contain at least 64 bits.

en.wikipedia.org/wiki/C_data_types
Вы все еще хотите использовать тип данных int?
Именно int. Во первых есть куча старых 8-16 бит компиляторов, в которых просто нет stdint.h. Во вторых, на 16 битной платформе int32 и uint32 ужасно неэффективны, да и память доступна через окна. И в третьих можно на эту тему спорить без конца и остаться при своем мнении. Я не навязываю своего стиля, а высказываю мнение. С — древний язык, я пишу на нем лет 30 уже и рекомендации к стилям менялись неоднократно. Тип int и его арифметика реализованы на всех платформах с максимальной эффективностью для данной платформы. А его ограничения просто нужно держать в голове…
Увидел, плохо сделал.
Можно исправить так:
if(color==WHITE)
    memcpy(displayBuff,0xFF,BUFFER_SIZE);
else if(color==BLACK)
    memcpy(displayBuff,0x00,BUFFER_SIZE);
с помощью библиотеки CMSIS

Уже за это спасибо
В свое время мне было лень писать самому графику для этого дисплея и я нашел в сети подходящую библиотеку. А вот с цветными дисплеями я играться люблю. Особенно, если он позволяет микроконтроллеру читать свою память. Так можно фреймбуфер держать только в дисплее и если нужно что-то там модифицировать, то достаточно прочесть интересующий блок памяти, изменить его и записать обратно.
Что то я не понимаю:
1. Вам нужно передать 128*64=8192 бита по SPI
2. SPI передает 1 бит за такт частоты 18 МГц.
3. Максимальная частота обновления 18000000/8192=2197Гц~2.2кГц
4. Откуда 4.2кГц?
Так вышло потому, что SPI тут сконфигурирован на частоте 36Мгц. SPI1 сидит на шине APB2, частота которой 72Мгц, делитель SPI выставлен на 2. При такой частоте и получаем 4.2кГц.
На самом деле я сам удивился, почему оно так заработало (Cube не позволяет сконфигурировать SPI на такой частоте, хотя через регистры все работает). Было бы конечно неплохо посмотреть с помощью осциллографа как там на самом деле, но такой возможности сейчас нет. Конечно никакого толку от такой частоты нет, все было сделано сугубо в академических целях в процессе обучения.
Ну тогда понятно, все сходится.
А что касается настроек через библиотеки (КУБ — это тоже библиотеки) и напрямую, то часто сохраняются старые ограничения, которые давно уже сняты. Единственное, чем следует руководствоваться — это документация на конкретный МК и, если она допускает высокие частоы, то их и следует использовать.
И это совсем не академические цели, а вполне себе продакшн — умение выжать все соки из конкретного МК.
Только в доке на SSD1306 максимальная частота клока 10 МГц.
А не подскажите страницу? У меня явного ограничения найти не получилось, да и все прекрасно работает.
Боюсь, что «все прекрасно работает» — это не аргумент.
Таблицы 13.3 и 13.4 дата — период тактовой частоты интерфейса SPI — не менее 100нс.
Да, из таблицы 13.4: Clock Cycle Time — 100 ns Min.
while((SPI1->SR & SPI_SR_BSY))
Кстати проверка флага SPI_SR_BSY при отправке данных, не лучшее решение, так вы проверяете есть ли еще данные в сдвиговом регистре, ну и как пишут в RM:
The BSY flag must be used with caution: refer to Section28.5.10: SPI status flags and
Procedure for disabling the SPI on page767.

Вместо этого лучше проверять освободился ли буфер — SPI_SR_RXNE, так по идее должно быть еще и быстрее (немного)
«SPI_SR_RXNE»
Это --Rx buffer not empty (RXNE)

правильно будет TXE перед отправкой:
Tx buffer empty flag (TXE)
Вы абсолютно правы, в данном случае — TXE. Бывает )
Решил таки произвести замер скорости передачи строки в 1000 символов, разница… ну она все же есть:

SPI_SR_TXE
image

SPI_SR_BSY
image
  1. Зачем конструкции вида:


    SPI1->CR1|=SPI_CR1_SSM;//Программный NSS
    SPI1->CR1|=SPI_CR1_SSI;//NSS - high
    SPI1->CR2|=SPI_CR2_TXDMAEN;//Разрешить запросы DMA
    SPI1->CR1|=SPI_CR1_SPE;//включить SPI1

    Для установки 1 бита каждый раз вычитывать volatile-регистр — ненужный расход памяти и тактов. Компилятор не будет это оптимизировать.
    Лучше свернуть к 1 записи:


    SPI1->CR1|= SPI_CR1_SSM |  ... ;

  2. Обращение к BSRR регистру неправильно:


    #define CS_SET GPIOA->BSRR  |=  GPIO_BSRR_BS2

    Этот регистр только для записи. Читать его ненужно — только записывать:


    #define CS_SET GPIOA->BSRR  =  GPIO_BSRR_BS2

Вы говорите про дисплей на базе чипа ssd1306, но так и не называете сам дисплей. Почему это важно? Уверен, на плате дисплея есть места для резисторов, убирая или добавляя которые Вы устанавливаете значения контактов BS[2:0] контроллера (ssd1306 datasheet, 13), что в свою очередь определяет интерфейс между MCU и ssd1306.

Статья будет в разы интереснее, если Вы расскажете о всех доступных интерфейсах в данной реализации дисплея. Особенно интересен 3-wire SPI, где отсутствует линия D/C#, а по SPI передаются данные в формате 9 бит: 1 бит – выбор данные-команда, 8 бит – значение.

Для чего нужен режим 3-wire SPI: чтобы не дёргать каждый раз контакт D/C#, отправлять команды и данные на дисплей используя SPI-DMA. Упрощает программу, упрощает использование. Данный контроллер имеет возможность частичного обновления экрана (ssd1306 datasheet, 35-36). Для установки границ нужно отправлять сначала команды, а потом данные. При использовании 3-wire SPI Вы сформируете один буфер с командами и данными, запустите SPI-DMA, займётесь иными полезными операциями.

О том, как реализовать 9-бит SPI, тоже желательно расписать! На сериях F0 и F7 можно выбрать количество бит данных SPI от 4 до 16, а в других сериях только 8 или 16 ЕМНИП.

Прекрасная библиотека LVGL поможет раскрыть весь потенциал дисплеев на контроллерах ssd1306 и ему подобных. Библиотека реализует частичное обновление экрана.

Вы выбрали тему, которую можно разбить на группу интересных статей. Постарайтесь доносить в каждой статье что-то одно. В данной статье не было полного обзора ни на дисплей (платка, резисторы), ни на контроллер дисплея (а вся серия стоит внимания). Понимаю желание настраивать периферию непосредственно через запись значений в регистры. Однако, если Вы говорите про дисплей, то лучше бы использовать библиотеку HAL, чтобы не отвлекать читателя кодом. А код напрашивается на переработку.

Начало серии статей положено. Надеюсь на Ваши успехи и дальнейшее изложение успехов в статьях.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории