1 项目描述
Funpack第三季第五期活动,选用板卡 Teensy4.1 ,实现任务1,点灯,从板子的以太网排针处引出接口,并通过网口通信,控制板子上的LED灯。
1.1 项目介绍
该项目采用 Teensy4.1 板卡,以 Arduino IDE 作为开发环境,通过网线连接到路由器,建立一个局域网网页,手机或者电脑等移动设备在同一个局域网可以访问网页,在网页上控制板卡上的LED亮灭,也可以拖到滑条控制LED的亮度。
1.2 Teensy4.1 板卡介绍
Teensy4.1 开发板是一款成熟的开发板,具有完整的 Arduino 开发环境,易于开发。它采用恩智浦的 i.MX RT1062 芯片,具有 Arm Cortex-M7 内核,具有实时微控制器性能和高集成度,适用于工业和物联网应用。
i.MX RT1060 CM7 工作频率高度600MHz,具有8MB Flash和1MB片上RAM。该系列提供2D图形、摄像头、各种存储器接口和各种连接接口,包括UART、SPI、I2C、USB、2路10/100M 以太网和 3路 CAN。用于实时应用的其他功能包括高速GPIO、CAN FD 和同步并行 NAND/NOR/PSRAM 控制器。
1.3 设计思路
该项任务分解成以下4个子任务:
- 开发板点亮、熄灭LED
- 开发板以PWM的方式调节LED的亮度
- 开发板通过网线接入网络,创建网页服务器
- 服务器添加功能,网页控制LED亮灭,拖动滑条控制LED亮度
1.3.1 点亮、熄灭LED
第一次接触 Arduino 开发环境,先从点灯开始。安装 Arduino IDE,创建点灯工程并不难,这里不赘述了。以下是一个最简单的点灯代码:
const int led_pin = 13; // 内置的 LED 管脚是 13
void setup(void)
{
pinMode(led_pin, OUTPUT); // 设置管脚为输出模式
}
void loop(void)
{
digitalWrite(led_pin, HIGH); // 管脚输出高电平
delay(500); // 延时 500ms
digitalWrite(led_pin, LOW); // 管脚输出低电平
delay(500); // 延时 500ms
}
1.3.2 以PWM的方式调节LED亮度
Arduino 平台支持脉宽调制(PWM, Pulse Width Modulation),这是一种用于控制模拟电路或调节数字信号平均功率的技术。PWM 通过快速地开启和关闭数字信号来实现这一点,其占空比(即信号处于高电平的时间与周期的比率)决定了输出的平均电压值。
- 占空比 (Duty Cycle):是指一个周期内信号为高电平的时间所占的比例。例如,50% 的占空比意味着信号一半时间是高电平,另一半时间是低电平。
- 频率 (Frequency):PWM 信号的频率决定了每秒钟发生多少个完整的周期。不同的引脚可能支持不同的 PWM 频率。
- 分辨率 (Resolution):指 PWM 占空比可以设置的精度,通常以位数表示。8 位分辨率意味着有 256 (2^8) 个可能的占空比设置。
并非所有的 Arduino 数字引脚都支持 PWM。例如 Teensy4.1 标有 PWM 的管脚才支持,如下图所示。
analogWrite() 函数
在 Arduino 中,使用 analogWrite() 函数来生成 PWM 信号。该函数接受两个参数:要设置的引脚编号和占空比值(范围通常是0到255,对应0%到100%的占空比)。
通过 PWM 调节 LED 亮度的示例代码如下:
int ledPin = 9; // 选择连接 LED 的引脚
void setup() {
pinMode(ledPin, OUTPUT); // 设置为输出模式
}
void loop() {
for (int i = 0; i <= 255; i++) { // 从 0 到 255 循环
analogWrite(ledPin, i); // 改变 PWM 占空比
delay(15); // 等待 15 毫秒
}
for (int i = 255; i >= 0; i--) { // 从 255 到 0 循环
analogWrite(ledPin, i); // 改变 PWM 占空比
delay(15); // 等待 15 毫秒
}
}
1.3.3 接入网线,创建服务器
连接 Ethernet Kit 到 Teensy4.1 开发板,另一头连接到路由器。然后代码中引入 NativeEthernet 库,先设置MAC和IP地址,然后创建HTTP服务器,等待客户端连接并处里客户端连接请求。
以下代码是一个最简单的示例:
#include <NativeEthernet.h>
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; // 设置板卡MAC地址
IPAddress ip(192, 168, 3, 16); // 设置板卡IP 地址
EthernetServer server(80); // 创建以太网服务器,端口 80 (HTTP协议的默认端口)
void handleRequest(String request) {
// TODO: 处理网页请求
}
void sendResponse(EthernetClient client) {
// 生成网页响应,再发送给客户端
}
void setup(void) {
Serial.begin(115200); // 初始化串口
Ethernet.begin(mac, ip); // 初始化以太网
server.begin(); // 初始化HTTP服务器
Serial.println("Server is at " + String(Ethernet.localIP() )); // 打印本地以太网IP地址
}
void loop(void) {
// 监听客户端请求
EthernetClient client = server.available();
if (client) {
boolean currentLineIsBlank = true;
String currentRequest = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
currentRequest += c;
if (c == '\n' && currentlineIsBlank) { // 请求结束符(空行)
handleRequest(currentRequest); // 处理网页请求
sendResponse(client); // 生成 HTTP 响应,发送网页到客户端
break;
}
if (c == '\n') {
currentLineIsBlank = true;
} else if (c != '\r') {
currentLineIsBlank = false;
}
}
}
delay(1);
client.stop();
}
}
1.3.4 网页控制LED亮灭、滑条控制亮度
实现上面的 handleRequest()
函数,处理客户端请求,解析LED控制命令,然后实现 sendRequest()
函数,把执行结果封装成网页响应文本信息,再发送给客户端。
这两个函数的详细实现参见下文软件流程图。
1.4 硬件介绍
此次活动采购了两个物件,一个是主控 Teensy4.1,一个是 SparkFun Electronics ETHERNET Kit For Teensy4.1。
在将 Teensy4.1 焊上排针,把 Ethernet Kit 排线、网口焊接到PCB上,最终插入到 Teensy4.1 板卡上,效果如下图3.
插曲:Ethernet Kit 一头通过网线接入到路由器,只有一个灯是亮的。我以为是哪里出问题了,以为另外一个灯坏了。最终检查 Ethernet Kit 原理图,才发现只有一个灯接到了 LED 管脚,另外一个灯两个管脚都浮空。
2 软件流程图
此项目基于 HTTP 服务,在局域网建立一个网页服务器,等待客户端请求,然后处理请求,最后把处理后的结果包装到网页文本中再次发送给客户端。
2.1 主流程
整个程序的主流程如下图所示:
- 在全局变量和
setup()
函数中初始化硬件、初始化网络,最后初始化 HTTP 服务; - HTTP 服务等待客户端连接,接收客户端请求;
- 处理客户端请求,并发送响应给客户端;
- 关闭当前客户端请求,并等待下一个客户端请求;
相关代码如下
#include <Arduino.h>
#include <NativeEthernet.h>
// 设置连接以太网的MAC地址和IP地址
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 3, 16); // 设置开发板的IP地址
// 创建以太网服务器,端口80(HTTP协议的默认端口)
EthernetServer server(80);
// LED控制引脚
int pwmPin = 13; // 使用 PWM 的引脚(pin 13)
int brightness = 128; // 初始化亮度为最大值的一半(128)
int previousBrightness = 128; // 保存熄灭前的亮度
bool isLedOn = true; // 保存LED的开关状态
/*----------------------------------------- 函数声明 ---------------------------------------------*/
void handleRequest(String request);
void sendResponse(EthernetClient client);
int percentToBrightness(int percent);
/*----------------------------------------- 函数实现 ---------------------------------------------*/
// 将百分比转换为对应的 0-255 亮度值
int percentToBrightness(int percent)
{
return map(percent, 0, 100, 0, 255);
}
void setup()
{
Serial.begin(9600); // 初始化串口,用于调试输出
// 初始化以太网
Ethernet.begin(mac, ip);
server.begin();
// 设置LED引脚为输出模式
pinMode(pwmPin, OUTPUT);
// 初始化LED状态和亮度,设置初始亮度为最大值的一半
analogWrite(pwmPin, brightness);
Serial.print("Server is at ");
Serial.println(Ethernet.localIP());
}
void loop()
{
// 监听客户端请求
EthernetClient client = server.available();
if (client) {
boolean currentLineIsBlank = true;
String currentRequest = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
currentRequest += c;
// 请求结束符(空行)
if (c == '\n' && currentLineIsBlank) {
// 处理网页请求
handleRequest(currentRequest);
// 生成HTTP响应,发送网页到客户端
sendResponse(client);
break;
}
if (c == '\n') {
currentLineIsBlank = true;
} else if (c != '\r') {
currentLineIsBlank = false;
}
}
}
delay(1);
client.stop();
}
}
2.2 处理客户端请求
服务器接收到客户端的请求,分为两类:
- 一类是
GET /TOGGLE
即网页上的按钮,按下表示LED由点亮变为熄灭,由熄灭变为点亮; - 一类是
BRIGHTNESS=
即网页上的滑条,滑条拖动一次则发送这个请求给服务器。服务器根据滑条数值来改变LED的亮度;
我修复了一个问题,如果滑条拖动到0则熄灭LED,但是如果下次从网页按钮点亮LED,那么由于之前的滑条数值为0,那么点亮LED时的亮度值为0,因此点亮动作执行了但是LED实际没有点亮。我增加了一个变量 previousBrightness
来保存之前的亮度值,如果之前的亮度值为0,再次点亮LED时,默认把亮度值调整为 50% ,可以避免没有点亮的问题。
这个功能对应的函数是 handlRequest()
,代码如下:
// 处理客户端请求,解析LED的控制指令
void handleRequest(String request)
{
// 控制LED亮灭
if (request.indexOf("GET /TOGGLE") != -1) {
if (isLedOn) {
previousBrightness = brightness; // 保存当前亮度
analogWrite(pwmPin, 0); // 熄灭LED
isLedOn = false;
} else {
// 重新打开LED,如果亮度为0,恢复最大值的一半
if (previousBrightness == 0) {
brightness = 128;
} else {
brightness = previousBrightness;
}
analogWrite(pwmPin, brightness);
isLedOn = true;
}
} else if (request.indexOf("BRIGHTNESS=") != -1) { // 解析亮度调节请求,百分比值映射到0-255
int index = request.indexOf("BRIGHTNESS=");
String percentValue = request.substring(index + 11); // 百分比值
int percent = percentValue.toInt();
// 将百分比转换为0-255的亮度范围
brightness = percentToBrightness(percent);
brightness = constrain(brightness, 0, 255); // 限制范围为0-255
// 如果亮度不为0,点亮LED
if (brightness > 0) {
analogWrite(pwmPin, brightness); // 使用PWM控制亮度
isLedOn = true; // 无论LED之前是否关闭,都应该点亮
previousBrightness = brightness; // 更新保存的亮度值
} else {
// 如果亮度为0,保持LED关闭
analogWrite(pwmPin, 0);
isLedOn = false;
}
}
}
2.3 发送响应请求
在处理完客户端的请求后,在这个函数 sendResponse()
中构造响应文本,然后发送到对应的客户端。流程如下:
- 网页界面布局,添加标签、按键、滑条等元素
- 根据变量
isLedOn
设置 LED 状态的按键样式 - 根据变量
brightness
亮度值更新滑条的百分比数值 - 给滑条绑定函数,每当滑条拖动则提交表格,更新
BRIGHTNESS=xxx
请求 - script 函数,实现滑条拖动时监听状态的函数
- script 函数,
window.load()
窗口加载时更具isLedOn
更新滑条的背景色 - script 函数,设置一个定时器,每隔1000ms请求主页
- 发送网页文本和样式到客户端
3 功能展示及说明
3.1 网页效果
先确保电脑或者手机和开发板在同一个局域网,然后在浏览器中输入开发板IP地址,如下图:
- 网页页面中间上方一个标签显示 LED Control
- 中间一行左边一个标签 LED Status: 右边是一个按键表示 LED 亮灭的状态,单击可以切换LED 点亮、熄灭;
- 下方一行左边一个标签 Set LED Brightness: 右边是一个滑条,拖动可以改变LED亮度。
- 如果滑条拖动到最左边,滑条数值为0,则表示关闭LED,同时上方的按键变为灰色;如果拖动滑条数值不为0,上方的按键变为黄色;
演示视频,见 B站视频-Funpack3-5 Teensy4.1 网络控制LED
4 心得体会
此次活动收获颇丰:
- 第一次接触 Arduino 开发环境,从零学会了环境搭建、代码编写、调试。随着学习的深入,尝试着用 PlatformIO IDE 也能搭建环境、写代码,更方便的完成任务;
- 之前接触过NXP的其他芯片,尝试用 NXP 官方 IDE 开发 i.MX RT1062 点灯程序,编译输出 hex 文件,成功用 Teensy Loader 烧录并运行;
- Arduino 环境支持的第三方库非常丰富,引用非常方便,开发项目原型非常便捷;
- 引用第三方库创建网页应用非常方便,只需处理用户请求、再发送结果给客户端,流程简洁,可类比UART命令行程序,读取输入、解析命令、执行命令、发送结果;
- Arduino IDE 也有缺点,编译每次都从零开始,速度很慢,属实让人着急;用 PlatformIO IDE 就非常方便,支持增量编译。