Драйверы GPIO, UART и ADC в экосистеме Zephyr
Материал носит ознакомительный характер. Работа с электроникой и микроконтроллерами требует соблюдения техники безопасности; автор не несет ответственности за возможные повреждения оборудования или иной ущерб, возникший в ходе обучения.
Мы переходим к самому интересному — оживлению железа. В Arduino вы привыкли к digitalWrite(): одной команде, которая прячет внутри всю магию. В Zephyr RTOS подход профессиональный: вы получаете полный контроль над ресурсами, но взамен соблюдаете строгий регламент работы с периферией.
От «магии» к системному подходу
В Zephyr нельзя просто отправить сигнал на «пин 13». Операционная система управляет ресурсами через Дескриптор устройства (device handle).
Дескриптор устройства — это программный «ключ» или ссылка на конкретный модуль (GPIO, UART, ADC), который ОС подготовила к работе. Чтобы взаимодействовать с железом, вы сначала запрашиваете у системы этот дескриптор.
Алгоритм работы с любым драйвером всегда состоит из трех шагов:
- Kconfig (
prj.conf): включаете поддержку нужного типа драйвера. - Device Tree (
.overlay): привязываете функции к конкретным ножкам чипа nRF52840. - Код C: получаете дескриптор и вызываете функции API драйвера.
GPIO: Управление цифровыми пинами
Для работы с GPIO в Zephyr используется структура gpio_dt_spec. Она упаковывает в себя всё: порт, номер пина и настройки из Device Tree.
Как делать не стоит
Избегайте «магических чисел» (hardcode) в коде. Это делает программу хрупкой:
// Плохо: при смене платы придется переписывать код везде
gpio_pin_configure(dev, 13, GPIO_OUTPUT_ACTIVE);
Правильный подход в Zephyr
Сначала активируйте GPIO в prj.conf:
CONFIG_GPIO=y
Затем используйте безопасный метод получения данных из Device Tree:
#include <zephyr/drivers/gpio.h>
// Извлекаем настройки светодиода по его псевдониму (alias) из Device Tree
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
void main(void) {
// 1. Проверяем готовность драйвера — это страховка от сбоев
if (!device_is_ready(led.port)) {
return;
}
// 2. Конфигурируем пин на выход
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
while (1) {
gpio_pin_toggle_dt(&led); // Инвертируем состояние
k_msleep(500);
}
}
Совет: Всегда используйте
device_is_ready(). Если вы забыли включить драйвер в Kconfig или ошиблись в Device Tree, эта проверка предотвратит «зависание» или непредсказуемое поведение прошивки. 🛡️
UART: Обмен данными
В nRF52840 модуль UART незаменим для логов и связи с внешними устройствами.
Для активации в prj.conf добавьте:
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y (если нужны прерывания)
Функция printk() в Zephyr заменяет привычный Serial.print(). Но для прямой передачи данных используйте API драйвера UART:
#include <zephyr/drivers/uart.h>
// Получаем дескриптор конкретного модуля UART
const struct device *uart_dev = DEVICE_DT_GET(DT_NODELABEL(uart0));
void send_uart_data(const char *msg) {
for (int i = 0; i < strlen(msg); i++) {
// Побайтовая передача через драйвер
uart_poll_out(uart_dev, msg[i]);
}
}
ADC: Чтение аналоговых сигналов
Работа с ADC в nRF52840 требует настройки точности: разрешения и времени накопления заряда.
В prj.conf добавьте:
CONFIG_ADC=y
Чтение напряжения
У nRF52840 12-битное разрешение. Чтобы перевести «сырые» данные (RAW) в милливольты, используйте формулу:
Обычно мВ (внутренняя опора с делителем 1/6).
#include <zephyr/drivers/adc.h>
// Настройка канала
static const struct adc_channel_cfg channel_cfg = {
.gain = ADC_GAIN_1_6,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = 0,
.input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 // Пин AIN0
};
void read_battery() {
int16_t buf;
struct adc_sequence sequence = {
.channels = BIT(0),
.buffer = &buf,
.buffer_size = sizeof(buf),
.resolution = 12,
};
const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc));
adc_read(adc_dev, &sequence);
// В buf теперь значение от 0 до 4095
}
Чек-лист: почему не работает драйвер?
Если периферия «молчит», проверьте цепочку по списку:
| Этап | Что проверить | Типичная ошибка |
|---|---|---|
| Kconfig | Файл prj.conf | Забыли CONFIG_XXX=y |
| Device Tree | Файл .overlay | Ошибка в номере пина или пропущен status = "okay"; |
| Инициализация | Код C | Не вызван или не прошел device_is_ready() |
| API | Код C | Вызов функций, не поддерживаемых этим драйвером |
Сейчас наш код выполняется линейно, как в Arduino. Но nRF52840 умеет гораздо больше. В следующей теме мы научимся распределять задачи между независимыми потоками и использовать системные таймеры, чтобы создать настоящую многозадачную систему. 🚀