ASUS Tinker Board 2S 與 MCU 的無線通訊 – Wi-Fi篇

前言

目前在嵌入式系統或MCU常見的區域型無線通訊方式,如下表大致可以區分成5大類:

通訊技術 適用距離 Bits / Sec 常見應用
NFC 數公分~數公尺 約數百K 門禁裝置
藍牙 約 <50公尺

(5.X版可達300公尺)

約數十M 辦公室自動化、居家自動化
Wi-Fi 約 < 50公尺

(透過強波可到數百公尺)

約 <數G 辦公室自動化、居家自動化、工業自動化
Zigbee 約數百公尺 約數百K 辦公室自動化、居家自動化、工業自動化
LoRa 約數公里 約數十K 工業自動化、農業自動化

WiFi在無線通訊應用中是具有高頻寬、高速、訊號涵蓋範圍廣的特性,是許多行動裝置中最常使用的通訊技術,也廣泛應用於各種網際網路的訊息傳輸應用,在物聯網的應用裡,WiFi更是扮演著非常重要的角色,透過TCP / IP協定可以實現物聯網對於各種裝置的相互通聯,本篇教學將會透過WiFi、TCP / IP、UDP三種協定服務實作教學,實現Tinker Board 2S與MCU (Linkit-7697)進行網際網路的遠端遙控應用。

Tinker Board 2S透過PCIE-M.2擴充槽安裝通訊模組,其通訊模組包含WiFi與藍牙通訊的功能,通訊模組上也提供了可以外接天線或是與強波裝置連結的端子台,這主要是為了可以強化Tinker Board 2S在進行網際網路應用的彈性,無論是當成Server或是Client都可以具有一定的傳輸效能。

Tinker Board 2S做為嵌入式系統,其安裝的作業系統Linux-Debian也充分支援各種WiFi的協定應用,透過Python程式已Socket的方式進行通訊軟體介面的建立,因此本篇教學文將會著眼在Python程式設計的相關重點說明為主。

Linkit-7697微控器本身內建有WiFi與BLE藍牙通訊的功能,在小巧的微控器晶片裡充分整合了通訊與硬體擴充電路介面的操作,是一種非常適合運用在各種物聯網應用的微控器,此外,Linkit-7697的WiFi開發資源非常完整,可以開發成Server或是Client的各種應用,因此本篇教學將會以Linkit-7697進行WiFi遠端遙控的操作。

撰寫/攝影 曾俊霖
時間 2小時 材料表 ASUS  Tinker Board 2S / 2GB-連結

MCU Linkit-7697

 

難度 中等

 

一、測試電路

本次測試電路是以Linkit-7697控制RGB三色LED模組進行測試,如下圖。

二、Linkit7697測試電路範例程式

1. MCU的WiFi UDP存取程式設計流程(簡單的輸入與輸出資料傳輸測試)

(1). 設定WiFi通訊SSID與密碼
(2). 起始化並設定UDP通訊連接埠
(3). 接收UDP資料並進行命令格式解碼
(4). 依照命令進行RGB LED的控制
(5). 透過UDP連線回傳資料回應Tinker Board 2S

2. Linkit7697測試程式片段:

LinkIt 7697 wifi
#include <LWiFi.h>
#include <WiFiUdp.h>

#define LED_G 10
#define LED_B 11
#define LED_R 12

int status = WL_IDLE_STATUS;
char ssid[] = "CAVEDU_02"; // your network SSID (name)
char pass[] = "12345678"; // your network password (use for WPA / WEP)
int keyIndex = 0; // your network key Index number (needed only for WEP)

unsigned int localPort = 7890; // local port to listen on

char Reply_R_ON[] = "Red ON"; // a string to send back
char Reply_R_OFF[] = "Red OFF"; // a string to send back
char Reply_G_ON[] = "Green ON"; // a string to send back
char Reply_G_OFF[] = "Green OFF"; // a string to send back
char Reply_B_ON[] = "Blue ON"; // a string to send back
char Reply_B_OFF[] = "Blue OFF"; // a string to send back

String SA[2];
int A[2];

WiFiUDP Udp;

void setup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}

// attempt to connect to Wifi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);
}
Serial.println("Connected to wifi");
printWifiStatus();

Serial.println("\nStarting connection to server...");
// if you get a connection, report back via serial:
Udp.begin(localPort);
}

