D133物联网电子秤项目
D133物联网电子秤项目
项目开始日期:2025,2,28
需求
硬件采购需求:
3.5寸RGB屏幕(目前已适配4.3寸(480x272)RGB565屏幕)
语音播报(语音模块+喇叭)
软件需求:
- lvgl用户交互界面
- 处理电子称串口数据
- 语音播报消息
- wifi上传到服务器
作用:
RGB屏幕实现电子秤输入,同时语音播报。
数据上传服务器
等待中流程:
采购:屏幕,语音播报模块+喇叭
技术:串口包数据包格式,WiFi上传格式
拆分任务:
- [x ] 实现wifi上传数据到服务器
要求:lvgl+屏幕+WiFi上传数据包到服务器(已实现)
拉取SDK
git clone --depth=1 https://gitee.com/lcsc/luban-lite.git
git代码管理
git remote add origin https://gitee.com/sword-level_0/digital-scale-d133.git
git push -u origin "master"
移植UART
串口0为Debug调试打印接口
使用
在user_uart3.c文件中将函数导出为命令,可以在终端开启串口
// 导出函数为命令
MSH_CMD_EXPORT(uart3_test_on, Test transmission and reception using UART3 serial port);
在终端开启
uart3_test_on

发送
send_demoData
接受串口数据

移植外设
外设框架
移植0.96IIC
复制驱动文件夹至路径
application\rt-thread\helloworld\user_bsp\0-96-iic-single-screen
在上层路径Kconfig添加
在配置中,配置开启
需要将冲突配置取消:
LVGL相关和数字视频接口
Application options --->
[*] LVGL (official): powerful and easy-to-use embedded GUI library ----
[*] ArtInChip LVGL demo ----
Board options --->
[*] Using DVP

外设路径

外设上层路径

test_0_96_iic_single_screen
进程间通信(消息队列)
消息队列的使用
发送
/* 格式化时间字符串 */
snprintf(msg_buf, sizeof(msg_buf), "time=%d", time); // 使用snprintf函数将时间变量格式化为字符串,存储在msg_buf中
/* 发送消息到队列 */
result = rt_mq_send(mq, msg_buf, sizeof(msg_buf)); // 调用rt_mq_send函数将msg_buf中的消息发送到队列mq中
if (result != RT_EOK) // 如果发送失败
{
rt_kprintf("Failed to send message\n"); // 打印错误信息
}
接收
char msg_buf[16]; // 定义一个字符数组,用于存储接收到的消息,数组大小为16字节
rt_err_t result; // 定义一个变量result,用于存储函数返回的错误码
while (1) // 无限循环,程序将一直执行这个循环体
{
/* 阻塞等待消息 */
result = rt_mq_recv(mq, msg_buf, sizeof(msg_buf), RT_WAITING_FOREVER); // 调用rt_mq_recv函数从消息队列mq中接收消息
if (result == RT_EOK)
rt_kprintf("Received: %s\n", msg_buf);
else
rt_kprintf("Recv failed: %d\n", result);
}
同一文件
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_hal_gpio.h"
#define RT_USING_OVERFLOW_CHECK
static rt_mq_t mq = RT_NULL; // 消息队列句柄
/* 消息队列初始化 */
static int msg_queue_init(void)
{
/* 创建消息队列:名称、消息大小、队列容量、队列模式 */
mq = rt_mq_create("time_mq", 20, 10, RT_IPC_FLAG_FIFO);
if (mq == RT_NULL)
{
rt_kprintf("Failed to create message queue\n");
return -1;
}
return 0;
}
INIT_APP_EXPORT(msg_queue_init); // 系统启动时自动初始化
/* 线程1:发送时间信息 */
static rt_thread_t user_app_thread = RT_NULL;
static void user_APP_thread_entry(void *param)
{
int time = 0;
int count = 0;
char msg_buf[20];
rt_err_t result;
while (1)
{
/* 格式化时间字符串 */
snprintf(msg_buf, sizeof(msg_buf), "time=%d", time);
/* 发送消息到队列 */
result = rt_mq_send(mq, msg_buf, sizeof(msg_buf));
if (result != RT_EOK)
{
rt_kprintf("Failed to send message\n");
}
/* 每两次发送(1秒)增加时间 */
if (++count % 2 == 0)
time++;
rt_thread_mdelay(500); // 500ms间隔
}
}
/* 启动线程1的命令 */
static void usr_APP_run(int argc, char **argv)
{
// 修改线程创建时的栈大小参数(原512改为2048)
user_app_thread = rt_thread_create(
"user_app_thread",
user_APP_thread_entry, RT_NULL,
2048, // 修改点:栈大小增加到2048字节
25, 5);
if (user_app_thread)
rt_thread_startup(user_app_thread);
else
rt_kprintf("Create thread1 failed\n");
}
MSH_CMD_EXPORT(usr_APP_run, "Start time sender thread");
/* 线程2:接收并打印信息 */
static rt_thread_t user_app2_thread = RT_NULL;
static void user_APP2_thread_entry(void *param)
{
char msg_buf[16]; // 原20改为16("time=2147483647"仅需14字节)
rt_err_t result;
while (1)
{
/* 阻塞等待消息 */
result = rt_mq_recv(mq, msg_buf, sizeof(msg_buf), RT_WAITING_FOREVER);
if (result == RT_EOK)
rt_kprintf("Received: %s\n", msg_buf);
else
rt_kprintf("Recv failed: %d\n", result);
}
}
/* 启动线程2的命令 */
static void usr_APP2_run(int argc, char **argv)
{
// 修改线程创建时的栈大小参数(原512改为2048)
user_app2_thread = rt_thread_create(
"user_app2_thread",
user_APP2_thread_entry, RT_NULL,
2048, // 修改点:栈大小增加到2048字节
25, 5);
if (user_app2_thread)
rt_thread_startup(user_app2_thread);
else
rt_kprintf("Create thread2 failed\n");
}
MSH_CMD_EXPORT(usr_APP2_run, "Start message receiver thread");
不同文件
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_hal_gpio.h"
extern rt_mq_t mq; // 消息队列句柄
/* 外部线程:接收并打印信息 */
static rt_thread_t user_External_app_thread = RT_NULL;
static void user_External_app_thread_entry(void *param)
{
char msg_buf[16]; // 原20改为16("time=2147483647"仅需14字节)
rt_err_t result;
while (1)
{
/* 阻塞等待消息 */
result = rt_mq_recv(mq, msg_buf, sizeof(msg_buf), RT_WAITING_FOREVER);
if (result == RT_EOK)
rt_kprintf("Received: %s\n", msg_buf);
else
rt_kprintf("Recv failed: %d\n", result);
}
}
/* 启动线程的命令 */
static void usr_External_app_run(int argc, char **argv)
{
// 修改线程创建时的栈大小参数(原512改为2048)
user_External_app_thread = rt_thread_create(
"usr_External_app_thread", // 线程名
user_External_app_thread_entry, RT_NULL,
2048, // 修改点:栈大小增加到2048字节
25, 5);
if (user_External_app_thread)
rt_thread_startup(user_External_app_thread);
else
rt_kprintf("Create _External——thread failed\n");
}
MSH_CMD_EXPORT(usr_External_app_run, "Start message receiver thread");
改动
在声明句柄的文件中:
static rt_mq_t mq = RT_NULL; // 消息队列句柄去掉
static //静态修饰改成
rt_mq_t mq = RT_NULL; // 消息队列句柄
在外部文件中
extern rt_mq_t mq; // 声明引用外部消息队列句柄
终端命令
发送消息队列
usr_APP_run
接收消息队列,打印
usr_APP2_run
oled显示+接收消息队列
test_0_96_iic_single_screen
网路(2.4GWIFI)
终端
扫描WIFI
wlan wifi_scan
连接wifi:
输入名称,密码
wlan wifi_connect SSID PASSWORD
wlan wifi_connect jianzhiji 8765432111
开启网卡
dhcpc WL0 start
查看ip
ifconfig

