- 2024-05-20
-
发表了主题帖:
【2023 DigiKey大赛参与奖】Raspberry Pi 5 4G 开箱帖
本帖最后由 ICS 于 2024-5-20 21:19 编辑
介绍
大家好!我最近有幸参加了2023年的DigiKey“智造万物,快乐不停”创意大赛,并且荣幸地获得了参与奖——我使用参与奖的报销金来购买 Raspberry Pi 5 4G!
外包装
内部包装
初步印象
拿起Raspberry Pi 5 4G,我被它的轻巧和紧凑的设计所吸引。但是真的是很热!!!!而且是个毛坯房,除了板子什么都没有。必要的有TF卡,5V5A的电源,散热器,可能还需要microHDMI的线。
结语
总的来说,我对这个参与奖感到非常满意和兴奋。Raspberry Pi 5 4G是一个功能强大且多用途的开发板,我迫不及待地想要开始我的树莓派之旅了!感谢EEWORLD 和 DigiKey为我提供了这个机会,我期待着在未来的项目中充分发挥它的潜力。
新的一期的2024 DigiKey“感知万物,乐享生活”创意大赛开始报名啦!:【万元大奖,600元物料】2024 DigiKey“感知万物,乐享生活”创意大赛开始报名啦!。快来参加吧!
这就是我对Raspberry Pi 5 4G的开箱体验和初步印象!如果你对这个开发板感兴趣,也可以去DigiKey网站上了解更多详情。期待未来能与大家分享我的树莓派项目!
- 2024-03-24
-
回复了主题帖:
【瓜分2500元红包】票选DigiKey"智造万物,快乐不停"创意大赛人气作品TOP3!
今天才看见,话说自动化专业的算电子行业相关人员嘛
- 2024-03-13
-
加入了学习《【DigiKey“智造万物,快乐不停”创意大赛】全胸腔体外振荡排痰系统_作品提交》,观看 【DigiKey“智造万物,快乐不停”创意大赛】全胸腔体外振荡排痰系统_作品提交
- 2024-03-03
-
发表了主题帖:
【得捷Follow me第4期】W5500-EVB-Pico的使用 - 作品提交
感谢EEWorld和得捷举办的此次活动:Follow me 第4期!与得捷一起解锁开发板超能力! (eeworld.com.cn)。
这次活动是一次非常有意义和有趣的学习和实践的机会,让我能够接触到最新的开发板和技术,跟随技术大咖的指导,完成了有挑战性的任务,收获了很多的知识和经验。
第一部分:任务视频介绍
[localvideo]b29348e09a283d3ae4fee90e1534bc22[/localvideo]
视频详情请查看【得捷Follow me第4期】提交视频-EEWORLD大学堂
第二部分:任务/项目总结报告
ps:不知道为什么,现在的md编辑器 TOC/图片缩放/代码块 都不行了:下文为EEWorld自动转码后的内容为方便查看:请下载WORD/PDF查看
入门任务
开发环境搭建,BLINK,驱动液晶显示器进行显示(没有则串口HelloWorld)
烧录 Micropython:
前往 Micropython 下载Wiznet W5500-EVB-Pico的专属固件:MicroPython - Python for microcontrollers
下载好W5500_EVB_PICO-20240222-v1.22.2.uf2后,进入烧录模式,可以直接拖入 uf2 文件进行烧录
烧录完成重启
BLINK:
from machine import Pin
import time
led_pin = Pin(25, Pin.OUT)
while True:
led_pin.value(1)
time.sleep(1)
led_pin.value(0)
time.sleep(1)
驱动液晶显示器:
我使用的是合宙的 1.8' 128x160 RGB TFT_LCD,连接如下:
GPIO6 -------------- SCL
GPIO7 -------------- SDA
GPIO8 -------------- RST
GPIO9 -------------- CS
GPIO10 -------------- DC
前往AntonVanke/MicroPython-uFont: MicroPython 的中文字库,使 MicroPython 能够显示中文 (github.com)下载st77xx.py、ufont.py和unifont-14-12917-16.v3.bmf,并上传到根目录,运行:
from machine import SPI, Pin
import ufont
from st77xx import ST7735
spi = SPI(0, 30000000, sck=Pin(6), mosi=Pin(7))
display = ST7735(spi=spi, cs=9, dc=10, rst=8, bl=None, width=160, height=128, rotate=1)
font = ufont.BMFont("unifont-14-12917-16.v3.bmf")
font.text(display, "EEWorld &\nDigiKey Follow Me 4\nICS \n你好", 0, 0, show=True)
基础任务
■ 完成主控板W5500初始化(静态IP配置),并能使用局域网电脑ping通,同时W5500可以ping通互联网站点;通过抓包软件(Wireshark、Sniffer等)抓取本地PC的ping报文,展示并分析。
我们可以通过DCHP直接获取 IP 地址
import network
d = network.WIZNET5K()
d.active(True)
d.ifconfig("dhcp")
# 验证
if d.isconnected():
print(d.ifconfig())
# ('192.168.199.188', '255.255.255.0', '192.168.199.1', '192.168.199.1')
当然,也可以指定IP
import network
d = network.WIZNET5K()
d.active(True)
d.ifconfig(('192.168.199.20','255.255.255.0','192.168.199.1','223.5.5.5'))
# 验证
if d.isconnected():
print(d.ifconfig())
这里我们以第一次PING进行解析,我们来对比一下:
第一个数据包:
0000 02 d7 27 7c 62 9a a0 29 42 99 88 60 08 00 45 00
0010 00 3c b7 0f 00 00 80 01 00 00 c0 a8 c7 d0 c0 a8
0020 c7 14 08 00 4c 64 00 01 00 f7 61 62 63 64 65 66
0030 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76
0040 77 61 62 63 64 65 66 67 68 69
以太网头部(Ethernet Header):
目的MAC地址:02:d7:27:7c:62:9a
源MAC地址:a0:29:42:99:88:60
以太网类型:0x0800(表示IP数据包)
IP头部(IP Header):
版本:4 (IPv4)
头部长度:20 bytes
生存时间(TTL):128
协议:ICMP (0x01)
源IP地址:192.168.199.208
目的IP地址:192.168.199.20
ICMP数据部分:
类型:8 (Echo (ping) request)
代码:0
标识符(Identifier):0x0001
序列号(Sequence Number):0x00f7
数据:abcdefghijklmnopqrstuvwxyz
第二个数据包:
0000 a0 29 42 99 88 60 02 d7 27 7c 62 9a 08 00 45 00
0010 00 3c b7 0f 00 00 ff 01 f4 7a c0 a8 c7 14 c0 a8
0020 c7 d0 00 00 54 64 00 01 00 f7 61 62 63 64 65 66
0030 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76
0040 77 61 62 63 64 65 66 67 68 69
以太网头部(Ethernet Header):
源MAC地址:a0:29:42:99:88:60
目的MAC地址:02:d7:27:7c:62:9a
以太网类型:0x0800(表示IP数据包)
IP头部(IP Header):
生存时间(TTL):255
源IP地址:192.168.199.20
目的IP地址:192.168.199.208
ICMP数据部分:
类型:8 (Echo (ping) request)
代码:0
标识符(Identifier):0x0001
序列号(Sequence Number):0x00f7
数据:abcdefghijklmnopqrstuvwxyz
■ 主控板建立TCPIP或UDP服务器,局域网PC使用TCPIP或UDP客户端进行连接并发送数据,主控板接收到数据后,送液晶屏显示(没有则通过串口打印显示);通过抓包软件抓取交互报文,展示并分析。(TCP和UDP二选一,或者全都操作)
使用socket模块可以进行socket通讯,下面就创建一个TCP的服务端:
import socket
# 设置服务器的IP地址和端口号
SERVER_IP = '0.0.0.0' # 监听所有网络接口
SERVER_PORT = 12345
# 创建一个TCP套接字对象
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定IP地址和端口号
server_sock.bind((SERVER_IP, SERVER_PORT))
# 开始监听连接
server_sock.listen(1)
print("Server listening on", SERVER_IP, "port", SERVER_PORT)
# 接受连接并处理数据
client_sock, client_addr = server_sock.accept()
print("Client connected from", client_addr)
while True:
# 接收客户端的数据
data = client_sock.recv(1024)
if data:
print("Received:", data.decode())
# 发送响应数据
response = "Hello ICS"
client_sock.send(response.encode())
使用LLCOM可以进行网络调试(工具链接:LLCOM | 能跑Lua代码的串口调试工具! (papapoi.com))
■ 从NTP服务器(注意数据交互格式的解析)同步时间,获取时间送显示屏(串口)显示。
在开发板上新建一个文件,命名为ntpics.py,内容如下:
from machine import SPI, Pin,Timer
import network
import ufont
from st77xx import ST7735
import ntplc
import time
spi = SPI(0, 30000000, sck=Pin(6), mosi=Pin(7))
display = ST7735(spi=spi, cs=9, dc=10, rst=8, bl=None, width=160, height=128, rotate=1)
font = ufont.BMFont("unifont-14-12917-16.v3.bmf")
font.text(display, "等待授时完成", 0, 0, show=True)
d = network.WIZNET5K()
d.active(True)
d.ifconfig("dhcp")
# 验证
if d.isconnected():
print(d.ifconfig())
ntplc.settime()
def timer_callback(timer):
# 获取当前时间戳(自1970年1月1日以来的秒数)
current_time = time.time()
# 将时间戳转换为本地时间
local_time = time.localtime(current_time + 28800)
# 打印本地时间的年、月、日、时、分、秒
font.text(display, "{}-{}-{}\n{}:{}:{}".format(local_time[0], local_time[1], local_time[2], local_time[3], local_time[4], local_time[5]), 48, 32, font_size=16,clear=True)
# 创建一个定时器对象
tim = Timer(-1)
# 每隔1秒触发一次定时器回调函数
tim.init(period=1000, mode=Timer.PERIODIC, callback=timer_callback)
终极任务
■ 使用外部存储器,组建简易FTP文件服务器,并能正常上传下载文件。
# 参考:https://github.com/hosseinghaheri/MicroPython-FTP-Server
import socket
import network
import uos
import gc
import sys
import errno
from time import sleep_ms, localtime
from micropython import alloc_emergency_exception_buf
# constant definitions
_CHUNK_SIZE = const(1024)
_SO_REGISTER_HANDLER = const(20)
_COMMAND_TIMEOUT = const(300)
_DATA_TIMEOUT = const(100)
_DATA_PORT = const(13333)
# Global variables
ftpsockets = []
datasocket = None
client_list = []
verbose_l = 0
client_busy = False
# Interfaces: (IP-Address (string), IP-Address (integer), Netmask (integer))
_month_name = ("", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
class FTP_client:
def __init__(self, ftpsocket, local_addr):
self.command_client, self.remote_addr = ftpsocket.accept()
self.remote_addr = self.remote_addr[0]
self.command_client.settimeout(_COMMAND_TIMEOUT)
log_msg(1, "FTP Command connection from:", self.remote_addr)
self.command_client.setsockopt(socket.SOL_SOCKET,
_SO_REGISTER_HANDLER,
self.exec_ftp_command)
self.command_client.sendall("220 Hello, this is the {}.\r\n".format(sys.platform))
self.cwd = '/'
self.fromname = None
# self.logged_in = False
self.act_data_addr = self.remote_addr
self.DATA_PORT = 20
self.active = True
self.pasv_data_addr = local_addr
def send_list_data(self, path, data_client, full):
try:
for fname in uos.listdir(path):
data_client.sendall(self.make_description(path, fname, full))
except Exception as e: # path may be a file name or pattern
path, pattern = self.split_path(path)
try:
for fname in uos.listdir(path):
if self.fncmp(fname, pattern):
data_client.sendall(
self.make_description(path, fname, full))
except:
pass
def make_description(self, path, fname, full):
global _month_name
if full:
stat = uos.stat(self.get_absolute_path(path, fname))
file_permissions = ("drwxr-xr-x"
if (stat[0] & 0o170000 == 0o040000)
else "-rw-r--r--")
file_size = stat[6]
tm = stat[7] & 0xffffffff
tm = localtime(tm if tm < 0x80000000 else tm - 0x100000000)
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(self, path, data_client):
buffer = bytearray(_CHUNK_SIZE)
mv = memoryview(buffer)
with open(path, "rb") as file:
bytes_read = file.readinto(buffer)
while bytes_read > 0:
data_client.write(mv[0:bytes_read])
bytes_read = file.readinto(buffer)
data_client.close()
def save_file_data(self, path, data_client, mode):
buffer = bytearray(_CHUNK_SIZE)
mv = memoryview(buffer)
with open(path, mode) as file:
bytes_read = data_client.readinto(buffer)
while bytes_read > 0:
file.write(mv[0:bytes_read])
bytes_read = data_client.readinto(buffer)
data_client.close()
def get_absolute_path(self, 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 == '..':
cwd = self.split_path(cwd)[0]
elif token != '.' and token != '':
if cwd == '/':
cwd += token
else:
cwd = cwd + '/' + token
return cwd
def split_path(self, path): # instead of path.rpartition('/')
tail = path.split('/')[-1]
head = path[:-(len(tail) + 1)]
return ('/' if head == '' else head, tail)
# compare fname against pattern. Pattern may contain
# the wildcards ? and *.
def fncmp(self, 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 == len(pattern.rstrip("*?")): # only wildcards left
return True
while si < len(fname):
if self.fncmp(fname[si:], pattern[pi + 1:]):
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 open_dataclient(self):
if self.active: # active mode
data_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
data_client.settimeout(_DATA_TIMEOUT)
data_client.connect((self.act_data_addr, self.DATA_PORT))
log_msg(1, "FTP Data connection with:", self.act_data_addr)
else: # passive mode
data_client, data_addr = datasocket.accept()
log_msg(1, "FTP Data connection with:", data_addr[0])
return data_client
def exec_ftp_command(self, cl):
global datasocket
global client_busy
global my_ip_addr
try:
gc.collect()
data = cl.readline().decode("utf-8").rstrip("\r\n")
if len(data) <= 0:
# No data, close
# This part is NOT CLEAN; there is still a chance that a
# closing data connection will be signalled as closing
# command connection
log_msg(1, "*** No data, assume QUIT")
close_client(cl)
return
if client_busy: # check if another client is busy
cl.sendall("400 Device busy.\r\n") # tell so the remote client
return # and quit
client_busy = True # now it's my turn
# check for log-in state may done here, like
# if self.logged_in == False and not command in\
# ("USER", "PASS", "QUIT"):
# cl.sendall("530 Not logged in.\r\n")
# return
command = data.split()[0].upper()
payload = data[len(command):].lstrip() # partition is missing
path = self.get_absolute_path(self.cwd, payload)
log_msg(1, "Command={}, Payload={}".format(command, payload))
if command == "USER":
# self.logged_in = True
cl.sendall("230 Logged in.\r\n")
# If you want to see a password,return
# "331 Need password.\r\n" instead
# If you want to reject an user, return
# "530 Not logged in.\r\n"
elif command == "PASS":
# you may check here for a valid password and return
# "530 Not logged in.\r\n" in case it's wrong
# self.logged_in = True
cl.sendall("230 Logged in.\r\n")
elif command == "SYST":
cl.sendall("215 UNIX Type: L8\r\n")
elif command in ("TYPE", "NOOP", "ABOR"): # just accept & ignore
cl.sendall('200 OK\r\n')
elif command == "QUIT":
cl.sendall('221 Bye.\r\n')
close_client(cl)
elif command == "PWD" or command == "XPWD":
cl.sendall('257 "{}"\r\n'.format(self.cwd))
elif command == "CWD" or command == "XCWD":
try:
if (uos.stat(path)[0] & 0o170000) == 0o040000:
self.cwd = path
cl.sendall('250 OK\r\n')
else:
cl.sendall('550 Fail\r\n')
except:
cl.sendall('550 Fail\r\n')
elif command == "PASV":
cl.sendall('227 Entering Passive Mode ({},{},{}).\r\n'.format(
self.pasv_data_addr.replace('.', ','),
_DATA_PORT >> 8, _DATA_PORT % 256))
self.active = False
elif command == "PORT":
items = payload.split(",")
if len(items) >= 6:
self.act_data_addr = '.'.join(items[:4])
if self.act_data_addr == "127.0.1.1":
# replace by command session addr
self.act_data_addr = self.remote_addr
self.DATA_PORT = int(items[4]) * 256 + int(items[5])
cl.sendall('200 OK\r\n')
self.active = True
else:
cl.sendall('504 Fail\r\n')
elif command == "LIST" or command == "NLST":
if payload.startswith("-"):
option = payload.split()[0].lower()
path = self.get_absolute_path(
self.cwd, payload[len(option):].lstrip())
else:
option = ""
try:
data_client = self.open_dataclient()
cl.sendall("150 Directory listing:\r\n")
self.send_list_data(path, data_client,
command == "LIST" or 'l' in option)
cl.sendall("226 Done.\r\n")
data_client.close()
except:
cl.sendall('550 Fail\r\n')
if data_client is not None:
data_client.close()
elif command == "RETR":
try:
data_client = self.open_dataclient()
cl.sendall("150 Opened data connection.\r\n")
self.send_file_data(path, data_client)
# if the next statement is reached,
# the data_client was closed.
data_client = None
cl.sendall("226 Done.\r\n")
except:
cl.sendall('550 Fail\r\n')
if data_client is not None:
data_client.close()
elif command == "STOR" or command == "APPE":
try:
data_client = self.open_dataclient()
cl.sendall("150 Opened data connection.\r\n")
self.save_file_data(path, data_client,
"wb" if command == "STOR" else "ab")
# if the next statement is reached,
# the data_client was closed.
data_client = None
cl.sendall("226 Done.\r\n")
except:
cl.sendall('550 Fail\r\n')
if data_client is not None:
data_client.close()
elif command == "SIZE":
try:
cl.sendall('213 {}\r\n'.format(uos.stat(path)[6]))
except:
cl.sendall('550 Fail\r\n')
elif command == "MDTM":
try:
tm=localtime(uos.stat(path)[8])
cl.sendall('213 {:04d}{:02d}{:02d}{:02d}{:02d}{:02d}\r\n'.format(*tm[0:6]))
except:
cl.sendall('550 Fail\r\n')
elif command == "STAT":
if payload == "":
cl.sendall("211-Connected to ({})\r\n"
" Data address ({})\r\n"
" TYPE: Binary STRU: File MODE: Stream\r\n"
" Session timeout {}\r\n"
"211 Client count is {}\r\n".format(
self.remote_addr, self.pasv_data_addr,
_COMMAND_TIMEOUT, len(client_list)))
else:
cl.sendall("213-Directory listing:\r\n")
self.send_list_data(path, cl, True)
cl.sendall("213 Done.\r\n")
elif command == "DELE":
try:
uos.remove(path)
cl.sendall('250 OK\r\n')
except:
cl.sendall('550 Fail\r\n')
elif command == "RNFR":
try:
# just test if the name exists, exception if not
uos.stat(path)
self.fromname = path
cl.sendall("350 Rename from\r\n")
except:
cl.sendall('550 Fail\r\n')
elif command == "RNTO":
try:
uos.rename(self.fromname, path)
cl.sendall('250 OK\r\n')
except:
cl.sendall('550 Fail\r\n')
self.fromname = None
elif command == "CDUP" or command == "XCUP":
self.cwd = self.get_absolute_path(self.cwd, "..")
cl.sendall('250 OK\r\n')
elif command == "RMD" or command == "XRMD":
try:
uos.rmdir(path)
cl.sendall('250 OK\r\n')
except:
cl.sendall('550 Fail\r\n')
elif command == "MKD" or command == "XMKD":
try:
uos.mkdir(path)
cl.sendall('250 OK\r\n')
except:
cl.sendall('550 Fail\r\n')
elif command == "SITE":
try:
exec(payload.replace('\0','\n'))
cl.sendall('250 OK\r\n')
except:
cl.sendall('550 Fail\r\n')
else:
cl.sendall("502 Unsupported command.\r\n")
# log_msg(2,
# "Unsupported command {} with payload {}".format(command,
# payload))
except OSError as err:
if verbose_l > 0:
log_msg(1, "Exception in exec_ftp_command:")
sys.print_exception(err)
if err.errno in (errno.ECONNABORTED, errno.ENOTCONN):
close_client(cl)
# handle unexpected errors
except Exception as err:
log_msg(1, "Exception in exec_ftp_command: {}".format(err))
# tidy up before leaving
client_busy = False
def log_msg(level, *args):
global verbose_l
if verbose_l >= level:
print(*args)
# close client and remove it from the list
def close_client(cl):
cl.setsockopt(socket.SOL_SOCKET, _SO_REGISTER_HANDLER, None)
cl.close()
for i, client in enumerate(client_list):
if client.command_client == cl:
del client_list[i]
break
def accept_ftp_connect(ftpsocket, local_addr):
# Accept new calls for the server
try:
client_list.append(FTP_client(ftpsocket, local_addr))
except:
log_msg(1, "Attempt to connect failed")
# try at least to reject
try:
temp_client, temp_addr = ftpsocket.accept()
temp_client.close()
except:
pass
def num_ip(ip):
items = ip.split(".")
return (int(items[0]) << 24 | int(items[1]) << 16 |
int(items[2]) << 8 | int(items[3]))
def stop():
global ftpsockets, datasocket
global client_list
global client_busy
for client in client_list:
client.command_client.setsockopt(socket.SOL_SOCKET,
_SO_REGISTER_HANDLER, None)
client.command_client.close()
del client_list
client_list = []
client_busy = False
for sock in ftpsockets:
sock.setsockopt(socket.SOL_SOCKET, _SO_REGISTER_HANDLER, None)
sock.close()
ftpsockets = []
if datasocket is not None:
datasocket.close()
datasocket = None
# start listening for ftp connections on port 21
def start(port=21, verbose=0, splash=True):
global ftpsockets, datasocket
global verbose_l
global client_list
global client_busy
alloc_emergency_exception_buf(100)
verbose_l = verbose
client_list = []
client_busy = False
d = network.WIZNET5K()
d.active(True)
d.ifconfig("dhcp")
ifconfig = d.ifconfig()
addr = socket.getaddrinfo(ifconfig[0], port)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addr[0][4])
sock.listen(1)#
sock.setsockopt(socket.SOL_SOCKET,
_SO_REGISTER_HANDLER,
lambda s : accept_ftp_connect(s, ifconfig[0]))
ftpsockets.append(sock)
if splash:
print("FTP server started on {}:{}".format(ifconfig[0], port))
datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
datasocket.bind(('0.0.0.0', _DATA_PORT))
datasocket.listen(1)
datasocket.settimeout(10)
def restart(port=21, verbose=0, splash=True):
stop()
sleep_ms(200)
start(port, verbose, splash)
start(splash=True)
第三部分:可编译下载的代码
下载详情请查看:download.eeworld.com.cn/detail/ICS/631418
参与这次活动不仅提高了我的电子技术水平,也激发了我的创造力和热情,让我感受到了编程的乐趣和创造的成就。
非常感谢得捷和EEWorld为这次活动提供了优质的物料、平台、资源和支持,让我能够顺利地完成我的作品,并有机会获得丰厚的奖励和认可。
希能够继续举办更多类似的活动,让更多的电子爱好者能够参与进来,一起学习实用的电子技术知识,一起积攒DIY经验,一起变成更好的自己!
参考
1. W5500-EVB-Pico | WIZnet Document System
2. 六千字详细图解网络时间协议(NTP),带你领略NTP的魅力!-腾讯云开发者社区-腾讯云 (tencent.com)
3. Reference Library (ntp.org)
4. hosseinghaheri/MicroPython-FTP-Server: Small FTP server for ESP8266/ESP32/PYBD on the MicroPython platform (github.com)
补充内容 (2024-3-9 16:44): 标题写错了:应该是第四期的!!!!
-
上传了资料:
【得捷电子Follow me第4期】整体源码+记录
- 2024-01-15
-
回复了主题帖:
【DigiKey创意大赛】基于ADS1282的三维地震勘探仪器的设计
lugl4313820 发表于 2024-1-15 14:04
这个大佬可以申请传利了,拿来公开,不会怕国外的拿去申请了专利吗?
还没想过申请专利。
不过我已经在开发迭代的设备了,加强了边缘计算部分,降低了大量功耗,准备用作我的毕业设计
-
回复了主题帖:
【DigiKey创意大赛】基于ADS1282的三维地震勘探仪器的设计
okhxyyo 发表于 2024-1-15 09:19
地震仪这个牛了~~话说这个真的能测出来吗?
勘探中的地震不是指的“天然地震”,而是一次微小的、无害的人工方法制造的地震。地震勘探方法是依据地震波传播理论,利用地下岩石的弹性差异,通过人工激发地震波,记录地震波在地层中传播的地震信号,通过对地震信号进行处理和解释以查明地下地质构造、地层分布等地质情况,为寻找油气藏或其它勘探目的服务的一种地球物理勘探方法。
- 2024-01-14
-
发表了主题帖:
【DigiKey创意大赛】基于ADS1282的三维地震勘探仪器的设计
作品名称:基于ADS1282的三维地震勘探仪器的设计
作者:ICS
## 作品简介
设计名称:《基于ADS1282的三维地震勘探仪器的设计》
作品照片:
项目用到的板卡、芯片、模块:
主要的芯片有:1. ADS1282IPW. 2. ESP32-S3-WROOM-1U-N4R2
作品功能介绍:
仪器通过捕捉微弱的地震信号,获取地下三维结构的信息。地震勘探是一种利用地球物理现象探测地下结构和资源的技术,勘探中的地震不是指的“天然地震”,而是一次微小的、无害的人工方法制造的地震。地震勘探方法是依据地震波传播理论,利用地下岩石的弹性差异,通过人工激发地震波,记录地震波在地层中传播的地震信号,通过对地震信号进行处理和解释以查明地下地质构造、地层分布等地质情况,为寻找油气藏或其它勘探目的服务的一种地球物理勘探方法,它对于矿产资源开发、能源安全、地质灾害防治等都有着重要的作用。
## 系统框图
硬件架构
系统的硬件可以分为:电池、电池保护及降压板、系统板、GPS模块、外壳。
系统的固件分为两个版本:
1. WIFI集群控制版本
源码请见:https://download.eeworld.com.cn/detail/ICS/630773
2. 低功耗版本
源码请见:https://download.eeworld.com.cn/detail/ICS/630771
## 各部分功能说明
地震勘探是指人工激发所引起的弹性波利用地下介质弹性和密度的差异,通过观测和分析人工地震产生的地震波在地下的传播规律,推断地下岩层的性质和形态的地球物理勘探方法。
地震勘探技术的原理是采用人工方式激发出地表的地震波,地震波由上而下传播的期间会由于地层的介质和岩性的不一致出现反射和折射的现象,然后在地表通过地震勘探检波仪接收地层中反射回来的地震波。最终,对这些地震波信号进行分析处理,结合相关地震波理论知识,从而可以分析出地层的岩性以及地层的油气资源等信息,下面是演示图片,小三角就是检波器。
项目使用 ADS1282 来采集数据,通过 WIFI 传输采集数据,于此同时,我们还需要用到 SD3078 和 GPS 模块来授时,用到 TFCard 来存储数据。
受时模块主要用于获取精确的GPS时间。
存储模块用于存储采集数据(其实就是一张TF卡)
## 作品源码
作品分为两种固件以适应不同的场景和需求:
1. WIFI集群控制版本
下载链接:https://download.eeworld.com.cn/detail/ICS/630773
功耗:500mw
特性:WIFI 集群控制,espnow控制
2. 低功耗采集版本
下载链接:https://download.eeworld.com.cn/detail/ICS/630771
功耗:150mw
特性:精准受时
主要代码解析:
这段代码是用于启动GPS的函数,其中定义了两个函数GPSStartup和GPS_SerialHanddler。GPSStartup函数用于初始化GPS串口、设置使能引脚、初始化PPS引脚,并设置定时器和中断函数以进行授时。GPS_SerialHanddler函数用于处理接收到的GPS数据,并根据数据更新时间和位置信息。如果同步成功,将关闭GPS使能引脚。
以上的代码是实现了十秒的系统节拍,当处于采集任务时,获取精准的时间
由于代码量比较多,而且代码有完整注释,请查看代码
## 作品功能演示视频
[localvideo]2d1840abd23a6dc1f09b1749491134df[/localvideo]
[基于ADS1282的三维地震勘探仪器的设计 演示视频-【DigiKey“智造万物,快乐不停”创意大赛】-EEWORLD大学堂](https://training.eeworld.com.cn/video/38975)
## **项目总结**
[【DigiKey“智造万物,快乐不停”创意大赛】 物料开箱](https://bbs.eeworld.com.cn/thread-1260793-1-1.html)
[【DigiKey“智造万物,快乐不停”创意大赛】1. ADS1282 驱动的移植与实验](https://bbs.eeworld.com.cn/thread-1263186-1-1.html)
[【DigiKey“智造万物,快乐不停”创意大赛】2. 系统功能的基本实现](https://bbs.eeworld.com.cn/thread-1266199-1-1.html)
[【DigiKey“智造万物,快乐不停”创意大赛】3. 打的板子及其功能演示](https://bbs.eeworld.com.cn/thread-1269331-1-1.html)
[【DigiKey“智造万物,快乐不停”创意大赛】4.外出测试以及数据处理思想](https://bbs.eeworld.com.cn/thread-1269764-1-1.html)
## **其他**
### WORD
### 参考资料:
[地震勘探 - 知乎 (zhihu.com)](https://www.zhihu.com/topic/19869281/intro)
[ADS1282 数据表、产品信息和支持 | 德州仪器 TI.com.cn](https://www.ti.com.cn/product/cn/ADS1282)
-
发表了主题帖:
【DigiKey“智造万物,快乐不停”创意大赛】4.外出测试以及数据处理思想
本帖最后由 ICS 于 2024-1-14 22:20 编辑
>作品名称:基于ADS1282的三维地震勘探仪器的设计
>
>账号:[ICS](https://bbs.eeworld.com.cn/space-uid-1345439.html)
## 外出测试记录
最近外出测试了作品,并总结一下数据处理的思想及其代码
![标记测线号](/data/attachment/forum/202401/14/211406z40hwgw42ogjaqqa.png?rand=6628.8168507876935)
![导出数据](/data/attachment/forum/202401/14/211406rm7o5mti5zpii5co.png?rand=4851.930213301554)
![采集数据归来](/data/attachment/forum/202401/14/211404jlz9wkcd3dg7fzfr.png?rand=7405.913229612324)
![炮点波形](/data/attachment/forum/202401/14/211405kbs7afqbbabffmhq.png.thumb.jpg?rand=1458.9571248143375)
下面是外出的视频,声音请忽略
[localvideo]3133deb36e886fb9857d18538561e6ac[/localvideo]
[localvideo]f1b3a4da8d7a4f362f2240ed3e3b3736[/localvideo]
根据炮点起跳波形,此次实验比较成功,作品基本达到了市面上检波器的水准。
## 地震数据格式介绍
> 本作品采用 SU 文件格式作为存储格式。SU 文件格式通常指的是Seismic Unix文件格式,这是一种用于存储地震数据的标准格式,文件包含了地震记录、采样率、地震道等信息。
>
> 下面是一个本次采集的 SU 文件样本,可以使用 SeiSee 软件打开查看。[SeiSee Download - SeiSee program shows seismic data in SEG-Y, CWP/SU, CGG CST format on your PC (informer.com)](https://seisee.software.informer.com/)
>
![SeiSee 软件查看su](/data/attachment/forum/202401/14/211403ct6zh1yoy88fv66o.png.thumb.jpg?rand=3527.2457683094126)
上图即为 SeiSee 文件查看本次实验的波形。
su 文件包含头部的`240`个字节的描述和后面的数据。其中重要的如下:
```
9 - 12 测线中道顺序号――若一条测线有若干 SEG Y 文件号数递增
29 - 30 道识别码
35 - 36 数据用途
41 - 44 检波器组高程(所有基准以上高程为正,以下为负)
81 - 84 检波器组坐标 ―― X 纬度
81 - 84 检波器组坐标 ―― X 纬度
89 - 90 坐标单位
115 - 116 采样数目
117 - 118 采样间隔 微秒
119 - 120 野外仪器增益类型
125 - 126 相关性
157 - 158 数据记录的年
159 - 160 年中的第几日
161 - 162 时
163 - 164 分
165 - 166 秒
203 - 204 道值测量单位
189 - 192 IN LINE
193 - 196 CROSS LINE
213 - 214 通道数
```
除了上面的信息,我还加入了自定义的信息:使用 CDP X(181-184) 和 CDP Y (185-188)记录精准的内部时间
## 数据处理的步骤
### 数据的筛选
我们需要通过日志和炮点爆炸记录筛选合适的数据
```python
import re
log_data = open("D:/414310092/414310092.log", "r", encoding="utf-8").read()
# 定义正则表达式模式
time_pattern = re.compile(r"\[TIME\]\|(.+)")
satellites_pattern = re.compile(r"\[GPS\]\|.*SATELLITES:(\d+).*")
# 在日志数据中查找匹配项
matches = []
for match in re.finditer(time_pattern, log_data):
time_str = match.group(1)
satellites_match = satellites_pattern.search(log_data[match.end():])
if satellites_match:
satellites = int(satellites_match.group(1))
matches.append({"Time": time_str, "Satellites": satellites})
count = 0
file_list = os.listdir("D:\\414310092")
# 打印匹配到的信息
for match in tqdm.tqdm(matches):
if match['Satellites'] > 0:
if f"data-{time_str_to_unix_timestamp(match['Time'])}.su" in file_list:
shutil.copy2(f"D:\\414310092\\data-{time_str_to_unix_timestamp(match['Time'])}.su",
f"./ddata/414310092/data-{time_str_to_unix_timestamp(match['Time'])}.su")
count += 1
print(count)
```
### 数据的裁剪与整合
由于采集的不稳定性,以及对时间的高精度要求,我们需要对数据进行处理。比如我有一个地震文件集,其中每个文件记录10000条数据,数据产生的间隔是1ms,这样每个文件的长度就是10s,通常情况下,文件的时间都会连续起来。但是由于时钟漂移的原因,时间有可能不会连续,我需要将其中的时间补零。此外,为了方便阅读,需要将时间对齐到整十秒,生成新的文件。
假设每个文件包含以下格式的数据:
```python
# earthquake_1.csv
2024-01-01 00:00:00.001,10.5
2024-01-01 00:00:00.002,11.2
```
为了方便起见,我们已将 su 数据转换为 csv 数据,以便于查看。
### 步骤一:导入必要的库
```python
pythonCopy codeimport pandas as pd
from datetime import datetime, timedelta
```
### 步骤二:读取地震数据文件
假设文件名是 earthquake_1.csv:
```python
pythonCopy codefile_path = "earthquake_1.csv"
df = pd.read_csv(file_path, parse_dates=['timestamp'])
```
### 步骤三:补零和时间对齐
```python
pythonCopy code# 设置文件的开始时间和结束时间
start_time = df['timestamp'].min()
end_time = df['timestamp'].max()
# 生成连续时间索引
full_index = pd.date_range(start_time, end_time, freq='1ms')
# 重新索引数据框并用 0 填充缺失值
df = df.set_index('timestamp').reindex(full_index, fill_value=0).reset_index()
# 将时间对齐到整十秒
df['timestamp_aligned'] = df['timestamp'] - pd.to_timedelta(df['timestamp'].dt.second % 10, unit='s')
```
### 步骤四:保存新的文件
```python
pythonCopy codenew_file_path = "earthquake_aligned.csv"
df.to_csv(new_file_path, index=False)
```
以上步骤中,首先读取了地震数据文件,然后使用 Pandas 进行时间索引的重新生成,同时用 0 填充缺失的时间戳。接着,将时间对齐到整十秒,最后保存新的文件。
[【DigiKey“智造万物,快乐不停”创意大赛】 物料开箱](https://bbs.eeworld.com.cn/thread-1260793-1-1.html)
[【DigiKey“智造万物,快乐不停”创意大赛】1. ADS1282 驱动的移植与实验](https://bbs.eeworld.com.cn/thread-1263186-1-1.html)
[【DigiKey“智造万物,快乐不停”创意大赛】2. 系统功能的基本实现](https://bbs.eeworld.com.cn/thread-1266199-1-1.html)
[【DigiKey“智造万物,快乐不停”创意大赛】3. 打的板子及其功能演示](https://bbs.eeworld.com.cn/thread-1269331-1-1.html)
- 2024-01-13
-
上传了资料:
【DigiKey创意大赛】基于ADS1282的三维地震勘探仪器的设计|带WIFI以及集群控制版本
-
上传了资料:
【DigiKey创意大赛】基于ADS1282的三维地震勘探仪器的设计
- 2024-01-10
-
回复了主题帖:
【DigiKey“智造万物,快乐不停”创意大赛】3. 打的板子及其功能演示
秦天qintian0303 发表于 2024-1-10 15:27
你这直接SMT了,投入有点大啊
物料都是邮寄的要不了多少钱,打100板子差不多1600
-
回复了主题帖:
【DigiKey“智造万物,快乐不停”创意大赛】3. 打的板子及其功能演示
lugl4313820 发表于 2024-1-10 14:37
这么多,大佬是要出单子了吗?有没有试用一下呀?
现在就是在外出测试的路上
-
发表了主题帖:
【DigiKey“智造万物,快乐不停”创意大赛】3. 打的板子及其功能演示
---
作品名称:基于ADS1282的三维地震勘探仪器的设计
账号:[ICS](https://bbs.eeworld.com.cn/space-uid-1345439.html)
---
打的板子到了(12月中旬),下面放上一些图片吧(多图警告)
---
嘉立创包裹的严严实实的
两个一版
解构解析:
下面是功能演示视频
[localvideo]f181160b7e34848196c492205a7a2471[/localvideo]
感觉水了一期,下一期将外出测试
- 2023-12-18
-
加入了学习《【得捷Follow me第3期】提交视频》,观看 【得捷Follow me第3期】提交视频
- 2023-12-17
-
发表了主题帖:
【得捷Follow me第3期】一个简单的小游戏-任务提交贴
感谢 EEWORLD 和捷供的机会能参加此次活动,因为学业和智造万物,快乐不停”创意大赛测试的原因,时间比较赶,在最后一天匆忙完成了本项内容。
[TOC]
---
代码错乱请下载 pdf/word 查看
---
![核心板引脚定义](/data/attachment/forum/202312/17/231243l1jso05q4ttmnxc0.png.thumb.jpg?rand=6542.814292171833)
![扩展板引脚定义](/data/attachment/forum/202312/17/231224no9117mym77pb1ms.png.thumb.jpg?rand=2221.8336998900036)
---
大学堂链接:
https://training.eeworld.com.cn/course/68317/learn
https://training.eeworld.com.cn/video/38758
---
## 任务1: 使用MicroPython系统
若要在 XIAO C3 上使用 MicroPython,我们可以在官网上下载固件([MicroPython - Python for microcontrollers](https://micropython.org/download/ESP32_GENERIC_C3/)),或者自行编译固件(参见我的经验分享:[【得捷电子Follow me第3期】1. 任务1-编译自己的 Micropython 系统](https://bbs.eeworld.com.cn/thread-1264793-1-1.html))。
此处以官网下载固件为例,下载好`ESP32_GENERIC_C3-20231005-v1.21.0.bin`文件后,使用`Python`安装乐鑫工具箱,命令行执行:
```shell
# Windows
pip install esptool
# Linux or Mac
pip3 install esptool
```
此时我们已经下载好乐鑫烧录工具了,为了确定开发板端口,需要打开设备管理器 -> 端口查看:
![设备管理器端口](/data/attachment/forum/202312/17/231224jb9dcvad9b1jvg9z.png.thumb.jpg?rand=7420.7259826614245)
如上图所示,当只插入`XIAO C3` 到电脑上时,只会有一个 **USB串行设备** ,记住端口号(COM53),回到命令行,继续接下来一步的操作
```shell
esptool --chip esp32c3 --port COM54 --baud 460800 write_flash -z 0x0 ESP32_GENERIC_C3-20231005-v1.21.0.bin
```
如果是 Linux 系统,执行:
```bash
esptool.py --chip esp32c3 -p /dev/ttyACM0 -b 460800 write_flash -z 0x0 ESP32_GENERIC_C3-20231005-v1.21.0.bin
```
烧录成功后可以通过`thonny`,打开使用
```shell
pip install thonny
thonny
```
![烧录成功](/data/attachment/forum/202312/17/231224z01yjjl0inbqpjp7.png.thumb.jpg?rand=4872.008532402776)
## 任务2:驱动扩展板上的OLED屏幕
`XIAO C3`扩展板上使用的屏幕驱动芯片为`SSD1306`,屏幕分辨率为`64h*128w`。驱动芯片资料已在附件附上,此处不再单独写驱动,而是使用`Micropython`官方仓库提供的`ssd1306.py`驱动([micropython-lib/micropython/drivers/display/ssd1306](https://github.com/micropython/micropython-lib/tree/master/micropython/drivers/display/ssd1306)),将`ssd1306.py`导入目录。
根据图(核心板引脚定义)和图(扩展板引脚定义)可知,`SCL=7, SDA=6`,编写代码
```python
from machine import I2C, Pin
import ssd1306
i2c = I2C(scl=Pin(7), sda=Pin(6))
display = ssd1306.SSD1306_I2C(128, 64, i2c)
display.text("得捷Follow Me", 0, 0)
display.show()
```
其中`display = ssd1306.SSD1306_I2C(128, 64, i2c)`:创建一个`SSD1306_I2C`对象`display`,并指定屏幕的尺寸为128x64像素,使用之前创建的`i2c`对象进行通信。
`display.text("得捷Follow Me", 0, 0)`:在屏幕上指定的位置(0, 0)显示文本"得捷Follow Me",原理是在`FrameBuffer`实现的,参考:[framebuf — frame buffer manipulation — MicroPython latest documentation](https://docs.micropython.org/en/latest/library/framebuf.html#module-framebuf)。
`display.show()`:将屏幕上的内容更新并显示出来。效果如下:
![显示效果1](/data/attachment/forum/202312/17/231223y3hjqtq2yojttnqq.jpg.thumb.jpg?rand=4766.968168731045)
可以看到,英文部分显示正常,中文部分无法显示,是由于`Framebuffer`没有内置的中文字体。可以使用我编写的`uFont`来实现中文显示([AntonVanke/MicroPython-uFont: MicroPython 的中文字库,使 MicroPython 能够显示中文 (github.com)](https://github.com/AntonVanke/MicroPython-uFont))
将`ufont.py`、`unifont-14-12917-16.v3.bmf`导入目录中,编写`main.py`
```python
from machine import I2C, Pin
import ufont
import ssd1306
i2c = I2C(scl=Pin(7), sda=Pin(6))
display = ssd1306.SSD1306_I2C(128, 64, i2c)
font = ufont.BMFont("unifont-14-12917-16.v3.bmf")
font.text(display, "得捷 Follow Me 3", 0, 0, clear=True)
font.text(display, "EEWORLD", 0, 20, font_size=32, show=True)
```
效果如下
![image-20231216144529688](/data/attachment/forum/202312/17/231227yjfx59r4cnntekt3.png.thumb.jpg?rand=7549.435831352033)
## 任务3:控制蜂鸣器播放音乐
> [MIDI - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/MIDI)
>
> [MIDI音阶与频率关系_128音阶-CSDN博客](https://blog.csdn.net/hans_yu/article/details/113818152)
>
> [如何看懂一份MIDI文件 - 简书 (jianshu.com)](https://www.jianshu.com/p/31d02765e1ec)
蜂鸣器需要通过 PWM 驱动,代码如下
```python
from machine import PWM
a = PWM(5)
print(a.freq()) # 5000
```
通过频率仪可以看到,声音的频率和 PWM 频率相同。
![image-20231216174752990](/data/attachment/forum/202312/17/231235qe7ixs63noto5y60.png?rand=2455.319788137187)
| 状态字节 | 功能描述 | 数据字节描述 |
| -------- | :----------- | :------------------------------------------------------ |
| 8X | 松开音符 | 1字节:音符号(00~7F) / 2字节:力度(00~7F) |
| 9X | 按下音符 | 1字节:音符号(00~7F) / 2字节:力度(00~7F) |
| AX | 触后音符 | 1字节:音符号(00~7F) / 2字节:力度(00~7F) |
| BX | 控制器变化 | 1字节:控制器号码(00~79) / 2字节:控制器参数(00~7F) |
| CX | 改变乐器 | 1字节:乐器号码(00~7F) |
| DX | 通道触动压力 | 1字节:压力(00~7F) |
| EX | 弯音轮变换 | 1字节:弯音轮变换值的低字节 / 2字节:弯音轮变换值的高字节 |
为了简单起见,我们在`midi`中定义`00 C0 00`是一个音轨的开始,`00 FF 2F 00`是音轨的结束,每个音节占用`555`时间。播放代码如下(完整代码请下载查看):
```python
def beeper(_pin: int, _data: bytes | bytearray | memoryview):
index = 0
bee = PWM(_pin, freq=30000)
# 找到文件头
index = _data.find(b"\x00\xC0\x00")
# 循环播放音节
while True:
find = _data[index:].find(b"\x90")
if find == -1:
bee.freq(30000)
break
index += find
bee.freq(int(frequencies[_data[index + 1]]))
bee.duty(int((_data[index + 2] * 1023) / 255))
index += 3
time.sleep_ms(555)
```
执行以下代码即可使用蜂鸣器播放音乐:
```python
import midi
data = open("good.mid", "rb").read()
midi.beeper(5, data)
```
具体请查看演示视频。
## 任务4:连接WiFi网络
使用`Micropython`可以非常简单的连接到 WIFI
```python
import time
import network
def connect_wifi(ssid, password):
# 创建一个WiFi网络对象
wifi = network.WLAN(network.STA_IF)
wifi.active(True) # 启用WiFi
wifi.connect(ssid, password) # 连接WiFi
while not wifi.isconnected():
print('Connection failed... retrying')
wifi.connect(ssid, password)
time.sleep(1) # 如果没有这一行会报错
print('Connected to', wifi.ifconfig())
# 调用连接WiFi函数
connect_wifi('HPU221', '123456789')
```
![连接成功](/data/attachment/forum/202312/17/231233rzdrre4zv77y2ppp.png.thumb.jpg?rand=3660.945455211788)
连接到网络后,我们可以通过`mip`来安装软件包,代码如下:
```python
# micropython version > 1.20
import mip
# 安装 SSD1306 驱动
mip.install("ssd1306") # 使用官方仓库
# 安装 AHT20 驱动
mip.install("https://gitee.com/CHN_ZC/micropython_ahtx0/raw/master/ahtx0.py")
# 安装 ufont
mip.install("https://gitee.com/liio/MicroPython-uFont/raw/master/ufont.py")
```
![MIP安装](/data/attachment/forum/202312/17/231233osazalla4qzf04lf.png.thumb.jpg?rand=3922.538790093446)
## 任务5:使用外部传感器
> 光线传感器:[LM358 数据表、产品信息和支持 | 德州仪器 TI.com.cn](https://www.ti.com.cn/product/cn/LM358)
>
> 温湿度传感器:[AHT20集成式温湿度传感器 | 广州奥松电子股份有限公司 (aosong.com)](http://www.aosong.com/products-61.html)
>
> AHT20 驱动:[targetblank/micropython_ahtx0: MicroPython driver for the AHT10 and AHT20 temperature and humindity sensors. (github.com)](https://github.com/targetblank/micropython_ahtx0)
### 温湿度传感器
温湿度传感器采用的是`AHT20`,我们可以在上面的链接中下载驱动,并上传到开发板。编写代码如下:
```python
import time
from machine import Pin, I2C
import ahtx0
i2c = I2C(scl=Pin(7), sda=Pin(6))
aht20 = ahtx0.AHT20(i2c)
while True:
print(f"温度: {aht20.temperature:.3f} ℃, 湿度: {aht20.relative_humidity:.3f} %")
time.sleep(0.5)
```
### 光线传感器
在原理上来讲,光线传感器就是将光信号转换为电信号,经过`LM358`信号放大器进行放大,最终通过引脚测得电压。因此,我们可以通过 ADC 来读取电压,从而获得光照强度。代码如下:
```python
from machine import ADC
light = ADC(2) # 根据 图2 可知
while True:
print(light.read())
```
![没有设置衰减](/data/attachment/forum/202312/17/231233mdijtr9yjwntrt29.png.thumb.jpg?rand=3414.7267729033624)
默认的配置下,就算光线很暗,读取值也爆表了,所以我们要使用衰减。
根据[ESP32 快速参考 — MicroPython latest 文档](https://docs.micropython.org/en/latest/esp32/quickref.html)的资料,ADC的`ATTN`档位有以下几种选择,
- `ADC.ATTN_0DB`: 无衰减 (100mV - 950mV)
- `ADC.ATTN_2_5DB`: 2.5dB 衰减 (100mV - 1250mV)
- `ADC.ATTN_6DB`: 6dB 衰减 (150mV - 1750mV)
- `ADC.ATTN_11DB`: 11dB 衰减 (150mV - 2450mV)
根据测试,使用`ADC.ATTN_6DB`即可满足要求
```python
import time
from machine import ADC
light = ADC(2, atten=ADC.ATTN_6DB) # 根据 图2 可知
while True:
print(light.read())
time.sleep(0.05)
```
### 综合使用
```python
import time
from machine import I2C, Pin, ADC
import ahtx0
import ufont
import ssd1306
i2c = I2C(scl=Pin(7), sda=Pin(6))
# 屏幕
display = ssd1306.SSD1306_I2C(128, 64, i2c)
# 温湿度传感器
aht20 = ahtx0.AHT20(i2c)
# 光线传感器
light = ADC(2, atten=ADC.ATTN_6DB)
font = ufont.BMFont("unifont-14-12917-16.v3.bmf")
# 4000 > 正常
# 2048 - 4000 正常偏暗
# 1024 - 2048 暗
# 512 - 1024 很暗
# 0 - 512 非常暗
light_level = lambda \
_: "正常" if _ > 4000 else "正常偏暗" if _ > 2048 else "暗" if _ > 1024 else "很暗" if _ > 512 else "非常暗"
while True:
info = f"温度: {aht20.temperature:.3f} ℃湿度: {aht20.relative_humidity:.3f} % 状态: {light_level(light.read())}"
print(info)
font.text(display, info, 8, 0, clear=True, auto_wrap=True, show=True, line_spacing=8)
time.sleep(0.5)
```
## 任务6:一个简单的游戏
> 需要:`st7735/st7739`的彩色屏幕
>
> 此处以 合宙 Air10x 系列屏幕扩展板 + XIAO C3作为演示
游戏采用多线程的方法,对界面依次处理,见以下代码:
```python
_thread.start_new_thread(update_screen, (fps,)) # 处理屏幕渲染
_thread.start_new_thread(update_pos, ()) # 处理角色和敌方位置
_thread.start_new_thread(add_enemy, ()) # 添加敌人到队列
_thread.start_new_thread(_check, ()) # 判断碰撞
_thread.start_new_thread(time_update, ()) # 更新游戏速度
```
最后使用轮询对按钮进行控制,代码如下:
```python
while True:
if key_status and center_key.value() == 0:
add_bullet()
# 上移
elif player_pos[1] > 10 and up_key.value() == 0:
player_pos[1] -= player_speed
# 下移
elif player_pos[1] < 78 and down_key.value() == 0:
player_pos[1] += player_speed
# 左移
elif player_pos[0] > 7 and left_key.value() == 0:
player_pos[0] -= player_speed
# 右移
elif player_pos[0] < 100 and right_key.value() == 0:
player_pos[0] += player_speed
elif hp < 0:
break
time.sleep_ms(30)
```
![小游戏结束图片](/data/attachment/forum/202312/17/231239k44xpi6ucu4rn5cp.png.thumb.jpg?rand=5996.381965066975)
详细内容,请见视频。
---
## 附录
### 文件下载
https://download.eeworld.com.cn/detail/ICS/630297
### 大学堂链接
https://training.eeworld.com.cn/course/68317/learn
### 视频查看
[localvideo]b961bfa4cbe6f79e9c572b8b94e961db[/localvideo]
尽管我努力地完成了这项任务,但由于编写过于匆忙,内容和代码中可能会存在疏忽之处,希望各位能够多多指正,不胜感激。
---
修改日志:
1. [2023年12月18日 13点47分] 对视频进行重新配音(昨天在学院天台录的,冻得发抖)
2. [2023年12月18日 13点53分] 导出为WORD、PDF文件
-
加入了学习《【得捷电子Follow me第3期】项目总结报告》,观看 【得捷Follow me第3期】项目总结报告
-
上传了资料:
【得捷电子Follow me第3期】整体源码+记录
- 2023-12-08
-
发表了主题帖:
【DigiKey“智造万物,快乐不停”创意大赛】2. 系统功能的基本实现
本帖最后由 ICS 于 2023-12-8 16:09 编辑
> 作品名称:基于ADS1282的三维地震勘探仪器的设计
上一节介绍了如何将 ADS1282 的驱动程序移植到 Arduino 上面,这一节我将介绍ADS1282的三维地震勘探仪器的整体设计框架。
[TOC]
#### 固件实现基本步骤
##### 概述
如果我们想要获得地震信号,首先,需要由检波器获取地震信号,检波器是一种能够将地震波转换为电信号的装置,通常使用压电晶体或电磁线圈等原理来实现然后,由ADS1282转换为数字信号,ADS1282是一款高性能的单片模数转换器(ADC),具有集成的低噪声可编程增益放大器(PGA)和双通道输入多路复用器。ADS1282可以将检波器输出的模拟电信号转换为数字信号,并通过SPI总线传输给ESP32S3。 最后,由ESP32S3存到TF卡。
其中,ESP32作为主控,所以在软件层面我们只需要关注两个:
> 1. 使用 ESP32-S3 将 ADS1282 的数据拿出来
> 2. 使用 ESP32-S3 将数据存到 TF 卡
##### 使用 ESP32-S3 将 ADS1282 的数据拿出来
根据我们上一个分享的内容[^1],我们只需要编写一个代码,将 ADS1282 上的数据读取出来:
```c++
#include
// ADC引脚
// 时钟引脚
#define ADC_SCL 21
// DOUT 引脚
#define ADC_MISO 13
// DIN 引脚
#define ADC_MOSI 12
// CS 引脚 NOTE: 在 ads1282 上没有这个引脚
#define ADC_CS -1
// DRDY 引脚
#define ADC_DRDY 14
// RESET 引脚
#define ADC_RESET 47
// DONE 引脚
#define ADC_DONE 10
void main(){
Serial.begin(115200);
// ......
ADCStartup(ADC_SCL, ADC_MISO, ADC_MOSI, ADC_CS, ADC_DRDY, ADC_RESET);
/* ADC 采样率设置为 1000 */
writeRegisterBitsValue(CONFIG0_ADDRESS, CONFIG0_DR_MASK, CONFIG0_DR_1000SPS);
/* ADC 增益设置为 1 */
writeRegisterBitsValue(CONFIG1_ADDRESS, CONFIG1_PGA_MASK, CONFIG1_PGA_1);
}
void loop(){
// 等待中断
if (waitForDRDYinterrupt(10))
{
Serial0.println(convertToVoltage(adc_data), 5);
}
}
```
在主循环中,我们等待 DRDY 发送准备完成信号,然后用串口输出,但是这段程序由一个问题:一切后续工作都在两个 DRDY 间隙里进行,如果是串口输出这种占用时间相对较少的,可能没有什么影响,但是对于 TF 卡读写来说,占用的时间比较长,因此可能出现某个 DRDY 中断在写入 TF 卡过程中,导致采样率不一致。
##### 使用 ESP32-S3 将数据存到 TF 卡
```c++
#include
int sd_freq = 40000000;
SdFs sd;
FsFile file;
SPIClass SD_SPI = SPIClass(HSPI);
bool mountSDCard()
{
if (digitalRead(SD_DETECT_PIN) != LOW)
{
return false;
}
// 初始化 SPI
SD_SPI.begin(SD_SCL, SD_MISO, SD_MOSI, SD_CS);
while (!sd_mounted && sd_freq > 4000000)
{
if (sd.begin(SdSpiConfig(SD_CS, DEDICATED_SPI, sd_freq, &SD_SPI)))
{
sd_mounted = true;
}
// 如果挂载失败, 尝试改变 sd_freq
sd_freq >>= 1;
}
return sd_mounted;
}
```
按照我们的理解,每当一个 DRDY 中断发生,我们就可以读取出一个 ADC 数据存储到 TF 卡中,但是由于中断看门狗的存在,写入 TF 卡如果在 DRDY 中断中便会出现`Interrupt wdt timeout on CPU1`[^2]。因此,我们只能在主循环中写入 TF 卡。
##### 总结:遇到的问题以及解决办法
###### 1. 在 DRDY 中断中写入 TF 卡时出现Interrupt wdt timeout on CPU1(即读即写)
解决方法:避免在中断中进行长时间阻塞的处理,将数据暂存至 lwrb 环形队列中
```c++
// ADC 中断
void ADCInterruptHandeler()
{
adcVoltageData = convertToVoltage(readData());
lwrb_write(&lwrbBuf, (void *)&adcVoltageData, 4);
}
// 主函数中 TF 卡的写入
// 判断当前是否是文件打开和文件写入模式
if (file_open && sys_config.outputMode & sys_config.OutputMode_TF)
{
bufLen = lwrb_read(&lwrbBuf, readBufData, (getFileSize() - file.size()) > 0 ? (getFileSize() - file.size()) : 0);
}
file.write(readBufData, bufLen);
```
###### 2. TF 卡死机,必须断电重启才能正常写入
解决方法:避免分散的写入 TF 卡数据,由于本次使用的模块 PSRAM 足够,仅在关闭文件时写入,再打开下一个文件。
```c++
bool openFile(const char *path)
{
if (sd_mounted)
{
file.close();
file_open = file.open(path, O_WRITE | O_CREAT | O_APPEND);
file.seek(0);
return file_open;
}
return false;
}
```
#### PCB 设计及制造
在地震勘探过程中,最重要的是时间,只有时间同步,才能对采集的数据进行分析。因此在本次创赛中,我采用了 SD3078 作为本次的主 RTC,使用 GPS 同步时间信号。采用 REF5050 作为电压基准芯片,以确保数据的准确性。其它的物料请参见参考资料3[^3]。
下一篇将会展示打板的开箱及其功能展示。
#### 参考
[^1]: [【DigiKey“智造万物,快乐不停”创意大赛】1. ADS1282 驱动的移植与实验](https://bbs.eeworld.com.cn/thread-1263186-1-1.html)
[^2]: [看门狗 - ESP32-C3 - — ESP-IDF 编程指南 latest 文档 (espressif.com)](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/system/wdts.html)
[^3]: [【DigiKey“智造万物,快乐不停”创意大赛】 物料开箱](https://bbs.eeworld.com.cn/thread-1260793-1-1.html)
- 2023-11-28
-
回复了主题帖:
【得捷电子Follow me第3期】0. 开箱贴
秦天qintian0303 发表于 2023-11-28 08:29
楼主这次购买的很丰富 啊
就买了推荐的那几样