Перейти к основному содержимому

Работа с GPIO

примечание

АО «БАЙКАЛ ЭЛЕКТРОНИКС», ИНН: 7707767484
АО «БАЙКАЛ ЭЛЕКТРОНИКС» оставляет за собой право вносить любые изменения в настоящий документ без дополнительного уведомления.

Скачать PDF ⬇️ AppNote_gpio_v1.pdf

Введение

В данном документе представлена информация, примеры и разъяснения по работе с контроллером GPIO. Документ снабжен примерами кода на языке Си.

подсказка

Примеры кода в настоящем документе разработаны с использованием SDK v2.2.0 и проверены на плате EVU-BA v2.5.

Для ознакомления с общей информацией о МК см. документы «BE-U1000. Краткое описание» и «BE-U1000. Руководство пользователя». Для ознакомления с характеристиками МК см. документы «BE-U1000. Техническая спецификация» и «BE-U1000. Reference Manual».

Условные обозначения, термины и сокращения

В документе используются сокращения и термины, указанные в таблице ниже. Табл. 2-1 – Сокращения и термины

ОбозначениеРасшифровка
МКМикроконтроллер BE-U1000.
CLICАппаратный блок, предназначенный для обработки прерываний на уровне отдельного процессорного ядра.
CRUControl Registers Unit
Блок микроконтроллера, отвечающий за общие настройки: тактирование, сброс, выбор режимов работы ядер Core 1 и Core 2, GPIO и т.д.
GPIOGeneral Purpose Input-Output
Программно управляемый вывод общего назначения (ввода/вывода)

Общая информация

Микроконтроллер BE-U1000 содержит три порта GPIO, каждый из которых поддерживает следующие функции:

  • 16 независимых линий;
  • логика подавления дребезга контактов при использовании прерываний;
  • объединенный сигнал прерывания.

Перед конфигурированием GPIO необходимо настроить некоторые параметры CRU. Блок CRU используется для:

  • генерации тактового сигнала (pclk) и сигнала для функции подавления дребезга (dbnclk);
подсказка

