I wrote [Correct the display of the 16x8 LED matrix] in another article. Using that LED Matrix, if you send a message display request from Linux, we will create an Arduino that scrolls the message and returns a response. The point of this article is "Asynchronous (strictly speaking, pseudo-asynchronous) request reception and message display on Arduino".
What to use.
usb 1-12: new full-speed USB device number 3 using xhci_hcd
usb 1-12: New USB device found, idVendor=0403, idProduct=6001
usb 1-12: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-12: Product: FT232R USB UART
usb 1-12: Manufacturer: FTDI
usb 1-12: SerialNumber: ALxxxxxx
usbcore: registered new interface driver usbserial_generic
usbserial: USB Serial support registered for generic
usbcore: registered new interface driver ftdi_sio
usbserial: USB Serial support registered for FTDI USB Serial Device
ftdi_sio 1-12:1.0: FTDI USB Serial Device converter detected
usb 1-12: Detected FT232RL
usb 1-12: FTDI USB Serial Device converter now attached to ttyUSB0
usb 1-6: new full-speed USB device number 4 using xhci_hcd
usb 1-6: New USB device found, idVendor=2341, idProduct=0058
usb 1-6: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-6: Product: Arduino Nano Every
usb 1-6: Manufacturer: Arduino LLC
usb 1-6: SerialNumber: FAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
cdc_acm 1-6:1.0: ttyACM0: USB ACM device
usbcore: registered new interface driver cdc_acm
cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
serial_test.ino
#include <Wire.h>
void setup()
{
Wire.begin();
Serial.begin(115200);
Serial.println("serial test");
}
void loop()
{
String str;
if(Serial.available()>0) {
str = Serial.readString();
Serial.println("[" + str + "]");
}
}
screen Operation check with
$ screen /dev/ttyUSB0 115200
serial test
hogehoge
[hogehoge]
When you pass a request from Linux to an arduino, it will return a response as the content is executed. For the time being, implement the following functions as a starting point. Data is passed in JSON.
function | request | response |
---|---|---|
Scroll display of text | {"textscr":"hello world !"} | {"status":200, "msg":"..."} |
Arduino health check | {"status":{}} | {"status":200, "msg":"..."} |
Below is an excerpt of the main Arduino code
test.ino
int sendResponse(int status, String msg) {
StaticJsonDocument<100> response;
response["status"] = status;
response["msg"] = msg;
serializeJson(response, Serial);
Serial.println();
}
int ExecCmd(String cmd, JsonVariant value) {
if(cmd == "status") {
sendResponse(100, "alive");
} else if(cmd == "textscr") {
JsonObject obj2 = value.as<JsonObject>();
String msg = obj2["msg"];
int size = obj2["size"];
if(size < 1 || size > 2) {
size = 1;
}
matrix.setTextSize(size);
matrix.setTextWrap(false);
matrix.setTextColor(LED_ON);
int l = msg.length();
for(int16_t x = 7; x >= -6*l; x--) {
matrix.clear();
matrix.setCursor(x,0);
matrix.print(msg);
matrix.writeDisplay();
delay(100);
}
sendResponse(200, "msg:" + msg + ",size:" + (String)size);
} else { // "status" : 404
sendResponse(404, "command not found:" + cmd);
}
}
StaticJsonDocument<200> request;
void loop() {
if(Serial.available() > 0) {
DeserializationError error = deserializeJson(request, Serial);
if (error) {
sendResponse(500, error.c_str());
return;
}
JsonObject obj = request.as<JsonObject>();
for (JsonPair p : obj) {
ExecCmd((String)p.key().c_str(), p.value());
}
}
}
test.ino
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include <ArduinoJson.h>
#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif
class aitendo_KLED1608K33D_8x16matrix : public Adafruit_LEDBackpack, public Adafruit_GFX {
public:
aitendo_KLED1608K33D_8x16matrix(void);
void drawPixel(int16_t x, int16_t y, uint16_t color);
private:
};
aitendo_KLED1608K33D_8x16matrix::aitendo_KLED1608K33D_8x16matrix(void) : Adafruit_GFX(16, 8) {
}
void aitendo_KLED1608K33D_8x16matrix::drawPixel(int16_t x, int16_t y, uint16_t color) {
if ((y < 0) || (x < 0)) return;
if ((getRotation() % 2 == 0) && ((x >= 16) || (y >= 8))) return;
if ((getRotation() % 2 == 1) && ((y >= 16) || (x >= 8))) return;
// check rotation, move pixel around if necessary
switch (getRotation()) {
case 0:
if (x >= 8) {
x -= 8;
y += 8;
}
break;
case 1:
y = 16 - y - 1;
if(y >= 8) {
y -= 8;
x += 8;
}
_swap_int16_t(x, y);
break;
case 2:
x = 16 - x - 1;
y = 8 - y - 1;
if (x >= 8) {
x -= 8;
y += 8;
}
break;
case 3:
x = 8 - x - 1;
if(y >= 8) {
y -= 8;
x += 8;
}
_swap_int16_t(x, y);
break;
}
if (color) {
displaybuffer[x] |= 1 << y;
} else {
displaybuffer[x] &= ~(1 << y);
}
}
aitendo_KLED1608K33D_8x16matrix matrix = aitendo_KLED1608K33D_8x16matrix();
void setup() {
Serial.begin(115200);
Serial.println("16x8 LED Matrix");
matrix.begin(0x70);
matrix.setBrightness(5);
matrix.setRotation(0);
matrix.clear();
matrix.writeDisplay();
}
int sendResponse(int status, String msg) {
StaticJsonDocument<100> response;
response["status"] = status;
response["msg"] = msg;
serializeJson(response, Serial);
Serial.println();
}
int ExecCmd(String cmd, JsonVariant value) {
if(cmd == "status") {
sendResponse(100, "alive");
} else if(cmd == "textscr") {
JsonObject obj2 = value.as<JsonObject>();
String msg = obj2["msg"];
int size = obj2["size"];
if(size < 1 || size > 2) {
size = 1;
}
matrix.setTextSize(size);
matrix.setTextWrap(false);
matrix.setTextColor(LED_ON);
int l = msg.length();
for(int16_t x = 7; x >= -6*l; x--) {
matrix.clear();
matrix.setCursor(x,0);
matrix.print(msg);
matrix.writeDisplay();
delay(100);
}
sendResponse(200, "msg:" + msg + ",size:" + (String)size);
} else { // "status" : 404
sendResponse(404, "command not found:" + cmd);
}
}
StaticJsonDocument<200> request;
void loop() {
if(Serial.available() > 0) {
DeserializationError error = deserializeJson(request, Serial);
if (error) {
sendResponse(500, error.c_str());
return;
}
JsonObject obj = request.as<JsonObject>();
for (JsonPair p : obj) {
ExecCmd((String)p.key().c_str(), p.value());
}
}
}
Below Linux side code First of all, I finally checked the USB connection between the Linux box and Arduino, but here I am using Ubuntu, which is the test environment Windows Linux Subsystem.
test.py
import serial
import time
import json
s = serial.Serial()
s.port = "/dev/ttyS4" #Connect to COM4 arduino
s.baudrate = 115200
s.timeout = 1
s.dtr = False #Prevents arduino from being reset when connecting serially
s.open()
time.sleep(1) #Wait for feelings
s.reset_input_buffer() #Cleaning the receive buffer of the serial port
def request(data):
print("request:", data)
s.write(json.dumps(data).encode()) # encode()Must be a binary string in
while True: #Wait for the response to come back
msg = s.readline().decode()
if(len(msg) > 0):
print("response:", msg)
break
request({"textscr" : {"msg":"Hello World !!!"}})
request({"status" : {}})
Execution result It is correct that the response of "textscr" request is {"status": 200, "msg": Hello World !!! "}, but it seems that the first character was spilled at this time. Consider error handling. Is it?
As you can see from the execution result, the response is not returned until the textscr display is completed, and another command is not accepted while waiting. It's a little difficult to handle. The text scroll on the Arduino side uses a for loop to wait 100ms for displaying characters and then shift it horizontally by 1 dot, so every time the text is shifted by 1 dot, the process is returned to loop () and the next scroll is performed. Allows you to receive commands. In addition, if a new textscr is sent during scrolling, the existing display execution should be discarded and overwritten.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include <ArduinoJson.h>
#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif
class aitendo_KLED1608K33D_8x16matrix : public Adafruit_LEDBackpack, public Adafruit_GFX {
public:
aitendo_KLED1608K33D_8x16matrix(void);
void drawPixel(int16_t x, int16_t y, uint16_t color);
private:
};
aitendo_KLED1608K33D_8x16matrix::aitendo_KLED1608K33D_8x16matrix(void) : Adafruit_GFX(16, 8) {
}
void aitendo_KLED1608K33D_8x16matrix::drawPixel(int16_t x, int16_t y, uint16_t color) {
if ((y < 0) || (x < 0)) return;
if ((getRotation() % 2 == 0) && ((x >= 16) || (y >= 8))) return;
if ((getRotation() % 2 == 1) && ((y >= 16) || (x >= 8))) return;
// check rotation, move pixel around if necessary
switch (getRotation()) {
case 0:
if (x >= 8) {
x -= 8;
y += 8;
}
break;
case 1:
y = 16 - y - 1;
if(y >= 8) {
y -= 8;
x += 8;
}
_swap_int16_t(x, y);
break;
case 2:
x = 16 - x - 1;
y = 8 - y - 1;
if (x >= 8) {
x -= 8;
y += 8;
}
break;
case 3:
x = 8 - x - 1;
if(y >= 8) {
y -= 8;
x += 8;
}
_swap_int16_t(x, y);
break;
}
if (color) {
displaybuffer[x] |= 1 << y;
} else {
displaybuffer[x] &= ~(1 << y);
}
}
aitendo_KLED1608K33D_8x16matrix matrix = aitendo_KLED1608K33D_8x16matrix();
#define DR_UNRELATED 0
#define DR_STOP 1
#define DR_OVERRIDE 2
#define DR_NEW 10
#define DR_CONTINUE 11
typedef struct {
const char *cmd;
int (*drawfunc)(int, JsonVariant);
} Cmds;
Cmds *drawing;
void setup() {
drawing = NULL;
Serial.begin(115200);
Serial.println("16x8 LED Matrix");
matrix.begin(0x70);
matrix.setBrightness(5);
matrix.setRotation(0);
matrix.clear();
matrix.writeDisplay();
}
int sendResponse(int status, String msg) {
StaticJsonDocument<100> response;
response["status"] = status;
response["msg"] = msg;
serializeJson(response, Serial);
Serial.println();
}
int cmdStatus(int stat, JsonVariant value) {
sendResponse((drawing ? 102 : 100), (drawing ? "drawing" : "free time"));
return(DR_UNRELATED);
}
int cmdTextscr(int stat, JsonVariant value) {
static String sMsg;
static int16_t l, x;
if(stat == DR_NEW) {
JsonObject obj = value.as<JsonObject>();
String msg = obj["msg"];
sMsg = msg;
l = msg.length();
x = 7;
matrix.setTextSize(1);
matrix.setTextWrap(false);
matrix.setTextColor(LED_ON);
}
if(x >= -6*l) {
matrix.clear();
matrix.setCursor(x,0);
matrix.print(sMsg);
matrix.writeDisplay();
delay(100);
x--;
} else {
//sendResponse(200, "finish textscr msg:" + msg);
return(DR_STOP);
}
if(stat == DR_NEW) {
sendResponse(200, "textscr msg:" + sMsg);
return(DR_OVERRIDE);
}
return(DR_UNRELATED);
}
Cmds cmds[] = {
{"status", cmdStatus},
{"textscr", cmdTextscr},
{"", NULL},
};
StaticJsonDocument<200> request;
JsonVariant JVNULL = JsonVariant();
void loop() {
if(Serial.available() > 0) {
DeserializationError error = deserializeJson(request, Serial);
if (error) {
sendResponse(500, error.c_str());
return;
}
JsonObject obj = request.as<JsonObject>();
for (JsonPair p : obj) {
String cmd = (String)p.key().c_str();
int i;
for(i = 0; cmds[i].cmd != ""; i++) {
if((String)cmds[i].cmd == cmd) {
int r = (*cmds[i].drawfunc)(DR_NEW, p.value());
switch(r) {
case DR_OVERRIDE:
drawing = &cmds[i];
break;
case DR_STOP:
drawing = NULL;
break;
}
break;
}
}
if(cmds[i].cmd == "") {
sendResponse(404, "command not found:" + cmd);
}
}
} else {
if(drawing) {
int r = drawing->drawfunc(DR_CONTINUE, JVNULL);
switch(r) {
case DR_STOP:
drawing = NULL;
break;
}
}
}
}
Excerpt below.
test2.ino
#define DR_UNRELATED 0 //Returned if cmdxxxx processing does not overwrite other running requests
#define DR_STOP 1 //Returned when cmdxxxx processing is complete
#define DR_OVERRIDE 2 //Returned when cmdxxxx processing overwrites another running request
#define DR_NEW 10 //Pass to cmdxxxx when a new request comes in
#define DR_CONTINUE 11 //Passed when calling cmdxxxx in subsequent processing
int cmdStatus(int stat, JsonVariant value) {
...
}
int cmdTextscr(int stat, JsonVariant value) {
...
}
typedef struct {
const char *cmd;
int (*drawfunc)(int, JsonVariant);
} Cmds;
Cmds *drawing;
Cmds cmds[] = {
{"status", cmdStatus},
{"textscr", cmdTextscr},
{"", NULL},
};
void loop() {
if(Serial.available() > 0) {
...
for (JsonPair p : obj) {
String cmd = (String)p.key().c_str(); //Store the received request string in cmd
int i;
for(i = 0; cmds[i].cmd != ""; i++) { //cmds that match cmd[]Loop to find and call members
if((String)cmds[i].cmd == cmd) {
int r = (*cmds[i].drawfunc)(DR_NEW, p.value());
switch(r) {
case DR_OVERRIDE:
drawing = &cmds[i]; //Remember the function you just called because it will continue to process.
break;
case DR_STOP:
drawing = NULL; //Since the function just called is already completed, there is no function to continue processing
break;
}
break;
}
}
...
}
} else {
if(drawing) { //If there is a request function running, call that function
int r = drawing->drawfunc(DR_CONTINUE, JVNULL);
...
}
}
}
Now, on the Linux side, you don't have to wait after sending a request, and you can send a request at any time.
test2.py
import serial
import time
import datetime
import json
s = serial.Serial()
s.port = "/dev/ttyS4"
s.baudrate = 115200
s.timeout = 1
s.dtr = False #Prevents arduino from being reset when connecting serially
s.open()
time.sleep(1) #Wait for feelings
s.reset_input_buffer() #Cleaning the receive buffer of the serial port
def request(data):
print(datetime.datetime.now().strftime('%H:%M:%S'), "request:", data)
s.write(json.dumps(data).encode()) # encode()Must be a binary string in
while True: #Wait for the response to come back
msg = s.readline().decode()
if(len(msg) > 0):
print(datetime.datetime.now().strftime('%H:%M:%S' ), "response:", msg)
break
request({"textscr" : {"msg":"Hello World !!!"}})
time.sleep(3)
request({"status" : {}})
time.sleep(1)
request({"textscr" : {"msg":"\\(^_^)/"}})
time.sleep(6)
request({"status" : {}})
test2.py execution result
ubuntu:~$ python3 test.py
00:25:39 request: {'textscr': {'msg': 'Hello World !!!'}}
00:25:39 response: {"status":200,"msg":"textscr msg:Hello World !!!"}
00:25:42 request: {'status': {}}
00:25:42 response: {"status":102,"msg":"drawing"}
00:25:43 request: {'textscr': {'msg': '\\(^_^)/'}}
00:25:43 response: {"status":200,"msg":"textscr msg:\\(^_^)/"}
00:25:49 request: {'status': {}}
00:25:49 response: {"status":100,"msg":"free time"}
The time is added before request and response for easy understanding. It will return a response as soon as you start scrolling, and if you send another textscr request while scrolling, it will execute the new request. Perfect. end.
Recommended Posts