D133物联网电子秤项目

D133物联网电子秤项目

项目开始日期:2025,2,28

需求

硬件采购需求:

3.5寸RGB屏幕(目前已适配4.3寸(480x272)RGB565屏幕)

语音播报(语音模块+喇叭)

软件需求:

  1. lvgl用户交互界面
  2. 处理电子称串口数据
  3. 语音播报消息
  4. 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调试打印接口

image-20250301092223249

使用

在user_uart3.c文件中将函数导出为命令,可以在终端开启串口

// 导出函数为命令
MSH_CMD_EXPORT(uart3_test_on, Test transmission and reception using UART3 serial port);

在终端开启

uart3_test_on

image-20250301092657012

发送

send_demoData

接受串口数据

image-20250301092928923

移植外设

外设框架


移植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

image-20250301144809070

外设路径

image-20250301151841133

外设上层路径

image-20250301145347332

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

image-20250302161917084

例程分析

例程结构

网络概念解析

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地址,具体步骤如下:

  1. 本地缓存查询
    • 浏览器首先检查自身缓存(如Chrome的DNS缓存)中是否有该域名的IP记录214。
    • 若无,则查询操作系统缓存(如Windows的hosts文件)和本地DNS缓存14。
  2. 本地DNS服务器查询
    • 若本地缓存未命中,请求会发送至用户配置的本地DNS服务器(如运营商提供的114.114.114.114)414。
    • 本地DNS服务器若缓存了域名记录,直接返回IP;否则进入递归查询流程14。
  3. 递归查询与根域名服务器
    • 本地DNS服务器依次向根域名服务器、顶级域名服务器(.com)、权威域名服务器(百度注册的DNS服务器)发起迭代查询,最终获取www.baidu.com的IP地址714。
  4. 返回IP地址
    • 查询结果逐级返回至浏览器,例如常见的百度IP地址可能为 220.181.111.147202.108.22.5(实际IP可能因负载均衡而变化)27。

相关命令

  • 使用 nslookup www.baidu.comping www.baidu.com 可直接查看解析后的IP地址3。

2. 建立TCP连接

获取IP地址后,浏览器通过TCP协议与服务器建立连接:

  1. 三次握手
    • 客户端发送SYN包,服务器返回SYN-ACK包,客户端再发送ACK包确认,完成可靠连接的建立27。
  2. 端口与协议
    • 服务器默认监听80端口(HTTP)或443端口(HTTPS)212。

3. 发起HTTP请求与响应

  1. HTTP请求
    • 浏览器发送HTTP GET请求,包含请求头(如User-Agent、Cookie等)和请求路径(如/表示首页)712。
  2. 服务器处理
    • 百度服务器根据请求生成响应,可能涉及负载均衡、动态内容生成或静态资源返回1213。
  3. HTTP响应
    • 服务器返回状态码(如200 OK)、响应头(如Content-Type)及HTML内容27。

4. 浏览器渲染页面

  1. 解析HTML与资源加载
    • 浏览器解析HTML代码,并加载其中的CSS、JavaScript、图片等资源,可能触发多次HTTP请求212。
  2. 渲染引擎处理
    • 构建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 协议

实现步骤

  1. 启用 lwIP MQTT 模块
    lwipopts.h 中启用 MQTT 支持:

    #define LWIP_MQTT 1
    
  2. 集成 MQTT 客户端
    lwIP 提供轻量级 MQTT 客户端,需包含头文件:

    #include "mqtt.h"
    
  3. 连接与通信示例

    // 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 协议

实现步骤

  1. 启用 HTTP 服务器
    lwipopts.h 中启用 HTTP 支持:

    #define LWIP_HTTPD 1
    #define LWIP_HTTPD_SSI 1   // 可选:支持 SSI
    #define LWIP_HTTPD_CGI 1   // 可选:支持 CGI
    
  2. 实现 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));
    
  3. 启动 HTTP 服务器

    httpd_init();
    

4. 使用 DHCP 服务器

配置方法

  1. 启用 DHCP 服务器功能
    lwipopts.h 中启用:

    #define LWIP_DHCP 1         // 启用 DHCP 客户端
    #define LWIP_DHCPS 1        // 启用 DHCP 服务器
    
  2. 设置地址池
    在代码中配置 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);
    
  3. 处理 DHCP 请求
    lwIP 自动处理客户端请求,分配 IP 并管理租期。


5. 启用 lwIP 调试选项

作用
调试选项用于输出网络状态、协议解析细节和错误信息,帮助定位连接问题、内存泄漏或协议错误。

