- 2024-10-29
-
发表了主题帖:
【2024 DigiKey创意大赛】环境灯控制系统作品提交
本帖最后由 wakojosin 于 2024-10-29 23:55 编辑
作品简介
原来的项目是打算使用matter协议进行设备间的互联,但是由于网络原因,环境的搭建一直不成功,所以改成了WiFi的形式完成设备联网,然后通过mqtt进行设备间的通讯。
本项目主要用到的物料是esp32-s3-lcd-ev-board和esp32-s3-devkitc-1两个开发板,没有用到额外的传感器灯外围器件。两个板子的主控都是esp32s3,lcd-ev-board具有lcd和触摸屏等外围设备,kevkitc则是只有简单的外设。功能是通过esp32-s3-lcd-ev-board作为控制板,可以监控网络中接入的设备,然后通过UI界面进行展示设备的状态以及控制设备。设备端是以esp32-s3-devkitc-1来完成功能的,主要是当状态改变的时候,主动上报,当控制板下发命令的时候,响应控制命令,当前的控制命令就是电灯。
下面是设备的实物照
图表 1开灯时的界面显示
图表 2关灯时的界面显示
图表 3带上灯光板关灯状态
图表 4带上灯光板开灯状态
图表 5设置界面
二、系统框图
首先介绍一下本系统设计思路,环境灯的主要目标是可以通过一个中控面板方便的控制环境中的灯光系统,在家庭环境中则是可以方便的控制家中的各种各样的灯,所以开发了这么一个环境灯控制系统,本系统主要用到了wifi功能,通讯协议则是选择了mqtt,当有新的设备接入时,中控可以自动将设备添加到设备列表中,然后通过选择不同的灯,就可以控制这个灯的状态,比如开关,色彩等(当前只实现开关功能),同时也可以显示具有温湿度功能的灯所上传的温湿度数据(中控已经实现,但是当前没有带温湿度数据的灯),灯部分则是简单的实现了按键控制LED的开关,然后状态可以自动同步到中控中。
下面简单介绍一下系统软硬件,首先是硬件,下面是lcd-ev-board的原理图截图
图表 6中控主控
图表 7LCD接口原理图
图表 8触摸屏原理图
图表 9屏幕与主板的接口定义
下面是软件的系统框图
图表 10主流程
图表 11LED板流程图
三、各部分功能说明
下面对主要的功能模块进行解释说明,首先是wifi连接,这部分包含了wifi扫描和密码输入,下面是主要的代码逻辑:
下面是清空wifi列表的接口,wifi_list_wifis是wifi列表
void custom_clear_wifi_list(lv_ui *ui)
{
lv_obj_t *parent = ui->wifi_list_wifis;
lv_obj_t *child;
while(lv_obj_get_child_cnt(parent) > 1)
{
child = lv_obj_get_child(parent, 1);
lv_obj_del(child);
}
}
这个是wifi列表按下后的回调函数,主要功能是切换到键盘
static void wifi_list_wifis_item_x_event_handler (lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
switch (code) {
case LV_EVENT_CLICKED:
{
const char *cur_wifi = lv_list_get_btn_text(guider_ui.wifi_list_wifis, lv_event_get_target(e));
char buf[64];
int len = strlen(cur_wifi);
len = len > sizeof(buf)-1 ? sizeof(buf)-1 : len;
memcpy(buf, cur_wifi, len);
buf[len] = '\0';
ui_load_scr_animation(&guider_ui,&guider_ui.get_passwd,guider_ui.get_passwd_del,&guider_ui.wifi_del, setup_scr_get_passwd, LV_SCR_LOAD_ANIM_MOVE_LEFT, 200, 200, false, true);
lv_label_set_text(guider_ui.get_passwd_label_wifi, buf);
break;
}
}
}
这部分是添加wifi的接口,当扫描到wifi后就通过这个接口将扫描结果添加到wifi列表中
void custom_add_wifi_list(lv_ui *ui, const char *str)
{
lv_obj_t *parent = ui->wifi_list_wifis;
lv_obj_t *item = lv_list_add_btn(parent, LV_SYMBOL_WIFI, str);
lv_obj_add_event_cb(item, wifi_list_wifis_item_x_event_handler, LV_EVENT_ALL, ui);
}
接下去介绍一下mqtt部分的主要代码,首先是mqtt的处理任务,用来执行mqtt的连接、断开等控制操作
static void mqttc_process_entry(void *p)
{
struct mqttc_handler *handler = p;
esp_mqtt_client_handle_t client = NULL;
EventGroupHandle_t event = NULL;
uint32_t evt;
while(1)
{
evt = xEventGroupWaitBits(event, MQTTC_EVT_FLAG_ALL_BITS, pdTRUE, pdFALSE, portMAX_DELAY);
if(evt & BIT31)
{
ESP_LOGE(TAG, "wait bits timeout");
continue;
}
if(MQTTC_EVT_FLAG_IS_SET(evt, MQTTC_EVT_FLAG_CONNECTED))
{
int msg_id;
msg_id = esp_mqtt_client_subscribe(client, D2C_HEATBEAT, 0);
ESP_LOGI(TAG, "msg_id=%d", msg_id);
}
if(MQTTC_EVT_FLAG_IS_SET(evt, MQTTC_EVT_FLAG_DISCONNECTED))
{
ESP_LOGI(TAG, "mqtt disconned.");
}
if(MQTTC_EVT_FLAG_IS_SET(evt, MQTTC_EVT_FLAG_START))
{
if(handler->state == MQTTC_STA_DISCONNECTED)
{
ESP_LOGI(TAG, "mqtt client start:");
esp_err_t err = esp_mqtt_client_start(client);
ESP_LOGI(TAG, "%d", err);
handler->state = MQTTC_STA_CONNECTING;
}
}
if(MQTTC_EVT_FLAG_IS_SET(evt, MQTTC_EVT_FLAG_STOP))
{
ESP_LOGI(TAG, "mqtt client stop:");
esp_err_t err = esp_mqtt_client_stop(client);
ESP_LOGI(TAG, "%d", err);
}
}
}
然后是mqtt数据接收及处理部分的代码,这部分通过cJSON对接收到的数据进行解析,然后判断对应的数据项,获取所需的数据,执行对应的功能。
static void mqttc_data_arrived(void *handler, const char *topic, int topic_len, uint8_t *data, int data_len)
{
if(strncmp(topic, D2C_HEATBEAT, topic_len) == 0)
{
cJSON *data_root = cJSON_Parse((char *)data);
struct mqttc_handler *mqc = (struct mqttc_handler *)handler;
do
{
int led;
cJSON *item = cJSON_GetObjectItem(data_root, "led");
led = item->valueint;
item = cJSON_GetObjectItem(data_root, "device");
const char *device = item->valuestring;
ESP_LOGI(TAG, "device:%s, led:%d", device, led);
if(custom_check_device_list(&guider_ui, device) == false)
{
if(mqttc_device_subscribe(mqc, device) != ESP_OK)
{
ESP_LOGE(TAG, "sub failed:%p");
break;
}
custom_add_device_list(&guider_ui, device);
}
char humi[8],temp[8];
item = cJSON_GetObjectItem(data_root, "humi");
if(item != NULL)
{
int int_val = item->valueint;
int decimal_val = int_val%10;
if(decimal_val < 0)
{
decimal_val = -decimal_val;
}
int_val /= 10;
snprintf(humi, sizeof(humi), "%d.%d", int_val, decimal_val);
}
else
{
humi[0] = '\0';
}
item = cJSON_GetObjectItem(data_root, "temp");
if(item != NULL)
{
int int_val = item->valueint;
int decimal_val = int_val%10;
if(decimal_val < 0)
{
decimal_val = -decimal_val;
}
int_val /= 10;
snprintf(temp, sizeof(temp), "%d.%d", int_val, decimal_val);
}
else
{
temp[0] = '\0';
}
custom_update_device_data(&guider_ui, led, humi, temp);
}while(0);
if(data_root)
{
cJSON_Delete(data_root);
data_root = NULL;
}
}
}
至此,核心的业务逻辑已经介绍的差不多了,剩下的就是内容的显示,还有时间同步等相关功能,感兴趣的同学可以直接阅读源码。
四、作品源码
中控板源码:https://download.eeworld.com.cn/detail/wakojosin/634839
环境灯源码:https://download.eeworld.com.cn/detail/wakojosin/634840
五、作品功能演示视频
[localvideo]7821ca06ffcd7ff906b716adb58dcc03[/localvideo]
六、项目总结
很高兴能够参加本次大赛,虽然没能按原计划学习matter相关的知识,但是从物联网的方向学习了多项技术,从而可以完整的实现一个非常实用的控制方案,也能够非常灵活的进行扩展,作为一个IO控制系统,实现简单的智能家居方案。
由于时间上面安排比较紧张,对于esp32的平台也不够熟悉,中间遇到的问题需要花很长的时间来定位和解决,所以没能够达到预期的成果。虽然大赛有截止时间,但是未来的学习还很长,通过esp32平台希望未来能够学习更多的物联网、AIOT相关的知识。
-
上传了资料:
【2024 DigiKey创意大赛】环境灯源码
-
上传了资料:
【2024 DigiKey创意大赛】环境灯控制系统源码
- 2024-09-24
-
回复了主题帖:
【2024 DigiKey创意大赛】 开箱贴
秦天qintian0303 发表于 2024-9-22 18:52
ESP32-S3-LCD-EV-Board估计是这次购买最多的人了
国产芯片要崛起~
- 2024-09-21
-
发表了主题帖:
【2024 DigiKey创意大赛】 开箱贴
# 【2024 DigiKey创意大赛】 开箱贴
很高兴参加此次DigiKey创意大赛,我选的物料是ESP32-S3-LCD-EV-Board和ESP32-S3-DevKitC,下面是刚收到的板子照片分享给大家。
## 拆箱
拆包装后里面是两个盒子,一个是ESP32-S3-LCD-EV-Board,另一个是ESP32-S3-DevKitC
打开箱子看看:
ESP32-S3-LCD-EV-Board-SUB2对应的版本是v1.5,板载模组是ESP32-S3-WROOM-1 N16R16V,对应的资源是16MB的FLASH和16MB的RAM,搭配的是3.95寸480x480,接口是RGB565,主控是GC9503CV的屏幕,带的触摸芯片是FT5x06。
ESP32-S3-DevKitC-1对应的ESP32-S3-WROOM-1版本是N8R8,即8MB的FLASH和8MB的RAM。
## 上电
开机动画:
初始化,选择语言:
选择演示的功能,可以选择UI的演示和语言控制的演示:
我选择了UI的演示,界面如下:
长时间不按屏幕就会出来时钟界面:
## 小结
开箱的分享到此结束,感谢点阅。接下来就是学习一下esp32-s3的使用,体验一下esp32-s3的强大性能和丰富的无线网络资源。
- 2024-06-23
-
发表了主题帖:
#AI挑战营终点站#模型优化--激活函数比较
本帖最后由 wakojosin 于 2024-6-23 17:40 编辑
# 模型介绍
使用的模型是LeNET,模型结构如下:
- 输入层:接收32x32的灰度图像;
- C1卷积层:使用6个5x5的卷积核进行卷积操作,输出尺寸为28x28;
- S2池化层:使用2x2的平均池化操作,输出14x14;
- C3卷积层:使用16个5x5卷积核对S2的输出进行卷积,输出尺寸为10x10
- S4池化层:再次使用2x2的平均池化,输出尺寸5x5;
- C5卷积层:这是全连接层前的卷积操作,使用120个5x5的卷积核,在之前特征图的基础上进行全连接计算,输出1x1x120的特征向量;
- F6全连接层:拥有84个神经元,将C5的输出进一步压缩为一个固定长度的向量,用于分类前的特征表示;
- 输出层:最后一个逻辑回归(或层softmax层),用于将F6的输出转化为10类输出的概率分布,对应0~9的数字
## 模型
```python
# act_func = nn.Softmax()
act_func = nn.ReLU()
net = torch.nn.Sequential(
# Reshape(),
nn.Conv2d(1, 6, kernel_size=5, stride=1), act_func, # (32-5)/1+1=28
nn.AvgPool2d(kernel_size=2, stride=2), # (28-2)/2+1=14
nn.Conv2d(6, 16, kernel_size=5), act_func, # 14-5+1=10
nn.AvgPool2d(kernel_size=2, stride=2), # (10-2)/2+1=5
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), act_func,
nn.Linear(120, 84), act_func,
nn.Linear(84, 10)
)
```
# 优化对比
此次优化对比主要是针对激活函数。
## 激活函数
1. ReLU
函数f(x)=max(0,x)
特点:
- 计算简单,只有线性关系和阈值操作,不会出现梯度饱和
- 对负数输出恒为0,可能会导致部分神经元永远不被激活;
2. Sigmoid
函数f(x)=1/(1+e^(-x))
特点:
- 输出范围有限,介于0~1之间,适用于二分类概率输出场景;
- 输出连续可微,适用于逻辑回归等模型;
- 当输入绝对值较大时,sigmoid函数的导数值非常接近0(梯度饱和),导致反向传播时梯度消失,会减慢学习过程;
- 计算相对复杂,设计指数运算,比ReLU更耗时;
3. Softmax
函数p(i)=e^z(i)/sum(e^z(j)),j=1..n
特点:
- 概率输出,将输入转化为概率分布,所有输出之和为1,非常适合多分类任务;
- 类别间比较,提供一种自然的方式比较不同类别的相对置信度;
- 计算复杂,相比ReLU和Sigmoid,sofsh书倒数tmax涉及到更加复杂的数学计算,包括指数和归一化;
-
## 结果对比
激活函数的实际效果:
func |Avg.Loss|Accuracy|speed
:-: | :-: | :-: |:-:
Softmax|0.000047|0.985500|mid
sigmoid|0.000058|0.981900|slow
ReLU |0.000028|0.990900|quick
- 2024-05-18
-
回复了主题帖:
使用Nordic NRF5340+NRF7002开发板尝试matter例程
qzc飘曳 发表于 2024-5-16 12:14
都支持什么开发环境啊?
windows,linux都可以,安装ncs环境就行.
-
发表了主题帖:
matter设备--为light_bulb添加温湿度endpoint
准备
由于matter使用zap对endpoint/cluster等进行添加/编辑/删除等操作,所以需要安装zap工具.
zap工具必须版本一直,可以通过<ncs目录>modules/lib/matter/scripts/setup/nrfconnect/get_zap.py来安装,命令如下:
cd <ncs>/modules/lib/matter
python scripts/setup/nrfconnect/get_zap.py -l ~/Programs/ -o
~/Programs/是指定的安装目录.
<ncs>表示ncs安装目录.
配置
命令格式如下:
zap src/light_bulb.zap --zcl <ncs>/modules/lib/matter/src/app/zap-templates/zcl/zcl.json --gen <ncs>/modules/lib/matter/src/app/zap-templates/app-templates.json
需要注意的是zcl.json和app-tamplates.json文件不可以拷贝到其他地方,我是了会报错,拷贝之后可能需要修改里面的内容.
然后会出现配置界面如下:
我已经添加了endpoint-2,endpoint添加是使用+ADD POINT按钮,点了之后会出现以下界面:
device可以下拉进行选择,选和是自己的就行.
温湿度传感器endpoint的内容如下:
通过过滤器只显示使能的cluster后就是上面的界面.
根据实际情况进行属性,命令等设置.
完成后保存退出即可.
生成源码
使用以下命令进行代码生成
python <ncs>/modules/lib/matter/scripts/tools/zap/generate.py <工程目录>/src/light_bulb.zap -t <ncs>/modules/lib/matter/src/app/zap-templates/app-templates.json -o <工程目录>/src/zap-generated
工程目录和ncs推荐使用绝对路径.
如果出现'Exception: Could not find a suitable clang-format'提示,可以通过apt安装clang-format进行解决.
修改
源码生成之后,需要修改app_task.cpp中的以下函数:
void AppTask::UpdateClustersState();
void AppTask::UpdateTemperatureClusterState();
void AppTask::UpdatePressureClusterState();
void AppTask::UpdateRelativeHumidityClusterState();
温湿度传感器默认设备是bme680,根据实际情况添加驱动然后修改源码
UpdateClustersState中修改对传感器进行采集部分.源码如下:
const int result = sensor_sample_fetch(sBme688SensorDev);
UpdateTemperatureClusterState中对数据行进转换,合规检测等,然后通过
Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Set(kTemperatureMeasurementEndpointId, newValue);
进行数据的提交.
UpdatePressureClusterState和UpdateRelativeHumidityClusterState也是类似.
可以根据实际情况进行删减.
至此设备端的修改完成.
- 2024-05-15
-
发表了主题帖:
使用Nordic NRF5340+NRF7002开发板尝试matter例程
前期准备
根据nordic官方文档搭建好开发环境.地址:https://nrfconnect.github.io/vscode-nrf-connect/get_started/install.html
下载配置工具chip-tool,地址:https://github.com/nrfconnect/sdk-connectedhomeip/releases
创建例程
看图,通过例子创建工程,分别创建Matter Light Bulb和Matter Light Switch.
编译两个工程,如果没有编译配置,可以通过add build configuration来添加,这个主要是选择相应的板子:
配置好两个工程之后可以通过Build All Configurations来编译所有工程,或者通过对应工程的编译按钮进行分别编译.
分别下载例程到开发板,过程如下图,按序号进行:
配置
配网使用如下命令,我使用的是直接连接路由器,即matter over wifi:
参考链接:https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.1.4/matter/chip_tool_guide.html
./chip-tool-debug pairing ble-wifi <node id> <ssid> <passwd> <pin_code> <discriminator>
node id是自定义的,用于后面节点的绑定,在fabric(网络)中唯一
ssid是wifi名,passwd是wifi密码,这部分也支持hex形式,我直接用的明文
pin_code和discriminator可以查看串口日志,内容大概如下:
I: 428 [DL] Product Name: not-specified
I: 432 [DL] Hardware Version: 0
I: 435 [DL] Setup Pin Code (0 for UNKNOWN/ERROR): 20202021
I: 440 [DL] Setup Discriminator (0xFFFF for UNKNOWN/ERROR): 3840 (0xF00)
I: 446 [DL] Manufacturing Date: 2022-01-01
I: 450 [DL] Device Type: 65535 (0xFFFF)
日志中可以看到Pin Code和Discriminator分别是20202021和3840
配网命令举例:
./chip-tool-debug pairing ble-wifi 0x1233 CU_3cm6 123456ab 20202021 3840
./chip-tool-debug pairing ble-wifi 0x1234 CU_3cm6 123456ab 20202021 3840
这个过程需要按一下板子上的按键B1来开启蓝牙功能,因为配网是借助蓝牙的.
命令运行2次,除了node id不一样,其他都一样,0x1233(4659)是Matter Light Switch,0x1234(4660)是灯Matter Light Bulb
然后是绑定bulb和switch,使用的命令如下:
chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects": [112233], "targets": null}, {"fabricIndex": 1, "privilege": 3, "authMode": 2, "subjects": [<light_switch_node_ID>], "targets": [{"cluster": 6, "endpoint": 1, "deviceType": null}, {"cluster": 8, "endpoint": 1, "deviceType": null}]}]' <light_bulb_node_ID> 0
替换node id之后如下:
./chip-tool-debug accesscontrol write acl '[{"fabricIndex":1,"privilege":5,"authMode":2,"subjects":[112233],"targets":null},{"fabricIndex":1,"privilege":3,"authMode": 2,"subjects":[4659],"targets":[{"cluster":6,"endpoint":1,"deviceType":null},{"cluster":8,"endpoint":1,"deviceType":null}]}]' 0x0000000000001234 0
绑定完成之后应该就可以通过switch来控制bulb了.
测试
按下switch上的B2按钮日志如下:
I: Button has been pressed, keep in this state for at least 500 ms to change light sensitivity of bound lighting devices.
I: Notify Bounded Cluster | endpoint: 1 cluster: 6
I: 4579967 [DIS]Found an existing secure session to [1:0000000000001234]!
D: 4579974 [DIS]OperationalSessionSetup[1:0000000000001234]: State change 1 --> 5
I: 4579982 [EM]<<< [E:44316i S:40176 M:82341530] (S) Msg TX to 1:0000000000001234 [76BE] [UDP:[fe80::16c1:ffff:fe00:fc5f]:5540] --- Type 0001:08 (IM:InvokeCommandRequest)
I: 4580099 [EM]>>> [E:44316i S:40176 M:266680066 (Ack:82341530)] (S) Msg RX from 1:0000000000001234 [76BE] --- Type 0001:09 (IM:InvokeCommandResponse)
I: 4580112 [DMG]Received Command Response Status for Endpoint=1 Cluster=0x0000_0006 Command=0x0000_0002 Status=0x0
D: Binding command applied successfully!
I: 4580127 [EM]<<< [E:44316i S:40176 M:82341531 (Ack:266680066)] (S) Msg TX to 1:0000000000001234 [76BE] [UDP:[fe80::16c1:ffff:fe00:fc5f]:5540] --- Type 0000:10 (SecureChannel:StandaloneAck)
对应的bulb日志如下:
I: 4645373 [EM]>>> [E:44316r S:46234 M:82341530] (S) Msg RX from 1:0000000000001233 [76BE] --- Type 0001:08 (IM:InvokeCommandRequest)
I: 4645385 [ZCL]Toggle ep1 on/off from state 0 to 1
I: 4645390 [ZCL]On Command - OffWaitTime : 0
I: 4645394 [ZCL]On/Toggle Command - Stop Timer
I: 4645399 [ZCL]Cluster OnOff: attribute OnOff set to 1
I: Turn On Action has been initiated
I: Turn On Action has been completed
I: 4645410 [ZCL]Cluster LevelControl: attribute CurrentLevel set to 1
I: 4645418 [EM]<<< [E:44316r S:46234 M:266680066 (Ack:82341530)] (S) Msg TX to 1:0000000000001233 [76BE] [UDP:[fe80::16c1:ffff:fe00:d84d]:5540] --- Type 0001:09 (IM:InvokeCommandResponse)
I: 4645436 [ZCL]Cluster LevelControl: attribute CurrentLevel set to 254
I: Level Action has been initiated
I: Setting brightness level to 254
I: Level Action has been completed
I: 4645495 [EM]>>> [E:44316r S:46234 M:82341531 (Ack:266680066)] (S) Msg RX from 1:0000000000001233 [76BE] --- Type 0000:10 (SecureChannel:StandaloneAck)
此时bulb上的led就会改变状态,如果是亮的状态就会熄灭,如果是熄灭状态就会点亮.
如果重启板子,会自动联网配对,无需再配置.
到此测试完成.
- 2024-05-09
-
回复了主题帖:
入围名单公布:嵌入式工程师AI挑战营(初阶),获RV1106 Linux 板+摄像头的名单
本帖最后由 wakojosin 于 2024-5-9 15:41 编辑
个人信息已确认,领取板卡,可继续完成&分享挑战营第二站和第三站任务。
- 2024-04-28
-
回复了主题帖:
【AI挑战营第二站】算法工程化部署打包成SDK
ONNX是一种开发的模型格式,通过ONNX可以使用不同的机器学习框架进行模型训练,然后导出统一的格式,进而可以被部署到支持ONNX的所有平台.
RKNN是rockchip的模型格式,可以通过他家的toolkit将ONNX转换成RKNN并支持量化、推理、性能和内存评估、量化精度分析以及模型加密
#AI挑战营第二站#ONNX模型转RKNN
-
发表了主题帖:
#AI挑战营第二站#ONNX模型转RKNN
本帖最后由 wakojosin 于 2024-4-28 23:35 编辑
介绍
ONNX是一种开发的模型格式,通过ONNX可以使用不同的机器学习框架进行模型训练,然后导出统一的格式,进而可以被部署到支持ONNX的所有平台.
RKNN是rockchip的模型格式,可以通过他家的toolkit将ONNX转换成RKNN并支持量化、推理、性能和内存评估、量化精度分析以及模型加密,工具位于github,地址为:https://github.com/airockchip/rknn-toolkit2
安装
通过git下载完成rknn-toolkit2之后,即可在目录rknn-toolkit2/packages下看到requirements文件和whl包,通过pip进行相应版本的安装即可.
我用的清华源,如果期间出现以下类似的问题,可以尝试其他的源进行安装.
ERROR: Could not find a version that satisfies the requirement tf-estimator-nightly==2.8.0.dev2021122109 (from tensorflow) (from versions: none)
ERROR: No matching distribution found for tf-estimator-nightly==2.8.0.dev2021122109
临时换源的方式:
pip install tensorflow==2.8.0 -i https://pypi.mirrors.ustc.edu.cn/simple/
我最后是通过https://pypi.doubanio.com/simple完成安装的
安装结果测试:python -c 'from rknn.api import RKNN'
如果不报错就安装成功了.
模型转化
此部分代码可以参考example里面的例子.
def onnx2rknn(onnx_model, rknn_model, dataset=None):
# Create RKNN object
rknn = RKNN(verbose=True)
# pre-process config
print('--> config model')
rknn.config(target_platform='rv1106')
print('done')
# Load model
print('--> Loading model')
ret = rknn.load_onnx(model=onnx_model)
if ret != 0:
print('Load model failed!')
exit(ret)
print('done')
# Build model
print('--> Building model')
ret = rknn.build(do_quantization=True, dataset=dataset)
if ret != 0:
print('Build model failed!')
exit(ret)
print('done')
# Export rknn model
print('--> Export rknn model')
ret = rknn.export_rknn(rknn_model)
if ret != 0:
print('Export rknn model failed!')
exit(ret)
print('done')
rknn.release()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("fonnx", help="onnx file path.")
parser.add_argument("frknn", help="rknn file path.")
parser.add_argument("dataset", help="dataset path.")
args = parser.parse_args()
print("onn_file:", args.fonnx)
print("rknn_file:", args.frknn)
print("dataset:", args.dataset)
onnx2rknn(args.fonnx, args.frknn, args.dataset)
日至信息供参考:
python onnx2rknn.py lenet.onnx lenet.rknn examples/onnx/resnet50v2/dataset.txt luckfoxenv
onn_file: lenet.onnx
rknn_file: lenet.rknn
dataset: examples/onnx/resnet50v2/dataset.txt
W __init__: rknn-toolkit2 version: 1.6.0+81f21f4d
--> config model
done
--> Loading model
W load_onnx: It is recommended onnx opset 19, but your onnx model opset is 17!
W load_onnx: Model converted from pytorch, 'opset_version' should be set 19 in torch.onnx.export for successful convert!
Loading : 100%|██████████████████████████████████████████████████| 10/10 [00:00<00:00, 19239.93it/s]
W load_onnx: The config.mean_values is None, zeros will be set for input 0!
W load_onnx: The config.std_values is None, ones will be set for input 0!
done
--> Building model
I base_optimize ...
I base_optimize done.
I
I fold_constant ...
I fold_constant done.
I
I correct_ops ...
I correct_ops done.
I
I fuse_ops ...
I fuse_ops results:
I replace_flatten_gemm_by_conv: remove node = ['/6/Flatten', '/7/Gemm'], add node = ['/7/Gemm_2conv', '/7/Gemm_2conv_reshape']
I swap_reshape_relu: remove node = ['/7/Gemm_2conv_reshape', '/8/Relu'], add node = ['/8/Relu', '/7/Gemm_2conv_reshape']
I convert_gemm_by_conv: remove node = ['/9/Gemm'], add node = ['/9/Gemm_2conv_reshape1', '/9/Gemm_2conv', '/9/Gemm_2conv_reshape2']
I unsqueeze_to_4d_relu: remove node = [], add node = ['/10/Relu_0_unsqueeze0', '/10/Relu_0_unsqueeze1']
I convert_gemm_by_conv: remove node = ['/11/Gemm'], add node = ['/11/Gemm_2conv_reshape1', '/11/Gemm_2conv', '/11/Gemm_2conv_reshape2']
I fuse_two_reshape: remove node = ['/7/Gemm_2conv_reshape', '/9/Gemm_2conv_reshape2', '/10/Relu_0_unsqueeze1']
I remove_invalid_reshape: remove node = ['/9/Gemm_2conv_reshape1', '/10/Relu_0_unsqueeze0', '/11/Gemm_2conv_reshape1']
I fold_constant ...
I fold_constant done.
I fuse_ops done.
I
I sparse_weight ...
I sparse_weight done.
I
GraphPreparing : 100%|████████████████████████████████████████████| 12/12 [00:00<00:00, 3997.75it/s]
Quantizating : 100%|███████████████████████████████████████████████| 12/12 [00:00<00:00, 569.73it/s]
I
I quant_optimizer ...
I quant_optimizer results:
I adjust_relu: ['/10/Relu', '/8/Relu', '/4/Relu', '/1/Relu']
I quant_optimizer done.
I
W build: The default input dtype of 'input.1' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
W build: The default output dtype of '22' is changed from 'float32' to 'int8' in rknn model for performance!
Please take care of this change when deploy rknn model with Runtime API!
I rknn building ...
I RKNN: [23:30:53.007] compress = 0, conv_eltwise_activation_fuse = 1, global_fuse = 1, multi-core-model-mode = 7, output_optimize = 1,enable_argb_group=0 ,layout_match = 1, pipeline_fuse = 0
I RKNN: librknnc version: 1.6.0 (585b3edcf@2023-12-11T07:56:14)
D RKNN: [23:30:53.008] RKNN is invoked
D RKNN: [23:30:53.014] >>>>>> start: rknn::RKNNExtractCustomOpAttrs
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNExtractCustomOpAttrs
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNSetOpTargetPass
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNSetOpTargetPass
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNBindNorm
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNBindNorm
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNAddFirstConv
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNAddFirstConv
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNEliminateQATDataConvert
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNEliminateQATDataConvert
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNTileGroupConv
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNTileGroupConv
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNTileFcBatchFuse
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNTileFcBatchFuse
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNAddConvBias
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNAddConvBias
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNTileChannel
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNTileChannel
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNPerChannelPrep
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNPerChannelPrep
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNBnQuant
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNBnQuant
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNFuseOptimizerPass
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNFuseOptimizerPass
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNTurnAutoPad
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNTurnAutoPad
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNInitRNNConst
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNInitRNNConst
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNInitCastConst
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNInitCastConst
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNMultiSurfacePass
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNMultiSurfacePass
D RKNN: [23:30:53.015] >>>>>> start: rknn::RKNNReplaceConstantTensorPass
D RKNN: [23:30:53.015] <<<<<<<< end: rknn::RKNNReplaceConstantTensorPass
D RKNN: [23:30:53.015] >>>>>> start: OpEmit
D RKNN: [23:30:53.016] <<<<<<<< end: OpEmit
D RKNN: [23:30:53.016] >>>>>> start: rknn::RKNNLayoutMatchPass
I RKNN: [23:30:53.016] AppointLayout: t->setNativeLayout(64), tname:[input.1]
I RKNN: [23:30:53.016] AppointLayout: t->setNativeLayout(64), tname:[/1/Relu_output_0]
I RKNN: [23:30:53.016] AppointLayout: t->setNativeLayout(64), tname:[/2/MaxPool_output_0]
I RKNN: [23:30:53.016] AppointLayout: t->setNativeLayout(64), tname:[/4/Relu_output_0]
I RKNN: [23:30:53.016] AppointLayout: t->setNativeLayout(64), tname:[/5/MaxPool_output_0]
I RKNN: [23:30:53.016] AppointLayout: t->setNativeLayout(64), tname:[/8/Relu_output_0_before]
I RKNN: [23:30:53.016] AppointLayout: t->setNativeLayout(64), tname:[/10/Relu_output_0_shape4]
I RKNN: [23:30:53.016] AppointLayout: t->setNativeLayout(64), tname:[22_conv]
D RKNN: [23:30:53.016] <<<<<<<< end: rknn::RKNNLayoutMatchPass
D RKNN: [23:30:53.016] >>>>>> start: rknn::RKNNAddSecondaryNode
D RKNN: [23:30:53.016] <<<<<<<< end: rknn::RKNNAddSecondaryNode
D RKNN: [23:30:53.016] >>>>>> start: OpEmit
D RKNN: [23:30:53.016] finish initComputeZoneMap
D RKNN: [23:30:53.016] <<<<<<<< end: OpEmit
D RKNN: [23:30:53.016] >>>>>> start: rknn::RKNNProfileAnalysisPass
D RKNN: [23:30:53.016] node: Reshape:/11/Gemm_2conv_reshape2, Target: NPU
D RKNN: [23:30:53.016] <<<<<<<< end: rknn::RKNNProfileAnalysisPass
D RKNN: [23:30:53.016] >>>>>> start: rknn::RKNNOperatorIdGenPass
D RKNN: [23:30:53.016] <<<<<<<< end: rknn::RKNNOperatorIdGenPass
D RKNN: [23:30:53.016] >>>>>> start: rknn::RKNNWeightTransposePass
W RKNN: [23:30:53.019] Warning: Tensor /11/Gemm_2conv_reshape2_shape need paramter qtype, type is set to float16 by default!
W RKNN: [23:30:53.019] Warning: Tensor /11/Gemm_2conv_reshape2_shape need paramter qtype, type is set to float16 by default!
D RKNN: [23:30:53.019] <<<<<<<< end: rknn::RKNNWeightTransposePass
D RKNN: [23:30:53.019] >>>>>> start: rknn::RKNNCPUWeightTransposePass
D RKNN: [23:30:53.019] <<<<<<<< end: rknn::RKNNCPUWeightTransposePass
D RKNN: [23:30:53.019] >>>>>> start: rknn::RKNNModelBuildPass
D RKNN: [23:30:53.028] RKNNModelBuildPass: [Statistics]
D RKNN: [23:30:53.028] total_regcfg_size : 6216
D RKNN: [23:30:53.028] total_diff_regcfg_size: 6256
D RKNN: [23:30:53.028] <<<<<<<< end: rknn::RKNNModelBuildPass
D RKNN: [23:30:53.028] >>>>>> start: rknn::RKNNModelRegCmdbuildPass
D RKNN: [23:30:53.028] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
D RKNN: [23:30:53.028] Network Layer Information Table
D RKNN: [23:30:53.028] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
D RKNN: [23:30:53.028] ID OpType DataType Target InputShape OutputShape DDRCycles NPUCycles MaxCycles TaskNumber RW(KB) FullName
D RKNN: [23:30:53.028] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
D RKNN: [23:30:53.028] 0 InputOperator INT8 CPU \ (1,1,32,32) 0 0 0 0/0 0 InputOperator:input.1
D RKNN: [23:30:53.028] 1 ConvRelu INT8 NPU (1,1,32,32),(6,1,5,5),(6) (1,6,28,28) 2321 19600 19600 1/0 1 Conv:/0/Conv
D RKNN: [23:30:53.028] 2 MaxPool INT8 NPU (1,6,28,28) (1,6,14,14) 2546 0 2546 1/0 12 MaxPool:/2/MaxPool
D RKNN: [23:30:53.028] 3 ConvRelu INT8 NPU (1,6,14,14),(16,6,5,5),(16) (1,16,10,10) 1829 2800 2800 1/0 9 Conv:/3/Conv
D RKNN: [23:30:53.028] 4 MaxPool INT8 NPU (1,16,10,10) (1,16,5,5) 325 0 325 1/0 1 MaxPool:/5/MaxPool
D RKNN: [23:30:53.028] 5 ConvRelu INT8 NPU (1,16,5,5),(120,16,5,5),(120) (1,120,1,1) 8045 3200 8045 1/0 48 Conv:/7/Gemm_2conv
D RKNN: [23:30:53.028] 6 ConvRelu INT8 NPU (1,120,1,1),(84,120,1,1),(84) (1,84,1,1) 1798 384 1798 1/0 10 Conv:/9/Gemm_2conv
D RKNN: [23:30:53.028] 7 Conv INT8 NPU (1,84,1,1),(10,84,1,1),(10) (1,10,1,1) 195 64 195 1/0 1 Conv:/11/Gemm_2conv
D RKNN: [23:30:53.028] 8 Reshape INT8 NPU (1,10,1,1),(2) (1,10) 7 0 7 1/0 0 Reshape:/11/Gemm_2conv_reshape2
D RKNN: [23:30:53.028] 9 OutputOperator INT8 CPU (1,10) \ 0 0 0 0/0 0 OutputOperator:22
D RKNN: [23:30:53.028] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
D RKNN: [23:30:53.028] <<<<<<<< end: rknn::RKNNModelRegCmdbuildPass
D RKNN: [23:30:53.028] >>>>>> start: rknn::RKNNFlatcModelBuildPass
D RKNN: [23:30:53.029] Export Mini RKNN model to /tmp/tmpujiow0wl/dumps/main_graph.mini.rknn
D RKNN: [23:30:53.029] >>>>>> end: rknn::RKNNFlatcModelBuildPass
D RKNN: [23:30:53.029] >>>>>> start: rknn::RKNNMemStatisticsPass
D RKNN: [23:30:53.029] ---------------------------------------------------------------------------------------------------------------------------------
D RKNN: [23:30:53.029] Feature Tensor Information Table
D RKNN: [23:30:53.029] -----------------------------------------------------------------------------------------------+---------------------------------
D RKNN: [23:30:53.029] ID User Tensor DataType DataFormat OrigShape NativeShape | [Start End) Size
D RKNN: [23:30:53.029] -----------------------------------------------------------------------------------------------+---------------------------------
D RKNN: [23:30:53.029] 1 ConvRelu input.1 INT8 NC1HWC2 (1,1,32,32) (1,1,32,32,1) | 0x00012880 0x00012c80 0x00000400
D RKNN: [23:30:53.029] 2 MaxPool /1/Relu_output_0 INT8 NC1HWC2 (1,6,28,28) (1,1,28,28,16) | 0x00012c80 0x00015d80 0x00003100
D RKNN: [23:30:53.029] 3 ConvRelu /2/MaxPool_output_0 INT8 NC1HWC2 (1,6,14,14) (1,1,14,14,16) | 0x00015d80 0x000169c0 0x00000c40
D RKNN: [23:30:53.029] 4 MaxPool /4/Relu_output_0 INT8 NC1HWC2 (1,16,10,10) (1,1,10,10,16) | 0x00012880 0x00012ec0 0x00000640
D RKNN: [23:30:53.029] 5 ConvRelu /5/MaxPool_output_0 INT8 NC1HWC2 (1,16,5,5) (1,1,5,5,16) | 0x00012ec0 0x00013080 0x000001c0
D RKNN: [23:30:53.029] 6 ConvRelu /8/Relu_output_0_before INT8 NC1HWC2 (1,120,1,1) (1,8,1,1,16) | 0x00012880 0x00012900 0x00000080
D RKNN: [23:30:53.029] 7 Conv /10/Relu_output_0_shape4 INT8 NC1HWC2 (1,84,1,1) (1,6,1,1,16) | 0x00012900 0x00012960 0x00000060
D RKNN: [23:30:53.029] 8 Reshape 22_conv INT8 NC1HWC2 (1,10,1,1) (1,1,1,1,16) | 0x00012880 0x00012890 0x00000010
D RKNN: [23:30:53.029] 9 OutputOperator 22 INT8 UNDEFINED (1,10) (1,10) | 0x00012900 0x00012940 0x00000040
D RKNN: [23:30:53.029] -----------------------------------------------------------------------------------------------+---------------------------------
D RKNN: [23:30:53.029] ----------------------------------------------------------------------------------------------------
D RKNN: [23:30:53.029] Const Tensor Information Table
D RKNN: [23:30:53.029] ------------------------------------------------------------------+---------------------------------
D RKNN: [23:30:53.029] ID User Tensor DataType OrigShape | [Start End) Size
D RKNN: [23:30:53.029] ------------------------------------------------------------------+---------------------------------
D RKNN: [23:30:53.029] 1 ConvRelu 0.weight INT8 (6,1,5,5) | 0x00000000 0x00000280 0x00000280
D RKNN: [23:30:53.029] 1 ConvRelu 0.bias INT32 (6) | 0x00000280 0x00000300 0x00000080
D RKNN: [23:30:53.029] 3 ConvRelu 3.weight INT8 (16,6,5,5) | 0x00000300 0x00001c00 0x00001900
D RKNN: [23:30:53.029] 3 ConvRelu 3.bias INT32 (16) | 0x00001c00 0x00001c80 0x00000080
D RKNN: [23:30:53.029] 5 ConvRelu 7.weight INT8 (120,16,5,5) | 0x00001c80 0x0000d800 0x0000bb80
D RKNN: [23:30:53.029] 5 ConvRelu 7.bias INT32 (120) | 0x0000d800 0x0000dc00 0x00000400
D RKNN: [23:30:53.029] 6 ConvRelu 9.weight INT8 (84,120,1,1) | 0x0000dc00 0x00010600 0x00002a00
D RKNN: [23:30:53.029] 6 ConvRelu 9.bias INT32 (84) | 0x00010600 0x00010900 0x00000300
D RKNN: [23:30:53.029] 7 Conv 11.weight INT8 (10,84,1,1) | 0x00010900 0x00010cc0 0x000003c0
D RKNN: [23:30:53.029] 7 Conv 11.bias INT32 (10) | 0x00010cc0 0x00010d40 0x00000080
D RKNN: [23:30:53.029] 8 Reshape /11/Gemm_2conv_reshape2_shape INT64 (2) | 0x00010d40*0x00010d80 0x00000040
D RKNN: [23:30:53.029] ------------------------------------------------------------------+---------------------------------
D RKNN: [23:30:53.029] ----------------------------------------
D RKNN: [23:30:53.029] Total Internal Memory Size: 16.3125KB
D RKNN: [23:30:53.029] Total Weight Memory Size: 67.375KB
D RKNN: [23:30:53.029] ----------------------------------------
D RKNN: [23:30:53.029] <<<<<<<< end: rknn::RKNNMemStatisticsPass
I rknn buiding done.
done
--> Export rknn model
done
- 2024-04-15
-
回复了主题帖:
免费申请:幸狐 RV1106 Linux 开发板(带摄像头),助力AI挑战营应用落地
#AI挑战营第一站#基于pytorch模型mnist数据及训练测试 https://bbs.eeworld.com.cn/thread-1277786-1-1.html
预期应用:实现数字的识别,实现基于数字的EAN13条码识别与验证.
-
回复了主题帖:
【AI挑战营第一站】模型训练:在PC上完成手写数字模型训练,免费申请RV1106开发板
1、跟帖回复:用自己的语言描述,模型训练的本质是什么,训练最终结果是什么
训练本质是训练拟合函数的参数,使模型尽可能符合我们的应用需求.训练的结果是模型的参数信息.
2、跟帖回复:PyTorch是什么?目前都支持哪些系统和计算平台?
pytorch是开源的机器学习库.
支持的系统:Windows,macOS,Linux(各种发行版,如Ubuntu,Debian,CentOS等).
硬件方面分CPU和GPU版本.平台有服务器端,移动端,嵌入式端.
3、动手实践:#AI挑战营第一站#基于pytorch模型mnist数据及训练测试 https://bbs.eeworld.com.cn/thread-1277786-1-1.html
-
发表了主题帖:
#AI挑战营第一站#基于pytorch模型mnist数据及训练测试
本帖最后由 wakojosin 于 2024-4-15 20:39 编辑
环境
数据准备
data_train = MNIST('./data/mnist',
download=True,
transform=transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor()]))
data_test = MNIST('./data/mnist',
train=False,
download=True,
transform=transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor()]))
data_train_loader = DataLoader(data_train, batch_size=256, shuffle=True, num_workers=8)
data_test_loader = DataLoader(data_test, batch_size=1024, num_workers=8)
LeNet模型构建
'''
卷积神将网络的计算公式为:
N=(W-F+2P)/S+1
其中
N:输出大小
W:输入大小
F:卷积核大小
P:填充值的大小
S:步长大小
'''
net = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=6, kernel_size=(5,5)), #卷积层提特征,6*28*28
nn.Sigmoid(), #激活函数:多项式提供非线性
nn.AvgPool2d(kernel_size=(5,5), stride=2), #下采样,(28-5+2*0)/2+1=>6*12*12
nn.Conv2d(in_channels=6, out_channels=16, kernel_size=(5,5)),#16*8*8
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=(5,5), stride=2),#(8-5+2*0)/2+1=>16*2*2
nn.Flatten(),
nn.Linear(16*2*2, 120), nn.Sigmoid(),#全连接:固定特征维度,激活函数:提供非线性网络
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10)
)
训练
def train(epoch):
net.train() #训练模式
loss_list, batch_list = [], []
for i, (images, labels) in enumerate(data_train_loader):
optimizer.zero_grad()
output = net(images) #喂数据
loss = criterion(output, labels) #计算loss
loss_list.append(loss.detach().cpu().item())
batch_list.append(i+1)
loss.backward() #返传
optimizer.step() #优化器参数迭代
模型验证及转换
def test(epoch):
net.eval() #测试模式
total_correct = 0
avg_loss = 0.0
for i, (images, labels) in enumerate(data_test_loader):
output = net(images)
avg_loss += criterion(output, labels).sum()
pred = output.detach().max(1)[1]
total_correct += pred.eq(labels.view_as(pred)).sum()
avg_loss /= len(data_test)
print('Test Avg. Loss: %f, Accuracy: %f' % (avg_loss.detach().cpu().item(), float(total_correct) / len(data_test)))
torch.save(net.state_dict(), "pth/epoch{}_accuracy{}.pth".format(epoch, float(total_correct) / len(data_test)))
dummy_input = torch.randn(1, 1, 32, 32, requires_grad=True) #batch_size=1,channels=1,w*h=32*32
torch.onnx.export(net, dummy_input, "lenet.onnx") #先推理后根据tensor把模型图变成静态
onnx_model = onnx.load("lenet.onnx")
onnx.checker.check_model(onnx_model) #检查onnx模型
验证输出结果: Test Avg. Loss: 0.000170, Accuracy: 0.945300
模型图
小结
基于经典的LeNet进行MNIST数据集训练及验证,学习了整体流程.
模型见附件.
- 2024-04-01
-
回复了主题帖:
Zephyr RTOS 简介 —— 来自 Linux 基金会的 IoT 操作系统
iot-fans.xyz这个的文章好像没了,点不进去
- 2024-03-26
-
回复了主题帖:
KW41Z板卡-openthread的udp通讯
freebsder 发表于 2024-3-26 15:51
KW41Z 应该不是nxp主推的了吧
nxp对这块的推广力度感觉不大
-
回复了主题帖:
KW41Z板卡-openthread的udp通讯
Jacktang 发表于 2024-3-25 07:48
KW41Z开发板和nrf52840开发板,都跑openthread协议栈进行的实验测试后情况如何
可以正常组网
- 2024-03-24
-
发表了主题帖:
KW41Z板卡-openthread的udp通讯
本帖最后由 wakojosin 于 2024-3-24 11:54 编辑
测试环境
使用的硬件是KW41Z开发板和nrf52840开发板,两者都跑openthread协议栈进行下面的实验。
固件方面两者烧写的是各自的ftd-cli工程。
下面测试过程用NRF代表NRF52840,NXP代表KW41Z。
测试过程
启动接口。
NRF:
> ifconfig up
Done
> thread start
Done
>
> state
leader
Done
>
NXP:
> eui64
1bf3040000000000
Done
> ifconfig up
Done
>
入网,此时使用NRF作为leader设备,NXP作为节点(会自动升级为rooter)。
NRF:
> commissioner start
Commissioner: petitioning
Done
> Commissioner: active
commissioner joiner add 1bf3040000000000 PPSSKK
Done
> ~ Discovery Request from 12954efa1b74f63f: version=4,joiner=1
Commissioner: Joiner start 5795cc01d9ef484e
Commissioner: Joiner connect 5795cc01d9ef484e
=========[[THCI] direction=recv | type=JOIN_FIN.req | len=048]==========
| 10 01 01 21 0A 4F 50 45 | 4E 54 48 52 45 41 44 22 | ...!.OPENTHREAD"
| 05 4B 57 34 31 5A 23 10 | 74 68 72 65 61 64 2D 72 | .KW41Z#.thread-r
| 65 66 65 72 65 6E 63 65 | 25 06 18 B4 30 00 00 10 | eference%..40...
------------------------------------------------------------------------
=========[[THCI] direction=send | type=JOIN_FIN.rsp | len=003]==========
| 10 01 01 .. .. .. .. .. | .. .. .. .. .. .. .. .. | ................
------------------------------------------------------------------------
Commissioner: Joiner finalize 5795cc01d9ef484e
-MESH-CP-: [THCI] direction=send | type=JOIN_ENT.ntf
-MESH-CP-: [THCI] direction=recv | type=JOIN_ENT.rsp
Commissioner: Joiner end 5795cc01d9ef484e
Commissioner: Joiner remove 5795cc01d9ef484e
>
> ipaddr
fd9f:129:4246:a051:0:ff:fe00:fc31
fd9f:129:4246:a051:0:ff:fe00:fc00
fd9f:129:4246:a051:0:ff:fe00:2400
fd9f:129:4246:a051:b860:2a7f:75dd:e101
fe80:0:0:0:28f0:3f23:5560:65b0
Done
NXP:
> joiner start PPSSKK
Done
> Join success
>
> thread start
Done
>
> state
child
Done
>
> ipaddr
fd9f:129:4246:a051:0:ff:fe00:2401
fd9f:129:4246:a051:104e:6c93:d338:7114
fe80:0:0:0:4c86:1466:c96e:a32c
Done
UDP测试
NRF:
> udp open
Done
> udp bind :: 1234
Done
> udp send fd9f:129:4246:a051:104e:6c93:d338:7114 2345 hello41z
Done
> 10 bytes from fd9f:129:4246:a051:104e:6c93:d338:7114 2345 hello52840
NXP:
> udp open
Done
> udp bind :: 2345
Done
> 8 bytes from fd9f:129:4246:a051:b860:2a7f:75dd:e101 1234 hello41z
> udp send fd9f:129:4246:a051:b860:2a7f:75dd:e101 1234 hello52840
Done
>
UDP命令分析
路径位于:ot-kw41z/openthread/src/cli/cli_udp.cpp
udp open
//这个命令主要是调用otUdpOpen,HandleUdpReceive函数用来处理接收到的数据
otUdpOpen(GetInstancePtr(), &mSocket, HandleUdpReceive, this);
udp bind
//这个命令主要是调用otUdpBind来开启端口监听
otUdpBind(GetInstancePtr(), &mSocket, &sockaddr, netif);
udp send
//这个命令主要是构建一个message,将数据写进message中,然后通过otUdpSend发送
message = otUdpNewMessage(GetInstancePtr(), &messageSettings);
otMessageAppend(message, aArgs[0].GetCString(), aArgs[0].GetLength());
otUdpSend(GetInstancePtr(), &mSocket, message, &messageInfo);
udp connect:调用otUdpConnect来与指定主机建立连接。
udp close:调用otUdpClose来关闭UDP客户端。
写自己的demo
在ot-kw41z/openthread/examples/apps路径下,复制cli,重命名为demo,然后修改此目录下的CMakeLists.txt,前后内容如下
修改前:
if(OT_APP_CLI)
add_subdirectory(cli)
endif()
add_subdirectory(ncp)
修改后:
if(OT_APP_CLI)
add_subdirectory(cli)
add_subdirectory(demo)
endif()
add_subdirectory(ncp)
增加demo.c,主要代码如下:
static otMessage *mMessage = NULL;
static otMessageInfo mMessageInfo;
static otUdpSocket mSocket;
void DemoProcess(otInstance *instance)
{
if(NULL == instance)
{
return;
}
if(!otUdpIsOpen(instance, &mSocket))
{
return;
}
otMessage *message = mMessage;
if(message != NULL)
{
mMessage = NULL;
if(OT_ERROR_NONE != otUdpSend(instance, &mSocket, message, &(mMessageInfo)))
{
DBG_INFO_OUT("send message failed!\n");
otMessageFree(message);
}
}
}
bool DemoMessageSet(otMessage *message, const otMessageInfo *info)
{
if(mMessage == NULL)
{
mMessage = message;
memcpy(&mMessageInfo.mPeerAddr, &(info->mPeerAddr), sizeof(mMessageInfo.mPeerAddr));
mMessageInfo.mPeerPort = info->mPeerPort;
return true;
}
else
{
return false;
}
}
static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
char buf[1500];
int length;
bool isSecurity = otMessageIsLinkSecurityEnabled(aMessage);
DBG_INFO_OUT("%d bytes from ", otMessageGetLength(aMessage) - otMessageGetOffset(aMessage));
otIp6AddressToString(&aMessageInfo->mPeerAddr, buf, sizeof(buf));
DBG_INFO_OUT("%s:%d (%d) ", buf, aMessageInfo->mPeerPort, isSecurity);
length = otMessageRead(aMessage, otMessageGetOffset(aMessage), buf, sizeof(buf) - 1);
buf[length] = '\0';
DBG_INFO_OUT("%s\n", buf);
// create message
otInstance *instance = (otInstance *)aContext;
if(instance)
{
otMessage *message = otUdpNewMessage(instance, NULL);
if(message)
{
if(OT_ERROR_NONE == otMessageAppend(message, buf, length))
{
if(DemoMessageSet(message, aMessageInfo))
{
message = NULL;
}
}
if(message)
{
otMessageFree(message);
}
}
}
}
void DemoInit(otInstance *instance)
{
otSockAddr sockaddr;
if(instance== NULL)
{
return ;
}
if(otUdpIsOpen(instance, &mSocket))
{
return ;
}
memset(&mSocket, 0, sizeof(mSocket));
if(OT_ERROR_NONE != otUdpOpen(instance, &mSocket, HandleUdpReceive, instance))
{
return ;
}
memset(&sockaddr, 0, sizeof(sockaddr));
otIp6AddressFromString("::", &sockaddr.mAddress);
sockaddr.mPort = 12345;
if(OT_ERROR_NONE != otUdpBind(instance, &mSocket, &sockaddr, OT_NETIF_THREAD))
{
return ;
}
}
代码实现的是echo功能。
- 2024-03-15
-
发表了主题帖:
KW41Z板卡Openthread源码学习(二)
前言
本篇主要从main函数入手分析整个执行过程。主要就是整理了调用过程,带有简单的注释,协议栈没有深入,深入内容有点多。
源码整理
主函数
/* openthread/example/apps/cli/main.c */
int main(int argc, char *argv[])
{
otInstance *instance;
OT_SETUP_RESET_JUMP(argv);
pseudo_reset:
// 调用src/system.c中的otSysInit函数来初始化引脚、时钟等
otSysInit(argc, argv);
// 单例模式得到openthread对象
instance = otInstanceInitSingle();
assert(instance);
// 调用openthread/examples/apps/cli/cli_uart.cpp中的
// extern "C" void otAppCliInit(otInstance *aInstance)
// 间接调用了src/uart.c中的otPlatUartEnable()
// 以及openthread/src/cli/cli.cpp中的
// extern "C" void otCliInit(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext)
// 实例化了一个解释器对象。
otAppCliInit(instance);
// 向解释器注册命令
IgnoreError(otCliSetUserCommands(kCommands, OT_ARRAY_LENGTH(kCommands), instance));
// 如果不发生关机和重新初始化的情况就一直进行这个循环
while (!otSysPseudoResetWasRequested())
{
// 间接调用openthread/src/core/common/tasklet.cpp中的
// void Tasklet::Scheduler::ProcessQueuedTasklets(void)
// 用于处理注册的任务
otTaskletsProcess(instance);
// 间接调用src/system.c中的void otSysProcessDrivers(otInstance *aInstance)
// 用于处理来自硬件的请求,比如串口、射频、定时器等。
otSysProcessDrivers(instance);
}
otInstanceFinalize(instance);
goto pseudo_reset;
return 0;
}
openthread/src/core/common/tasklet.hpp
这部分是整个openthread的核心,支撑起了其他模块的正常运行,当模块发生事件后就会将任务挂到调度器中等待被调度。
/*任务类定义,后面会还有上下文的任务和特定类型的任务模块,不展开*/
class Tasklet : public InstanceLocator
{
public:
/**
* 任务调度器类
*
*/
class Scheduler : private NonCopyable
{
friend class Tasklet;
public:
/**
* 调度器初始化
*
*/
Scheduler(void)
: mTail(nullptr)
{
}
/**
* 只是是否有任务挂起.
*
* @retval TRUE If there are tasklets pending.
* @retval FALSE If there are no tasklets pending.
*
*/
bool AreTaskletsPending(void) const { return mTail != nullptr; }
/**
* 处理所有运行队列中的任务。
*
*/
void ProcessQueuedTasklets(void);
private:
void PostTasklet(Tasklet &aTasklet);
Tasklet *mTail; // A circular singly linked-list
};
/**
* 任务回调函数定义.
*
* @param[in] aTasklet A reference to the tasklet being run.
*
*/
typedef void (&Handler)(Tasklet &aTasklet);
/**
* 任务构造函数(任务对象创建),初始化内部成员属性.
*
* @param[in] aInstance A reference to the OpenThread instance object.
* @param[in] aHandler A pointer to a function that is called when the tasklet is run.
*
*/
Tasklet(Instance &aInstance, Handler aHandler)
: InstanceLocator(aInstance)
, mHandler(aHandler)
, mNext(nullptr)
{
}
/**
* 将任务放到任务列表中。
*
* If the tasklet is already posted, no change is made and run queue stays as before.
*
*/
void Post(void);
/**
* 指示任务是否注册到任务列表中.
*
* @retval TRUE The tasklet is posted.
* @retval FALSE The tasklet is not posted.
*
*/
bool IsPosted(void) const { return (mNext != nullptr); }
private:
void RunTask(void) { mHandler(*this); }
Handler mHandler;
Tasklet *mNext;
};
命令处理
// 串口命令处理过程
void kw41zUartProcess(void);
static void processReceive(void);
void otPlatUartReceived(const uint8_t *aBuf, uint16_t aBufLength);
static void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength);
ProcessCommand();
// 承接上面
static otError ProcessCommand(void);
extern "C" void otCliInputLine(char *aBuf);
void Interpreter::ProcessLine(char *aBuf);
// 解析字符串,获取命令
SuccessOrExit(error = Utils::CmdLineParser::ParseCmd(aBuf, args, kMaxArgs));
// 执行命令,内部定义了任务列表kCommands
// 任务列表包含了命令及其解释器
// 所有命令的执行在此完成
ProcessCommand(args);
射频
// 射频接收处理过程
void kw41zRadioProcess(otInstance *aInstance);
extern "C" void otPlatRadioReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError);
void Radio::Callbacks::HandleReceiveDone(Mac::RxFrame *aFrame, Error aError);
void SubMac::HandleReceiveDone(RxFrame *aFrame, Error aError);
void SubMac::Callbacks::ReceiveDone(RxFrame *aFrame, Error aError);
Get<LinkRaw>().InvokeReceiveDone(aFrame, aError);
Get<Mac>().HandleReceivedFrame(aFrame, aError);
// 承上Get<LinkRaw>().InvokeReceiveDone(aFrame, aError);
void LinkRaw::InvokeReceiveDone(RxFrame *aFrame, Error aError);
void NcpBase::LinkRawReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError);
otError NcpBase::PackRadioFrame(otRadioFrame *aFrame, otError aError);
// 承上Get<Mac>().HandleReceivedFrame(aFrame, aError);
void Mac::HandleReceivedFrame(RxFrame *aFrame, Error aError);
// 后面就是对数据的解析处理,根据数据类型执行不同的操作
// 展开就是协议栈的实现了,所以不深入。
小结
源码整理了命令处理和射频处理,基本上还是比较清楚的,接下来先实现一个功能实时效果吧。