Иногда в работе инженера кажется, что текущая задача является уникальной, что с такой проблемой никто ни разу не сталкивался. Соответственно, решение должно быть таким же уникальным.
Однако при более пристальном взгляде зачастую оказывается, что задача не просто не уникальна, она сама по себе является представителем некоего класса задач, для которого выработано общее типовое решение.

Например, вам может потребоваться измерять наработку различных подвижных узлов оборудования и для этого нужно фиксировать время начала и конца движения каждого из узлов.
Или при работе операторов с оборудованием применяется некоторая ролевая модель и необходимо логировать действия пользователя, записывая когда, кто и что сделал.
Или оборудование производит измерения, по большей части с идентичными значениями. А чтобы не заполнять базу данных бесконечной чередой одинаковых чисел решено производить замеры через неравные интервалы времени только при изменениях текущих показаний свыше определённого порога.

Во всех этих задачах требуется получать временные метки — таймстампы. Если вы используете микроконтроллер STM32, сделать это очень просто.

Как правильно получать таймстампы на STM32?

Устанавливаете и запускаете CubeMX. Выбираете «File→New Project…»
Вво́дите в строке поиска название вашего микроконтроллера, выбираете его из списка и нажимаете «Start Project». Я работаю с демоплатой Nucleo‑U031R8, соответственно у меня будет микроконтроллер STM32U031R8T6.

На левой панели в разделе «System Core» заходите в пункт RCC.
На панели «RCC Mode and Configuration» устанавливаете параметр «Low Speed Clock (LSE)» в режим «BYPASS Clock Source».

Также я поставил галку в пункте «Master Clock Output 2». Не слишком академический приём, но для демонстрации подойдёт: я в дальнейшем получу тактовый сигнал 1 МГц на выходе MCO2 и подключу его на вход LSE для тактирования часов реального времени.

Далее следует выбрать на левой панели пункт RTC в разделе «Timers».
И на панели «RCC Mode and Configuration» проставить галки в пунктах «Activate Clock Source», «Activate Calendar», «Timestamp», а также прописать число 99 в поле «Asynchronous Predivider» и число 9999 в поле «Synchronous Predivider».

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

Далее нужно перейти на вкладку «NVIC Settings» и поставить единственную галку в колонке «Enabled».

Для демонстрации ещё потребуется задействовать UART. Поэтому в разделе «Connectivity» на правой панели нужно выбрать пункт USART1 и на вкладке «Parameter Settings» прописать число 9600 в поле «Baud Rate».

Для завершения настройки осталось перейти во вкладку «Clock Configuration», прописать число 1000 в поле «Input Frequency» на входе LSE, выбрать в селекторе «RTC Clock Mux» позицию LSE, выбрать в селекторе «MCO2 Source Mux» позицию HSI16, установить делитель после селектора в значение 16 и нажать кнопку «Generate Code».

После генерации кода появится предложение открыть проект в среде разработки, с которым следует согласиться.

В проекте нужно создать буфер FIFO, так как события, требующие получения таймстампов могут происходить быстрее, чем осуществляется отправка данных через UART. Для этого в файле main.c сразу после объявления глобальных переменных (хэндлеров) hrtc и huart следует написать так:

volatile uint16_t tsFifoHead = 0;
volatile uint16_t tsFifoTail = 0;
typedef struct
{
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sDate;
} TimeStampTypeDef;
#define TSFIFO_SIZE 32
volatile TimeStampTypeDef tsFifo[TSFIFO_SIZE];

Затем там же, в файле main.c, потребуется добавить функцию (точнее, переписать коллбэк), вызываемую при прерывании, которая бы копировала зафиксированные значения времени в буфер FIFO:

void HAL_RTCEx_TimeStampEventCallback(RTC_HandleTypeDef *hrtc)
{
	uint16_t nextHead = tsFifoHead + 1;
	if(nextHead >= TSFIFO_SIZE) nextHead = 0;
	HAL_RTCEx_GetTimeStamp(hrtc, &(tsFifo[nextHead].sTime), &(tsFifo[nextHead].sDate), RTC_FORMAT_BIN);
	tsFifoHead = nextHead;
}

Наконец, можно в функции main, прямо в главном цикле, реализовать отправку данных из буфера FIFO по UART:

while (1)
{
	#define TXBUFF_SIZE 50
	if(tsFifoTail != tsFifoHead)
	{
		tsFifoTail = tsFifoTail + 1;
		if(tsFifoTail >= TSFIFO_SIZE) tsFifoTail = 0;
		uint8_t tx_buff[TXBUFF_SIZE] = {" "};
		snprintf(tx_buff, TXBUFF_SIZE, "timestamp: %02d:%02d:%02d.%04d \n",
				tsFifo[tsFifoTail].sTime.Hours,
				tsFifo[tsFifoTail].sTime.Minutes,
				tsFifo[tsFifoTail].sTime.Seconds,
				(10000-1)-tsFifo[tsFifoTail].sTime.SubSeconds);
		HAL_UART_Transmit(&huart1, tx_buff, TXBUFF_SIZE, 1000);
	}
}

Теперь соединяем на плате выход микроконтроллера MCO2 со входом RCC_OSC32_IN, выход микрокотроллера USART1_TX со входом Rx моста USB‑UART, собираем проект, загружаем прошивку, запускаем терминал, жмём на кнопку и получаем таймстампы!

И это всё?

Нет, не всё.

