Go и arduino

13 minute read

На ардуине много всего можно построить и, иногда, этим всем нужно как-то управлять. Для этого можно использовать последовательный порт, отправляя команды с помощью Go программы.

В ардуине есть класс Serial, который служит для связи устройства ардуино с компьютером или другими устройствами, поддерживающими последовательный интерфейс обмена данными. Все платы arduino имеют хотя бы один последовательный порт (UART, иногда называют USART). Для обмена данными Serial используют цифровые порты ввод/вывода 0 (RX) и 1 (TX), а также USB порт. Важно помнить, что если вы используете класс Serial, то нельзя одновременно с этим использовать порты 0 и 1 для других целей.

В Go работать с последовательным портом, проще всего используя, пакет sio. У этого пакета не очень большой функционал, но его вполне хватает для оправки и получения данных с устройства.

Эхо

Для примера, напишем небольшую программу, которая отправляет данные на устройство и получает их обратно. Начнем с Go части. Cначала нужно установить соединение:

port, err := sio.Open("/dev/ttyACM0", syscall.B9600)
if err != nil {
    log.Fatal(err)
}

/dev/ttyACM0 - имя порта, которое можно посмотреть в arduino ide или используя плагин для sublime

syscall.B9600 - скорость передачи данных в бит/c (бод). Для наших экспериментов не имеет принципиального значения, на какой скорости будет происходить обмен, важно, чтоб этот параметр был одинаковым и на устройстве и в нашей Go-программе.

После этого можно отправлять данные в порт.

_, err = port.Write([]byte("test\n"))
if err != nil {
    log.Fatal(err)
}

В устройстве первым делом устанавливаем соединение.

void setup() {
    Serial.begin(9600);
}

Потом получаем данные и оправляем их обратно в порт.

if (Serial.available() > 0) {  
    // если есть доступные данные
    // считываем байт
    incomingByte = Serial.read();

    Serial.print(incomingByte);
}

Теперь пришло время считывать данные на стороне Go программы.

reader := bufio.NewReader(port)
reply, err := reader.ReadBytes('\n')
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%q", reply)

Все вместе это выглядит так:

// echo.go

package main

import (
    "bufio"
    "fmt"
    "github.com/schleibinger/sio"
    "log"
    "syscall"
)

func main() {
    // устанавливаем соединение
    port, err := sio.Open("/dev/ttyACM0", syscall.B9600)
    if err != nil {
        log.Fatal(err)
    }

    // отправляем данные
    _, err = port.Write([]byte("test\n"))
    if err != nil {
        log.Fatal(err)
    }

    // получаем данные
    reader := bufio.NewReader(port)
    reply, err := reader.ReadBytes('\n')
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%q", reply)
}
// echo.ino

// переменная для хранения полученного байта
char incomingByte = 0;

void setup() {
    // устанавливаем последовательное соединение
    Serial.begin(9600);
}


void loop() {
    if (Serial.available() > 0) {  
        // если есть доступные данные
        // считываем байт
        incomingByte = Serial.read();

        Serial.print(incomingByte);
    }
}

Последний твит

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

Конечно, сначала нужно получить твиты. И для этого заюзаем пакет anaconda.

anaconda.SetConsumerKey(consumerKey)
anaconda.SetConsumerSecret(consumerSecret)
api := anaconda.NewTwitterApi(key, secretKey)

consumerKey, consumerSecret, key, secretKey - соответственно ключи взятые из настроек твиттер приложения

Получаем твиты по ключевому слову #golang:

searchResult, _ = api.GetSearch("#golang", nil)
twitt = " -- " + searchResult[0].Text
fmt.Println(twitt)

В searchResult будет храниться список всех найденных твитов, но нам нужен только последний по времени.

Оправляем твит в последовательный порт:

var twitt string
var searchResult []anaconda.Tweet

for {

    searchResult, _ = api.GetSearch("#golang", nil)
    twitt = " -- " + searchResult[0].Text
    fmt.Println(twitt)

    // отправляем данные
    _, err = port.Write([]byte(twitt))
    if err != nil {
        log.Fatal(err)
    }

    time.Sleep(120 * time.Second)
}

Таким образом, в порт будет оправляться новый твит каждые три минуты.

Ардуино-часть нашего маленького проекта начинается с объявления и инициализации:

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int bytesCount = 0;
char data[140] = "";
char string[16] = "";

LiquidCrystal - это наш LCD дисплей, data - это полученные данные, bytesCount - количество полученных байтов, string - это строка информации, которая будет отображаться на дисплее.

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

if (Serial.available() > 0) {  
    memset(&data[0], 0, sizeof(data));
    bytesCount = Serial.readBytes(data, 1000);
}

memset(&data[0], 0, sizeof(data)); - это очистка массива перед получением новых данных, так как следующий твит может оказаться короче предыдущего.

И последний шаг - отображение данных в виде бегущей строки:

for(int i = 0; i < bytesCount; i++){
    lcd.setCursor(0, 1);
    for(int j = 0; j < 16; j++){
        if (data[i+j] == NULL) string[j] = data[(i+j) - bytesCount];
        else string[j] = data[i+j];
    }
    lcd.print(string);
    delay(300);
}

Полностью исходники можно посмотреть на гитхабе. Для управления зависимостями использовался gpm.

Жду предложений и замечаний.