【得捷电子Follow me第4期】汇总提交帖:上手W5500-EVB-Pico
[复制链接]
本帖最后由 MioChan 于 2024-2-12 19:04 编辑
Follow Me 第四期的板子是 W5500-EVB-Pico,使用方式与 Raspberry Pi Pico 板基本相同,主要是多了一个RJ45网口。
我选购的器件是 W5500-EVB-Pico和Arduino R4 WIFI,因为感觉推荐的那块屏幕感觉性价比太低,刚好R4还自带点阵LED,能配合 W5500-EVB-Pico作为它的一块小屏幕,我在Arduino上建立一个接受Post请求的API服务器,以便Pico可以将要显示的内容通过网络请求发送到R4的点阵屏。两个板子都使用Arduino IDE编写和上传程序。源码请见附件,正文只贴一些关键片段。
入门任务:开发环境搭建,BLINK,驱动液晶显示器进行显示(没有则串口HelloWorld)
环境搭建非常简单,只需要到官网下载对应的固件即可,然后将其拖入板子烧录模式在PC上显示的U盘中,然后在Arduino选好对应的板子就可以上传程序了
首先需要在Arduino R4开发板上建立一个API 服务器,接受其它设备Post请求的字符串,然后将其在板载点阵LED上滚动显示即可,核心代码主要就是实现API服务器和文字滚动显示。
void checkForNewClient() { //建立API服务器
WiFiClient client = server.available();
if (client) {
Serial.println("New client");//判断是否有设备发送请求
// Read the first line of the request
String firstLine = client.readStringUntil('\n'); //解析请求
// Check if this is a POST request
if (firstLine.startsWith("POST")) {
// Read the headers and find the Content-Length
int contentLength = -1;
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line.startsWith("Content-Length:")) {
contentLength = line.substring(15).toInt();
}
// Check if the end of headers is reached (empty line)
if (line == "\r") {
break;
}
}
// Read the request body
if (contentLength > 0) {
String requestBody = client.readStringUntil('\n');
// Parse JSON from the request body
DynamicJsonDocument doc(1024);
deserializeJson(doc, requestBody);
String content = doc["content"];
if (content != "") {
Serial.println("Received content: " + content);
receivedContent = " "+content+" "; // Update the received string
} else {
Serial.println("No content received");
}
}
}
// Send response to the client
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
client.println("Response sent");
client.stop();
Serial.println("Client disconnected");
}
}
void displayScrollingText(String text) { //实现LED点阵上的滚动显示
matrix.beginDraw();
matrix.stroke(0xFFFFFFFF);
matrix.textScrollSpeed(50);
matrix.textFont(Font_5x7);
matrix.beginText(0, 1, 0xFFFFFF);
matrix.println(text);
matrix.endText(SCROLL_LEFT);
matrix.endDraw();
}
接下来是主控板W5500的部分,任务要求BLINK和输出Hello World,因为我们需要向R4的点阵LED上发送Post请求一遍显示文字,所以这一步已经完成了主控板W5500网络功能的初始化,代码如下:
#include <SPI.h>
#include <Ethernet.h>
//初始化和网络配置部分
EthernetClient client;
IPAddress server(192, 168, XX, XX); // 设置Arduino R4的 IP 地址
uint16_t serverPort = 80;
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
unsigned long lastTime = 0;
const unsigned long interval = 15000; // 15秒间隔
void setup() {
Serial.begin(9600);
delay(4000);
Serial.println("Hello World");//串口输出Hello World
pinMode(LED_BUILTIN, OUTPUT);
delay(100);
digitalWrite(LED_BUILTIN, HIGH);
if (Ethernet.begin(mac) == 0) { //DHCP配网
Serial.println("Failed to configure Ethernet ");
while (true);
}
delay(1000);
lastTime = millis();
}
//Blink和发送文字Post请求
void loop() {
if (millis() - lastTime >= interval) {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
sendPostRequest();
lastTime = millis();
}
}
void sendPostRequest(String content ) {
if (client.connect(server, serverPort)) {
Serial.println("Connected to server");
client.println("POST / HTTP/1.1");
client.println("Host: 192.168.50.18");
client.println("Content-Type: application/json");
client.println("Connection: close");
client.print("Content-Length: ");
client.println(23);
client.println();
if( content != "")
client.println("{\"content\":\""+content+"\"}");
else
client.println("{\"content\":\"Hello from W5500\"}");
} else {
Serial.println("Connection failed");
}
while (client.available()) {
char c = client.read();
Serial.write(c);
}
if (!client.connected()) {
Serial.println();
Serial.println("Disconnecting from server...");
client.stop();
}
}
基础任务一:完成主控板W5500初始化(静态IP配置),并能使用局域网电脑ping通,同时W5500可以ping通互联网站点;通过抓包软件(Wireshark、Sniffer等)抓取本地PC的ping报文,展示并分析。
基础任务一中,因为我们的板载本来就要和R4开发板通信,所以上个任务就已经完成了配网,因为题目要求静态IP,只需要用 Ethernet.begin(mac, staticIP, myDns, gateway, subnet);就可以实现,局域网电脑可以成功ping通Pico
从Wireshark抓包截图,我们可以看到一系列的ICMP ping请求。每个请求的源地址是192.168.50.76,目标地址是192.168.50.200。每个ICMP报文的长度都是74字节。
这些请求连续发送,并且序列号(seq)逐个递增,这表明是从一个设备向另一个设备连续发送ping请求。序列号的递增可以帮助发送方跟踪响应与请求的匹配情况。ICMP回显请求用于检测网络连接的状态,通过这些请求可以判断两个IP地址之间是否有有效的网络连接。
也能ping通EEWorld的首页
基础任务二:主控板建立TCPIP或UDP服务器,局域网PC使用TCPIP或UDP客户端进行连接并发送数据,主控板接收到数据后,送液晶屏显示(没有则通过串口打印显示);通过抓包软件抓取交互报文,展示并分析。(TCP和UDP二选一,或者全都操作)
任务二中建立TCPIP服务器其实在入门任务就完成了,因为Pico和R4开发板的通信就是通过TCP协议,我们只需要将R4开发板的IP改为PC的IP即可
Wireshark截图显示了一系列TCP协议的数据包。这些数据包是在源IP地址192.168.50.200和目的IP地址192.168.50.76之间传输的。我们可以看到不同的TCP标志位,包括SYN、ACK和PSH,这些都是TCP三次握手和数据传输过程中常见的标志。
SYN(同步序列编号):用于建立连接时的握手过程,我们可以看到握手开始时的SYN包和相应的SYN-ACK回复。
ACK(确认):确认收到对方的包,几乎所有的TCP包都设置了ACK标志。
PSH(推送):提示接收端立即将这些数据推送给应用程序,而不是等待缓冲区填满。
源端口49154到目标端口80的数据包通常表示客户端尝试访问服务器上的Web服务。端口80是HTTP服务的标准端口。从数据包的长度来看,有些包含了少量的数据(例如,长度为2或15字节的包),这些可能是包含控制信息或者是小量数据的传输。
这种TCP通信模式是典型的客户端与服务器之间的交互,涉及连接建立、数据传输以及最终的连接终止。通过这些数据包的分析,可以检查TCP连接的状态、数据传输的顺序和完整性,以及连接的性能问题。
Wireshark抓包截图中,我们可以看到两个UDP报文。第一个报文(编号88)是从源IP地址192.168.50.76的端口8888发送到目标IP地址192.168.50.200的端口8888。该报文的长度为56字节, 其中UDP负载数据长度为14字节。第二个报文(编号100)是从192.168.50.200的8888端口发回到192.168.50.76的8888端口,长度为60字节,负载数据长度为12字节。
这表明在两个设备之间使用UDP协议进行了通信,端口8888用于发送和接收数据。由于UDP是一种无连接的协议,这些报文显示了数据的发送和可能的响应,但不保证数据的送达。UDP通常用于需要快速传输,如视频流或在线游戏,而不是准确无误的数据传输。
进阶任务:从NTP服务器(注意数据交互格式的解析)同步时间,获取时间送显示屏(串口)显示。
进阶任务中在示例程序上做一些改动即可完成,下面是具体的效果,可以看出pico可以正常获取北京时间,并将其输出在串口和R4的点阵上
终极任务(二选一)
■ 终极任务二:使用外部存储器,组建简易FTP文件服务器,并能正常上传下载文件。
终极任务因为在老家过年期间物流都停了,手边没有现成的SD卡模块,本来想做任务一,但发现调用得捷的API非常麻烦,必须https有各种验证问题,刚好在站内看到一个能用Pico板载外存实现FTP,直接抄了作业。这是用MicroPython做的。
import socket
import network
import uos
import gc
from time import localtime
from machine import Pin,SPI
import time
def w5x00_init():
#spi init
spi=SPI(0,2_000_000, mosi=Pin(19),miso=Pin(16),sck=Pin(18))
nic = network.WIZNET5K(spi,Pin(17),Pin(20)) #spi,cs,reset pin
nic.active(True)#network active
nic.ifconfig(('192.168.50.200','255.255.255.0','192.168.50.1','192.168.50.1'))#Set static network address information
while not nic.isconnected():
time.sleep(1)
print(nic.regs())#Print register information
#Print network address information
print("IP Address:",nic.ifconfig()[0])
print("Subnet Mask:",nic.ifconfig()[1])
print("Gateway:",nic.ifconfig()[2])
print("DNS:",nic.ifconfig()[3])
return nic
month_name = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def send_list_data(path, dataclient, full):
try: # whether path is a directory name
for fname in uos.listdir(path):
dataclient.sendall(make_description(path, fname, full))
except: # path may be a file name or pattern
pattern = path.split("/")[-1]
path = path[:-(len(pattern) + 1)]
if path == "": path = "/"
for fname in uos.listdir(path):
if fncmp(fname, pattern) == True:
dataclient.sendall(make_description(path, fname, full))
def make_description(path, fname, full):
if full:
stat = uos.stat(get_absolute_path(path,fname))
file_permissions = "drwxr-xr-x" if (stat[0] & 0o170000 == 0o040000) else "-rw-r--r--"
file_size = stat[6]
tm = localtime(stat[7])
if tm[0] != localtime()[0]:
description = "{} 1 owner group {:>10} {} {:2} {:>5} {}\r\n".format(
file_permissions, file_size, month_name[tm[1]], tm[2], tm[0], fname)
else:
description = "{} 1 owner group {:>10} {} {:2} {:02}:{:02} {}\r\n".format(
file_permissions, file_size, month_name[tm[1]], tm[2], tm[3], tm[4], fname)
else:
description = fname + "\r\n"
return description
def send_file_data(path, dataclient):
with open(path, "r") as file:
chunk = file.read(512)
while len(chunk) > 0:
dataclient.sendall(chunk)
chunk = file.read(512)
def save_file_data(path, dataclient, mode):
with open(path, mode) as file:
chunk = dataclient.read(512)
while len(chunk) > 0:
file.write(chunk)
chunk = dataclient.read(512)
def get_absolute_path(cwd, payload):
# Just a few special cases "..", "." and ""
# If payload start's with /, set cwd to /
# and consider the remainder a relative path
if payload.startswith('/'):
cwd = "/"
for token in payload.split("/"):
if token == '..':
if cwd != '/':
cwd = '/'.join(cwd.split('/')[:-1])
if cwd == '':
cwd = '/'
elif token != '.' and token != '':
if cwd == '/':
cwd += token
else:
cwd = cwd + '/' + token
return cwd
# compare fname against pattern. Pattern may contain
# wildcards ? and *.
def fncmp(fname, pattern):
pi = 0
si = 0
while pi < len(pattern) and si < len(fname):
if (fname[si] == pattern[pi]) or (pattern[pi] == '?'):
si += 1
pi += 1
else:
if pattern[pi] == '*': # recurse
if (pi + 1) == len(pattern):
return True
while si < len(fname):
if fncmp(fname[si:], pattern[pi+1:]) == True:
return True
else:
si += 1
return False
else:
return False
if pi == len(pattern.rstrip("*")) and si == len(fname):
return True
else:
return False
def ftpserver():
DATA_PORT = 13333
ftpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ftpsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ftpsocket.bind(socket.getaddrinfo("0.0.0.0", 21)[0][4])
datasocket.bind(socket.getaddrinfo("0.0.0.0", DATA_PORT)[0][4])
ftpsocket.listen(1)
datasocket.listen(1)
datasocket.settimeout(10)
msg_250_OK = '250 OK\r\n'
msg_550_fail = '550 Failed\r\n'
try:
dataclient = None
fromname = None
while True:
cl, remote_addr = ftpsocket.accept()
cl.settimeout(300)
cwd = '/'
try:
# print("FTP connection from:", remote_addr)
cl.sendall("220 Hello, this is the ESP8266.\r\n")
while True:
gc.collect()
data = cl.readline().decode("utf-8").rstrip("\r\n")
if len(data) <= 0:
print("Client disappeared")
break
command = data.split(" ")[0].upper()
payload = data[len(command):].lstrip()
path = get_absolute_path(cwd, payload)
print("Command={}, Payload={}, Path={}".format(command, payload, path))
if command == "USER":
cl.sendall("230 Logged in.\r\n")
elif command == "SYST":
cl.sendall("215 UNIX Type: L8\r\n")
elif command == "NOOP":
cl.sendall("200 OK\r\n")
elif command == "FEAT":
cl.sendall("211 no-features\r\n")
elif command == "PWD":
cl.sendall('257 "{}"\r\n'.format(cwd))
elif command == "CWD":
try:
files = uos.listdir(path)
cwd = path
cl.sendall(msg_250_OK)
except:
cl.sendall(msg_550_fail)
elif command == "CDUP":
cwd = get_absolute_path(cwd, "..")
cl.sendall(msg_250_OK)
elif command == "TYPE":
# probably should switch between binary and not
cl.sendall('200 Transfer mode set\r\n')
elif command == "SIZE":
try:
size = uos.stat(path)[6]
cl.sendall('213 {}\r\n'.format(size))
except:
cl.sendall(msg_550_fail)
elif command == "QUIT":
cl.sendall('221 Bye.\r\n')
break
elif command == "PASV":
addr = nic.ifconfig()[0]
cl.sendall('227 Entering Passive Mode ({},{},{}).\r\n'.format(
addr.replace('.',','), DATA_PORT>>8, DATA_PORT%256))
dataclient, data_addr = datasocket.accept()
# print("FTP Data connection from:", data_addr)
elif command == "LIST" or command == "NLST":
if not payload.startswith("-"):
place = path
else:
place = cwd
try:
send_list_data(place, dataclient, command == "LIST" or payload == "-l")
cl.sendall("150 Here comes the directory listing.\r\n")
cl.sendall("226 Listed.\r\n")
except:
cl.sendall(msg_550_fail)
if dataclient is not None:
dataclient.close()
dataclient = None
elif command == "RETR":
try:
send_file_data(path, dataclient)
cl.sendall("150 Opening data connection.\r\n")
cl.sendall("226 Transfer complete.\r\n")
except:
cl.sendall(msg_550_fail)
if dataclient is not None:
dataclient.close()
dataclient = None
elif command == "STOR":
try:
cl.sendall("150 Ok to send data.\r\n")
save_file_data(path, dataclient, "w")
cl.sendall("226 Transfer complete.\r\n")
except:
cl.sendall(msg_550_fail)
if dataclient is not None:
dataclient.close()
dataclient = None
elif command == "APPE":
try:
cl.sendall("150 Ok to send data.\r\n")
save_file_data(path, dataclient, "a")
cl.sendall("226 Transfer complete.\r\n")
except:
cl.sendall(msg_550_fail)
if dataclient is not None:
dataclient.close()
dataclient = None
elif command == "DELE":
try:
uos.remove(path)
cl.sendall(msg_250_OK)
except:
cl.sendall(msg_550_fail)
elif command == "RMD":
try:
uos.rmdir(path)
cl.sendall(msg_250_OK)
except:
cl.sendall(msg_550_fail)
elif command == "MKD":
try:
uos.mkdir(path)
cl.sendall(msg_250_OK)
except:
cl.sendall(msg_550_fail)
elif command == "RNFR":
fromname = path
cl.sendall("350 Rename from\r\n")
elif command == "RNTO":
if fromname is not None:
try:
uos.rename(fromname, path)
cl.sendall(msg_250_OK)
except:
cl.sendall(msg_550_fail)
else:
cl.sendall(msg_550_fail)
fromname = None
else:
cl.sendall("502 Unsupported command.\r\n")
# print("Unsupported command {} with payload {}".format(command, payload))
except Exception as err:
print(err)
finally:
cl.close()
cl = None
finally:
datasocket.close()
ftpsocket.close()
if dataclient is not None:
dataclient.close()
nic = w5x00_init()
ftpserver()
源码:https://download.eeworld.com.cn/detail/eew_nkXjf8/631128
|