Недавно я писал статью про модуль USB GPIO EXTENDER компании «Открытые разработки», где было отмечено, что прошивка этой версии модуля не поддерживает формат AT-команд для /ppp-client at-chat Роутер ОС Микротик, поэтому отправить команды модулю можно, а получить ответ нельзя. Это даёт возможность управления выходными линиями GPIO, но не позволяет использовать входные линии. Хочу немного дополнить предыдущую статью описанием другой, программируемой версии модуля USB GPIO EXTENDER T (TOIC), выполненной на темно-синей плате в отличии от непрограммируемой версии (а может такая попалась мне), которая содержит встроенный язык программирования TOIC, а компания поставляет среду разработки и прошивки скрипта в модуль для этого языка. Версия с TOIC поставляется с демонстрационными скриптами Demo1 и Demo2 доступными на сайте разработчика. Demo 1 практически аналогичен прошивке непрограммируемого USB GPIO EXTENDER (5 линий OUT, 4 линии IN). Demo 2 (под спойлером) поддерживает не только GPIO линии ввода/вывода, но и ADC, PWM и SPI.
Данный скрипт следующим образом настраивает пины устройства:
и поддерживает следующие команды:
На сайте TOIC, также разработанным Open Development, есть весьма урезанное описание среды разработки и языка программирования и данный пример скрипта, а также скрипты для других устройств компании. Но без описаний переменных скрипта и значений регистров модуля до конца понять всё не представляется возможным.
Я немного модифицировал скрипт для возможности полноценного использования устройства в Микротик Роутер ОС, добавив в возвраты команд \r\nOK\r\n, что позволяет интерфейсу /ppp-client при at-chat «увидеть» OK от устройства и вернуть буфер в Роутер ОС.
На базе модифицированной прошивки я написал также новую функцию OpnDevExtTOIC version 1, полностью поддерживающую в Микротик эту прошивку и позволяющую получать все ответы от устройства, в том числе состояние линии IN, значения PWM и ADC и ответы SPI-интерфейса, добавив ещё инструкцию (case 'L ') для управления LED-индикатором, которую также использую как тестовую команду «AT» для проверки связи с модулем.
Функция имеет более удобные названия команд ($1 параметр) для модуля, они приведены в коде функции.
Надо отметить, что в
TOIC-версия модуля отображается как «TOIC-F0-GE», а не как «USB GPIO EXTENDER», что актуально для непрограммируемой версии. Не знаю одинаково ли это для всех экземпляров TOIC-версии, или имя в firmware может меняться взависимости от партии или прошивки TOIC. Если у Вас отображается другое имя, то его нужно заменить в коде моей функции (переменная USBresLinuxName), иначе функция не найдет модуль.
Пригодится кому-либо или нет — вопрос, у меня работает. Дальше ковырять скрипт я не стал, так как не имею навыков программирования на TOIC, представляющим собой какую-то урезанную смесь Питона и Си++ (возможно тем, кто свободно программирует на этих языках и посмотрит исходники скриптов выше, это не составит никакого труда).
Что касается версии Demo 1 скрипта, то её я тоже модифицировал аналогичным образом:
Настроенные пины:
Демонстрационный код 1 поддерживает следующие команды:
И под него соответствующая функция для Микротик
Тут следует отметить, что как и в непрограммируемой версии модуля, так и данная прошивка TOIC-версии позволяет считывать состояние IN-линий, но не позволяет считывать состояние линий OUT. Функциональность линий назначается скриптом один раз при запуске скрипта и я не знаю может ли меняться, но возможно считывать состояние линий можно только, когда они назначены, как входные. Тогда, чтобы считать состояние OUT-линии, её нужно временно переназначить, а это может изменить её состояние. Это уже вопрос к разработчику — можно ли вообще считывать состояние OUT-линий в его устройстве. Если аппаратно нет — это существенный минус.
Мои версии модифицированных прошивок USB GPIO EXTENDER T (TOIC) от 08.10.2024 г. под Микротик и коды моих функций OpnDevExtTOIC для Роутер ОС можно скачать по ссылке на GitHub.
С появлением модификации прошивок можно адекватно использовать устройство с Микротик, как отправляя команды на выполнение, так и получая ответы. В том числе можно считывать состояние IN-линий и др. в зависимости от версии прошивки.
Например, для версии прошивки 1 с использванием функции версии 2 на
получим в Терминале:
Значит можно смело использовать команды и возвраты ответов от модуля в своих скриптах Микротик. Например, присоединив к USB GPIO EXTENDER T модуль радиоприемника 433 Мгц можно получить USB-радиопульт с управлением Микротик Роутер ОС для любых нужд, подобно как я это делал для Serial1-порта RBM33G. Теперь это стало возможным для любой RB Микротик с USB-портом.
Надо отметить, что без навыков программирования в Си++ или Питоне прошивки всё равно сложно модифицировать переопределяя пины под конкретные задачи. Разработчику следовало бы написать универсальную прошивку, в которой пользователь просто бы мог назначить пины устройства в пользовательских переменных без каких-либо правок исполняемого кода.
Так что пока за устройство поставим «5-», а за софт к нему «4».
Скрипт прошивки USB GPIO EXTENDER T Demo 2
#define FW_STR "2.1 02 Aug 2021"
/*
IO1 - PA4 - ADC
IO2 - PA2 - OUTPUT
IO3 - PA0 - SPI CS
IO4 - PA6 - SPI MISO
IO5 - PA7 - SPI MOSI
IO6 - PA3 - INPUT
IO7 - PA1 - PWM OUTPUT
IO8 - NC
IO9 - PB1 - PWM INPUT
IOx - PA5 - SPI SCK
LED - PF0
*/
var _msg() {
var c = stoi(&MSG.RX, 'c');
memcpy(SYS.RAM, &MSG.RX, MSG.SIZE);
switch (c) {
case 'S':
PA2.VALUE = 1;
break;
case 'R':
PA2.VALUE = 0;
break;
case 'A':
sprintf(&UART0.TX, "A%d\n", PA4.VALUE);
break;
case 'K':
PA0.VALUE = 0;
memcpy(&SPI.DR, SYS.RAM+1, MSG.SIZE-1);
sprintf(&UART0.TX, "K%d\n", SYS.RAM+1);
PA0.VALUE = 1;
break;
case 'G':
sprintf(&UART0.TX, "G%d\n", PA3.VALUE);
break;
case 'F':
sprintf(&UART0.TX, "F%d\n", PB1.VALUE);
break;
case 'I':
sprintf(&UART0.TX, "I%s\n", FW_STR);
break;
default:
break;
}
}
var io_setup() {
// IO1 - PA4 - ADC
PA4.MODE = GPIO_MODE_ADC;
// IO2 - PA2 - OUTPUT
PA2.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// IO6 - PA3 - INPUT
PA3.MODE = GPIO_MODE_INPUT | GPIO_PULL_DOWN;
// IO7 - PA1 - PWM OUTPUT
PA1.MODE = GPIO_MODE_OPWM|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// IO9 - PB1 - PWM INPUT
PB1.MODE = GPIO_MEASURE_FREQ|GPIO_MODE_IPWM|GPIO_INIT_LOW;
// LED - PF0
PF0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_HIGH|GPIO_OTYPE_PP;
// IO3 - PA0 - SPI CS
// IO4 - PA6 - SPI MISO
// IO5 - PA7 - SPI MOSI
// IOx - PA5 - SPI SCK
PA0.MODE = GPIO_INIT_LOW|GPIO_OTYPE_PP|GPIO_TYPE_SOFTWARE|GPIO_MODE_OUTPUT;
SPI.SETUP = SPI_SETUP_POLARITY_LOW|SPI_SETUP_EDGE_LEADING|1000/*kBod*/;
SPI.EN = 1;
PA1.PWM = 128;
TIM2.FREQ = TIM3.FREQ = 2;
TIM2.EN = TIM3.EN = 1;
UART0.CFG = UART_MODE_PLAIN | UART_CONFIG_START;
__enable_irq();
}
var main() {
io_setup();
while (1) {};
return 0;
}
Данный скрипт следующим образом настраивает пины устройства:
IO1 — PA4 — АЦП (0-3.3В)
IO2 — PA2 — Дискретный выход
IO3 — PA0 — SPI CS
IO4 — PA6 — SPI MISO
IO5 — PA7 — SPI MOSI
IO6 — PA3 — Дискретный вход (до 3.3В)
IO7 — PA1 — PWM выход с фиксированной частотой
IO8 — не используется
IO9 — PB1 — PWM вход (до 3.3В)
IOx — PA5 — SPI SCK
и поддерживает следующие команды:
S — Установить выход (2) в 1,
R — Установить выход (2) в 0,
G — Считать данные со входа (6),
A — Считать данные с АЦП (1),
K — Отправить в SPI данные и прочитать ответ,
F — Считать данные с определителя частоты PWM (9),
I — Считать информацию о версии скрипта.
На сайте TOIC, также разработанным Open Development, есть весьма урезанное описание среды разработки и языка программирования и данный пример скрипта, а также скрипты для других устройств компании. Но без описаний переменных скрипта и значений регистров модуля до конца понять всё не представляется возможным.
Я немного модифицировал скрипт для возможности полноценного использования устройства в Микротик Роутер ОС, добавив в возвраты команд \r\nOK\r\n, что позволяет интерфейсу /ppp-client при at-chat «увидеть» OK от устройства и вернуть буфер в Роутер ОС.
Скрипт прошивки USB GPIO EXTENDER T Demo 2 (modify Sertik 08/10/2024)
#define FW_STR "2.1 for Mikrotik modify Sertik"
/*
IO1 - PA4 - ADC
IO2 - PA2 - OUTPUT
IO3 - PA0 - SPI CS
IO4 - PA6 - SPI MISO
IO5 - PA7 - SPI MOSI
IO6 - PA3 - INPUT
IO7 - PA1 - PWM OUTPUT
IO8 - NC
IO9 - PB1 - PWM INPUT
IOx - PA5 - SPI SCK
LED - PF0
*/
var _msg() {
var c = stoi(&MSG.RX, 'c');
memcpy(SYS.RAM, &MSG.RX, MSG.SIZE);
switch (c) {
case 'S':
PA2.VALUE = 1;
sprintf(&UART0.TX, "%d\r\nOK\r\n", PA2.VALUE);
break;
case 'R':
PA2.VALUE = 0;
sprintf(&UART0.TX, "%d\r\nOK\r\n", PA2.VALUE);
break;
case 'A':
sprintf(&UART0.TX, "%d\r\nOK\r\n", PA4.VALUE);
break;
case 'K':
PA0.VALUE = 0;
memcpy(&SPI.DR, SYS.RAM+1, MSG.SIZE-1);
sprintf(&UART0.TX, "%d\r\nOK\r\n", SYS.RAM+1);
PA0.VALUE = 1;
break;
case 'G':
sprintf(&UART0.TX, "%d\r\nOK\r\n", PA3.VALUE);
break;
case 'F':
sprintf(&UART0.TX, "%d\r\nOK\r\n", PB1.VALUE);
break;
case 'L':
PF0.VALUE = 1;
sprintf(&UART0.TX, "OK\r\n");
delay (1000)
PF0.VALUE = 0;
break;
case 'I':
sprintf(&UART0.TX, "%s\r\nOK\r\n", FW_STR);
break;
default:
break;
}
}
var io_setup() {
// IO1 - PA4 - ADC
PA4.MODE = GPIO_MODE_ADC;
// IO2 - PA2 - OUTPUT
PA2.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// IO6 - PA3 - INPUT
PA3.MODE = GPIO_MODE_INPUT | GPIO_PULL_DOWN;
// IO7 - PA1 - PWM OUTPUT
PA1.MODE = GPIO_MODE_OPWM|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// IO9 - PB1 - PWM INPUT
PB1.MODE = GPIO_MEASURE_FREQ|GPIO_MODE_IPWM|GPIO_INIT_LOW;
// LED - PF0
PF0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// IO3 - PA0 - SPI CS
// IO4 - PA6 - SPI MISO
// IO5 - PA7 - SPI MOSI
// IOx - PA5 - SPI SCK
PA0.MODE = GPIO_INIT_LOW|GPIO_OTYPE_PP|GPIO_TYPE_SOFTWARE|GPIO_MODE_OUTPUT;
SPI.SETUP = SPI_SETUP_POLARITY_LOW|SPI_SETUP_EDGE_LEADING|1000/*kBod*/;
SPI.EN = 1;
PA1.PWM = 128;
TIM2.FREQ = TIM3.FREQ = 2;
TIM2.EN = TIM3.EN = 1;
UART0.CFG = UART_MODE_PLAIN | UART_CONFIG_START;
__enable_irq();
}
var main() {
io_setup();
while (1) {};
return 0;
На базе модифицированной прошивки я написал также новую функцию OpnDevExtTOIC version 1, полностью поддерживающую в Микротик эту прошивку и позволяющую получать все ответы от устройства, в том числе состояние линии IN, значения PWM и ADC и ответы SPI-интерфейса, добавив ещё инструкцию (case 'L ') для управления LED-индикатором, которую также использую как тестовую команду «AT» для проверки связи с модулем.
fOpnDevExtTOIC version 1
#----------------------------------------------------------------
# Functon support OPEN Dev USB GPIO EXTENDER TOIC
# by Sertik version 1.0 08/10/2024
#----------------------------------------------------------------
# check in ROS 6.49.10
# usage:
# $fOpenDevExtTOIC list - output a list of function commands for the module to the terminal and log
# :put [$fOpenDevExtTOIC logic] - return the set value of the operating logic
# $fOpenDevExtTOIC logic [true/false] - set forward/reverse operating logic
# $fOpenDevExtTOIC OutOn - turn on OUT (2)
# $fOpenDevExtTOIC OutOff - turn off OUT (2)
# $fOpenDevExtTOIC In - read IN (6)
# $fOpenDevExtTOIC SPI xxxxx - send xxxxx to SPI and return module`s answer
# $fOpenDevExtTOIC PWM - read PWM-OUT
# $fOpenDevExtTOIC AT - check connect and LED
# $fOpenDevExtTOIC ATI - request information about the firmware version
# module commands:
# -----------------------------------------------------------------------
# S sets the output (2) to 1.
# R sets the output (2) to 0.
# G read the current value of input (6)
# A - read ADC
# K - send data to SPI and take answer
# F - read data in PWM
# L - test connect
# I request information about the firmware version.
:global fOpenDevExtTOIC do={
:local version "1.0 08/10/2024"
:local ModuleType "USB GPIO EXTENDER TOIC"
:local portTypeUSB "usb"
:global OpenDevModuleType $ModuleType
:local USBresLinuxName "TOIC-F0-GE"
:global OpenDevReleLogic
:local UsbGpioExtFlag false
:local portUSB;
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local ArrayCom {
"logic"="X"
"OutOn"="S"
"OutOff"="R"
"In"="G"
"ADC"="A"
"PWM"="F"
"SPI"="K"
"ATI"="I"
"AT"="L"
}
:if ([:len $1]=0) do={:return "Еrror: no set name command"}
# help
:if ($1="help") do={
:put ""; :put "---- Function support for $OpenDevModuleType ----"
:put " version $version"
:put " usage:"
:terminal style "syntax-meta"
:put "$0 help"
:put "$0 list"
:put "$0 logic [true/false]"
:put "$0 reset"
:put "$0 SetOut XXYXY [0-1]"
:put "$0 ReadIn"
:put "$0 In [6-9]"
:put "$0 OutOn X [1-5]"
:put "$0 OutOff Y [1-5]"
:put "$0 AT"
:put "$0 ATI"
:terminal style none
:return []}
# list
:if ($1="list") do={
:put ""; :put "<---- Supported $OpenDevModuleType commands ---->"
:foreach k,v in $ArrayCom do={:put (" "."$k")}
:return []}
# logic
:if ($1="logic") do={
:if (($2="true") or ($2="false")) do={
:if ($2="true") do={:set OpenDevReleLogic true}
:if ($2="false") do={:set OpenDevReleLogic false}
:return OK
} else={:if ([:len $2]=0) do={
:if (($OpenDevReleLogic=true) or ($OpenDevReleLogic=false)) do={
:return $OpenDevReleLogic} else={:return "logic is not specified or incorrect"}
}
:return ("Error"." $0"." $1")}
}
local UsbGpioExtName
:do {
:set UsbGpioExtName [/system resource usb get [/system resource usb find name~$USBresLinuxName] name]
} on-error={}
:if ($UsbGpioExtName=$USBresLinuxName) do={:set UsbGpioExtFlag true}
:if ($UsbGpioExtFlag=false) do={:return "Error: Not find $OpenDevModuleType module in ROS system. Please, check device in USB port"}
:global ODUsbGPIOExtPort
:local NewPort
:local NowPort $ODUsbGPIOExtPort; # сохранить текущий порт
:do {
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
} on-error={}
:set NewPort $portUSB
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for $OpenDevModuleType module, port inactive or busy. Please, check /port"}
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set ODUsbGPIOExtPort $NewPort} else={:set ODUsbGPIOExtPort $NowPort}
:if ([:len $ODUsbGPIOExtPort]=0) do={:set ODUsbGPIOExtPort $NewPort}
:local consoleFlagOff false
if ([:len [/system console find port=$ODUsbGPIOExtPort and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$ODUsbGPIOExtPort] disable=yes
}
do {
/port set [/port find name=$ODUsbGPIOExtPort] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $ODUsbGPIOExtPort. Function $0 d`not work"}
# main function`s code
:local cmd ($ArrayCom->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command $OpenDevModuleType: $1 $2"
:log warning "Execute command $OpenDevModuleType: $1 $2"
:if ([:len $OpenDevReleLogic]=0) do={:set OpenDevReleLogic true}
:if (($1="OutOn") && ($OpenDevReleLogic=false)) do={:set cmd ($ArrayCom->"OutOff")}
:if (($1="OutOff") && ($OpenDevReleLogic=false)) do={:set cmd ($ArrayCom->"OutOn")}
:if ([/interface ppp-client find name=$PppclientName]) do={/interface ppp-client remove [/interface ppp-client find name=$PppclientName]}
# :put ("Send module "."$USBresLinuxName "."command: "."$cmd"."$2")
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$ODUsbGPIOExtPort null-modem=yes disabled=yes
:delay 1s
:local GPIOanswer [/interface ppp-client at-chat $PppclientName input=("$cmd"."$2") as-value]
/interface ppp-client remove [/interface ppp-client find name=$PppclientName]
:if ($consoleFlagOff) do={
:do {/system console set [/system console find port=$ODUsbGPIOExtPort] disable=no} on-error={}
}
# end
:return $GPIOanswer
}
Функция имеет более удобные названия команд ($1 параметр) для модуля, они приведены в коде функции.
Надо отметить, что в
/system resource usb print
TOIC-версия модуля отображается как «TOIC-F0-GE», а не как «USB GPIO EXTENDER», что актуально для непрограммируемой версии. Не знаю одинаково ли это для всех экземпляров TOIC-версии, или имя в firmware может меняться взависимости от партии или прошивки TOIC. Если у Вас отображается другое имя, то его нужно заменить в коде моей функции (переменная USBresLinuxName), иначе функция не найдет модуль.
Пригодится кому-либо или нет — вопрос, у меня работает. Дальше ковырять скрипт я не стал, так как не имею навыков программирования на TOIC, представляющим собой какую-то урезанную смесь Питона и Си++ (возможно тем, кто свободно программирует на этих языках и посмотрит исходники скриптов выше, это не составит никакого труда).
Что касается версии Demo 1 скрипта, то её я тоже модифицировал аналогичным образом:
Скрипт прошивки USB GPIO EXTENDER T Demo 1 (modify Sertik 08/10/2024)
#define FW_STR «1.1 for Mikrotik modify Sertik»
/*
IO1 — PA4 — OUTPUT
IO2 — PA2 — OUTPUT
IO3 — PA0 — OUTPUT
IO4 — PA7 — OUTPUT
IO5 — PA6 — OUTPUT
IO6 — PA3 — INPUT
IO7 — PA1 — INPUT
IO8 — PA13 — INPUT
IO9 — PB1 — INPUT
IO10 — BOOT
IOx — PA5 — INPUT PULLED DONW R10K
LED — PF0
*/
var GPIO_OUT = [PA4.VALUE, PA2.VALUE, PA0.VALUE, PA7.VALUE, PA6.VALUE];
var GPIO_IN = [PA3.VALUE, PA1.VALUE, PA13.VALUE, PB1.VALUE, PB1.VALUE];
var io_setup() {
// OUTPUTS
PA4.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA2.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA7.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA6.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// INPUTS
PA3.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA1.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA13.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PB1.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA5.MODE = GPIO_MODE_INPUT | GPIO_PULL_DOWN; // unused
// LED — PF0
PF0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// UART
UART0.CFG = UART_MODE_PLAIN | UART_CONFIG_START;
__enable_irq();
}
var _msg() {
if(MSG.PORT == 4) {
memcpy(&RING.BUF, &MSG.RX, MSG.SIZE);
}
}
var main() {
var c;
var pstate;
var p_index;
var p;
io_setup();
if((RING.ALLOC = 16) != 16) {
return 1;
}
SYS.WDG = 1;
while(1){
if(RING.QUEUE > 0) {
c = RING.PULL;
if (c == '~') {
pstate = 1;
p_index = 0;
} else if (pstate == 1) {
pstate = 0;
switch © {
case 'I':
sprintf(&UART0.TX, "%s\r\nOK\r\n", FW_STR);
pstate = 0;
break;
case 'L':
PF0.VALUE = 1;
sprintf(&UART0.TX, «OK\r\n»);
delay (1000);
PF0.VALUE = 0;
break;
case 'B':
sprintf(&UART0.TX, «module reseting\r\nOK\r\n»);
delay (1000);
SYS.RESET = 1;
break;
case 'A':
sprintf(&UART0.TX, "%d%d%d%d%d\r\nOK\r\n", *GPIO_IN[0], *GPIO_IN[1], *GPIO_IN[2], *GPIO_IN[3], *GPIO_IN[4]);
pstate = 0;
break;
case 'S':
case 'R':
case 'G':
case 'P':
pstate = c;
break;
default:
break;
}
} else if (pstate) {
p = c — '0';
if (pstate == 'S' || pstate == 'R') { // ~S or ~R
if (p > 0 && p < 6) {
GPIO_OUT[p — 1] = (pstate == 'S');
sprintf(&UART0.TX, "%c%d\r\nOK\r\n", pstate, p);
} else {
sprintf(&UART0.TX, «Err\r\n»);
}
pstate = 0;
} else if (pstate == 'G') { // ~G
if (p > 0 && p < 6) {
sprintf(&UART0.TX, "%d\r\nOK\r\n", *GPIO_IN[p — 1]);
} else {
sprintf(&UART0.TX, «Err\r\n»);
}
pstate = 0;
} else if (pstate == 'P') { // ~P
GPIO_OUT[p_index++] = (c == '1');
if (p_index == 5) {
sprintf(&UART0.TX, "%d%d%d%d%d\r\nOK\r\n", *GPIO_OUT[0], *GPIO_OUT[1], *GPIO_OUT[2], *GPIO_OUT[3], *GPIO_OUT[4]);
pstate = 0;
}
}
}
} else {
delay(10);
}
SYS.WDG = 42;
}
return 0;
}
/*
IO1 — PA4 — OUTPUT
IO2 — PA2 — OUTPUT
IO3 — PA0 — OUTPUT
IO4 — PA7 — OUTPUT
IO5 — PA6 — OUTPUT
IO6 — PA3 — INPUT
IO7 — PA1 — INPUT
IO8 — PA13 — INPUT
IO9 — PB1 — INPUT
IO10 — BOOT
IOx — PA5 — INPUT PULLED DONW R10K
LED — PF0
*/
var GPIO_OUT = [PA4.VALUE, PA2.VALUE, PA0.VALUE, PA7.VALUE, PA6.VALUE];
var GPIO_IN = [PA3.VALUE, PA1.VALUE, PA13.VALUE, PB1.VALUE, PB1.VALUE];
var io_setup() {
// OUTPUTS
PA4.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA2.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA7.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA6.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// INPUTS
PA3.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA1.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA13.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PB1.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA5.MODE = GPIO_MODE_INPUT | GPIO_PULL_DOWN; // unused
// LED — PF0
PF0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// UART
UART0.CFG = UART_MODE_PLAIN | UART_CONFIG_START;
__enable_irq();
}
var _msg() {
if(MSG.PORT == 4) {
memcpy(&RING.BUF, &MSG.RX, MSG.SIZE);
}
}
var main() {
var c;
var pstate;
var p_index;
var p;
io_setup();
if((RING.ALLOC = 16) != 16) {
return 1;
}
SYS.WDG = 1;
while(1){
if(RING.QUEUE > 0) {
c = RING.PULL;
if (c == '~') {
pstate = 1;
p_index = 0;
} else if (pstate == 1) {
pstate = 0;
switch © {
case 'I':
sprintf(&UART0.TX, "%s\r\nOK\r\n", FW_STR);
pstate = 0;
break;
case 'L':
PF0.VALUE = 1;
sprintf(&UART0.TX, «OK\r\n»);
delay (1000);
PF0.VALUE = 0;
break;
case 'B':
sprintf(&UART0.TX, «module reseting\r\nOK\r\n»);
delay (1000);
SYS.RESET = 1;
break;
case 'A':
sprintf(&UART0.TX, "%d%d%d%d%d\r\nOK\r\n", *GPIO_IN[0], *GPIO_IN[1], *GPIO_IN[2], *GPIO_IN[3], *GPIO_IN[4]);
pstate = 0;
break;
case 'S':
case 'R':
case 'G':
case 'P':
pstate = c;
break;
default:
break;
}
} else if (pstate) {
p = c — '0';
if (pstate == 'S' || pstate == 'R') { // ~S or ~R
if (p > 0 && p < 6) {
GPIO_OUT[p — 1] = (pstate == 'S');
sprintf(&UART0.TX, "%c%d\r\nOK\r\n", pstate, p);
} else {
sprintf(&UART0.TX, «Err\r\n»);
}
pstate = 0;
} else if (pstate == 'G') { // ~G
if (p > 0 && p < 6) {
sprintf(&UART0.TX, "%d\r\nOK\r\n", *GPIO_IN[p — 1]);
} else {
sprintf(&UART0.TX, «Err\r\n»);
}
pstate = 0;
} else if (pstate == 'P') { // ~P
GPIO_OUT[p_index++] = (c == '1');
if (p_index == 5) {
sprintf(&UART0.TX, "%d%d%d%d%d\r\nOK\r\n", *GPIO_OUT[0], *GPIO_OUT[1], *GPIO_OUT[2], *GPIO_OUT[3], *GPIO_OUT[4]);
pstate = 0;
}
}
}
} else {
delay(10);
}
SYS.WDG = 42;
}
return 0;
}
Настроенные пины:
IO1 — PA4 — выход 1
IO2 — PA2 — выход 2
IO3 — PA0 — выход 3
IO4 — PA7 — выход 4
IO5 — PA6 — выход 5
IO6 — PA3 — Вход1
IO7 — PA1 — Вход2
IO8 — PA13 — Вход3
IO9 — PB1 — Вход4
IO10 — Контакт входа в загрузчик
IOx — PA5 — Доп. вход с подтяжкой резистром 10КОм к земле
PF0 — управляемый светодиод
Демонстрационный код 1 поддерживает следующие команды:
~Sx Установка выхода в x в 1
~Rx Установка выхода в x в 0
~Gx Чтение текущего значения входа x
~A Чтение значений всех входов в виде «xxxxx». Пример ответа «~A11001»
~Pxxxxх Запись значений всех выходов в виде «xxxxx». Например «~P11001»
~B Перезагрузка модуля.
~I Запросить информация о прошивке.
Примечание: т.к. IO10 используется для перехода в загрузчик, то он не используется в программе и вместо него при ~A отображается еще раз сигнал IO9.
И под него соответствующая функция для Микротик
fOpenDevExtTOIC version 2
#----------------------------------------------------------------
# Functon support OPEN Dev USB GPIO EXTENDER T (TOIC)
# by Sertik version 2.0 08/10/2024
#----------------------------------------------------------------
# check in ROS 6.49.10
# usage:
# $fOpenDevExt list - output a list of function commands for the module to the terminal and log
# :put [f$OpenDevExt logic] - return the set value of the operating logic
# $fOpenDevExt logic [true/false] - set forward/reverse operating logic
# $fOpenDevExt reset - module reset
# $fOpenDevExt SetOut XXYXY [0-1] - setting all Out outputs, for example 00111
# $fOpenDevExt OutOn X [1-5] - “turn on” output X
# $fOpenDevExt In X [1-5] - read IN X
# $fOpenDevExt ReadIn XXYXY - read all In input
# $fOpenDevExt OutOff Y - “turn off” output Y [1-5]
# $fOpenDevExt AT - test connect
# $fOpenDevExt ATI - firmware information
# module commands:
# -----------------------------------------------------------------------
# ~Sx sets the output in x to 1.
# ~Rx sets the output at x to 0.
# ~Gx read the current value of input x.
# ~A read the values of all inputs as "xxxxx". Example response "~A11001".
# ~Pxxxxx writes the values of all outputs as “xxxxx”. For example "~P11001".
# ~B reboot the module.
# ~I request information about the firmware version.
# ~L test AT connect
:global fOpenDevExtTOIC do={
:local version "2.0 08/10/2024"
:local ModuleType "USB GPIO EXTENDER TOIC"
:local portTypeUSB "usb"
:global OpenDevModuleType $ModuleType
:local USBresLinuxName "TOIC-F0-GE"
:global OpenDevReleLogic
:local UsbGpioExtFlag false
:local portUSB;
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local ArrayCom {
"logic"="X"
"OutOn"="~S"
"OutOff"="~R"
"SetOut"="~P"
"ReadIn"="~A"
"In"="~G"
"reset"="~B"
"AT"="~L"
"ATI"="~I"
}
:if ([:len $1]=0) do={:return "Еrror: no set name command"}
# help
:if ($1="help") do={
:put ""; :put "---- Function support for $OpenDevModuleType ----"
:put " version $version"
:put " usage:"
:terminal style "syntax-meta"
:put "$0 help"
:put "$0 list"
:put "$0 logic [true/false]"
:put "$0 reset"
:put "$0 SetOut XXYXY [0-1]"
:put "$0 OutOn X [1-5]"
:put "$0 OutOff Y [1-5]"
:put "$0 ReadIn"
:put "$0 In [6-9]"
:put "$0 AT"
:put "$0 ATI"
:terminal style none
:return []}
# list
:if ($1="list") do={
:put ""; :put "<---- Supported $OpenDevModuleType commands ---->"
:foreach k,v in $ArrayCom do={:put (" "."$k")}
:return []}
# logic
:if ($1="logic") do={
:if (($2="true") or ($2="false")) do={
:if ($2="true") do={:set OpenDevReleLogic true}
:if ($2="false") do={:set OpenDevReleLogic false}
:return OK
} else={:if ([:len $2]=0) do={
:if (($OpenDevReleLogic=true) or ($OpenDevReleLogic=false)) do={
:return $OpenDevReleLogic} else={:return "logic is not specified or incorrect"}
}
:return ("Error"." $0"." $1")}
}
local UsbGpioExtName
:do {
:set UsbGpioExtName [/system resource usb get [/system resource usb find name~$USBresLinuxName] name]
} on-error={}
:if ($UsbGpioExtName=$USBresLinuxName) do={:set UsbGpioExtFlag true}
:if ($UsbGpioExtFlag=false) do={:return "Error: Not find $OpenDevModuleType module in ROS system. Please, check device in USB port"}
:global ODUsbGPIOExtPort
:local NewPort
:local NowPort $ODUsbGPIOExtPort; # сохранить текущий порт
:do {
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
} on-error={}
:set NewPort $portUSB
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for $OpenDevModuleType module, port inactive or busy. Please, check /port"}
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set ODUsbGPIOExtPort $NewPort} else={:set ODUsbGPIOExtPort $NowPort}
:if ([:len $ODUsbGPIOExtPort]=0) do={:set ODUsbGPIOExtPort $NewPort}
:local consoleFlagOff false
if ([:len [/system console find port=$ODUsbGPIOExtPort and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$ODUsbGPIOExtPort] disable=yes
}
do {
/port set [/port find name=$ODUsbGPIOExtPort] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $ODUsbGPIOExtPort. Function $0 d`not work"}
# main function`s code
:local cmd ($ArrayCom->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command $OpenDevModuleType: $1 $2"
:log warning "Execute command $OpenDevModuleType: $1 $2"
:if ((($1="OutOn") or ($1="OutOff")) and ([:len $2]!=1)) do={:return ("Error function: "."$0 "."$2 - there is incorrect data, set number in range [1-5]")}
:if (($1="SetOut") and ([:len $2]!=5)) do={:return ("Error function: "."$0 "."$2 - there is incorrect data, set the status of 5 outputs")}
:if ([:len $OpenDevReleLogic]=0) do={:set OpenDevReleLogic true}
:if (($1="OutOn") && ($OpenDevReleLogic=false)) do={:set cmd ($ArrayCom->"OutOff")}
:if (($1="OutOff") && ($OpenDevReleLogic=false)) do={:set cmd ($ArrayCom->"OutOn")}
:if (($1="SetOut") && ($OpenDevReleLogic=false)) do={
:local r ""
:for i from=0 to=([:len $2] -1) do={
:if ([:pick $2 $i (1 + $i)] = "1") do={:set r ($r . "0")} else={:set r ($r . "1")}
}
:set $2 $r
}
:if ([/interface ppp-client find name=$PppclientName]) do={/interface ppp-client remove [/interface ppp-client find name=$PppclientName]}
# :put ("Send module "."$USBresLinuxName "."command: "."$cmd"."$2")
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$ODUsbGPIOExtPort null-modem=yes disabled=yes
:local GPIOanswer [/interface ppp-client at-chat $PppclientName input=("$cmd"."$2") as-value]
:delay 1s
/interface ppp-client remove [/interface ppp-client find name=$PppclientName]
:if ($consoleFlagOff) do={
:do {/system console set [/system console find port=$ODUsbGPIOExtPort] disable=no} on-error={}
}
# end
:return $GPIOanswer
}
Тут следует отметить, что как и в непрограммируемой версии модуля, так и данная прошивка TOIC-версии позволяет считывать состояние IN-линий, но не позволяет считывать состояние линий OUT. Функциональность линий назначается скриптом один раз при запуске скрипта и я не знаю может ли меняться, но возможно считывать состояние линий можно только, когда они назначены, как входные. Тогда, чтобы считать состояние OUT-линии, её нужно временно переназначить, а это может изменить её состояние. Это уже вопрос к разработчику — можно ли вообще считывать состояние OUT-линий в его устройстве. Если аппаратно нет — это существенный минус.
Мои версии модифицированных прошивок USB GPIO EXTENDER T (TOIC) от 08.10.2024 г. под Микротик и коды моих функций OpnDevExtTOIC для Роутер ОС можно скачать по ссылке на GitHub.
С появлением модификации прошивок можно адекватно использовать устройство с Микротик, как отправляя команды на выполнение, так и получая ответы. В том числе можно считывать состояние IN-линий и др. в зависимости от версии прошивки.
Например, для версии прошивки 1 с использванием функции версии 2 на
:put ([$fOpenDevExtTOIC In 4]->"output")
получим в Терминале:
1
OK
Значит можно смело использовать команды и возвраты ответов от модуля в своих скриптах Микротик. Например, присоединив к USB GPIO EXTENDER T модуль радиоприемника 433 Мгц можно получить USB-радиопульт с управлением Микротик Роутер ОС для любых нужд, подобно как я это делал для Serial1-порта RBM33G. Теперь это стало возможным для любой RB Микротик с USB-портом.
Надо отметить, что без навыков программирования в Си++ или Питоне прошивки всё равно сложно модифицировать переопределяя пины под конкретные задачи. Разработчику следовало бы написать универсальную прошивку, в которой пользователь просто бы мог назначить пины устройства в пользовательских переменных без каких-либо правок исполняемого кода.
Так что пока за устройство поставим «5-», а за софт к нему «4».
NutsUnderline
весь этот TOIC сильно напоминает Arduino, но заявлена некая VM в микроконтроллере