4

Stm32 без IDE — памятка 2: мигаем светодиодом и кое-что ещё

Серия Технические заметки

Итак, в прошлый раз мы (надеюсь, и вы тоже) собрали и запустили при помощи консольных программ проект, созданный (и первично собранный) в STM32 CubeIDE. Теперь немного освоимся в нём.

Напомню, у нас есть структура каталогов проекта с исходными файлами (обратим внимание на Core и Drivers), а также аналогичная ей структура в подкаталоге Debug с правилами для сборки исходников в исполняемый файл. (Debug — это на самом деле название профиля сборки; по умолчанию кубик создаёт ещё Release, но, как нетрудно догадаться, он неудобнее для отладки, так что мы его собирать не будем; принципиальной же разницы между ними нет.)

Прежде чем переходить к заявленной заадаче, давайте потыкаем палочкой систему сборки (дело в том, что нам понядобится добавить в неё некоторые новые файлы, так что идём последовательно).

Создадим (любым текстовым редактором; хотя, конечно, Writer или Word будут для этого весьма некстати) файл Core/Src/our_file.c следующего содержания:

int value = 0;

И Core/inc/our_file.h:

extern int value;

Затем в Core/Src/main.c после строки /* USER CODE BEGIN Includes */ добавим #include "our_file.h", а в суперцикл добавим value ++;

/* USER CODE BEGIN WHILE */

while (1)

{

value ++;

Попробуем собрать:

aleksei@RNWS-008 /home/adk/STM32CubeIDE/31-live/lesson1/Debug $ make all | grep -v arm-none-eabi-gcc
/opt/st/stm32cubeide_1.12.0/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.200.202301161003/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-no
ne-eabi/bin/ld: ./Core/Src/main.o: in function `main':
/home/adk/STM32CubeIDE/31-live/lesson1/Debug/../Core/Src/main.c:96: undefined reference to `value'
collect2: error: ld returned 1 exit status
make: *** [makefile:64: lesson1.elf] Ошибка 1

Понятно, в чём дело: компилятор не знает о файле our_file.c, где идёт выделение памяти под переменную value. В GUI кубика мы в таком случае нажимаем ПКМ на нужном файле → Resource configuration → снимаем галочки «Exclude from build». Стало быть, некий аналог этих галочек есть и в генерируемых кубиком makefilах. Откроем файл Debug/Core/Src/subdir.mk. Здесь три переменных — C_SRCS, OBJS и C_DEPS. Первая содержит все исходные файлы текущего каталога, вторая — получающиеся из них объектные файлы. Добавим в них строчки с именем нашего файла и соответствующим объектником:

C_SRCS += ../Core/Src/our_file.c#

OBJS += ./Core/Src/our_file.o#

Признаться, мне больше нравится такой синтаксис, чем склеивание строк через бэкслеши, но если вы решите добавлять строки в сите кубика, не забывайте про пробелы перед бэкслешами. Последняя переменная (C_DEPS) нужна больше для внутренний надобностей CubeIDE, но для единнобразия можете добавить и строчку для неё (по аналогии — базовое имя файла + расширение .d). В системе сборки кубика есть небольшая нелогичность: чтобы добавить объект в сборку, должно не только присутствовать правило для его сборки в файле subdir.mk, но его ещё нужно внести в общий список объектов проекта. Откроем файл Debug/objects.list и добавим в него строку

"./Core/Src/our_file.o"

Теперь соберём ещё раз проект (make), переключимся на терминал с отладчиком (arm-none-eabi-gdb) и нажмём Ctrl-C, чтобы приостановить выполнение прогарммы (аналогично нажатию кнопки «Pause» в интерфейсе CubeIDE). Вновь дадим команду load. Отладчик при этом определит, что файл был обновлён с последнего запуска, и загрузит новый файл в микроконтроллер. Вновь запустим и приостановим программу:

(gdb) load

Transfer rate: 13 KB/sec, 1104 bytes/write.
(gdb) b main
Breakpoint 4 at 0x80005d8: file ../Core/Src/main.c, line 105.
(gdb) c
Continuing.

Breakpoint 4, main () at ../Core/Src/main.c:105
105  HAL_Init();
(gdb) p value  
$5 = 0
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
HAL_GetTick () at ../Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c:325
325  return uwTick;
(gdb) p value  
$6 = 16850
(gdb)

Отлично. Работает.
Маленькое пояснение: сперва я установил бряк на начало функции main. Дошёл до неё командой c(ontinue). Проверил состояние переменной value (сразу после загрузки прошивки проверять value бессмысленно: она ещё не инициализирована). Продолжил выполнение программы и тут же остановил её уже вручную, комбинацией Ctrl+c. Отладчик написал мне, где я нахожусь, за что ему спасибо, а затем я ещё раз проверил, что переменная докуда-то досчитала.

Сейчас попробуем помигать светодиодом. Для этого нам понадобится подключить в Core/Src/main.c файл stm32f4xx_hal_gpio.h. А вот подключать соответствующие исходники не понадобится, так как конкретно GPIO используется много где, и кубик пропишет его даже для пустого проекта. Останется лишь добавить в наш main.c функцию инициализации (по аналогии с тем, как это делает кубик при создании функции MX_GPIO_Init()):

void our_gpio_Init(void)

{

GPIO_InitTypeDef GPIO_InitStruct = {0};

__HAL_RCC_GPIOA_CLK_ENABLE();

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET);

GPIO_InitStruct.Pin = GPIO_PIN_10;

GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_InitStruct.Pull = GPIO_NOPULL;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

}

