内容一:3-5分钟短视频
FollowMe第四期视频
内容二:任务总结报告
■ 入门任务二:开发环境搭建,BLINK,驱动液晶显示器进行显示(没有则串口HelloWorld)
开发环境使用Arduino ID + C++E完成前置任务,用Thonny + MicroPython完成终极任务。
BLINK程序直接使用Arduinio IDE内置的例程即可,不需要任何修改,直接编译上传代码。
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(1000;
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
展示:
■ 基础任务一:完成主控板W5500初始化(静态IP配置),并能使用局域网电脑ping通,同时W5500可以ping通互联网站点;通过抓包软件(Wireshark、Sniffer等)抓取本地PC的ping报文,展示并分析。
本任务需要用Ethernet这个库,由于w5500和rp2040通过SPI连接,需要SPI库,以及进行域名解析的DNS库
#include <SPI.h>
#include <Ethernet.h>
#include <Dns.h>
#include <EthernetICMP.h>
IPAddress remoteip;
DNSClient dnClient;
char buffer[256];
SOCKET pingSocket = 0;
EthernetICMPPing ping(pingSocket, (uint16_t)random(0, 255));
byte mac[] = { 0x0E, 0xA0, 0xBE, 0xE4, 0xFE, 0x0D };
void pingTest() {
EthernetICMPEchoReply echoReply = ping(remoteip, 4);
if (echoReply.status == SUCCESS) {
sprintf(buffer,
"Reply[%d] from: %d.%d.%d.%d: bytes=%d time=%ldms TTL=%d",
echoReply.data.seq,
echoReply.addr[0],
echoReply.addr[1],
echoReply.addr[2],
echoReply.addr[3],
REQ_DATASIZE,
millis() - echoReply.data.time,
echoReply.ttl);
}
else {
sprintf(buffer, "Echo request failed; %d", echoReply.status);
}
Serial.println(buffer);
}
void setup() {
Serial.begin(115200);
while (!Serial) {
; // 等待串口连接
}
IPAddress ip(192, 168, 10, 188);
IPAddress myDns(192, 168, 10, 1);
IPAddress gateway(192, 168, 10, 1);
IPAddress subnet(255, 255, 255, 0);
Serial.println("Ethernet Begin");
Ethernet.init(17);
Ethernet.begin(mac, ip, myDns, gateway, subnet);
Serial.print("My Mac address: ");
byte macBuffer[6];
Ethernet.MACAddress(macBuffer);
for (byte octet = 0; octet < 6; octet++) {
Serial.print(macBuffer[octet], HEX);
if (octet < 5) {
Serial.print('-');
}
}
dnClient.begin(Ethernet.dnsServerIP());
const char domains[3][20] = { "www.eeworld.com.cn", "www.digikey.cn", "www.baidu.com" };
for (int i = 0; i < 3; i++) {
if (dnClient.getHostByName(domains[i], remoteip) == 1) {
Serial.print(domains[i]);
Serial.print(" = ");
Serial.println(remoteip);
if (i < 3)
pingTest();
}
else Serial.println(F("dns lookup failed"));
}
}
void loop() {
remoteip.fromString("192.168.10.188");
pingTest();
delay(1000);
}
展示:
■ 基础任务二:主控板建立TCPIP或UDP服务器,局域网PC使用TCPIP或UDP客户端进行连接并发送数据,主控板接收到数据后,送液晶屏显示(没有则通过串口打印显示);通过抓包软件抓取交互报文,展示并分析。(TCP和UDP二选一,或者全都操作
开发板首先设置静态ip连接网络,然后使用TCP,同时另一台PC通过TCP向开发板发送信息。
#include <Ethernet.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xE1, 0xF2, 0xE3 };
IPAddress ip(192, 168, 10, 188);
IPAddress gateway(192, 168, 10, 1);
IPAddress myDns(8, 8, 8, 8);
IPAddress subnet(255, 255, 255, 0);
IPAddress setNet() {
Serial.println("Ethernet Begin");
Ethernet.init(17);
Ethernet.begin(mac, ip, myDns, gateway, subnet);
Serial.print("My IP address: ");
Serial.println(Ethernet.localIP());
Serial.print("My Mac address: ");
byte macBuffer[6];
Ethernet.MACAddress(macBuffer);
for (byte octet = 0; octet < 6; octet++) {
Serial.print(macBuffer[octet], HEX);
if (octet < 5) {
Serial.print('-');
}
}
Serial.println("");
return Ethernet.localIP();
}
EthernetServer server(5000);
String server_loop() {
String msg;
EthernetClient client = server.available();
if (client) {
Serial.println("new client");
while (client.connected()) {
if (client.available()) {
char c = client.read();
msg += String(c);
}
}
}
return msg;
}
void setup() {
Serial.begin(115200);
while (!Serial)
;//等待串口
setNet();
server.begin();
}
void loop() {
String msg = server_loop();
if (msg != "") {
Serial.println(msg);
}
}
PC端发送代码:
import socket
message = "Hello WIZNet-W5500-EVB-Pico, this is a message from vscode"
ip_addr=("192.168.10.188",5000)
client=socket.socket()
client.connect(ip_addr)
client.send(bytes(message,"utf-8"))
client.close()
展示:
■ 进阶任务:从NTP服务器(注意数据交互格式的解析)同步时间,获取时间送显示屏(串口)显示。
开发板设置静态IP后,访问国家Ntp授时网站cn.ntp.org.cn获取时间
#include <SPI.h>
#include <Ethernet.h>
#include <NTPClient.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 10, 188);
IPAddress myDns(192, 168, 10, 1);
IPAddress gateway(192, 168, 10, 1);
IPAddress subnet(255, 255, 255, 0);
EthernetUDP Udp;
NTPClient timeClient(Udp, "cn.ntp.org.cn");
void setup() {
Serial.begin(115200);
Serial.begin(9600);
while(!Serial){
;
}
Ethernet.init(17);
Ethernet.begin(mac, ip, myDns, gateway, subnet);
timeClient.begin();
}
void loop() {
timeClient.update();
Serial.print("Current time: ");
Serial.println(timeClient.getFormattedTime());
char timeString[9]; // 用于保存格式化后的时.分.秒字符串
sprintf(timeString, "%02d.%02d.%02d", timeClient.getHours(), timeClient.getMinutes(), timeClient.getSeconds());
Serial1.println(timeString); // 输出时.分.秒格式的字符串
delay(5000); // 每5秒更新一次时间到串口显示屏
}
显示效果:
■ 终极任务二:使用外部存储器,组建简易FTP文件服务器,并能正常上传下载文件。
此任务用arduino实现较为困难,故使用Thonny + MicroPython实现,代码如下:
import gc
import uos
import time
import socket
import network
from time import localtime
from machine import Pin, SPI
from micropython import const
_LED_PIN = const(25) # 绿色 LED 引脚
_SPI_SPEED = const(2_000_000) # SPI 速率
_MOSI_PIN = const(19) # SPI MOSI 引脚
_MISO_PIN = const(16) # SPI MISO 引脚
_SCK_PIN = const(18) # SPI SCK 引脚
_CS_PIN = const(17) # SPI CS 引脚
_RST_PIN = const(20) # SPI RESET 引脚
FTP_ROOT_PATH = const("/Ftp") # FTP 根目录
month_name = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
# SPI 定义
spi=SPI(0, _SPI_SPEED, mosi=Pin(_MOSI_PIN), miso=Pin(_MISO_PIN), sck=Pin(_SCK_PIN))
nic = None
""" W5500 初始化 """
def w5x00_init():
global nic
# 网口初始化
nic = network.WIZNET5K(spi, Pin(_CS_PIN), Pin(_RST_PIN)) #spi,cs,reset pin
nic.active(True)
# 配置网络
nic.ifconfig(('192.168.10.188','255.255.255.0','192.168.10.1','8.8.8.8'))
while not nic.isconnected():
time.sleep(1)
print(nic.regs())
print("IP地址: %s" %nic.ifconfig()[0])
print("子网掩码: %s" %nic.ifconfig()[1])
print("网关: %s" %nic.ifconfig()[2])
print("DNS: %s" %nic.ifconfig()[3])
""" 响应文件列表请求 """
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):
try:
with open(path, "rb") as file:
chunk = file.read(512)
print("chunk 0: ", len(chunk))
while len(chunk) > 0:
print("chunk: ", len(chunk))
dataclient.sendall(chunk)
chunk = file.read(512)
except Exception as err:
print("error: ", err.args, err.value, err.errno)
""" 保存文件上传数据 """
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
""" 文件名比较 """
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
""" 启动FTP服务 """
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)
print("FTP服务启动成功!监听端口:21");
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 = FTP_ROOT_PATH
try:
print("新的FTP连接来自: %s:%s" %(remote_addr[0], remote_addr[1]))
cl.sendall("220 Welcome! This is the W5500_EVB_PICO!\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("命令={}, 参数={}, 路径={}".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数据连接来自: %s:%s" %(data_addr[0], data_addr[1]))
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, "wb")
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()
if __name__ == "__main__":
print("run in main")
w5x00_init() # 初始化网络
ftpserver() # 运行 FTP Server
效果:
内容三:可编译下载的代码
代码链接:代码
心得体会
本次FollowMe活动涵盖了从搭建开发环境到实现网络通讯、时间同步和文件传输等多个方面的技术内容,是一项非常全面和实用的项目。完成这些任务需要对嵌入式系统、网络通信、协议解析等知识有较深入的理解和掌握。
从入门任务开始,了解硬件和软件的基本交互,逐步深入学习和实践,帮助我建立起完整的技术体系。在基础任务中,通过网络通信实现局域网与互联网的连接,抓包分析可以帮助我们更深入地了解数据交互过程,发现问题和优化方案。进阶任务则更进一步地提高了网络通信的要求,需要在数据的接收与显示过程中做更多的处理操作。而终极任务则突破了单一功能,结合外部存储器和FTP协议,实现了文件的传输管理,扩展了功能的同时也加深了对整个系统的理解和应用。
在完成这些任务的过程中,需要不断尝试和调试,遇到问题就分析解决问题。同时,及时查阅文档和资料,积极参与相关领域的社区和讨论,向他人请教和分享经验也是非常重要的。另外,保持学习的热情和对技术的好奇心,不断提升自己的技术水平和解决问题的能力也是很重要的。
总而言之,完成这些任务帮助我建立起扎实的技术基础,培养解决问题的能力和创新意识。同时,也拓展了我对技术领域的认识和理解,为未来的学习和发展打下坚实的基础。感谢Digikey得捷电子,感谢EEWorld。
建议:多多开展此类活动,培养更多全栈工程师
|