В этом уроке напишем первую программу, научимся считывать значение цифровых входов и устанавливать состояние выходов. Реализуем управление такими простыми элементами, как кнопка и светодиод на платформе Maixduino.
Что нужно знать
Существует 3 фреймворка для разработки приложений для процессора К210:
- Standalone SDK для Kendryte K210;
- Kendryte FreeRTOS SDK — Этот SDK предназначен для Kendryte K210, который содержит поддержку FreeRTOS;
- Arduino (на основе Standalone SDK).
Для работы с периферийными устройствами на Maixduino существует 3 основных компонента: GPIO, GPIOHS и FPIOA.
- GPIO (General Purpose Input Output / Интерфейс ввода/вывода общего назначения) — чип имеет 8 GPIO общего назначения.
- GPIOHS (General Purpose Input Output High Speed / Высокоскоростной интерфейс ввода/вывода общего назначения) – чип имеет 32 высокоскоростных GPIO. Похоже на обычный GPIO, но быстрее.
- FPIOA (Field Programmable I/O Array / Программируемый массив ввода/вывода) позволяет пользователю соотносить 256 внутренних функций с 48 физическими входами / выходами на чипе.
Из этого следует, что Maixduino более гибок, чем простой Arduino на базе микроконтроллеров AVR. Мы можем сопоставить любое устройство с любым физическим контактом (контактами), например, кнопки, светодиоды, устройства I2C и SPI, и т. д.
Создание нового проекта
- Открываем PlatformIO: Home и выбираем New Project, чтобы создать новый проект;
- Задаём название проекта в поле Name;
- В Boards ищем плату Sipeed MAIXDUINO;
- Выбираем Фреймворк Kendryte FreeRTOS SDK;
- В Location можно указать путь, где будет храниться проект, но можно оставить по умолчанию.
При создании первого проекта, все необходимые файлы и библиотеки будут загружены и установлены автоматически, и это может занять больше времени, чем обычно.
Настройка проекта
В папке src необходимо создать два файла: main.cpp и project_cfg.h. В первом файле мы напишем программу, а во втором мы определим макросы и настроим функции выводов.
В корневом каталоге есть файл platformio.ini — файл конфигурации проекта PlatformIO. По умолчанию PlatformIO автоматически определяет порт загрузки. Но Вы можете настроить собственный порт, используя параметр upload_port
. Список портов вы можете найти в Диспетчер Устройств или во вкладке Devices в PIO Home.
В platformio.ini вы также можете изменить скорость загрузки, используя параметр upload_speed
, порт монитора, параметр monitor_port
, и скорость монитора порта, параметр monitor_speed
. Порт загрузки и порт монитора должны совпадать.
Пример файла platformio.ini
; PlatformIO Project Configuration File ; ; Build options: build flags, source filter ; Upload options: custom upload port, speed and extra flags ; Library options: dependencies, extra library storages ; Advanced options: extra scripting ; ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [env:sipeed-maixduino] platform = kendryte210 board = sipeed-maixduino framework = kendryte-freertos-sdk upload_port = COM3 upload_speed = 1500000 monitor_port = COM3 monitor_speed = 115200
Как работать с GPIO
Если мы хотим записывать или читать данные из GPIO, мы должны сначала его настроить. Как упоминалось выше, K210 имеет 48 контактов и 256 функций для них. Но поскольку на плате формфактора Arduino количество контактов ограничено, используются не все 48 контактов.
Сначала, чтобы определить, какие контакты и где они назначены, мы должны открыть схему платы — Maixduino-4.30(schematic).pdf. Тут нужно найти разъемы (Connector).
Все контакты разъема P3 (HEADER-1X6) подключены к ESP-32, поэтому его нельзя использовать с K210. Но разъемы P2 и P5 идут на K210, и их можно использовать в качестве контактов ввода-вывода общего назначения.
Настройка порта
Перед настройкой вывода на выход или на вход ему необходимо назначить одну функцию из 32 GPIOHS или 8 GPIO. Это можно сделать двумя способами:
- с использованием функции
int fpioa_set_function (int number, fpioa_function_t function)
; - с конфигурацией объекта
g_fpioa_cfg
.
Первый вариант можно использовать в небольших проектах с небольшим количеством конфигураций или в проектах, которые требуют изменения функции вывода во время работы программы (runtime).
Второй вариант более предпочтителен, поскольку за настройку всех выводов отвечает только один файл, таким образом проект становится более структурированным и читаемым.
В приведенных ниже примерах показано, как соотносить функцию GPIOHS0 на вывод под номером 3 (это вывод IO3 на разъеме P5). Обратите внимание, что перечисление выполняется относительно FUNC_GPIOHS0
. В дальнейшем будут использоваться только значения от 0 до 31. Поэтому желательно использовать макросы (#define
).
Пример 1:
fpioa_set_function(3, static_cast<fpioa_function_t>(FUNC_GPIOHS0 + 0));
Пример 2:
#ifndef PROJECT_CFG_H #define PROJECT_CFG_H #include <pin_cfg.h> const fpioa_cfg_t g_fpioa_cfg = { /* Версия */ .version = PIN_CFG_VERSION, /* Число функций */ .functions_count = 1, /* Офисание функций */ .functions = { /* */ {3, static_cast<fpioa_function_t>(FUNC_GPIOHS0 + 0)}, }, }; #endif
После этого необходимо открыть устройство gpio0
с помощью функции io_open
.
/* Открываем GPIO0 устройство */ gpio = io_open("/dev/gpio0");
И наконец настраиваем режим работы вывода (пина) – на вход или выход.
/* Устанавливаем режим работы пина 0 на вход. */ gpio_set_drive_mode(gpio, 0, GPIO_DM_INPUT);
/* Устанавливаем режим работы пина 0 на выход. */ gpio_set_drive_mode(gpio, 0, GPIO_DM_OUTPUT);
/* Устанавливаем режим работы пина 0 на вход с подтягивающим резистором (pull-up resistor). */ gpio_set_drive_mode(gpio, 0, GPIO_DM_INPUT_PULL_UP);
/* Устанавливаем режим работы пина 0 на вход с стягивающим резистором (pull-down resistor). */ gpio_set_drive_mode(gpio, 0, GPIO_DM_INPUT_PULL_DOWN);
Примеры программ
Чтобы лучше понять, как всё это работает, приведу два примера программ. Комментарии также будут добавлены в код для более подробного описания.
Пример 1 — мигаем светодиод
В первом примере будем мигать светодиодом, подключенным к одному из выводов платы. В файле project_cfg.h настроим функцию вывода, используемый для мигания светодиода. В файле main.cpp настроим вывод на выход и создадим задачу blinkLedTask
, которая будет вызываться с интервалом 100 мс, и при каждом вызове светодиод будет менять свое состояние с ВКЛ на ВЫКЛ и наоборот.
Схема подключения
Светодиод подключается на 13-й контакт Maixduino/Arduino через резистор, ограничивающий ток.
Файл project_cfg.h
#ifndef PROJECT_CFG_H #define PROJECT_CFG_H #include <pin_cfg.h> /** * Номер внутреннего пина */ #define LED_IO (0) /** * Номер физического пина */ #define LED_PIN (3) const fpioa_cfg_t g_fpioa_cfg = { /* Версия */ .version = PIN_CFG_VERSION, /* Число функций */ .functions_count = 1, /* Офисание функций */ .functions = { /* */ {LED_PIN, static_cast<fpioa_function_t>(FUNC_GPIOHS0 + LED_IO)}, }, }; #endif
Файл main.cpp
#include "project_cfg.h" #include <FreeRTOS.h> #include <devices.h> #include <syslog.h> #include <task.h> /** * Указатель на устройство GPIO */ static handle_t gpio; /** * Текущее состояние светодиода */ static gpio_pin_value_t ledState; /** * Прототип задачи включения/выключения светодиода * * @param pvParameters Функции задач принимают параметр, имеющий тип указателя на void (т. е. void*). Значение, указанное в pvParameters, будет передано в задачу. */ static void blinkLedTask(void *pvParameters); /** * */ int main() { BaseType_t retCode; /* Открываем GPIO0 устройство */ gpio = io_open("/dev/gpio0"); /* Перехват ошибок в процессе разработки */ configASSERT(gpio); /* Устанавливаем режим работы LED_IO пина на выход. */ gpio_set_drive_mode(gpio, LED_IO, GPIO_DM_OUTPUT); /* Задаём начальное состояние светодиода (выключаем) */ ledState = GPIO_PV_LOW; /* Пишем состояние в пин */ gpio_set_pin_value(gpio, LED_IO, ledState); /* Создаём задачу с мигающим светодиодом */ retCode = xTaskCreateAtProcessor(1, &blinkLedTask, "Blink Led task", 512, nullptr, 3, nullptr); /* Проверяем, если задача была успешно создана */ if (retCode == pdPASS) { /* В случае успеха выводим информационное сообщение */ LOGI("MFRB", "Blink Led task is running"); } else { /* В случае неудачи выводим предупреждающее сообщение */ LOGW("MFRB", "Blink Led task start problems"); } for (;;) { } return 0; } static void blinkLedTask(void *pvParameters) { while (1) { /* Меняем состояние в 1/0 */ if (GPIO_PV_HIGH == ledState) { ledState = GPIO_PV_LOW; } else { ledState = GPIO_PV_HIGH; } /* Пишем новое состояние в пин */ gpio_set_pin_value(gpio, LED_IO, ledState); /* Помещаем задачу в состояние Blocked на фиксированное количество тиков прерываний. Находясь в состоянии Blocked, задача не использует процессорное время, поэтому процессор загружен только полезной работой. С помощью макроса pdMS_TO_TICKS мы конвертируем миллисекунды в тики */ vTaskDelay(pdMS_TO_TICKS(100)); } }
Пример 2 — кнопка и светодиод
Во втором примере помимо светодиода подключим еще и кнопку. Если кнопка не нажата, светодиод будет менять свое состояние (мигать) каждые 500 мс, а при нажатии — каждые 100 мс. Вывод, подключенный к кнопке, настроен на вход с подтягивающим резистором gpio_set_drive_mode(gpio, BTN_IO, GPIO_DM_INPUT_PULL_UP);
.
Схема подключения
Светодиод подключается на 13-й контакт Maixduino/Arduino через резистор, ограничивающий ток, а кнопка на 12-й контакт и GND.
Файл project_cfg.h
#ifndef PROJECT_CFG_H #define PROJECT_CFG_H #include <pin_cfg.h> /** * Номер внутреннего пина */ #define LED_IO (0) #define BTN_IO (1) /** * Номер физического пина */ #define LED_PIN (3) #define BTN_PIN (10) const fpioa_cfg_t g_fpioa_cfg = { /* Версия */ .version = PIN_CFG_VERSION, /* Число функций */ .functions_count = 2, /* Офисание функций */ .functions = { /* */ {LED_PIN, static_cast<fpioa_function_t>(FUNC_GPIOHS0 + LED_IO)}, {BTN_PIN, static_cast<fpioa_function_t>(FUNC_GPIOHS0 + BTN_IO)}, }, }; #endif
Файл main.cpp
#include "project_cfg.h" #include <FreeRTOS.h> #include <devices.h> #include <syslog.h> #include <task.h> /** * Указатель на устройство GPIO */ static handle_t gpio; /** * Текущее состояние светодиода */ static gpio_pin_value_t ledState; /** * Прототип задачи включения/выключения светодиода * * @param pvParameters Функции задач принимают параметр, имеющий тип указателя на void (т. е. void*). Значение, указанное в pvParameters, будет передано в задачу. */ static void blinkLedTask(void *pvParameters); /** * */ int main() { BaseType_t retCode; /* Открываем GPIO0 устройство */ gpio = io_open("/dev/gpio0"); /* Перехват ошибок в процессе разработки */ configASSERT(gpio); /* Устанавливаем режим работы LED_IO пина на выход. */ gpio_set_drive_mode(gpio, LED_IO, GPIO_DM_OUTPUT); /* Устанавливаем режим работы BTN_IO пина на вход с подтягивающим резистором. */ gpio_set_drive_mode(gpio, BTN_IO, GPIO_DM_INPUT_PULL_UP); /* Задаём начальное состояние светодиода (выключаем) */ ledState = GPIO_PV_LOW; /* Пишем состояние в пин */ gpio_set_pin_value(gpio, LED_IO, ledState); /* Создаём задачу с мигающим светодиодом */ retCode = xTaskCreateAtProcessor(1, &blinkLedTask, "Blink Led task", 512, nullptr, 3, nullptr); /* Проверяем, если задача была успешно создана */ if (retCode == pdPASS) { /* В случае успеха выводим информационное сообщение */ LOGI("MFRB", "Blink Led task is running"); } else { /* В случае неудачи выводим предупреждающее сообщение */ LOGW("MFRB", "Blink Led task start problems"); } for (;;) { } return 0; } static void blinkLedTask(void *pvParameters) { /* Состояние кнопки */ gpio_pin_value_t btnState; /* Время повторения */ unsigned int timeInMs; while (1) { /* Считываетм состояние кнопки. */ btnState = gpio_get_pin_value(gpio, BTN_IO); if (btnState == GPIO_PV_LOW) { /* Если кнопка нажата, мы меняем повторяемость задачи на 100 мс. */ timeInMs = 100; } else { /* Если нет - 500 мс. */ timeInMs = 500; } /* Меняем состояние в 1/0 */ if (GPIO_PV_HIGH == ledState) { ledState = GPIO_PV_LOW; } else { ledState = GPIO_PV_HIGH; } /* Пишем новое состояние в пин */ gpio_set_pin_value(gpio, LED_IO, ledState); /* Помещаем задачу в состояние Blocked на фиксированное количество тиков прерываний. Находясь в состоянии Blocked, задача не использует процессорное время, поэтому процессор загружен только полезной работой. С помощью макроса pdMS_TO_TICKS мы конвертируем миллисекунды в тики */ vTaskDelay(pdMS_TO_TICKS(timeInMs)); } }
Материалы
Kendryte · GitHub
Maixduino-4.30(schematic)
Maixduino — одноплатный компьютер с ускорителем AI, RISC-V AI, форм-фактор Arduino и беспроводной модуль ESP32
Pretty nice post. I just stumbled upon your weblog and wanted to
say that I’ve truly enjoyed surfing around your blog posts.
In any case I will be subscribing to your rss feed and I hope you write again soon!