Пришла пора привести в понятный вид скрипт линковки для прошивки под STM32 (или любой другой ARM Cortex-M).
Прошлый раз мы писали как попало и не очень понятно было, где и что лежит. Будем устранять непонятности.
Я все время говорю – символы, секции. Давайте займемся терминологией.
Первое и главное – это символ. Для компоновщика символ это именованный адрес не более того. (правда похоже на переменную в любом языке программирования?). Причем не важно, что скрывается под этим символом – адрес переменной, код функции, все что угодно. Плюс некоторые «атрибуты», такие как принадлежность к секции, размер. Когда в коде написано uint32_t var_a = 12345;
, при компиляции создается символ var_a
, в атрибуты помещается размер в 4 байта и принадлежность к секции .data
(мы уже говорили, что инициализированные переменные попадают в эту секцию). Для функций – примерно тоже самое, только секция будет .text
, ну и размер нельзя вот прямо так сразу назвать, но после компиляции он конечно же будет известен.
Читать далее
Метка: stm32
ARM Without Magic. Урок 1.1 Переменные и инициализация.
В прошлый раз мы оформили только две секции в прошивке, для таблицы векторов прерываний и кода. Поморгать светодиодом таким образом можно, а вот для работы с переменными нужно еще чуть-чуть допилить.
Но для начала неплохо бы разобраться, где и как хранятся переменные. Из учебника по C мы помним, что переменные бывают: локальные, статические и динамические. Напомню, что из себя они представляют.
- Переменные локальные, которые объявляются внутри функций хранятся в стеке, всю работу по выделению памяти, инициализации и последующему удалению берет на себя компилятор, нам никаких действий предпринимать не нужно.
- Статические переменные хранятся в специальных секциях в области оперативной памяти, память под них выделяется на этапе линковки, а инициализация должна быть выполнена при загрузке микроконтроллера.
- Память под динамические переменные выделяется во время работы программы, они так же хранятся в специально отведенном регионе памяти. Но с точки зрения линковщика нужно только сообщить системным вызовам размер этого региона, все остальное остается на совести программиста. В основном считается, что использование динамической памяти на микроконтроллерах — это плохо (почему?). Поэтому пока мы не будем их использовать, оставим этот вопрос открытым на потом.
А сегодня мы займемся статическими переменными.
Читать далее
ARM Without Magic. Урок 1.0 Самое начало.
Небольшое предисловие.
Этот урок, как и последующие, написан прежде всего для новичков в прошивко-строении и не совсем новичков программировании. Для людей, которые хотят разобраться, как все устроено и почему работает именно так. После вдумчивого прочтения этого курса не должно остаться никаких вопросов, о том, что делает та или иная строка в любом файле, какие команды для сборки и отладки прошивки вызываются, для чего они нужны и как работают. Я постараюсь избавиться от ассемблерных вставок (в паре мест они будут, но не более), все что нужно знать – это язык программирования C на каком-то начальном уровне, сложные места постараюсь разъяснять. Ну и конечно для чтения документации потребуется хоть немного знать английский язык, к документации мы будем обращаться часто.
Я не буду особо заострять внимание на работу с блоками периферии, во-первых, потому что они все время разные, а во-вторых, по этой теме написаны тысячи статей и отснято тысячи бестолковых видеоуроков, что я вряд ли смогу сказать что-то новое и полезное. Я склоняюсь к тому, что лучший учебник по управлению светодиодом и отправке несколько байт через USART — это документация на соответствующий процессор. Она правильная и полная. В общем этакий экскурс в BareMetal для прикладных программистов. И основное отличие прошивки МК от прикладной программы в том, что все приходится делать самому. Инициализировать переменные, раскидывать секции по правильным адресам. Обычно это скрыто под капотом всяких IDE и их шаблонов, да так хорошо спрятано, что многие относятся к этим процессам как к магии: поставил волшебных галочек или произнес страшное заклятие — и прошивка собралась. Само по себе это не плохо, я тоже уже не первый год пользуюсь шаблоном для создания новых проектов, но мне нравится знать как оно работает и где подкрутить, чтобы получить требуемый результат.
Мы не будем привязываться к какой-то особенной среде разработки, мы не будем пользоваться генераторами проектов типа STM32 CubeMX, мы на первом этапе вообще прошивку руками соберем, без всяких систем сборок, и отлаживать будем тоже руками.
Ориентироваться я буду на процессоры STM32, так как с ними мне приходится работать чуть ли не каждый день, они дешевые и доступные. Из приборов для начала потребуется отладочная плата на процессоре STM32F103C8 (например Blue-pill) и отладчик ST-Link v2. Все это доступно для покупки на AliExpress за сущие копейки. Но все что будет написано довольно легко адаптировать к любому ARM Cortex-M процессору. Собственно цель курса в том, чтобы, используя полученные знания можно было без труда использовать любой доступный процессор.
Еще немного про Core-Coupled Memory (CCM) на STM32
В дополнение к прошлой заметке
Как я уже говорил, у CCM есть некоторые особенности, чтобы их понять и не наступать на грабли, по каждому чипу лучше всего сверяться с документацией. Например для STM32F407, CCM подключена только к D-BUS (шине данных), поэтому на этих чипах нет никакой возможности выполнять код лежащий в CCM.
Для STM32F3xx наоборот, ST вполне даже рекомендует оптимизировать быстродействие переместим код в CCM, обещая при этом +43% к производительности. Но опять же, согласно RM0316 CCM не имеет подключения к шинам DMA S3,S4 поэтому DMA работать откажутся.
По поводу производительности, по тестам для STM32F407 выходит такой результат, в таблице время выполнения теста относительно FLASH/RAM (больше — хуже):
Код в FLASH | Код в SRAM | |
---|---|---|
Данные в SRAM | 0% | +14.53% |
Данные в CCM | +2.4% | +15.73% |
А вот результаты того же теста для STM32F303:
Код в FLASH | Код в SRAM | Код в CCM | |
---|---|---|---|
Данные в SRAM | 0% | 0% | -16.93% |
Данные в CCM | +4.6% | +10.77% | -12.32% |
Проблема со сбросом stm32F7xx в OpenOCD
Симптомы проблемы такие: на версии Open On-Chip Debugger 0.10.0+dev-00924-g16496488 выглядело как ошибка:
in procedure 'program'
in procedure 'program_error' called at file "embedded:startup.tcl", line 474
на Open On-Chip Debugger 0.10.0+dev-00973-g80f1a92b
ошибки нет, но чипу мозг выносит, вместо резета он прыгает в какую-нибудь функцию и там умирает
Вроде как решается правкой stm32f7x.cfg
reset_config srst_only srst_nogate
на
reset_config srst_nogate
STM32 CCM
Есть у некоторых взрослых STM-ок такая дурная штука, как CCM (core coupled memory). Это отдельный регион памяти, не шуточного размера в 64K (для F4xx), который по-умолчанию вообще не используется. Выделена эта память в отдельный регион, потому что имеет подключение только к D-BUS ядра, что накладывает кое-какие ограничения. Главное — это невозможно использовать вместе с DMA и DMA2D и выполнять код (примечание: это для F40x, но вообще неплохо заглянуть в PDF, раздел «Архитектура системы»), а в остальном — память как память.
Есть несколько способов хоть как-то его начать использовать, самый простой способ — это положить туда стек.
Для этого нужно модифицировать всего одну строку скрипта линковки, ту что задает адрес _estack и положить в этот символ адрес конца CCM (0x10010000):
_estack = 0x10010000; /* end of CCMRAM */
При этом весь стек переедет в CCM и как следствие все локальные переменные, аргументы функции итд.
Второй не менее простой, но довольно ручной способ — это указание секции при описании переменной:
__attribute__((section(".ccmram"))) int ccmvar; |
однако, стоит при этом помнить, что без модификации startup-скрипта при инициализации там будет мусор, заполним нулями, добавив где-нибудь после FillZeroBSS
/* Zero fill the CCM segment. */ ldr r2, =_sccmram FillZeroCCM: movs r3, #0 str r3, [r2], #4 LoopFillZeroCCM: ldr r3, = _eccmram cmp r2, r3 bcc FillZeroCCM |
а заодно уберем в скрипте линковки из описания секции .ccmram запись инициализации во FLASH:
.ccmram (NOLOAD): { . = ALIGN(4); _sccmram = .; /* create a global symbol at ccmram start */ *(.ccmram) *(.ccmram*) . = ALIGN(4); _eccmram = .; /* create a global symbol at ccmram end */ } >CCMRAM |
теперь все переменные с attribute((section(«.ccmram»))) будут стартовать с нулевыми значениями.
Подробнее про переменные, секции и инициализацию можно почитать тут: ARM Without Magic. Урок 1.1 Переменные и инициализация.
STM32: Шифруем прошивку.
Не сильно хорошая, но все же защита от бездумного копирования устройства. А может быть и еще что-то. В общем, идея такая: зашифровать некоторые критические функции, без которых устройство работать не будет, хитрожопые алгоритмы или математика какая-нибудь. Причем, желательно не потерять удобную сборку и отладку, и использовать желательно без всяких указателей. Я использую arm-none-eabi-gcc в качестве тулчейна и CMake как систему сборки. Поэтому все нижесказанное относится именно к этой связки и для других компиляторов-сборщиков придется немного перепилить.
Читать далее
STM32 и malloc во внешней памяти FMC / SDRAM
После того, как FMC/FSMC запустился и работает, внешняя память отражается по соответствующим адресам. Эту память можно использовать по захардкоденным адресам
char * ptr = (char*)0xC0000000; |
но каждый раз вычислять адреса не комильфо. Тем более, для этого как раз есть специальная штука — heap и malloc/free.
Нужно только рассказать malloc где у нас эта память и сколько её. Самый простой способ — исправить правила линковки: Суть такова. _sbrk использует память начиная c адреса переданного линковщиком как end, стоит положить его адрес SDRAM, как malloc начинает раздавать адреса из неё.
Читать далее
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\\ |
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 в соответствующей конфигурации