Для получения подробной информации о настройке системы тактирования микроконтроллера BE-U1000, обратитесь к документу BE-U1000. Рекомендации по использованию. Настройка тактовой частоты.

  • управления сбросом порта GPIO;
  • настройка режима работы выводов (настройка альтернативной функции, включение входного буфера, управление резисторами подтяжки вывода к питанию/к земле, управление максимальным током вывода (drive strength).

На рисунке 3-1 представлена упрощённая функциональная схема, которая иллюстрирует основные функции GPIO контроллера и блока CRU. Иллюстрация

Рисунок 3-1—Упрощенная функциональная схема GPIO и CRU

подсказка

На рисунке выше x обозначает соответствующий порт ввода/вывода, где x = A для GPIO_0, x = B для GPIO_1 и x = C для GPIO_2. Символом звездочки (*) обозначается 0 для GPIO_0, 1 для GPIO_1 и 2 для GPIO_2.

Конфигурация CRU

Для корректной работы GPIO, требуется настройка CRU. Конфигурация CRU, используемая при различных сценариях использования GPIO, приведена в данном разделе.

Настройка альтернативной функции вывода

Выводы портов микроконтроллера BE-U1000 подключаются к встроенным периферийным устройствам через мультиплексоры, которые позволяют выбрать одну из шести функций для каждого вывода. Функции выводов настраиваются через следующие биты регистров IOAFCRx:

  • Для PA[0]-PA[7] – через биты PA0_AF-PA7_AF регистра IOAFCR0.
  • Для PA[8]-PA[15] – через биты PA8_AF-PA15_AF регистра IOAFCR1.
  • Для PB[0]-PB[7] – через биты PB0_AF-PB7_AF регистра IOAFCR2.
  • Для PB[8]-PB[15] – через биты PB8_AF-PB15_AF регистра IOAFCR3.
  • Для PC[0]-PC[7] – через биты PC0_AF-PC7_AF регистра IOAFCR4.
  • Для PC[8]-PC[15] – через биты PC8_AF-PC15_AF регистра IOAFCR5.
подсказка

Для получения подробной информации о функциях выводов микроконтроллера BE-U1000, обратитесь к разделу Alternate Functions документа BE-U1000. Reference Manual.

Для настройки режима работы вывода – линии порта GPIO, необходимо установить соответствующие биты регистра IOAFCRx в значение 0x0 (Функция 0). Ниже приведён пример настройки вывода PA[6] на функцию 0.

*(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) =
(*(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) & ~(7UL << 24));

Включение входного буфера

При работе вывода в режиме входа порта GPIO, необходимо включить соответствующий входной буфер. Без включённого буфера состояние вывода всегда будет читаться как 0. Входной буфер вывода управляется через следующие биты регистров IOAFCRx:

  • Для PA[0]-PA[7] – через биты PA0_IE-PA7_IE регистра IOAFCR0.
  • Для PA[8]-PA[15] – через биты PA8_IE-PA15_IE регистра IOAFCR1.
  • Для PB[0]-PB[7] – через биты PB0_IE-PB7_IE регистра IOAFCR2.
  • Для PB[8]-PB[15] – через биты PB8_IE-PB15_IE регистра IOAFCR3.
  • Для PC[0]-PC[7] – через биты PC0_IE-PC7_IE регистра IOAFCR4.
  • Для PС[8]-PС[15] – через биты PC8_IE-PC15_IE регистра IOAFCR5.

Для включения входного буфера, необходимо установить соответствующие биты регистра IOAFCRx в значение 0x1. Установка битов в значение 0x0 приведёт к отключению входного буфера. Ниже приведён пример включения входного буфера для PA[2].

*(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) |= (1UL << 11);

Управление резисторами подтяжки вывода

При работе вывода в режиме входа порта GPIO, для устранения состояния «плавающего» (неопределённого) уровня, необходимо включить внутреннюю подтяжку — к питанию (Pull-Up) или к земле (Pull-Down). Подтяжка обеспечивает фиксированный логический уровень на выводе, когда внешний сигнал отсутствует. Управление подтяжкой осуществляется через следующие регистры:

  • IOPUCRx – управление подтяжкой к питанию (Pull-Up).
  • IOPDCRx – управление подтяжкой к земле (Pull-Down).

На каждый вывод выделен один бит в каждом из регистров:

  • Для PA[0]-PA[15] – биты PA_PU_EN[0]-PA_PU_EN[15] (регистр IOPUCR0) и PA_PD_EN[0]-PA_PD_EN[15] (регистр IOPDCR0).
  • Для PB[0]-PB[15] – биты PB_PU_EN[16]-PB_PU_EN[31] (регистр IOPUCR0) и PB_PD_EN[16]-PB_PD_EN[31] (регистр IOPDCR0).
  • Для PC[0]-PC[15] – биты PC_PU_EN[0]-PC_PU_EN[15] (регистр IOPUCR1) и PC_PD_EN[0]-PC_PD_EN[15] (регистр IOPDCR1).

Комбинация битов задаёт режим подтяжки соответствующего вывода, где x – порт (PA, PB, PC), а n – номер бита:

Бит x_PU_EN[n]Бит x_PD_EN[n]Режим
00Подтяжка отключена (плавающий вход).
10Подтяжка к питанию (Pull-Up)
01Подтяжка к земле (Pull-Down)
11Подтяжка и к питанию, и к земле

Ниже приведён пример настройки подтяжки к питанию для PC[1].

*(volatile uint32_t *)(CRU_BASE_ADR + 0x30) |= (1UL << 1);
*(volatile uint32_t *)(CRU_BASE_ADR + 0x38) &= ~ (1UL << 1);

Управление максимальным током вывода (drive strength)

При работе вывода в режиме выхода порта GPIO, для управления максимальным выходным током необходимо использовать настройку максимальной силы тока вывода. Эта настройка определяет, насколько быстро вывод может переключаться между состояниями LOW и HIGH (скорость нарастания/спада уровня сигнала).

Более высокое значение максимальной силы тока обеспечивает более крутые фронты сигнала, однако увеличивает энергопотребление и электромагнитную эмиссию в окружающее пространство.

Управление максимальной силой тока осуществляется через регистры IODSCRx. На каждый вывод выделено 2 бита, что позволяет задать один из четырёх уровней:

  • 0 — до 4,5 мА; минимальная скорость, низкое энергопотребление, минимальная электромагнитная эмиссия.
  • 1 — до 9 мА; средняя скорость.
  • 2 — 13,5 мА; высокая скорость.
  • 3 — 18 мА; максимальная скорость, максимальная крутизна фронтов, максимальное энергопотребление и эмиссия в окружающее пространство.

Для настройки максимальной силы тока выводов GPIO используются следующие биты регистров IODSCRx:

  • Для PA[0]-PA[15] – биты PADS0-PADS15 регистра IODSCR0.
  • Для PB[0]-PB[15] – биты PBDS0-PBDS15 регистра IODSCR1.
  • Для PC[0]-PC[15] – биты PCDS0-PCDS15 регистра IODSCR2.

Ниже приведён пример настройки Drive strength в уровень 3 (18 мА) для вывода PA[1].

*(volatile uint32_t *)(CRU_BASE_ADR + 0x54) =
(*(volatile uint32_t *)(CRU_BASE_ADR + 0x54) & ~(3UL << 2)) | (3UL << 2);

Управление сбросом

подсказка

Для получения подробной информации о настройке системы тактирования микроконтроллера BE-U1000, обратитесь к документу BE-U1000. Рекомендации по использованию. Настройка тактовой частоты.

Для портов GPIO предусмотрен сигнал программного сброса. Сброс осуществляется при помощи регистров PCLK0EN, PCLK1EN, и PCLK2EN. Установка сброса – 0x0, снятие сброса – 0x1. Для сброса GPIO контроллеров используются следующие битовые поля регистров PCLKxEN:

  • GPIO0 – PCLK0EN.GPIO0RSTN.
  • GPIO1 – PCLK1EN.GPIO1RSTN.
  • GPIO2 – PCLK2EN.GPIO2RSTN.

Ниже приведён пример снятия сброса GPIO0.

*(volatile uint32_t *)(CRU_BASE_ADR + 0x0C) |= (1UL << 27);

Конфигурация контроллера GPIO

В данном разделе описывается конфигурация порта GPIO: установка режима работы вывода (вход или выход), настройка прерываний.

Настройка режима работы вывода в качестве линии порта GPIO

Выводы порта GPIO могут работать в следующих режимах:

  • Вход – считывание логического сигнала (0 или 1), который подаётся на вывод с внешнего устройства (например, с кнопки или датчика).
  • Выход – генерация логического сигнала, который используется для управления внешним устройством (например, светодиодом или реле).
подсказка

При работе вывода GPIO в режиме входа необходимо включить соответствующий ему входной буфер (см. раздел Включение входного буфера для получения подробной информации).

Управление режимом работы вывода GPIO осуществляется через биты регистра GPIO_DDR. Работа в режиме входа – 0x0, работа в режиме выхода – 0x1. Ниже приведён пример настройки вывода GPIO0[1] на работу в режиме выхода.

*(volatile uint32_t *)(GPIO0_BASE_ADR + 0x04) |= (1UL << 1);

Вывод в режиме входа (GPIO_DDR = 0x0).

Если вывод настроен как вход, его текущее состояние можно прочитать с помощью регистра GPIO_EXT. Ниже приведён пример считывания состояния вывода GPIO0[2].

*(volatile uint32_t *)(GPIO0_BASE_ADR + 0x50) & (1UL << 2)) ? 1 : 0