启用方法

  1. 全局调试开关
    lwipopts.h 中设置:

    #define LWIP_DEBUG 1                // 启用调试
    #define LWIP_DBG_MIN_LEVEL LWIP_DBG_LEVEL_ALL  // 输出所有级别日志
    
  2. 模块级调试
    按需启用特定模块的调试:

    #define DHCP_DEBUG LWIP_DBG_ON      // DHCP 调试
    #define TCP_DEBUG LWIP_DBG_ON       // TCP 调试
    #define HTTP_DEBUG LWIP_DBG_ON      // HTTP 调试
    
  3. 输出调试信息
    实现 lwip_log 函数或重定向到串口:

    void lwip_log(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);  // 输出到控制台或串口
        va_end(args);
    }
    

典型调试场景

  • 内存泄漏:启用 MEM_DEBUGMEMP_DEBUG
  • TCP 重传:启用 TCP_CWND_DEBUG 查看拥塞窗口变化。
  • HTTP 请求解析:启用 HTTP_DEBUG 跟踪请求处理。

总结

  • 堆栈大小:根据协议复杂度和线程任务调整,避免溢出。
  • 协议支持:通过 lwipopts.h 启用 MQTT/HTTP/DHCP,并实现回调函数。
  • 调试:启用调试宏并实现日志输出,快速定位问题。

建议结合具体平台(如 FreeRTOS、Linux 等)和硬件资源(内存、CPU)进行参数调优。

开启网络

image-20250303211145500

image-20250303211207087

image-20250303220100125

image-20250303221202783

image-20250303221115056

image-20250303221757077

image-20250303221748038

image-20250303222058480

image-20250303222152926

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地址及相关配置参数,从而简化网络配置并避免手动操作带来的错误。


一、核心功能

  1. IP地址自动分配
    • 动态分配:从地址池中临时分配IP(有租期限制)
    • 静态绑定:为特定设备(如服务器)固定分配IP

lwIP

(Lightweight IP) 是一个轻量化的开源 TCP/IP 协议栈,专为资源受限的嵌入式系统设计。它由瑞典计算机科学研究院的 Adam Dunkels 开发,广泛应用于物联网设备、工业控制、传感器网络等场景。

  1. 模块化架构
    • 支持按需裁剪功能模块(如 DHCP、DNS、IPv6),开发者可灵活配置协议栈功能。
  2. 多协议支持
    • 包含完整的 TCP/IP 协议族:
      • 网络层:IPv4、IPv6、ICMP、IGMP
      • 传输层:TCP、UDP
      • 应用层:HTTP、SNMP、MQTT(需扩展实现)

lwIP 的典型应用场景

场景 说明
物联网设备 智能家居设备(如 Wi-Fi 插座、传感器)通过 lwIP 实现网络通信。
工业控制 PLC、工业网关使用 lwIP 接入以太网,支持 Modbus TCP 等协议。
嵌入式 Web 服务器 通过 lwIP 的 HTTP 模块,在设备上运行轻量级 Web 页面(如配置界面)。
低功耗设备 结合低功耗无线技术(如 LoRa、BLE),实现电池供电设备的间歇性网络通信。

image-20250304204927459

image-20250304204949681

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
image-20250317091909194

需出现IP地址才算链接成功

可以 通过ping ip来再次确认是否连接到互联网还是内网:

ping www.baidu.com
//DNS解码的百度ip地址
ping 183.2.172.177

image-20250304154921014

无线网络

本章节讲解如何在 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模块该选

image-20250304213318529

image-20250307221754392

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

image-20250307221907769

配置 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

image-20250307220702309

image-20250307220728154

image-20250307220748956

image-20250307220901070

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. 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

image-20250308110115371

image-20250308111342559

image-20250308111011890

image-20250308111602358

例程路径

luban-lite\packages\third-party\webclient\samples

例程运行逻辑:

image-20250308114101502

终端执行:

tab提示命令:

image-20250308114132723

使用例程:

先连接WIFI

wlan  wifi_connect jianzhiji 8765432111	

检查是否分配ip地址

ifconfig
ping 183.2.172.177

请求:webclient_get_sample:

运行命令:
web_get_test

请求网站并返回数据

作用:

演示如何使用 WebClient 软件包发送 HTTP GET 请求并接收响应数据。文件中的代码展示了如何创建 WebClient 会话、发送 GET 请求、接收响应数据并处理内存分配错误等操作。