Можно оставить в коллбэке прерывания только копирование в FIFO сырых данных из регистров, перенеся разделение на секунды, минуты, часы и дни (а также перевод из двоично‑десятичного кода) в отправку по UART. Или даже перенести эту логику на сервер.
Можно «закольцевать» счётчики начала и конца буфера не по условию превышения максимального количества элементов массива, а наложением маски.

Что для этого потребуется

Объявление FIFO будет выглядеть так:

volatile uint16_t tsFifoHead = 0;
volatile uint16_t tsFifoTail = 0;
typedef struct
{
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sDate;
} TimeStampTypeDef;
#define TSFIFO_WIDTH 5
typedef struct
{
	uint32_t rawTime;
	uint16_t rawSubS;
	uint16_t rawDate;
} RawTimeStampTypeDef;
RawTimeStampTypeDef tsFifo[1<<(TSFIFO_WIDTH+1)];

Коллбэк прерывания будет выглядеть так:

void HAL_RTCEx_TimeStampEventCallback(RTC_HandleTypeDef *hrtc)
{
	static uint16_t nextHead = 0;
	nextHead = (tsFifoHead + 1)&((uint16_t)((1<<(TSFIFO_WIDTH+1))-1));
	tsFifo[nextHead].rawTime = ((RTC_TypeDef*)RTC)->TSTR;
	tsFifo[nextHead].rawSubS = ((RTC_TypeDef*)RTC)->TSSSR;
	tsFifo[nextHead].rawDate = ((RTC_TypeDef*)RTC)->TSDR;
	((RTC_TypeDef*)RTC)->SCR = RTC_SCR_CITSF | RTC_SCR_CTSF;
	tsFifoHead = nextHead;
}

Главный цикл будет выглядеть так:

while (1)
{
	#define TXBUFF_SIZE 50
	if(tsFifoTail != tsFifoHead)
	{
		uint8_t tx_buff[TXBUFF_SIZE] = {" "};
		tsFifoTail = (tsFifoTail + 1)&((uint16_t)((1<<(TSFIFO_WIDTH+1))-1));
		TimeStampTypeDef tsSend;

		tsSend.sTime.SubSeconds = READ_BIT(tsFifo[tsFifoTail].rawSubS, RTC_TSSSR_SS);
		tsSend.sTime.Hours = (uint8_t)((tsFifo[tsFifoTail].rawTime & (RTC_TSTR_HT | RTC_TSTR_HU)) >> RTC_TSTR_HU_Pos);
		tsSend.sTime.Minutes = (uint8_t)((tsFifo[tsFifoTail].rawTime & (RTC_TSTR_MNT | RTC_TSTR_MNU)) >> RTC_TSTR_MNU_Pos);
		tsSend.sTime.Seconds = (uint8_t)((tsFifo[tsFifoTail].rawTime & (RTC_TSTR_ST | RTC_TSTR_SU)) >> RTC_TSTR_SU_Pos);
		tsSend.sDate.Month = (uint8_t)((tsFifo[tsFifoTail].rawDate & (RTC_TSDR_MT | RTC_TSDR_MU)) >> RTC_TSDR_MU_Pos);
		tsSend.sDate.Date = (uint8_t)(tsFifo[tsFifoTail].rawDate & (RTC_TSDR_DT | RTC_TSDR_DU));
		tsSend.sDate.WeekDay = (uint8_t)((tsFifo[tsFifoTail].rawDate & (RTC_TSDR_WDU)) >> RTC_TSDR_WDU_Pos);

		tsSend.sTime.Hours = (uint8_t)RTC_Bcd2ToByte(  tsSend.sTime.Hours);
		tsSend.sTime.Minutes = (uint8_t)RTC_Bcd2ToByte(tsSend.sTime.Minutes);
		tsSend.sTime.Seconds = (uint8_t)RTC_Bcd2ToByte(tsSend.sTime.Seconds);
		tsSend.sDate.Month = (uint8_t)RTC_Bcd2ToByte(  tsSend.sDate.Month);
		tsSend.sDate.Date = (uint8_t)RTC_Bcd2ToByte(   tsSend.sDate.Date);
		tsSend.sDate.WeekDay = (uint8_t)RTC_Bcd2ToByte(tsSend.sDate.WeekDay);

		snprintf(tx_buff, TXBUFF_SIZE, "timestamp: %02d:%02d:%02d.%04d \n",
				tsSend.sTime.Hours,
				tsSend.sTime.Minutes,
				tsSend.sTime.Seconds,
				(10000-1)-tsSend.sTime.SubSeconds);
		HAL_UART_Transmit(&huart1, tx_buff, TXBUFF_SIZE, 1000);
	}
}

Это сократит время выполнения коллбэка примерно в 6 раз.

Можно добавить в логику отправки данных (или, опять же, в логику на сервере) перевод времени в формат UNIX Time — время в секундах, отсчитываемое от 1 января 1970 года.
Можно поменять делитель «Asynchronous Predivider» с 99 на 0, а затем при отправке или на сервере делить получившееся время на 100. Это позволит достичь точности таймстампов в 1 мкс (в вышеприведённых примерах кода эта точность составляет 100 мкс).
Можно полученный функционал дополнить проверкой переполнения буфера FIFO, а также проверкой ситуации, когда два события происходят с задержкой менее 1 мкс.

Но тут возникает вопрос:

Зачем вы всё это рассказываете?

