Глава 5. Использование периферийных интерфейсов
5.1. Использование интерфейса Bluetooth
Bluetooth является беспроводным интерфейсом для обмена информацией между различными устройствами, позволяя этим устройствам обмениваться данными, когда они находятся друг от друга в радиусе около 10 м. Радиус действия зависит от количества помех на пути передачи сигнала. В микрокомпьютере NanoPi-AR находится модуль Bluetooth версии 4.2, который поддерживает режим Low Energy. В качестве примера рассмотрим процесс передачи файлов. Для работы с модулем Bluetooth необходимо активировать сервис «bluetooth_radio» в графическом интерфейсе микрокомпьютера (Рис. 5.1). После этого в списке Bluetooth устройств нужно найти сетевое хранилище с названием «manipulator» и подключиться к нему.

Рис. 5.1. Список служб в графическом интерфейсе микрокомпьютера NanoPi-AR
Отправка файлов на микрокомпьютер через Bluetooth осуществляется стандартным способом. Отправленные файлы сохраняются в корневую папку микрокомпьютера. Для скачивания файлов из корневой папки можно воспользоваться программой Bluetooth File Transfer (Рис. 5.2).
Установочный файл программы находится в каталоге «/nanopi_examples/ additional/bluetooth/».

Рис. 5.2. Окно программы Bluetooth File Transfer
5.2. Использование интерфейса I2C
Интерфейс I2C широко распространен в современной электронике. Данный интерфейс представляет собой две двунаправленные линии связи, называемые линией последовательных данных (SDA) и линией тактирования (SCL), реализующие последовательный протокол обмена данными. В дополнение к двум сигнальным линиям требуются линия питания и земли. Данный интерфейс позволяет последовательно подключать несколько модулей к одному контроллеру. Это происходит благодаря тому, что каждый I2C модуль является ведомым (Slave) устройством и имеет уникальный адрес, за счет которого ведущее устройство (Master) способно находить конкретное устройство в цепи и обмениваться данными именно с ним. Таким образом, устройства, поддерживающие I2C интерфейс, могут быть очень разнообразными по своей сути, и для работы с ними, как правило, применяются соответствующие библиотеки, которые существенно облегчают процесс написания кода и обработки данных.
Для примера работы с шиной I2C воспользуемся графическим дисплеем на чипе SSD1306, позволяющим выводить изображение 128x64 пикселя. Схема подключения к плате расширения для интегрирования одноплатного микрокомпьютера NanoPi-AR Shield изображена на Рис. 5.3.

Рис. 5.3. Подключение I2С дисплея к периферийной плате NanoPi-AR Shield
Для компиляции примера необходимо добавить дополнительные библиотеки в сборку проекта. Для этого нужно отредактировать файл «Cmake.txt». Дополнительный
код для работы библиотеки выделен курсивом:
cpp
cmake_minimum_required(VERSION 3.14)
project(example_18_i2c)
f
ile(GLOB SRC_DYNAMIXEL
"../src/dynamixel_sdk/*.cpp"
)
f
ile(GLOB SRC_SSD1306
"../src/libssd1306/*.cxx"
)
add_executable(example_18_i2c)
target_sources(example_18_i2c PRIVATE
example_18_i2c.cpp
${SRC_DYNAMIXEL}
${SRC_SSD1306}
)
target_compile_definitions(example_18_i2c PRIVATE -DFMT_HEADER_
ONLY=1)
target_compile_options(example_18_i2c PRIVATE -Wno-psabi)
target_include_directories(example_18_i2c PRIVATE
../src
../src/dynamixel_sdk
../src/libssd1306
)Для работы с I2C дисплеем используется проект «example_08_noi». Программа запускает бесконечный цикл для отображения сообщения «Hello world» по центру дисплея. При завершении цикла программа очищает память дисплея. Листинг программы представлен ниже:
cpp
// Подключение библиотек, необходимых для работы проекта
#include <array>
#include <chrono>
#include <cmath>
#include <csignal>
#include <cstring>
#include <exception>
#include <iostream>
#include <memory>
#include <thread>
#include <system_error>
#include "OledFont8x8.h"
#include "OledFont8x16.h"
#include "OledI2C.h"
// Инициализация пространства имён для работы с шиной I2C
namespace
{
volatile static std::sig_atomic_t run = 1;
}
static void
signalHandler(
int signalNumber)
{
switch (signalNumber)
{
case SIGINT:
case SIGTERM:
run = 0;
break;
};
}
// Функция вывода на экран сообщения «Hello World»
void helloworld(
SSD1306::OledI2C& oled)
{
static constexpr SSD1306::PixelStyle style{SSD1306::PixelStyle::Set};
char mess[12] = "Hello World"; // выводимая строка
int length = 11; // количество символов в выводимой строке
int offset = (128 - (8 * length)) / 2; // формула центровки сообщения по горизонтали
SSD1306::OledPoint location{offset, 18}; //центровка сообщения по вертикали
location = drawString8x16(location, mess, style, oled);
oled.displayUpdate(); // обновление дисплея с новыми параметрами
}
// Основная функция проекта
Int main()
{
try
{
constexpr std::array<int, 2> signals{SIGINT, SIGTERM};
for (auto signal : signals)
{
if (std::signal(signal, signalHandler) == SIG_ERR){
std::string what{"installing "};
what += strsignal(signal);
what += " signal handler";
throw std::system_error(errno, std::system_category(), what);
}
}
SSD1306::OledI2C oled{"/dev/i2c-0", 0x3C}; // инициализация дисплея по адресу 0x3C
while (run)
{
helloworld(oled); // запуск функции отображения сообщения
}
oled.clear(); // очистка памяти дисплея при остановке цикла
oled.displayUpdate(); // обновление дисплея с новыми параметрами
}
catch (std::exception& e)
{
std::cerr << e.what() << "\n";
}
return 0;
}Для завершения программы необходимо нажать сочетание клавиш "CTRL+C". Эскиз выполнения программы показан на Рис. 5.4.

Рис. 5.4. Эскиз выполнения проекта "example_18_i2c"
5.3. Использование интерфейса SPI
Протокол SPI является последовательным периферийным интерфейсом передачи данных в режиме полного дуплекса, предназначенным для обеспечения простого высокоскоростного сопряжения микроконтроллеров и периферии. Данный интерфейс является синхронным, поэтому любая передача синхронизирована с общим тактовым сигналом, генерируемым ведущим устройством. Принимающее устройство синхронизирует получение битовой последовательности с тактовым сигналом. К одному последовательному периферийному интерфейсу ведущего устройства может присоединяться несколько микросхем. Ведущее устройство выбирает ведомое для передачи, активируя сигнал «chip select» на ведомом устройстве.
В SPI используются четыре цифровых сигнала:
MOSI — выход ведущего, вход ведомого. Используется для передачи данных от ведущего устройства ведомому.
MISO — вход ведущего, выход ведомого. Используется для передачи данных от ведомого устройства ведущему.
SCK — последовательный тактовый сигнал. Используется для передачи тактового сигнала для ведомых устройств.
CS — выбор микросхемы, выбор ведомого.
Для демонстрации работы шины SPI в данном примере будет использоваться контроллер DXL-IoT, который будет подключатся к микрокомпьютеру NanoPi-AR через его периферийную плату расширения в качестве ведомого. Также для индикации работы шины в схеме будет использоваться светодиод, подключенный к контроллеру DXL-IoT. Схема проекта представлена на Рис. 5.5. Для работы контроллера DXL-IoT в режиме ведомого устройства нужно загрузить демонстрационный скетч «dxliot_spi_example.ino», расположенный в каталоге «/nanopi_examples/additional/spi/». Данный код ожидает получение на шине SPI значения «1» и, если он получил это значение, зажигает светодиод, подключенный к пину 7. Если на шину SPI приходят другие данные, плата выключает светодиод. Для загрузки кода нужно использовать среду Arduino IDE.

Рис. 5.5. Схема подключения модулей для использования интерфейса SPI
Код программы представлен ниже:
cpp
#include<SPI.h> // библиотека для работы с шиной SPI
#define LEDpin 7 // Установка пина, к которому подключен светодиод
volatile boolean received; // переменная состояния получения данных
volatile byte Slavereceived, Slavesend; // переменная для хранения данных
void setup(){
pinMode(LEDpin,OUTPUT); // установка пин 7 на вывод
pinMode(MISO,OUTPUT); // установка MISO на вывод
SPCR |= _BV(SPE); // установка SPI в режим ведомого
received = false;
SPI.attachInterrupt(); // включение прерывания для протокола SPI
}
ISR (SPI_STC_vect){ // функция обработки прерывания
Slavereceived = SPDR; // сохранение полученных данных в переменную
received = true;
}
void loop(){
if(received) {
if (Slavereceived==1){ // если полученное значение равно "1"
digitalWrite(LEDpin,HIGH); // включение светодиода
}
else{ // если полученное значение не равно "1"
digitalWrite(LEDpin,LOW); // выключение светодиода
}
}
}Пример работы приведен в проекте «example_19_spi». Программа отправляет в шину SPI байт со значением «1» и выводит в терминал сообщение «LED ON». После чего ожидает 1 секунду, и отправляет байт со значением 0, выводя в терминал сообщение «LED OFF».
Листинг программы представлен ниже:
cpp
// библиотеки для работы проекта
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <iostream>
// инициализация шины SPI
int main(int argc, char **argv){
int fd;
struct spi_ioc_transfer xfer[2];
unsigned char buf[32], buf_rx[32], *bp;
int len, status;
fd = open("/dev/spidev0.0", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
memset(xfer, 0, sizeof xfer);
memset(buf, 0, sizeof buf);
len = sizeof buf;
unsigned int speed = 1000000;
int ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
printf("can’t set max speed hz");
ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if (ret == -1)
printf("can’t get max speed hz");
buf[0] = 0x01; // запись в буфер отправки значения «1»
len = 1; // указание длины буфера
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 1;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer); // отправка байта информации
std::cout << "LED ON" << std::endl;
// вывод сообщения в терминал
sleep(1);
buf[0] = 0x00; // запись в буфер отправки значения «0»
len = 1; // указание длины буфера
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 1;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer); // отправка байта информации
std::cout << "LED OFF" << std::endl; // вывод сообщения в терминал
return 0;
}Результат выполнения программы показан на Рис. 5.6.

Рис. 5.6. Результат выполнения проекта «example_19_spi»
5.4. Использование аудиовыхода
Перед воспроизведением аудиофайла необходимо воспользоваться утилитой «alsamixer», чтобы произвести настройки звуковой карты для вывода звука через интерфейс H3. Пример настроек изображён на Рис. 5.7.

Рис. 5.7. Настройки H3 для воспроизведения аудио
Для тестирования аудиовыхода в папке с примерами имеется звуковой файл «test16bit_12khz.wav». Он расположен в каталоге «/nanopi examples/additional/audio/». Для воспроизведения данного файла необходимо подключить динамик в соответствующий разъем периферийной платы и выполнить команду «aplay» c соответствующими флагами.
Список основных ключей:
- -h, помощь: показать синтаксис команды;
- -l, вывести список всех аудиоустройств;
- -D, выбор используемого аудиоустройства;
- -t, выбор типа воспроизводимого аудиофайла;
- -c, выбор числа каналов для воспроизведения;
- -r, выбор частоты воспроизводимого аудиофайла;
- -d, выбор длительности воспроизводимого аудиофайла.
Для воспроизведения тестового файла необходимо ввести команду «aplay -D hw:1,0 test_16bit_12khz.wav», в которой с помощью ключа «-D» выбран интерфейс «hw:1.0», используемый для вывода звука через разъём 14. Пример выполнения программы показан на Рис. 5.8.

Рис. 5.8. Результат выполнения команды «aplay»
5.5. Использование аудиовхода
Перед записью аудиофайла необходимо воспользоваться утилитой «alsamixer», чтобы произвести настройки звуковой карты для записи звука через интерфейс H3. Пример настроек изображён на Рис. 5.9.

Рис. 5.9. Настройки H3 для записи аудио
Для записи звукового файла необходимо подключить микрофон в соответствующий разъём периферийной платы и выполнить команду «arecord» c соответствующими флагами.
Список основных ключей:
- -h, помощь: показать синтаксис команды;
- -l, вывести список всех аудиоустройств;
- -D, выбор используемого аудиоустройства;
- -t, выбор типа записываемого аудиофайла;
- -r, выбор частоты записываемого аудиофайла;
- -d, выбор длительности записываемого аудиофайла.
Для записи тестового файла необходимо ввести команду «arecord -d 5 -D hw:1,0 -f cd test.wav», в которой с помощью ключа «-D» выбран интерфейс «hw:1.0», используемый для вывода звука через разъём 13, и длительность записи с помощью ключа «-d». Пример выполнения программы показан на Рис. 5.10. Для воспроизведения записи можно воспользоваться командой «aplay -D hw:1,0 test.wav».

Рис. 5.10. Результат выполнения команды «arecord»