Вывод в режиме выхода (GPIO_DDR = 0x1)

Если вывод настроен как выход, управление логическим уровнем вывода осуществляется с помощью регистра GPIO_DR. Ниже приведён пример установки логического уровня 1 на выводе GPIO0[1].

*(volatile uint32_t *)(GPIO0_BASE_ADR + 0x00) |= (1UL << 1);

Настройка прерываний

подсказка

Для использования вывода GPIO в качестве источника прерываний, соответствующий вывод должен быть в режиме входа (см. раздел Настройка вывода в качестве входа/выхода), а входной буфер вывода должен быть включён (см. раздел Включение входного буфера).

Разрешение прерывания

Каждый вывод GPIO, работающий в режиме входа, может быть сконфигурирован как источник внешних прерываний. Разрешение прерывания контролируется через биты регистра GPIO_INTEN. При установке соответствующего бита GPIO_INTEN в 0x1, изменения сигнала на входе будут вызывать аппаратное прерывание (в соответствии с настроенными типом и полярностью). Ниже приведён пример разрешения прерывания для вывода GPIO2[1].

*(volatile uint32_t *)(GPIO2_BASE_ADR + 0x30) |= (1UL << 1);

Сброс прерывания

После того как прерывание было обработано, необходимо сбросить его флаг. Это позволяет контроллеру правильно распознавать следующие изменения сигнала на входе. Сброс прерывания вывода GPIO осуществляется записью 0x1 в соответствующий бит регистра GPIO_EOI. Ниже приведён пример сброса прерывания для вывода GPIO2[1].

*(volatile uint32_t *)(GPIO2_BASE_ADR + 0x4C) |= (1UL << 1);

Выбор типа прерывания

Контроллер способен детектировать два типа прерываний:

  • по уровню – прерывание генерируется по заданному логическому уровню на входе. Прерывание будет постоянно активно, пока на входе сохраняется заданный уровень сигнала;
  • по фронту – прерывание генерируется однократно в момент изменения сигнала на входе (переход из одного состояния в другое).
подсказка

Выбор типа прерывания для вывода не учитывается при включении генерации прерывания по обоим фронтам сигнала (соответствующий бит регистра GPIO_BOTHEDGE установлен в 0x1). Подробнее см. в Управление фронтом срабатывания прерывания.

Выбор типа детектируемого прерывания осуществляется через биты регистра GPIO_INTLVL. Установка в 0x0 – прерывание по уровню; 0x1 – прерывание по фронту. Ниже приведён пример установки типа прерывания по фронту для входа GPIO2[1].

*(volatile uint32_t *)(GPIO2_BASE_ADR + 0x38) |= (1UL << 1);

Управление полярностью прерывания

подсказка

