Авг 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;
 
}
Июн 15

UART размер экрана

Для общения через UART с железками (avr, stm32 и прочими) в основном использую GNU screen:

$ screen /dev/tty.ubserial 9600

А вот что-то дернуло, раскрасить вывод микроконтроллера и раскрасил (не мудрено, печатаем что-то вроде \033[31m и вперед, ну только цвета меняем). А потом поехало: захотелось печатать в разных местах экрана (\033[y;xH), а вот как напечатать в центре? Надо же знать размер экрана, а он у меня (да и у всех) каждый раз разный.
Долго возился, выяснил, что screen отдавать размер окна (\033[18t) не умеет. Зато DCS передает напрямую терминалу из которого запущен: вот и нашелся грязный хак: оборачиваем запрос размера в ESC-P последовательность, и вот уже знаем размер экрана.
Печатаем хитрую строку:

\033P\033[18t033\

и получаем ответ:

\[8;31;163t

31 строка, 163 колонки, а 8 — это номер CSI-репорта. Он всегда такой.

вот тут большой список похоже что вообще всех ESC-последовательностей которые терминалы поддерживают: ttssh2.osdn.jp

P.S.
Ну а бонусом — таким же способом можно поставить заголовок окна который screen ставить умеет только через жопу.

\033P\033]0;New Window Title\a\033\\
Фев 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 в соответствующей конфигурации

Июн 01

OpenOCD & OSX & FTDI-CoLink

Если установлены драйвера FTDI VCP (Virtual Com Port) OpenOCD к Colink не подцепится — канал A уже используется.
Или выгрузить кекст или поправить
/System/Library/Extensions/FTDIUSBSerialDriver.kext/Contents/Info.plist
на предмет комментирования секции подключения канала А:

                <key>FT2232C_A</key>
                <dict>
                        <key>CFBundleIdentifier</key>
                        <string>com.FTDI.driver.FTDIUSBSerialDriver</string>
                        <key>IOClass</key>
                        <string>FTDIUSBSerialDriver</string>
                        <key>IOProviderClass</key>
                        <string>IOUSBInterface</string>
                        <key>bConfigurationValue</key>
                        <integer>1</integer>
                        <key>bInterfaceNumber</key>
                        <integer>0</integer>
                        <key>idProduct</key>
                        <integer>24592</integer>
                        <key>idVendor</key>
                        <integer>1027</integer>
                </dict>

ну и перезагрузить кекст.