Авг 31

STM32 и malloc во внешней памяти FMC / SDRAM

После того, как FMC/FSMC запустился и работает, внешняя память отражается по соответствующим адресам. Эту память можно использовать по захардкоденным адресам

char * ptr = (char*)0xC0000000;

но каждый раз вычислять адреса не комильфо. Тем более, для этого как раз есть специальная штука — heap и malloc/free.
Нужно только рассказать malloc где у нас эта память и сколько её. Самый простой способ — исправить правила линковки: Суть такова. _sbrk использует память начиная c адреса переданного линковщиком как end, стоит положить его адрес SDRAM, как malloc начинает раздавать адреса из неё.
например из соответствующих правил сборки грохнуть

PROVIDE( end = .)

и дописать свой:

end = 0xC000000;

Или добавить полноценно, в описании памяти добавить регион SDRAM

MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 320K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 1024K
SDRAM(rw)       : ORIGIN = 0xC0000000, LENGTH = 8M
}

и добавить секцию в раздел SECTIONS:

 ._user_heap(NOLOAD) :
  {
    PROVIDE(_heap_start = .);
    PROVIDE ( end = . );
    . += LENGTH(SDRAM);
    PROVIDE(_heap_end = .);
  }  >SDRAM

или (если не нравится, что size показывает дофига) без секции:

_heap_start = ORIGIN(SDRAM);
_heap_end = ORIGIN(SDRAM)+LENGTH(SDRAM);

А символы _heap_start и _heap_end будем использовать в своей реализации _sbrk, которая еще проверит не вылезаем ли мы за размер:

#include <stdlib.h>
#include <errno.h>
 
caddr_t _sbrk(int incr) {
    extern uint8_t _heap_start;
    extern uint8_t _heap_end;
 
    static uint8_t *current_end = NULL;
    uint8_t *current_block_address;
 
    if (!current_end)
        current_end = &_heap_start;
 
    current_block_address = current_end;
 
    incr = (incr + 3) & (~3);
    if (current_end + incr > &_heap_end) {
        errno = ENOMEM;
        return (caddr_t) -1;
    }
    current_end += incr;
    return (caddr_t) current_block_address;
 
}
Фев 06

ARM Semihosting

В ARM-контроллерах есть полезнейшая вещица, Semihosting, позволяет переадресовывать I/O операции к хосту
Почему-то при отладке, в основном используют только printf() для вывода отладочных сообщений, хотя возможности порядком шире: можно переадресовать любую(!) I/O операцию. Например запись файла.
Для быстрого примера, создадим проект в Eclipse, конфигуратор проекта спросит про системные вызовы:
Use system calls — вот тут надо выбрать Semihosting (POSIX system calls via host) — это полное перенаправление всех POSIX вызовов к хосту.
Trace output — не важно, пусть будет Semihosting STDOUT, отладочные сообщения будут падать в STDOUT сервера
Ну и чтобы побыстрее все проверить — content — стандартный шаблон Blinky — мигалка светодиодом.

А дальше все просто — добавляем в main обычный код для вывода в файл:

 FILE* f = fopen("./test.txt", "w");
  if(f){
      fprintf(f, "Hello from ARM...\n");
      fclose(f);
  }

Думаю, и так понятно, что мы открываем файл test.txt для записи в текущем (относительно semihosting сервера) файл, и если открылся — записываем в него строку.
собираем, а вот запускать для наглядности будем из консоли:
у меня под рукой STM32VL-Discovery, поэтому конфиг запуска для неё

$ openocd -f board/stm32vldiscovery.cfg

Далее, запускаем openocd который и будет semihosting server
цепляемся к нему отладчиком

$ arm-none-eabi-gdb project.elf

и выполняем следующие команды в отладчике

target remote :3333  # подключаемся а GDB-серверу OpenOCD
monitor arm semihosting enable # включаем semihosting
monitor init # инициализируем кристал
monitor reset init # сбрасываем кристал
load  # загружаем в кристал файл указанный в параметре запука gdb
continue # запускаем запущенную программу

прошивка blinky содержит в себе примеры отладочных сообщений, так что можно вполне их посмотреть в консоли OpenOCD, а после выполнения добавленного участка кода — можно смотреть на файл test.txt:

$ cat test.txt
Hello from ARM...

Да, самое главное — прошивка с включенным Semihosting не сможет работать в standalone режиме (т.е. без подключенного отладчика), застопорится на первом же обращении к хосту. Поэтому в release версии нужно отключать. Для этого в элипс достаточно удалить define в соответствующей конфигурации