Выбор полярности прерывания для вывода не учитывается при включении генерации прерывания по обоим фронтам сигнала (соответствующий бит регистра GPIO_BOTHEDGE установлен в 0x1). Подробнее см. в Управление фронтом срабатывания прерывания.

Настройка полярности прерывания определяет, при каком состоянии или переходе сигнала на входе будет генерироваться прерывание. Полярность прерывания контролируется через биты регистра GPIO_INTPOLARITY. При установке в 0x0, прерывание генерируется при низком уровне сигнала (для прерываний по уровню) или при обнаружении заднего фронта сигнала (для прерываний по фронту). При установке в 0x1, прерывание генерируется при высоком уровне сигнала (для прерываний по уровню) или при обнаружении переднего фронта сигнала (для прерываний по фронту). Ниже приведён пример установки полярности прерывания в 0 для генерации прерывания по низкому уровню сигнала или заднему фронту сигнала для входа GPIO2[1].

*(volatile uint32_t *)(GPIO2_BASE_ADR + 0x3C) &= ~ (1UL << 1);

Управление фронтом срабатывания прерывания

Для прерываний по фронту (соответствующий бит регистра GPIO_INTLVL установлен в 0x1) имеется дополнительная настройка, определяющая, по каким переходам сигнала будет генерироваться прерывание:

  • Один фронт  – прерывание генерируется только при обнаружении одного выбранного фронта сигнала (переднего или заднего). Выбор конкретного фронта определяется настройкой полярности (через биты регистра GPIO_INTPOLARITY).
  • Оба фронта – прерывание генерируется при любом изменении сигнала на входе вывода (как при переходе 0 → 1, так и при переходе 1 → 0). При этом настройки типа прерывания (через биты регистра GPIO_INTLVL) и полярности прерывания (через биты регистра GPIO_INTPOLARITY) не учитываются.

Данная настройка управляется через биты регистра GPIO_BOTHEDGE. При установке соответствующего бита в 0x0, прерывание будет генерироваться при обнаружении одного выбранного фронта сигнала, а при установке в 0x1 – прерывание генерируется по обоим фронтам.

Ниже приведён пример выбора фронта срабатывания прерывания для генерации прерывания при обнаружении одного выбранного фронта сигнала на входе GPIO2[1].

*(volatile uint32_t *)(GPIO2_BASE_ADR + 0x68) &= ~ (1UL << 1);

Маскирование прерывания

Механизм маскирования позволяет временно блокировать или разблокировать обработку прерываний от выхода без сброса настроек конфигурации. Маскирование прерывания контролируется через биты регистра GPIO_INTMSK, где установка бита в 0x1 замаскирует прерывание, а установка бита в 0x0 размаскирует прерывание. Ниже приведён пример размаскирования прерывания для входа GPIO2[1].

*(volatile uint32_t *)(GPIO2_BASE_ADR + 0x34) &= ~ (1UL << 1);

Включение функции устранения дребезга

При обнаружении изменений сигнала на входе GPIO, особенно при работе с механическими контактами (кнопки, переключатели, реле), возникает явление дребезга контакта — множественные кратковременные изменения сигнала в момент установления электрического контакта. Для входов, сконфигурированных как источники прерываний, дребезг может привести к ложным многократным срабатываниям прерываний. Механизм устранения дребезга позволяет подавлять ложные сигналы, длительность которых меньше одного периода сигнала устранения дребезга (dbnclk). Включение функции устранения дребезга осуществляется установкой соответствующих битов регистра GPIO_DEBOUNCE в 0x1. Ниже приведён пример включения функции устранения дребезга для входа GPIO2[1].

*(volatile uint32_t *)(GPIO2_BASE_ADR + 0x48) |= (1UL << 1);

Примеры использования порта GPIO

Обработка GPIO прерываний

подсказка

Данный пример не рассматривает настройку тактовых частот pclk и cclk. Для получения подробной информации о настройке системы тактирования микроконтроллера BE-U1000, обратитесь к документу BE-U1000. Рекомендации по использованию. Настройка тактовой частоты.

В основе данного подхода лежит использование встроенного в порт GPIO механизма генерации прерываний. При обнаружении заданного изменения сигнала на выводе, контроллер GPIO самостоятельно формирует запрос, который направляется в CLIC (Core Local Interrupt Controller). Параметры срабатывания (тип, полярность, антидребезг) настраиваются через регистры GPIO. Ключевое требование при работе с этим механизмом – обязательная программная очистка флага прерывания. Очистка флага выполняется вызовом специальZXной функции, что добавляет дополнительные такты выполнения и несколько снижает быстродействие системы (снижение быстродействия можно оценить относительно обработки прерываний EXTI, где очистка флага не требуется). Данный подход предпочтителен в случаях, когда скорость реакции не критична, а также при необходимости использовать сразу несколько выводов одного порта в качестве источников прерываний. Для оценки быстродействия данного, подхода были проведены измерения временных параметров реакции системы на нажатие кнопки. С помощью осциллографа измерялся интервал времени между изменением сигнала на выводе, к которому подключена кнопка, и изменением напряжения на выводе светодиода.

