- 2024-12-10
-
加入了学习《【Follow me第二季第4期】任务汇报》,观看 【Follow me第二季第4期】任务汇报
-
加入了学习《Follow me第二季第1期》,观看 创意任务二:章鱼哥
-
加入了学习《Follow me第二季第1期》,观看 创意任务三:触摸钢琴
-
加入了学习《Follow me第二季第1期》,观看 创意任务一:可穿戴装饰——报警器
- 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期视频》,观看 得捷Follow me第二季第1期视频
-
发表了主题帖:
【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 第二季第一期任务代码
- 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和得捷提供的活动机会,期待下次还能有幸参与!