Работа с 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 | Аппаратный блок, предназначенный для обработки прерываний на уровне отдельного процессорного ядра. |
| CRU | Control Registers Unit Блок микроконтроллера, отвечающий за общие настройки: тактирование, сброс, выбор режимов работы ядер Core 1 и Core 2, GPIO и т.д. |
| GPIO | General 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] | Режим |
|---|---|---|
| 0 | 0 | Подтяжка отключена (плавающий вход). |
| 1 | 0 | Подтяжка к питанию (Pull-Up) |
| 0 | 1 | Подтяжка к земле (Pull-Down) |
| 1 | 1 | Подтяжка и к питанию, и к земле |
Ниже приведён пример настройки подтяжки к питанию для 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, МГц | Временной интервал |
|---|---|---|
| 200 | 100 | 1.52 мкс |
| 100 | 100 | 1.89 мкс |
| 25 | 25 | 5.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, МГц | Временной интервал |
|---|---|---|
| 200 | 100 | 560 нс |
| 100 | 100 | 880 нс |
| 25 | 25 | 3.52 мкс |
Для сравнения быстродействия данного подхода и GPIO-подхода (см. пример Обработка GPIO прерываний), в таблице ниже приведены значения временных интервалов от момента нажатия кнопки (спад сигнала) до момента переключения светодиода для различных частот cclk и pclk как для обработки прерываний EXTI, так и для обработки прерываний GPIO.
Частота cclk, МГц | Частота pclk, МГц | Временной интервал EXTI | Временной интервал GPIO |
|---|---|---|---|
| 200 | 100 | 560 нс | 1.52 мкс |
| 100 | 100 | 880 нс | 1.89 мкс |
| 25 | 25 | 3.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.0 | 30.06.2026 | Начальная версия |
| 1.1 | 01.07.2026 | Корректировки примеров кода |