На графиках представлены:

  • канал 1 (голубой) — сигнал на выводе, к которому подключена кнопка, отражающий момент нажатия (спад сигнала).
  • канал 2 (розовый) — сигнал на выводе светодиода, отражающий момент переключения в обработчике прерывания.

На рисунках ниже представлены осциллограммы сигналов на кнопке и светодиоде для различных значений cclk и pclk. Иллюстрация

Рисунок 6-1 — Осциллограмма реакции системы на нажатие кнопки (cclk=200 МГц, pclk=100 МГц) Иллюстрация

Рисунок 6-2 — Осциллограмма реакции системы на нажатие кнопки (cclk=100 МГц, pclk=100 МГц) Иллюстрация

Рисунок 6-3 — Осциллограмма реакции системы на нажатие кнопки (cclk=25 МГц, pclk=25 Мгц)

В таблице ниже приведены значения временных интервалов от момента нажатия кнопки (спад сигнала) до момента включения светодиода для различных частот cclk и pclk.

Частота cclk, МГцЧастота pclk, МГцВременной интервал
2001001.52 мкс
1001001.89 мкс
25255.86 мкс

Ниже приведён листинг программы для обработки прерываний GPIO.

#include <stdint.h>
//Определения базовых адресов блоков
#define CRU_BASE_ADR  0x14000000UL
#define GPIO0_BASE_ADR  0x11009000UL
#define GPIO2_BASE_ADR  0x14008000UL
#define CLIC_BASE_ADR  0x0D000000UL

//запись в CSR регистры
static inline void csr_write(uint32_t csr, uint32_t value)
{
    __asm__ volatile ("csrw %0, %1" : : "i"(csr), "r"(value));
}
//чтение из CSR регистров
static inline uint32_t csr_read(uint32_t csr)
{
    uint32_t value;
    __asm__ volatile ("csrr %0, %1" : "=r"(value) : "i"(csr));
    return value;
}

//  Инициализация светодиода на PA[1]
static void led_init(void)
{
    //включение тактирования GPIO0 порта, PCLK0EN[27]=1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x0C) |= (1UL << 27);
    //установка АФ0, IOAFCR0[6:4] = 0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) =
    (*(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) & ~(7UL << 4));
    //отключение подтяжки к питанию, IOPUCR0[1]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x2C) &= ~ (1UL << 1);
    //отключение подтяжки к земле, IOPDCR0[1]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x34) &= ~ (1UL << 1);
    //отключение входного буфера, IOAFCR0[7]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) &= ~ (1UL << 7);
    //установка максимального DS, IODSCRO[3:2]=0x3
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x54) =
    (*(volatile uint32_t *)(CRU_BASE_ADR + 0x54) & ~(3UL << 2)) | (3UL << 2);
    //включение выхода, GPIO_DDR[1]=1
    *(volatile uint32_t *)(GPIO0_BASE_ADR + 0x04) |= (1UL << 1);
}

//  Инициализация кнопки на PC[1]
static void btn_init(void)
{
    //включение тактирования GPIO2 порта, PCLK2EN[23]=1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x14) |= (1UL << 23);
    //установка АФ0, IOAFCR4[6:4]=0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x4C) =
    (*(volatile uint32_t *)(CRU_BASE_ADR + 0x4C) & ~(7UL << 4));
    //включение подтяжки к питанию, IOPUCR1[1] = 1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x30) |= (1UL << 1);
    //отключение подтяжки к земле, IOPDCR1[1]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x38) &= ~ (1UL << 1);
    //включение входного буфера, IOAFCR4[7]=1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x4C) |= (1UL << 7);
    //установка минамального DS, IODSCR2[3:2]=0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x5C) &= ~ (3UL << 2);
    //включение входа, GPIO_DDR[1]=0
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x04) &= ~ (1UL << 1);
    //включение прерывания по фронту, GPIO_INTLVL[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x38) |= (1UL << 1);
    //отключение прерывания по обоим фронтам, GPIO_BOTHEDGE[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x68) &= ~ (1UL << 1);
    //установка полярности прерываний (задний фронт), GPIO_INTPOLARITY[1]=0
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x3C) &= ~ (1UL << 1);
    //включение устранения дребезга, GPIO_DEBOUNCE[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x48) |= (1UL << 1);
    //разрешение прерывания, GPIO_INTEN[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x30) |= (1UL << 1);
    //демаскирование прерывания, GPIO_INTMSK[1]=0
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x34) &= ~ (1UL << 1);
    //Настройка CLIC
    //настройка аттрибутов (CLICINTATTR)
    uint8_t attr_value = 0xC0 | 0x00 | 0x00 | 0x01;  // 0xC1
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x1002 + (4 * 61)) =
    (*(volatile uint8_t *)(CLIC_BASE_ADR + 0x1002 + (4 * 61)) & ~0xCF) | attr_value;
    //настройка управления (регистр CLICINTCTL)
    uint8_t intctl = 0x80 | 0x10 | 0x08;  // 0x98
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x1003 + (4 * 61)) = intctl;
    //включение прерывания (CLICINTIE)
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x1001 + (4 * 61)) |= 0x01;
}

