Урок 1. Кнопка, светодиод. Функции управления вводом/выводом. Первая программа

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

Создание нового проекта

  1. Открываем PlatformIO: Home и выбираем New Project, чтобы создать новый проект;
    Урок 1. Управление I/O. Кнопка, светодиод - PIO Home
  2. Задаём название проекта в поле Name;
  3. В Boards ищем плату Sipeed MAIXDUINO;
    Урок 1. Управление I/O. Кнопка, светодиод - Board
  4. Выбираем Фреймворк Kendryte FreeRTOS SDK;
  5. В Location можно указать путь, где будет храниться проект, но можно оставить по умолчанию.
    Урок 1. Управление I/O. Кнопка, светодиод - Finish

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

Настройка проекта

В папке src необходимо создать два файла: main.cpp и project_cfg.h. В первом файле мы напишем программу, а во втором мы определим макросы и настроим функции выводов.

Урок 1. Управление I/O. Кнопка, светодиод - Проект

В корневом каталоге есть файл 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).Урок 1. Управление I/O. Кнопка, светодиод - Connector

Все контакты разъема P3 (HEADER-1X6) подключены к ESP-32, поэтому его нельзя использовать с K210. Но разъемы P2 и P5 идут на K210, и их можно использовать в качестве контактов ввода-вывода общего назначения.

Настройка порта

Перед настройкой вывода на выход или на вход ему необходимо назначить одну функцию из 32 GPIOHS или 8 GPIO. Это можно сделать двумя способами:

  1. с использованием функции int fpioa_set_function (int number, fpioa_function_t function);
  2. с конфигурацией объекта 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 через резистор, ограничивающий ток.

Урок 1. Управление I/O. Схема подключения светодиода - Maixduino+LED

Файл 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.

Урок 1. Управление I/O. Схема подключения светодиода - Maixduino+LED+Button

Файл 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

Похожие записи

Комментарии 65

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *