diff --git a/My Wiki/MCU.md b/My Wiki/MCU.md new file mode 100644 index 0000000..bb41d30 --- /dev/null +++ b/My Wiki/MCU.md @@ -0,0 +1,196 @@ +--- +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. + + - Обращение к уже освобожденной памяти. \ No newline at end of file