int main(void)
{
    //настройка CLIC
    //устанавливаем базовый приоритет (NMBITS биты в регистре CLICCFG)
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x00) =
    (*(volatile uint8_t *)(CLIC_BASE_ADR + 0x00) & ~(0x3UL << 5)) | (0x1UL << 5);
    //устанавливаем уровень (NLBITS биты в регистре CLICCFG)
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x00) =
    (*(volatile uint8_t *)(CLIC_BASE_ADR + 0x00) & ~(0xFUL << 1)) | (0x1UL << 1);

    led_init();
    btn_init();

    //устанаваливаем порог приоритета (запись 0 в CSR регистр CSR_MINTTHRESH)
    csr_write(0x347, 0);
    //глобальное разрешение прерываний на ядре (бит MIE в регистре MSTATUS)
    uint32_t mstatus = csr_read(0x300);
    mstatus |= (1UL << 3);
    csr_write(0x300, mstatus);

    for (;;) {
    }
    return 0;
}

//  Обработчик прерывания
__attribute__ ((interrupt)) void GPIO2_IRQHandler(void){
    //нажатие кнопки включит светодиод, GPIO_DR[1]=1
    *(volatile uint32_t *)(GPIO0_BASE_ADR + 0x00) |= (1UL << 1);
    //очистка флага прерывания для кнопки, GPIO_EOI[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x4C) |= (1UL << 1);
}

Обработка прерываний EXTI

подсказка

Данный пример не рассматривает настройку тактовых частот pclk и cclk. Для получения подробной информации о настройке системы тактирования микроконтроллера BE-U1000, обратитесь к документу BE-U1000. Рекомендации по использованию. Настройка тактовой частоты.

В основе данного подхода лежит использование механизма внешних прерываний EXTI (External Interrupt). Параметры срабатывания (тип, полярность, антидребезг) настраиваются через регистры GPIO.
Ключевое преимущество при работе с этим механизмом – аппаратная очистка флага прерывания. В отличие от GPIO-подхода (см. пример Обработка GPIO прерываний), флаг сбрасывается автоматически при входе в обработчик, что исключает необходимость программной очистки. Это сокращает время выполнения обработчика, повышает быстродействие системы и снижает вероятность ошибок.

Данный подход предпочтителен в случаях, когда требуется высокая скорость реакции на внешние события, минимальное время обработки прерывания или поддержка пробуждения микроконтроллера из режимов сна.

Для оценки быстродействия данного подхода, были проведены измерения временных параметров реакции системы на нажатие кнопки. С помощью осциллографа измерялся интервал времени между изменением сигнала на выводе, к которому подключена кнопка, и изменением напряжения на выводе светодиода. На графиках представлены:

  • канал 1 (голубой) — сигнал на выводе, к которому подключена кнопка, отражающий момент нажатия (спад сигнала).
  • канал 2 (розовый) — сигнал на выводе светодиода, отражающий момент переключения в обработчике прерывания.

На рисунках ниже представлены осциллограммы сигналов на кнопке и светодиоде для различных значений cclk и pclk. Иллюстрация

Рисунок 6-4 — Осциллограмма реакции системы на нажатие кнопки (cclk=200 МГц, pclk=100 МГц) Иллюстрация

Рисунок 6-5 — Осциллограмма реакции системы на нажатие кнопки (cclk=100 МГц, pclk=100 МГц) Иллюстрация

Рисунок 6-6 — Осциллограмма реакции системы на нажатие кнопки (cclk=25 МГц, pclk=25 Мгц)

В таблице ниже приведены значения временных интервалов от момента нажатия кнопки (спад сигнала) до момента переключения светодиода для различных частот cclk и pclk.

Частота cclk, МГцЧастота pclk, МГцВременной интервал
200100560 нс
100100880 нс
25253.52 мкс

Для сравнения быстродействия данного подхода и GPIO-подхода (см. пример Обработка GPIO прерываний), в таблице ниже приведены значения временных интервалов от момента нажатия кнопки (спад сигнала) до момента переключения светодиода для различных частот cclk и pclk как для обработки прерываний EXTI, так и для обработки прерываний GPIO.

Частота cclk, МГцЧастота pclk, МГцВременной интервал EXTIВременной интервал GPIO
200100560 нс1.52 мкс
100100880 нс1.89 мкс
25253.52 мкс5.86 мкс

