- 2024-10-31
-
发表了主题帖:
【2024 DigiKey创意大赛】独居老人居家宝
本帖最后由 EE-Bond 于 2024-10-31 23:59 编辑
【项目背景与概述】
本项目旨在利用乐鑫开发板和麦克风阵列技术,打造一款智能化的老人家庭照护系统,为老年人提供更安全、更舒适的居家环境
【使用的板卡】
ESP32-S3-LCD-EV-Board:
麦克风阵列:
麦克风阵列控制板:
【视频】
视频
【作品功能】
为独居老人打造一款集对话、控制、告警、AI于一身的照护系统,具有如下核心功能:
1、语音控制家居
2、环境监测
3、老人遇险呼救
4、基于Home Assistant的家居控制
【功能框图】
使用AI实现对使用者呼救信息的判别
功能请看视频演示或下载代码使用。
【分享链接】
https://bbs.eeworld.com.cn/thread-1295335-1-1.html
https://bbs.eeworld.com.cn/thread-1291494-1-1.html
【代码】
代码
-
加入了学习《得捷电子Follow me第4期》,观看 得捷电子Follow me第4期
-
上传了资料:
得捷大赛代码
- 2024-10-03
-
发表了主题帖:
【2024 DigiKey 创意大赛】进度分享,86面板开发指南
本帖最后由 EE-Bond 于 2024-10-3 12:23 编辑
手头这块ESP32-S3-LCD-EV-Board开发板搭载一块480*480屏幕,显示效果也不错,搭建开发环境耗费了不少时间,主要的开发方式有ESP-IDF、VScode插件、ESP-IDE、Arduino。
以上所有开发方式我都试了个遍,最简单易用、功能也全的还是Arduino:移植好了lvgl、支持SquareLine工程、友好的界面,于是最终选择Arduino作为开发工具。
一、安装好Arduino后,需要安装这些库:注意lvgl的版本,lvgl (>= v8.3.9, < v9),
安装好以后,在文件-示例中可以看到这三个库就安装完成。
二、配置lvgl,需要进入到arduino的库目录下的lvgl文件夹,复制一份lv_conf_template.h到libraries下,并更名为lv_conf.h:
三、修改配置文件:
1、进入lv_conf.h将#if 0 修改为 #if 1 使文件生效。
2、打开ESP32_Display_Panel下的示例,ESP32_Display_Panel-LVGL-v8-Porting
编辑ESP_Panel_Board_Supported.h文件,修改第十行为#define ESP_PANEL_USE_SUPPORTED_BOARD (1) :
四、编译之前需要修改配置,需要注意的是必须开启PSRAM,否则会导致严重错误,无限重启:
五、如果要使用examples和demos:
/* 如果需要使用LVGL内置的 examples 和 demos , 需要取消下面这些头文件的注释:
// #include <demos/lv_demos.h>
// #include <examples/lv_examples.h>
* 同时需要复制 `lvgl/examples` 到 `lvgl/src/examples`. 对于demos也是一样的操作:复制 `lvgl/demos` 到 `lvgl/src/demos`.
lvgl/src目录下应该是这样的:
源文件位于lvgl库目录下
六、使用
在上述示例的void setup()函数的第一行:
是显示的文字内容,可以改成任何想要的英文(中文字库需要自行添加):
可以添加一个色环示例看看rgb色彩的效果:
lv_example_colorwheel_1();
七、上传代码:
最终显示效果:
总结:这样这个开发板的环境就搭建好了,目前自定义的ui还在设计中。。。
- 2024-08-31
-
加入了学习《FollowMe 第二季: 1 Adafruit Circuit Playground Express及任务讲解》,观看 Adafruit Circuit Playground Express 及任务讲解
- 2024-08-25
-
发表了主题帖:
【2024 DigiKey 创意大赛】ESP86面板(ESP32-S3-LCD-EV-Board)物料开箱
本帖最后由 EE-Bond 于 2024-8-25 21:02 编辑
本次大赛所用物料共2个:
Seeed 6麦克风阵列
ESP32-S3-LCD-EV-Board,所搭载的屏幕与家用86面板尺寸相同
到手给ESP32-S3-LCD-EV-Board上电,初始例程还挺精美,UI直接爱了:
开机页面:
初始智能家居面板:
休眠页面:
另外还有语音唤醒功能就不展示了,将一并在后续的创意制作进度中展示。
最后:感谢Digikey,感谢EEWorld。
ESP推荐的烧录工具:ESP Launchpad
可直接烧录官方例程,免去编译;后续开发环境搭建好后,也可以用它烧录编译好的程序。
-
加入了学习《【Follow me第二季第1期】使用circuitpython开发并完成全部任务》,观看 【Follow me第二季第1期】使用circuitpython开发并完成全部任务
- 2024-02-25
-
发表了主题帖:
FolowMe第四期任务提交
内容一: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。
建议:多多开展此类活动,培养更多全栈工程师
-
上传了资料:
FollowMe第四期代码