void loop() {

char packetBuffer[255]; //buffer to hold incoming packet

// if there's data available, read a packet
int packetSize = Udp.parsePacket();
if (packetSize) {
Serial.print("Received packet of size ");
Serial.println(packetSize);
Serial.print("From ");
IPAddress remoteIp = Udp.remoteIP();
Serial.print(remoteIp);
Serial.print(", port ");
Serial.println(Udp.remotePort());

// read the packet into packetBufffer
int len = Udp.read(packetBuffer, 255);
if (len > 0) {
packetBuffer[len] = 0;
}
Serial.println("Contents:");
Serial.println(packetBuffer);

String value = String(packetBuffer);

String_to_Int(value, 2);

Serial.print(SA[0]);
Serial.print(",");
Serial.println(A[1]);

if (SA[0] == "G") {
if (A[1] == 1) {
digitalWrite(LED_G, HIGH);
// send a reply, to the IP address and port that sent us the packet we received
Udp.beginPacket(Udp.remoteIP(), localPort);
Udp.write(Reply_G_ON);
Udp.endPacket();
}
if (A[1] == 0) {
digitalWrite(LED_G, LOW);
// send a reply, to the IP address and port that sent us the packet we received
Udp.beginPacket(Udp.remoteIP(), localPort);
Udp.write(Reply_G_OFF);
Udp.endPacket();
}
value = "";
}

if (SA[0] == "B") {
if (A[1] == 1) {
digitalWrite(LED_B, HIGH);
// send a reply, to the IP address and port that sent us the packet we received
Udp.beginPacket(Udp.remoteIP(), localPort);
Udp.write(Reply_B_ON);
Udp.endPacket();
}
if (A[1] == 0) {
digitalWrite(LED_B, LOW);
// send a reply, to the IP address and port that sent us the packet we received
Udp.beginPacket(Udp.remoteIP(), localPort);
Udp.write(Reply_B_OFF);
Udp.endPacket();
}
value = "";
}

if (SA[0] == "R") {
if (A[1] == 1) {
digitalWrite(LED_R, HIGH);
// send a reply, to the IP address and port that sent us the packet we received
Udp.beginPacket(Udp.remoteIP(), localPort);
Udp.write(Reply_R_ON);
Udp.endPacket();
}
if (A[1] == 0) {
digitalWrite(LED_R, LOW);
// send a reply, to the IP address and port that sent us the packet we received
Udp.beginPacket(Udp.remoteIP(), localPort);
Udp.write(Reply_R_OFF);
Udp.endPacket();
}
value = "";
}
}
}

void String_to_Int(String temp, int count)
{
int index;

index = temp.indexOf(',');
SA[0] = temp.substring(0, index);

for (int i = 1; i < count; i++) {
temp = temp.substring(index + 1, temp.length());
index = temp.indexOf(',');
SA[i] = temp.substring(0, index);
A[i] = SA[i].toInt();
}
}

void printWifiStatus() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());

// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);

// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}

這個測試程式主要可以「接收」使用者的命令進行RGB-LED電路模組的控制,並且「發送」訊息回應使用者。

接收命令 RGB-LED動作 發送回應訊息
R,1 亮起紅色光 Red ON
R,0 熄滅紅色光 Red OFF
G,1 亮起綠色光 Green ON
G,0 熄滅綠色光 Green OFF
B,1 亮起藍色光 Blue ON
B,0 熄滅藍色光 Blue OFF

 

3. Linkit7697的操作狀態訊息輸出(透過Serial 序列資料輸出訊息)

(a) 初始狀態訊息輸出

 

(b) 接收來自Tinker Board 2S UDP資料並輸出狀態

4. Tinker Board 2S Python測試流程:

    • 起始化並設定WiFi TCP / IP
    • 設定UDP通訊模式
    • 輸入控制命令
    • 透過UDP發送控制命令至MCU
    • 等候來自MCU的回應訊息
    • 接收MCU的回應訊息
    • 顯示來自MCU的回應訊息

5. Tinker Board 2S Python測試程式:

tinker GUI app to talk with LinkIt 7697 via wifi
import socket
import threading

import inspect
import ctypes

import time
import datetime
import requests

import tkinter
import tkinter.font as tkFont

from PIL import Image
import PIL.Image, PIL.ImageTk

import os
import io

class App:
def __init__(self, window, window_title):
self.window = window
self.window.title(window_title)
self.window.geometry('800x600')
self.window.resizable(False, False)

self.thread_sw = 1

self.fontStyle = tkFont.Font(size=30)
self.header_label = tkinter.Label(window, text='網路監控系統',font=self.fontStyle)
self.header_label.place(x=80, y=0, width=640)

# 建立套接字
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 繫結本地資訊
self.udp_socket.bind(('', 7890))

# 建立一個子執行緒用來接收網路資料
self.t = threading.Thread(target=self.recv_msg, args=(self.udp_socket,))
self.t.start()

# 建立網路節點1顯示訊息
self.fontStyle = tkFont.Font(size=20)
self.Node_1_Label = tkinter.Label(window,text="網路節點1",padx = 20, font=self.fontStyle)
self.Node_1_Label.place(x=20, y=150, width=150)

