Files
2026-03-19 17:03:08 +03:00

190 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- mcu
- stm32
- HAL
---
---
### 1. Блок: Архитектура, ядро и память (Cortex-M)
_Цель: оценить понимание работы процессорного ядра, памяти и загрузки._
**Вопрос 1:**
Опишите полный путь программы от нажатия кнопки "питание" до выполнения первой инструкции в `main()`. Что происходит в Startup-файле?
- **Ожидаемый ответ:**
1. После подачи питания и сброса, процессор забирает первое слово (32 бита) из таблицы векторов (начало памяти, обычно 0x08000000) — это начальное значение **основного указателя стека (MSP)**.
2. Процессор забирает второе слово из таблицы векторов — это адрес **обработчика сброса (Reset_Handler)**.
3. Выполняется код `Reset_Handler` (обычно написан на ассемблере или Си в startup-файле). Его задачи:
- Копирование инициализированных данных из Flash в RAM (секция `.data`).
- Обнуление секции неинициализированных переменных (секция `.bss`).
4. Вызывается функция `SystemInit()` (настройка базовой частоты, PLL).
5. Вызывается функция `main()`.
**Вопрос 2:**
В чем разница между стеком (Stack) и кучей (Heap)? Где хранятся локальные и глобальные переменные?
- **Ожидаемый ответ:**
- **Stack:** Растет вниз. Используется для хранения локальных переменных, адресов возврата функций, сохранения контекста при прерываниях. Память выделяется автоматически и очень быстро.
- **Heap:** Используется для динамического выделения памяти (`malloc`/`new`). В bare-metal (голом металле) и критичных к времени системах на МК использование кучи часто ограничивают или запрещают из-за фрагментации и недетерминизма.
- **Глобальные переменные:** Хранятся в RAM (секции `.data` и `.bss`).
- **Локальные:** На стеке.
- **Константы (`const`):** В Flash (секция `.rodata`).
**Вопрос 3:**
Для чего в программировании регистров используется ключевое слово **`volatile`**? Приведите два примера из практики STM32.
- **Ожидаемый ответ:**
Ключевое слово запрещает компилятору оптимизировать доступ к переменной, заставляя каждый раз читать её из памяти, а не использовать кэшированное значение в регистре CPU.
1. **Регистры периферии:** Например, флаг `RXNE` в регистре `USART_SR`. Если компилятор увидит цикл `while (!(USART_SR & RXNE_FLAG));`, без `volatile` он может оптимизировать чтение, вынеся его за пределы цикла.
2. **Переменные, разделяемые с прерываниями:** Глобальный флаг, который устанавливается в прерывании и проверяется в основном цикле `while(flag == 0) {}`. Без `volatile` компилятор может решить, что флаг никогда не меняется в основном потоке, и зациклить программу [](https://www.nowcoder.com/discuss/793216015415934976?sourceSSR=post)[](https://www.pvsm.ru/sobesedovanie/445258#begin).
### 2. Блок: Библиотеки (HAL, LL, SPL) и периферия
_Цель: проверить практический опыт написания кода и конфигурации._
**Вопрос 4:**
Чем отличается HAL от LL? В каком случае вы выберете LL, а в каком HAL?
- **Ожидаемый ответ:**
- **HAL (Hardware Abstraction Layer):** Высокоуровневые, переносимые функции с таймаутами. Удобен для быстрой разработки, сложных стеков (USB, ETH), когда скорость выполнения не критична. "Толстый" и может быть медленным.
- **LL (Low-Layer):** Легковесные, inline-функции, работающие почти напрямую с регистрами. Нужен для high-speed периферии (SPI на максимальной частоте, тактирование таймеров) или для проектов с жесткими ограничениями по памяти/производительности. Требует глубокого понимания работы регистров.
- _Доп. вопрос:_ Можно ли их миксовать? (Ответ: Можно для разных периферийных устройств, но для одного и того же (например, UART) лучше не стоит из-за конфликтов управления состояниями (handles) [](https://www.programmersought.com/article/79177505633/).
**Вопрос 5:**
Настройте таймер (например, TIM2) на генерацию прерывания с частотой 1 кГц при частоте шины APB1 = 80 МГц.
- **Ожидаемый ответ (ход мыслей):**
1. Включить тактирование таймера (`__HAL_RCC_TIM2_CLK_ENABLE()`).
2. Настроить предделитель (**Prescaler**) и период (**AutoReload**).
3. Расчет: Частота таймера = 80 МГц / (Prescaler + 1). Желаемая частота срабатывания = Частота таймера / (AutoReload + 1) = 1 кГц.
4. Выбираем компромисс, например: `Prescaler = 8000 - 1` => частота таймера = 10 кГц. Тогда `AutoReload = 10 - 1` => получим 1 кГц.
5. Включить прерывание по обновлению (Update Interrupt) в NVIC.
6. Запустить таймер.
**Вопрос 6:**
В чем «подвох» работы с **I2C** в STM32 через HAL? Почему иногда "вешает" систему?
- **Ожидаемый ответ:**
- Функции типа `HAL_I2C_Master_Transmit()` по умолчанию работают с бесконечным таймаутом (`HAL_MAX_DELAY`) или большим. Если устройство на шине "зависло" и не отвечает ACK, программа заблокируется в этом цикле.
- Необходимо либо использовать режимы с таймаутом (указать конечное значение в мс), либо переходить на **прерывания (Interrupt)** или **DMA**, чтобы освободить CPU.
- Также нужно не забывать про программный сброс шины, если она зависла.
### 3. Блок: Продвинутая периферия (DMA, Interrupts, RTOS)
_Цель: оценить способность писать эффективный многозадачный код._
**Вопрос 7:**
Объясните механизм работы DMA (Direct Memory Access). Как вы организуете прием потока данных по UART (RS-485) с помощью DMA в режиме "кругового буфера" (Circular mode)?
- **Ожидаемый ответ:**
- DMA — это интеллектуальный "пересыльщик" данных, который работает независимо от ядра CPU.
- Настройка UART DMA Rx в круговом режиме:
1. Выделяем буфер в памяти (например, `uint8_t uart_rx_buffer[256]`).
2. Настраиваем DMA на запись данных из регистра `UART->DR` в этот буфер.
3. Включаем прерывание от DMA по достижению половины буфера (Half-Transfer) и по завершению (Transfer Complete).
4. В обработчике прерывания мы понимаем, какая половина буфера сейчас заполнена, и можем обрабатывать данные, пока DMA продолжает писать во вторую половину. Это классический механизм double-buffer (ping-pong).
**Вопрос 8 (RTOS):**
В чем разница между семафором (Semaphore) и мьютексом (Mutex)? Что такое **инверсия приоритета (Priority Inversion)** и как FreeRTOS с ней борется?
- **Ожидаемый ответ:**
- **Семафор:** Счетчик. Используется для уведомления о событиях или подсчета ресурсов (может быть дан несколько раз).
- **Мьютекс:** Двоичный семафор, но с приоритетом. Обладает механизмом **наследования приоритета** (Priority Inheritance). Используется для защиты общего ресурса от одновременного доступа.
- **Инверсия приоритета:** Ситуация, когда высокоприоритетная задача ждет ресурса, захваченного низкоприоритетной, а средняя задача (не работающая с ресурсом) вытесняет низкую и выполняется первой, косвенно "опуская" приоритет высокой задачи ниже средней.
- **Решение в FreeRTOS:** На время, пока мьютекс захвачен низкоприоритетной задачей, ее приоритет временно повышается до уровня ожидающей высокоприоритетной задачи (наследование приоритета) [](https://www.nowcoder.com/discuss/793216015415934976?sourceSSR=post)[](https://www.pvsm.ru/sobesedovanie/445258#begin).
**Вопрос 9:**
Проектируем устройство сбора данных с 8 датчиков по SPI (макс. скорость 10 МГц). Данные должны обрабатываться и отправляться по USB. Предложите архитектуру задачи/прерывания/DMA.
- **Ожидаемый ответ:**
1. Так как скорость высокая, используем **SPI + DMA** для опроса датчиков (по таймеру). DMA автоматически перекидывает данные в память.
2. По завершению DMA (Transfer Complete) генерируется прерывание, которое отправляет семафор задаче `DataProcessor`.
3. Задача `DataProcessor` (средний приоритет) просыпается, обрабатывает массив данных (усреднение, фильтрация, подсчет CRC).
4. Обработанные данные кладутся в **очередь (Queue)** для задачи `USB_Sender`.
5. Задача `USB_Sender` (высокий приоритет) забирает из очереди и отправляет через USB-стек (который внутри себя тоже использует DMA).
### 4. Блок: Низкий уровень (CMSIS, Регистры) и отладка
_Цель: отделить инженеров, которые понимают "как это работает под капотом" от тех, кто просто копирует код из CubeMX._
**Вопрос 10:**
Покажите (или опишите), как на прямую через регистры (CMSIS) настроить ножку PA5 на выход и установить на ней высокий уровень. (Без HAL).
- **Ожидаемый ответ:**
c
// Включаем тактирование порта A (RCC)
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// Настраиваем PA5 как общий выход (Push-pull)
GPIOA->MODER &= ~GPIO_MODER_MODER5_Msk; // Очищаем биты
GPIOA->MODER |= GPIO_MODER_MODER5_0; // 01 = General purpose output mode
// Устанавливаем высокий уровень на выходе (Output Data Register)
GPIOA->ODR |= GPIO_ODR_OD5;
**Вопрос 11:**
Программа на МК периодически "вылетает" (уходит в `HardFault_Handler`). Ваши действия по отладке?
- **Ожидаемый ответ:**
1. **Анализ стека:** Смотрю на значения регистров в момент остановки (особенно Program Counter, Link Register, Stack Pointer).
2. **Чтение регистров фault'а:** В STM32 есть специальные регистры (CFSR, HFSR, BFAR), которые точно указывают причину: обращение к памяти по неверному адресу (MemManage), выполнение недопустимой инструкции (UsageFault) или деление на ноль [](https://www.pvsm.ru/sobesedovanie/445258#begin).
3. **Использование отладчика:** Можно посмотреть дизассемблированный код по адресу из Program Counter.
4. **Вероятные причины:**
- Выход за границы массива (испортили стек).
- Размер стека задачи в RTOS слишком мал.
- Использование указателя без проверки на NULL.
- Обращение к уже освобожденной памяти.