Зачем вы это всё рассказываете сейчас,
- когда есть AN3371 от самого STMicroelectronics, где описывается работ с часами реального времени (в том числе и функционалом таймстампов);
- когда есть ChatGPT, который вполне сносно напишет этот базовый функционал;

- когда есть 219 уроков по STM32, детально описывающих все аспекты работы с этими микроконтроллерами?

К тому же серии STM32 в следующем году исполняется 20 лет. Как сказал в схожей ситуации @Khort:

Лет 15 это все там есть как минимум, отлажено настолько что никто уже давно в этот код и не лезет внутрь. Нет, можно конечно и самому все написать, но я бы клеил шилдик "РЕТРО" к такому материалу.

Во‑первых следует заметить, что в фундаментальном труде «219 уроков по STM32» часы реального времени упоминаются лишь раз и в контексте внешнего подключения часов в виде отдельной микросхемы DS3231. Вообще, по запросу в поисковике «пример RTC_TSTR» (Real‑Time Clock, Time Stamp Time Register) на русском языке найдутся лишь упоминания даташитов, но не проекты или туториалы. Да и на английском языке в профильных сообществах можно увидеть вопросы вроде What is the STM32 "timestamp" feature designed for?

Во‑вторых, на Хабре в 2026 году вполне появляются, например, статьи как «склеить» таймеры, чтобы генерировать таймстампы.
Причём эти статьи до сих пор находят аудиторию и даже своих зилотов, готовых доказывать что без «велосипедов» (взамен штатно предусмотренного механизма) никак не обойтись.

Буквально. Человек в дискуссии доказывал, что RTC в STM32 не способны инкрементироваться с периодом в 1 мкс, так как на картинке древа тактирования в reference manual нарисовано «32.768 kHz»:

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

...такое ограничение в 1000 кГц вполне отражено на древе тактирования в кодогенераторе CubeMX:

В дискуссиях с подобного рода техническими зилотами, ими нередко применяется рационализация (не в смысле «улучшение», а в смысле «оправдание»). Например, можно столкнуться с тезисом: «ну хорошо, микросекундный таймстамп можно получить на основе RTC. А вдруг потребуется бо́льшая точность?»

Надо понимать, что
во‑первых, точность таймстампов будет всегда ограничена максимальной частотой тактирования;
во‑вторых, при фиксированном размере регистра счёта тактовых импульсов, увеличение точности будет приводить к уменьшению максимального периода измерения времени — и наоборот;
в‑третьих, при фиксированной ширине шины памяти, расширение регистра счёта будет увеличивать минимальное время реакции на два соседних события.

Если взять STM32U031R с максимальной частотой тактирования ядра в 56 МГц и попытаться охватить периодом измерения 1 год, обеспечив при этом максимально возможную точность, то потребуется регистр счёта длиной в 51 бит. Это потребует «склеивания» четырёх 16‑битных регистров. Причём «склеивание» включает в себя решение проблемы атомарности копирования, когда скопировав младший регистр в окрестности переполнения, вы не можете сразу точно сказать, успел ли поменяться старший регистр.

Копирование четырёх регистров, а также проверки на переполнение приведут к тому, что реакция на два соседних события будет происходить с задержкой в 2...3 мкс, если не больше.

В рамках рационализации вероятнее всего будет конструироваться такая задача, для которой
- точность таймстампов должна быть строго выше 1 мкс;
- но не превышать 1/56×10⁶с;
- максимальный период должен превышать размер 16‑битного регистра;
- но не слишком сильно;
- при этом формулировка задачи должна гарантировать мягкие требования к реакции на соседние события.

Что‑то похожее на анекдот про «Ты за меня или за медведя?»

Феномен имеет зонтичный характер и сродни эффекту Манделлы.

Есть немалое количество инженеров полагающих, что JTAG это интерфейс для программирования микроконтроллеров, а не система изначально созданная для тестирования межсоединений на печатных платах. Некоторые из таких инженеров даже выдумывают свою систему тестирования межсоединений на базе UART!

Есть немалое количество инженеров полагающих, что наиболее органичным способом вывести отладочный printf(...) из микроконтроллера это передать его содержимое через мост USB‑UART в терминал, а не через SWV отладчика в консоль среды разработки. Некоторые из таких инженеров даже разрабатывают весьма продвинутую систему отладочного текстового ввода‑вывода через UART с ролевой моделью!

Возможно об этом всё же стоит говорить, это стоит обсуждать, если нет желания однажды оказаться в среде специалистов с полностью чуждыми приёмами работы, при этом считающими эти приёмы безальтернативно правильными.

Ведь в ходе обсуждения может быть поднят экзистенциальный вопрос…

Что такое «правильно»?

Вернёмся к таймстампам.

На первый взгляд концепция аппаратных таймстампов выглядит вполне правильно, профессионально и даже быть может респектабельно.
Не важно, замеряете ли вы ими очень малый или очень большой промежуток времени, все данные из RTC скопируются в промежуточный буфер одновременно. Вам не нужно будет решать проблему «склеенных» счётчиков.
Максимальный период измерения времени значителен и составляет 99 лет.
Да и точность в одну микросекунду, а также возможность автоматической фиксации таймстампа по внешнему сигналу кажутся вполне заманчивыми, чтобы использовать аппаратные возможности микроконтроллера.

На практике, если вы попытаетесь в точности повторить последовательность действий из начала статьи, у вас ничего не получится.

Во‑первых по умолчанию современный CubeMX генерирует код для среды разработки EWARM. Чтобы код сгененрировался для STM32CubeIDE необходимо перейти на вкладку «Project Manager» и выбрать в поле «Toolchain/IDE» нужную среду.

Во‑вторых, тактирование RTC возможно осуществить
- от внешнего низкоскоростного источника (LSE, Low Speed External) с частотой до 1 МГц;
- от внутреннего низкоскоростного источника (LSI) с фиксированной частотой в 32 кГц;
- от внешнего высокоскоростного источника (HSE), прошедшего через делитель;

Иными словами, обеспечить получение таймстампов с точностью в 1 мкс без внешних источников тактирования и/или соединения MCO→ RCC_OSC32_IN невозможно.
Но и тактирование RTC от HSE тоже не всегда способно обеспечить 1 мкс. Да, в серии STM32F4 делитель HSE можно выбрать из длинного списка вариантов:

Но в серии STM32U0 делитель фиксированный — 32 раза. При этом, частота самого HSE должна находиться в диапазоне 4...48 МГц, то есть поставив 32‑мегагерцовый генератор, всё ещё есть возможность затактировать RTC через внутреннее соединение.

А, например, в серии STM32F1 фиксированный делитель равен 128, при максимальной частоте HSE в 24 МГц.
То есть рационализация рационализацией, но представить себе целый ряд ситуаций, когда микросекундная точность таймстампов в принципе не будет достижима штатными средствами не так уж и трудно.

Во‑третьих, генератор кода вставляет в инициализацию выхода MCO2...

GPIO_InitStruct.Alternate = GPIO_AF0_TRACE;

...однако в файле stm32u0xx_hal_gpio_ex.h среди констант отсутствует «GPIO_AF0_TRACE», соответственно компиляция свежесгенерированного кода закончится ошибкой.
В этом случае следует вручную поправить код на...

GPIO_InitStruct.Alternate = GPIO_AF0_MCO2;

В‑четвёртых, на демоплате Nucleo‑U031R8 пользовательская кнопка по умолчанию не подтянута к питанию. Чтобы она начала функционировать, нужно вывод 23 гребёнки CN7 соединить через резистор с питанием 3,3 В.

А линия RCC_OSC32_IN не подключена к выходному пину. Да, формально пин PC14 (RCC_OSC32_IN) имеет выход на гребёнку. Но на электрической схеме видно, что контактные площадки перемычки SB22 по умолчанию не замкнуты (DNF).

Потому, чтобы всё заработало нужно сделать как‑то так:

Наконец, самое главное.
У STM32U031R субсекундный регистр RTC, непосредственно считающий такты, является 16‑битным. Следовательно он может посчитать 10 тыс. тактов, что даст точность в 100 мкс.
Ну хорошо, 25 тыс. тактов, что даст точность в 40 мкс.
А для 1 мкс нужен 1 миллион.

Возможен хак, когда RTC будет считать время в 100 раз быстрее, а получившиеся значения затем программно будут поделены на 100.
Казалось бы, при наличие календаря, считающего до 99 лет вполне рабочий вариант.

Но.

Вот так выглядит регистр даты часов реального времени RTC_DR:

А вот так выглядит буферный регистр RTC_TSDR, куда по прерыванию копируются данные регистра RTC_DR:

Да, уважаемые читатели, в отличие от 32‑битного RTC_DR, регистр RTC_TSDR является 16‑битным. Поэтому при копировании по прерыванию годы отрезаются! Соответственно в распоряжении остаётся лишь интервал времени длинною в 1 год, который будучи поделён на 100 (для обеспечения микросекундной точности) даст примерно 3,5 суток максимального периода измерений.

И тут дело не только в таймстампах.

Взять дилемму — вывести printf() через UART или через SWV? Такой выбор есть для STM32F4. Для STM32U0 такого выбора нет. Потому, что ядром STM32U0 является Cortex‑M0+, для которого SWV не предусмотрен, как и ряд другого отладочного функционала, доступного в Cortex‑M3.

Или взять автоматический анализ качества монтажа и целостности дорожек при помощи интерфейса JTAG. Не существует опенсорсного или хотя бы триального ПО, способного по net‑листу из EDA сгенерировать последовательность тестовых векторов, а затем прогнать их через FT2232. Всё многообразие компаний, предлагающих решения для JTAG‑тестирования основывают их на собственных отладчиках.

Но даже если вы купите софт, отладчик и прочитаете в документации на микросхему:

The Boundary Scan Descriptive Language (BSDL) file for this device is available by contacting your local XXX sales representative.

...есть ненулевая вероятность, что ваш local sales representative вернёт вам такой ответ производителя:

Sorry that took so long. We could not find the files for the <IC1_NAME>. The IC design team found a related device the <IC2_NAME> with a similar BSDL file. There are differences in the addressing and /BW cells since the file is for a smaller device (128K vs 512K addresses). Perhaps you could modify this file. Our apps team does not have the capability to rewrite and check a modified file (we no longer have the necessary software).

По сути, выбор «правильного» технического решения периодически похож на задачу: как забить гвоздь, если вам выдали набор инструментов состоящий из эргономичного утюга и молотка с «ограниченным» функционалом, таким, что его рукоятка обрезана на длину 3 сантиметра.
Можете взяться двумя пальцами за 3‑сантиметровую рукоятку, можете обхватить оголовье молотка ладонью, можете бить по гвоздю утюгом. И обосновать правильность любого выбора.