# 發送起始訊息給本機清空訊息欄位
self.dest_ip = '127.0.0.1'
self.dest_port = 7890
self.msg = ''
self.udp_socket.sendto(self.msg.encode("utf-8"), (self.dest_ip, self.dest_port))

# 建立發送位址顯示訊息
self.fontStyle = tkFont.Font(size=16)
self.Send_IP_Label = tkinter.Label(window,text="發送位址",padx = 20, font=self.fontStyle)
self.Send_IP_Label.place(x=20, y=520, width=100)

# 建立發送位址輸入訊息
self.fontStyle = tkFont.Font(size=16)
self.Send_IP_input = tkinter.Entry(window,font=self.fontStyle,fg='blue')
self.Send_IP_input.place(x=125, y=520, width=200)

# 建立位址埠號顯示訊息
self.fontStyle = tkFont.Font(size=16)
self.Port_Label = tkinter.Label(window,text="位址埠號",padx = 20, font=self.fontStyle)
self.Port_Label.place(x=330, y=520, width=100)

# 建立位址埠號輸入訊息
self.fontStyle = tkFont.Font(size=16)
self.Port_input = tkinter.Entry(window,font=self.fontStyle,fg='blue')
self.Port_input.place(x=435, y=520, width=100)

# 建立發送資訊顯示訊息
self.fontStyle = tkFont.Font(size=16)
self.Data_Label = tkinter.Label(window,text="發送資訊",padx = 20, font=self.fontStyle)
self.Data_Label.place(x=20, y=555, width=100)

# 建立發送資訊輸入訊息
self.fontStyle = tkFont.Font(size=16)
self.Data_input = tkinter.Entry(window,font=self.fontStyle,fg='blue')
self.Data_input.place(x=125, y=555, width=400)

# 建立發送資料按鈕
self.fontStyle = tkFont.Font(size=20)
self.Send_Button = tkinter.Button(window, text='發送資料', font=self.fontStyle, command=self.Send_Data)
self.Send_Button.place(x=550, y=525, width=200, height=60)

# 建立關閉多執行緒按鈕
self.fontStyle = tkFont.Font(size=20)
self.Send_Button = tkinter.Button(window, text='關閉執行緒', font=self.fontStyle, command=self.Close_Thread)
self.Send_Button.place(x=550, y=450, width=200, height=60)

# After it is called once, the update method will be automatically called every delay milliseconds
self.delay = 1000
self.update()

self.window.mainloop()

def Send_Data(self):
self.dest_ip = self.Send_IP_input.get()
self.dest_port = int(self.Port_input.get())
self.msg = self.Data_input.get()
self.udp_socket.sendto(self.msg.encode("utf-8"), (self.dest_ip, self.dest_port))

def Close_Thread(self):
self.thread_sw = 0
self.stop_thread(self.t)
self.udp_socket.shutdown(socket.SHUT_RDWR)
self.udp_socket.close()

def update(self):
"""接收網路資料並顯示"""
# 顯示網路節點1的資料
self.fontStyle = tkFont.Font(size=20)
self.Node_1_value_Label = tkinter.Label(
self.window,
text = str(self.recv_msg),
padx = 20, bg="white", fg="red", font=self.fontStyle)
self.Node_1_value_Label.place(x=20, y=200, width=400)

self.window.after(self.delay, self.update)

def recv_msg(self,udp_socket):
while (self.thread_sw == 1):
# print('self.thread_sw = ',self.thread_sw)
print('thread_sw = ',self.thread_sw)
# 1. 接收資料
self.recv_msg = udp_socket.recvfrom(1024)
# 2. 解碼
self.recv_ip = self.recv_msg[1]
self.recv_msg = self.recv_msg[0].decode("utf-8")

def _async_raise(self,tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# """if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")

def stop_thread(self,thread):
self.thread_sw = 0
self._async_raise(thread.ident, SystemExit)

def __del__(self):
print('Good Bye!')


# Create a window and pass it to the Application object
App(tkinter.Tk(), "網路監控系統")

(a) Tinker Board 2S 程式初始狀態

(b) 設定發送IP(如:Linkit7697的IP為192.168.12.139)、設定通訊埠(如:7890)與設定欲發送命令資料(如:B,1,讓LED亮藍燈),按下「發送資料」後,網路節點1(來自Linkit7697的回應訊息)回應「Blue ON」

本篇教學文主要針對Tinker Board 2S以WiFi通訊透過UDP協定進行與MCU的通訊,透過UDP的傳輸,具有資料傳輸效率甚高的特點(因為不需要進行嚴謹的資安檢查),非常適合用來進行高速的資料傳輸,因此,將會於另一篇教學文中,介紹透過UDP協定進行多個計算機系統之間串流影像傳輸,實現遠端的影像串流傳輸,並且透過人工智慧影像辨識的整合,達成網路串流影像辨識的目的,大家就拭目以待吧!

相關文章

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *