mirror of
https://github.com/EmbedDevIOT/obsidian-vault-.git
synced 2026-11-06 01:13:26 +03:00
vault backup: 2026-03-19 17:03:08
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
---
|
||||
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.
|
||||
|
||||
- Обращение к уже освобожденной памяти.
|
||||
Reference in New Issue
Block a user