- 2024-03-03
-
加入了学习《【得捷Follow me第4期】项目任务提交2》,观看 【得捷Follow me第4期】项目任务提交
-
加入了学习《【得捷Follow me第4期】项目任务提交》,观看 视频
- 2024-02-21
-
上传了资料:
【得捷Follow me第4期】项目任务提交
-
加入了学习《【得捷电子Follow me第4期】全部任务合集》,观看 【得捷电子Follow me第4期】全部任务合集
- 2024-02-20
-
加入了学习《【得捷电子Follow me第4期】使用micropython完成全部任务》,观看 【得捷电子Follow me第4期】使用micropython完成全部任务
-
发表了主题帖:
【得捷Follow me第4期】项目任务提交
入门任务:开发环境搭建,BLINK,驱动液晶显示器进行显示。
W5500-RPI-PICO开发板对Arduino有很好的支持,安扎官方文档,我们先在arduino IDE中添加开发板地址,由于我购买了一块cardputer作为项目中的协处理器,负责屏幕显示,因此也一并添加相关支持。:
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
添加完成后,在开发板管理器中搜索pico并安装。
blink完成非常简单,直接使用arduino官方的basic example blink,不需要任何修改,直接上传。
上传完成后就可以看到绿灯闪起来。
液晶屏是在cardputer上进行驱动的,我先在cardputer上初始化好液晶屏,然后开启一个硬件串口作为输入,当接收到信息时将它显示在屏幕上。这样就可以实现一个简单的串口显示器。W5500-RPI-PICO只需要将需要显示的内容用串口发送出去就可以。
在使用cardputer之前,我们需要安装一下对应的库。
安装好后使用起来就非常简单了。
#include "M5Cardputer.h"
#include <HardwareSerial.h>
HardwareSerial mySerial(2);
void setup() {
auto cfg = M5.config();
M5Cardputer.begin(cfg, true);
M5Cardputer.Display.setRotation(1);
M5Cardputer.Display.setTextColor(GREEN);
M5Cardputer.Display.setTextDatum(middle_center);
M5Cardputer.Display.setTextFont(&fonts::Orbitron_Light_24);
M5Cardputer.Display.setTextSize(1);
M5Cardputer.Display.drawString("Ready",
M5Cardputer.Display.width() / 2,
M5Cardputer.Display.height() / 2);
Serial.begin(115200);
mySerial.begin(115200, SERIAL_8N1, 2, 1);
}
void loop() {
if (mySerial.available()) {
String receivedString = mySerial.readString();
Serial.println(receivedString);
if (receivedString != "") {
M5Cardputer.Display.clear();
M5Cardputer.Display.drawString(receivedString,
M5Cardputer.Display.width() / 2,
M5Cardputer.Display.height() / 2);
}
}
}
这里我们用cardputer上的GPIO1和GPIO2作为TX和RX。接下来我们用pico 使用serial1打印一个hello,就可以显示在cardputer上。注意由于pico在连接上串口监视器后并不会重置,因此要先等待串口连接后,才可以继续执行串口打印任务,否则无法在串口看到输出。
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
while (!Serial)
;
Serial.println("Now send Hello");
Serial1.println("Hello");
}
void loop() {
}
基础任务一:完成主控板W5500初始化(静态IP配置),并能使用局域网电脑ping通,同时W5500可以ping通互联网站点;通过抓包软件(Wireshark、Sniffer等)抓取本地PC的ping报文,展示并分析。
同样的,先安装一下需要的库。这个库需要自己去github上下载下来zip,然后在arduino ide中导入zip库。
https://github.com/masterx1981/Ethernet.git
静态配置好ip,MAC等参数,就可以联网,然后分别解析dns并ping一下三个网站。
#include <Ethernet.h>
#include <Dns.h>
#include <EthernetICMP.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xE1, 0xF2, 0xE3 };
IPAddress ip(192, 168, 50, 188);
IPAddress gateway(192, 168, 50, 1);
IPAddress myDns(8, 8, 8, 8);
IPAddress subnet(255, 255, 255, 0);
IPAddress setNet() {
Serial.println("Ethernet Begin");
Ethernet.init(17);
// DHCP
// Ethernet.begin(mac);
// 静态IP设置
Ethernet.begin(mac, ip, myDns, gateway, subnet);
// 打印INFO
Serial.print("My IP address: ");
Serial.println(Ethernet.localIP());
Serial.print("My subnet: ");
Serial.println(Ethernet.subnetMask());
Serial.print("My DNS address: ");
Serial.println(Ethernet.dnsServerIP());
Serial.print("My GateWay address: ");
Serial.println(Ethernet.gatewayIP());
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();
}
DNSClient dnClient;
SOCKET pingSocket = 4;
EthernetICMPPing ping(pingSocket, (uint8_t)random(0, 255));
void pingTest(const IPAddress &dstip) {
EthernetICMPEchoReply echoReply = ping(dstip, 4);
char buffer[256];
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 ping_site() {
dnClient.begin(Ethernet.dnsServerIP());
const char domains[3][20] = { "www.eeworld.com.cn", "www.digikey.cn", "www.digikey.com" };
IPAddress dstip;
for (int i = 0; i < 3; i++) {
if (dnClient.getHostByName(domains[i], dstip) == 1) {
Serial.print(domains[i]);
Serial.print(" = ");
Serial.println(dstip);
pingTest(dstip);
} else
Serial.println(F("dns lookup failed"));
}
}
void setup() {
Serial.begin(115200);
while (!Serial)
;
setNet();
ping_site();
}
void loop() {
}
打开wireshark开始抓包,然后用电脑ping一下上面串口打印出的W5500-RPI-PICO开发板,接着在wireshark中用ip地址筛选,就可以看到捕获到的icmp包。
基础任务二:主控板建立TCPIP或UDP服务器,局域网PC使用TCPIP或UDP客户端进行连接并发送数据,主控板接收到数据后,送液晶屏显示(没有则通过串口打印显示);通过抓包软件抓取交互报文,展示并分析。
w5500-rpi-pico连接上网络后,建立一个tcp服务器并监听5000端口。如果有客户连入,则循环读取内容,并将内容添加至字符串。等客户结束连接后,用串口监视器和cardputer同时打印接收的内容。
#include <Ethernet.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xE1, 0xF2, 0xE3 };
IPAddress ip(192, 168, 50, 188);
IPAddress gateway(192, 168, 50, 1);
IPAddress myDns(8, 8, 8, 8);
IPAddress subnet(255, 255, 255, 0);
IPAddress setNet() {
Serial.println("Ethernet Begin");
Ethernet.init(17);
// DHCP
// Ethernet.begin(mac);
// 静态IP设置
Ethernet.begin(mac, ip, myDns, gateway, subnet);
// 打印INFO
Serial.print("My IP address: ");
Serial.println(Ethernet.localIP());
Serial.print("My subnet: ");
Serial.println(Ethernet.subnetMask());
Serial.print("My DNS address: ");
Serial.println(Ethernet.dnsServerIP());
Serial.print("My GateWay address: ");
Serial.println(Ethernet.gatewayIP());
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);
Serial1.begin(115200);
while (!Serial)
;
setNet();
server.begin();
}
void loop() {
String msg = server_loop();
if (msg != "") {
Serial.println(msg);
Serial1.println(msg);
}
}
编译代码时会有一定概率报错Compilation error: cannot declare variable 'server' to be of abstract type 'EthernetServer'。如果出现这种情况,需要修改一下两个库文件:
Ethernet.h
// virtual void begin();
virtual void begin(uint16_t port=0);
EthernetServer.cpp
//void EthernetServer::begin()
void EthernetServer::begin(uint16_t port)
电脑上发送tcp访问请求我使用的是python,用python写了个非常简单的代码,发送Hello!!!后关闭连接。
import socket
msg = "Hello!!!"
ip_addr=("192.168.50.188",5000)
client=socket.socket()
client.connect(ip_addr)
client.send(bytes(msg,"utf-8"))
client.close()
运行一下python代码,就可以看到效果。
打开wireshark,也可以抓到我们发送过去的信息。
进阶任务:从NTP服务器(注意数据交互格式的解析)同步时间,获取时间送显示屏(串口)显示。
通过NTP服务器获取时间的方式是基于UDP的,UDP是只管发送,不管对方收不收的到,因此单次请求可能会失败,所以需要设定一定的重试次数。每次重试之间需要有间隔,以免被对方服务器当作网络攻击直接屏蔽。代码中先把请求的UDP报文写好,再用该方法像服务器发送请求。成功获取到时间后,发送到串口和屏幕。
#include <Ethernet.h>
#include <EthernetUdp.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xE1, 0xF2, 0xE3 };
IPAddress ip(192, 168, 50, 188);
IPAddress gateway(192, 168, 50, 1);
IPAddress myDns(8, 8, 8, 8);
IPAddress subnet(255, 255, 255, 0);
IPAddress setNet() {
Serial.println("Ethernet Begin");
Ethernet.init(17);
// DHCP
// Ethernet.begin(mac);
// 静态IP设置
Ethernet.begin(mac, ip, myDns, gateway, subnet);
// 打印INFO
Serial.print("My IP address: ");
Serial.println(Ethernet.localIP());
Serial.print("My subnet: ");
Serial.println(Ethernet.subnetMask());
Serial.print("My DNS address: ");
Serial.println(Ethernet.dnsServerIP());
Serial.print("My GateWay address: ");
Serial.println(Ethernet.gatewayIP());
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();
}
EthernetUDP Udp;
const uint8_t NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];
char bjtime[256];
void sendNTPpacket(const char *address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); // NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
void ntp()
{
while (true)
{
sendNTPpacket("time.nist.gov"); // send an NTP packet to a time server
// wait to see if a reply is available
delay(1000);
if (Udp.parsePacket())
{
// We've received a packet, read the data from it
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
// the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, extract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900 = ");
Serial.println(secsSince1900);
// now convert NTP time into everyday time:
Serial.print("Unix time = ");
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears;
// print Unix time:
Serial.println(epoch);
// print the hour, minute and second:
sprintf(bjtime, "%d : %d : %d",
(epoch % 86400L) / 3600 + 8,
(epoch % 3600) / 60,
epoch % 60);
return;
}
// wait 3 seconds before asking for the time again
Serial.println("Retry in 3 seconds...");
delay(3000);
}
}
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
while (!Serial)
;
setNet();
Udp.begin(8888);
ntp();
Serial.println(bjtime);
Serial1.println(bjtime);
}
void loop() {
}
终极任务二:使用外部存储器,组建简易FTP文件服务器,并能正常上传下载文件。
这个任务我本来打算继续在arduino平台完成的,虽然ftp部分没有很大的问题,但是储存这块出了问题。不管是littlefs,还是SD库,我尝试了各种主流的sd卡驱动库,都无法在修改默认SPI的情况下成功读取并写入SD卡。因此只好转战micropython,因为micropython可以直接在内存中虚拟出一块区域作为储存使用。
github中8266的uftp代码可以直接使用,唯一需要更改的一个小地方,就是将我们的IP地址传入即可。
import socket
import network
import uos
import gc
from time import localtime
from machine import Pin,SPI
import time
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.188','255.255.255.0','192.168.50.1','8.8.8.8'))#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])
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()
ftpserver()
项目视频
项目源码
https://download.eeworld.com.cn/detail/%E6%B1%9F%E5%8D%8E210/631263
心得
通过followme活动让我学到了很多,在活动中可以尝试很多之前没有尝试过的平台,希望活动越办越好。