Причём от того, что конкретный инженер знает, что такое RTC, SWV или EDA (или не знает и потому использует «велосипедные» решения), «ручка молотка» не удлинится. Иногда незнание — действительно сила.

Ни тебя ни меня нет на самом деле

Представим себе ситуацию, что однажды функционал таймстампов будет коренным образом переработан. Вместо надстройки над RTC, с их двоично‑десятичным кодом, рассчитанным буквально на часы с табло, будет сделан отдельный 64...96‑битный регистр с тактированием от чего угодно и механизмом прямого доступа к памяти по внешнему событию.

Тогда всем тем, кто привык использовать аппаратные таймстампы в STM32 так, как это описано в начале статьи, можно будет говорить: «Дед, прими таблетки! Так уже никто не работает!»

Тут, однако, вот какое дело.
В 2013 году Intel решила создать «убийцу» Raspberry Pi и заодно Arduino — одноплатный компьютер Intel Galileo. В пресс‑релизах рассказывалось о Windows 8 за $0 на данном одноплатнике.
Затем последовал Intel Galileo Gen.2, затем Intel Edison. Для данных компьютеров была выпущена линейка процессоров Intel Quark, включая Quark D1000 и Quark D2000 — на ядре x86 и в корпусе QFN‑48!
А затем в 2017 году Intel махнула шашкой и закончила производство Intel Edison. Процессоры Intel Quark выпускались до 2019 года и тоже сошли на нет.

При этом, почти одновременно с «новейшим и революционным» семейством Intel Quark, в 2014 году компания Rochester Electronics осуществила перезапуск производства микроконтроллеров Intel 80C196K.
Микроконтроллеры этой линейки выпускались компанией Intel с 1982 по 2007 год. Но в середине 2010‑х они оказались весьма востребованы, а их складские запасы истощены. По состоянию на 2026 год на сайте Rochester микроконтроллеры EN80C196KC20 находятся в состоянии «Manufacturer Life Cycle: Rochester Active».

Зачастую, мы представляем историю как прогресс, как поступательное движение от простого к сложному. Но кто сказал, что это единственное направление? Почему появившись в очередной серии микроконтролеров, гипотетический функционал аппаратных таймстампов не может внезапно уйти в прошлое из‑за прекращения выпуска этой новой серии? А методы работы, характерные для предыдущих поколений устройств — вновь стать актуальными?

Если это так, то правильность решения оказывается в зависимости не столько от физических законов или технических ограничений. Правильность технических решений оказывается социальным конструктом (кстати, так считаю не только я), зависящим от экономической ситуации, традиций среды, особенностей менеджмента, системы образования, отраслевой мифологии и так далее. Правильность становится подвержена влиянию контенста, рефреймингу, как и любые социальные явления.

Есть в этом что‑то глубоко ироничное, что инженеры, гордящиеся (а иногда — кичащиеся) логикой в обосновании тех или решений не так уж сильно отличаются, например, от специалистов гуманитарных дисциплин :)

