- 2024-10-07
-
发表了主题帖:
【Follow me第二季第2期】任务汇总贴
本帖最后由 eew_gz8e7C 于 2024-10-7 15:30 编辑
一 、视频介绍
二、任务实现
2.1 入门任务(必做):搭建环境并开启第一步Blink / 串口打印Hello EEWorld!
软件流程:
代码:
void setup() {
// put your setup code here, to run once:
//配置波特率
Serial.begin(115200);
while (!Serial) { }
//配置LED引脚为输出模式
//pinMode(102, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
//演示控制板载LED亮灭,并且打印LED状态
digitalWrite(LED_BUILTIN, LOW);
printf("LED ON\n");
delay(1000);
digitalWrite(LED_BUILTIN, HIGH);
printf("LED OFF\n");
delay(1000);
}
效果展示:
[localvideo]ec0a5858a7dcf0f85c9950ccb593d237[/localvideo]
实现过程:
【Follow me第二季第2期】开箱+环境搭建+Blink&串口打印 - DigiKey得捷技术专区 - 电子工程世界-论坛 (eeworld.com.cn)
2.2 基础任务(必做):驱动12x8点阵LED;用DAC生成正弦波;用OPAMP放大DAC信号;用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线
软件流程:
代码:
// LED_MX
#include "Arduino_LED_Matrix.h" //调用LED_Matrix库
ArduinoLEDMatrix matrix; //实例化点阵对象
void setup() {
// put your setup code here, to run once:
Serial.begin(115200); //设置波特率
matrix.begin(); //点阵使能
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
}; //点阵爱心数据
matrix.renderBitmap(frame, 8, 12); //点阵渲染显示
}
void loop() {
// put your main code here, to run repeatedly:
}
//DAC
#include "analogWave.h"
analogWave wave(DAC); // 使用DAC引脚实例化模拟曲线对象wave
float freq = 0.5; // 设置曲线初始频率
void setup() {
Serial.begin(115200); // 串口波特率
wave.sine(freq); // 使用模拟曲线对象wave按照初始频率生成正弦波
wave.amplitude(0.5);
}
void loop() {
printf("%d\n",analogRead(A4)); // 读取正弦值
delay(100);
}
//OPAMP
#include "analogWave.h"
#include <OPAMP.h>
analogWave wave(DAC); // 使用DAC引脚实例化模拟曲线对象wave
float freq = 1; // 设置曲线初始频率
void setup() {
Serial.begin(250000); // 串口波特率
OPAMP.begin(OPAMP_SPEED_HIGHSPEED); //设置OPAMP
wave.sine(freq); // 使用模拟曲线对象wave按照初始频率生成正弦波
wave.amplitude(0.4); //设置正弦曲线幅值为0.4
}
void loop() {
printf("%d\n",analogRead(A4)); // 读取DAC输出正弦值
Serial.print(" ");
printf("%d\n",analogRead(A5)); // 读取OPAMP输出正弦值
delay(100);
}
效果展示:
LED_MX
[localvideo]8987fd7d0371bf40d459191834415b3a[/localvideo]
[localvideo]1df186dd57e7f6fa0f96498f07afe472[/localvideo]
实现过程:
【Follow me第二季第2期】基础任务 点阵+DAC正弦波+OPAMP放大+串口打印与曲线输出 - DigiKey得捷技术专区 - 电子工程世界-论坛 (eeworld.com.cn)
2.3 进阶任务(必做):通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant)
软件流程:
代码:
#include <ArduinoMqttClient.h>
#include <WiFiS3.h>
#include "secrets.h"
//wifi信息
char ssid[] = WIFISSID;
char pass[] = WIFIPWD;
//mqtt服务器信息
const char broker[] = BROKER;
const int port = BROKER_PORT;
//mqtt客户端
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
//最大重连次数
const int maxTryTimes = 10;
//测试用发送订阅主题
const char sendTopic[] = "HA/echo";
//测试用接收订阅主题
const char backTopic[] = "HA/back";
//发送间隔
const long interval = 1000;
unsigned long previousMillis = 0;
int count = 0;
void setup() {
//初始化串口
Serial.begin(115200);
while (!Serial) {
;
}
// 连接WIFI
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("You're connected to the network");
Serial.println(WiFi.localIP());
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
int tryTimes=0;
//连接MQTT服务器
while (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
if (tryTimes>maxTryTimes-1)
{
printf("MQTT connection failed over %d times",maxTryTimes);
while(1){};
}
delay(500);
printf("Try more %d times\n",++tryTimes);
}
Serial.println("You're connected to the MQTT broker!");
Serial.println();
Serial.print("Subscribing to topic: ");
Serial.println(backTopic);
Serial.println();
//订阅主题
mqttClient.subscribe(backTopic);
Serial.print("Waiting for messages on topic: ");
Serial.println(backTopic);
Serial.println();
}
void loop() {
// 查看主题消息
int messageSize = mqttClient.parseMessage();
if (messageSize) {
// we received a message, print out the topic and contents
Serial.print("Received a message with topic '");
Serial.print(mqttClient.messageTopic());
Serial.print("', length ");
Serial.print(messageSize);
Serial.println(" bytes:");
// 打印输出主题消息
while (mqttClient.available()) {
Serial.print((char)mqttClient.read());
}
Serial.println();
Serial.println();
}
unsigned long currentMillis = millis();
//发送测试消息到主题
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
Serial.print("Sending message to topic: ");
Serial.println(sendTopic);
Serial.print("echo ");
Serial.println(count);
mqttClient.beginMessage(sendTopic);
mqttClient.print("echo ");
mqttClient.print(count);
mqttClient.endMessage();
Serial.println();
count++;
}
}
效果展示:
[localvideo]f924b9457d2c86b2d70e4e27d9e9bd84[/localvideo]
实现过程:
【Follow me第二季第2期】进阶任务 MQTT协议接入到开源的智能家居HomeAssistant - DigiKey得捷技术专区 - 电子工程世界-论坛 (eeworld.com.cn)
2.4 扩展任务 通过外部 环境光传感器,上传光照度到HA,通过HA面板显示数据+通过外部温湿度传感器,上传温湿度到HA,通过HA面板显示数据
软件流程:
代码:
//LIGHT
#include "secrets.h"
#include <WiFiS3.h>
#include <ArduinoHA.h>
#include <Wire.h>
#define LIGHT_SENSOR A4
//wifi信息
char ssid[] = WIFISSID;
char pass[] = WIFIPWD;
//mqtt服务器信息
const char broker[] = BROKER;
const int port = BROKER_PORT;
byte mac[] = { 0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A };
//tcp客户端
WiFiClient client;
//HA虚拟设备配置
HADevice device(mac, sizeof(mac));
//mqtt客户端实例化
HAMqtt mqtt(client, device);
//HA传感器实例化
HASensorNumber lightSensor("LightSensor", HABaseDeviceType::PrecisionP2);
//参考上次更新时间
unsigned long lastUpdateAt = 0;
//光线传感器
void getLightValue(float* const pval) {
int sensorValue = analogRead(LIGHT_SENSOR);
*pval = sensorValue/1023.0;
}
void setup() {
//初始化串口
Serial.begin(115200);
while (!Serial) {
;
}
// 连接WIFI
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("You're connected to the network");
Serial.println(WiFi.localIP());
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
device.setName("亮度");
device.setSoftwareVersion("1.0.0");
lightSensor.setIcon("mdi:home");
lightSensor.setName("亮度");
lightSensor.setUnitOfMeasurement("%");
mqtt.begin(BROKER, BROKER_PORT);
}
void loop() {
mqtt.loop();
float light;
if ((millis() - lastUpdateAt) > 1000) {
lightSensor.setValue(light*100);
Serial.print("light:");
Serial.print(light*100);
Serial.println("%");
lastUpdateAt = millis();
}
}
//TEMP&HUMI
#include "secrets.h"
#include <WiFiS3.h>
#include <ArduinoHA.h>
#include <Wire.h>
#include "AHT20.h"
//wifi信息
char ssid[] = WIFISSID;
char pass[] = WIFIPWD;
//mqtt服务器信息
const char broker[] = BROKER;
const int port = BROKER_PORT;
byte mac[] = { 0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A };
//tcp客户端
WiFiClient client;
//HA虚拟设备配置
HADevice device(mac, sizeof(mac));
//mqtt客户端实例化
HAMqtt mqtt(client, device);
//HA传感器实例化
HASensorNumber tempSensor("tempSensor", HABaseDeviceType::PrecisionP2);
HASensorNumber humiSensor("humiSensor", HABaseDeviceType::PrecisionP2);
//参考上次更新时间
unsigned long lastUpdateAt = 0;
//温湿度传感器AHT20
AHT20 AHT;
void setup() {
//初始化串口
Serial.begin(115200);
while (!Serial) {
;
}
AHT.begin();
// 连接WIFI
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("You're connected to the network");
Serial.println(WiFi.localIP());
//设置MQTT服务器
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
//设置HA虚拟设备信息
device.setName("温湿度");
device.setSoftwareVersion("1.0.0");
tempSensor.setIcon("mdi:home");
tempSensor.setName("温度");
tempSensor.setUnitOfMeasurement("°C");
humiSensor.setIcon("mdi:home");
humiSensor.setName("湿度");
humiSensor.setUnitOfMeasurement("%");
mqtt.begin(BROKER, BROKER_PORT);
}
void loop() {
mqtt.loop();
float humi, temp, light;
if ((millis() - lastUpdateAt) > 1000) {
int ret = AHT.getSensor(&humi, &temp);
if (ret) {
tempSensor.setValue(temp);
humiSensor.setValue(humi * 100);
Serial.print("humidity: ");
Serial.print(humi * 100);
Serial.print("%\t temerature: ");
Serial.println(temp);
}
lastUpdateAt = millis();
}
}
效果展示:
[localvideo]93edf11b65f728a3059856114e416e5e[/localvideo]
[localvideo]b219ef71ed177c22136ff5c9a4344897[/localvideo]
实现过程:
【Follow me第二季第2期】扩展任务 HA面板显示温湿度、光照度数据 - DigiKey得捷技术专区 - 电子工程世界-论坛 (eeworld.com.cn)
三、心得体会
工作之余能够通过论坛活动学习并掌握uno r4 wifi的使用,并且深入了解了开源智能家居Home Assistant系统的使用,收获满满。虽然过程中遇到了许多问题,但是通过和坛友们沟通询问也都全部一一解决。感谢eeworld和得捷提供的活动机会。祝类似的活动越办越好。
四、任务代码汇总
https://download.eeworld.com.cn/detail/eew_gz8e7C/634546
-
加入了学习《Follow Me第二季第二期》,观看 Follow Me第二季第二期
-
加入了学习《Follow Me 第二季第二期总结视频》,观看 follow me 集合
-
上传了资料:
Follow me第二季第二期任务代码
-
发表了主题帖:
【Follow me第二季第2期】扩展任务 HA面板显示温湿度、光照度数据
本帖最后由 eew_gz8e7C 于 2024-10-7 10:39 编辑
Follow me第二季第2期扩展任务之
环境光传感器,温湿度传感器,上传数据到HA,通过HA面板显示数据
本节任务采用Follow me第一季采购的传感器模块Grove - Light Sensor v1.2、Grove - AHT20 I2C来实现。
首先安装home-assistant-integration库,调用该库可以快速构建HA能够自动发现的MQTT传感器实例。
环境光传感器数据上传到HA,通过HA面板显示数据
根据Grove - Light Sensor v1.2传感器模块引脚定义完成连线。
光传感器信号为模拟信号,通过UNO R4 WIFIF A4引脚读取光照值并转化为%值输出。
#include "secrets.h"
#include <WiFiS3.h>
#include <ArduinoHA.h>
#include <Wire.h>
#define LIGHT_SENSOR A4
//wifi信息
char ssid[] = WIFISSID;
char pass[] = WIFIPWD;
//mqtt服务器信息
const char broker[] = BROKER;
const int port = BROKER_PORT;
byte mac[] = { 0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A };
//tcp客户端
WiFiClient client;
//HA虚拟设备配置
HADevice device(mac, sizeof(mac));
//mqtt客户端实例化
HAMqtt mqtt(client, device);
//HA传感器实例化
HASensorNumber lightSensor("LightSensor", HABaseDeviceType::PrecisionP2);
//参考上次更新时间
unsigned long lastUpdateAt = 0;
//光线传感器
void getLightValue(float* const pval) {
int sensorValue = analogRead(LIGHT_SENSOR);
*pval = sensorValue/1023.0;
}
void setup() {
//初始化串口
Serial.begin(115200);
while (!Serial) {
;
}
// 连接WIFI
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("You're connected to the network");
Serial.println(WiFi.localIP());
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
device.setName("亮度");
device.setSoftwareVersion("1.0.0");
lightSensor.setIcon("mdi:home");
lightSensor.setName("亮度");
lightSensor.setUnitOfMeasurement("%");
mqtt.begin(BROKER, BROKER_PORT);
}
void loop() {
mqtt.loop();
float light;
if ((millis() - lastUpdateAt) > 1000) { // update in 2s interval
getLightValue(&light);
lightSensor.setValue(light*100);
Serial.print("light:");
Serial.print(light*100);
Serial.println("%");
lastUpdateAt = millis();
}
}
效果展示
[localvideo]70715e1f28e45fd802052367d52f5408[/localvideo]
温湿度传感器数据上传到HA,通过HA面板显示数据
根据Grove - AHT20 I2C传感器模块引脚定义完成连线。
Grove - AHT20 I2C传感器模块通过I2C总线传输传感器数据,Arduino IDE中搜索下载Seed_Arduino_AHT20库可以快速调用并读取传感器数据。
#include "secrets.h"
#include <WiFiS3.h>
#include <ArduinoHA.h>
#include <Wire.h>
#include "AHT20.h"
//wifi信息
char ssid[] = WIFISSID;
char pass[] = WIFIPWD;
//mqtt服务器信息
const char broker[] = BROKER;
const int port = BROKER_PORT;
byte mac[] = { 0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A };
//tcp客户端
WiFiClient client;
//HA虚拟设备配置
HADevice device(mac, sizeof(mac));
//mqtt客户端实例化
HAMqtt mqtt(client, device);
//HA传感器实例化
HASensorNumber tempSensor("tempSensor", HABaseDeviceType::PrecisionP2);
HASensorNumber humiSensor("humiSensor", HABaseDeviceType::PrecisionP2);
//参考上次更新时间
unsigned long lastUpdateAt = 0;
//温湿度传感器AHT20
AHT20 AHT;
void setup() {
//初始化串口
Serial.begin(115200);
while (!Serial) {
;
}
AHT.begin();
// 连接WIFI
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("You're connected to the network");
Serial.println(WiFi.localIP());
//设置MQTT服务器
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
//设置HA虚拟设备信息
device.setName("温湿度");
device.setSoftwareVersion("1.0.0");
tempSensor.setIcon("mdi:home");
tempSensor.setName("温度");
tempSensor.setUnitOfMeasurement("°C");
humiSensor.setIcon("mdi:home");
humiSensor.setName("湿度");
humiSensor.setUnitOfMeasurement("%");
mqtt.begin(BROKER, BROKER_PORT);
}
void loop() {
mqtt.loop();
float humi, temp, light;
if ((millis() - lastUpdateAt) > 1000) {
int ret = AHT.getSensor(&humi, &temp);
if (ret) {
tempSensor.setValue(temp);
humiSensor.setValue(humi * 100);
Serial.print("humidity: ");
Serial.print(humi * 100);
Serial.print("%\t temerature: ");
Serial.println(temp);
}
lastUpdateAt = millis();
}
}
效果展示
[localvideo]b0b2b2063c613ebb80f80d1d49814c13[/localvideo]
- 2024-09-30
-
加入了学习《【Follow me第二季第2期】任务提交》,观看 【Follow me第二季第2期】
-
发表了主题帖:
【Follow me第二季第2期】进阶任务 MQTT协议接入到开源的智能家居HomeAssistant
本帖最后由 eew_gz8e7C 于 2024-9-30 12:08 编辑
Follow me第二季第2期进阶任务之
通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant)
本期活动购置了树莓派zerow用于部署HA,首先介绍如何部署HA;
树莓派zerow部署HA
树莓派zerow系统烧录
首先下载安装树莓派镜像安装工具,准备好SD卡和读卡器用于安装树莓派系统;
安装Imager后,通过单击树莓派Imager图标或运行来启动应用程序,单击CHOOSE DEVICE并从列表中选择您的树莓派zero。
接下来,单击选择操作系统并选择要安装的操作系统。Imager始终在列表顶部显示适合您的型号的树莓派OS推荐版本,这里选择Raspberry Pi OS(32-bit)。
然后单击选择SD卡并选择您的存储设备。
接下来,点击NEXT。
在弹出窗口中,自定义操作系统设置,包括:
用户名和密码
WiFi认证
主机名
时区
键盘布局
远程连接
SERVICES选项卡用于远程连接树莓派的相关设置,根据需要进行设置,保存后即可开始写入。
完成后插入SD卡即可完成启动。启动后树莓派指示灯开始闪烁,通过SSH登录树莓派终端进行后续配置。
执行 sudo apt-get update、sudo apt-get upgrade进行软件环境的更新。
树莓派zerow部署HA
这里采用docker进行HA的部署。
首先完成docker的安装,由于网络不畅通,需要采用国内镜像源进行安装,这里采用腾讯源。
# 卸载旧版本(如果有):
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done
# 添加 Docker 官方 GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL http://mirrors.tencent.com/docker-ce/linux/raspbian/gpg -o /etc/apt/keyrings/docker_tencentyun.asc
sudo chmod a+r /etc/apt/keyrings/docker_tencentyun.asc
# 添加仓库到 Apt 源:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker_tencentyun.asc] http://mirrors.tencent.com/docker-ce/linux/raspbian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker_tencentyun.list > /dev/null
sudo apt-get update
# 安装 Docker 软件包
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 验证
sudo docker run hello-world
docker安装完毕后即可拉取HA镜像。
docker pull homeassistant/home-assistant
待镜像拉取完成即可运行HA容器。
docker run -d \
--name homeassistant \
--privileged \
--restart=unless-stopped \
-e TZ=Asia/Shanghai \
-v /data/homeassistant:/config \ #将物理机/data/homeassistant目录挂载至容器的/config目录
-v /run/dbus:/run/dbus:ro \ #将物理机/run/dbus目录挂载至容器的/run/dbus目录,否则HA蓝牙不可用
--network=host \
homeassistant/home-assistant
运行后执行docker ps即可查看运行状态,如果已经正常运行,则打开树莓派主机地址:8123端口即可进入HA页面进行注册配置,至此HA部署完成。
树莓派zerow搭建MQTT服务器
通过docker镜像搭建MQTT服务器,这里使用eclipse-mosquitto开源MQTT服务器。
#1. 拉取eclipse-mosquitto镜像
docker pull eclipse-mosquitto
#2. 为容器创建配置、数据等文件夹
sudo mkdir -p /data/mosquitto/config
sudo mkdir -p /data/mosquitto/data
sudo mkdir -p /data/mosquitto/log
#3. 更改文件夹权限
sudo chmod -R 755 /data/mosquitto
sudo chmod chmod -R 777 /data/ /mosquitto/log
#4. 设置配置文件
sudo nano /data/mosquitto/config/mosquitto.conf
# 配置文件内容:
# 配置端口号及远程访问IP
listener 1883
# 配置websocket的连接端口
listener 9001
# 以websocket方式连接mqtt服务
#protocol websockets
persistence true
# 数据存储路径
persistence_location /mosquitto/data/
# 运行日志存储路劲
log_dest file /mosquitto/log/mosquitto.log
# 设置匿名访问
allow_anonymous true
#5. 运行容器
docker run -td \
--name mqtt \
--privileged \
--restart=unless-stopped \
-v /data/mosquitto/config:/mosquitto/config \
-v /data/mosquitto/data:/mosquitto/data \
-v /data/mosquitto/log:/mosquitto/log \
--network=host \
eclipse-mosquitto
配置内网穿透实现MQTT服务器、HA面板远程访问
有需要远程访问MQTT服务器、HA面板的朋友可以参考在树莓派安装cpolar实现内网穿透。下载安装可以参考官网教程,需要注意配置文件的设置,可以参考:
authtoken: *****
tunnels:
mqtt:
proto: tcp
addr: "1883" #对应MQTT服务器端口
ha:
proto: tcp
addr: "8123" #对应HA面板服务器端口
UNO R4 WIFI 通过MQTT接入HA
下载安装ArduinoMqttClient库,可以快速调用实现MQTT客户端。
核心代码
#include <ArduinoMqttClient.h>
#include <WiFiS3.h>
#include "secrets.h"
//wifi信息
char ssid[] = SSID;
char pass[] = WIFIPWD;
//mqtt服务器信息
const char broker[] = BROKER;
const int port = BROKER_PORT;
//mqtt客户端
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
//最大重连次数
const int maxTryTimes = 10;
//测试用订阅主题
const char topic[] = "HA/echo";
//发送间隔
const long interval = 1000;
unsigned long previousMillis = 0;
int count = 0;
void setup() {
//初始化串口
Serial.begin(115200);
while (!Serial) {
;
}
// 连接WIFI
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("You're connected to the network");
Serial.println(WiFi.localIP());
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
int tryTimes=0;
//连接MQTT服务器
while (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
if (tryTimes>maxTryTimes-1)
{
printf("MQTT connection failed over %d times",maxTryTimes);
while(1){};
}
delay(500);
printf("Try more %d times\n",++tryTimes);
}
Serial.println("You're connected to the MQTT broker!");
Serial.println();
Serial.print("Subscribing to topic: ");
Serial.println(topic);
Serial.println();
//订阅主题
mqttClient.subscribe(topic);
Serial.print("Waiting for messages on topic: ");
Serial.println(topic);
Serial.println();
}
void loop() {
// 查看主题消息
int messageSize = mqttClient.parseMessage();
if (messageSize) {
// we received a message, print out the topic and contents
Serial.print("Received a message with topic '");
Serial.print(mqttClient.messageTopic());
Serial.print("', length ");
Serial.print(messageSize);
Serial.println(" bytes:");
// 打印输出主题消息
while (mqttClient.available()) {
Serial.print((char)mqttClient.read());
}
Serial.println();
Serial.println();
}
unsigned long currentMillis = millis();
//发送测试消息到主题
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
Serial.print("Sending message to topic: ");
Serial.println(topic);
Serial.print("echo ");
Serial.println(count);
mqttClient.beginMessage(topic);
mqttClient.print("echo ");
mqttClient.print(count);
mqttClient.endMessage();
Serial.println();
count++;
}
}
进入HA面板,依次进入 设置->设备与服务->添加集成,搜索加载MQTT,然后进入MQTT配置如下,启动unr4 wifi的mqtt程序测试主题消息的发送与接收。
效果展示
[localvideo]1a1093c5331ec44e3be36ff309ec3b9a[/localvideo]
-
回复了主题帖:
【Follow me第二季第2期】开箱+环境搭建+Blink&串口打印
戈壁滩上的辉煌 发表于 2024-9-13 09:00
感觉Arduino UNO R4 WiFi还是挺好玩的,是不是都可以用这个板子进行啊
可以的,HA可以通过docker安装在PC上
-
发表了主题帖:
【Follow me第二季第2期】基础任务 点阵+DAC正弦波+OPAMP放大+串口打印与曲线输出
本帖最后由 eew_gz8e7C 于 2024-9-30 09:39 编辑
Follow me第二季第2期基础任务之
驱动12x8点阵LED;
用DAC生成正弦波;
用OPAMP放大DAC信号;
用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线;
驱动12x8点阵LED
通过Arduino_LED_Matrix库可以便捷调用点阵,只需要关注点阵内容矩阵即可,通过byte类型的二维向量存储点阵内容,元素0代表灭,元素1代表亮。
核心代码
#include "Arduino_LED_Matrix.h"//调用LED_Matrix库
ArduinoLEDMatrix matrix;//实例化点阵对象
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);//设置波特率
matrix.begin();//点阵使能
byte frame[8][12] = {
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};//点阵爱心数据
matrix.renderBitmap(frame, 8, 12);//点阵渲染显示
}
void loop() {
// put your main code here, to run repeatedly:
}
效果展示
用DAC生成正弦波,并打印到串口用上位机显示曲线
UNO R4 WiFi 还包含一个 DAC,它可以作为真正的模拟输出引脚。
此 DAC 引脚默认的写入分辨率为 10 位。写入到引脚的值应该在 0-1023 之间。设置正弦波的幅值为0.5,则输出波形幅值在0~511之间。
因为没有示波器,使用A4引脚ADC采集通过串口输出观察波形。
核心代码
#include "analogWave.h"
analogWave wave(DAC); // 使用DAC引脚实例化模拟曲线对象wave
float freq = 0.5; // 设置曲线初始频率
void setup() {
Serial.begin(115200); // 串口波特率
wave.sine(freq); // 使用模拟曲线对象wave按照初始频率生成正弦波
wave.amplitude(0.5);
}
void loop() {
printf("%d\n",analogRead(A4)); // 读取正弦值
delay(100);
}
效果展示
[localvideo]b7fe640bf630efe99f8db8bede74c20b[/localvideo]
用OPAMP放大DAC信号
OPAMP 是一种用途广泛且应用广泛的电子元件,属于模拟集成电路类。它的主要功能是放大电压信号。如图所示,R1为接地端的电阻,R2为放大器输出端的电阻。输入电压为v,放大倍数为A,则R1、R2、A、v满足Av=1+R2/R1,在确定放大倍数后需要根据放大倍数选择对应比例的R1和R2。这里我选择两个2kΩ电阻实现2倍的放大。
核心代码
#include "analogWave.h"
#include <OPAMP.h>
analogWave wave(DAC); // 使用DAC引脚实例化模拟曲线对象wave
float freq = 1; // 设置曲线初始频率
void setup() {
Serial.begin(250000); // 串口波特率
OPAMP.begin(OPAMP_SPEED_HIGHSPEED); //设置OPAMP
wave.sine(freq); // 使用模拟曲线对象wave按照初始频率生成正弦波
wave.amplitude(0.4); //设置正弦曲线幅值为0.4
}
void loop() {
printf("%d\n",analogRead(A4)); // 读取DAC输出正弦值
Serial.print(" ");
printf("%d\n",analogRead(A5)); // 读取OPAMP输出正弦值
delay(100);
}
效果展示
[localvideo]411afb3d93a0903f8a86cc80182f05e6[/localvideo]
- 2024-09-09
-
发表了主题帖:
【Follow me第二季第2期】开箱+环境搭建+Blink&串口打印
本帖最后由 eew_gz8e7C 于 2024-9-9 10:05 编辑
物料开箱
本期活动为Arduino UNO R4 WiFi,活动中有利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant)的相关任务,故本次活动物料额外购买了树莓派zerow用于部署HA。
下单两周左右物料到货了,特地给树莓派zerow也配备了外壳和散热片。
环境搭建
既然是Arduino的板子,开发环境自然选用Arduino。
安装并打开Arduino后,在开发板管理器中搜索安装 UNO R4 WIFI支持库。
然后配置开发板和串口。
Blink&串口打印
查看LED引脚信息为LED_BUILTIN P102。
通过演示控制LED引脚高低电平实现BLINK,同时打印输出LED状态。
void setup() {
// put your setup code here, to run once:
//配置波特率
Serial.begin(115200);
while (!Serial) { }
//配置LED引脚为输出模式
//pinMode(102, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
//演示控制板载LED亮灭,并且打印LED状态
digitalWrite(LED_BUILTIN, LOW);
printf("LED ON\n");
delay(1000);
digitalWrite(LED_BUILTIN, HIGH);
printf("LED OFF\n");
delay(1000);
}
效果
[localvideo]d0ba9afa2fa617ccea2cefc775c804ee[/localvideo]
- 2024-08-31
-
加入了学习《【Follow me第二季第1期】全部任务演示》,观看 全部任务演示2.0
-
发表了主题帖:
【Follow me第二季第1期】任务汇总提交
本帖最后由 eew_gz8e7C 于 2024-8-31 18:15 编辑
一、项目介绍
感谢得捷和eeworld提供的活动机会,首先简单自我介绍,我是机械专业毕业,当前从事新能源汽车行业,去年也有幸参加了几次Follow me的活动,Follow me活动选的板子非常好,上手简单能快速实现有趣的功能,几期活动下来学到了许多,祝Follow me越办越好。
本次Follow me第二季第1期的任务主板为Circuit Playground Express,基于ATSAMD21微控制器,采用32位ARM® Cortex®-M0+内核,具有10颗Mini NeoPixel LED、光线/温度/湿度/声音/加速度/红外传感器,板载一个迷你扬声器、一个滑动开关、两个用户按钮,其可玩性非常高。
二、任务实现
1. 入门任务(必做):开发环境搭建,板载LED点亮
环境搭建
Circuit Playground Express 支持Microsoft MakeCode,可以进行可视化编程或者采用JavaScript编程。同时,他也支持Arduino和CircuitPython,使用起来相当灵活方便。这里我采用CircuitPython作为开发环境。
首先从官网下载CircuitPython 9.1.1 ;
Usb连接电脑,双击reset按键进入bootloader模式,此时出现CPLAYBOOT盘,然后将CircuitPython 9.1.1拖入即可开始CPY刷入;
刷入完成后出现CIRCUITPY盘;
下载安装Thony(VS Code/ mu editor)IDE。
至此CircuitPython环境搭建完成。
点亮板载红色LED
进入REPL,运行下面命令,可以输出板载资源信息,可以看到板载的LED已经被封装进borad模块。
>>>dir(board)
调用board的LED,设置其为数字IO输出,设置其值为True或False即可控制LED的亮灭。
代码如下:
#task0.1
import board
import digitalio
import time
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
while True:
led.value = True
time.sleep(1)
led.value = False
time.sleep(1)
另外,可以安装adafruit_circuitplayground库,adafruit_circuitplayground对板载资源进一步封装,使得板载资源调用更加方便。
#task0.2
from adafruit_circuitplayground import cp
import time
while True:
cp.red_led = True
time.sleep(1)
cp.red_led = False
time.sleep(1)
[localvideo]ea559db11bce1cc944f6d8c709ced584[/localvideo]
2. 基础任务一(必做):控制板载炫彩LED,跑马灯点亮和颜色变换
通过设置RGB三原色的亮度值可以调配出各种想要的色彩,板载炫彩LED可以通过adafruit_circuitplayground模块中的pixel来调用,其调用过程类似给长度为10的链表中的元素进行赋值,例如cp.pixels[0] = (255, 0, 0)即将第1颗灯珠设置为红色,另外其fill方法可以一次性设置所有灯珠的颜色。
下面的代码通过随机数来设置rgb色彩值,给所有灯珠设置随机颜色:
#task1.1
from adafruit_circuitplayground import cp
import time
import random
cp.pixels.brightness=0.01
while True:
x=random.randint(0,255)
y=random.randint(0,255)
z=random.randint(0,255)
print((x,y,z))
time.sleep(1)
[localvideo]b6d1baf3a22a22b03505d626f4725237[/localvideo]
另外,使用CircuitPython 的rainbowio库的colorwheel函数可以帮助我们按照色环数值选取色彩,colorwheel函数接受一个介于0至255之间的整数作为参数,返回一个对应于该位置的RGB颜色值。这个函数会生成一个完整的彩虹色谱,从红色开始,经过橙色、黄色、绿色、青色、蓝色、洋红色,再回到红色。
[localvideo]be9c34597bbc7da9f2898db7ce73a544[/localvideo]
3.基础任务二(必做):监测环境温度和光线,通过板载LED展示舒适程度
温度读取
板载环境温度传感器为热敏电阻,其接有一个10k下拉电阻,通过采集电路的电压值反应温度变化,adafruit_circuitplayground库已经封装好了温度的计算可以直接调用。
import time
from adafruit_circuitplayground import cp
while True:
print(cp.temperature)
time.sleep(0.1)
光线强度读取
import time
from adafruit_circuitplayground import cp
while True:
print(cp.light)
time.sleep(0.1)
根据温度和光线的舒适度分别在两侧的炫彩灯珠显示。前5个灯珠指示温度,以温度25℃为最舒适温度(温度范围-20~40℃),此时前5个灯珠均显示为绿色,高于25℃向红色渐变,低于25摄氏度向蓝色渐变。后5个灯珠指示亮度,以亮度200为最舒适亮度(亮度范围0~400),此时后5个灯珠均显示为绿色,高于200向红色渐变,低于200向蓝色渐变。
# task2
import time
from adafruit_circuitplayground import cp
import time
from rainbowio import colorwheel
cp.pixels.brightness=0.1
#前5个灯珠指示温度,以温度25℃为最舒适温度(温度范围-20~40℃),此时4个灯珠均显示为绿色,高于25℃向红色渐变,低于25摄氏度向蓝色渐变
#后5个灯珠指示亮度,以l亮度200为最舒适亮度(亮度范围0~400),此时4个灯珠均显示为绿色,高于200向红色渐变,低于200摄氏度向蓝色渐变
tmpMax=40
tmpMin=-20
tmpOk=25
lightOk=200
lightMax=400
lightMin=0
def trans(value,valueMax,valueMin,valueOk):
if value>=25:
if value>valueMax: value=valueMax
val=255/3-(value-valueOk)/(valueMax-valueOk)*(255/3)
else:
if value<valueMin: value=valueMin
val=255/3+(valueOk-value)/(valueOk-valueMin)*(255/3)
return colorwheel(val)
while True:
print(cp.temperature,cp.light)
cp.pixels[0:5]=[trans(cp.temperature,tmpMax,tmpMin,tmpOk)]*5
cp.pixels[5:10]=[trans(cp.light,lightMax,lightMin,lightOk)]*5
time.sleep(0.1)
温度读取与展示
[localvideo]a3563e0a58e0e2285e4cde4d8f76beed[/localvideo]
光线强度读取与展示
[localvideo]0b9769581757e768f82a4b1c1290ed93[/localvideo]
4.基础任务三(必做):接近检测——设定安全距离并通过板载LED展示,检测到入侵时,发起声光报警
红外实现
利用红外发射管发出红外脉冲,然后检测红外接收器信号强度来测算遮挡距离,在到达设定阈值后发出声光报警。
from adafruit_circuitplayground import cp
from rainbowio import colorwheel
import pulseio
import board
import time
import array
import analogio
#设置灯珠
cp.pixels.brightness=0.05
cp.pixels.auto_write=False
#红外接收和发射引脚
tx_pin = board.REMOTEOUT
ds_pin = board.IR_PROXIMITY
#设置脉冲输出
pulseout = pulseio.PulseOut(tx_pin, frequency=38000, duty_cycle=2 ** 15)
# 定义发射脉冲序列
pulse_sequence = array.array('H', [1024]) # 脉冲宽度,单位是微秒
# 红外接近传感器模拟量读取
dispin = analogio.AnalogIn(ds_pin)
# 距离阈值
maxVal=40000
minVal=31000
#滤波窗口设置
windowSize=5
#距离检测
def distanceLv():
#信号滤波
irValList=[]
for i in range(windowSize+2):
pulseout.send(pulse_sequence)
irValList.append(dispin.value)
time.sleep(0.001)
#信号处理,转换为0~10的距离等级
irVal=(sum(irValList)-max(irValList)-min(irValList))/windowSize
if irVal<minVal:
irVal=minVal
elif irVal>maxVal:
irVal=maxVal
distanceLv=round(((maxVal-irVal)/(maxVal-minVal)*10)**4/(10**3))
return distanceLv
#距离展示
def disShow(value):
#达到最小距离,开始报警
if value==0:
cp.pixels.fill((255,0,0))
cp.pixels.show()
time.sleep(0.1)
cp.pixels.fill((0,0,0))
cp.pixels.show()
time.sleep(0.1)
cp.play_tone(1500, 0.05)
#距离等级展示
else:
l=11-value
for i in range(l):
cp.pixels[i]=colorwheel(85-i*9.44)
cp.pixels.show()
cp.pixels[l:10]=[(0,0,0)]*(10-l)
cp.pixels.show()
time.sleep(0.2)
#主循环
while True:
d=distanceLv()
disShow(d)
print((d,))
time.sleep(0.1)
[localvideo]7772fed79710d188b8345fc4dbc2d06e[/localvideo]
超声波距离传感器实现
import time
import board
from adafruit_circuitplayground import cp
from rainbowio import colorwheel
i2c = board.I2C()
# 距离阈值
maxVal=300000
minVal=15000
#设置灯珠
cp.pixels.brightness=0.05
cp.pixels.auto_write=False
#距离展示
def disShow(value):
#达到最小距离,开始报警
if value==0:
cp.pixels.fill((255,0,0))
cp.pixels.show()
time.sleep(0.1)
cp.pixels.fill((0,0,0))
cp.pixels.show()
time.sleep(0.1)
cp.play_tone(1500, 0.05)
#距离等级展示
else:
l=11-value
for i in range(l):
cp.pixels[i]=colorwheel(85-i*9.44)
cp.pixels.show()
cp.pixels[l:10]=[(0,0,0)]*(10-l)
cp.pixels.show()
time.sleep(0.2)
#距离检测
def distanceLv(irVal):
if irVal<minVal:
irVal=minVal
elif irVal>maxVal:
irVal=maxVal
distanceLv=round(((irVal-minVal)/(maxVal-minVal)*10))
return distanceLv
while not i2c.try_lock():
print("waiting for i2c bus")
try:
while True:
i2cAds=[hex(device_address) for device_address in i2c.scan()]
print(
"I2C addresses found:",
i2cAds,
)
if '0x57' in i2cAds:
print("1601 connectted!")
break
time.sleep(2)
while True:
i2c.writeto(0x57, bytearray([1]))
time.sleep(0.5)
bt=bytearray([0,0,0])
i2c.readfrom_into(0x57, bt)
# print(bt)
dis=int.from_bytes(bt, 'big')
print(dis)
disShow(distanceLv(dis))
finally:
i2c.unlock()
[localvideo]9696fa717bdaa0ddcc207670d121d599[/localvideo]
5.进阶任务(必做):制作不倒翁——展示不倒翁运动过程中的不同灯光效果
根据板载三轴加速度传感器的数值估算出不倒翁偏摆的方向,将偏摆方向映射到10个彩灯上,当不倒翁处于立直状态彩灯全为绿色,当发生偏摆则偏摆向下一侧的彩灯向红色变换,偏摆向上的彩灯向蓝色变换。
import time
import math
from adafruit_circuitplayground import cp
from rainbowio import colorwheel
cp.pixels.brightness=0.08
highColorIndex=255/3*2
lowColorIndex=0
g=9.81
#计算不倒翁的方向
def calDirection():
x, y, z = cp.acceleration
y=-y
xyz=[x,y,z]
for i in range(3):
if xyz[i]>g:
xyz[i]=g
elif xyz[i]<-g:
xyz[i]=-g
elif xyz[i]==0:
xyz[i]=0.0001
xyz[i]=xyz[i]/g
# print((xyz))
alph=math.atan(xyz[0]/xyz[1])
if xyz[1]<0:
alph=alph+math.pi
if alph<0:
alph=alph+2*math.pi
if xyz[2]<0:
xyz[2]=0
beta=1-xyz[2]
#返回倾斜方向角度和倾斜程度
return alph,beta
#炫彩灯珠显示不倒翁状态
def pixelsShow(alph,beta):
colorIndexRange=abs((highColorIndex-lowColorIndex))*(beta**0.7)/2
for i in range(10):
colorIndexChange=math.cos(((i+0.5)/10.0*2*math.pi-alph))*colorIndexRange
colorIndex=255/3-colorIndexChange
print(i,colorIndexChange,colorIndex)
cp.pixels[i]=colorwheel(colorIndex)
while True:
alph,beta=calDirection()
pixelsShow(alph,beta)
# time.sleep(0.05)
[localvideo]4a6dcc3268bbb20ae6f1a6c0ae2daf9c[/localvideo]
6 创意任务二:章鱼哥——章鱼哥的触角根据环境声音的大小,章鱼哥的触角可舒展或者收缩
首先根据舵机的引脚完成连线。
调用板载麦克风采集声音,初始化麦克风时根据当前环境声的幅值作为初始基准值,超出基准值的声音幅值将根据大小分为10个等级,根据等级值点亮彩灯,同时在大于2级时根据幅值大小设置舵机转速快慢并在对应转速分别转动0.1s,由舵机来回转动引起的振动来使章鱼的触角伸缩。
import time
import board
import pwmio
import audiobusio
from adafruit_motor import servo
import array
import math
import neopixel
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
sample_rate=16000, bit_depth=16)
pwm = pwmio.PWMOut(board.A2, duty_cycle=2 ** 15, frequency=50)
PEAK_COLOR = (100, 0, 255)
NUM_PIXELS = 10
CURVE = -2
SCALE_EXPONENT = math.pow(10, CURVE * -0.1)
NUM_SAMPLES = 160
pixels = neopixel.NeoPixel(board.NEOPIXEL, NUM_PIXELS, brightness=0.1, auto_write=False)
pixels.fill(0)
pixels.show()
def constrain(value, floor, ceiling):
return max(floor, min(value, ceiling))
def log_scale(input_value, input_min, input_max, output_min, output_max):
normalized_input_value = (input_value - input_min) / \
(input_max - input_min)
return output_min + \
math.pow(normalized_input_value, SCALE_EXPONENT) \
* (output_max - output_min)
def normalized_rms(values):
minbuf = int(mean(values))
samples_sum = sum(
float(sample - minbuf) * (sample - minbuf)
for sample in values
)
return math.sqrt(samples_sum / len(values))
def mean(values):
return sum(values) / len(values)
def volume_color(volume):
return 200, volume * (255 // NUM_PIXELS), 0
my_servo = servo.ContinuousServo(pwm)
samples = array.array('H', [0] * NUM_SAMPLES)
mic.record(samples, len(samples))
samples = array.array('H', [0] * NUM_SAMPLES)
mic.record(samples, len(samples))
input_floor = normalized_rms(samples) + 10
input_ceiling = input_floor + 500
peak = 0
while True:
mic.record(samples, len(samples))
magnitude = normalized_rms(samples)
c = log_scale(constrain(magnitude, input_floor, input_ceiling),
input_floor, input_ceiling, 0, NUM_PIXELS)
print(c)
pixels.fill(0)
for i in range(NUM_PIXELS):
if i < c:
pixels[i] = volume_color(i)
if c >= peak:
peak = min(c, NUM_PIXELS - 1)
elif peak > 0:
peak = peak - 1
if peak > 0:
pixels[int(peak)] = PEAK_COLOR
pixels.show()
if c>2.0:
my_servo.throttle = c/10.0
time.sleep(0.1)
my_servo.throttle = -c/10.0
time.sleep(0.11)
my_servo.throttle = 0.0
[localvideo]964094efd0d6836bbdaa61e8d04fde90[/localvideo]
三、可编译下载的代码
download.eeworld.com.cn/detail/eew_gz8e7C/634236
-
加入了学习《得捷Follow me第二季第1期视频》,观看 得捷Follow me第二季第1期视频
-
上传了资料:
Follow me 第二季第一期任务代码
- 2024-08-18
-
加入了学习《【Follow me第二季第1期】在arduino环境下多app调度全部任务》,观看 【Follow me第二季第1期】在arduino环境下多app调度全部任务
- 2024-02-25
-
发表了主题帖:
【得捷Follow me第4期】W5500-EVB-Pico--树莓派PICO与W5500的学习与使用
本帖最后由 eew_gz8e7C 于 2024-2-25 22:21 编辑
很高兴能获得本期FOLLOW ME活动的参与机会,通过本期活动学习了W5500-EVB-Pico的基本使用,将学习成果汇总如下:
Follow me第4期-总结视频-Follow me第4期 总结视频-EEWORLD大学堂
一、任务及硬件简介
本期任务的主板为W5500-EVB-Pico,其具备了RP2040的易用性,MPY\CPY\Arduino都能很好的兼容,同时加持了W5500网口,使其能够便捷的接入以太网,做网络应用的开发。
主板介绍:
板载的W5500及LED占用了部分IO如下:
显示组件:
根据坛内大佬推荐,本期活动选用pervasivedisplays的电子纸显示器及转接板作为显示套件。pervasivedisplays官方有详细的配套树莓派pico的使用说明,这里摘出重要部分,其他的可参考官方链接。
1.显示屏与转接板连接
2.转接板与PICO连接:
3.连接后效果:
二、任务实现及成果展示
1.开发环境搭建,BLINK,驱动液晶显示器进行显示(没有则串口HelloWorld)
1)开发环境选用Arduino,需要安装第三方平台支持库:https://github.com/WIZnet-ArduinoEthernet/arduino-pico/releases/download/global/package_rp2040-ethernet_index.json,安装方法 文件-》首选项-》其他开发板管理地址处填入确认。
2)安装W5500-EVB-Pico支持库:
工具-》选项-》开发板管理器 搜索wiznet,然后选择开发板
另外,对于电子纸屏幕可以安装官方提供的arduino驱动,在工具-》库管理中搜索ext3即可找到安装。
3)核心代码:
// SDK
#include <Arduino.h>
// Screen
#include "PDLS_EXT3_Basic_Global.h"
#include "hV_HAL_Peripherals.h"
// Configuration
#include "hV_Configuration.h"
// #define EPD_EXT3_266
#define EPD_EXT3_154
#ifdef EPD_EXT3_266
// // Define variables and constants for 2.66 296x152
Screen_EPD_EXT3 epd(eScreen_EPD_EXT3_266, boardRaspberryPiPico_RP2040);
#define LINE1_POS 40, 30
#define LINE2_POS 40, 80
#define FONT Font_Terminal16x24
#endif
#ifdef EPD_EXT3_154
// // Define variables and constants for 1.54 152 x 152
Screen_EPD_EXT3 epd(eScreen_EPD_EXT3_154, boardRaspberryPiPico_RP2040);
#define LINE1_POS 20, 40
#define LINE2_POS 20, 80
#define FONT Font_Terminal8x12
#endif
// Utilities
///
/// @brief Wait with countdown
/// @param second duration, s
///
void wait(uint8_t second)
{
for (uint8_t i = second; i > 0; i--)
{
Serial.print(formatString(" > %i \r\n", i));
delay(1000);
}
Serial.print(" \r\n");
}
// Add setup code
///
/// @brief Setup
///
void setup()
{
Serial.begin(115200);
delay(500);
pinMode(LED_BUILTIN, OUTPUT);
Serial.println("begin... ");
epd.begin();
Serial.println("Characters... ");
epd.clear();
epd.setOrientation(1);//set orientation
epd.selectFont(FONT);
epd.gText(LINE1_POS, "Follow me 4", myColours.red);
epd.gText(LINE2_POS, "w5500-evb-pico", myColours.black);
epd.flush();
wait(8);
}
// Add loop code
///
/// @brief Loop, empty
///
void loop()
{
Serial.print("Follow me 4\n");
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
4)效果展示:
2.基础任务一:完成主控板W5500初始化(静态IP配置),并能使用局域网电脑ping通,同时W5500可以ping通互联网站点;通过抓包软件(Wireshark、Sniffer等)抓取本地PC的ping报文,展示并分析。
1)核心代码:
局域网ping
#include <SPI.h>
#include <Ethernet.h>
#include <Arduino.h>
// 网卡mac地址
byte mac[] = { 0xAE, 0x5D, 0x10, 0x75, 0x88, 0xAD };
// 静态ip地址、DNS服务、网关、子网掩码
// byte ip[] = { 192, 168, 1, 188 };
IPAddress ip(192, 168, 1, 100);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
void setup() {
// 配置串口
Serial.begin(115200);
delay(500);
// 静态IP设置
Ethernet.init(17);
Ethernet.begin(mac, ip, dns, gateway, subnet);
}
void loop() {
Serial.print("RP2040 IP address: ");
Serial.println(Ethernet.localIP());
delay(5000);
}
互联网ping
#include <SPI.h>
#include <Ethernet.h>
#include <Dns.h>
// 网卡mac地址
byte mac[] = { 0xAE, 0x5D, 0x10, 0x75, 0x88, 0xAD };
// 静态ip地址、DNS服务、网关、子网掩码
// byte ip[] = { 192, 168, 1, 188 };
IPAddress ip(192, 168, 1, 100);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
DNSClient dnClient;
IPAddress dstip;
void setup() {
// 配置串口
Serial.begin(115200);
delay(500);
// 静态IP设置
Ethernet.init(17);
Ethernet.begin(mac, ip, dns, gateway, subnet);
}
void loop() {
Serial.print("RP2040 IP address: ");
Serial.println(Ethernet.localIP());
dnClient.begin(Ethernet.dnsServerIP());
const char domains[3][20] = { "www.eeworld.com.cn", "www.digikey.cn", "www.digikey.com" };
for (int i = 0; i < 3; i++) {
if (dnClient.getHostByName(domains[i], dstip) == 1) {
Serial.print(domains[i]);
Serial.print(" = ");
Serial.println(dstip);
} else Serial.println(F("dns lookup failed"));
}
delay(5000);
}
2)效果展示:
3)抓包分析:
打开wireshark,选择和开发板共网的网卡,设置筛选条件为
(ip.dst==192.168.1.172 and ip.src==192.168.1.100) or (ip.src==192.168.1.172 and ip.dst==192.168.1.100)
可以筛选出pc与pico间的报文,可以看到四组报文对应ping命令执行后的四次通信:
3.基础任务二:主控板建立TCPIP或UDP服务器,局域网PC使用TCPIP或UDP客户端进行连接并发送数据,主控板接收到数据后,送液晶屏显示(没有则通过串口打印显示);通过抓包软件抓取交互报文,展示并分析。(TCP和UDP二选一,或者全都操作)
1.TCP服务器核心代码:
#include<Ethernet.h>
//网络配置
// 网卡mac地址
byte mac[] = { 0xAE, 0x5D, 0x10, 0x75, 0x88, 0xAD };
// 静态ip地址、DNS服务、网关、子网掩码
// byte ip[] = { 192, 168, 1, 188 };
IPAddress ip(192, 168, 1, 100);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
// 网络服务客户端
EthernetClient client;
bool haveClient = false;
#define SERVER_PORT 2024
int LEDSTATE = 0;
// 有线网络服务
EthernetServer server(SERVER_PORT);
void setup() {
// 配置串口
Serial.begin(115200);
delay(500);
pinMode(LED_BUILTIN,OUTPUT);
digitalWrite(LED_BUILTIN,LEDSTATE);
// 静态IP设置
Ethernet.init(17);
Ethernet.begin(mac, ip, dns, gateway, subnet);
Serial.println("TCP server Begin @ 192.168.1.100:2024\n");
server.begin();
}
void loop() {
// 处理客户端连接
if (!haveClient) {
// 检查新连接
client = server.available();
if (client) {
haveClient = true;
Serial.println("New client");
while (client.connected()){
if (client.available()){
String buff = client.readStringUntil('\r');
Serial.println(buff);
if (LEDSTATE){
LEDSTATE=0;
}else{
LEDSTATE=1;
}
digitalWrite(LED_BUILTIN,LEDSTATE);
}
}
}
} else if ((!client.connected()) && haveClient) {
client.stop();
client = EthernetClient();
haveClient = false;
Serial.println("client closed!");
}
}
2)tcp通信效果:
3)TCP抓包分析:
可以看到从TCP握手连接到数据传输再到挥手断开的过程:
4.进阶任务:从NTP服务器(注意数据交互格式的解析)同步时间,获取时间送显示屏(串口)显示。
1)安装支持库,arduino有ntp支持库,可以搜索NTPClient下载。
2)核心代码:
注意标准授时结果与北京时间相差8小时,需要设置8*60*60s的补偿方可得到北京时间。
#include <NTPClient.h> //NTP库
#include <Ethernet.h>
//网络配置
// 网卡mac地址
byte mac[] = { 0xAE, 0x5D, 0x10, 0x75, 0x88, 0xAD };
// 静态ip地址、DNS服务、网关、子网掩码
// byte ip[] = { 192, 168, 1, 188 };
IPAddress ip(192, 168, 1, 100);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
EthernetUDP ntpUDP;
long timeOffSet=60*60*8;
NTPClient timeClient(ntpUDP,timeOffSet); // NTP获取时间
void setup() {
// 静态IP设置
Ethernet.init(17);
Ethernet.begin(mac, ip, dns, gateway, subnet);
delay(80000);
//等待网络配置
timeClient.begin();
}
char buffer[100];
void loop() {
timeClient.update();
Serial.print(timeClient.getFormattedTime());
Serial.print("\n");
delay(1000);
}
3)效果展示:
5.终极任务二:使用外部存储器,组建简易FTP文件服务器,并能正常上传下载文件。
原想使用arduino实现ftp但是始终未能成功,无奈只能换个思路采用mpy实现。
1)核心代码:
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 w5500_start():
global nic
# 网口初始化
nic = network.WIZNET5K(spi, Pin(_CS_PIN), Pin(_RST_PIN)) #spi,cs,reset pin
nic.active(True)
# 配置网络
nic.ifconfig(('192.168.1.100','255.255.255.0','192.168.1.1','192.168.1.1'))
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")
w5500_start() # 初始化网络
ftpserver() # 运行 FTP Server
2)效果展示:
三、任务源码
https://download.eeworld.com.cn/detail/eew_gz8e7C/631389
补充内容 (2024-3-1 19:11):
四、心得体会与建议:
通过本次活动的任务实现,入门了arduino IDE的使用,另外对以太网网络通信也加深了理解。感谢eeworld和得捷提供的活动机会,期待下次还能有幸参与!
-
加入了学习《得捷Follow me第3期-总结视频》,观看 得捷Follow me第3期-总结视频
-
上传了资料:
FM4-eew_gz8e7C.zip
- 2024-02-16
-
加入了学习《【得捷电子Follow me第4期】上手W5500-EVB-Pico》,观看 【得捷电子Follow me第4期】上手W5500-EVB-Pico
- 2023-12-09
-
发表了主题帖:
【得捷Follow me第3期】任务提交
本帖最后由 eew_gz8e7C 于 2023-12-9 17:03 编辑
第一部分
第二部分
任务1:使用MicroPython系统
本节任务目标:熟悉Seeed Studio XIAO ESP32C3开发板基本操作,安装esptool,并给开发板刷写MicroPython系统,完成入门程序的运行。
安装好python环境,需要安装的包esptool、mpremote
通过USB接入Seeed Studio XIAO ESP32C3,查看新增COM口,使用esptool刷入固件v1.21.0 (2023-10-05) .bin
esptool.py --chip esp32-c3 -p COM* write_flash -z 0x0 ESP32_GENERIC_C3-20231005-v1.21.0.bin
重启ESP32后,使用mpremote 连接ESP32进入REPL
mpremote connect COM*
打印系统信息,看到输出的mpy版本,至此mpy刷入成功,且可正常运行代码!
print(os.uname())
心得体会建议
在ESP32接入电脑前,提前打开设备管理器以便及时确认对应COM口。
任务2:驱动扩展板上的OLED屏幕
本节任务目标:使用扩展板上的OLED屏幕显示文字和图形。
首先确定扩展板上的屏幕引脚信息,从第一部分硬件介绍可以确认IIC引脚6:SDA,7:SCL。
为了能便捷高效调用OLED屏幕,这里调用peterhinch大神的micropython-nano-gui库来实现屏幕的各种显示功能,它是一个轻量化的micropython显示界面库,基于FrameBuffer驱动的屏幕均可使用,包含了label、dial、textbox等多种显示控件可供调用。
micropython-nano-gui下载好后,使用thony将按下图文件夹将文件上传至esp32:
使用micropython-nano-gui需要在color_setup.py中配置屏幕引脚及宽高信息:
from machine import Pin, I2C
import gc
from drivers.ssd1306.ssd1306 import SSD1306_I2C as SSD
# seeed studio ESP32-C3,OLED连接到 I2C0
i2c = I2C(0, scl=Pin(7), sda=Pin(6), freq=400000)
# 屏幕大小定义
oled_width = 128
oled_height = 64
# 内存回收清理
gc.collect()
# 实例SSD对象
ssd = SSD(oled_width, oled_height, i2c)
如果要显示中文,需要提前将显示的中文字符转为mpy专用字库以便调用,采用micropython-font-to-py可以快速生成所需字库,并上传至lib/gui/fonts文件夹以供调用。
创建3个Lable实例化对象显示中英文,调用FrameBuffer基类中的图形方法绘制图形。
# 1. 屏幕调用基础配置
from color_setup import ssd
from gui.core.nanogui import refresh
from gui.core.writer import Writer
from gui.widgets import label
from gui.widgets.label import Label
# 2.字库调用
import gui.fonts.arial10 as arial10
import gui.fonts.font10 as font10
import gui.fonts.font6 as font6
import gui.fonts.cn16 as zh
import time
# 3.任务2
def task2_1():
# 清屏
ssd.fill(0)
refresh(ssd)
# 在屏幕上显示中英文字符
wri1 = Writer(ssd, font6, verbose=False)
txt1 = Label(writer=wri1, row=2, col=0, text=127,invert=True,align=label.ALIGN_CENTER) #此处text参数为Label的宽度
wri2 = Writer(ssd, font10, verbose=False)
txt2 = Label(writer=wri2, row=20, col=0,text=127,align=label.ALIGN_CENTER)
wri3 = Writer(ssd, zh, verbose=False)
txt3 = Label(writer=wri3, row=40, col=0, text=127,align=label.ALIGN_CENTER)
txt1.value("Task2")
txt2.value("EEWorld")
txt3.value("得捷第三期")
# 输出显示
refresh(ssd)
def task2_2():
#清除屏幕
ssd.fill(0)
refresh(ssd)
#图形显示
ssd.rect(2,2,20,20,1)
ssd.ellipse(40,12,10,10,1)
ssd.ellipse(80,12,20,10,1)
# 输出显示
refresh(ssd)
if __name__=="__main__":
task2_1()
time.sleep(5)
task2_2()
效果展示:[localvideo]0a27fbb71bd2b47ddb2c7f225c5860e7[/localvideo]
心得体会建议:通过调用micropython-nano-gui库可以大幅缩短屏幕类应用开发所需时间。
任务3:控制蜂鸣器播放音乐
本节任务目标:使用Seeed Studio XIAO ESP32C3控制蜂鸣器发出不同频率的声音,并播放一段音乐。
从第一部分硬件介绍可以确认蜂鸣器的引脚为GPIO5
声音的大小与发生体的振幅有关,声音的音调与发声体的振动频率有关。使用PWM控制蜂鸣器,通过调节PWM波的频率来控制音调,调节PWM波的占空比来调整声音大小,将蜂鸣器引脚设置为PWM输出
from machine import Pin, PWM
import utime
# 设置D3(GPIO 5)口为PWM
beeper = PWM(Pin(5))
常见的音调频率见下图
创建字典保存音调与频率对应关系
# 定义音调频率
tones = {'1': 262, '2': 294, '3': 330, '4': 349, '5': 392, '6': 440, '7': 494, '-': 0,
'a':523, 'b': 587, 'c': 659, 'd': 698, 'e': 784, 'f': 880, 'g': 988,' ':0,',':0,
'A':1046, 'B': 1175, 'C': 1318, 'D': 1397, 'E': 1568, 'F': 1760, 'G': 1976}
创建乐谱
# 定义小星星旋律
melody = "1155665-4433221-5544332-5544332-1155665-4433221"
# 定义沧海一声笑旋律
chysx="fecba-cba65-5656abcefecbab-65321-cba65-5656abcefecba"
定义播放函数,并运行试听
def buzzer(disc):
for tone in disc:
freq = tones[tone]
if freq:
beeper.init(duty=20, freq=freq) # 调整PWM的频率,使其发出指定的音调
else:
beeper.duty(0) # 空拍时一样不上电
# 停顿一下 (四四拍每秒两个音,每个音节中间稍微停顿一下)
utime.sleep_ms(400)
beeper.duty(0) # 设备占空比为0,即不上电
utime.sleep_ms(100)
beeper.deinit() # 释放PWM
if __name__=="__main__":
buzzer(melody)
utime.sleep_ms(500)
buzzer(chysx)
效果展示[localvideo]f158b0af64b6572fcc04a9e71614c41b[/localvideo]
心得体会建议:通过本次任务不仅熟悉了蜂鸣器的使用,对ESP32的PWM输出也有了基本掌握。
任务4:连接WiFi网络
本节任务目标:将Seeed Studio XIAO ESP32C3连接到WiFi网络,并访问互联网信息。
通过micropython库连接wifi网络非常简单,使用network模块可以方便地进行WIFI网络/AP热点的配置
使用network.WLAN()进行wifi连接或者ap热点的配置
import network
import time
class WLAN():
def __init__(self,mode='ap',ssid='ESP-AP',key='esp32666'):
if mode=='ap':
self.setAp(ssid,key)
elif mode=='wlan':
self.connect(ssid,key)
def connect(self,ssid:str,key:str):
self.wlan = network.WLAN(network.STA_IF)
self.wlan.active(True)
msg=""
if self.wlan.isconnected():
self.wlan.disconnect()
time.sleep(1)
if not self.wlan.isconnected():
print('connecting to network...')
self.wlan.connect(ssid, key)
wait_time=0
while not self.wlan.isconnected():
time.sleep(1)
wait_time+=1
msg=f"wait for {wait_time} s"
print(msg)
if wait_time>10:
msg=f"connect failed!"
print(msg)
return msg
msg=f"connected! ssid={ssid} ip={self.wlan.ifconfig()[0]}"
print(msg)
return msg
def scan(self):
return self.wlan.scan()
def setAp(self,apssid='ESP-AP',apkey='esp32666'):
self.ap=network.WLAN(network.AP_IF)
if apkey:
self.ap.config(ssid=apssid,password=apkey,authmode=4)
else:
self.ap.config(ssid=apssid,authmode=0)
self.ap.active(True)
if __name__=='__main__':
w=WLAN('wlan','xxx','xxxxxx')
进行https访问测试
import urequests
response = urequests.get("https://www.baidu.com/")
print(response.text)
效果展示[localvideo]c658277f0a3ca1e5d40383692981f30f[/localvideo]
心得体会建议:换用本次活动推荐的PCB天线能够取得更好的WIFI信号效果。
任务5:使用外部传感器
本节任务目标:连接环境光传感器或温湿度传感器,获取传感器的数值,并转换成真实的物理量。
AHT20温湿度传感器采用IIC总线传输数据,光线传感器通过ADC采集,光线传感器接入扩展板A0接口,温湿度传感器接入IIC接口。
AHT20温湿度需要ahtx0驱动可以通过两个渠道下载安装: https://github.com/targetblank/micropython_ahtx0 https://pypi.org/project/micropython-ahtx0/。
另外需要注意,ESP32 adc默认采集电压范围0~1.1V,可以通过配置衰减来扩大量程。
from machine import Pin, I2C, ADC
import utime
from ahtx0 import AHT20
import gui.fonts.font6 as font6
import gui.fonts.cn16 as cn
from gui.core.nanogui import refresh
from gui.core.writer import Writer
from gui.widgets import label
from gui.widgets.label import Label
from color_setup import ssd
import _thread
adcLight=ADC(Pin(2),atten=ADC.ATTN_11DB)#atten=ADC.ATTN_11DB配置衰减
i2cHumiture=I2C(0, scl=Pin(7), sda=Pin(6), freq=400000)
huSensor = AHT20(i2cHumiture)
wri1 = Writer(ssd, font6, verbose=False)
txt1 = Label(writer=wri1, row=2, col=0, text=127,invert=True,align=label.ALIGN_CENTER) #此处text参数为Label的宽度
wri2 = Writer(ssd, cn, verbose=False)
txt2 = Label(writer=wri2, row=17, col=0,text=127,align=label.ALIGN_LEFT)
wri3 = Writer(ssd, cn, verbose=False)
txt3 = Label(writer=wri3, row=32, col=0, text=127,align=label.ALIGN_LEFT)
wri4 = Writer(ssd, cn, verbose=False)
txt4 = Label(writer=wri3, row=47, col=0, text=127,align=label.ALIGN_LEFT)
lightVal=None
def sensorLoop():
global lightVal
while True:
light_adc = adcLight.read_uv()
lightVal = light_adc / 1000.0
print("Light{:.2f} mV".format(lightVal))
print("Temperature{:.2f}".format(huSensor.temperature))
print("Humidity{:.2f}\n".format(huSensor.relative_humidity))
utime.sleep(1)
def sensorLoopThread():
_thread.start_new_thread( sensorLoop, () )
def showSensorData():
while True:
ssd.fill(0)
txt1.value("Task5")
txt2.value("光:{:.1f}".format(lightVal))
txt3.value("温:{:.1f}℃".format(huSensor.temperature))
txt4.value("湿:{:.1f}%".format(huSensor.relative_humidity))
refresh(ssd)
utime.sleep(2)
def task5():
sensorLoopThread()
utime.sleep(0.5)
showSensorData()
if __name__=="__main__":
task5()
效果展示:[localvideo]7a6c6f421208eed454efa949c5d5ad20[/localvideo]
心得体会建议:温湿度信号采集频率不要超过1s/次,否则自身发热可能导致温度采集结果偏差。
任务6:综合实践-开灯提醒器
任务目标:监测环境光强度,如果光线太暗,通过屏幕和蜂鸣器提醒用户开灯,达到保护视力的效果。
实现思路:循环读取光线传感器电压值,并在屏幕显示,当传感器输出电压值低于50mv时,通过蜂鸣器及屏幕提示开灯并点亮LED,当传感器输出电压高于200mV时关闭LED。
关键代码
from machine import Pin, I2C, ADC, PWM
import utime
from ahtx0 import AHT20
import gui.fonts.font6 as font6
import gui.fonts.cn16 as cn
from gui.core.nanogui import refresh
from gui.core.writer import Writer
from gui.widgets import label
from gui.widgets.label import Label
from color_setup import ssd
import _thread
adcLight=ADC(Pin(2),atten=ADC.ATTN_11DB)
i2cHumiture=I2C(0, scl=Pin(7), sda=Pin(6), freq=400000)
huSensor = AHT20(i2cHumiture)
# 设置D3(GPIO 5)口为PWM
beeper = PWM(Pin(5),duty=0)
# 设置D2(GPIO 4)口为LED负载
led=Pin(4,Pin.OUT)
led.off()
wri1 = Writer(ssd, font6, verbose=False)
txt1 = Label(writer=wri1, row=2, col=0, text=127,invert=True,align=label.ALIGN_CENTER) #此处text参数为Label的宽度
wri2 = Writer(ssd, cn, verbose=False)
txt2 = Label(writer=wri2, row=17, col=0,text=127,align=label.ALIGN_LEFT)
wri3 = Writer(ssd, cn, verbose=False)
txt3 = Label(writer=wri3, row=32, col=0, text=127,align=label.ALIGN_LEFT)
wri4 = Writer(ssd, cn, verbose=False)
txt4 = Label(writer=wri3, row=47, col=0, text=127,align=label.ALIGN_LEFT)
lightVal=None
def sensorLoop():
global lightVal
while True:
light_adc = adcLight.read_uv()
lightVal = light_adc/1000.0
print("Light:{:.2f}".format(lightVal))
# print("Temperature:{:.2f}".format(huSensor.temperature))
# print("Humidity:{:.2f}\n".format(huSensor.relative_humidity))
utime.sleep(0.5)
def sensorLoopThread():
_thread.start_new_thread( sensorLoop, () )
def showSensorData():
while True:
ssd.fill(0)
txt1.value("Task6")
txt2.value("光:{:.1f}".format(lightVal))
if lightVal<50.0:
txt3.value("光线暗!请开灯!")
beeper.init(duty=20, freq=1000)
utime.sleep_ms(200)
beeper.duty(0)
utime.sleep_ms(50)
beeper.init(duty=20, freq=1000)
utime.sleep_ms(200)
beeper.duty(0)
led.on()
else:
if lightVal>200.0:
led.off()
txt3.value("")
refresh(ssd)
utime.sleep(2)
def task6():
sensorLoopThread()
utime.sleep(0.5)
showSensorData()
if __name__=="__main__":
task6()
效果展示[localvideo]5cedc010dd5546e819b7b8b3fbbebd4c[/localvideo]
心得体会建议:PIN口负载能力有限,控制灯带开关可以通过三极管开关电路实现。
第三部分
可执行的任务代码
总结
通过本期活动,学习了ESP32 Micropython的基本使用方法,掌握了SSD1306的LCD屏幕的中英文及图形显示、PWM驱动蜂鸣器播放简单的音乐、ESP32的WIFI连接与网络访问、光线及温湿度传感器的信号读取。最后感谢得捷电子和EEWORLD举办的本次活动。