В этом уроке напишем первую программу, научимся считывать значение цифровых входов и устанавливать состояние выходов. Реализуем управление такими простыми элементами, как кнопка и светодиод на платформе 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


