STM32 и Дзен. Компиляция и запуск отдельных программ.


Возможно, не все, описанное тут, будет правильным решением. Некоторые вещи я делал либо исходя из своих поверхностных представлений о том, как это должно работать, либо вообще по интуиции.
Если в комментариях кто-то отпишется, укажет на ошибки и посоветует более правильное решение — буду очень благодарен.
Может кого-то эта тема заинтересует, и совместными усилиями можно будет что-нибудь эдакое выдать, чтобы потом все желающие могли использовать.
Суть в следующем.
Имеется плата, на которой стоит микроконтроллер STM32 и, например, SD-карточка. Идеальный вариант, если есть SRAM, подключенный по FSMC, но не обязательно. Внутренней памяти, на первое время, хватит.
Что нужно: компилить в Keil (или, возможно, другой среде) программу, заливать ее на SD-карточку и запускать на контроллере.
Как будем запускать?
Сразу на ум приходят два очевидных варианта:
1) Если программа небольшая, в несколько килобайт — заливаем ее в RAM и оттуда запускаем.
2) Если программа побольше — предварительно ее переписываем во внутренний флеш и запускаем из него.
Ну а если программа занимает больше, чем свободно во внутреннем флеше, единственный выход — внешний RAM по FSMC.
В этой статье остановлюсь на первом варианте.
Этап первый. SD и FAT.
Подробно описывать не буду. На эту тему много всего написано как в инете, так и в этом сообществе.
SD-карточку можно подключить по SPI, можно по SDIO (если контроллер F103). И то и то есть в примерах стандартной периферийной библиотеки от ST.
Файловую систему легко поднять с помощью библиотеки FatFs. О ней, вроде, тоже были статьи.
И вообще, можно и без файловой системы обойтись, главное, чтобы можно было откуда-нибудь считать предварительно откомпиленную программу.
Этап второй. Подготовка проекта с программой.
В моем контроллере 64кб оперативы. Считаю, что «32 килобайта хватит каждому»: грузить будем по адресу 0x20008000 — половина RAM.
Нужно скомпилить проект так, чтобы все адреса были расчитаны исходя из этого адреса.

Обязательно делаем .hex — файл. Из него потом сделаем бинарник, который зальем на SD-карточку.

Также советую включить C Compiler Listing, чтобы можно было проконтролировать как код уложится.

Ставим галочку One ELF Section per Function, в дефайны добавляем тип контроллера:

Ну и напоследок, указываем базовый адрес и точку входа во вкладке Linker.