例程分析
例程结构
网络概念解析
LwIP(Light Weight IP)是一个小型开源的TCP/IP协议栈,由瑞典计算机科学院(SICS)的Adam Dunkels开发。它的设计初衷是用少量的资源消耗(尤其是RAM)实现一个较为完整的TCP/IP协议栈,其中“完整”主要指的是TCP协议的完整性。
DNS域名解析
当在浏览器中输入 www.baidu.com 并解析时,整个过程涉及多个步骤和技术协议,最终目的是将域名转换为服务器IP地址并完成网页加载。以下是解析的主要流程及相关技术细节:
1. DNS域名解析
DNS(Domain Name System)负责将域名转换为对应的IP地址,具体步骤如下:
- 本地缓存查询
- 浏览器首先检查自身缓存(如Chrome的DNS缓存)中是否有该域名的IP记录214。
- 若无,则查询操作系统缓存(如Windows的
hosts文件)和本地DNS缓存14。
- 本地DNS服务器查询
- 若本地缓存未命中,请求会发送至用户配置的本地DNS服务器(如运营商提供的114.114.114.114)414。
- 本地DNS服务器若缓存了域名记录,直接返回IP;否则进入递归查询流程14。
- 递归查询与根域名服务器
- 本地DNS服务器依次向根域名服务器、顶级域名服务器(
.com)、权威域名服务器(百度注册的DNS服务器)发起迭代查询,最终获取www.baidu.com的IP地址714。
- 本地DNS服务器依次向根域名服务器、顶级域名服务器(
- 返回IP地址
- 查询结果逐级返回至浏览器,例如常见的百度IP地址可能为
220.181.111.147或202.108.22.5(实际IP可能因负载均衡而变化)27。
- 查询结果逐级返回至浏览器,例如常见的百度IP地址可能为
相关命令:
- 使用
nslookup www.baidu.com或ping www.baidu.com可直接查看解析后的IP地址3。
2. 建立TCP连接
获取IP地址后,浏览器通过TCP协议与服务器建立连接:
- 三次握手
- 客户端发送SYN包,服务器返回SYN-ACK包,客户端再发送ACK包确认,完成可靠连接的建立27。
- 端口与协议
- 服务器默认监听80端口(HTTP)或443端口(HTTPS)212。
3. 发起HTTP请求与响应
- HTTP请求
- 浏览器发送HTTP GET请求,包含请求头(如User-Agent、Cookie等)和请求路径(如
/表示首页)712。
- 浏览器发送HTTP GET请求,包含请求头(如User-Agent、Cookie等)和请求路径(如
- 服务器处理
- 百度服务器根据请求生成响应,可能涉及负载均衡、动态内容生成或静态资源返回1213。
- HTTP响应
- 服务器返回状态码(如200 OK)、响应头(如Content-Type)及HTML内容27。
4. 浏览器渲染页面
- 解析HTML与资源加载
- 浏览器解析HTML代码,并加载其中的CSS、JavaScript、图片等资源,可能触发多次HTTP请求212。
- 渲染引擎处理
- 构建DOM树、CSSOM树,合并为渲染树,最终布局和绘制页面712。
5. 连接释放
- 数据传输完成后,通过TCP四次挥手释放连接27。
技术协议总结
| 协议层级 | 协议与作用 |
|---|---|
| 应用层 | HTTP(传输网页)、DNS(域名解析)2 |
| 传输层 | TCP(可靠连接)、UDP(DNS查询)27 |
| 网络层 | IP(路由选择)、ARP(IP转MAC地址)47 |
| 链路层 | 以太网帧(物理传输)4 |
以下是关于 lwIP 的线程堆栈大小、MQTT/HTTP 协议使用、DHCP 服务器配置以及调试选项的详细说明:
1. lwIP 线程堆栈大小
作用:
lwIP 在多线程环境下运行时,每个网络线程(如 TCP/IP 主线程、协议处理线程等)需要分配足够的堆栈空间。堆栈大小不足会导致栈溢出、系统崩溃或数据损坏。
配置方法:
-
默认值:
lwIP 默认堆栈大小取决于移植的平台(如 FreeRTOS、裸机等),通常在lwipopts.h或平台配置文件中定义。
例如,在 FreeRTOS 中,TCP/IP 线程堆栈可能默认为 1KB~4KB。 -
调整堆栈:
根据协议复杂度和数据量调整。例如:- 简单应用(如 UDP Echo):1KB~2KB
- 复杂应用(如 HTTP 文件传输):4KB~8KB
- MQTT/HTTP + TLS:可能需要 8KB~16KB(加密算法占用更多栈空间)
在 FreeRTOS 中修改示例:
// FreeRTOS 任务创建时指定堆栈大小 xTaskCreate(tcpip_thread, "lwIP", 4096, NULL, 2, NULL); -
检测方法:
使用工具(如 FreeRTOS 的堆栈溢出检测钩子函数vApplicationStackOverflowHook)或手动填充魔法值检查溢出。
2. 使用 MQTT 协议
实现步骤:
-
启用 lwIP MQTT 模块:
在lwipopts.h中启用 MQTT 支持:#define LWIP_MQTT 1 -
集成 MQTT 客户端:
lwIP 提供轻量级 MQTT 客户端,需包含头文件:#include "mqtt.h" -
连接与通信示例:
// MQTT 连接配置 struct mqtt_connect_client_info_t ci; memset(&ci, 0, sizeof(ci)); ci.client_id = "client_id"; // 连接到 Broker mqtt_client_t *client = mqtt_client_new(); mqtt_connect(client, &ip_addr, 1883, mqtt_connection_cb, NULL, &ci); // 发布消息 const char *message = "Hello MQTT"; mqtt_publish(client, "topic", message, strlen(message), MQTT_QOS_0, 0, NULL);
3. 使用 HTTP 协议
实现步骤:
-
启用 HTTP 服务器:
在lwipopts.h中启用 HTTP 支持:#define LWIP_HTTPD 1 #define LWIP_HTTPD_SSI 1 // 可选:支持 SSI #define LWIP_HTTPD_CGI 1 // 可选:支持 CGI -
实现 HTTP 回调函数:
定义页面处理和 CGI 函数:// 处理 HTTP 请求 const char *http_response = "<html><body>Hello HTTP</body></html>"; err_t http_req_handler(struct pbuf *p, struct tcp_pcb *pcb) { tcp_write(pcb, http_response, strlen(http_response), TCP_WRITE_FLAG_COPY); tcp_close(pcb); return ERR_OK; } // 注册 HTTP 路径 http_set_cgi_handlers(http_cgi_handlers, LWIP_ARRAYSIZE(http_cgi_handlers)); -
启动 HTTP 服务器:
httpd_init();
4. 使用 DHCP 服务器
配置方法:
-
启用 DHCP 服务器功能:
在lwipopts.h中启用:#define LWIP_DHCP 1 // 启用 DHCP 客户端 #define LWIP_DHCPS 1 // 启用 DHCP 服务器 -
设置地址池:
在代码中配置 DHCP 地址池:ip4_addr_t dhcp_start, dhcp_end; IP4_ADDR(&dhcp_start, 192, 168, 1, 100); IP4_ADDR(&dhcp_end, 192, 168, 1, 200); dhcp_server_init(netif_default, &dhcp_start, &dhcp_end); -
处理 DHCP 请求:
lwIP 自动处理客户端请求,分配 IP 并管理租期。
5. 启用 lwIP 调试选项
作用:
调试选项用于输出网络状态、协议解析细节和错误信息,帮助定位连接问题、内存泄漏或协议错误。
启用方法:
-
全局调试开关:
在lwipopts.h中设置:#define LWIP_DEBUG 1 // 启用调试 #define LWIP_DBG_MIN_LEVEL LWIP_DBG_LEVEL_ALL // 输出所有级别日志 -
模块级调试:
按需启用特定模块的调试:#define DHCP_DEBUG LWIP_DBG_ON // DHCP 调试 #define TCP_DEBUG LWIP_DBG_ON // TCP 调试 #define HTTP_DEBUG LWIP_DBG_ON // HTTP 调试 -
输出调试信息:
实现lwip_log函数或重定向到串口:void lwip_log(const char *fmt, ...) { va_list args; va_start(args, fmt); vprintf(fmt, args); // 输出到控制台或串口 va_end(args); }
典型调试场景:
- 内存泄漏:启用
MEM_DEBUG和MEMP_DEBUG。 - TCP 重传:启用
TCP_CWND_DEBUG查看拥塞窗口变化。 - HTTP 请求解析:启用
HTTP_DEBUG跟踪请求处理。
总结
- 堆栈大小:根据协议复杂度和线程任务调整,避免溢出。
- 协议支持:通过
lwipopts.h启用 MQTT/HTTP/DHCP,并实现回调函数。 - 调试:启用调试宏并实现日志输出,快速定位问题。
建议结合具体平台(如 FreeRTOS、Linux 等)和硬件资源(内存、CPU)进行参数调优。
开启网络









help
以下是 RT-Thread Shell 命令的中文翻译:
RT-Thread Shell 命令列表
| 命令 | 功能描述 |
|---|---|
list_fd |
列出文件描述符 |
sensor |
传感器测试功能 |
sensor_polling |
传感器轮询模式测试功能 |
sensor_int |
传感器中断模式测试功能 |
sensor_fifo |
传感器 FIFO 模式测试功能 |
free |
显示系统内存使用情况 |
ps |
列出系统中的线程 |
help |
显示 RT-Thread Shell 帮助 |
list |
列出系统对象 |
list_device |
列出系统中的设备 |
list_timer |
列出系统中的定时器 |
list_mempool |
列出内存池 |
list_memheap |
列出内存堆 |
list_msgqueue |
列出消息队列 |
list_mailbox |
列出邮箱 |
list_mutex |
列出互斥锁 |
list_event |
列出事件 |
list_sem |
列出信号量 |
list_thread |
列出线程 |
version |
显示 RT-Thread 版本信息 |
clear |
清空终端屏幕 |
tail |
输出文件的最后 N 行数据 |
echo |
将字符串写入文件 |
df |
显示磁盘剩余空间 |
umount |
从文件系统卸载设备 |
mount |
挂载设备到文件系统:mount <设备> <挂载点> <文件系统类型> |
mkfs |
格式化磁盘为指定文件系统 |
mkdir |
创建目录 |
pwd |
显示当前工作目录路径 |
cd |
切换工作目录 |
rm |
删除文件 |
cat |
查看文件内容 |
mv |
重命名文件或移动文件 |
cp |
复制文件 |
ls |
列出目录或文件信息 |
ulog_filter |
显示 ulog 日志过滤设置 |
ulog_kw |
设置 ulog 全局关键字过滤 |
ulog_tag |
设置 ulog 全局标签过滤 |
ulog_lvl |
设置 ulog 全局日志级别过滤 |
ulog_tag_lvl |
按标签设置 ulog 日志级别过滤 |
ulog_be_lvl |
按后端设置 ulog 日志级别过滤 |
f |
运行一个函数 |
m |
修改内存值 |
p |
打印内存值 |
meminfo |
显示内存信息 |
run_in_loop_thre |
在循环线程中运行命令 |
run_in_thread |
在线程中运行命令 |
reboot |
重启系统 |
reset |
重置设备模块 |
list_pinmux |
列出引脚功能配置 |
arecord |
录制音频为 WAV 文件 |
aplay |
播放 WAV 文件 |
test_clock |
测试时钟模块(CMU CLK) |
test_dma_memset |
测试 DMA 内存填充(参数:值 长度) |
test_dma_memcpy |
测试 DMA 内存复制(参数:长度) |
test_dvp |
测试 DVP 和摄像头模块 |
test_gpai |
测试 GPAI 设备 |
test_gpio |
测试 GPIO 设备 |
test_eth |
网络回环测试 |
iperf |
网络性能测试(-s 为服务端/-c 为客户端) |
sf |
SPI Flash 操作 |
fal |
FAL(Flash 抽象层)操作 |
test_tsen |
温度传感器(TSen)测试 |
top |
显示 CPU 使用率 |
dhcpc |
启动/停止 DHCP 客户端(动态主机配置协议),自动分配IP、子网掩码、网关和DNS,这样用户就不用手动配置了 |
ifconfig |
显示或配置网络信息 |
ping |
网络连通性测试(输入 ping help 查看帮助) |
test_0_96_iic_si |
测试 0.96 英寸 IIC 屏幕 |
usr_APP2_run |
启动消息接收线程 |
usr_APP_run |
启动时间发送线程 |
usr_External_app |
启动外部应用线程(消息接收) |
send_demoData |
发送测试数据 |
dma_dump |
显示 DMA 寄存器(参数:通道号) |
efuse |
操作 EFUSE |
epwm_status |
显示 EPWM 状态 |
mtop |
测试 mtop 功能 |
pwm_set_tb |
设置 PWM 时基 |
pwm_status |
显示 PWM 状态 |
tsen_status |
显示温度传感器状态 |
wdt_status |
显示看门狗状态 |
list_irq |
列出系统中断 |
canstat |
显示 CAN 设备状态 |
adc |
ADC 操作(输入 adc help 查看选项) |
pin |
引脚操作(输入 pin help 查看选项) |
pwm |
PWM 操作(输入 pwm help 查看选项) |
lptimer_dump |
显示低功耗定时器(LPTimer)信息 |
pm_dump |
显示电源管理状态 |
pm_run |
切换电源管理模式 |
pm_module_delay |
设置模块延迟休眠请求 |
pm_module_reques |
请求模块电源管理模式 |
pm_module_releas |
释放模块电源管理模式计数 |
pm_module_releas |
释放模块电源模式 |
pm_request |
请求电源管理模式 |
pm_release_all |
释放所有电源管理模式计数 |
pm_release |
释放电源管理模式 |
list_alarm |
列出闹钟信息 |
date |
获取/设置日期和时间(本地时区):date [年 月 日 时 分 秒] |
player_demo |
播放器演示 |
audio_player_dem |
音频播放器演示 |
wlan |
WiFi 操作(输入 wlan help 查看子命令) |
使用说明
- 格式:
命令 [参数],例如ping 192.168.1.1。 - 帮助:输入
命令 help查看详细用法(如ping help)。 - 硬件相关命令(如
test_gpio,pwm)需确保硬件支持。
网络相关概念
DHCP
''(Dynamic Host Configuration Protocol,动态主机配置协议)是一种网络协议,用于自动分配和管理网络设备的IP地址及相关配置参数,从而简化网络配置并避免手动操作带来的错误。
一、核心功能
- IP地址自动分配
- 动态分配:从地址池中临时分配IP(有租期限制)
- 静态绑定:为特定设备(如服务器)固定分配IP
lwIP
(Lightweight IP) 是一个轻量化的开源 TCP/IP 协议栈,专为资源受限的嵌入式系统设计。它由瑞典计算机科学研究院的 Adam Dunkels 开发,广泛应用于物联网设备、工业控制、传感器网络等场景。
- 模块化架构
- 支持按需裁剪功能模块(如 DHCP、DNS、IPv6),开发者可灵活配置协议栈功能。
- 多协议支持
- 包含完整的 TCP/IP 协议族:
- 网络层:IPv4、IPv6、ICMP、IGMP
- 传输层:TCP、UDP
- 应用层:HTTP、SNMP、MQTT(需扩展实现)
- 包含完整的 TCP/IP 协议族:
lwIP 的典型应用场景
| 场景 | 说明 |
|---|---|
| 物联网设备 | 智能家居设备(如 Wi-Fi 插座、传感器)通过 lwIP 实现网络通信。 |
| 工业控制 | PLC、工业网关使用 lwIP 接入以太网,支持 Modbus TCP 等协议。 |
| 嵌入式 Web 服务器 | 通过 lwIP 的 HTTP 模块,在设备上运行轻量级 Web 页面(如配置界面)。 |
| 低功耗设备 | 结合低功耗无线技术(如 LoRa、BLE),实现电池供电设备的间歇性网络通信。 |


iperf
iperf 是一款开源的网络性能测试工具,主要用于测量 TCP/UDP 带宽、延迟、抖动和丢包率等网络性能指标。它通过在客户端和服务器之间发送数据流,评估网络的吞吐量和稳定性,是网络工程师和开发者的常用工具。
一、核心功能
| 功能 | 说明 |
|---|---|
| 带宽测试 | 测量网络最大传输速率(如测出实际带宽是否达标) |
| 协议支持 | 支持 TCP、UDP、SCTP 协议 |
| 多线程测试 | 可并行多连接测试(模拟多用户场景) |
| 数据方向控制 | 支持上行(客户端→服务器)、下行(服务器→客户端)、双向测试 |
| 统计报告 | 实时输出带宽、抖动(Jitter)、丢包率(Loss%)等详细指标 |
SDMC
通常指 同步数字主控制器(Synchronous Digital Master Controller),是一种用于协调多设备同步操作的高精度时序控制核心模块,尤其在高速数字系统、通信设备和复杂嵌入式系统中广泛应用。
WLAN连接
wifi(RTL8189FTV-WIFI模块2.4GHz,SDIO接口)
扫描wifi
wlan wifi_scan
连接wifi (输入名称,密码)
wlan wifi_connect SSID PASSWORD
wlan wifi_connect jianzhiji 8765432111
wlan wifi_connect jianzhiji 8765432111
ifconfig
需出现IP地址才算链接成功
可以 通过ping ip来再次确认是否连接到互联网还是内网:
ping www.baidu.com
//DNS解码的百度ip地址
ping 183.2.172.177

无线网络
本章节讲解如何在 Luban-Lite 系统中配置 SDIO 接口的无线网络,需要配置的主要有以下几部分:
- 配置 SDMC 接口;
- 配置 WiFi 模组;
- 配置 lwIP 协议栈;
- 配置内核;
配置 SDMC 接口和 WiFi 模组
使用 SDMC 接口前,需将对应 SDMC 接口选中,以 D133CBV-QFN88 开发板为例,使用 scons --menuconfig 命令进行配置:
Board options
[ * ] Using SDMC0 # 硬件连接哪个 SDMC 接口,选择哪个SDMC
[ * ] Enable the interrupt of sdmc
[ * ] Using Wireless lan --->
[ * ] Using Realtek wlan driver --->
Select Realtek wlan0 modul (rtl8189)--- > # 以 RTL8189 为例
Realtek wlan0 paramete --->
(PC.7) realtek wlan0 power on gpio
(192.168.3.20) wlan0 ip addr
(192.168.3.1) wlan0 gateway addr
(255.255.255.0) wlan0 netmask
[*] Enable Realtek driver debug information
使用RTL8189FTV--WIFI模块该选


例程地址:packages -> third-party -> webclient ->samples

配置 lwIP
使用 scons --menuconfig 命令进入配置页面,配置如下:
Local packages options --->
Third-party packages options --->
LwIP: light weight TCP/IP stack
[*] LwIP Examples --->
[*] Using net tools
[*] Enable DNS for name resolution
[*] Enable alloc ip address through DHCP
[*] Enable ping features --->
配置内核
Rt-Thread options --->
RT-Thread Kernel --->
(4) The priority level value of timer thread
(4096) The stack size of timer thread
RT-Thread Components --->
Device Drivers --->
(4096) The stack size for sdio irq thread
(5) The priority level value of sdio irq thread




WLAN基础管理命令
以下是这些 wlan 相关命令的详细功能解析和用法说明(按用途分类整理):
一、WiFi基础管理命令
1. 网络开关
| 命令 | 功能 | 示例 |
|---|---|---|
wifi_on |
启动WiFi模块 | wifi_on |
wifi_off |
关闭WiFi模块 | wifi_off |
2. 连接管理
| 命令 | 功能 | 参数说明 | 示例 |
|---|---|---|---|
wifi_connect |
通过SSID和密码连接网络 | <SSID> <密码> |
wifi_connect MyWiFi 12345678 |
wifi_connect_bssid |
指定BSSID连接(防AP同名欺骗) | <SSID> <密码> <BSSID> |
wifi_connect_bssid MyWiFi 1234 AA:BB:CC:DD:EE:FF |
wifi_disconnect |
断开当前连接 | 无 | wifi_disconnect |
二、网络扫描与诊断
1. 扫描相关
| 命令 | 功能 | 参数说明 | 示例 |
|---|---|---|---|
wifi_scan |
执行全频段WiFi扫描 | 无 | wifi_scan |
wifi_reoder_scan |
按信号强度排序扫描结果 | 无 | wifi_reoder_scan |
wifi_scan_with_ssid |
扫描指定SSID是否存在 | <SSID> |
wifi_scan_with_ssid MyWiFi |
wifi_scan_with_multissid |
批量扫描多个SSID | <SSID1> <SSID2>... |
wifi_scan_with_multissid Home Office |
2. 诊断工具
| 命令 | 功能 | 参数说明 | 示例 |
|---|---|---|---|
ping |
网络连通性测试 | <IP> [-t] [-n count] |
ping 192.168.1.1 -n 5 |
get_auto_chl |
获取自动信道选择结果 | 无 | get_auto_chl |
三、高级调试与监控
1. 混杂模式(抓包)
| 命令 | 功能 | 参数说明 | 示例 |
|---|---|---|---|
wifi_promisc |
开启混杂模式监听原始数据包 | <enable/disable> |
wifi_promisc enable |
rawdata_enable |
启用原始数据捕获 | 无 | rawdata_enable |
rawdata_disable |
禁用原始数据捕获 | 无 | rawdata_disable |
rawdata_send |
发送原始数据帧(需指定格式) | <hex数据> |
rawdata_send 00AAFF... |
2. 调试接口
| 命令 | 功能 | 参数说明 | 示例 |
|---|---|---|---|
iwpriv |
设置/读取私有驱动参数 | <参数> [值] |
iwpriv set_power 10 |
wifi_debug |
开启调试日志(级别可调) | <debug_level:0-5> |
wifi_debug 3 |
四、AP模式操作
| 命令 | 功能 | 参数说明 | 示例 |
|---|---|---|---|
wifi_ap |
启动SoftAP热点模式 | <SSID> <密码> [channel] |
wifi_ap MyHotspot 12345678 6 |
五、系统信息与退出
| 命令 | 功能 | 参数说明 | 示例 |
|---|---|---|---|
wifi_info |
显示当前连接状态(IP/RSSI/BSSID等) | 无 | wifi_info |
exit |
退出命令行界面 | 无 | exit |
help |
显示命令帮助 | 无 | help |
常见问题处理指南
1. 连接失败
# 检查驱动状态
wifi_info
# 扫描确认AP存在
wifi_scan_with_ssid MyWiFi
# 尝试指定BSSID连接
wifi_connect_bssid MyWiFi password AA:BB:CC:DD:EE:FF
2. 网络延迟高
# 持续ping测试
ping 192.168.1.1 -t
# 查看当前信道质量
iwpriv get_rssi
3. 抓包分析
wifi_promisc enable
rawdata_enable
# 此时数据会输出到指定接口(需配合tcpdump或wireshark)
参数顺序规则(重要!)
- 对于
ping命令,选项顺序影响参数有效性:# 正确:发送5次请求 ping 192.168.1.1 -n 5 # 错误:-t出现在-n之后会被忽略 ping 192.168.1.1 -n 5 -t # 实际只执行5次
调试日志级别说明
| 级别 | 日志详细程度 |
|---|---|
| 0 | 关闭所有调试输出 |
| 1 | 关键错误信息 |
| 2 | 基本连接状态变更 |
| 3 | 协议交互过程(推荐调试级别) |
| 4 | 详细数据包跟踪 |
| 5 | 底层寄存器操作(开发者用) |
建议调试时先设置 wifi_debug 3 查看关键交互信息。
HTTP
[单片机] → (连接Wi-Fi) → [TCP握手] → [发送HTTP请求] → [接收响应] → [处理数据]
E:\D133EBS\WIFI\luban-lite\kernel\rt-thread\components\net\lwip\lwip-2.1.2\src\apps\http\http_client.c
E:\D133EBS\WIFI\luban-lite\packages\third-party\lwip\src\apps\http\http_client.c
-
1. 包含头文件**: 在你的源文件中包含 `http_client.h` 头文件,以便使用 HTTP 客户端的接口和数据结构。 \#include "lwip/apps/http_client.h" 2. **配置 HTTP 客户端连接**: 配置 `httpc_connection_t` 结构体,设置代理地址、端口和回调函数等。 *httpc_connection_t* settings; *memset*(&*settings*, 0, sizeof(settings)); settings.result_fn = my_result_callback; settings.headers_done_fn = my_headers_done_callback; 3. **定义回调函数**: 实现 HTTP 客户端的回调函数,用于处理 HTTP 请求的结果和接收的头部信息。 void *my_result_callback*(void **arg*, *httpc_result_t* *httpc_result*, *u32_t* *rx_content_len*, *u32_t* *srv_res*, *err_t* *err*) { *// 处理 HTTP 请求结果* } *err_t* *my_headers_done_callback*(*httpc_state_t* **connection*, void **arg*, struct pbuf **hdr*, *u16_t* *hdr_len*, *u32_t* *content_len*) { *// 处理接收到的 HTTP 头部信息* return ERR_OK; } 4. **发起 HTTP 请求**: 使用 `httpc_get_file` 或 `httpc_get_file_dns` 函数发起 HTTP 请求。 *ip_addr_t* server_addr; *IP4_ADDR*(&*server_addr*, 192, 168, 1, 1); *// 设置服务器 IP 地址* const char *uri = "/path/to/resource"; *httpc_state_t* *connection; *err_t* err = *httpc_get_file*(&*server_addr*, HTTP_DEFAULT_PORT, uri, &*settings*, my_recv_callback, NULL, &*connection*); if (err != ERR_OK) { *// 处理错误* } 5. **接收数据回调函数**: 实现数据接收回调函数,用于处理接收到的数据。 *err_t* *my_recv_callback*(void **arg*, struct altcp_pcb **pcb*, struct pbuf **p*, *err_t* *err*) { if (p != NULL) { *// 处理接收到的数据* *altcp_recved*(pcb, p->tot_len); *pbuf_free*(p); } return ERR_OK; } 通过上述步骤,你可以使用 [http_client.c](vscode-file://vscode-app/d:/VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 文件中的 HTTP 客户端功能发起 HTTP 请求并处理响应。确保在你的项目中正确配置 lwIP 库,并根据需要调整回调函数的实现。
menuconig开启http




例程路径
luban-lite\packages\third-party\webclient\samples
例程运行逻辑:

终端执行:
tab提示命令:
使用例程:
先连接WIFI
wlan wifi_connect jianzhiji 8765432111
检查是否分配ip地址
ifconfig
ping 183.2.172.177
请求:webclient_get_sample:
运行命令:
web_get_test
请求网站并返回数据
作用:
演示如何使用 WebClient 软件包发送 HTTP GET 请求并接收响应数据。文件中的代码展示了如何创建 WebClient 会话、发送 GET 请求、接收响应数据并处理内存分配错误等操作。
例程实现的功能
- HTTP GET请求发送
- 支持向指定URI(如
http://www.rt-thread.com/service/rt-thread.txt)发送GET请求。- 可处理 普通请求(分块传输或固定长度)和 简化请求(短数据场景)。
- 响应数据处理
- 接收服务器返回的响应数据,并打印输出到终端。
- 自动处理 分块传输编码(Chunked Transfer Encoding) 和 固定内容长度(Content-Length) 两种HTTP响应格式。
- 命令行交互支持
- 通过RT-Thread的
FINSH/MSH命令行接口,提供用户可配置的测试命令,支持自定义URI和请求模式。
终端输出结果:

使用函数命令,访问
web_get_test url

例如访问官方测试网站
web_get_test http://www.rt-thread.com/service/rt-thread.txt
终端返回的结果

网站内容:

内容可以对应上,证明成功请求网站并返回了内容
自定义URI——访问个人网站
web_get_test https://www.cnblogs.com/tianwuyvlianshui
该问题表示未启用https,确实如其言,我们只开启了http,后续开启https再尝试

测试其他http网站
# 测试 GET 请求
curl http://httpbin.org/get
# 测试 POST 请求
curl -X POST http://httpbin.org/post -d "key=value"
# 返回指定状态码(如 404)
curl http://httpbin.org/status/404
1、测试 GET 请求
web_get_test http://httpbin.org/get
httpbin.org
- 用途:最全面的 HTTP 测试服务,支持 GET/POST/PUT/DELETE 等方法,可返回请求头、IP、状态码等。
该网站是测试网站,用于测试 HTTP 请求的公共服务,它会将你的请求信息原样返回。

测试带参数请求
web_get_test http://httpbin.org/get?name=RT-Thread&id=123

成功添加访问参数
"name": "RT-Thread",
"id": "123"
测试html网站
web_get_test http://example.com

成功返回该静态网站的html文件
将返回的html保存打开就是该网站

测试 返回指定状态码(如 404)
web_get_test http://httpbin.org/status/404

webclient_post_sample:
运行命令:
web_post_test
web_post_test url
web_post_test http://www.rt-thread.com/service/echo
作用:
演示如何使用 WebClient 软件包发送 HTTP POST 请求并接收响应数据。文件中的代码展示了如何创建 WebClient 会话、发送 POST 请求、接收响应数据并处理内存分配错误等操作。
实现了如何使用 WebClient 软件包发送 HTTP POST 请求并接收响应数据。具体实现了以下功能:
- 定义常量和变量:定义了响应缓冲区大小、请求头缓冲区大小和默认的 POST 请求 URI。
- 发送普通 POST 请求:
- 函数
webclient_post_comm创建 WebClient 会话,构建请求头,发送 POST 请求,并接收和打印响应数据。- 发送简化 POST 请求:
- 函数
webclient_post_smpl创建请求头,发送 POST 请求,并接收和打印响应数据。- 测试函数:
- 函数
webclient_post_test根据命令行参数选择发送普通 POST 请求或简化 POST 请求,并处理 URI 内存分配和释放。- 命令行集成:
- 使用 FinSH 命令行系统集成测试命令
web_post_test,可以在命令行中执行 POST 请求测试。
终端输出结果:
webclient_shard_download_sample:
运行命令:
web_shard_test
作用:
演示如何使用 WebClient 软件包进行分片下载。文件中的代码展示了如何发送 HTTP GET 请求并分片接收响应数据,以及如何处理和打印接收到的数据。
终端输出结果:
测试http pose:
如果服务器是回显服务(如 http://www.rt-thread.com/service/echo),响应内容应与发送的 post_data 完全一致
终端测试命令:
web_post_test http://www.rt-thread.com/service/echo
pose发送内容:

终端打印pose响应

响应内容应与发送的 post_data 完全一致
D133 ping 电脑
电脑与板子同连手机热点
电脑ip

板卡ip

可以看到在同一局域网192.168.106.xxx
理论上可以ping通,实际则可能不行,原因是电脑防护墙阻挡
关闭则可以ping通(测试完记得重新开启)
抓包


web_post_test http://192.168.106.31:8080
带参数
web_post_test http://192.168.106.31:8080?a=1&b=2&c=3
测试单片机http pose请求
使用python搭建内网服务器
from http.server import BaseHTTPRequestHandler, HTTPServer # 从http.server模块导入BaseHTTPRequestHandler和HTTPServer类
import time # 导入time模块,用于处理时间相关功能
host_ip = "0.0.0.0" # 监听所有IP地址,表示服务器将接受来自任何IP的请求
port = 8080 # 端口号
class RequestHandler(BaseHTTPRequestHandler):
# 定义一个处理POST请求的方法
def do_POST(self):
# 从请求头中获取Content-Length字段,该字段表示请求体的长度
content_length = int(self.headers['Content-Length'])
# 读取请求体数据,长度为content_length,并将其解码为utf-8字符串
post_data = self.rfile.read(content_length).decode('utf-8')
# 打印接收到的POST数据
print(f"Received POST data: {post_data}")
# 发送HTTP响应状态码200,表示请求成功
self.send_response(200)
# 发送响应头,设置Content-type为text/plain,表示响应体是纯文本
self.send_header('Content-type', 'text/plain')
# 结束响应头
self.end_headers()
# 向客户端发送响应体数据"OK",表示处理成功
self.wfile.write(b"OK")
server = HTTPServer((host_ip, port), RequestHandler)
print(f"Server running at http://{host_ip}:{port}")
server.serve_forever()
D133端(客户端,发送端,发送HTTP POST请求到服务器(内网)):
请求的数据(默认POST请求数据)

将数据通过http post 发送到服务器

命令(命令+网址):
web_post_test http://192.168.106.31:8080


在PC

PC端(内网服务器,服务端,作接收端)

数据上报对接api
URL头部
http://xxx
URL头部+api
web_post_test http://xxx/api/yyy
Postman
Postman 是一款广泛应用于 API 开发与测试 的协作工具,旨在简化 API 的生命周期管理(设计、开发、测试、部署、监控等)。
主要功能
- API 请求测试与调试
- 支持发送 HTTP/HTTPS 请求(GET、POST、PUT、DELETE 等)。
- 可自定义请求头(Headers)、参数(Params)、身份验证(OAuth、JWT 等)和请求体(Body)。
- 直观的界面帮助快速调试 API,查看响应状态码、响应时间和返回数据(JSON、XML 等)。
- 自动化测试
- 使用 JavaScript 编写测试脚本,验证 API 响应是否符合预期。
- 支持断言(Assertions)、动态变量和环境变量,提升测试灵活性。
- 集成 CI/CD 工具(如 Jenkins、GitHub Actions)通过 Newman 命令行工具运行自动化测试。
- 协作与文档
- 团队可共享 API 集合(Collections) 和环境配置,提升协作效率。
- 自动生成 API 文档,支持一键发布,便于前后端开发者或第三方调用者查阅。
- Mock 服务器
- 创建虚拟 API 端点,模拟真实响应,支持前端开发与后端 API 并行工作。
- 监控与性能测试
- 定期运行 API 测试,监控服务可用性与性能,生成报告分析潜在问题。
适用场景
- 开发者:快速调试接口,验证功能逻辑。
- 测试工程师:设计自动化测试用例,确保 API 稳定性。
- 团队协作:统一管理 API 资产,减少沟通成本。
- 文档编写:自动生成易读的 API 文档,替代手动维护。
版本与平台
- 免费版:满足个人基础需求(请求测试、简单自动化)。
- 付费版(Pro/Enterprise):解锁团队协作、高级监控、私有 API 文档等功能。
- 跨平台支持:提供 Windows、macOS、Linux 客户端及网页版。
优势
- 用户友好:图形化操作降低学习成本,替代命令行工具(如 cURL)。
- 生态丰富:支持 OpenAPI 规范、WebSocket 测试、GraphQL 等扩展场景。
- 云端同步:数据云端存储,多设备无缝切换。
简单示例
- 在 Postman 中新建请求,输入 API 地址。
- 设置请求方法(如 POST),添加 JSON 格式的请求体。
- 添加测试脚本验证响应状态码是否为 200。
- 保存到集合,分享给团队成员或生成文档。
无论是独立开发还是团队协作,Postman 都能显著提升 API 开发效率,是现代软件工程中不可或缺的工具之一。
API文档解析与使用
api接口网址
http://xxx
涉及实际产品,略
Postman测试应用
api接口网址
http://xxx
时间戳转换网站
https://tool.lu/timestamp/
网站头URL
http://xxx
使用方式:网站头URL+API尾部
例如
则使用该api的URL为
http://xxx/api/yyy
添加CJSON解析库

在串口终端控制HTTP_API访问服务器
/*
演示如何使用 WebClient 软件包发送 HTTP POST 请求并接收响应数据。
文件中的代码展示了如何创建 WebClient 会话、发送 POST 请求、
接收响应数据并处理内存分配错误等操作。
*/
#include <string.h>
#include <rtthread.h>
#include <webclient.h>
#include <stdio.h>
// 定义POST请求响应缓冲区的大小为1024字节
#define POST_RESP_BUFSZ 1024
// 定义POST请求头部缓冲区的大小为1024字节
#define POST_HEADER_BUFSZ 1024
**涉及实际产品,略**
typedef enum {
API_GET_BALANCE_NO, // 远程编码
API_LOAD_KEY, // 获取密钥
**涉及实际产品,略**
} API_TYPE;
// API地址映射表
static const char* API_URI_MAP[] = {
[API_GET_BALANCE_NO] = "**涉及实际产品,略**",
.......
};
// 获取API地址的宏
#define GET_API_URI(api_type) (API_URI_MAP[(api_type)])
/* 静态函数获取URI */
static inline const char* get_api_uri(API_TYPE type) {
return API_URI_MAP[type];
}
// /* 函数调用初始化 */
// const char* POST_LOCAL_URI = NULL;
// void post_api_uris(char str) {
// POST_LOCAL_URI = get_api_uri(str);
// }
// 获取时间戳函数(get)
// 定义一个静态函数get_timestamp,用于获取时间戳并存储在buffer中
static int get_timestamp(char *buffer, size_t buffer_size)
{
// 定义一个指向webclient_session结构体的指针session,并初始化为RT_NULL
struct webclient_session *session = RT_NULL;
// 定义一个指向字符数组的指针response_buf,用于存储HTTP响应,并初始化为RT_NULL
char *response_buf = RT_NULL;
// 定义一个整型变量ret,用于存储函数返回值,并初始化为RT_EOK(表示成功)
int ret = RT_EOK;
// 定义一个常量字符串url,指向获取时间戳的URL
const char *url = "http://acs.m.taobao.com/gw/mtop.common.getTimestamp/";
// 创建一个webclient会话,缓冲区大小为512字节
session = webclient_session_create(512);
// 如果会话创建失败,打印错误信息并返回内存不足错误码
if (!session) {
rt_kprintf("[ERROR] Create session failed\n");
return -RT_ENOMEM;
}
// 使用webclient发送GET请求,如果返回值不是200(HTTP OK),打印错误信息并设置返回值为错误码
if ((ret = webclient_get(session, url)) != 200) {
rt_kprintf("[ERROR] GET failed (%d)\n", ret);
ret = -RT_ERROR;
// 跳转到退出标签,进行资源释放
goto __exit;
}
// 分配256字节的内存用于存储HTTP响应
response_buf = web_malloc(256);
// 如果内存分配失败,设置返回值为内存不足错误码并跳转到退出标签
if (!response_buf) {
ret = -RT_ENOMEM;
goto __exit;
}
// 从webclient会话中读取响应数据到response_buf,最多读取256字节
int total_read = webclient_read(session, response_buf, 256);
// 如果读取失败或没有读取到数据,设置返回值为错误码并跳转到退出标签
if (total_read <= 0) {
ret = -RT_ERROR;
goto __exit;
}
// 在response_buf的末尾添加字符串结束符
response_buf[total_read] = '\0';
// 在response_buf中查找时间戳的开始位置
char *t_start = strstr(response_buf, "\"t\":\"");
// 如果找到了时间戳的开始位置
if (t_start) {
// 移动指针到时间戳的实际开始位置
t_start += 5;
// 查找时间戳的结束位置
char *t_end = strchr(t_start, '\"');
// 如果找到了时间戳的结束位置
if (t_end) {
// 计算时间戳的长度
int t_len = t_end - t_start;
// 如果时间戳长度大于等于buffer_size,打印错误信息并设置返回值为错误码
if (t_len >= buffer_size) {
rt_kprintf("[ERROR] Timestamp too long\n");
ret = -RT_ERROR;
goto __exit;
}
// 将时间戳复制到buffer中
strncpy(buffer, t_start, t_len);
// 在buffer的末尾添加字符串结束符
buffer[t_len] = '\0';
// 打印获取到的时间戳
rt_kprintf("[INFO] Time value: %s\n", buffer);
} else {
// 如果没有找到时间戳的结束位置,设置返回值为错误码
ret = -RT_ERROR;
}
} else {
// 如果没有找到时间戳的开始位置,打印错误信息并设置返回值为错误码
rt_kprintf("[ERROR] Invalid response format\n");
ret = -RT_ERROR;
}
// 退出标签,用于释放资源
__exit:
// 如果会话指针不为空,关闭会话
if (session) webclient_close(session);
// 如果响应缓冲区指针不为空,释放内存
if (response_buf) web_free(response_buf);
// 返回函数执行结果
return ret;
}
// 定义一个指向常量字符数组的指针post_data
// const char *post_data = "{\"uniqueCode\":\"0102250310001\"}"; // 转义后的合法字符串
/* send HTTP POST request by common request interface, it used to receive longer data */
// 定义一个静态函数webclient_post_comm,用于发送POST请求并处理响应
/*核心功能
发送 HTTP POST 请求
将 post_data 数据通过 POST 方式发送到指定的 uri(服务器地址)。
自动添加必要的 HTTP 头部(如 Content-Length 和 Content-Type)。
支持处理二进制或文本数据(通过 application/octet-stream 类型)。
接收服务器响应
读取服务器返回的响应数据,并通过串口打印输出。
支持分块读取响应内容(循环读取直到数据结束)。
错误处理
内存分配失败时返回错误码(-RT_ENOMEM)。
检测 HTTP 响应状态码,若非 200 则视为错误。
确保资源释放(会话关闭、内存回收),避免内存泄漏*/
static int webclient_post_comm(const char *uri, const void *post_data, size_t data_len)
{
// 定义一个指向webclient会话结构的指针session,并初始化为RT_NULL
struct webclient_session* session = RT_NULL;
// 定义一个指向unsigned char类型的指针buffer,并初始化为RT_NULL
unsigned char *buffer = RT_NULL;
// 定义一个整型变量index,用于循环计数
int index, ret = 0;
// 定义一个整型变量bytes_read,用于存储每次读取的字节数
int bytes_read, resp_status;
// 1. 分配响应缓冲区
buffer = (unsigned char *) web_malloc(POST_RESP_BUFSZ);
// 如果内存分配失败,打印错误信息并返回内存不足错误码
if (buffer == RT_NULL)
{
rt_kprintf("no memory for receive response buffer.\n");
ret = -RT_ENOMEM;
goto __exit;// 内存不足时退出
}
// 2. 创建 WebClient 会话
/* create webclient session and set header response size */
session = webclient_session_create(POST_HEADER_BUFSZ);// 初始化 HTTP 会话
if (session == RT_NULL)
{
ret = -RT_ENOMEM;
goto __exit; // 创建失败时退出
}
// 3. 添加 HTTP 头部
/* build header for upload 用于上传的 build 标头*/
webclient_header_fields_add(session, "Content-Length: %d\r\n", strlen(post_data));// 数据长度
webclient_header_fields_add(session, "Content-Type: application/json\r\n");// 数据类型
/* send POST request by default header */
// 4. 发送 POST 请求,返200表示成功
if ((resp_status = webclient_post(session, uri, post_data, data_len)) != 200) // 发送数据
{
rt_kprintf("webclient POST request failed, response(%d) error.\n", resp_status);//打印POST 请求失败
ret = -RT_ERROR;
goto __exit;
}
// 5. 读取并打印服务器响应数据
rt_kprintf("webclient post response data: \n");
do
{
bytes_read = webclient_read(session, buffer, POST_RESP_BUFSZ);// 分块读取
if (bytes_read <= 0)
{
break;
}
for (index = 0; index < bytes_read; index++)
{
rt_kprintf("%c", buffer[index]);// 逐字符打印响应内容
}
} while (1);
rt_kprintf("\n");
__exit:
if (session)
{
webclient_close(session);
session = RT_NULL;
}
if (buffer)
{
web_free(buffer);
}
return ret;
}
/* send HTTP POST request by simplify request interface, it used to received shorter data */
// 定义一个静态函数webclient_post_smpl,用于发送HTTP POST请求并处理响应
// 参数uri:请求的URI
// 参数post_data:POST请求的数据
// 参数data_len:POST请求数据的长度
static int webclient_post_smpl(const char *uri, const char *post_data, size_t data_len)
{
// 定义一个指针response,用于存储服务器响应的数据,初始为RT_NULL
char *response = RT_NULL;
// 定义一个指针header,用于存储HTTP请求头,初始为RT_NULL
char *header = RT_NULL;
// 定义一个变量resp_len,用于存储服务器响应的数据长度,初始为0
size_t resp_len = 0;
// 定义一个变量index,用于循环遍历响应数据,初始为0
int index = 0;
// 添加Content-Length头到HTTP请求头中,值是post_data的长度
webclient_request_header_add(&header, "Content-Length: %d\r\n", strlen(post_data));
// 添加Content-Type头到HTTP请求头中,值是application/json
webclient_request_header_add(&header, "Content-Type: application/json\r\n");
// 发送HTTP POST请求,并接收服务器响应
// 如果请求失败,打印错误信息,释放header内存,返回-RT_ERROR
if (webclient_request(uri, header, post_data, data_len, (void **)&response, &resp_len) < 0)
{
rt_kprintf("webclient send post request failed.");
web_free(header);
return -RT_ERROR;
}
// 打印发送POST请求成功的提示信息
rt_kprintf("webclient send post request by simplify request interface.\n");
// 打印服务器响应的数据提示信息
rt_kprintf("webclient post response data: \n");
// 遍历响应数据并逐字符打印
for (index = 0; index < resp_len; index++)
{
rt_kprintf("%c", response[index]);
}
// 打印换行符
rt_kprintf("\n");
// 如果header不为空,释放header内存
if (header)
{
web_free(header);
}
// 如果response不为空,释放response内存
if (response)
{
web_free(response);
}
// 返回0表示函数执行成功
return 0;
}
//命令使用的函数
int webclient_post_test_API(int argc, char **argv, char* post_data)
{
char *uri = RT_NULL; // 初始化URI指针为NULL
// init_api_uris(API_GET_BALANCE_NO);
if (argc < 2) // 如果参数个数为1
{
uri = web_strdup(argv[1]); // 复制默认的POST本地URI
if(uri == RT_NULL) // 如果内存分配失败
{
rt_kprintf("no memory for create post request uri buffer.\n"); // 打印错误信息
return -RT_ENOMEM; // 返回内存不足错误码
}
//const char *post_data = "RT-Thread is an open source IoT operating system from China!";
webclient_post_comm(uri, (void *)post_data, strlen(post_data)); // 发送普通POST请求
}
else if (argc == 2) // 如果参数个数为2
{
if (strcmp(argv[1], "-s") == 0) // 如果第一个参数是"-s"
{
uri = web_strdup(argv[1]); // 复制默认的POST本地URI
if(uri == RT_NULL) // 如果内存分配失败
{
rt_kprintf("no memory for create post request uri buffer.\n"); // 打印错误信息
return -RT_ENOMEM; // 返回内存不足错误码
}
webclient_post_smpl(uri, (void *)post_data, strlen(post_data)); // 发送简化POST请求
}
else // 如果第一个参数不是"-s"
{
uri = web_strdup(argv[1]); // 复制用户提供的URI
if(uri == RT_NULL) // 如果内存分配失败
{
rt_kprintf("no memory for create post request uri buffer.\n"); // 打印错误信息
return -RT_ENOMEM; // 返回内存不足错误码
}
webclient_post_comm(uri, (void *)post_data, strlen(post_data)); // 发送普通POST请求
}
}
else if(argc == 3 && strcmp(argv[1], "-s") == 0) // 如果参数个数为3且第一个参数是"-s"
{
uri = web_strdup(argv[2]); // 复制用户提供的URI
if(uri == RT_NULL) // 如果内存分配失败
{
rt_kprintf("no memory for create post request uri buffer.\n"); // 打印错误信息
return -RT_ENOMEM; // 返回内存不足错误码
}
webclient_post_smpl(uri, (void *)post_data, strlen(post_data)); // 发送简化POST请求
}
else // 如果参数个数不符合要求
{
rt_kprintf("web_post_test [uri] - webclient post request test.\n"); // 打印使用说明
rt_kprintf("web_post_test -s [uri] - webclient simplify post request test.\n"); // 打印使用说明
return -RT_ERROR; // 返回错误码
}
if (uri) // 如果URI指针不为NULL
{
web_free(uri); // 释放URI内存
}
return RT_EOK; // 返回成功码
}
/* 增强版API请求函数 */
int post_api_uris(API_TYPE api_type)
{
int ret = RT_EOK;
char* post_json = RT_NULL; // 需要发送的json数据
const char *uri = RT_NULL; //对接的api地址
char time_val[32] = {0}; // 定义一个时间戳数组,用于存储时间戳字符串
// 校验码 使用 HMAC SHA256 (Base64) 签名方法进行签名,非空参数
int* sha256_sign = 0;
// 电子秤加载全部配置信息,0不加载,1全部加载
int* dzc_isload = 1;
/* 根据API类型生成不同JSON数据 */
switch (api_type)
{
// 获取电子秤密钥
case API_LOAD_KEY:
{
post_json = web_malloc(256); // 分配256字节的内存空间给post_json变量,用于存储JSON数据
// 时间戳获取,转化为秒
ret = (get_timestamp(time_val, sizeof(time_val))/1000);
if (ret != RT_EOK) {
rt_kprintf("[ERROR] Failed to get timestamp\n");
return ret;
}
snprintf(post_json, 256,
"{\"key\":\"%s\",\"isload\":\"%d\",\"timestamp\":\"%s\",\"sign\":\"%d\"}",
xinpian, dzc_isload, time_val, sha256_sign);
break;
}
// 获取台秤基础信息
case API_BALANCE_INFO:
{
post_json = web_malloc(256);
// 时间戳获取,转化为秒
ret = (get_timestamp(time_val, sizeof(time_val))/1000);
if (ret != RT_EOK) {
rt_kprintf("[ERROR] Failed to get timestamp\n");
return ret;
}
snprintf(post_json, 256,
"{\"key\":\"%s\",\"isload\":\"%d\",\"timestamp\":\"%s\",\"sign\":\"%d\"}",
xinpian, dzc_isload, time_val, sha256_sign);
break;
}
// 订单状态查询
case API_PAY_STATUS:
{
post_json = web_malloc(256);
// 时间戳获取,转化为秒
ret = (get_timestamp(time_val, sizeof(time_val))/1000);
if (ret != RT_EOK) {
rt_kprintf("[ERROR] Failed to get timestamp\n");
return ret;
}
snprintf(post_json, 256,
"{\"key\":\"%s\",\"isload\":\"%d\",\"timestamp\":\"%s\",\"sign\":\"%d\"}",
xinpian, dzc_isload, time_val, sha256_sign);
break;
}
default:
rt_kprintf("暂未实现的API类型: %d\n", api_type);
return -RT_EINVAL;
}
memset(time_val, 0, sizeof(time_val)); // 清空时间戳
/* 检查内存分配 */
if (!post_json) {
rt_kprintf("Memory allocation failed\n");
return -RT_ENOMEM;
}
/* 获取API地址 */
uri = get_api_uri(api_type);
if (!uri) {
rt_kprintf("Invalid API URI\n");
web_free(post_json);
return -RT_ERROR;
}
/* 发送请求 */
char* argv[] = { "web_post_test", (char*)uri };
ret = webclient_post_test_API(2, argv, post_json);
web_free(post_json);
return ret;
}
// 旧命令入口函数
// #ifdef FINSH_USING_MSH
// #include <finsh.h>// 命令入口函数 开启命令
// MSH_CMD_EXPORT_ALIAS(webclient_post_test_API, web_post_test, webclient post request test.);
// #endif /* FINSH_USING_MSH */
#ifdef FINSH_USING_MSH
#include <finsh.h>
/* API类型映射表 */
static const struct {
const char *name;
API_TYPE type;
} api_type_map[] = {
{"get_balance", API_GET_BALANCE_NO}, // 通过远程编码获得在线通信编码
**涉及实际产品,略**
};
/* 新命令入口函数 */
static void api_cmd(int argc, char **argv)
{
if (argc != 2) {
rt_kprintf("Usage: api_call <type>\n"
"Available types:\n"
"get_balance, load_key, balance_info, pay_status, get_pay, order_add, set_file, set_status, get_breed, set_price, get_vip, log_report, add_points, get_time, get_version\n");
return;
}
/* 查找匹配的API类型 */
for (size_t i = 0; i < sizeof(api_type_map)/sizeof(api_type_map[0]); i++) {
if (strcmp(argv[1], api_type_map[i].name) == 0) {
int ret = post_api_uris(api_type_map[i].type);
rt_kprintf("API调用结果: %s\n", ret == RT_EOK ? "成功" : "失败");
return;
}
}
rt_kprintf("无效的API类型!\n");
}
/* 注册新命令 */
MSH_CMD_EXPORT_ALIAS(api_cmd, api_call, Call specified API interface);
#endif /* FINSH_USING_MSH */
/*
编译并运行程序: 确保你的 RT-Thread 环境已经正确配置,并且该文件已经编译进你的固件中。启动你的设备并进入 RT-Thread 的命令行界面。
使用命令调用 API: 你可以使用 api_call 命令来调用不同的 API 接口。命令格式如下:
api_call <type>
示例:
获取远程编码:
api_call get_balance
串口
涉及实际产品,略
| 术语 | 组成 | |
|---|---|---|
| 净重 | 物品自身重量 | Mg |
| 毛重 | 净重 + 皮重 | Mg+mg |
| 皮重 | 包装/容器重量 | mg |
命令 0X20——一键发送重量
| 1 | 2 | 3 | 4 | 5 | 6-9 | 10 | 11-13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|
| 包头 | 长度 | 命令 | 正负+单位值 | 小数位 | 净重 | 稳定状态 | 皮重 | 验证码 | 包尾 |
| 0xAA | 0x0B | 0x20 | +KG:0x81;-KG:0x01 | UCHAR | UINT32 | YES:0x0A;NO:0x08 | 3 BYTE | UCHAR | 0x2F |
试验数据
| 1 | 2 | 3 | 4 | 5 | 6-9 | 10 | 11-13 | 14 | 15 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 包头 | 长度 | 命令 | 正负+单位值 | 小数位 | 净重(4) | 稳定状态 | 皮重 | 验证码 | 包尾 | |
| 数据1: | 01 | 03 | 00 01 86 A0 | 0A | 00 00 00 | |||||
| 结果1: | -KG | -100.00 | 100 | Yes | ||||||
| 数据2: | 04 | |||||||||
| 结果2 | -10.00 | |||||||||
小数位分析:
结论:输出数据=净重位数据*10^-(小数位数据)
例如
净重位数据=(0x64-》100)
小数位数据位=0x02
结果=100*10^(-2)=1
验证:

结论正确
数据1:
AA 0B 20 01 03 00 01 86 A0 0A 00 00 00 FA 2F
AA 0B 20 01 03 00 01 86 A0 0A 00 00 00 FA 2F



整数部分长度 = 6-小数位
00: 100000(6),6-0=6
03:100(3),6-3=3
04:10(2),6-4=2
净重位(4)分析:
数据=净重位16进制转10进制


想接收2580
需发送:符号位:81(+),小数位:00(数据x10^-0),净重:00 00 0A 14(2580)
验证:

发送:00 00 0A 14
接收:2.58Kg(小数位03,)
解析:
| 包头 | AA | -- |
|---|---|---|
| 长度 | 0B | 11 |
| 命令 | 20 | 一键发送重量 |
| 正负+单位 | 01 | -x |
| 小数位 | 03 | |
| 净重(3byte) | 00 01 86 | |
| 稳定状态 | 0A | yes |
| 皮重(3byte) | 00 00 00 | |
| 验证码 | FA | 忽略 |
| 包尾 | 2F | -- |
Raw: AA 0B 20 01 03 00 01 86 A0 0A 00 00 00 FA 2F
Data=Net: -100.00 KG | Stable: Yes | Tare: 0
AA 0B 20 01 03 00 01 86 A0 0A 00 00 00 FA 2F
解析:
| 使用 | |||
|---|---|---|---|
| 包头 | AA | ||
| 长度 | 0B | 11 | |
| 命令 | 20 | 一键发送重量 | |
| 正负+单位 | 01 | -x | |
| 小数位 | 03 | 数据=净重*10^(-小数位) | |
| 净重(3byte) | 00 01 86 | 数据=净重(16进制)转(10进制) | |
| 稳定状态 | 0A | yes | |
| 皮重(3byte) | 00 00 00 | ||
| 验证码 | FA | 忽略 | 待了解 |
| 包尾 | 2F | -- |
Raw: AA 0B 20 01 03 00 01 86 A0 0A 00 00 00 FA 2F
Data=Net: -100.00 KG | Stable: Yes | Tare: 0
皮重(3byte):

00 00 00:0
00 00 32 :50
00 00 64: 100
校验码:
校验码的计算规则为:
将包头(0xAA)到皮重字段(第13字节)的所有字节进行累加,取累加和的低8位作为校验码。以下是完整验证过程和示例解析:
校验码计算规则
-
计算范围
- 起始:包头
0xAA(第1字节) - 结束:皮重字段的第3字节(第13字节)
- 起始:包头
-
计算方法
$$
\text{校验码} = \left( \sum_{i=1}^{13} \text{字节}_i \right) \mod 256校验码=(i=1∑13字节i)mod256
$$
终端控制串口发送命令
#include "user_uart3.h"
uint8_t cmd20[] = {//一键发送重量One-click send weight
0xAA,0x01,0x20,0xCB,0x2F,
0x00 // 终止符
};
uint8_t cmd21_GW[] = {//取毛重,发送有问题,终止符和命令相同导致Take the gross weight.
0xAA,0x02,0x21,0x00,0xCD,0x2F,
0x00 // 终止符
};
//一键发送重量One-click send weight
void send_command_0x20(void)
{
Serial_Send_String(cmd20);
}MSH_CMD_EXPORT(send_command_0x20, CMD_20 One-click send weight);
//取毛重,发送有问题,终止符和命令相同导致Take the gross weight.
void send_command_0x21_GW(void)
{
Serial_Send_String(cmd21_GW);
}MSH_CMD_EXPORT(send_command_0x21_GW,CMD_21_GW Take the net weight);// 导出函数为命令
//略......
串口解析代码
#include "Serial_Port_Analysis.h"//添加串口解析命令头文件
// 十六进制字符串转字节数组
uint8_t* hex_str_to_bytes(const char* hex_str, size_t* out_len) {
char* tmp = strdup(hex_str);
char* token = strtok(tmp, " ");
uint8_t* bytes = malloc(strlen(hex_str)/3 + 1);
size_t count = 0;
while (token != NULL) {
bytes[count++] = (uint8_t)strtol(token, NULL, 16);
token = strtok(NULL, " ");
}
*out_len = count;
free(tmp);
return bytes;
}
// 数据包分割(改进版)
PacketList split_packets(const uint8_t* data, size_t len) {
PacketList list = {NULL, 0};
size_t start = 0;
int in_packet = 0;
for (size_t i = 0; i < len; ++i) {
if (data[i] == HEADER && !in_packet) {
start = i;
in_packet = 1;
} else if (data[i] == FOOTER && in_packet) {
size_t pkt_len = i - start + 1;
list.packets = realloc(list.packets, (list.count+1)*sizeof(Packet));
list.packets[list.count].data = malloc(pkt_len);
memcpy(list.packets[list.count].data, data+start, pkt_len);
list.packets[list.count].length = pkt_len;
list.count++;
in_packet = 0;
}
}
return list;
}
// 函数声明:计算校验和
// 参数:data - 指向数据数组的指针,length - 数据数组的长度
// 返回值:计算得到的校验和(8位无符号整数)
uint8_t calculate_checksum(uint8_t *data, size_t length) {
uint32_t checksum = 0; // 使用32位整数来避免溢出
for (size_t i = 0; i < length - 2; i++) {
checksum += data[i];
}
return checksum % 256; // 取低8位
}
// 解析单个数据包(支持多命令)
ParsedPacket parse_packet(const uint8_t* data, size_t len) {
ParsedPacket result = {0};
// ADD_ParsedPacket ADD_result = {0};
// 基础校验
if (len < 5 || data[0] != HEADER || data[len-1] != FOOTER) {
rt_kprintf("无效数据包\n");
return result;
}
// 通用字段解析
result.header = data[0];//包头
result.length = data[1];//数据长度
result.command = data[2];//命令
result.footer = data[len-1];//包尾
// 按命令类型处理
switch (result.command) {
case 0x20: { // 重量数据包
if (len < 11) break;//15-4:data_len-4
//校验码
result.checksum = data[len-2];
// 校验和校验
uint8_t calculated_checksum = calculate_checksum(data, len-2);
if (result.checksum != calculated_checksum) {
rt_kprintf("校验和错误\n");
return result;
}
// 符号和单位
switch (data[3]) {
case 0x81: strcpy(result.sign_unit, "+KG"); break;
case 0x01: strcpy(result.sign_unit, "-KG"); break;
default: strcpy(result.sign_unit, "未知");
}
// 小数位
result.decimal = data[4];
// 净重(4字节大端)
uint32_t net_weight_raw = (data[5] << 24) | (data[6] << 16) | (data[7] << 8) | data[8];
result.net_weight = net_weight_raw / pow(10, result.decimal);
// 稳定状态
strcpy(result.stability, (data[9] == 0x0A) ? "稳定" : "不稳定");
// 皮重(3字节)
uint32_t tare = (data[10] << 16) | (data[11] << 8) | data[12];
result.tare = tare / pow(10, result.decimal);
break;
}
case 0x21: { // 取毛重或净重数据包
if (len < 7) break;
// 符号和单位
switch (data[3]) {
case 0x81: strcpy(result.sign_unit, "+KG"); break;
case 0x01: strcpy(result.sign_unit, "-KG"); break;
default: strcpy(result.sign_unit, "未知");
}
// 小数位
result.decimal = data[4];
// 净重/毛重(4字节大端)
uint32_t net_weight_raw = (data[5] << 24) | (data[6] << 16) | (data[7] << 8) | data[8];
result.net_weight = net_weight_raw / pow(10, result.decimal);
//校验码
result.checksum = data[len-2];
// 校验和校验
uint8_t calculated_checksum = calculate_checksum(data, len-2);
if (result.checksum != calculated_checksum) {
rt_kprintf("校验和错误\n");
return result;
}
break;
}
case 0x22: { // 去皮应答
// 包头1 + 长度1 + 命令1 + 状态1 + 校验1 + 包尾1 = 6字节
if (len != 6) {
rt_kprintf("ERR: 去皮包长度错误\n");
// return result;
}
// 状态字段解析
switch (data[3]) {
case 0x00:
strncpy(result.ACK, "去皮成功", sizeof(result.ACK));
// rt_kprintf("去皮成功\n");
break;
case 0x01:
strncpy(result.ACK, "拥堵去皮", sizeof(result.ACK));
// rt_kprintf("拥堵,去皮失败\n");
break;
case 0x02:
strncpy(result.ACK, "非法命令", sizeof(result.ACK));
// rt_kprintf("非法命令\n");
break;
default:
strncpy(result.ACK, "未知状态", sizeof(result.ACK));
// rt_kprintf("未知状态\n");
}
break;
//校验码
result.checksum = data[len-2];
// 校验和校验
uint8_t calculated_checksum = calculate_checksum(data, len-2);
if (result.checksum != calculated_checksum) {
rt_kprintf("校验和错误\n");
return result;
}
}
case 0x23: { // 回皮(取皮重)
if (len < 7) break;
// 符号和单位
switch (data[3]) {
case 0x81: strcpy(result.sign_unit, "+KG"); break;
case 0x01: strcpy(result.sign_unit, "-KG"); break;
default: strcpy(result.sign_unit, "未知");
}
// 小数位
result.decimal = data[4];
// 皮重(4字节大端)
uint32_t net_weight_raw = (data[5] << 24) | (data[6] << 16) | (data[7] << 8) | data[8];
result.net_weight = net_weight_raw / pow(10, result.decimal);
//校验码
result.checksum = data[len-2];
// 校验和校验
uint8_t calculated_checksum = calculate_checksum(data, len-2);
if (result.checksum != calculated_checksum) {
rt_kprintf("校验和错误\n");
return result;
}
break;
}
case 0x24: { // 置零应答
if (len != 6) {
rt_kprintf("ERR: 置零包长度错误\n");
// return result;
}
// 状态字段解析
switch (data[3]) {
case 0x00:
strncpy(result.ACK, "置零成功", sizeof(result.ACK));
// rt_kprintf("去皮成功\n");
break;
case 0x01:
strncpy(result.ACK, "置零去皮", sizeof(result.ACK));
// rt_kprintf("拥堵,去皮失败\n");
break;
case 0x02:
strncpy(result.ACK, "非法命令", sizeof(result.ACK));
// rt_kprintf("非法命令\n");
break;
default:
strncpy(result.ACK, "未知状态", sizeof(result.ACK));
// rt_kprintf("未知状态\n");
}
break;
//校验码
result.checksum = data[len-2];
// 校验和校验
uint8_t calculated_checksum = calculate_checksum(data, len-2);
if (result.checksum != calculated_checksum) {
rt_kprintf("校验和错误\n");
return result;
}
}
default:
rt_kprintf("未知命令: 0x%02X\n", result.command);
}
return result;
}
// 解析数据包,打印结果
void print_packet(const ParsedPacket* pkt) {
char buffer[100];
rt_kprintf("包头: 0x%02X\n", pkt->header);
rt_kprintf("长度: 0x%02X\n", pkt->length);
rt_kprintf("命令: 0x%02X\n", pkt->command);
if (pkt->command == 0x20) {
rt_kprintf("单位: %s\n", pkt->sign_unit);
rt_kprintf("小数位: %d\n", pkt->decimal);
snprintf(buffer, sizeof(buffer), "%.*f", pkt->decimal, pkt->net_weight);
rt_kprintf("净重: %s\n", buffer);
// rt_kprintf("净重: %.*f\n", pkt->decimal, pkt->net_weight);
rt_kprintf("稳定状态: %s\n", pkt->stability);
snprintf(buffer, sizeof(buffer), "%.*f", pkt->decimal, pkt->tare);
rt_kprintf("皮重: %s\n", buffer);
// rt_kprintf("皮重: %.*f\n", pkt->decimal, pkt->tare);
} else if (pkt->command == 0x21) {
rt_kprintf("单位: %s\n", pkt->sign_unit);
rt_kprintf("小数位: %d\n", pkt->decimal);
snprintf(buffer, sizeof(buffer), "%.*f", pkt->decimal, pkt->net_weight);
rt_kprintf("净重/毛重: %s\n", buffer);
// rt_kprintf("净重: %.*f\n", pkt->decimal, pkt->net_weight);
}else if (pkt->command == 0x22) {
rt_kprintf("去皮状态: %s\n", pkt->ACK);
}else if (pkt->command == 0x23) {
rt_kprintf("单位: %s\n", pkt->sign_unit);
rt_kprintf("小数位: %d\n", pkt->decimal);
snprintf(buffer, sizeof(buffer), "%.*f", pkt->decimal, pkt->net_weight);
rt_kprintf("皮重: %s\n", buffer);
}else if (pkt->command == 0x24) {
rt_kprintf("置零状态: %s\n", pkt->ACK);
}
rt_kprintf("包尾: 0x%02X\n\n", pkt->footer);
}
作用:串口输入十六进制数据流,自动按照包头AA,包尾2F进行分包,然后按照分别解析各数据包命令和数据。

LVGL
4.3寸RGB565(480*272)的屏幕,触摸芯片是GT911。
menuconfig配置
Application options --->
*** Filesystem related ***
[*] Using File System Image 0 --->
--- Using File System Image 0
Select File System Type (FATFS) --->
(packages/artinchip/lvgl-ui/aic_demo/base_demo/lvgl_src/) Data Directory
(app.fatfs) Image Name
[*] auto calcuate image size
[ ] Using File System Image 1 ----
*** lvgl demo select related ***
-*- LVGL (official): powerful and easy-to-use embedded GUI library --->
(20) Priority of LVGL thread
(32768) Stack size of LVGL thread
(5) Display refresh period (ms)
[ ] Support SquareLine Studio
[ ] Enable built-in examples
[ ] Enable built-in demos
-*- ArtInChip lvgl demo
select lvgl demo (lvgl demo with basic function) --->
(X) lvgl demo with basic function
( ) lvgl demo of meter
(16) LVGL color depth(32/16)
(8) LVGL image cached number
(/rodata/lvgl_data) LVGL Resource Directory


文件系统映像设置

lvgl设置

lvgl例程设置




移植EEZ
1、复制EEZ Studio生成的ui文件夹至\packages\artinchip\lvgl-ui\aic_demo下
2、在ui.c和ui.h中屏蔽EEZ_FLOW相关定义,防止找不到EEZ_FLOW类头文件报错
ui.c

ui.h

3、在ui文件夹下添加SConscript文件

from building import *
# 导入`building`模块的所有内容,是用于构建系统的工具和配置。
import os
# 导入`os`模块,用于文件和路径操作。
cwd = GetCurrentDir()
# 获取当前工作目录路径,赋值给变量`cwd`。
group = []
# 初始化一个空列表`group`,用于存储构建的代码组信息。
src = Glob('*.c')
# 使用 `Glob` 函数查找当前目录下的所有 `.c` 文件,并将其文件路径存储到变量 `src` 中。
# `Glob` 是构建系统中的常见工具函数,用于匹配文件。
CPPPATH = [cwd]
# 定义预处理器头文件路径列表 `CPPPATH`,初始包含当前目录路径。
list = os.listdir(cwd)
# 使用 `os.listdir` 列出当前目录下的所有文件和文件夹,存储在变量 `list` 中。
for d in list:
# 遍历目录内容 `list` 中的每一项。
path = os.path.join(cwd, d)
# 构造完整路径 `path`,将 `cwd` 和当前项 `d` 拼接起来。
if os.path.isfile(os.path.join(path, 'SConscript')):
# 检查当前路径是否包含名为 `SConscript` 的文件。
group = group + SConscript(os.path.join(d, 'SConscript'))
# 如果存在 `SConscript` 文件,则调用 `SConscript` 函数执行,并将返回的结果添加到 `group` 列表中。
# `SConscript` 是 SCons 构建工具中的函数,用于加载子目录的构建脚本。
group = group + DefineGroup('LVGL-port', src, depend = ['EEZ_FOR_LVGL'], CPPPATH = CPPPATH) #修改的地方 ‘EEZ_FOR_LVGL’
# 定义一个新的代码组 `LVGL-port`:
# - `src`: 包含所有 `.c` 文件路径。
# - `depend`: 定义依赖项,指定该组依赖于 `EEZ_FOR_LVGL`。
# - `CPPPATH`: 将当前目录路径添加到编译器的头文件搜索路径。
# 最终返回的结果与 `group` 合并。
# 如果定义了 EEZ_FOR_LVGL 则将代码组加入编译!
Return('group')
# 返回 `group` 变量给调用者,作为最终的代码组信息供后续使用。
4、在\application\Kconfig中添加
6、在menuconfig中选择使用ui例程
Prompt: LVGL EEZ_FOR_LVGL demo │ │ Location: │
│ -> Application options │ │ -> ArtInChip LVGL demo (AIC_LVGL_DEMO [=y]) │ │ -> select LVGL demo (<choice> [=y]) │ │ Defined at application/Kconfig:261
7、在\luban-lite\application\Kconfig中255行添加
# 增加
config EEZ_FOR_LVGL
bool "LVGL EEZ_FOR_LVGL demo"
E:\D133EBS\ezz_d133_2_font\luban-lite\packages\artinchip\lvgl-ui\aic_demo\ui -- 自己添加的文件夹 ui
E:\D133EBS\ezz_d133_2_font\luban-lite\packages\artinchip\lvgl-ui\aic_demo\ui\SConscript #添加
E:\D133EBS\ezz_d133_2_font\luban-lite\packages\artinchip\lvgl-ui\aic_ui.c //130 行 #注释
E:\D133EBS\ezz_d133_2_font\luban-lite\application\Kconfig //255 行 #添加
修改程序结构使LVGL运行在线程里
逻辑:在aic_ui.c中创建线程,将lvgl初始化和运行放入线程中
aic_ui.c中修改过的代码片段
#include "lvgl.h"
#include "aic_ui.h"
#include "aic_osal.h"
#include "lvgl/demos/lv_demos.h"
//lvgl线程
static void lvgl_entry(void *parameter) {
/* LVGL初始化(如显示驱动、输入设备初始化) */
ui_init(); // 初始化用户界面,包括显示驱动和输入设备等
/* 主循环:周期性调用LVGL任务处理函数 */
while (1) {
// lv_task_handler(); // LVGL任务处理
ui_tick(); // UI刷新
rt_thread_mdelay(5); // 延时5ms,避免过度占用CPU
}
}
void aic_ui_init()
{
/* qc test demo is only for aic internal qc testing, please ignore it. */
// // 修改的地方
// #ifdef EEZ_FOR_LVGL
// extern void ui_init(void);
// ui_init();
// #endif
//创建线程
rt_thread_t lvgl_thread = rt_thread_create("lvgl", lvgl_entry, RT_NULL, 4096, 20, 10);
if (lvgl_thread != RT_NULL) {
rt_thread_startup(lvgl_thread);
}
return;
}
解释:将lvgl放入线程中,更好由RT-Thread系统管理。
官方例程代码默认逻辑只执行一次初始化,后续lvgl相关操作不易管理,在其余线程中操作lvgl函数导致线程卡死。
将lvgl放入线程中避免线程卡死,同时可以在该线程中刷新显示,实时更新变量和页面
修改变量显示
EEZ默认显示浮点型和字符型变量会报错类型不匹配

生成的错误代码:
类型不匹配get_var_net_weight_val();函数返回float类型变量数据,将其复制给字符指针cur_val导致类型不匹配报错
需要手动修改相应变量定义和显示代码
将float类型变量转化成字符串保存,在通过函数显示

// 在需要使用字符串的地方:
char weight_str[50]; // 声明一个足够大的字符数组来存储转换后的字符串
float weight_val = get_var_net_weight_val(); // 获取 float 类型的权重值————<定时器回调函数中更新>
// weight_val+=0.01;
// 将 float 转换为字符串,保留两位小数
sprintf(weight_str, "=%.2fkg", weight_val); // 保留两位小数
// 现在您可以使用 weight_str 作为 const char* 类型
// 例如,如果您想更新标签的文本:
lv_label_set_text(objects.obj1, weight_str);
更新变量
由于芯片方适配的LVGL默认没有实时刷新,变量改变后并不能更新显示
需要手动更新变量标签并刷新显示
1、使用LVGL定时器回调函数间隔刷新页面,以此更新变量
创建定时器:
lv_timer_create(timer_callback*, ms, NULL);
timer_callback是定时器到期时要调用的回调函数*
ms是定时器的时间间隔,单位为毫秒*
NULL是传递给回调函数的用户数据,这里没有传递任何数据*
封装成函数便于在ui_init()中初始化
//定时器回调函数
void timer_callback(lv_timer_t * timer) {
// counter++; // 增加计数器值
// set_var_lvnum(counter); // 更新全局变量
// update_counter_display(); // 更新显示
}
//初始化定时器
// 定义一个函数,用于显示计数器
// 参数ms表示定时器的时间间隔,单位为毫秒
void lv_timer_init(int ms)
{
// 创建一个定时器,并指定回调函数和定时时间
// timer_callback是定时器到期时要调用的回调函数
// ms是定时器的时间间隔,单位为毫秒
// NULL是传递给回调函数的用户数据,这里没有传递任何数据
lv_timer_create(timer_callback, ms, NULL);
}
在lvgl中初始化lvgl定时器
void ui_init() {
// 调用 create_screens 函数,创建所有需要的屏幕
create_screens();
// 调用 loadScreen 函数,加载主屏幕,SCREEN_ID_MAIN 是主屏幕的标识符
loadScreen(SCREEN_ID_MAIN);
//初始化定时器
lv_timer_init(1000);//定时器周期1000ms
}
2、使lvgl运行在线程中,线程中刷新lvgl
详细在该篇前文:目录——LVGL——修改程序结构使LVGL运行在线程里
改变字符型变量
char network_analysis_A_val[100] = {0};
sprintf(network_analysis_A_val, "密钥内容: %s\n", storage[i].data.key_data.key); // 保留两位小数
set_var_network_analysis_val(network_analysis_A_val);
使用sprintf函数将storage[i].data.key_data.key字符串添加到字符串末尾%s处,保存到字符数组network_analysis_A_val[]中。
使用eez生成更改字符变量的函数set_var_network_analysis_val()将字符传入变量
整合
开启



阶段性成果(图片)
整体框架:

串口终端显示执行过程:

RGB屏幕显示数据




浙公网安备 33010602011771号