И вызвать её в main(), где-нибудь во второй секции. А затем в суперцикле помигать HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_10), HAL_Delay(300);

В принципе, на этом всё, осталась пара замечаний:

1) то, что gpio тянется по умолчанию, это хорошо, но давайте рассмотрим чуть иной пример. Скажем, нам нужно подключить АЦП и померить что-нибудь. Если мы хотим работать на чуть более низком уровне, скажем, CMSIS, то нам достаточно подключить заголовочный файл stm32f401xc.h (он лежит среди доступных к подключению каталогов, а именно в Drivers/CMSIS/Device/ST/STM32F4xx/Include). Если же мы, как уважающие себя люди, привыкли оперировать горячими закусками, то заметим, что в Drivers/STM32F4xx_HAL_Driver/Inc лежат далеко не все нужные нам файлы. Здесь мы можем либо скопировать недостающее из репозитория (где он расположен, можно глянуть в кубике: Window→Preferences→STM32Cube→Frimware Updater), либо указать на нужный каталог в строке сборки в файле Debug/Core/Src/subdir.mk (после ключа -I). Здесь я предполагаю первый вариант и скопировал в проект файлы, относящиеся к АЦП: *adc.h и *adc_ex.h — в Drivers/STM32F4xx_HAL_Driver/Inc, а *adc.c и *adc_ex.c — в Drivers/STM32F4xx_HAL_Driver/Src (заметьте, там будут не только файлы HAL, но и LL; впрочем, …ll….h действительно нужен). Добавил их в соответствующий subdir.mk, а также в список объектов. Также неплохо бы добавить соответстующие файлы или шаблоны в команду очистки проекты (внизу файлы Debug/Drivers/STM32F4xx_HAL_Driver/Src/subdir.mk — цель clean-Drivers-2f-STM32F4xx_HAL_Driver-2f-Src) (я добавил ./Drivers/STM32F4xx_HAL_Driver/Src/*.su ./Drivers/STM32F4xx_HAL_Driver/Src/*.o ./Drivers/STM32F4xx_HAL_Driver/Src/*.d ./Drivers/STM32F4xx_HAL_Driver/Src/*.cyclo )

Скомпилировал. Успешно. Но если сейчас попробовать вызвать любую функцию АЦП, то компилятор выдаст ошибку:

/* USER CODE BEGIN PV */

ADC_HandleTypeDef hadc1;

/* USER CODE END PV */

...

/* USER CODE BEGIN 2 */

HAL_ADC_Start(& hadc1);

/* USER CODE END 2 */

aleksei@RNWS-008 ~/STM32CubeIDE/other/test/Debug $ make all | grep -v arm-none-eabi-gcc
/opt/st/stm32cubeide_1.12.0/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.200.202301161003/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-no
ne-eabi/bin/ld: ./Core/Src/main.o: in function `main':
/home/aleksei/STM32CubeIDE/other/test/Debug/../Core/Src/main.c:88: undefined reference to `HAL_ADC_Start'
collect2: error: ld returned 1 exit status
make: *** [makefile:64: test.elf] Ошибка 1

Дело в том, что о нашем намерении собрать данную часть HAL нужно уведомить не только утилиту make, но и саму библиотеку. Для этого необходимо найти в файле Core/Inc/stm32f4xx_hal_conf.h строку #define HAL_ADC_MODULE_ENABLED и раскомментировать её. Зачем это сделано, признаться, не до конца понимаю, но у богатых свои причуды.

Собственно, на этом всё. Единственное, давайте на сладкое всё же запустим АЦП и при помощи отладчика извлечём какие-никакие данные.

/* USER CODE BEGIN PV */

ADC_HandleTypeDef hadc1;

int cnt = 0;

uint16_t data[1000] = {0,};

/* USER CODE END PV */

/* USER CODE BEGIN 0 */

static void my_ADC1_Init(void)

{

GPIO_InitTypeDef GPIO_InitStruct = {0};

__HAL_RCC_ADC1_CLK_ENABLE();

__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_InitStruct.Pin = GPIO_PIN_1;

GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;

GPIO_InitStruct.Pull = GPIO_NOPULL;

HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

ADC_ChannelConfTypeDef sC>

hadc1.Instance = ADC1;

hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;

hadc1.Init.Resoluti>

hadc1.Init.ScanC>

hadc1.Init.C>

hadc1.Init.Disc>

hadc1.Init.ExternalTrigC>

hadc1.Init.ExternalTrigC>

hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;

hadc1.Init.NbrOfC>

hadc1.Init.DMAC>

hadc1.Init.EOCSelecti>

HAL_ADC_Init(&hadc1);

sConfig.Channel = ADC_CHANNEL_1;

sConfig.Rank = 1;

sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;

HAL_ADC_ConfigChannel(&hadc1, &sConfig);

}

/* USER CODE END 0 */

while (1)

{

HAL_ADC_Start(& hadc1);

HAL_ADC_PollForConversion(&hadc1, 1);

data[cnt] = HAL_ADC_GetValue(&hadc1);

cnt++;

if(cnt >= 1000)

{

cnt = 0;

}

И проверим в работе. Не забудем собрать, а затем — загрузим обновлённую прошивку и запустим её:

(gdb) load

Transfer rate: 14 KB/sec, 1102 bytes/write.
(gdb) c
Continuing.
^C

(gdb) b main.c:128
Breakpoint 4 at 0x80005e4: file ../Core/Src/main.c, line 129.
(gdb) c
Continuing.

Breakpoint 4, main () at ../Core/Src/main.c:129
129  HAL_ADC_Start(& hadc1);
(gdb)

Здесь я прервал программу в произвольном месте при помощи Ctrl+C, затем поставил точку останова где-то в main.Теперь посмотрим, что прочиталось в массив:

(gdb) p data
$10 = {245, 251, 259, 249, 257, 247, 254, 260, 248, 252, 257, 247, 254, 260, 247, 254, 260, 248, 252, 258, 245, 251, 259, 249, 257, 247, 254, 260, 247, 254, 260, 248, 252, 257, 247, 254, 260,  
248, 252, 258, 245, 251, 260, 247, 254, 260, 247, 254, 260, 247, 254, 261, 252, 257, 247, 254, 260, 247, 254, 260, 248, 252, 258, 244, 247, 254, 260, 248, 252, 258, 245, 251, 259, 249, 257,  
247, 254, 260, 247, 254, 260, 247, 256, 243, 249, 257, 247, 253, 262, 250, 255, 265, 257, 247, 254, 260, 247, 254, 260, 247, 254, 261, 251, 260, 247, 254, 260, 248, 252, 257, 247, 254, 260,  
247, 254, 260, 248, 252, 257, 247, 254, 260, 247, 254, 260, 248, 252, 258, 245, 251, 259, 249, 257, 247, 254, 260, 248, 252, 257, 247, 254, 260, 248, 252, 257, 247, 254, 260, 248, 252, 257,  
247, 254, 260, 247, 254, 260, 248, 252, 257, 247, 254, 260, 248, 252, 258, 245, 251, 259, 250, 255, 264, 253, 262, 250, 255, 265, 257, 247, 254, 260, 247, 254, 260, 248, 252, 257, 247, 254,  
260, 247, 254, 260, 248, 252, 257, 247, 254, 260, 247...}

Что-то прочиталось, уже неплохо. А теперь — ради чего всё это:

(gdb) dump memory ~/tmp/test_data.bin data (data + sizeof (data))
(gdb)

Теперь мы получили в файле набор двоичных данных из массива data и можем относительно легко его разобрать, скажем, в таблицу примерно так:

Ну, тут вариантов масса, поэтому нет смысла писать текстом

Ну, тут вариантов масса, поэтому нет смысла писать текстом

Помимо двоичных данных команда dump умеет сохранять данные и в виде формата inetl hex и некоторых других.

К чему я это всё? А, на самом деле просто к тому, что команду dump очень полезно знать, даже если вы работаете в GUI STM32CubeIDE. Ведь её можно ввести во вкладке Debugger Console и получить слепок нужной вам области памяти.

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества