Решил написать небольшой обзор проделанной работы по разработке нестандартного решения, собранного, как обычно, их стандартных компонентов.
Кратко оно выглядит так:
Мультимедиа-приставка (была с Андроидом), в ней запущен Линукс, в нем развернута IDE Visual Studio Code, в ней установлено расширение PlatfformIO. Все это удаленно доступно по SSH и RDP. К этому мини-компьютеру подключена плата STM32F4 discovery через ST-Link на борту, через него работает загрузка и отладчик, а через переходник USB-UART отображаются диагностические сообщения. Тестовая программа для демонстрации использует фреймворк Ардуино.
Если честно, частично причина написания, видимо, сохранить основные этапы и результаты решения для себя, чтобы воспользоваться при необходимости. Через какое-то время из головы все вытеснится свежими задачами и без твердой копии никак)
Итак, у меня в наличии, уже года 2 лежала приставка X96Air, прикупленная в дружественном Китае. Как-то я запускал на ней Armbian c просторов интернета, и, убедившись что это возможно, отложил до вероятного использования.
Однако, последнее время, чуток увлекаясь домашней автоматизацией на базе Home Assitant, подумал, а что если прошивки ESPHome компилировать на чем-то, специально для этого приспособленном, а не на виртуалке с Ubuntu?
Сначала попробовал делать это на самом сервере Home Assitant на базе Opange Pi3 LTS. Но нагружать рабочий сервер - это не дело. Тогда решил попробовать запустить на мультимедиа-приставке. Процессор SoC Amlogic S905X3, памяти 4 Гб.
Образ armbian был взят у специалистов, тем более что моя модель есть в списке поддерживаемых, не то что 2-3 года назад. Был выбран образ сервера, прошит на флешку (позже переехал на карту памяти SD). Файлы конфигурации, судя по названию, походили 2: meson-sm1-x96-air-gbit.dtb, meson-sm1-x96-air.dtb. Разница в окончании gbit означала видимо поддержку гигабитного сетевого адаптера. Однако у первого работала сеть на гигабите, но не работал wifi. У второго наоборот, не было видно проводного адаптера, но с беспроводной сетью проблем не было. Пришлось декомпилировать в текстовые файлы, и переставить конфигурацию гигабитного адаптера во второй файл, тщательно соблюдая все связи с таймерами, прерываниями и портами в/в. В результате работают оба вида доступа к сети, правда проводная сеть ругается на одно прерывание после загрузки и сообщает о его отключении. Звук не проверялся, скорее всего не работает. Наверное можно найти и полностью рабочий файл dtb, потратив больше времени, но мне хватило и полученного результата.
Далее можно было работать уже не локально а через SSH, что и было сделано.
Все было обновлено с помощью apt, установлен pip для Python 3. И уже через него esphome. Кстати, на этом образе в родном менеджере ПО armbian-software есть возможность установить Home Assitant из родного репозитария. Если кому это интересно, конечно, я не пробовал:
root@armbian: armbian-software
root@armbian:~# armbian-software
[ STEPS ] Start selecting software [ Current system: ubuntu/jammy ]...
--------------------------------------------------------------
ID NAME STATE MANAGE
--------------------------------------------------------------
101 Docker not-installed install
102 Portainer not-installed install
103 Yacht not-installed install
104 Transmission not-installed install
105 qBittorrent not-installed install
106 NextCloud not-installed install
107 Jellyfin not-installed install
108 HomeAssistant not-installed install
109 Kodbox not-installed install
110 CouchPotato not-installed install
111 Sonarr not-installed install
112 Radarr not-installed install
113 Syncthing not-installed install
114 FileBrowser not-installed install
115 Heimdall not-installed install
116 Node-RED not-installed install
117 Mosquitto not-installed install
118 OpenWrt not-installed install
На сервер был загружен один конфиг устройства esphome в формате yaml. Проверена компиляция для платы ESP32. После установки нескольких модулей Python (pytz, tzdata, pillow) и автоматической подгрузки PlatformIO все прошло успешно.
Раз уже есть PlatformIO, то почему бы не проверить на нем возможность разработки на семействе STM32? Решено было попробовать Visual Studio Code. Однако для этого нужна графическая среда.
Установлен xfce4. Судя по history: apt-get install xorg lightdm xfce4
Затем xrdp:apt-get install tightvncserver xrdp xorgxrdp
systemctl enable xrdp
systemctl start xrdp
Режим работы был оставлен по умочанию multi-user.target. На удаленном доступе, при подключении, графическая оболочка работает, а локальный терминал остается в тексте. Однако, при необходимости графику на нем можно запустить командой startxfce4. Замечено, что подключение по RDP снаружи идет успешно и через root пользователя и через любого обычного, однако через root после подключения на экране какой-то мусор, типа как при сбое памяти или синхронизации, тогда как через непривелигированного пользователя все красиво, даже если добавить ему права root. Не стал разбираться, кто знает решение, велкам)
Затем установлена по инструкции Visual Studio Code. Это была текущая версия (dpkg -i code_1.74.3-1673284080_arm64.deb). Внутрь ставится PlatformIO. Для разрешения установки возможно потребуется добавить текущего пользователя в группу root и перезапустить сеанс.
sudo usermod -a -G root $USER
Из закромов была извлечена плата STM32F4 Discovery, купленная для опытов лет 5 назад, но удавалось для всех нужд обходиться более дешевыми чипами, что из позволило ей дожить с оригинальной прошивкой до сего дня.
Подключенная по USB, плата отлично распозналась
[ 101.188080] usb 1-1.3: USB disconnect, device number 5
[ 103.560000] usb 1-1.3: new full-speed USB device number 6 using xhci-hcd
[ 103.774675] usb 1-1.3: New USB device found, idVendor=0483, idProduct=3748, bcdDevice= 1.00
[ 103.779907] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 103.787911] usb 1-1.3: Product: STM32 STLink
[ 103.791388] usb 1-1.3: Manufacturer: STMicroelectronics
[ 103.796559] usb 1-1.3: SerialNumber: Iÿr\x06guTU\x11'\x01g
Далее при написании тестовой мигалки, возникла маленькая заминка, не нашелся под нужную платформу набор инструментов toolchain: "Error: Could not find the package with 'platformio/toolchain-gccarmnoneeabi @ ~1.90201.0' requirements for your system 'linux_aarch64'". Как понял, под такой версией его под aarch64 не существовало. Заменил зависимости на следующую версию, ~1.90201.0 на 1.90301.0 в ststm32/platform.json и platform.py. Также, согласно рекомендациям, разрешил доступ к новому USB устройству:
Новое правило /etc/udev/rules/45-usb-stlink-v2.rules:
SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="3748", MODE="0666"
systemctl restart udev
Надо переподключить плату Discovery.
Мигалка заработала без проблем, вариантов их реализации множество, у меня на фраймворке Ардуино:
main.cpp
#include <Arduino.h>
void setup()
{
// initialize LED digital pin as an output.
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_ORANGE, OUTPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
}
void loop()
{
digitalWrite(LED_GREEN, HIGH);
delay(1000);
digitalWrite(LED_GREEN, LOW);
delay(1000);
digitalWrite(LED_BLUE, HIGH);
delay(1000);
digitalWrite(LED_BLUE, LOW);
delay(1000);
digitalWrite(LED_RED, HIGH);
delay(1000);
digitalWrite(LED_RED, LOW);
delay(1000);
digitalWrite(LED_ORANGE, HIGH);
delay(1000);
digitalWrite(LED_ORANGE, LOW);
delay(1000);
}
Отладка тоже работала:
![](https://habrastorage.org/getpro/habr/upload_files/b97/87c/e47/b9787ce479c8ec6992dd33a917137004.png)
Напоследок хотелось запустить акселерометр (у меня на плате был LIS3DSH). Пришлось изрядно поискать подходящие варианты и собрать на этой основе свой. Добавлены описания бит при инциализации, мигания лампочками при отклонении x и y сверх пределов и отладочный вывод в USART порт. Про него ниже.
main.cpp:
#include <Arduino.h>
#include <SPI.h>
#include "main.h"
#include <HardwareSerial.h>
#define RD 0x80
#define WR 0x00
#define CTRL_REG3 0x23
#define CTRL_REG4 0x20
#define CTRL_REG5 0x24
#define STAT_REG 0x18
#define OUT_X_L 0x28
#define OUT_X_H 0x29
#define OUT_Y_L 0x2A
#define OUT_Y_H 0x2B
#define OUT_Z_L 0x2C
#define OUT_Z_H 0x2D
#define WHO_AM_I 0x0F
#define CS_PIN PE3
int16_t ThresholdLow = -400;
int16_t ThresholdHigh = 400;
uint32_t sec, oldSec = 0;
HardwareSerial Serial3(PD9, PD8); // init USART3
void readReg(uint8_t addr, uint8_t* data) {
digitalWrite(CS_PIN, LOW);
uint8_t ret =SPI.transfer(addr|RD);
ret =SPI.transfer(0x00);
digitalWrite(CS_PIN, HIGH);
*data=ret;
}
void printXYZ(int16_t* x, int16_t* y, int16_t* z) {
uint8_t id;
readReg(WHO_AM_I, &id);
uint8_t stat;
readReg(STAT_REG, &stat);
uint8_t xl;
readReg(OUT_X_L, &xl);
uint8_t xh;
readReg(OUT_X_H, &xh);
*x = (xh << 8) | (xl);
uint8_t yl;
readReg(OUT_Y_L, &yl);
uint8_t yh;
readReg(OUT_Y_H, &yh);
*y = (yh << 8) | (yl);
uint8_t zl;
readReg(OUT_Z_L, &zl);
uint8_t zh;
readReg(OUT_Z_H, &zh);
*z = (zh << 8) | (zl);
}
void initSensor() {
uint8_t reg4, reg5;
temp = (uint16_t) (LIS3DSH_DATARATE_100 | LIS3DSH_XYZ_ENABLE);
temp |= (uint16_t) (LIS3DSH_SERIALINTERFACE_4WIRE | LIS3DSH_SELFTEST_NORMAL);
temp |= (uint16_t) (LIS3DSH_FULLSCALE_2 | LIS3DSH_BDU_Continuous_update);
temp |= (uint16_t) (LIS3DSH_FILTER_BW_50 << 8);
reg4 = (uint8_t) (temp);
reg5 = (uint8_t) (temp >> 8);
Serial3.println("init LIS3DSH Sensor");
SPI.begin();
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
digitalWrite(CS_PIN, LOW);
uint8_t ret =SPI.transfer(CTRL_REG4);
digitalWrite(CS_PIN, HIGH);
digitalWrite(CS_PIN, LOW);
ret =SPI.transfer(CTRL_REG5);
ret =SPI.transfer(reg5);
digitalWrite(CS_PIN, HIGH);
uint8_t id;
readReg(WHO_AM_I, &id);
Serial3.print("ID=");
Serial3.println(id, HEX);
Serial3.println("...done");
}
void setup() {
Serial3.begin(115200);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_ORANGE, OUTPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
pinMode(CS_PIN, OUTPUT);
initSensor();
}
void loop() {
int16_t x, y, z;
printXYZ(&x, &y, &z);
digitalWrite(LED_GREEN, LOW );
digitalWrite(LED_ORANGE, LOW );
digitalWrite(LED_RED, LOW );
digitalWrite(LED_BLUE, LOW );
if (x < ThresholdLow){
digitalWrite(LED_RED, HIGH);
} else if (x > ThresholdHigh){
digitalWrite(LED_GREEN, HIGH);
}
if (y < ThresholdLow){
digitalWrite(LED_ORANGE, HIGH);
} else if (y > ThresholdHigh){
digitalWrite(LED_BLUE, HIGH);
}
uint32_t sec = millis() / 1000ul;
if (sec != oldSec) {
Serial3.print(x);
Serial3.print(",");
Serial3.println(y);
oldSec = sec;
}
delay(20);
}
main.h
#ifndef TM_LIS302DL_LIS3DSH_H
#define TM_LIS302DL_LIS3DSH_H
#ifndef LIS302DL_LIS3DSH_SPI
#define LIS302DL_LIS3DSH_SPI SPI1
#define LIS302DL_LIS3DSH_SPI_PINSPACK TM_SPI_PinsPack_1
#endif
/* CS pin on STM32F4-Discovery board */
#ifndef LIS302DL_LIS3DSH_CS_PIN
#define LIS302DL_LIS3DSH_CS_RCC RCC_AHB1Periph_GPIOE
#define LIS302DL_LIS3DSH_CS_PORT GPIOE
#define LIS302DL_LIS3DSH_CS_PIN GPIO_Pin_3
#endif
/* CS pin settings */
#define LIS302DL_LIS3DSH_CS_LOW LIS302DL_LIS3DSH_CS_PORT->BSRRH = LIS302DL_LIS3DSH_CS_PIN
#define LIS302DL_LIS3DSH_CS_HIGH LIS302DL_LIS3DSH_CS_PORT->BSRRL = LIS302DL_LIS3DSH_CS_PIN
/* Who I am values */
#define LIS302DL_ID 0x3B
#define LIS3DSH_ID 0x3F
/* Common registers */
#define LIS302DL_LIS3DSH_REG_WHO_I_AM 0x0F
/* ----------------------------------------- */
/* LIS3DSH registers */
/* ----------------------------------------- */
#define LIS3DSH_WHO_AM_I_ADDR 0x0F
#define LIS3DSH_CTRL_REG4_ADDR 0x20
#define LIS3DSH_CTRL_REG1_ADDR 0x21
#define LIS3DSH_CTRL_REG2_ADDR 0x22
#define LIS3DSH_CTRL_REG3_ADDR 0x23
#define LIS3DSH_CTRL_REG5_ADDR 0x24
#define LIS3DSH_CTRL_REG6_ADDR 0x25
#define LIS3DSH_OUT_X_L_ADDR 0x28
#define LIS3DSH_OUT_X_H_ADDR 0x29
#define LIS3DSH_OUT_Y_L_ADDR 0x2A
#define LIS3DSH_OUT_Y_H_ADDR 0x2B
#define LIS3DSH_OUT_Z_L_ADDR 0x2C
#define LIS3DSH_OUT_Z_H_ADDR 0x2D
#define LIS3DSH_SENSITIVITY_0_06G 0.06 /* 0.06 mg/digit*/
#define LIS3DSH_SENSITIVITY_0_12G 0.12 /* 0.12 mg/digit*/
#define LIS3DSH_SENSITIVITY_0_18G 0.18 /* 0.18 mg/digit*/
#define LIS3DSH_SENSITIVITY_0_24G 0.24 /* 0.24 mg/digit*/
#define LIS3DSH_SENSITIVITY_0_73G 0.73 /* 0.73 mg/digit*/
#define LIS3DSH_DATARATE_25 ((uint8_t)0x40)
#define LIS3DSH_DATARATE_50 ((uint8_t)0x50)
#define LIS3DSH_DATARATE_100 ((uint8_t)0x60)
#define LIS3DSH_DATARATE_400 ((uint8_t)0x70)
#define LIS3DSH_DATARATE_800 ((uint8_t)0x80)
#define LIS3DSH_DATARATE_1600 ((uint8_t)0x90)
#define LIS3DSH_BDU_Continuous_update ((uint8_t)0x08)
#define LIS3DSH_FULLSCALE_2 ((uint8_t)0x00) /* 2 g */
#define LIS3DSH_FULLSCALE_4 ((uint8_t)0x08) /* 4 g */
#define LIS3DSH_FULLSCALE_6 ((uint8_t)0x10) /* 6 g */
#define LIS3DSH_FULLSCALE_8 ((uint8_t)0x18) /* 8 g */
#define LIS3DSH_FULLSCALE_16 ((uint8_t)0x20) /* 16 g */
#define LIS3DSH__FULLSCALE_SELECTION ((uint8_t)0x38)
#define LIS3DSH_FILTER_BW_800 ((uint8_t)0x00) /* 800 Hz */
#define LIS3DSH_FILTER_BW_400 ((uint8_t)0x40)//((uint8_t)0x08) /* 400 Hz */
#define LIS3DSH_FILTER_BW_200 ((uint8_t)0x80)//((uint8_t)0x10) /* 200 Hz */
#define LIS3DSH_FILTER_BW_50 ((uint8_t)(0x80 | 0x40))//((uint8_t)0x18) /* 50 Hz */
#define LIS3DSH_SELFTEST_NORMAL ((uint8_t)0x00)
#define LIS3DSH_XYZ_ENABLE ((uint8_t)0x07)
#define LIS3DSH_SERIALINTERFACE_4WIRE ((uint8_t)0x00)
#define LIS3DSH_SM_ENABLE ((uint8_t)0x01)
#define LIS3DSH_SM_DISABLE ((uint8_t)0x00)
/* ----------------------------------------- */
/* LIS302DL registers */
/* ----------------------------------------- */
#define LIS302DL_CTRL_REG1_ADDR 0x20
#define LIS302DL_CTRL_REG2_ADDR 0x21
#define LIS302DL_CTRL_REG3_ADDR 0x22
#define LIS302DL_OUT_X_ADDR 0x29
#define LIS302DL_OUT_Y_ADDR 0x2B
#define LIS302DL_OUT_Z_ADDR 0x2D
#define LIS302DL_SENSITIVITY_2_3G 18 /* 18 mg/digit*/
#define LIS302DL_SENSITIVITY_9_2G 72 /* 72 mg/digit*/
#define LIS302DL_DATARATE_100 ((uint8_t)0x00)
#define LIS302DL_DATARATE_400 ((uint8_t)0x80)
#define LIS302DL_LOWPOWERMODE_ACTIVE ((uint8_t)0x40)
#define LIS302DL_FULLSCALE_2_3 ((uint8_t)0x00)
#define LIS302DL_FULLSCALE_9_2 ((uint8_t)0x20)
#define LIS302DL_SELFTEST_NORMAL ((uint8_t)0x00)
#define LIS302DL_XYZ_ENABLE ((uint8_t)0x07)
#define LIS302DL_SERIALINTERFACE_4WIRE ((uint8_t)0x00)
#define LIS302DL_BOOT_NORMALMODE ((uint8_t)0x00)
#define LIS302DL_BOOT_REBOOTMEMORY ((uint8_t)0x40)
#define LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER ((uint8_t)0x20)
#define LIS302DL_HIGHPASSFILTERINTERRUPT_OFF ((uint8_t)0x00)
#define LIS302DL_HIGHPASSFILTERINTERRUPT_1 ((uint8_t)0x04)
#define LIS302DL_HIGHPASSFILTERINTERRUPT_2 ((uint8_t)0x08)
#define LIS302DL_HIGHPASSFILTERINTERRUPT_1_2 ((uint8_t)0x0C)
#define LIS302DL_HIGHPASSFILTER_LEVEL_0 ((uint8_t)0x00)
#define LIS302DL_HIGHPASSFILTER_LEVEL_1 ((uint8_t)0x01)
#define LIS302DL_HIGHPASSFILTER_LEVEL_2 ((uint8_t)0x02)
#define LIS302DL_HIGHPASSFILTER_LEVEL_3 ((uint8_t)0x03)
typedef enum {
TM_LIS302DL_LIS3DSH_Device_Error,
TM_LIS302DL_LIS3DSH_Device_LIS302DL,
TM_LIS302DL_LIS3DSH_Device_LIS3DSH
} TM_LIS302DL_LIS3DSH_Device_t;
typedef enum {
/* LIS3DSH */
TM_LIS3DSH_Sensitivity_2G,
TM_LIS3DSH_Sensitivity_4G,
TM_LIS3DSH_Sensitivity_6G,
TM_LIS3DSH_Sensitivity_8G,
TM_LIS3DSH_Sensitivity_16G,
/* LIS302DL */
TM_LIS302DL_Sensitivity_2_3G,
TM_LIS302DL_Sensitivity_9_2G
} TM_LIS302DL_LIS3DSH_Sensitivity_t;
typedef enum {
/* LIS3DSH */
TM_LIS3DSH_Filter_800Hz,
TM_LIS3DSH_Filter_400Hz,
TM_LIS3DSH_Filter_200Hz,
TM_LIS3DSH_Filter_50Hz,
/* LIS302DL */
TM_LIS302DL_Filter_2Hz,
TM_LIS302DL_Filter_1Hz,
TM_LIS302DL_Filter_500mHz,
TM_LIS302DL_Filter_250mHz
} TM_LIS302DL_LIS3DSH_Filter_t;
#endif
ST-LINK, как последовательный порт не видится в системе, пришлось искать и задействовать дополнительный порт. Подошел USART3 из свободных на гребенке Discovery пинов. Он и использован в тексте выше. Для организации порта в системе Armbian был воткнут обычный переходник USB-UART, который сразу обозначился в устройствах:
ttyUSB0
[277498.708244] usb 1-1.2: New USB device found, idVendor=067b, idProduct=2303, bcdDevice= 3.00
[277498.711135] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[277498.718480] usb 1-1.2: Product: USB-Serial Controller
[277498.723562] usb 1-1.2: Manufacturer: Prolific Technology Inc.
[277498.783420] usb 1-1.2: pl2303 converter now attached to ttyUSB0
Физически для подключения потребовался всего лишь один провод, т.к. земля у них уще общая через USB/ST-Link, остались только данные. Надо соединить вход RX на USB-UART переходнике и вывод PD8 (это TX) на STM32F4 Discovery. И согласно написанной программе видим вывод в терминале Visual Studio Code каждую секунду:
![](https://habrastorage.org/getpro/habr/upload_files/104/522/88a/10452288a9dfd1736b28555983dcb9df.png)
Ну и лампочки тоже загораются, иммитируя "пузырек" строительного уровня при наклонах платы.
![](https://habrastorage.org/getpro/habr/upload_files/ee9/723/645/ee9723645322976fe0774d729820a3ca.png)
Как резюме, получилась компактная, условно переносная система для разработки/доработки с возможностью, как локального, так и удаленного доступа в среде Visual Studio Code на том что она поддерживает.
Буду рад, если кому то пригодится.