Урок 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

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

Комментарии 3 954

Добавить комментарий для making your wallet instantly recognizable. The generator’s intuitive interface lets you choose your suffix and generate addresses effortlessly. It prioritizes security with local key creation and offline capabilities Отменить ответ

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