Ниже приведён листинг программы для обработки прерываний EXTI.

#include <stdint.h>
//Определения базовых адресов блоков
#define CRU_BASE_ADR  0x14000000UL
#define GPIO0_BASE_ADR  0x11009000UL
#define GPIO2_BASE_ADR  0x14008000UL
#define CLIC_BASE_ADR  0x0D000000UL

//запись в CSR регистры
static inline void csr_write(uint32_t csr, uint32_t value)
{
    __asm__ volatile ("csrw %0, %1" : : "i"(csr), "r"(value));
}
//чтение из CSR регистров
static inline uint32_t csr_read(uint32_t csr)
{
    uint32_t value;
    __asm__ volatile ("csrr %0, %1" : "=r"(value) : "i"(csr));
    return value;
}

//  Инициализация светодиода на PA[1]
static void led_init(void)
{
    //включение тактирования GPIO0 порта, PCLK0EN[27]=1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x0C) |= (1UL << 27);
    //установка АФ0, IOAFCR0[6:4] = 0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) =
    (*(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) & ~(7UL << 4));
    //отключение подтяжки к питанию, IOPUCR0[1]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x2C) &= ~ (1UL << 1);
    //отключение подтяжки к земле, IOPDCR0[1]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x34) &= ~ (1UL << 1);
    //отключение входного буфера, IOAFCR0[7]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) &= ~ (1UL << 7);
    //установка максимального DS, IODSCRO[3:2]=0x3
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x54) =
    (*(volatile uint32_t *)(CRU_BASE_ADR + 0x54) & ~(3UL << 2)) | (3UL << 2);
    //включение выхода, GPIO_DDR[1]=1
    *(volatile uint32_t *)(GPIO0_BASE_ADR + 0x04) |= (1UL << 1);
}

//  Инициализация кнопки на PC[1]
static void btn_init(void)
{
    //включение тактирования GPIO2 порта, PCLK2EN[23]=1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x14) |= (1UL << 23);
    //установка АФ0, IOAFCR4[6:4]=0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x4C) =
    (*(volatile uint32_t *)(CRU_BASE_ADR + 0x4C) & ~(7UL << 4));
    //включение подтяжки к питанию, IOPUCR1[1] = 1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x30) |= (1UL << 1);
    //отключение подтяжки к земле, IOPDCR1[1]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x38) &= ~ (1UL << 1);
    //включение входного буфера, IOAFCR4[7]=1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x4C) |= (1UL << 7);
    //установка минимального DS, IODSCR2[3:2]=0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x5C) &= ~ (3UL << 2);
    //включение входа, GPIO_DDR[1]=0
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x04) &= ~ (1UL << 1);
    //включение прерывания по фронту, GPIO_INTLVL[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x38) |= (1UL << 1);
    //отключение прерывания по обоим фронтам, GPIO_BOTHEDGE[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x68) &= ~ (1UL << 1);
    //установка полярности прерываний (задний фронт), GPIO_INTPOLARITY[1]=0
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x3C) &= ~ (1UL << 1);
    //включение устранения дребезга, GPIO_DEBOUNCE[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x48) |= (1UL << 1);
    //разрешение прерывания, GPIO_INTEN[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x30) |= (1UL << 1);
    //демаскирование прерывания, GPIO_INTMSK[1]=0
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x34) &= ~ (1UL << 1);
    //Настройка CLIC
    //настройка аттрибутов (CLICINTATTR)
    uint8_t attr_value = 0xC0 | 0x00 | 0x00 | 0x01;  // 0xC1
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x1002 + (4 * 61)) =
    (*(volatile uint8_t *)(CLIC_BASE_ADR + 0x1002 + (4 * 61)) & ~0xCF) | attr_value;
    //настройка управления (регистр CLICINTCTL)
    uint8_t intctl = 0x80 | 0x10 | 0x08;  // 0x98
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x1003 + (4 * 61)) = intctl;
    //включение прерывания (CLICINTIE)
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x1001 + (4 * 61)) |= 0x01;
}

int main(void)
{
    //настройка CLIC
    //устанавливаем базовый приоритет (NMBITS биты в регистре CLICCFG)
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x00) =
    (*(volatile uint8_t *)(CLIC_BASE_ADR + 0x00) & ~(0x3UL << 5)) | (0x1UL << 5);
    //устанавливаем уровень (NLBITS биты в регистре CLICCFG)
    *(volatile uint8_t *)(CLIC_BASE_ADR + 0x00) =
    (*(volatile uint8_t *)(CLIC_BASE_ADR + 0x00) & ~(0xFUL << 1)) | (0x1UL << 1);

    led_init();
    btn_init();

    //устанаваливаем порог приоритета (запись 0 в CSR регистр CSR_MINTTHRESH)
    csr_write(0x347, 0);
    //глобальное разрешение прерываний на ядре (бит MIE в регистре MSTATUS)
    uint32_t mstatus = csr_read(0x300);
    mstatus |= (1UL << 3);
    csr_write(0x300, mstatus);

    for (;;) {
    }
    return 0;
}

//  Обработчик прерывания
__attribute__ ((interrupt)) void GPIO2_IRQHandler(void){
    //нажатие кнопки включит светодиод, GPIO_DR[1]=1
    *(volatile uint32_t *)(GPIO0_BASE_ADR + 0x00) |= (1UL << 1);
    //очистка флага прерывания для кнопки, GPIO_EOI[1]=1
    *(volatile uint32_t *)(GPIO2_BASE_ADR + 0x4C) |= (1UL << 1);
}

Программная эмуляция открытого коллектора

Порты GPIO выполнены по схеме, отличной от схемы транзистора с открытым коллектором. Однако ряд периферийных устройств и интерфейсов требуют наличия линий, управляемых транзистором с открытым коллектором для корректной работы в мультимастерных конфигурациях и реализации механизма арбитража.

Для обеспечения совместимости с такими устройствами, применяется программная эмуляция режима «открытого коллектора». Эмуляция реализована путём переключения вывода порта GPIO между состояниями:

  • выход с низким уровнем (0) – для принудительного притягивания линии к земле;
  • вход – для "отпускания" линии.

Такой подход позволяет использовать выводы GPIO микроконтроллера для работы с протоколами, требующими «открытого коллектора».

Ниже приведён листинг программы для программной эмуляции открытого коллектора.

#include <stdint.h>
//Определения базовых адресов блоков
#define CRU_BASE_ADR  0x14000000UL
#define GPIO0_BASE_ADR  0x11009000UL

//Эмуляция открытого коллектора на PA[6]
static void open_drain_write(uint8_t state)
{
    if (state == 0) {
        //включение выхода, GPIO_DDR[6]=1
        *(volatile uint32_t *)(GPIO0_BASE_ADR + 0x04) |= (1UL << 6);
    }
    else {
        //включение входа, GPIO_DDR[6]=0
        *(volatile uint32_t *)(GPIO0_BASE_ADR + 0x04) &= ~ (1UL << 6);
    }
}

//Инициализация PA[6] для программной эмуляции открытого коллектора
static void open_drain_init(void)
{
    //включение тактирования GPIO0 порта, PCLK0EN[27]=1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x0C) |= (1UL << 27);
    //установка АФ0, IOAFCR0[26:24] = 0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) =
    (*(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) & ~(7UL << 24));
    //отключение подтяжки к питанию, IOPUCR0[6]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x2C) &= ~ (1UL << 6);
    //отключение подтяжки к земле, IOPDCR0[6]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x34) &= ~ (1UL << 6);
    //отключение входного буфера, IOAFCR0[27]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) &= ~ (1UL << 27);
    //установка минимального DS, IODSCR0[13:12]=0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x54) &= ~ (3UL << 12);
    //включение входа, GPIO_DDR[6]=0
    *(volatile uint32_t *)(GPIO0_BASE_ADR + 0x04) &= ~ (1UL << 6);
}

//  Инициализация кнопки на PA[2]
static void btn_init(void)
{
    //установка АФ0, IOAFCR0[10:8] = 0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) =
    (*(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) & ~(7UL << 8));
    //отключение подтяжки к питанию, IOPUCR0[2]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x2C) &= ~ (1UL << 2);
    //отключение подтяжки к земле, IOPDCR0[2]=0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x34) |= (1UL << 2);
    //включение входного буфера, IOAFCR0[11]=1
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x3C) |= (1UL << 11);
    //установка минимального DS, IODSCR0[5:4]=0x0
    *(volatile uint32_t *)(CRU_BASE_ADR + 0x54) &= ~ (3UL << 4);
    //включение входа, GPIO_DDR[2]=0
    *(volatile uint32_t *)(GPIO0_BASE_ADR + 0x04) &= ~ (1UL << 2);
}

//  Чтение кнопки на PA[2]
static uint8_t btn_is_pressed(void)
{
    //чтение значения из GPIO_EXT[2]
    return (*(volatile uint32_t *)(GPIO0_BASE_ADR + 0x50) & (1UL << 2)) ? 1 : 0;
}

int main(void)
{
    open_drain_init();
    btn_init();
    open_drain_write(0);

    for (;;) {
        uint8_t btn_state = btn_is_pressed();

        if (btn_state == 1) {
            // Кнопка нажата → светодиод гаснет
            open_drain_write(1);
        } else {
            // Кнопка отпущена → светодиод горит
            open_drain_write(0);
        }
    }
    return 0;
}

Скачать PDF ⬇️ AppNote_gpio_v1.pdf

История изменений

ВерсияДатаОписание
1.030.06.2026Начальная версия
1.101.07.2026Корректировки примеров кода