Комментарии (24)


  1. hardegor
    24.02.2026 17:20

    Много картинок, много рассуждений, а самое главное не написали - ваши таймстампы будут сильно зависеть от температуры, от точности настройки rtc и т.д. Так, побаловаться пойдет, а что-то серьёзное на них не построить.


  1. Khort
    24.02.2026 17:20

    Удивился на цитату. Ну что же, тогда вот и мои три копейки. Не критика, а просто в дополнение.
    По совпадению, примерно с месяц назад откопал свою старую макетку STM32F4 DISCOVERY и решил оживить. Последний раз я что то кодил для мк более 10 лет назад, и тогда же последний раз писал на Си, т.е. забыл почти все по этой теме. Но, мне было очень интересно как LLM может помочь в эмбиддерстве. Что я сделал:
    - спросил чатгпт, какой софт ставить. Был дан ответ, ставить STM CubeMX и CubeIDE. Поставил, запустил.
    - спросил чатгпт, как сгенерить профиль в CubeMX для проекта VirtualCom port для моей платы. Не сразу, но по наводящим вопросам удалось пробиться через конфиги и сгенерить проект. Т.е. все изложенное в этой публикации я проделал под четким руководством чатагпт буквально минут за 15, без чтения документации/гайдов, и вообще не включая мозг (гордиться нечем, знаю, но сам факт).
    - попросил чатгпт сгенерить мне код для VirtualCom port, включая небольшой обработчик команд / эмуляцию консоли. Заработало почти сразу: Я копипастил код в IDE, запускал, ошибки копипастил обратно в чатгпт, и следовал его указаниям. После нескольких таких итераций все заработало.
    - потом еще спросил чатгпт от файн-тюнить код для максимальной пропускной способности виртуального компорта. Ну и заодно спросил хороший терминал для винды, чтобы все это погонять в железе.
    Что я хочу сказать. У меня несколько знакомых программистов сейчас сидят без работы. Компании сокращают штат в пользу LLM, отсюда массовые увольнения. Похоже, это скоро коснется и эмбиддеров. А там и до меня доберутся. Но! Есть и плюсы. Для хоббистов теперь открыты все возможности, порог вхождения опустился ниже плинтуса.


    1. Flammmable Автор
      24.02.2026 17:20

      Сейчас модно ембеддидь на RP2350. А ещё у него есть к основному ядру маленькие ядра PIO, привязанные к GPIO. Эти PIO работают на частоте ядра (150 МГц), у них есть простенький ассемблер из 7 комманд, а также несколько сдвиговых регистров и линий для прерывания основного ядра. Код и для основного ядра и для PIO пишется на Питоне. Разумеется код на Питоне пишут сейчас при помощи LLM.

      Так что вероятно, плату с STM32F4 можно отложить обратно на полку :)


      1. Albert2009Zi
        24.02.2026 17:20

        Очень тонкий ответ ;) Как и сама статья в ответ на статью одного из "экспертов" (по крайней мере мне так показалось) :)


        1. Flammmable Автор
          24.02.2026 17:20

          Очень тонкий ответ ;) 

          Не то, чтобы тонкий. Мне самому STM32 привычней. Но я попробовал RP2350 и вынужден признать, что это очень даже новаторское изделие и вполне себе может вытеснить STM32 из... да из всего.


    1. Koyanisqatsi
      24.02.2026 17:20

      Они сидят без работы, потому что экономика хромает, а не из-за ИИ. Эмбедеры будут нужны, потому что у них есть лапки, а у ИИ нет.


    1. Albert2009Zi
      24.02.2026 17:20

      Попробуйте что-то посложнее. Скажем, что то несложное для джуно-миддла в эмбедде. Например - "напиши на HAL для STM32F4 рабочий драйвер датчика AM2302. Вывод результатов через UART. Постарайся написть так, чтобы можно было просто подставить твой код в пустой проект STM32CubeIDE". Результат ИИ вас разочарует. Мягко говоря, придется подебажить и совершить много итераций.

      Я сам использую ИИ и рад такому инструменту. Но реклама его возможностей, на мой, субъективный взгляд, пока опережает эти возможности.


      1. Khort
        24.02.2026 17:20

        Чат умеет читать даташиты, использовал его для поиска ОУ на замену, в частности искал аналоги TI у AD, и наоборот. Для такой задачи работает, но - требовать написать драйвер на основе описания регистров в даташите, конечно же перебор. Т.е. в данном примере я бы попросил сгенерить код для чтения/записи по интерфейсу (I2C?), а вот уже с регистрами команд и данных этого конкретно датчика разбирался бы вручную. Проверять как это сработает в реале не буду, разумеется. Просто мысли с дивана :-)
        У себя по работе (топология ИС) тоже теперь часто использую ИИ. Буквально за последний год все изменилось, стало проще накидать промт, чем кодить самому. Но - не агитирую, и не спорю что пока это костыли, а не тул


        1. Albert2009Zi
          24.02.2026 17:20

          У себя по работе (топология ИС) тоже теперь часто использую ИИ. 

          Ого. Круто. Прямо кристаллы проектируете? Приятно, что эксперты вашего уровня есть в русскоязычном коммюнити.


        1. Flammmable Автор
          24.02.2026 17:20

          У себя по работе (топология ИС)

          Кстати, любопытно, что происходит с топологом после того, как микросхема выпущена в производство?

          Я имею ввиду, что в до-ИИ-шные времена линейные программисты какого-нибудь энтерпрайза могли работать в режиме "фигак-фигак-в-продакшн", а потом долго сидеть "на поддержке".

          С электронщиками всё сложнее. Пока устройство разрабатывается, электронщик нужен, но он не приносит доход бизнесу. Как только устройство выпущено, бизнес начинает получать прибыль, однако электронщик, вообще говоря, перестаёт быть нужным.

          Но в электронике хотя бы порог входа исчисляется сотней долларов. Простенькая плата в Резоните тысяч 5; МК, рассыпуха, монтаж - тоже плюс-минус также.
          То есть
          во-первых при наличие серъезных проблем с первой версией изделия всё-таки есть возможность выпустить версию №2;
          во-вторых есть возможность работать над единичными устройствами.

          А в разработке микросхем как?

          Как представляется, единичное производство - это вообще не про микросхемы.
          Да и выпуск ver.2, ver.3, ver.100500 - тоже накладен.

          Прогоняет ли менеджмент такую тему, типа:
          "Мы всё понимаем и искренне ценим ваш вклад. Но и вы нас поймите, мы коммерческая организация. После успеха нашего чипа мы ориентированы на масштабирование его производства и расширении сбыта. На данный период мы заинтересованы скорее в большем количестве маркетологов. Но мы помним о вас и обратимся, если решим расширить линейку. Просто сейчас для этого не самый подходящий момент"?


          1. Khort
            24.02.2026 17:20

            Кстати, любопытно, что происходит с топологом после того, как микросхема выпущена в производство?

            Вы все верно написали. Чем больше штат, тем больше заказов, и эффективнее загрузка инженеров работой. Не говоря уже про лицензии на софт.

            Разовые проекты интересны инженеру только если это твой стартап, либо если работаешь как ИП с несколькими клиентами. Понятно, что в РФ мало кто так может, поэтому предпочитают сидеть на зарплате ровно и пинать балду между проектами. Так же и менеджменту интереснее работать с ИП, а не содержать штат, но на деле мало кто готов бодаться с налоговой (за работу с ИП "кошмарят").

            Еще потихоньку развиваются аутстафф сервисы, но туда идут работать только с иностранцами (китайцами), во всех остальных случаях это никому не выгодно - ни инженерам, ни их клиентам. Могу ошибаться.


            1. Flammmable Автор
              24.02.2026 17:20

              Чем больше штат, тем больше заказов, и эффективнее загрузка инженеров работой. Не говоря уже про лицензии на софт.

              Как представляется, выпускать каждую неделю по принципиально новой микросхеме ни у какого отдельного заказчика нет необходимости. А значит, команда разработчиков будет сбиваться в инжиниринговый центр и "засасывать" все возможные заказы, чтобы пустить равномерный поток работы через себя.

              Если с разработкой смартфонов происходит именно так (Ваш Xiaomi — это не Xiaomi. Кто делает китайские телефоны на самом деле?), то уж с разработкой микросхем, как кажется, - и подавно должно.

              Но укрупнение и концентрация ресурсов должны приводить к монополии.
              А монополия - к унификации и отсутствию конкуренции. По идее.

              И, типа, варианты для решения какой-то технической задачи:
              - либо, условно, покупается готовый MIK32 Амур;
              - либо "разрабатывается кастомная микросхема", но она будет разрабатываться в том же центре, где разрабатывался Амур, людьми, лучше всего разбирающимися в Амуре, и она будет чрезвычайно похожа на Амур.

              Второй вариант приводит к мысли, а не проще ли тогда всё-таки использовать первый вариант? Не проще ли, условно, сделать свой SoM на базе Амура и дискретных компонентов, чем заказывать разработку новой микросхемы?


  1. rukhi7
    24.02.2026 17:20

    Если взять STM32U031R с максимальной частотой тактирования ядра в 56 МГц и попытаться охватить периодом измерения 1 год, обеспечив при этом максимально возможную точность

    интересно зачем может потребоваться год измерения с такой точностью?

    Но если в прерываниях таймера просто инкрементировать 32-битный ИНТ, то на месяц вполне хватит значения счетчик таймера+32 бит переменная, и таймеры соединять не надо (и никого продавать не надо, как говорил дядя Федор). Но максимальная точность (=тактовой частоте проца) у вас все равно не получится потому что нужно время чтобы считывать значения, а в это ВРЕМЯ может произойти прерывание... и не только от этого таймера.

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


    1. DrGluck07
      24.02.2026 17:20

      А на некоторых контроллерах уже есть 64-битный таймер. Можно настроить на десятые микросекунды и не париться вообще.


      1. Flammmable Автор
        24.02.2026 17:20

        Главная проблема не в том, чтобы хоть чем-то досчитать до 2⁶⁴. Главная проблема, как при 32-битной шине памяти атомарно скопировать имеющееся значение из регистра в эту самую память.


  1. vladik_ne_vladik
    24.02.2026 17:20

    Спасибо за статью.

    В постановке задачи генерации абсолютных временных меток есть не упомянутая в статье сложность. Понятие абсолютного времени предполагает привязку либо к мировой инфраструктуре синхронизации времени (условно, NTP), либо к другим устройствам, использующим эти же абсолютные временные метки.

    По указанной причине рассматривать применение RTC для генерации точных абсолютных меток некорректно без учёта точности значения, лежащего в самом RTC. А если в рассмотрение взять синхронизацию абсолютного времени, то точность абсолютного времени будет зависеть в большей степени от него.

    Для задачи измерения длительности интервала времени проще использовать интерфейс 64-битного бегущего счётчика. Такой можно построить на основе 32-битного системного таймера в STM32.


    1. Flammmable Автор
      24.02.2026 17:20

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

      применение RTC для генерации точных абсолютных меток некорректно без учёта точности значения, лежащего в самом RTC.

      Уже второй комментарий в этом не слишком бурном обсуждении со словосочетанием вроде "точность самого RTC". Есть точность тактового сигнала, используемого RTC, это да.
      А что такое "точность самого RTC"?


      1. vladik_ne_vladik
        24.02.2026 17:20

        Часы реального времени хранят текущие дату и время. В сответствие с тактовым сигналом, от которого запитан RTC, происходит увеличение текущего значения времени. В этом процессе играет роль точность генератора тактового сигнала RTC (сдвиг частоты генерации от заявленной производителем, стабильность частоты генерации).

        Но надо не забывать, что часы реального времени начинают отсчёт с некоторого значения, которое тоже может быть неточным. Это может играть роль только если значение абсолютного времени, полученного из этого RTC, сопоставляется со значением времени, полученным из другого RTC (на какой-то другой машине).

        Если сопоставлять таймстемпы событий нужно только в рамках одной машины, то RTC здесь не единственное решение, и более точный результат по измерению длительности интервала времени обычно дают счётчик или таймер, но не часы реального времени.

        На STM32 системный таймер 32-битный, и требуется обрабатывать прерывания по переполнению. rdtsc на x86, конечно, удобнее.


        1. Flammmable Автор
          24.02.2026 17:20

          Если сопоставлять таймстемпы событий нужно только в рамках одной машины, то RTC здесь не единственное решение, и более точный результат по измерению длительности интервала времени обычно дают счётчик или таймер, но не часы реального времени.

          Дано: вам нужно собирать таймстампы и измерять между ними интервалы времени длительностью, скажем, до 10 суток, ядро микроконтроллера затактировано от внешнего кварцевого генератора (HSE). Также от HSE через делитель затактированы RTC.

          Расскажете, как таймер окажется точнее, чем RTC?


          1. vladik_ne_vladik
            24.02.2026 17:20

            Случайно ответил на этот комментарий в новой ветке обсуждения


  1. vladik_ne_vladik
    24.02.2026 17:20

    Предлагаю решение для STM32F4, т.к. документация под рукой (точность решения оценочно 1 мкс).
    Оно задействует только SysTick из Cortex M4.

    Рекомендованная частота тактирования RTC: RTCCLK = 1 МГц.
    По дереву тактирования можно настроить хоть HSE/2 (регистр RCC_CFGR, биты RTCPRE),
    но в описании предделителя указано, что требуется настроить входную частоту RTC в 1 МГц.
    В любом случае, интересно сравниться с 1 МГц.

    Для SysTick максимальная частота тактирования - это частота шины AHB, т.е. частота HSE.
    Размер счётчика SysTick - 24 бита. Для HSE=100МГц его хватит на пару секунд, это сильно меньше требуемого интервала времени.

    Поэтому текущее время в наносекундах будем хранить в 64-битной переменной counter (точнее, старшие 40 бит).

    Для счётчика SysTick настроим прерывание с частотой 100 Гц. Для частоты HSE=100МГц RELOAD_VALUE = 10000000, это помещается в 24 бита.

    В обработчике прерывания будем инкрементировать значение переменной counter на RELOAD_VALUE, т.е. на 10000000.

    Для получения текущего значения счётчика в цикле:

    1. Считываем переменную counter:

      - Считываем младшие 32 бита counter.

      - Выполняем барьерь dmb во избежание переупорядочивания транзакций на шине.

      - Считываем старшие 32 бита counter.

    2. Считываем текущее значение счётчика SysTick

    3. После барьера памяти (dmb) заново считываем младшие 32 бита переменной counter.

    4. Если младшие 32 бита счётчика изменилились, то переходим к этапу 1 цикла.

    После цикла текущее значение счётчика SysTick необходимо сложить с текущим значением переменной counter, при этом не забывая, что SysTick - убывающий счётчик.
    По сути, SysTick - это младшие 24 бита метки времени, а counter - это старшие 40 бит.

    Цикл необходим для синхронизации с обработчиком прерывания SysTick. При этом оборотов цикла будет не более двух, т.к. прерывания SysTick достаточно удалены друг от друга.

    С учётом того, что тут десятка два инструкций (реализация на ASM), решение всё-таки не даст точности больше 1 мкс при HSE=100 МГц.
    Но оно как будто бы и не хуже решения с RTC. 10 суток в 64 битах помещаются.

    Если что, я его не выдумал, оно вполне себе работает.


    1. rukhi7
      24.02.2026 17:20

      4.Если младшие 32 бита счётчика изменилились, то переходим к этапу 1 цикла.

      кажется возвращаться в начало цикла смысла уже нет если вам нужен тайм-стамп события которое запустило измерение,то есть произошло до начала цикла. Можно просто вернуть инкрементированное значение старших битов с младшими установленными в ноль, а для лучшей точности надо декремент делать старших битов и соединить с изначальным значением младших. Очень внимательно надо смотреть что вы хотите получить! Это значение времени (в относительных тиках) непосредственно до измерения или в определенный момент во время измерения. Погрешность измерения разных моментов будет разная в течении периода измерения и расчета измеренного значения. Но для тех кто привык уже работать с помощью промтов уровня: " как сгенерить профиль в CubeMX для проекта VirtualCom port для моей платы." (это как пример, а не в упрек автору промта, я и сам иногда такие промты пишу, то есть с них начинаю выяснение по теме теперь, имеются ввиду те кто не хочет уже идти глубже этих промтов) все детали этого решения вряд ли получится осознать.


      1. Flammmable Автор
        24.02.2026 17:20

        Но оно как будто бы...
        кажется возвращаться в начало цикла смысла уже нет...
        Очень внимательно надо смотреть что вы хотите получить!

        ...или использовать готовое аппаратное решение и стандартные библиотечные функции. Ага.

        Мой изначальный вопрос относился к вашему тезису:

        более точный результат по измерению длительности интервала времени обычно дают счётчик или таймер, но не часы реального времени.

        И таймер и RTC, в своей основе имеют счётчики, считающие импульсы. Мне было интересно, за счёт чего вы предполагаете увеличение точности первого в сравнении со вторым, если они могут быть затактированы от единого источника?

        На этот вопрос вы вполне ответили:

        С учётом того, что тут десятка два инструкций (реализация на ASM), решение всё-таки не даст точности больше 1 мкс при HSE=100 МГц...


        1. rukhi7
          24.02.2026 17:20

          вы перепутали это не мои тезизы. Но я могу пояснить почему

          более точный результат по измерению длительности интервала времени обычно дают счётчик или таймер, но не часы реального времени.

          это не корректно:

          И таймер и RTC, в своей основе имеют счётчики, считающие импульсы

          так как И таймер и RTC имеют в своей основе генераторы импульсов (частоты), но для RTC используются генераторы частоты повышенной точности и с подстройкой этой частоты, правильнее наверно говорить повышенной стабильности. Например погрешность-изменение от времени частоты обычного кварцевого генератора обычно это отклонение (неопределенность) в шестом знаке значения частоты, если посчитать что это значит например на 10МГц, это будет что-то в районе отклонения на минуты в сутки. Для RTC это отклонение не должно превышать какие-то милли секунды в сутки, это достигается в том числе за счет того что частота гораздо RTC ниже (медленнее). Это обычно принципиально разные генераторы.