Ну и пишем код, наподобие этого:
#include "stm32f10x.h"
uint32_t _main(int argc, char *argv[], uint32_t core_addr)
{
// что-нибудь тут делаем
return 123;
}
Компилим, получаем .hex-файл.
Открываем .txt и .hex, смотрим чтобы _main обязательно был вначале:
_main PROC
;;;10
;;;11 uint32_t _main(int argc, char *argv[], uint32_t core_addr)
000000 b570 PUSH {r4-r6,lr}
;;;12 {
000002 4605 MOV r5,r0
000004 460e MOV r6,r1
000006 4614 MOV r4,r2
...
В .hex:
:020000042000DA
:1080000070B505460E4614460448046000F05AFA5E
Байты совпадают.
Этап третий. Получаем бинарник.
Лезем в интернет, разбираемся, что же за формат такой .hex:
http://ru.wikipedia.org/wiki/Intel_Hex
Находим спецификацию.
Чтобы перевести .hex в бинарник, воспользуемся вспомогательной программой. В инете они есть, но я предпочел за 5 минут наклепать свою, ибо нужна была уверенность, что все переведется так как мне нужно.
Можете ей воспользоваться на свой страх и риск, но учтите, что она сырая.
(скачать)

Можно заливать на карточку.
Этап четвертый. Собственно, запуск.
// указатель на функцию, с теми же аргументами, что и _main
uint32_t (*program)(int argc, char *argv[], uint32_t core);
...
program = (int(*)(int, char **))0x20008001;
// Вот тут обратите внимание на адрес. Запускать надо не с 0x...00, а с 0x...01
// Кто может грамотно объяснить почему так, отпишитесь в комментариях. Я это понимаю лишь интуитивно: первая команда
// два байта занимает, а адрес команды, видимо, идет с последнего байта команды.
...
// адрес, куда будем заливать программу
uint8_t *addr;
addr = (uint8_t *)0x20008000;
...
// чтение из файла средствами FatFs
for (j = 0;;)
{
res = f_read(&prog, buff, sizeof(buff), &br);
for(i = 0; i< br; i++)
{
addr[i+j] = buff[i];
}
j += br;
if (res || br == 0) break; // error or eof
}
...
// Собственно, запуск
retval = (*program)(argc-1, argv + 1, (uint32_t)CORE);
Этап пятый. Ядро.
Ну запустили мы какую-то функцию из отдельного файла куда-то в память. И что дальше?
Нам же надо работать с периферией!
Можно опять прикрутить к проекту с программой стандартную периферийную библиотеку и возить с собой уйму платформозависимого кода.
Я предлагаю такое решение. Сделать таблицу с адресами функций, которые будем предоставлять пользовательским программам. Эдакое ядро, что-ли.
Адрес этой таблицы передавать пользовательской программе. Например так:
typedef struct _corefunc
{
uint16_t index;
uint32_t addr;
const char *desc;
} corefunc;
Ну и, для примера, заполним эту табличку:
corefunc CORE[] =
{
{ 0, (uint32_t)&fputc, "fputc" },
{ 1, (uint32_t)&LED_GetState, "LED_GetState" },
{ 2, (uint32_t)&LED_GetBlinkState, "LED_GetBlinkState" },
{ 3, (uint32_t)&LED_On, "LED_On" },
{ 4, (uint32_t)&LED_Off, "LED_Off" },
{ 5, (uint32_t)&LED_OnBlink, "LED_OnBlink" },
{ 6, (uint32_t)0, "" },
};
В первую очередь передадим программе наше переопределение fputc, чтобы та могла использовать printf для вывода.
Ну и, для разнообразия, добавим подержку светодиодов из моей статьи про USB.
Адрес этой таблички мы уже передали в программу выше (см. uint32_t core_addr)
Теперь надо что-то с этой табличкой сделать.
uint32_t _main(int argc, char *argv[], uint32_t core_addr)
{
_CORE = (corefunc *)core_addr;
unwrap_core();
return _pmain(argc, argv);
}
...
void unwrap_core()
{
fputc_ = (int(*)(int,FILE *))_CORE[0].addr;
LED_GetState_ = (uint8_t (*)(int))_CORE[1].addr;
...
// и т.д.
}
...
int fputc(int ch, FILE *f)
{
return (*fputc_)(ch, f);
}
uint8_t LED_GetState(int led)
{
return (*LED_GetState_)(led);
}
...
// и т.д.
Ну и завершающий штрих — сама программа:
void print_core(void);
int _pmain(int argc, char *argv[])
{
int i;
printf("Hello from test program\r\n");
for(i=0;i<argc;i++)
{
printf("Argument %d: %s\r\n", i, argv[i]);
}
print_core();
LED_OnBlink(2, 100, 100);
return argc;
}
void print_core()
{
int i;
printf("Aviable core functions:\r\n");
for(i=0;_CORE[i].addr != 0 && i<1024;i++)
{
printf("%d\t0x%x\t\t\t%s\r\n", _CORE[i].index, _CORE[i].addr, _CORE[i].desc);
}
}
И небольшой скриншотик:

Велосипед изобретен.
Жду конструктивной и неконструктивной критики, а также обвинения в дилетантстве, в комментариях :)
- +8
- 02 октября 2011, 18:42
- Ezhik
- 1
Файлы в топике:
Hex2ez.zip
Неплохой изврат. А если взять камень потолще, то на нем можно гигазы мелкого софта подгружать с флешки.
Гран-гран мерси! Вот теперь стало понятно.
Потихоньку задумываюсь над этаким некст-жен спектрумом с СДшкой вместо кассеты и Кортексом вместо Z80. Но пока мне секса хватает, так что оставим эту идею на потом.
Насчет абстракции кода от платформы- в CooCox IDE есть собственная библиотека СОХ, которая и служит посредником между низкоуровневым кодом и логикой программы. Используя ее один и тот же код можно скомпилировать на разные семейства кортексов.
Потихоньку задумываюсь над этаким некст-жен спектрумом с СДшкой вместо кассеты и Кортексом вместо Z80. Но пока мне секса хватает, так что оставим эту идею на потом.
Насчет абстракции кода от платформы- в CooCox IDE есть собственная библиотека СОХ, которая и служит посредником между низкоуровневым кодом и логикой программы. Используя ее один и тот же код можно скомпилировать на разные семейства кортексов.
- count_enable
- 02 октября 2011, 19:25
- ↓
Похожая штука (в виде самопала) есть в сониэриках, сделанных на базе OSE RTOS =) Правда, там честная ОС с честными процессами и API на программных прерываниях.
- teplofizik
- 30 июля 2012, 20:08
- ↑
- ↓
Чтобы перевести .hex в бинарник, воспользуемся вспомогательной программойВ составе binutils есть утилита objcopy, она как раз предназначена для преобразования форматов бинариков. Знает и elf и bin и hex много чего еще. Конвертирует из любого известого ей формата в любой другой. Присутствует в любом тулчейне на основе ГЦЦ.
Было-бы круто таким образом загрузку elf организовать.
Это не java, это скорее на зачатки однозадачной ОС похоже.
Как там кстати с возможностями управления памятью? Если есть контроллер памяти, то мона и большие бинарники в ОЗУ грузить, подгружая кусками по необходимости.
Как там кстати с возможностями управления памятью? Если есть контроллер памяти, то мона и большие бинарники в ОЗУ грузить, подгружая кусками по необходимости.
Берешь FreeRTOS прикручиваешь Java. И получается ОС с кучей ограничений. При этом можно будет запускать множество программ одновременно.
А причем тут жаба-то вообще? Тут что, где-то в байткод компилируют? Умение подгружать и выполнять бинарники — это таки свойство ОС, а не ВМ вроде жабы.
Поставь на арм java машину и получишь тоже самое. Только будет попроще и удобней писать проги. Также будет куда больше возможностей.
Java — это VM. Со всеми плюсами (довольно мощный язык, кроссплатформенный бинарный код), но и минусами (высокие требования к памяти и снижение скорости работы в 10-50 раз, т.к. на JIT на МК памяти не хватает).
Это и то, что в статье — мягко говоря, разные категории. И в таком виде, как в статье — это скорее однозадачная ОС, с загрузкой программ извне и API (та самая табличка функций ядра).
Это и то, что в статье — мягко говоря, разные категории. И в таком виде, как в статье — это скорее однозадачная ОС, с загрузкой программ извне и API (та самая табличка функций ядра).
В этой статье признаков ОС нет. Это обычный загрузчик.
Java на арм работает куда шустрее чем на x86. Но потери в производительности все же будут.
А если скрестить FreeRTOS и Java, то получиться облегченная версия uClinux.
Java на арм работает куда шустрее чем на x86. Но потери в производительности все же будут.
А если скрестить FreeRTOS и Java, то получиться облегченная версия uClinux.
Что-то ты ОС и ВМ в кашу мешаешь.
Что ты считаешь за признаки ОС? Да, типичного для встраиваемых РТОС управления задачами тут нет. Но та же DOS тоже была однозадачной и помимо загрузки программ и некоторого API умела только интерфейс пользователя (и тот реализовывался программой). Алсо, признаков жабы тут вообще нет, а на ос хотя бы намек (загрузчик — часть ОС, а тут еще и АПИ).
Что ты считаешь за признаки ОС? Да, типичного для встраиваемых РТОС управления задачами тут нет. Но та же DOS тоже была однозадачной и помимо загрузки программ и некоторого API умела только интерфейс пользователя (и тот реализовывался программой). Алсо, признаков жабы тут вообще нет, а на ос хотя бы намек (загрузчик — часть ОС, а тут еще и АПИ).
Java на арм работает куда шустрее чем на x86С чего бы? Да, на ARM с Jazelle Java испольняется частично аппаратно, но на x86 хватает ресурсов для JIT-трансляции в нативный код, так что проигрыш по производительности составляет процентов 15 по сравнению с хорошо оптимизированным С++, а это весьма и весьма неплохо. В сочетании с более высокой производительностью x86 — ARM-J оно таки ИМХО зарулит. На МК же нет ни Jazelle, ни ресурсов для JIT (в первую очередь ему нужна ОЗУ для хранения оттранслированных кусков кода).
А если скрестить FreeRTOS и Java, то получиться облегченная версия uClinux.А если скрестить Java и uClinux (вещи это вообще-то ортогональные — OS и VM, которая под ней работает) что получится?)
grub — загрузчик ОС. Он не предоставляет API и вообще специфичная для ПК штука. И главное — ОС без grub'а обойдется, а эти прожки — нет, они используют АПИ ядра.
В какое ядро? Jazelle в ARM-J? Оно да, ускорит, хотя солидная часть опкодов все равно выполняется софтово, в исключении. Но на x86 доступна JIT-компиляция, обеспечивающая Яве все то же быстродействие нативного кода.
На ARM-МК же ничего из этого не доступно, JVM будет работать в режиме интерпретации, а это, как известно, в самом лучшем случае 10-15 кратный проигрыш по сравнению с нативным кодом.
В какое ядро? Jazelle в ARM-J? Оно да, ускорит, хотя солидная часть опкодов все равно выполняется софтово, в исключении. Но на x86 доступна JIT-компиляция, обеспечивающая Яве все то же быстродействие нативного кода.
На ARM-МК же ничего из этого не доступно, JVM будет работать в режиме интерпретации, а это, как известно, в самом лучшем случае 10-15 кратный проигрыш по сравнению с нативным кодом.
Вот уж флаговый автомат никак нельзя назвать ОС. Это не более чем механизм распараллеливания задач. Для ОС вообще говоря необязательный — существуют однозадачные ОС.
Grub все же несколько другое. Его можно рассматривать как часть ОС, пусть и выделенную в отдельный проект. Причем эта часть ОС отрабатывает единократно при запуске и дальше не влияет на ее работу, а вот нечто в посте таки предоставляет интерфейс.
Grub все же несколько другое. Его можно рассматривать как часть ОС, пусть и выделенную в отдельный проект. Причем эта часть ОС отрабатывает единократно при запуске и дальше не влияет на ее работу, а вот нечто в посте таки предоставляет интерфейс.
Ну, во первых, в однозадачных ОС вроде MS-DOS никакого диспетчера не было. ОС есть программа, обеспечивающая взаимодействие пользователя, железа и прикладных программ. HAL'а тут нету (как и в DOS, впрочем), интерфейса пользователя пока тоже (хотя добавить вывод списка программ на SD и запуск выбранной несложно), а вот запуск и поддержка прикладных программ уже есть.
Во вторых, ну 1.25 МИПСа, и что? На современных х86 — уже до 5 DMIPS/MHz, у Core 2 например.
Вот только на ПК памяти много и жабу можно запустить в JIT'е, который минимум на порядок быстрее интерпретатора. А в МК только интерпретатор и влезет. Что, собсно, на примере .net MF и наблюдается.
Ну и в третьих, я не утверждаю что это ОС. Я говорю, что здесь видны зачатки ОС, а вот VM+FW вроде жабы или .net MF и не пахнет.
Во вторых, ну 1.25 МИПСа, и что? На современных х86 — уже до 5 DMIPS/MHz, у Core 2 например.
Вот только на ПК памяти много и жабу можно запустить в JIT'е, который минимум на порядок быстрее интерпретатора. А в МК только интерпретатор и влезет. Что, собсно, на примере .net MF и наблюдается.
Ну и в третьих, я не утверждаю что это ОС. Я говорю, что здесь видны зачатки ОС, а вот VM+FW вроде жабы или .net MF и не пахнет.
О, тем более, это уже вполне себе однозадачная ОС. Особенно если API развит. Скажем, FS хотя бы предоставляет.
Любопытно, однако, что здесь RTOS'ом в основном называют голые диспетчеры потоков (кооперативные или вытесняющие, опционально с средствами синхронизации/общения потоков). Видимо потому, что такие задачи ОС, как интерфейс пользователя (хотя это задача не ОС, а ее оболочки), запуск не-встроенных программ и HAL на МК менее востребованы, чем потоки. Но, тем не менее, система потоков — лишь часть ОС, и есть примеры ОС, ее не имеющие.
Любопытно, однако, что здесь RTOS'ом в основном называют голые диспетчеры потоков (кооперативные или вытесняющие, опционально с средствами синхронизации/общения потоков). Видимо потому, что такие задачи ОС, как интерфейс пользователя (хотя это задача не ОС, а ее оболочки), запуск не-встроенных программ и HAL на МК менее востребованы, чем потоки. Но, тем не менее, система потоков — лишь часть ОС, и есть примеры ОС, ее не имеющие.
Жесть. Со времён AVR мечтал о резиновой памяти :)
Жаль конечно что в stm32f100 серии нету FSMC. А то уже руки зачесались написать быдлокода на максимальные 4Гбайта :)
Жаль конечно что в stm32f100 серии нету FSMC. А то уже руки зачесались написать быдлокода на максимальные 4Гбайта :)
Это если перезаписывать :)
Можно подрубить NOR с программами и оттуда их грузить, а память внутреннюю юзать.
Правда смысла в этом…
Можно подрубить NOR с программами и оттуда их грузить, а память внутреннюю юзать.
Правда смысла в этом…
Ну почему, исполнять программу из NOR, если она большая, и подгружать данные оттуда же — вполне здравая идея.
Вот я нуб… :) Только сейчас доперло, что FSMC — это не какой-то отдельный интерфейс, а и есть тот самый контроллер внешней памяти. Тогда все понятно становится. Но что-то среди 107-й серии STM32 я FSMC все равно не нашел, даже в «многопиновых» исполнениях. Причем даташит то он качает одинаковый на все 107-е стм-ки.
Вот кстати всякие(mega8515 к примеру) AVR это отлично умеют делать.
да и что всегда мешало сделать нечто типа:
кстати подобная идея должна очень хорошо снюхатся с Elm-chan-овой библиотекой FAT.
да и что всегда мешало сделать нечто типа:
int address;
char * p;
address = [ram];
p = (char*)address;
file f;
while(!sd_feof(file))
{
*p = sd_fread(file);
p++;
}
int result2 = (*p) (options);
кстати подобная идея должна очень хорошо снюхатся с Elm-chan-овой библиотекой FAT.
Какой DOS. Обычная загрузка оверлея это называется. Ну а до DOS осталась самая малость
Да стандартный загрузчик бы не помешал с динамическим распределением памяти.
STM32 как всегда вкусненькое подбрасывает — спасибо.
Да стандартный загрузчик бы не помешал с динамическим распределением памяти.
STM32 как всегда вкусненькое подбрасывает — спасибо.
- TracerrecarT
- 03 октября 2011, 00:59
- ↓
На счёт таблицы с адресами: в кортексе нет программных прерываний как в x86, что бы системные вызовы делать можно было?
А зачем, если можно адрес напрямую грузить?Защита. Не нужно таблицу каждому процессу передавать. Либы проще строить. Ну и классика в конце концов.
Да, нашел пример. http://www.keil.com/download/docs/357.asp
Действительно, удобно.
Действительно, удобно.
Чтоб это стало классной ОС, нужны:
1) Фоновые задачи. Как ни крути без фоновых далеко не уедешь.
2) Нужен межпроцессорный обмен и функции синхронизации(мьютексы, секции, события и т.д.)
3) Возможность добавлять свои функции в ядро. Что то наподобие драйверов.
4) Должен быть механизм завершения процесса. Как например Ctrl+C.
5) Ориентация на микросд карточки. Они дешевые.
6) На внешнюю оперативку и большие процессы лучше не замахиваться. Там уже есть uClinux и с ним конкурировать не имеет смысла.
Тем самым это ОС займет свою нишу. Конкурентов у нее не будет.
Для запуска ОС нужен МК+сдкарточка.
1) Фоновые задачи. Как ни крути без фоновых далеко не уедешь.
2) Нужен межпроцессорный обмен и функции синхронизации(мьютексы, секции, события и т.д.)
3) Возможность добавлять свои функции в ядро. Что то наподобие драйверов.
4) Должен быть механизм завершения процесса. Как например Ctrl+C.
5) Ориентация на микросд карточки. Они дешевые.
6) На внешнюю оперативку и большие процессы лучше не замахиваться. Там уже есть uClinux и с ним конкурировать не имеет смысла.
Тем самым это ОС займет свою нишу. Конкурентов у нее не будет.
Для запуска ОС нужен МК+сдкарточка.
1) Фоновая задача=задача оторванная от терминала.
3) А зачем? Предположем, есть прога, которая обрабатывает данные с датчика темп. DS_как_её_там. Т.е нужен 1-Wire. Дать проге интефейс доступа к пинам и таймеру и пускай себе дрыгает. Не нужна? Выгрузить из памяти с дровами вместе. Хотя если таких прог >1 — накладно выходит. Но modprobe/kldload точно не нужен.
4) Опять же, это делает терминал.
P.S: Автору бы +1 поставил.
3) А зачем? Предположем, есть прога, которая обрабатывает данные с датчика темп. DS_как_её_там. Т.е нужен 1-Wire. Дать проге интефейс доступа к пинам и таймеру и пускай себе дрыгает. Не нужна? Выгрузить из памяти с дровами вместе. Хотя если таких прог >1 — накладно выходит. Но modprobe/kldload точно не нужен.
4) Опять же, это делает терминал.
P.S: Автору бы +1 поставил.
1) Фоновые задачи нужны по любому. Или хотя бы потоки. Иначе в реале эту ОС будет использовать просто ужасно неудобно.
3) Для реализации протоколов. Протоколы лучше сделать на уровне ядра. Тогда не будут проблем с таймингами.
4) Да это делает терминал. Но ОС не должна зависнуть из-за кривого процесса.
3) Для реализации протоколов. Протоколы лучше сделать на уровне ядра. Тогда не будут проблем с таймингами.
4) Да это делает терминал. Но ОС не должна зависнуть из-за кривого процесса.
Прикольно. мне понравилось. Теперь диспетчер задач, менеджер памяти, HAL, сопроцессор (ARM11 c явой), DDR
Ezhik для «полного» счастья ИМХО не хватает еще LCD дисплея от мобильника :) — нечто подобное делал еще на 8051, где в качестве RAM использовал кеш-память от 486 (32 или 64 КБ статики на корпус)
- ChipKiller
- 04 октября 2011, 23:15
- ↓
Это признак кода Thumb-режима. Посмотрите об этом в инете.program = (int(*)(int, char **))0x20008001; // Вот тут обратите внимание на адрес. Запускать надо не с 0x...00, а с 0x...01 // Кто может грамотно объяснить почему так, отпишитесь в комментариях. Я это понимаю лишь интуитивно: первая команда // два байта занимает, а адрес команды, видимо, идет с последнего байта команды.
как вариант можно почитать в спеке на отечественный контроллер.
milandr.ru/uploads/Products/product_80/spec_seriya_1986BE9x.rar
кстати они обещали 144 ногий 100МГц АРМ М3 за 160 руб =)
- prostoRoman
- 05 октября 2011, 18:05
- ↓
Вроде подороже. Если Вам нужна приёмка, то у Вас должен быть ПЗ и всё оттуда вытекающее =)
- prostoRoman
- 06 октября 2011, 10:05
- ↑
- ↓
Спасибо за пост. А использовать внешнюю ОЗУ есть опыт? Если да — поделитесь пожалуйста, удачи!
- prostoRoman
- 05 октября 2011, 18:07
- ↓
Комментарии (89)
RSS свернуть / развернуть