Что меняем в проекте

В предыдущей статье о 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 и землю. Подтяжка и светодиод находятся в одной цепи; внешнего резистора не добавляем.

PA5 open-drain: внутренняя подтяжка и LD2Слева ODR5 равен нулю: N-MOS включён, LD2 погашен. Справа ODR5 равен единице: каскад отпущен, слабая внутренняя подтяжка обеспечивает небольшой ток встроенного LD2. Внешних компонентов нет.PA5 open-drain: внутренняя подтяжка и LD2ODR5 = 0: N-MOS включёнVDDВнутренняяPull-upPA5N-MOSвключёнR31LD2Светодиод погашенODR5 = 1: N-MOS выключенVDDВнутренняяPull-upPA5N-MOSвыключенR31LD2Ток LD2 идёт через слабую подтяжкуИспользуется только PA5. SysTick переключает ODR5 каждые 500 мс.Яркость может быть намного ниже, чем в push-pull. Напряжение отпущенного вывода зависит от LD2.
Два состояния PA5 в этом проекте. Подтяжка находится внутри STM32, а R31 и 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.

  1. В STM32CubeIDE импортируйте каталог через File → Import → General → Existing Projects into Workspace. Не включайте копирование в workspace: относительный путь к Libraries должен сохраниться.
  2. После сборки выполните Refresh. В Debug Configurations выберите f401_led_blink_systick_open_drain, проверьте ELF этого проекта, ST-LINK GDB server и интерфейс SWD.
  3. Нажмите Debug, затем Resume после остановки в main. Не оставляйте активный breakpoint внутри обработчика, если наблюдаете непрерывное мигание.

Подробности сборки и запуска находятся в README проекта.

Что проверяем

  1. После инициализации GPIO проверьте: поле MODER5 = 01, OTYPER5 = 1, поле PUPDR5 = 01.
  2. Запустите непрерывное выполнение. LD2 должен чередовать выключенное состояние и слабое свечение; видимость свечения зависит от платы и освещения.
  3. Если свечение неразличимо, поставьте breakpoint на строку записи BSRR в обработчике. Выполните строку и проверьте (GPIOA->ODR & 0x20U). Через следующие 500 тиков значение должно смениться между 0 и 0x20. Это подтверждает команды каскаду, но само по себе не доказывает свечение LD2.
  4. Удалите breakpoint и продолжите выполнение. Пауза отладчика нарушает наблюдение реального периода; время ожидания на остановленном процессоре не считается работой таймера.

Значение IDR5 при отпущенном выходе под нагрузкой LD2 не используем как обязательный признак единицы. В этой схеме нагрузка влияет на напряжение контакта.

Если результат отличается

СимптомЧто проверить
LD2 очень тусклыйЭто ожидаемое ограничение питания через слабую внутреннюю подтяжку. Проверьте также периодические изменения ODR5.
Светодиод работает с прежней яркостьюПроверьте OTYPER5: возможно, загружен прежний push-pull-проект или PA5 нагружен внешней схемой.

Далее

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

Дополнительная документация: AN4899 — входные и выходные каскады GPIO.