Что меняем в проекте
В предыдущей статье о SysTick вывод PA5 был настроен как Output. Бит OTYPER5 не менялся и сохранял нулевое значение после сброса — push-pull. Выходной каскад сам задавал высокий и низкий уровни.
Новый проект — самостоятельная копия f401_led_blink_systick. В программе добавлены только выбор open-drain для PA5 и включение его внутренней подтяжки к питанию. Частота SysTick, переключение раз в 500 мс, обработчик и ожидание __WFI() сохранены. Другие выводы не добавляем.
Что понадобится
Плата NUCLEO-F401RE со встроенным ST-LINK/V2-1 и USB-кабель с передачей данных. Используем встроенный LD2 на PA5. Внешние компоненты и провода не нужны. Подключите USB к разъёму ST-LINK CN1; обе перемычки CN2 должны быть установлены. Снимите платы расширения, использующие PA5.
LD2 соединён с PA5 через штатную цепь платы, включающую SB21 и последовательный резистор R31. Соединение SB21 должно быть замкнуто. Разводку сверяйте со схемой MB1136 своей ревизии.
Как open-drain управляет LD2
У open-drain верхний транзистор выходного каскада отключён. Ноль в ODR5 включает нижний N-MOS и притягивает PA5 к земле — LD2 погашен. Единица выключает N-MOS: выходной каскад отпускает контакт, переходя в состояние высокого сопротивления Hi-Z.
Чтобы появился ток LD2, включаем внутреннюю Pull-up. При отпущенном каскаде ток идёт от питания через эту подтяжку, штатную цепь LD2 и землю. Подтяжка и светодиод находятся в одной цепи; внешнего резистора не добавляем.
Внутренняя подтяжка имеет сопротивление порядка десятков килоом. Это слабый источник тока: LD2 может светиться значительно тусклее, чем при push-pull, и быть плохо виден при ярком освещении. Гарантировать прежнюю яркость такая схема не позволяет. Электрические характеристики подтяжки приведены в datasheet STM32F401xD/xE.
Единица в ODR5 здесь означает «отпустить каскад». Напряжение PA5 зависит от подтяжки и нагрузки LD2 и не обязано достигать питания. По ODR5 нельзя заключить, что на контакте гарантирован высокий цифровой уровень.
Две новые настройки
Между заданием начального состояния PA5 и включением режима выхода добавлены две строки:
GPIOA->OTYPER = GPIOA->OTYPER | GPIO_OTYPER_OT5;
GPIOA->PUPDR = (GPIOA->PUPDR & ~GPIO_PUPDR_PUPD5) | GPIO_PUPDR_PUPD5_0;
В OTYPER одному выводу соответствует один бит. Установка OTYPER5 в 1 выбирает open-drain; остальные биты сохраняются.
В PUPDR одному выводу соответствуют два бита. Маской очищаем поле PA5 и записываем 01 — Pull-up. Значение 00 отключило бы внутренние подтяжки, 10 выбрало бы Pull-down. При выключенном N-MOS именно Pull-up обеспечивает небольшой ток LD2.
Поле MODER5 по-прежнему равно 01 — цифровой выход. Тип каскада выбирается через OTYPER отдельно от режима MODER. Регистры описаны в RM0368, §8.4.1–8.4.4.
SysTick и полный код
SysTick вызывает обработчик номинально каждую миллисекунду. Через 500 вызовов обработчик меняет g_led_on и записывает BS5 или BR5 в BSRR. BS5 устанавливает ODR5 и отпускает каскад; BR5 сбрасывает ODR5 и включает N-MOS. Полный период переключений составляет около секунды.
SystemCoreClockUpdate(), проверка результата SysTick_Config(), extern "C" для обработчика и __WFI() остаются такими же, как в предыдущем проекте. Ниже код из Sources/main.cpp без служебного заголовка файла:
#include <cstdint>
#include "stm32f4xx.h"
namespace {
constexpr std::uint32_t kMillisecondsPerSecond = 1000U;
constexpr std::uint32_t kBlinkIntervalMs = 500U;
std::uint32_t g_elapsed_ms = 0U;
bool g_led_on = false;
} // namespace
extern "C" void SysTick_Handler()
{
g_elapsed_ms += 1U;
if (g_elapsed_ms == kBlinkIntervalMs) {
g_elapsed_ms = 0U;
g_led_on = !g_led_on;
GPIOA->BSRR = g_led_on ? GPIO_BSRR_BS5 : GPIO_BSRR_BR5;
}
}
int main()
{
// LD2 on NUCLEO-F401RE is connected to PA5.
RCC->AHB1ENR = RCC->AHB1ENR | RCC_AHB1ENR_GPIOAEN;
(void)RCC->AHB1ENR;
GPIOA->BSRR = GPIO_BSRR_BR5;
GPIOA->OTYPER = GPIOA->OTYPER | GPIO_OTYPER_OT5;
GPIOA->PUPDR = (GPIOA->PUPDR & ~GPIO_PUPDR_PUPD5) | GPIO_PUPDR_PUPD5_0;
GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER5) | GPIO_MODER_MODER5_0;
SystemCoreClockUpdate();
if (SysTick_Config(SystemCoreClock / kMillisecondsPerSecond) != 0U) {
for (;;) {
}
}
for (;;) {
__WFI();
}
}
Сборка и загрузка
Нужны CMake 3.21+, Ninja и ARM GCC/G++ в PATH. Проект использует C++20 и CMSIS без HAL/LL. Если зависимости ещё не инициализированы, из корня репозитория выполните:
git submodule update --init Libraries/STM32CubeF4
git -C Libraries/STM32CubeF4 submodule update --init Drivers/CMSIS/Device/ST/STM32F4xx
Затем соберите новый проект:
cd f401_led_blink_systick_open_drain
cmake --preset Debug
cmake --build --preset Debug
Результат — Debug/f401_led_blink_systick_open_drain.elf. Startup, linker script и исходники принадлежат новому проекту; общая библиотека CMSIS остаётся в Libraries/STM32CubeF4.
- В STM32CubeIDE импортируйте каталог через File → Import → General → Existing Projects into Workspace. Не включайте копирование в workspace: относительный путь к Libraries должен сохраниться.
- После сборки выполните Refresh. В Debug Configurations выберите
f401_led_blink_systick_open_drain, проверьте ELF этого проекта, ST-LINK GDB server и интерфейс SWD. - Нажмите Debug, затем Resume после остановки в main. Не оставляйте активный breakpoint внутри обработчика, если наблюдаете непрерывное мигание.
Подробности сборки и запуска находятся в README проекта.
Что проверяем
- После инициализации GPIO проверьте: поле MODER5 = 01, OTYPER5 = 1, поле PUPDR5 = 01.
- Запустите непрерывное выполнение. LD2 должен чередовать выключенное состояние и слабое свечение; видимость свечения зависит от платы и освещения.
- Если свечение неразличимо, поставьте breakpoint на строку записи BSRR в обработчике. Выполните строку и проверьте
(GPIOA->ODR & 0x20U). Через следующие 500 тиков значение должно смениться между 0 и 0x20. Это подтверждает команды каскаду, но само по себе не доказывает свечение LD2. - Удалите breakpoint и продолжите выполнение. Пауза отладчика нарушает наблюдение реального периода; время ожидания на остановленном процессоре не считается работой таймера.
Значение IDR5 при отпущенном выходе под нагрузкой LD2 не используем как обязательный признак единицы. В этой схеме нагрузка влияет на напряжение контакта.
Если результат отличается
| Симптом | Что проверить |
|---|---|
| LD2 очень тусклый | Это ожидаемое ограничение питания через слабую внутреннюю подтяжку. Проверьте также периодические изменения ODR5. |
| Светодиод работает с прежней яркостью | Проверьте OTYPER5: возможно, загружен прежний push-pull-проект или PA5 нагружен внешней схемой. |
Далее
Теперь мы изменили электрическое поведение одного и того же PA5, сохранив код таймера. В следующей статье подробно разберём устройство вывода и режимы GPIO, затем настроим вход кнопки и опрос в главном цикле.
Дополнительная документация: AN4899 — входные и выходные каскады GPIO.