1. 项目简介
本项目旨在设计并实现一个嵌入式系统,该系统能够通过以太网接口与计算机进行通信,实现数据传输及控制功能。具体而言,嵌入式板卡将通过以太网接口与计算机相连,计算机能够实时获取板卡上的温度、触摸以及按键信息,同时,计算机还可以通过网络远程控制板卡上的RGB LED灯以及其他执行器(如扩展模块)工作。该项目涉及网络通信协议、嵌入式系统开发等多个方面,最终目标是实现一个功能齐全、性能稳定的嵌入式系统,能够为类似应用提供参考和借鉴。
工程文件已上传Github,项目仓库 。
2. 项目背景
随着物联网(IoT)技术的发展,基于以太网的通信技术在嵌入式系统中的应用越来越广泛。以太网具有数据传输速率高、通信稳定性好、易于扩展等优势,因此,在工业控制、智能家居、远程监控等领域有着广泛的应用前景。通过本项目的实施,能够加深对嵌入式系统中以太网通信的理解,并为未来的相关技术开发奠定基础。
3. 项目设计思路
3.1 总体设计
本项目的总体设计思路是通过以太网实现嵌入式板卡与计算机之间的双向通信。系统总体架构分为两部分:嵌入式端和计算机端。
嵌入式端:嵌入式系统通过以太网模块与计算机建立连接,并负责采集温度、触摸、按键信息,将数据通过以太网发送给计算机。同时,嵌入式系统接收来自计算机的控制指令,控制RGB LED灯及PWM模块的工作。
计算机端:计算机端的应用软件负责接收来自嵌入式系统的传感器数据,并通过友好的用户界面显示给用户。此外,计算机端还可以发送控制命令,远程控制嵌入式设备的运行状态。在调试阶段,计算机的软件为网络调试助手。
本项目系统设计如下
3.2 功能模块划分
- 为了实现上述功能,本项目将系统划分为以下几个模块:
- 以太网通信模块:负责嵌入式板卡与计算机之间的数据传输,支持TCP/IP或UDP协议,确保数据的稳定传输。
- 传感器采集模块:用于采集嵌入式板卡上的温度、触摸和按键信息,并将数据通过以太网传输给计算机。
- 执行器动作模块:负责接收来自计算机的控制指令,控制RGB LED灯的状态及其他执行器的运行。
3.3 通信协议设计
在通信协议设计方面,采用常见的TCP/IP协议进行可靠的数据传输,使用了Lwip作为通信框架,Netconn作为API。嵌入式板卡作为服务器端,计算机作为客户端,通过建立TCP连接进行数据交换。具体通信流程如下:
- 板卡到主机发送信息:在客户端访问板卡后,板卡会先向客户端发送连接成功信息,然后按照一定间隔发送温度信息。在按键触摸板按下后,会向客户端发送触摸板、按键按下的信息。
- 主机向板卡发送控制命令:计算机端通过调试助手发送控制指令。控制指令通过TCP连接发送到嵌入式板卡。嵌入式板卡接收控制指令并执行相应操作(如控制RGB LED灯的亮灭、颜色变化等)。
4. 硬件设计与介绍
4.1 硬件平台
嵌入式系统的硬件平台基于NXP官方的FRDM-MCXN947开发板。该开发板支持以太网通信的微控制器(MCU)其具有丰富的I/O接口,能够方便地连接各种传感器和执行器。此外,MCU内置了以太网控制器,可以直接实现以太网通信。
4.2 传感器模块
- 温度传感器:使用板载p3t1755温度传感器,用于实时监测环境温度,支持模拟或数字接口,数据采集后通过MCU处理并传输至计算机。
- 触摸传感器:用于检测用户的触摸操作,采用电容式触摸检测,通过轮询方式采集数据。
- 按键模块:用于检测用户的按键输入,使用板载SW3按键。
4.3 执行器模块
- RGB LED灯:通过多通道GPIO控制,实现RGB LED灯的颜色变化调节。控制指令由计算机端通过以太网发送至嵌入式板卡。
- 扩展模块:采用L9110S电机驱动模块,使用PWM对电机转速进行控制。
4.4 以太网模块
- 利用板卡单片机中集成的以太网外设和LAN8741A芯片实现网络通信。
5. 软件开发与实现
5.1 嵌入式端软件设计
嵌入式系统的软件部分采用嵌入式C语言开发,利用实时操作系统(RTOS)实现多任务并行处理。软件主要包含以下功能:
- 初始化配置:包括MCU的时钟配置、I/O口初始化、以太网模块初始化等。
- 传感器数据采集:周期性采集温度、触摸、按键信息,并将数据存储在缓冲区中。
- 以太网通信:通过TCP/IP协议栈,实现与计算机的双向数据通信。
- 命令解析与执行:接收并解析来自计算机的控制指令,执行相应操作。
5.2 计算机端数据获取和指令发送
计算机端的软件采用网络调试助手,主要负责与嵌入式系统进行数据交互。其主要功能包括:
- 连接管理:与嵌入式板卡建立TCP连接,并处理连接断开、重连等情况。
- 数据接收与显示:接收并解析传感器数据,实时显示在用户界面上。
- 控制命令发送:根据用户操作,生成控制指令并发送给嵌入式板卡。
6. 功能展示和说明
6.1 MCU与PC以太网通信实现
MCU与PC的以太网通信,在硬件层上调用了MCU内部的ENET外设,在运输层使用TCP协议。MCU作为服务端,PC作为客户端。在MCU完成以太网初始化后,PC通关连接服务端与MCU建立通信。通信的实现使用了Lwip框架,使用了netconn作为API。
6.2 传感器类模块信息获取
MCU按照一定的时间间隔对温度传感器数据进行读取,通信协议为I3C。在完成传感器数据读取后,使用Netconn API完成以太网数据发送。上位机数据通信界面如下图。
MCU按照轮询的方式对按键和触摸屏的状态进行获取,并通过Netconn API完成状态发送,按键和触摸屏状态发送如下图。
6.3 执行器类控制
首先定义了指令格式:”xxx xxx xxx +xxx”。该指令为一段长度为16的字符串,包含了3个取值范围0~100的正整数和一个取值范围-100~100的整数。前三个正整数为RGB通道的占空比,第四个整数为电机占空比和转速信息。由于时间限制尚未实现对LED进行PWM调光,因此实际指令解析中,第1、4和8位的逻辑值对应了RGB三通道引脚的输出逻辑值。
当上位机发送 “100 100 100 +000” 指令时,板卡RGB灯亮白色。当上位机发送 “100 000 100 +000” 指令是,板卡RGB灯亮紫色。当上位机发送 “100 000 000 +000” 时,板卡RGB灯亮红色。当上位机指令第四个整数数值变化时,电机转速也会相应变化。由于涉及到上位机指令和板卡执行器的同时演示,此部分功能的演示效果请见演示视频。
7. 主要代码片段说明
7.1 TCP Server
此部分代码初始化MCU位TCP通信的服务端,然后等待客户端连接。在客户端连接后,建立用于发送和接收数据的Netconn对象newconn,并发送连接成功通知,随后通过轮询的方式获取客户端消息。
static void server(void *thread_param)
{
char temperature_str[20];
err_t err;
ip4_addr_t ipaddr;
uint8_t send_buf[]= "Hello PC Client! \n";
uint32_t ulReturn;
/* Create a new connection handle */
conn = netconn_new(NETCONN_TCP);
netconn_bind(conn, IP_ADDR_ANY, 5001);
LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);
netconn_listen(conn);
while (1)
{
err = netconn_accept(conn, &newconn);
/* Accept the new connection */
if(err == ERR_OK)
{
PRINTF("\r\nnew connect\r\n");
netconn_write(newconn, send_buf, strlen(send_buf), 0);
struct netbuf *buf;
void *data;
u16_t len;
/* Receive & transmit data */
while ((err = netconn_recv(newconn, &buf)) == ERR_OK)
{
do
{
netbuf_data(buf, &data, &len);
err= netconn_write(newconn, data, len, NETCONN_COPY);
strncpy(data_uart,data,16);
data_uart[16] = '\0';
PRINTF(data_uart);
if (err != ERR_OK)
{
PRINTF("tcpecho: netconn_write: error \"%s\"\n",lwip_strerr(err));
}
}while(netbuf_next(buf) >= 0);
netbuf_delete(buf);
}
netconn_close(newconn);
netconn_delete(newconn);
// vTaskDelay(3);
}
else
continue;
// taskYIELD();
}
// }
}
7.2 温度传感器数据读取
首先通过I3C通信协议,访问温度传感器数据并进行数据解析以获取温度数据。在获取数据后通过newconn对象将温度信息通过以太网发送给PC客户端。为了保证通信不被中断打断,此段代码运行中操作系统应当进入临界段。
static void p3t1755_task(void *pvParameters)
{
for(;;)
{
char temperature_str[23];
uint32_t ulReturn;
ulReturn = taskENTER_CRITICAL_FROM_ISR();
result = P3T1755_ReadTemperature(&p3t1755Handle, &temperature);
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
if (result != kStatus_Success)
{
PRINTF("\r\nP3T1755 read temperature failed.\r\n");
}
sprintf(temperature_str,"Temperature:%.2f,PWM %d",temperature,pwmVal);
if(newconn != NULL)
ret = netconn_write(newconn,temperature_str,strlen(temperature_str),0);
sprintf(temperature_str,"\r\nTemperature:%.2f \r\n",temperature);
PRINTF(temperature_str);
// SDK_DelayAtLeastUs(1000000, CLOCK_GetCoreSysClkFreq());
vTaskDelay(2000);
}
}
7.3 按键、触摸板的状态读取
按键状态的读取采用软件滤波的方式检测状态,当按键被按下时通过以太网向客户端发送信息。触摸板的状态检测则通过在后台运行的低优先级的循环不断检测触摸板状态,当回调函数被触发时MCU将按键状态发送给客户端。
static void SW_task(void *pvParameters)
{
char key_str[20];
for(;;)
{
// PRINTF("SW3 task!\r\n");
if (!GPIO_PinRead(BOARD_SW3_GPIO, BOARD_SW3_GPIO_PIN))
{
vTaskDelay(50); // Debounce
if (!GPIO_PinRead(BOARD_SW3_GPIO, BOARD_SW3_GPIO_PIN))
{
sprintf(key_str,"key pressed");
if (newconn != NULL)
ret = netconn_write(newconn,key_str,strlen(key_str),0);
PRINTF("\r\nSW3 pressed!\r\n");
}
}
vTaskDelay(200);
}
}
static void touch_pad_task(void *pvParameters)
{
// uint32_t ulReturn;
for (;;)
{
nt_task();
vTaskDelay(10);
}
}
static void keypad_callback(const struct nt_control *control, enum nt_control_keypad_event event, uint32_t index)
{
char touch_str[20];
switch (event)
{
case NT_KEYPAD_RELEASE:
{
LED_RED_OFF();
LED_GREEN_OFF();
LED_BLUE_OFF();
}
break;
case NT_KEYPAD_TOUCH:
switch (index)
{
case 0:
/* WHILE on, full brightness */
PRINTF("\r\n touch pad pressed \r\n");
sprintf(touch_str,"touch pad pressed");
if (newconn != NULL)
ret = netconn_write(newconn,touch_str,strlen(touch_str),0);
// LED_RED_ON();
// LED_GREEN_TOGGLE();
// LED_BLUE_ON();
break;
... ...
}
... ...
break;
}
}
7.4 指令控制(RGB LED与PWM)
在server任务中读取的指令字符串的对应位,以获取各通道的逻辑值,并输出相应的状态。
static void rgb_led_task(void *pvParameters)
{
char red_buff[3],green_buff[3],blue_buff[3];
uint8_t red = 0,green = 0,blue = 0;
for (;;)
{
strncpy(red_buff,data_uart,1);
strncpy(green_buff,data_uart+4,1);
strncpy(blue_buff,data_uart+8,1);
red = atoi(red_buff);
green = atoi(green_buff);
blue = atoi(blue_buff);
if(red == 1)
LED_RED_ON();
else if(red == 0)
LED_RED_OFF();
if(green == 1)
LED_GREEN_ON();
else if(green == 0)
LED_GREEN_OFF();
if(blue == 1)
LED_BLUE_ON();
else if(blue == 0)
LED_BLUE_OFF();
vTaskDelay(50);
}
}
static void pwm_task(void *pvParameters)
{
pwm_config_t pwmConfig;
pwm_fault_param_t faultConfig;
char toward[1]="+";
char pwm_val[4] = "+000";
char buffer[5] = "+ 000";
// PWM_Init_all();
for (;;)
{
// strncpy(toward,data_uart+12,1);
strncpy(pwm_val,data_uart+12,4);
/* Delay at least 100 PWM periods. */
// SDK_DelayAtLeastUs((1000000U / APP_DEFAULT_PWM_FREQUENCE) * 100, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
pwmVal = atoi(pwm_val);
/* Reset the duty cycle percentage */
if(pwmVal>=0)
{
toward_flag = 0;
if(pwmVal > 100)
pwmVal = 100;
if(pwmVal < 4)
pwmVal = 4;
/* Update duty cycles for all 3 PWM signals */
PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_SignedCenterAligned, abs(pwmVal)); // p2_4
PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_1, kPWM_PwmA, kPWM_SignedCenterAligned, (0 >> 1)); // p2_6
// PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_2, kPWM_PwmA, kPWM_SignedCenterAligned, (pwmVal >> 2));
}
else
{
toward_flag = 1;
if(pwmVal < -100)
pwmVal = -100;
if(pwmVal > -4)
pwmVal = -4;
/* Update duty cycles for all 3 PWM signals */
PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_SignedCenterAligned, 0); // p2_4
PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_1, kPWM_PwmA, kPWM_SignedCenterAligned, (abs(pwmVal) >> 1)); // p2_6
}
/* Set the load okay bit for all submodules to load registers from their buffer */
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0 | kPWM_Control_Module_1 | kPWM_Control_Module_2, true);
vTaskDelay(10);
}
}
8. 项目优势与应用前景
本项目的实现,不仅增强了嵌入式系统与以太网通信的理解,还具有良好的实际应用前景。其优势包括:高效的数据传输:基于以太网通信,能够实现高速稳定的数据传输,适合应用于远程监控和控制系统。良好的扩展性:支持多种传感器和执行器的扩展,能够适应不同的应用场景。易于集成:设计上具有较高的灵活性,能够与现有的工业控制系统或智能家居系统无缝集成。
9. 未来的计划
- 引入PWM对LED光强进行调节;
- 使用信号量,消息队列替代全局变量进行参数传递,并学习RTOS中添加中断以实现更复杂的功能。
10. 感想
实时操作系统一直是我十分想学习和实操的一个知识点,它为复杂的嵌入式任务提供了解决方案,让开发者可以不用考虑时序的兼容性,专注于任务和应用的开发。对于本项目中的多传感器数据读取和处理,该技术很好的满足了这一需求。同时物联网也是我十分感兴趣的点,在本项目开始的一个月前,本科的学长邀请我参与一个以太网和wifi的嵌入式项目,由于现实条件限制本项目并未实际开展,从那时起物联网就好像一个锚沉在了心底,在我看来网络作为一种高速复杂的通信协议,相对于其他通信协议更加复杂,也更有实际应用价值。正是看到了该任务同时可以满足我的两个“夙愿”,我才选择参与本次Funpack活动。很感谢硬禾学堂和得捷电子提供的宝贵的学习机会。
对于活动的心得、厂商的建议,主要有四点。首先是将触摸板移植进RTOS的过程中,我采用了十分原始的轮询法,并且将他作为后台低优先级任务进行,我也十分惊讶这样系统居然可以work, 如果NXP的Touch库可以提供更丰富的包含操作系统和中断读取状态的历程供初学者学习那一定是极好的!然后是外设配置历程,历程中仅对IO和时钟主频利用配置软件进行配置,外设全部以代码的形式进行配置,这当然可以展示外设配置的过程,但如果提供配置软件配置的指导,以及在配置好的基础上还需要做哪些工作(句柄定义、句柄初始化等)更易于非专业人士快速上手。同时在对于新引入路径的配置中,IDE的官方没有提供一个完善的流程提示,以至于没有意识到需要将新路径添加到PATH的配置中(即path的配置和headfile的配置是两件事情),这一点一度让我怀疑人生,目前解决方案也已经分享在交流群了。最后是关于配置工具的优化问题,我的配置工具在拯救者y7000p笔记本上很难流畅运行,基本是3妙一卡,我自知电脑的配置应该是平均值以上的水准,希望可以对配置工具有所有化。需要声明:我的本科专业是机器人工程,研究生专业是生物医学工程,一直以来只是个嵌入式技术的应用者和爱好者,并非专业从业者,如有谬误请务必海涵!(关于第一点和第二点还会专门发一篇博客阐述)
参与项目的这两个月,我的生活有了无数个亟待解决的麻烦,不少新的变化要我去适应,还有无数烦恼袭来,我很庆幸没有当鸽子而是完成了任务。我实际完成任务的时间并不是很长,其中等待国际快递的流转就花费了半个月的时间,期间我借助野火提供的RTOS历程和Lwip教程对两个知识点有了初步的了解。剩下大多数时间,和其他本科毕业生、研究生新生不同的是,我并没有去享受暑假,更没有机会享受,我提前进入了研究生导师的课题组,基于STM32和拾波线圈开展了研究生课题并有所成果,这耗费了我大量精力。在这两个月我经历了诸如家人健康问题、毕业、分手、和研究生实验时常失败数据不理想等无数个我不得不翻越的大山,以至于我需要在筋疲力尽的周末匀出时间来才能调试学习熟悉项目,最后几乎是踩着DDL完成的项目。十分惭愧对于一直想做的东西,并没有能力给予足够的精力。在这无数个苦恼和变化中,正是对单片机自大一就养成的兴趣让我在生活中有了片刻喘息的机会,可以说和单片机呆在一起的时候我可以什么都不想,我想这就是热爱吧。现在看来也许生活就是在对客观限制的一系列妥协中,完成自己想做的“项目”的过程。
最后再次感谢Funpack活动提供的宝贵的学习和实操机会以及实验平台,感谢交流群里无数大佬在我坐牢时提供的宝贵帮助。感谢我导师的实验室(不便透露)提供的烙铁和稳压源供我做这个“私活”,感谢室友所在的MARS实验室提供的公-母杜邦线使外接模块成为可能(自己只有母母杜邦线,在这之前只在Arduino上用shield板连接排母)。
本文在仓促之间完成,疏漏在所难免,如有技术问题未尽事宜,请务必联系我(董 1464465962@qq.com)做修正或补充,本人将对项目100%负责,并愿意提供与项目有关的一系列支持。