Русский
Русский
English
Статистика
Реклама

Stm32f4

STM32F429 IL9341 LVGL, DOOM1

27.01.2021 22:06:12 | Автор: admin

Воспользовавшись Новогодними праздниками, продолжил поднимать элементы на своей плате. Первым делом после того как запустился дисплей провел тест Lvgl графической библиотеки. Результаты показались удовлетворительным. Около 20 FPF. Иногда были просадки но в целом, без использования DMA и контроллера Chrom-ART, который есть на борту картинка плавная. ART использовать не получится, потому что дисплей с интерфейсом SPI. Это было не первое ограничение с которым я столкнулся на пути оптимизации с целью увеличения FPS


Статью скорее надо рассматривать в образовательных или исследовательских целях. Я пришел к выводу, что если разрабатывать устройство то надо использовать все фичи, а то получится такой испытательный стенд. На котором не работает Chrom-ART

LVGL

Подключить https://lvgl.io было не сложно, тем более есть два порта под Discovery STM32F429 https://github.com/lvgl/lvportstm32f746_disco

Выделение полнокадрового буфера у меня не получилось по причине нехватки памяти в одном сегменте. Решил задействовать SDRAM. Тут меня ждало разочарование с передачей буфера экрана за один раз. Экран 3202402 = 153600 байт на фрэйм. STM32F429 DMA на SPI имеет ограничения по причине 16 битного внутреннего счетчика. Соответственно, чтобы передать кадр надо выполнить 153600 / 65535 = ~2,3 транзакции. Не очень ситуация. Это и оказалось бутылочным горлышком. Надо ждать когда будет отправлен последний кусок чтобы начинать отрисовывать новый фрэйм

DMA restriction 16 bit

There's also an underlying hardware issue: the DMA's NDTR (counting) register is 16-bit long.

Ниже то что получил в ответ на вопрос по проблеме https://forum.lvgl.io/t/stm32f429-ili9341-spi/4187

В голову приходит решение сделать Double buffer. В одном рисуем, отдаем на отправку и продолжаем рисовать в другом, если он освободился. В свою очередь оправка смотрит, есть ли готовый буфер и отправляет его переходя к другому. ART позволяет это делать почти автоматически. В изначальной реализации https://github.com/floppes/stm32doom это используется

lvgl forum answer