例程实现的功能

  1. HTTP GET请求发送
    • 支持向指定URI(如 http://www.rt-thread.com/service/rt-thread.txt)发送GET请求。
    • 可处理 普通请求(分块传输或固定长度)和 简化请求(短数据场景)。
  2. 响应数据处理
    • 接收服务器返回的响应数据,并打印输出到终端。
    • 自动处理 分块传输编码(Chunked Transfer Encoding)固定内容长度(Content-Length) 两种HTTP响应格式。
  3. 命令行交互支持
    • 通过RT-Thread的 FINSH/MSH 命令行接口,提供用户可配置的测试命令,支持自定义URI和请求模式。
终端输出结果:

image-20250308115047923

使用函数命令,访问

web_get_test url

image-20250308145434578

例如访问官方测试网站

web_get_test http://www.rt-thread.com/service/rt-thread.txt

终端返回的结果

image-20250308152026617

网站内容:

image-20250308151734226

内容可以对应上,证明成功请求网站并返回了内容

自定义URI——访问个人网站

web_get_test https://www.cnblogs.com/tianwuyvlianshui

该问题表示未启用https,确实如其言,我们只开启了http,后续开启https再尝试

image-20250308151315662

测试其他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 请求的公共服务,它会将你的请求信息原样返回。

image-20250308152555279

测试带参数请求

web_get_test http://httpbin.org/get?name=RT-Thread&id=123

image-20250308153214183

成功添加访问参数

  "name": "RT-Thread", 
  "id": "123"

测试html网站

web_get_test http://example.com

image-20250308153916293

成功返回该静态网站的html文件

将返回的html保存打开就是该网站

image-20250308154115340

测试 返回指定状态码(如 404)

web_get_test http://httpbin.org/status/404

image-20250308154411769

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 请求并接收响应数据。具体实现了以下功能:

  1. 定义常量和变量:定义了响应缓冲区大小、请求头缓冲区大小和默认的 POST 请求 URI。
  2. 发送普通 POST 请求
    • 函数 webclient_post_comm 创建 WebClient 会话,构建请求头,发送 POST 请求,并接收和打印响应数据。
  3. 发送简化 POST 请求
    • 函数 webclient_post_smpl 创建请求头,发送 POST 请求,并接收和打印响应数据。
  4. 测试函数
    • 函数 webclient_post_test 根据命令行参数选择发送普通 POST 请求或简化 POST 请求,并处理 URI 内存分配和释放。
  5. 命令行集成
    • 使用 FinSH 命令行系统集成测试命令 web_post_test,可以在命令行中执行 POST 请求测试。
终端输出结果:

image-20250308115126788

webclient_shard_download_sample:

运行命令:
web_shard_test
作用:

演示如何使用 WebClient 软件包进行分片下载。文件中的代码展示了如何发送 HTTP GET 请求并分片接收响应数据,以及如何处理和打印接收到的数据。

终端输出结果:

image-20250308115158349

测试http pose:

如果服务器是回显服务(如 http://www.rt-thread.com/service/echo),响应内容应与发送的 post_data 完全一致

终端测试命令:

web_post_test http://www.rt-thread.com/service/echo

pose发送内容:

image-20250308210839677

终端打印pose响应

image-20250308210954546

响应内容应与发送的 post_data 完全一致

D133 ping 电脑

电脑与板子同连手机热点

电脑ip

image-20250308213250356

板卡ip

image-20250308213331874

可以看到在同一局域网192.168.106.xxx

理论上可以ping通,实际则可能不行,原因是电脑防护墙阻挡

关闭则可以ping通(测试完记得重新开启)

image-20250308213502579 image-20250308213210251

抓包

image-20250308215728258

image-20250308215749225

image-20250308220042990
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请求数据)

image-20250325111206613

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

image-20250325111246634

命令(命令+网址):

web_post_test http://192.168.106.31:8080

image-20250308222941122

image-20250308225306796

在PC

image-20250308223017920

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

image-20250308223758674

数据上报对接api

URL头部

http://xxx

URL头部+api

web_post_test http://xxx/api/yyy

Postman

Postman 是一款广泛应用于 API 开发与测试 的协作工具,旨在简化 API 的生命周期管理(设计、开发、测试、部署、监控等)。

主要功能

  1. API 请求测试与调试
    • 支持发送 HTTP/HTTPS 请求(GET、POST、PUT、DELETE 等)。
    • 可自定义请求头(Headers)、参数(Params)、身份验证(OAuth、JWT 等)和请求体(Body)。
    • 直观的界面帮助快速调试 API,查看响应状态码、响应时间和返回数据(JSON、XML 等)。
  2. 自动化测试
    • 使用 JavaScript 编写测试脚本,验证 API 响应是否符合预期。
    • 支持断言(Assertions)、动态变量和环境变量,提升测试灵活性。
    • 集成 CI/CD 工具(如 Jenkins、GitHub Actions)通过 Newman 命令行工具运行自动化测试。
  3. 协作与文档
    • 团队可共享 API 集合(Collections) 和环境配置,提升协作效率。
    • 自动生成 API 文档,支持一键发布,便于前后端开发者或第三方调用者查阅。
  4. Mock 服务器
    • 创建虚拟 API 端点,模拟真实响应,支持前端开发与后端 API 并行工作。
  5. 监控与性能测试
    • 定期运行 API 测试,监控服务可用性与性能,生成报告分析潜在问题。

适用场景

  • 开发者:快速调试接口,验证功能逻辑。
  • 测试工程师:设计自动化测试用例,确保 API 稳定性。
  • 团队协作:统一管理 API 资产,减少沟通成本。
  • 文档编写:自动生成易读的 API 文档,替代手动维护。

版本与平台

  • 免费版:满足个人基础需求(请求测试、简单自动化)。
  • 付费版(Pro/Enterprise):解锁团队协作、高级监控、私有 API 文档等功能。
  • 跨平台支持:提供 Windows、macOS、Linux 客户端及网页版。

优势

  • 用户友好:图形化操作降低学习成本,替代命令行工具(如 cURL)。
  • 生态丰富:支持 OpenAPI 规范、WebSocket 测试、GraphQL 等扩展场景。
  • 云端同步:数据云端存储,多设备无缝切换。

简单示例

  1. 在 Postman 中新建请求,输入 API 地址。
  2. 设置请求方法(如 POST),添加 JSON 格式的请求体。
  3. 添加测试脚本验证响应状态码是否为 200。
  4. 保存到集合,分享给团队成员或生成文档。

无论是独立开发还是团队协作,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解析库

image-20250317152012525

在串口终端控制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

验证:

image-20250306114053498

结论正确

数据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

image-20250306110753821

image-20250306111438889

image-20250306111612321

整数部分长度 = 6-小数位

00: 100000(6),6-0=6

03:100(3),6-3=3

04:10(2),6-4=2

净重位(4)分析:

数据=净重位16进制转10进制

image-20250306112307180

image-20250306112427144

想接收2580

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

验证:

image-20250306112836249

发送: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):

image-20250306142025599

00 00 00:0

00 00 32 :50

00 00 64: 100

校验码:

校验码的计算规则为:

将包头(0xAA)到皮重字段(第13字节)的所有字节进行累加,取累加和的低8位作为校验码。以下是完整验证过程和示例解析:


校验码计算规则

  1. 计算范围

    • 起始:包头 0xAA(第1字节)
    • 结束:皮重字段的第3字节(第13字节)
  2. 计算方法
    $$
    \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进行分包,然后按照分别解析各数据包命令和数据。

image-20250324162316118

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

image-20250324220503153

image-20250324221957305

文件系统映像设置

image-20250324221336371

lvgl设置

image-20250324222307878

lvgl例程设置

image-20250324222820503

image-20250325091237589

image-20250324215657601

image-20250324215630266

移植EEZ

1、复制EEZ Studio生成的ui文件夹至\packages\artinchip\lvgl-ui\aic_demo

2、在ui.c和ui.h中屏蔽EEZ_FLOW相关定义,防止找不到EEZ_FLOW类头文件报错

ui.c

image-20250326104046989

image-20250326104208900

ui.h

image-20250326155337168

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

image-20250326105438066

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中添加

image-20250326105557703

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    
image-20250326152224739

7、在\luban-lite\application\Kconfig中255行添加

# 增加
config EEZ_FOR_LVGL
    bool "LVGL EEZ_FOR_LVGL demo"
image-20250326152850979
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默认显示浮点型和字符型变量会报错类型不匹配

image-20250328163007964

生成的错误代码:

类型不匹配get_var_net_weight_val();函数返回float类型变量数据,将其复制给字符指针cur_val导致类型不匹配报错image-20250328163244937

需要手动修改相应变量定义和显示代码

将float类型变量转化成字符串保存,在通过函数显示

image-20250328214230589

    // 在需要使用字符串的地方:
        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()将字符传入变量

整合

开启

image-20250324154832977

image-20250324154951811

image-20250324170827976

阶段性成果(图片)

整体框架:

image-20250503212524727

串口终端显示执行过程:

image-20250503212709925

RGB屏幕显示数据

image-20250503212537255

posted @ 2025-03-08 11:19  沁拒离  阅读(529)  评论(0)    收藏  举报