Технические заметки
2 поста
2 поста
1 пост
Lbook v3+ 2009 года. Был подарен мне родителями (на мои деньги, хе) на день рождения. Один раз менял экран, один раз — разъём. Потом купил что-то другое (нужна была подсветка… не помню, что именно), а эту отдал маме. А потом и она себе купила другую, но lbook не выкинула.
Потом я понемногу переходил на другие модели (разных производителей, в конце концов осел на Покетбуках: синхронизация, приятное общение с ТП… В общем, по совокупности), но всё не оставляло ощущение, что у Той Самой экран был лучше. Ну, и недавно был у мамы, спросил — да, говорит, лежит, забирай. Я забрал. Вставил аккумулятор, залил книжки, включил…
Электрокниги 2009 года и 2025. У 2009, помимо прочего, неоспоримое достоинство — логотип в виде ёжика.
В общем, не знаю, как на фото, а вживую читается примерно одинаково при том, что на lbook шрифт более мелкий (начертание там и там pt sans, но немного по-разному устанавливается кегель). Ну, сперва я не понял, что за притча, все ведь говорят, мол, нынешние экраны имеют более высокое разрешение, это типа лучше (да, знаю, что у фотоаппаратов всё не столь однозначно, но тут ведь другой принцип, по идее действительно с ростом разрешения качество должно расти). Ну, да, был ужасный опыт с lbook v60 с первым поколением e-ink pearl, там ещё и фон был сероват, но здесь такой проблемы нет. Явно разница в буквах, хотя на глаз и не мог понять в чём дело.Посмотрел с увеличением. Тут, вроде, стало яснее.
В общем, налицо слишком светлый «чёрный» (снимки делались в одних условиях) + слишком сильное сглаживание. Но, возможно, это следствие всего лишь сглаживания. К слову, на lbook сглаживание можно убрать, но тогда текст становится практически нечитаемым. Плюс, возможно, дело только в сглаживании?
Как видно в опыте с более крупным шрифтом (чтобы снизить влияние сглаживания на цвет буквы), вроде как чёрный и вправду не столь чёрный. Правда, в этом опыте несколько поменялось освещение. Но белых точек стало меньше, при этом общий контраст улучшился не очень сильно. Так что, похоже, дело и в сглаживании, и в типе экрана. Если с экранами ничего поделать, судя по всему, нельзя (они делаются мало кем и в любом случае идут разным производителям книг), то настройки сглаживания добавить имело бы смысл.
Итак, в прошлый раз мы (надеюсь, и вы тоже) собрали и запустили при помощи консольных программ проект, созданный (и первично собранный) в 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 и получить слепок нужной вам области памяти.
За вопрос спасибо студентам группы 031/EE-01.
Буквой в скобках обозначаю (например, так: "(Щ)" ) моменты , на которые позже хочу сослаться.
Исходные данные: есть проект STM32CubeIDE (скажем, пустой, только что сгенерированный), теперь хочется его его скомпилировать и запустить на микроконтроллере.
Материалы: МК на базе stm32F401 (впрочем, конкретная модель не важна *), многострадальный ноутбук с Rosa Fresh 12.5.1, кубик версии 1.12 (люблю я именно эту версию…). Программатор — китайский свисток, которому повезло, что кубик его принимает за своего.
Если у вас windows, я предполагаю, что вы сами установили и настроили make, драйвер st-link и т. п. Также все консольные команды я даю в расчёте на bash, это стоит иметь в виду.
Итак, мы запустили кубоид, создали в нём проект, а теперь хотим закрыть кубоид и далее работать с этим проектом. На самом деле для того, чтобы всё получилось без лишней головной боли, сперва соберите проект в кубе. У вас создастся каталог Debug (ну, я пока предполагаю, что вы ничего не трогали), а в нём — исполняемый файл, и куча промежуточных файлов. Теперь вы можете сделать Project →Clean, при этом удалится большинство промежуточнх файлов, но останутся иструкции для утилиты make, которой мы и воспользуемся.
Для этого нам понадобятся
текстовый редактор. Любой;
утилита make;
компилятор, способный создать исполняемый файл для МК;
сервер gdb, способный работать по интерфейсу SWD с МК;
при использовании ST-LINK_gdbserver (см. ниже) понадобится консольный stm32programmer;
клиент gdb, умеющий в инструкции процессора МК.
С редактором, думаю, более-менее ясно.
Компилятор и клиент GDB можно достать из уже установленного кубоида (если у вас его в принципе нет, можно ограничиться пакетом STM32CubeCLT (CLT означает command-line tools). Там нам понадобится найти расположение файлов arm-none-eabi-gcc и arm-none-eabi-gdb.
Собственно, arm — это архитектура процессора, для которого мы собираем программу, none-eabi означает отсутствие какой бы то ни было ОС, взяимодействующей с исполняемым файлом (всяческие RTOS в данном случае не в счёт).
Вот по серверу есть варианты. Можно использовать openOCD, st-util либо stlink-gdbserver. Я буду использовать stlink-gdbserver просто потому, что он идёт в поставке с кубиком (на самом деле у него есть ещё два преимущества: он умеет более чем в одно подключение одновременно, а главное — в отладку двухъядерных МК).
Поищем (я предполагаю, что кубик установлен в /opt/st/stm32cubeide_1.12.0, в зависимости от ОС, кончено, нужно использовать подходящие инструменты для поиска):
aleksei@RNWS-008 ~ $ find /opt/st/stm32cubeide_1.12.0 -name 'arm-none-eabi-gdb'
/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/arm-none-eabi-gdb
aleksei@RNWS-008 ~ $ find /opt/st/stm32cubeide_1.12.0 -name '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/arm-none-eabi-gcc
aleksei@RNWS-008 ~ $ find /opt/st/stm32cubeide_1.12.0 -name 'ST-LINK_gdbserver'
/opt/st/stm32cubeide_1.12.0/plugins/com.st.stm32cube.ide.mcu.externaltools.stlink-gdb-server.linux64_2.0.500.202301161003/tools/bin/ST-LINK_gdbserver
(Б) На самом деле, конечно, это всё можно найти и глазами, но в любом случае с непривычки будет сильное желание выкинуть эти длинные буквы подальше.Пути до (не включительно) arm-none-eabi-gdb и ST-LINK_gdbserver следует добавить к системной переменной PATH (нет, как это делается, я здесь пояснять не буду, это относится не к нашей теме, а к общей компьютерной грамотности).
(Ъ) А вот путь до arm-none-eabi-gcc нужно добавить в PATH обязательно.
(В) Для использования ST-LINK_gdbserver нам отдельно понадобится путь к программе STM32_Programmer_CLI. Примерно так:
aleksei@RNWS-008 ~ $ find /opt/st/stm32cubeide_1.12.0 -name 'STM32_Programmer_CLI'
/opt/st/stm32cubeide_1.12.0/plugins/com.st.stm32cube.ide.mcu.externaltools.cubeprogrammer.linux64_2.0.600.202301161003/tools/bin/STM32_Programmer_CLI
aleksei@RNWS-008 ~ $ export PATH_TO_PROGRAMMER='/opt/st/stm32cubeide_1.14.0/plugins/com.st.stm32cube.ide.mcu.externaltools.cubeprogrammer.linux64_2.1.100.202311100844/tools/bin'
(А) Далее, make можно использовать как системный, так и из поставки кубоида (так же, поищите make). В принципе, нет причин не использовать штатный make — если только у вас не windows. Но тут подскажу мало: вживую видел make в составе платформы msys2, по идее, должно быть достаточно make из кубика. Точно так же, добавьте её в PATH, и будет работать.
Отлично. если вы проделали это — треть работы позади.
Теперь перейдём к сборке проекта. Для этого необходимо зайти в терминале в каталог с проектом, далее в каталог Debug (посмотреть, где он лежит, можно из Cube IDE: щелчок правой кнопкой мыши → Show In → System Explorer
Теперь, узнав, где мы сидим (в моём случае это ~/STM32CubeIDE/other/test/Debug), открываем там терминал:
cd ~/STM32CubeIDE/other/test/Debug
и собираем проект (во всяком случае, те, кто не прогуливал лекцию про make):
make all
Для этого у нас должны быть установлены make (см (А) ) и переменная PATH должна указывать на каталог с arm-none-eabi-gcc (см. (Ъ)). Если make пишет что-то вроде /bin/bash: команда не найдена: arm-none-eabi-gcc), то возвращаемся к пункту (Ъ).
Если же эти два требования выполнены, то внезапно собирается. Почему?
Потому что в каталоге Debug кубик создаёт makefile, а также воссоздаёт примерную структуру каталогов проекта. В каждом из подкаталогов лежит фай subdir.mk, описывающий, как собирать данный подкаталог (в самом Debug эти инструкции вынесены в файлы с названиями objects.list, objects.mk, sources.mk). В принципе, мне по нраву такой подход. В сами файлы мы залезем чуть позже. А пока посмотрим, что получилось на выходе:
aleksei@RNWS-008 ~/STM32CubeIDE/other/test/Debug $ make all | grep -v arm-none-eabi-gcc
Finished building target: test.elf
arm-none-eabi-size test.elf
text data bss dec hex filename
5788 20 1580 7388 1cdc test.elf
Finished building: default.size.stdout
arm-none-eabi-objdump -h -S test.elf > "test.list"
Finished building: test.list
(grep -v использовал просто чтобы не выводить полотно текста).
У нас собрался файл Debug/lesson1.elf. Ура. Он модержит в себе инструкции и данные для работы программы; адреса, по которым эти инструкции и данные должны располагаться; отладочную информацию в соответствии с тем профилем сборки, который настроили в кубике — для Debug это практически весь исходный текст программы. Он нам и понадобится далее.
Теперь треть работы позади.
Как уже упоминал, работать будем с ST-LINK_gdbserver; при желании можете использовать st-util, она чуть проще в запуске, а результат для нас будет тем же. Или openOCD, если он у вас уже есть.
Предполагаю, что (Б) и (В) мы уже прошли. Тогда запускаем:
ST-LINK_gdbserver -p 4242 -d -t -cp $PATH_TO_PROGRAMMER
Здесь -p 4242 — это указание порта для работы с клиентом gdb; по умолчанию ST-LINK_gdbserver использует 61234, делайте как вам удобно, я обычно пишу его явно, чтобы не забыть.
-d — использовать SWD (подозреваю, что ключ необязателен).
-t — тоже необязательный для нас, разрешает множественные подключения.
-cp $PATH_TO_PROGRAMMER — обязательно нужно указать путь к STM32_Programmer_CLI (см. (В))
Если контроллер подключён, то увидим сообщение типа Waiting for debugger connection
Это означает, что мы смогли подключиться по USB к программатору и через него к целевому контроллеру. Если не смогли — проверяем контакты (в том числе разрешение на доступ к USB, мало ли…)
Далее в отдельном терминале заходим в каталог с файлом elf и запускаем клиент отладчика. Примечание для пользователей windows: здесь следует использовать именно стандартный терминал windows, штуковины типа git bash или терминала msys2 в данном конкретном случае не дадут работать автодополнению клиента gdb.
aleksei@RNWS-008 ~/STM32CubeIDE/other/test/Debug $ arm-none-eabi-gdb
(gdb) target extended-remote :4242
Remote debugging using :4242
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x08015760 in ?? ()
(gdb) file test.elf
Reading symbols from test.elf...
(gdb) load
Loading section .isr_vector, size 0x298 lma 0x8000000
Loading section .text, size 0x1bc5a lma 0x8000298
Loading section .init, size 0xc lma 0x801bef4
Loading section .fini, size 0xc lma 0x801bf00
Loading section .rodata, size 0x10444 lma 0x801bf0c
Loading section .eh_frame, size 0x4 lma 0x802c350
Loading section .ARM.exidx, size 0x8 lma 0x802c354
Loading section .data, size 0x9ec lma 0x802c35c
Loading section .init_array, size 0xc lma 0x802cd48
Loading section .fini_array, size 0x4 lma 0x802cd54
Loading section .init_array.00000, size 0x4 lma 0x802cd58
Start address 0x08007a24, load size 183642
Transfer rate: 41 KB/sec, 8347 bytes/write.
(gdb) continue
Continuing.
Собственно, программа запущена. Далее изучайте команды gdb (начните с: breakpoint, info breakpoint, delete, next, step, continue, run, disconnect, target, quit, print, x, frame; несколько особняком — dump, потому что на тему неё я позже ещё напишу пару слов). Имейте в виду, что здесь работает автодополнение команд (клавишей Tab), история (клавиши «Вверх» и «Вниз»), поиск по истории (Ctrl + r), сокращения команд (например, 'c' эквивалентно continue). Также если нажать ВВОД при пустой строке ввода, будет повторена последняя команда (это не очень удобно, но увы, без пересборки gdb этого не исправить; для отдельных команд можно отменить повтор последней команды при помощи «dont-repeat».
В следущей серии помигаем светодиодиком.
На одной далёкой планете, не очень сильно отличающейся от нашей, в школах не задавали домашний заданий. Вообще. Считалось, что дети дома должны мыть посуду, а если остаётся свободное время — играть или гулять.
Поэтому когда девочка Нилопа, ученица одиннадцатого [1] «˥» класса, вдруг заболела, она даже немного обрадовалась: на уроки ходить не нужно, посуды дома не очень много, к тому же у них была посудомоечная машина, так что оставалось только присматривать, не пора ли её включать — или доставать оттуда чистое. В общем, можно было играть, бегать по двору (болела она не очень сильно, и на второй день, хотя родители в школу её не отпустили, уже вовсю рвалась гулять) или зайти к подружке Несе (которая была немного постарше и тоже не очень сильно болела).
А потом Нилопа выздоревела. Точнее, родители — папа Ашол и мама Иля — сказали, что в пятницу [2] можно идти в школу. Нилопа опять немного обрадовалась, она успела соскучиться по их учительнице Ироткиве Илисавовне и вообще по своему классу. И, хотя пятничным утром её еле удалось разбудить (маме Иле даже пришлось минут пять дёргать её за левую ногу) в конце концов встала она весело, наскоро выпила чашку чая с кексиком и стала канючить:
— Мама, а мы не опоздаем? Мама, а папа предлагал в класс одну нашу игру отдать, только я не знаю, целая ли она, он обещал посмотреть, но забыл, наверное. Мама, а который час? Мама, а чашку сполоснуть или так поставить? А можно я на следующих выходных к бабушке Неле поеду? Мама, а мы сегодня точно в школу пойдём, а то мы так долго сидим тут…
Конечно, торопила она маму не просто так: мама всё это время расчёсывала дочке волосы, чего та терпеть не могла. Но возразить не решалась, потому что мама сегодня проснулась (или это показалось Нилопе?) какая-то недовольная. Во всяком случае, проверять мамино настроение не хотелось, хотелось только, чтобы это поскорее закончилось.
Но вот, наконец, они пришли в школу. Их, как Нилопа уже привыкла, встретила учительница и повела завтракать. На завтрак сегодня было любимое Нилопино варенье с булочками и какао с кашей, а первым уроком — чтение. После чтения были прописи. Нилопа их не очень любила, потому что хотелось поскорее писать целые буквы, а их пока что заставляли рисовать какие-то кусочки. Раскрыв тетрадь, Нилопа погрустнела: там, где у её соседки по парте Таки нестройными рядами шли какие-то кривульки — там у Нилопы были пустые строчки. Она задумалась: где писать дальше? Пропустить строчки или продолжить на первом же свободном месте? И то, и то казалось неправильным. Впрочем, к ней подошла Ироткива Илисавовна и сама написала несколько палочек в начале строки — пропустив четверть страницы.
Математики сегодня не было, но Нилопа задумалась: а что завтра? На математике тоже пропускать полстраницы? Во время урока она всё время поглядывала на пропущенные строчки, так что учительнице даже пару раз пришлось её одёргивать, чтобы та не отвлекалась.
А после уроков Нилопа обо всём забыла. И только перед сном вдруг вспомнила, и подбежала к папе, и спросила:
— Папочка, а можно я дома доделаю то, что нужно было в классе делать?..
Папа обещал подумать. На самом деле, конечно, думать он не стал, он ждал этого и подумал немного раньше. На следующий день, в субботу, после школы, Нилопа покормила Аньялу́, её домашнюю улитку, а потом позвонила Анни, маме Мифаресы, чтобы узнать, что она пропустила. И заполнила все пустые места в тетрадках.
[1] На этой далёкой планете номер класса означал, сколько осталось учиться. Так что их одиннадцатый класс примерно соответствовал нашему первому.
[2] Как вы уже догадались, учились они с пятницы по понедельник. А вторник, среда и четверг были выходными.
Десять лет назад переехал в новую квартиру. За это время успел жениться, ремонт, трое детей, купить и продать автомобиль… но вот теперь у коляски, служившей нам верой и правдой много лет (а до нас — кому-то ещё) отломилось пластиковое крепление. И на свет вылезла купленная на новоселье пачка соды… Н-да.
Справедливости ради, отломилось оно второй раз, но в первый в качестве наполнителя использовал кусок туалетной бумаги, хватило на полгода.
Фото из интернета.
Год 2016. Ялта.
В том году в Крым я ездил два раза, два мини-отпуска примерно по неделе. Впечатлений было много, но здесь — об одном из них, о гранатовом вине.
В первый раз поехали с женой на майские праздники.
Итак, захотел я сделать шашлык. Рынок. Мясо, овощи, приправы. И тут: «молодые люди, а попробуйте гранатовый сок! Очень вкусный!» И чуть тише: «И вино». И почти шёпотом: «И чача есть».
Ну, действительно, к шашлыку почему бы и не взять красного вина или сока? Пробуем. Пластиковые стаканчики. Сок, вино, вино гранатовое (на самом деле, как пояснил продавец, смесь винограда и граната). Чачу просить не стали, не хотелось. Виноградное вино — вино как вино, а вот гранатовое привело меня в восторг. Даже супруге понравилось, хотя она вообще-то и не пьёт. Решили купить по бутылке сока, обычного вина (ей на работу в качестве сувенира, мол, была, о коллегах не забыла) и гранатового — мне. Купили три бутылки по 1,5 л, пришли домой, шашлык на мази, можно и попробовать принесённые напитки. Наливаю, пью... и понимаю, что последний раз алкоголь я пил больше полугода назад. И даже от слабого вина я хоть минимальный эффект опьянения почувствовал бы. В общем, сняли пробу со всех трёх бутылок и поняли, что обманули нас. Разбавили даже сок, на вкус хуже дешёвого магазинного. Ну, страдать по этому поводу не стали, благо не за вином приехали. Бывает.
В Крыму очень понравилось. Хотел слетать туда ещё, и такая возможность представилась через полгода.
Конец лета. Договорился на работе о семи выходных подряд, полетели. Бахчисарай, потом Ялта, последний день. И тут вспоминаю, что хорошего, вернее, того самого, вина-то я так и не попил. Ладно, раз уж вспомнил, нужно идти.
Рынок, хожу, те же прилавки с гранатами, армянами, бутылками, но никто попробовать не предлагает. Выхожу с рынка, даю себе пять минут на размышления. Всё, сообразил: они же должны не покупателя видеть, а лоха. Получается, в тот раз я был на него похож, а сейчас нет. Слишком целеустремлённый вид. Ладно, секунду... Вот, теперь хорошо: рот полуоткрыт, глаза подолгу задерживаются на случайных прилавках, скорость не больше километра в час... Человек первый раз на рынке, в общем. Иду мимо тех же прилавков.
Тут же справа (тот же продавец, что полгода назад, помню его): «Молодой человек!» — «А?», — слева другой голос: «Вот, попробуйте!» — рука протягивает мне уже налитую в стаканчик жидкость (сок? вино?) — «Молодой человек, я к Вам первый обратился!»
Цирк. При этом я ничего не приукрашаю, расстояние между двумя прилавками — метров пять, иду между ними. Реплики дословно эти.
Видимо, я всё же несколько злопамятен, так как оборачиваюсь именно к тому, который обманул меня в прошлый раз.
— Да?
— Попробуйте!
— А что это, сок?
— И сок, и вино есть.
Пробую. Да, это тот самый вкус, что понравился мне тогда. И совсем не то, что он продал. Замечаю, из каких именно бутылок он наливает пробу.
— Знаете, очень вкусное вот это вино. Тут с гранатом, да? Я сегодня улетаю, пожалуй возьму бутылочку.
— Да, берите больше!
— К сожалению, денег не взял, но одну бутылку возьму.
(Это даже было правдой, жена улетала позже, поэтому деньги оставил ей.)
— Ну, тогда вот, берите, здесь, вроде, побольше.
— Да знаете, давайте вот эту (показываю), раз уж я из неё всё равно, получается, отпил.
Он понял. Он умел проигрывать. Выражение ненависти на его лице промелькнуло так быстро, что, если бы я не ожидал чего-то в этом роде, мог бы и не заметить. Наверное, так великие актёры ждут аплодисментов. Но я не актёр, мне было достаточно того, что он, отдав бутылку, отвернулся и сделал вид, что ему не до меня.
Вино было хорошим.