Your buffer setting (NULL, LV_HOR_RES_MAX * LV_VER_RES_MAX) is too high (as you already noticed by yourself

Normally the buffer is set around to be LVHORRESMAX * 20 (or 40, or more or less).You are using DMA. DMA in STM32F4/7 or STM32FH7 can only handle up buffer length up to 16 bit (means 65535 bytes).

You set your buffer to LVHORRESMAX * LVVERRES_MAX. For a 320 x 240 that is 76 800.
As you have a 16-bit color values, it means 76800 * 2 = 153600 bytes. Thats more than double of that what the DMA can handle at once.
As only less than a half of the necessary data is transferred to the display, frame rate gets more than doubled.

This is true if lvgl is updating the entire (or nearly the entire) display at once.
When only smaller parts of the display will be redrawn everything is ok. As the used buffer is smaller (less than the 16-bit limit of DMA).

You can of course change your flush function to split the display transfer into smaller parts,
but that will not change the overall time you need for transfer all the display data over SPI.
The bottleneck is SPI.

But with the method to have the buffer within the SDRAM (external memory), I think you will slow down lvgl.

Using the external SDRAM would make sense only when using it as a real frame buffer for display, in the case the STM32F4/7 would use it directly for (parallel) display driving.

Как приготовить Doom в Кубе

За основу взят порт https://github.com/floppes/stm32doom

https://github.com/floppes/stm32doomПо файловой системе изменений почти никаких. Надо только смонтировать диск. Подключаем по SDIO через 4е линии. Не забываем ставить делитель хотя бы на 4. Без него не проходит инициализация флэшки. Я ставлю на 10. После инициализации впрочем можно вернуть частоту назад.

 FRESULT res = f_mount(&SDFatFs, (TCHAR const*)SDPath, 1);

Добавляем секцию sdram. На ней будет организована куча. Так же обозначим для каких файлов будем использовать sdram для статических переменных. Еще я задействовал CCMRAM под стек

/* Highest address of the user mode stack */_estack = ORIGIN(CCMRAM) + LENGTH(CCMRAM);/* end of "RAM" Ram type memory *//* Internal Memory Map*/MEMORY{  CCMRAM    (xrw)    : ORIGIN = 0x10000000,   LENGTH = 64Krom (rx): ORIGIN = 0x08000000, LENGTH = 2048Kram (rwx)   : ORIGIN = 0x20000000, LENGTH = 192Ksdram (rwx) : ORIGIN = 0xD004B000, LENGTH = 7892K /* first 300K is used as LCD frame buffer */}SECTIONS {/* external SDRAM */.sdram (NOLOAD) :{. = ALIGN(4);*(.sdram .sdram.*)bin/chocdoom/i_video.o(COMMON)bin/chocdoom/r_bsp.o(COMMON)bin/chocdoom/w_wad.o(COMMON)bin/chocdoom/r_main.o(COMMON)bin/chocdoom/r_plane.o(COMMON)} > sdram...

Теперь подправим метод _sbrk для управления динамической памятью кучи через sdram. Реализация sbrkr на ваше усмотрение

#define HEAP_SIZE0x00700000 // 7 MB/* heap */__attribute__ ((section(".sdram")))static char heap[HEAP_SIZE];caddr_t _sbrk_r (struct _reent* r, int incr){...

Верхушка памяти отведена под два буфера экрана

    sdram (rwx) : ORIGIN = 0xС004B000, LENGTH = 7892K /* first 300K is used as LCD frame buffer */

Напоминаю, что FMC может управлять до двух банков, соответственно адреса банков

  1. 0xС0000000

  2. 0xD0000000

Отрисовка экрана находится в файле i_video.c

Надо еще было развернуть байты, но я это сделал в инициализации палитры

// swapped = (num>>8) | (num<<8)

Первый эксперимент был по отрисовке линии, второй всего буфера

void I_FinishUpdate (void){        int x, y;        byte index;        lcd_vsync = false;        for (y = 0; y < SCREENHEIGHT; y++)        {                for (x = 0; x < SCREENWIDTH; x++)                {                        index = I_VideoBuffer[y * SCREENWIDTH + x];                        ((uint16_t*)lcd_frame_buffer)[y * SCREENWIDTH + x] = rgb565_palette[index];                        // swapped = (num>>8) | (num<<8);                }//              lcd_draw_line (lcd_frame_buffer, y);        }        lcd_draw_buff (lcd_frame_buffer);        lcd_refresh ();        lcd_vsync = true;}

После вывода линий, стал подключать RTOS и уперся в выделение памяти. Тюнингом удалось дойти до заставки. Но потом сваливался в static void HardFault_Handler(void)

Еще момент, в том что автор немного подправил реализацию Chocdoom. Были падения по выходам за границы массива при внутреннем построении сцены. Как известно STM32F429 Discavery имеет 2.4" QVGA TFT LCD дисплей. Я же взял 320x240 SPI

Если бы не ограничение в передаче через DMA в устройство подключенное на SPI можно было бы обойтись без многопоточности. Просто отсылать после отрисовки буфер. И возможно ждать перед началом поставки следующего. Но STM32F4 может пересылать 0xFFFF байт за раз, а это при экране 320x240 и 2 байта на пиксель надо делать более двух раз

Первый и последний эксперимент был сделан с посылкой и ожиданием в 3 этапа. Ниже функция которая это делает

void fill_display(SPI_HandleTypeDef *hspi,                DMA_HandleTypeDef *hdma_spi_tx,                uint8_t *buff,                uint32_t size){        uint16_t max_transaction_size = 0xFFFF;        uint16_t send_size;        uint32_t offset = 0;        uint8_t done = 0;        HAL_GPIO_WritePin(GPIOC, DC_Pin, GPIO_PIN_SET);        HAL_GPIO_WritePin(GPIOC, CS_Pin, GPIO_PIN_RESET);        do {                if (offset + max_transaction_size <= size){                        send_size = max_transaction_size;                } else {                        send_size = size - offset;                        done = 1;                }                HAL_SPI_Transmit_DMA(hspi, buff + offset, send_size);                while (HAL_DMA_STATE_BUSY == HAL_DMA_GetState(hdma_spi_tx))                    { continue; }                offset += max_transaction_size;        } while(done == 0);        HAL_GPIO_WritePin(GPIOC, DC_Pin, GPIO_PIN_RESET);}

Сделал ради эксперимента. Скорость впечатлила. По сравнению с ESP32 из моих прошлых экспериментов http://personeltest.ru/aways/habr.com/ru/post/512130/ картинка побежала явно быстрее. ESP32 использовал RTOS но имел на борты PSRAM против SDRAM у STM32F429. Последовательный интерфейс против параллельного. Два ядра у ESP32 против одного у STM32F429. STM32F429 "немного" дороже раза этак в два

В дополнении я начал прикручивать кнопки и внезапно Doom стал работать задом на перед. Демка крутилась в обратную сторону. Открывать двери надо было спиной..

На этом моменте эксперимент был закончен

Материалы по теме

В планах

В планах собрать STM32F750x8. На борту у нее всего 64K flash но можно исполнять инструкции из внешней flash памяти. Расширяемый бюджетный STM32F7

3.9 Quad-SPI memory interface (QUADSPI)

All devices embed a Quad-SPI memory interface, which is a specialized communication interface targeting Single, Dual or Quad-SPI Flash memories. It can work in:

Direct mode through registers.

External flash status register polling mode.

Memory mapped mode. Up to 256 Mbytes external flash are memory mapped, supporting 8, 16 and 32-bit access. Code execution is supported. The opcode and the frame format are fully programmable. Communication can be either in Single Data Rate or Dual Data Rate.

Code execution is supported - Hello ESP32!

Схема всего этого поделья

Подробнее..

Что делать, если поймал HardFault?

22.07.2020 14:19:29 | Автор: admin
Что делать, если поймал HardFault? Как понять, каким событием он был вызван? Как определить строчку кода, которая привела к этому? Давайте разбираться.

Всем привет! Сложно найти программиста микроконтроллеров, который ни разу не сталкивался с тяжелым отказом. Очень часто он никак не обрабатывается, а просто остаётся висеть в бесконечном цикле обработчика, предусмотренном в startup файле производителя. В то же время программист пытается интуитивно найти причину отказа. На мой взгляд это не самый оптимальный путь решения проблемы.

В данной статье я хочу описать методику анализа тяжелых отказов популярных микроконтроллеров с ядром Cortex M3/M4. Хотя, пожалуй, методика слишком громкое слово. Скорее, я просто разберу на примере то, как я анализирую возникновение тяжелых отказов, и покажу, что можно сделать в подобной ситуации. Я буду использовать программное обеспечение от IAR и отладочную плату STM32F4DISCOVERY, так как эти инструменты есть у многих начинающих программистов. Однако это совершенно не принципиально, данный пример можно адаптировать под любой процессор семейства и любую среду разработки.




Падение в HardFault


Перед тем, как пытаться анализировать HatdFault, нужно в него попасть. Есть много способов это сделать. Мне сразу же пришло на ум попытаться переключить процессор из состояния Thumb в состояние ARM, путем задания адреса инструкции безусловного перехода четным числом.

Небольшое отступление. Как известно, микроконтроллеры семейства Cortex M3/M4 используют набор ассемблерных инструкций Thumb-2 и всегда работают в режиме Thumb. Режим ARM не поддерживается. Если попытаться задать значение адреса безусловного перехода(BX reg) со сброшенным младшим битом, то произойдет исключение UsageFault, так как процессор будет пытаться переключить своё состояние в ARM. Подробнее об этом можно почитать в [1](пункты 2.8 THE INSTRUCTION SET; 4.3.4 Assembler Language: Call and Unconditional Branch).

Для начала, я предлагаю смоделировать безусловный переход по четному адресу на языке C/C++. Для этого я создам функцию func_hard_fault, затем попытаюсь вызвать ее по указателю, предварительно уменьшив адрес указателя на единицу. Сделать это можно следующим образом:

void func_hard_fault(void);void main(void){  void (*ptr_hard_fault_func) (void); //Объявляем указатель на функцию отказа  ptr_hard_fault_func = reinterpret_cast<void(*)()>(reinterpret_cast<uint8_t *>(func_hard_fault) - 1); //Присваиваем ему значение со сброшенным младшим битом  ptr_hard_fault_func(); //Вызов функции отказа    while(1) continue;}void func_hard_fault(void) //Функция, вызов которой приведет к отказу{  while(1) continue;}


Посмотрим с отладчиком, что же у меня получилось.



Красным я выделил текущую инструкцию перехода по адресу в РОН R1, который содержит чётный адрес перехода. Как результат:



Еще проще данную операцию можно выполнить с помощью ассемблерных вставок:

void main(void){  //Переходим на свой же адрес  asm("LDR R1, =0x0800029A"); //Псевдо-инструкция для записи значения переходf  asm("BX r1"); //Переход по адресу R1    while(1) continue;}


Ура, мы попали в HardFault, миссия выполнена!

Анализ HardFault


Откуда мы попали в HardFault?


На мой взгляд, самое важное узнать то, откуда мы попали в HardFault. Сделать это не сложно. Для начала напишем свой обработчик ситуации HardFault.

extern "C"{void HardFault_Handler(void){  }}


Теперь поговорим о том, как выяснить, как мы здесь оказались. В процессорном ядре Cortex M3/M4 есть такая замечательная вещь, как сохранение контекста [1](пункт 9.1.1 Stacking). Если говорить простым языком, при возникновении любого исключения, содержимое регистров R0-R3, R12, LR, PC, PSR сохраняется в стеке.



Здесь для нас самым важным будет регистр PC, который содержит информацию о текущей выполняемой инструкции. Так как значение регистра было сохранено в стек во время возникновения исключительной ситуации, то там будет содержаться адрес последней выполняемой инструкции. Остальные регистры менее важны для анализа, но нечто полезное можно выцепить и из них. LR адрес возврата последнего перехода, R0-R3, R12 значения, которые могут подсказать в каком направлении двигаться, PSR просто общий регистр состояния программы.

Предлагаю выяснить значения регистров в обработчике. Для этого был написал такой код (подобный код я встречал в одном из файлов производителя):

extern "C"{void HardFault_Handler(void){  struct   {    uint32_t r0;    uint32_t r1;    uint32_t r2;    uint32_t r3;    uint32_t r12;    uint32_t lr;    uint32_t pc;    uint32_t psr;  }*stack_ptr; //Указатель на текущее значение стека(SP)   asm(      "TST lr, #4 \n" //Тестируем 3ий бит указателя стека(побитовое И)      "ITE EQ \n"   //Значение указателя стека имеет бит 3?      "MRSEQ %[ptr], MSP  \n"  //Да, сохраняем основной указатель стека      "MRSNE %[ptr], PSP  \n"  //Нет, сохраняем указатель стека процесса      : [ptr] "=r" (stack_ptr)      );  while(1) continue;}}


Как результат, имеем значения всех сохраняемых регистров:



Что же здесь произошло? Сначала мы завели указатель стека stack_ptr, тут все понятно. Сложности возникают с ассемблерной вставкой (если есть потребность в понимании ассемблерных инструкций для Cortex, то рекомендую [2]).

Почему мы просто не сохранили стек через MRS stack_ptr, MSP? Дело в том, что ядра Cortex M3/M4 имеют два указателя стека [1](пункт 3.1.3 Stack Pointer R13) основной указатель стека MSP и указатель стека процесса PSP. Они используются для разных режимов работы процессора. Не буду подробно углубляться в то, для чего это сделано и как это работает, однако дам небольшое пояснение.

Для выяснения режима работы процессора(используется в данный MSP или PSP), нужно проверить третий бит регистра связи. Этот бит определяет то, какой указатель стека используется для возвращения из исключения. Если этот бит установлен, то это MSP, если нет, то PSP. В целом, большинство приложений, написанных на C/C++ используют только MSP, и эту проверку можно и не делать.

Так что же по итогу? Имея список сохраняемых регистров, мы можем без труда определить то, откуда программа упала в HardFault по регистру PC. PC указывает на адрес 0x0800029A, который и является адресом нашей ломающей инструкции. Не стоит также забывать о важности значений остальных регистров.

Причина возникновения HardFault


На самом деле, мы можем также выяснить причину возникновения HardFault. В этом нам помогут два регистра. Hard fault status register (HFSR) и Configurable fault status register (CFSR; UFSR+BFSR+MMFSR). Регистр CFSR состоит из трёх регистров: Usage fault status register (UFSR), Bus fault status register (BFSR), Memory management fault address register (MMFSR). Почитать про них можно, например, в [1] и [3].

Предлагаю посмотреть, что эти регистры выдают в моём случае:



Во-первых, установлен бит HFSR FORCED. Значит произошёл отказ, который не может быть обработан. Для дальнейшей диагностики следует изучить остальные регистры статуса отказов.

Во-вторых, выставлен бит CFSR INVSTATE. Это значит, что произошёл UsageFault, так как процессор попытался выполнить инструкцию, которая незаконно использует EPSR.

Что такое EPSR? EPSR Execution program status register. Это внутренний регистр PSR специального регистра состояния программы(который, как мы помним, сохраняется в стеке). Двадцать четвертый бит этого регистра указывает на текущее состояние процессора (Thumb или ARM). Это как раз может определять нашу причину возникновения отказа. Давайте попробуем его считать:

volatile uint32_t EPSR = 0xFFFFFFFF;    asm(    "MRS %[epsr], PSP  \n"    : [epsr] "=r" (EPSR)      );


В результате выполнения получаем значение EPSR = 0.



Получается, что регистр показывает состояние ARM и мы нашли причину возникновения отказа? На самом деле нет. Ведь согласно [3](стр. 23), считывание этого регистра с помощью специальной команды MSR всегда возвращает нуль. Мне не очень понятно почему это работает именно так, ведь этот регистр и так только для чтения, а тут его полностью и считать нельзя(можно только некоторые биты через xPSR). Возможно это некие ограничения архитектуры.

По итогу, к сожалению, вся эта информация практически ничего не дает рядовому программисту МК. Именно поэтому я рассматриваю все данные регистры только как дополнение к анализу сохраненного контекста.

Однако, например, если отказ был вызван делением на нуль(данный отказ разрешается установкой бита DIV_0_TRP регистра CCR), то в регистре CFSR будет выставлен бит DIVBYZERO, что укажет нам на причину возникновения этого самого отказа.

Что дальше?


Что же можно сделать после того, как мы проанализировали причину возникновения отказа? Мне кажется хорошим вариантом такой порядок действий:
1) Вывести в отладочную консоль(printf) значения всех анализируемых регистров. Это можно сделать только при наличии отладчика JTAG.
2) Сохранить информацию об отказе во внутреннюю или внешнюю(если имеется) flash. Также можно вывести значение регистра PC на экран устройства(если имеется).
3) Перезагрузить процессор NVIC_SystemReset().



Источники


1) Joseph Yiu. The definitive guide to the ARM Cortex-M3.
2) Cortex-M3 Devices Generic User Guide.
3) STM32 Cortex-M4 MCUs and MPUs programming manual.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru