- 2025-01-07
-
回复了主题帖:
【极海APM32M3514电机通用评估板】外设篇(求助ADC)
已解决。
为什么会发生循环执行无法退出ADC中断的情况,是因为ADC中断处理函数 的执行时间 大于 ADC触发周期。因此产生了退不出中断的错觉。
- 2025-01-06
-
回复了主题帖:
【极海APM32M3514电机通用评估板】ADC电流采样 公式推理与验证
破案了,采样的UI是被SUM抬高了的,所以,在运放的反向输入端接SUM也就对应了 减去SUM偏置。所以 求ADC实际采样值,就等效了情况二,直接使用情况二的公式即可。
- 2024-12-26
-
发表了主题帖:
【极海APM32M3514电机通用评估板】ADC电流采样 公式推理与验证
本帖最后由 不语arc 于 2024-12-26 11:39 编辑
1.背景
在上一篇分享中,我提到了一个问题:
为什么U、V两相的运放反相输入端为什么是实际的SUM?官方给出的计算公式,是按照反相输入端接地计算得到的,虽然SUM值不算大。
在这里我对两种情况分别讨论,用实验结果来验证。
2.分组实验
2.1 情况一:U、V两相的运放反相输入端是总电流SUM
重新推理公式,虚短和虚断:
计算得:
因此:其中,Uo为运放输出值,也就是ADC实际采样的值,Uin为采样电阻两端的电压值。
由公式修改ADC采样函数
typedef struct
{
float Ia; // Phase A current
float Ib; // Phase B current
float Ic; // Phase C current
float Ibus;
float Vbus;
float VHandle; //0-3.3的范围
uint16_t ADC_Ia; // Phase A Voltage(I*R)
uint16_t ADC_Ib; // Phase B Voltage
uint16_t ADC_Ibus; // SUM Voltage
uint16_t ADC_Ia_offset; // 电流零点偏置
uint16_t ADC_Ib_offset; // 电流零点偏置
uint16_t ADC_Ibus_offset; // 电流零点偏置
uint16_t ADC_Vbus; // Phase bus Voltage
uint16_t ADC_Handle;
} CURRENT_Def;
#define ADC_V_K 0.0008 // 3.3/4096.0 // ADC 与电压转换系数
#define VREF 1.65 //参考电压偏置
#define Voffset 1.605 //偏置电压大小
#define AM_GAIN 4.86 //运放放大倍数
#define R_SENSE 0.02 //Ω 采样电阻
#define V_GAIN 21 //(100+100+10)/10
void GetADCvaule(){
float SUM;
current.ADC_Ia = (int16_t)ADC_GetValue(CURR_CHANNEL_U);
current.ADC_Ib = (int16_t)ADC_GetValue(CURR_CHANNEL_V);
current.ADC_Ibus = (int16_t)ADC_GetValue(IBUS_CHANNEL);
current.Ibus = (ADC_V_K*(current.ADC_Ibus-current.ADC_Ibus_offset) - Voffset) / (AM_GAIN * R_SENSE);
SUM = current.Ibus * R_SENSE;
current.Ia = (ADC_V_K*(current.ADC_Ia-current.ADC_Ia_offset) - Voffset + 4.84*SUM) / (AM_GAIN * R_SENSE);
current.Ib = (ADC_V_K*(current.ADC_Ib-current.ADC_Ib_offset) - Voffset + 4.84*SUM) / (AM_GAIN * R_SENSE);
// current.Ia = (ADC_V_K*(current.ADC_Ia-current.ADC_Ia_offset) - Voffset) / (AM_GAIN * R_SENSE);
// current.Ib = (ADC_V_K*(current.ADC_Ib-current.ADC_Ib_offset) - Voffset) / (AM_GAIN * R_SENSE);
current.Ic = current.Ibus - current.Ia - current.Ib;
current.ADC_Vbus = (int16_t)ADC_GetValue(VDC_CHANNEL);
current.Vbus = ADC_V_K*(current.ADC_Vbus) * V_GAIN;
current.ADC_Handle = (int16_t)ADC_GetValue(Handle_CHANNEL);
current.VHandle = ADC_V_K*(current.ADC_Handle); //0-3.3的范围
}
对电机进行foc控制,将ADC电流波形通过串口打印出来。测试了几组,稳定时波形差不多是这样。
2.2 情况二:U、V两相的运放反相输入端是GND
计算公式如下:
实际执行上述代码的注释部分,求Ia和Ib。
再次实验采样三相电流,打印输出。波形差不多。
3.结论
SUM为Ibus*0.02,由于当前的转速较慢,SUM值较小,未能根据波形判断出反相端 接入情况。后续将继续调试完成高转速下的波形判断,当Ibus电流变大,两种接入情况将一目了然。
- 2024-12-24
-
发表了主题帖:
【MCXA156开发板测评】环境配置
1.上手体验
MCXA156采用Arm® Cortex® M33内核,其中运行频率高达96MHz。开发板实物颜值极高,layout布局优雅。在硬件资源上,板载 CMSIS-DAP 减少了外部连线,同时方便调试。
2. ide下载
对恩智浦资料的下载需要注册一个账号。进入官网注册遇到了很大问题,使用了三个邮箱(qq、163、edu)都不太能实时收到注册邮件。等收到邮件的时候,验证码已经过期了。这里我是给官方提单反馈,完成了注册(看别人的贴子,好像谷歌邮箱可以正常注册)。
第一次使用恩智浦的MCU,当然要体验恩智浦完整的开发环境,所以选择MCUXpresso IDE作为编译工具。在官网下载MCUXpresso IDE for NXP MCUs | Linux, Windows and MacOS | NXP Semiconductors | NXP Semiconductors
有Linux、macos、windows、arm64多个平台的安装包,选择对应的默认安装即可。
3. 环境配置
进入IDE之后,选择Import SDK example
在弹出的界面 点击NXP IDE的小标志
在新界面搜索A156,选择对应的开发板即可安装SDK包
4. 例程测试
同上Import SDK example,选择A156开发板,就开到了全部例程,这里选择点灯程序,
点击编译、连接数据线到 CMSIS-DAP接口,点击debug按钮即可完成下载。
灯的闪烁速度比初始更快
[localvideo]9f665b6715c5ff6120657dc7e51cfa0e[/localvideo]
- 2024-12-19
-
回复了主题帖:
测评入围名单: NXP MCX A系列 FRDM-MCXA156开发板
个人信息无误,确认可以完成测评分享计划
- 2024-12-17
-
发表了主题帖:
【极海APM32M3514电机通用评估板】外设篇(求助ADC)
本帖最后由 不语arc 于 2024-12-17 17:02 编辑
0.前提
官方例程是使用滑模观测器的无感FOC控制,但我无法测得自己的电机的各项参数,调试滑模观测器参数始终无法让电机转动起来(求大佬指点)。所以后续我打算直接使用有感FOC完成对电机的控制。
极海lib中的算法相当于使用uint类型完成浮点计算,也就是标幺化。但使用官方api完成有感控制,需要做大量的移植工作,而且有难度。因此,计划先使用math库,完成有感foc控制。在本篇,我将先完成对各个外设的基本驱动。
1.usart
串口配置,参考:【极海APM32M3514电机通用评估板】 串口输出测试 - 国产芯片交流 - 电子工程世界-论坛
将swd复用为串口,在硬件上与ch340模块的TX管脚相连(用不到RX),这里我使用了波动开关。
swd连ch340时,在main.c中开头加一段延时,如此可以在这个时间中继续使用下载功能。不连ch340时,swd仍可以用于调试,需将初始化USART的代码注释掉。
上位机vofa+用于显示波形非常方便,这里使用JustFloat发送格式,只需要在包尾发送对应的tail[4]{0x00,0x00,0x80,0x7f}即可上传多个通道的数据。
发送函数:
//本协议是纯十六进制浮点传输
void vofa_JustFloat_output(float s1,float s2,float s3,float s4)
{
float data[4];
uint8_t tail[4] = {0x00, 0x00, 0x80, 0x7f};
// 发送数据
data[0] = s1;
data[1] = s2;
data[2] = s3;
data[3] = s4;
Usart_SendData(MINI_COM1,(uint8_t*)data,sizeof(float) * 4); //发送数据
// 发送帧尾
Usart_SendData(MINI_COM1,tail,4);
}
2.PWM
本身的pwm配置已经配置好了,宏定义如下。pwm时钟频率同样为72Mhz,计数采用中心对齐模式,pwm开关频率为16khz。
3. I2C编码器
见上一篇分享
4.ADC
五路ADC
其中W相电流 = Ibus - Iu - Iv
配置方式不变,注意右对齐也就是低12位存储读取的结果。
将读取结果转成实际值,注意这里的ADC_Ia_offset为电机未转动时,读取的a相偏置电流大小。
实际读取的值是运放 输出的结果,对其进行解算,得到实际值。
typedef struct
{
float Ia; // Phase A current
float Ib; // Phase B current
float Ic; // Phase C current
float Ibus;
float Vbus;
float VHandle; //0-3.3的范围
uint16_t ADC_Ia; // Phase A Voltage(I*R)
uint16_t ADC_Ib; // Phase B Voltage
uint16_t ADC_Ibus; // SUM Voltage
uint16_t ADC_Ia_offset; // 电流零点偏置
uint16_t ADC_Ib_offset; // 电流零点偏置
uint16_t ADC_Ibus_offset; // 电流零点偏置
uint16_t ADC_Vbus; // Phase bus Voltage
uint16_t ADC_Handle;
} CURRENT_Def;
#define ADC_V_K 0.0008 // 3.3/4096.0 // ADC 与电压转换系数
#define VREF 1.65 //参考电压偏置
#define AM_GAIN 4.86 //运放放大倍数
#define R_SENSE 0.02 //Ω 采样电阻
#define V_GAIN 21 //(100+100+10)/10
void GetADCvaule(){
current.ADC_Ia = (int16_t)ADC_GetValue(CURR_CHANNEL_U);
current.ADC_Ib = (int16_t)ADC_GetValue(CURR_CHANNEL_V);
current.ADC_Ibus = (int16_t)ADC_GetValue(IBUS_CHANNEL);
current.Ia = (ADC_V_K*(current.ADC_Ia-current.ADC_Ia_offset) - VREF) / (AM_GAIN * R_SENSE);
current.Ib = (ADC_V_K*(current.ADC_Ib-current.ADC_Ib_offset) - VREF) / (AM_GAIN * R_SENSE);
current.Ibus = (ADC_V_K*(current.ADC_Ibus-current.ADC_Ibus_offset) - VREF) / (AM_GAIN * R_SENSE);
current.Ic = current.Ibus - current.Ia - current.Ib;
current.ADC_Vbus = (int16_t)ADC_GetValue(VDC_CHANNEL);
current.Vbus = ADC_V_K*(current.ADC_Vbus) * V_GAIN;
current.ADC_Handle = (int16_t)ADC_GetValue(Handle_CHANNEL);
current.VHandle = ADC_V_K*(current.ADC_Handle); //0-3.3的范围
}
在这里提一个问题:为什么U、V两相的运放反相输入端为什么是实际的SUM?计算出的公式,是按照反相输入端接地计算得到的,虽然SUM值不算大。
另外,ADC的中断处理函数好像存在bug,为什么会发生循环执行无法退出ADC中断的情况。具体表现为ADC->STS 无法幅值,中断标志位无法清零。(关闭了看门狗)
两处写法很奇怪,但官方例程居然不怎么出现这个bug
- 2024-12-10
-
回复了主题帖:
【极海APM32M3514电机通用评估板】 串口输出测试
swd还能在线下载和调试吗
- 2024-12-09
-
发表了主题帖:
【极海APM32M3514电机通用评估板】模拟I2C读取AS5600
1. 背景
相较于无感FOC,有感FOC可以直接读取电机当前角度,而无需位置观测器。不仅实现容易,而且减少计算量。
评估版1.0的传感器接口只有霍尔编码器,而市面上很多编码器是I2C接口,或者SPI接口,无法通用。我手上有的电机编码器是AS5600型号,而开发板着实没有预留gpio口供使用,因此产生了将霍尔传感器接口改成普通的GPIO口的想法,使用模拟I2C驱动并读取电机旋转角度。
2.硬件电路改动
使用HA、HB分别作为SDA和SCL,将R7和R8两个电阻替换成0欧姆电阻即可。
3.AS5600编码器
◆AS5600与两极磁铁配对,可以输出12位分辨率的磁性旋转位置,支持IIC通信,还可以输出模拟电压和PWM信号。这里我是用I2C读取方式,分别对0x0C和0x0D两个寄存器进行读取。
在实物上,需要在电机旋转轴上吸附一个磁铁(旋转轴自带径向磁铁更好),as5600编码器放在磁铁正下方。
4.代码编写。这里创建两个源文件,分别对应了模拟I2C驱动和AS5600驱动
#include "dev_include.h"
/*
*********************************************************************************************************
* 函 数 名: i2c_Delay
* 功能说明: I2C总线位延迟,最快400KHz
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
uint8_t i;
/*
可用逻辑分析仪测量I2C通讯时的频率
工作条件:CPU主频168MHz ,MDK编译环境,1级优化
经测试,循环次数为20~250时都能通讯正常
*/
for (i = 0; i < 40; i++);
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Start
* 功能说明: CPU发起I2C总线启动信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Start(void)
{
/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
DEVICE_I2C_SDA_1();
DEVICE_I2C_SCL_1();
i2c_Delay();
DEVICE_I2C_SDA_0();
i2c_Delay();
DEVICE_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Start
* 功能说明: CPU发起I2C总线停止信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Stop(void)
{
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
DEVICE_I2C_SDA_0();
DEVICE_I2C_SCL_1();
i2c_Delay();
DEVICE_I2C_SDA_1();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_SendByte
* 功能说明: CPU向I2C总线设备发送8bit数据
* 形 参:_ucByte : 等待发送的字节
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
/* 先发送字节的高位bit7 */
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
DEVICE_I2C_SDA_1();
}
else
{
DEVICE_I2C_SDA_0();
}
i2c_Delay();
DEVICE_I2C_SCL_1();
i2c_Delay();
DEVICE_I2C_SCL_0();
if (i == 7)
{
DEVICE_I2C_SDA_1(); // 释放总线
}
_ucByte <<= 1; /* 左移一个bit */
i2c_Delay();
}
}
/*
*********************************************************************************************************
* 函 数 名: ee_WriteBytes
* 功能说明: 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
* 形 参:_usAddress : 起始地址
* _usSize : 数据长度,单位为字节
* _pWriteBuf : 存放读到的数据的缓冲区指针
* 返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
return 0;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_ReadByte
* 功能说明: CPU从I2C总线设备读取8bit数据
* 形 参:无
* 返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(unsigned char ack)
{
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
DEVICE_I2C_SCL_1();
i2c_Delay();
if (DEVICE_I2C_SDA_READ())
{
value++;
}
DEVICE_I2C_SCL_0();
i2c_Delay();
}
if (!ack)
i2c_NAck();//·¢?ínACK
else
i2c_Ack(); //·¢?íACK
return value;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_WaitAck
* 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
* 形 参:无
* 返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
uint8_t re;
DEVICE_I2C_SDA_1(); /* CPU释放SDA总线 */
i2c_Delay();
DEVICE_I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
i2c_Delay();
if (DEVICE_I2C_SDA_READ()) /* CPU读取SDA口线状态 */
{
re = 1;
}
else
{
re = 0;
}
DEVICE_I2C_SCL_0();
i2c_Delay();
return re;
}
//这两个函数没有使用过
/*
*********************************************************************************************************
* 函 数 名: i2c_Ack
* 功能说明: CPU产生一个ACK信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Ack(void)
{
DEVICE_I2C_SDA_0(); /* CPU驱动SDA = 0 */
i2c_Delay();
DEVICE_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
DEVICE_I2C_SCL_0();
i2c_Delay();
DEVICE_I2C_SDA_1(); /* CPU释放SDA总线 */
}
/*
*********************************************************************************************************
* 函 数 名: i2c_NAck
* 功能说明: CPU产生1个NACK信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_NAck(void)
{
DEVICE_I2C_SDA_1(); /* CPU驱动SDA = 1 */
i2c_Delay();
DEVICE_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
DEVICE_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_CfgGpio
* 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_CfgGpio(void)
{
GPIO_Config_T GPIO_InitStructure;
RCM_EnableAHBPeriphClock(DEVICE_I2C_GPIO_CLK); /* 打开GPIO时钟 */
GPIO_InitStructure.pin = DEVICE_I2C_SCL_PIN | DEVICE_I2C_SDA_PIN;
GPIO_InitStructure.speed = GPIO_SPEED_50MHz;
GPIO_InitStructure.mode = GPIO_MODE_OUT;
GPIO_InitStructure.outtype = GPIO_OUT_TYPE_OD; /* 开漏输出 */
GPIO_InitStructure.pupd = GPIO_PUPD_NO; //无上拉
GPIO_Config(DEVICE_I2C_GPIO_PORT, &GPIO_InitStructure);
i2c_Stop();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_CheckDevice
* 功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
* 形 参:_Address:设备的I2C总线地址
* 返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
i2c_CfgGpio(); /* 配置GPIO */
i2c_Start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(_Address | DEVICE_I2C_WR);
ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
i2c_Stop(); /* 发送停止信号 */
return ucAck;
}
#ifndef _BSP_I2C_GPIO_H
#define _BSP_I2C_GPIO_H
#include "apm32m35xx.h"
typedef uint8_t u8;
typedef uint16_t u16;
#define DEVICE_I2C_WR 0 /* 写控制bit */
#define DEVICE_I2C_RD 1 /* 读控制bit */
/* 定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
#define DEVICE_I2C_GPIO_PORT GPIOA /* GPIO端口 */
#define DEVICE_I2C_GPIO_CLK RCM_AHB_PERIPH_GPIOA /* GPIO端口时钟 */
#define DEVICE_I2C_SCL_PIN GPIO_PIN_1 /* 连接到SCL时钟线到编码器HB */
#define DEVICE_I2C_SDA_PIN GPIO_PIN_3 /* 连接到SDA数据线到编码器HA */
/* 定义读写SCL和SDA的宏 */
#define DEVICE_I2C_SCL_1() GPIO_SetBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SCL_PIN) /* SCL = 1 */
#define DEVICE_I2C_SCL_0() GPIO_ClearBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SCL_PIN) /* SCL = 0 */
#define DEVICE_I2C_SDA_1() GPIO_SetBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SDA_PIN) /* SDA = 1 */
#define DEVICE_I2C_SDA_0() GPIO_ClearBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SDA_PIN) /* SDA = 0 */
#define DEVICE_I2C_SDA_READ() GPIO_ReadInputBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SDA_PIN) /* 读SDA口线状态 */
void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t _ucByte);
uint8_t i2c_WaitAck(void);
uint8_t i2c_ReadByte(unsigned char);
void i2c_Ack(void);
void i2c_NAck(void);
void i2c_CfgGpio(void);
uint8_t i2c_CheckDevice(uint8_t _Address);
uint32_t I2C_ByteWrite(u8* pBuffer, u8 WriteAddr);//发送一字节
uint32_t I2C_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead);
#endif
#include "dev_include.h"
AS5600_ENCODER_Def as5600_encoder;
int RAW_Angle_Hi = 0x0c;
int RAW_Angle_Lo = 0x0d;
// 旋转的总圈数*2pi
float full_rotation_offset=0; // full rotation tracking;
long angle_data_prev=0; //上一时刻的角度(读出来的,两字节)
float angle_prev; //换算出的角度
unsigned long velocity_calc_timestamp; //速度计算的时刻
long cpr; //量程
// 返 回 值: 返回1表示错误
u8 AS5600_ReadOneByte(u16 ReadAddr)
{
u8 temp=1;
i2c_Start();
i2c_SendByte((AS5600_ADDRESS<<1)|0x00); //·¢ËÍдÃüÁî
i2c_WaitAck();
i2c_SendByte(ReadAddr); //·¢Ë͵ØÖ·
i2c_WaitAck();
i2c_Start();
i2c_SendByte((AS5600_ADDRESS<<1)|0x01); //½øÈë½ÓÊÕģʽ
i2c_WaitAck();
temp=i2c_ReadByte(0);
i2c_Stop();
return temp;
}
//写一字节 AS5600¸这个函数没咋用
void AS5600_WriteOneByte(u16 WriteAddr,u8 WriteData)
{
i2c_Start();
i2c_SendByte((0X36<<1)|0x00); //·¢ËÍдÃüÁî
i2c_WaitAck();
i2c_SendByte(WriteAddr); //·¢Ë͵ØÖ·
i2c_WaitAck();
i2c_Start();
i2c_SendByte(WriteData); //·¢ËÍÊý¾Ý
i2c_WaitAck();
i2c_Stop();//²úÉúÒ»¸öÍ£Ö¹Ìõ¼þ
//Delay_ms(10); 我没有实现这个 会有影响吗?
}
//readTwoBytes(int in_adr_hi, int in_adr_lo)这段代码是一个函数,其目的是从I2C设备(在代码中的变量名为AS5600_Address)中读取两个字节数据,并将其合并成一个16位的无符号整数返回。
//具体来说,函数接受两个整型参数in_adr_hi和in_adr_lo,它们用于指定需要读取的两个字节数据的地址。函数中首先通过Wire库开始I2C传输,向设备写入in_adr_lo和in_adr_hi分别作为数据地址,然后读取相应的字节数据。
//在每个Wire.requestFrom()调用之后,通过一个while循环等待数据接收完毕。然后读取接收到的低字节和高字节,并使用位运算将它们合并成一个16位的无符号整数。
//最后,返回合并后的整数。如果读取过程中出现错误或者函数没有成功读取到数据,则函数返回-1。
unsigned short readTwoBytes(int in_adr_hi, int in_adr_lo)
{
unsigned short retVal = 0;
u8 low = AS5600_ReadOneByte(in_adr_lo);
u8 high = AS5600_ReadOneByte(in_adr_hi);
retVal = (high << 8) | low;
return retVal;
}
unsigned short getRawAngle()
{
return readTwoBytes(RAW_Angle_Hi, RAW_Angle_Lo);
}
//初始化 磁编码器
void MagneticSensor_Init()
{
i2c_CfgGpio();
cpr=AS5600_CPR;
angle_data_prev = getRawAngle();
full_rotation_offset = 0;
velocity_calc_timestamp=0;
}
float getAngle()
{
long val = getRawAngle();
return ( val / (float)cpr) * _2PI;
}
float getFullAngle()
{
long val = getRawAngle();
long d_angle = val - angle_data_prev;
//计算旋转的总圈数
//通过判断角度变化是否大于80%的一圈 来判断是否发生了溢出,
//如果发生了,则将full_rotation_offset增加1(如果d_angle小于0)或减少1(如果d_angle大于0)。
if(abs(d_angle) > (0.8*cpr) ) full_rotation_offset += ( d_angle > 0 ) ? -_2PI : _2PI;
angle_data_prev = val;
return (full_rotation_offset + ( val / (float)cpr) * _2PI);
}
//获取编码器的机械角度,真实的物理角度
//获取电角度角度,FOC电角度
void Get_Encoder_Angles(void)
{
as5600_encoder.angle = getAngle() * (180.0 / PI);
as5600_encoder.angle_rad = getAngle();
// as5600_encoder.angle_rad_offset = 6.045f; //手动找到的零点,后续通过函数调用获得
//as5600_encoder.angle_rad_offset = 0.91426f; //手动找到的零点,后续通过函数调用获得
if(as5600_encoder.angle_rad>=as5600_encoder.angle_rad_offset) //减去零点偏置
{
as5600_encoder.angle_rad = as5600_encoder.angle_rad - as5600_encoder.angle_rad_offset;
}
else
{
as5600_encoder.angle_rad = 2*PI - as5600_encoder.angle_rad_offset + as5600_encoder.angle_rad;
}
as5600_encoder.electronic_angle = as5600_encoder.angle_rad*MOTOR_POLE;
}
//轴速度计算
// Shaft velocity calculation
float getVelocity()
{
unsigned long now_us;
float Ts, angle_c, vel;
// calculate sample time
now_us = SysTick->VAL; //_micros();
if(now_us<velocity_calc_timestamp)Ts = (float)(velocity_calc_timestamp - now_us)/9*1e-6f;
else
Ts = (float)(0xFFFFFF - now_us + velocity_calc_timestamp)/9*1e-6f;
// quick fix for strange cases (micros overflow)
if(Ts == 0.0f || Ts > 0.5f) Ts = 1e-3f;
// current angle
angle_c = getFullAngle();
// velocity calculation
vel = (angle_c - angle_prev)/Ts;
// save variables for future pass
angle_prev = angle_c;
velocity_calc_timestamp = now_us;
return vel;
}
#ifndef __AS5600_H
#define __AS5600_H
#include "stm32f4xx.h"
#include "user_config.h"
#include "./i2c/software_i2c/bsp_i2c_gpio.h"
//#include <math.h> //求浮点数的绝对值
#include <stdlib.h>
#define AS5600_CPR 4096 // as5600的满量程
//0x36是地址,但是要左移一位,因为最后一位是读写位,1是读,0是写
#define AS5600_ADDRESS 0x36
typedef struct
{
float angle; //0~360
float angle_rad; //弧度制
float electronic_angle;
float angle_rad_offset;
} AS5600_ENCODER_Def;
extern AS5600_ENCODER_Def as5600_encoder;
float getAngle(void); //返回弧度值 0-2Pi
float getFullAngle(void);//总弧度
float getVelocity(void);
void MagneticSensor_Init(void);
void Get_Encoder_Angles(void);
#define _2PI 6.28318530718f
#define PI 3.14159265f
#endif
5. 代码讲解
初始化定义。这里使用了编码器的HA和HB两个引脚,注意HA在硬件电路上是PA3引脚。我一开始配置时直接copy例程里的PA0引脚,调试很久才发现问题。
GPIO口初始化配置。配置成无上拉的开漏输出。
读一字节函数。 输入参数为寄存器地址,返回参数为对应的值。
读AS5600的编码器结果函数。分别传入高和低寄存器地址,对内容进行读取,并返回。返回结果是0-4096对应0-360°
6.主函数编写,当读取的角度大于180°时,led灯点亮。反之熄灭
IO_Init();
MagneticSensor_Init();
while(1){
// 将弧度转换为度
ang = getAngle()* (180.0 / 3.14158f);
if(ang > 180)
LED_Fault_ON();
else
LED_Fault_OFF();
for(i=0;i<1000;i++);
}
7. 效果展示
[localvideo]4dda6ee54bfcb23e9f2d56b0badfbafe[/localvideo]
-
回复了主题帖:
极海APM32M3514串口使用PF0和PF1问题
IllusionXX 发表于 2024-12-9 10:54
而且他硬件原理图,连接串口的电阻是没有焊的,接晶振的电阻是焊上的,看不懂这是要干嘛,也没有文档说明 ...
这设计太逆天了,不说我都没发现连的是晶振,我还隔这检查半天程序
-
回复了主题帖:
极海APM32M3514串口使用PF0和PF1问题
不开中断,串口能用么
- 2024-11-27
-
回复了主题帖:
测评入围名单:极海APM32M3514电机通用评估板
个人信息无误,确认可以完成测评分享计划
- 2024-07-09
-
回复了主题帖:
颁奖:嵌入式工程师AI挑战营(初阶),致敬敢于将边缘AI收入统治领域的你们
个人信息已确认,请安排邮寄。优秀奖选择均码防晒衣。感谢luckfox和EEworld对本次活动的大力支持!
- 2024-06-05
-
回复了主题帖:
#AI挑战营终点站# rv1106数字识别模型部署与问题求助
给你参考一下我的帖子。#AI挑战营终点站# rv1106平台使用yolov5识别手写数字 - Linux与安卓 - 电子工程世界-论坛 (eeworld.com.cn)
我是使用yolov5 7.0版本训练的yolov5s.pt权重,然后使用https://github.com/airockchip/yolov5的export.py函数转为onnx模型,再使用 https://github.com/airockchip/rknn_model_zoo的convert.py转成rknn模型。最终部署项目是 https://github.com/luckfox-eng29/luckfox_pico_rtsp_yolov5。
- 2024-05-28
-
发表了主题帖:
#AI挑战营终点站# rv1106平台使用yolov5识别手写数字
本帖最后由 不语arc 于 2024-6-5 15:36 编辑
背景:
使用前两期训练的 针对MNIST数据集的 模型存在以下缺点:
图片分辨率过低28x28,与现实存在较大差异。
对摄像头采集到的大图一次只能识别一个数字。
前处理操作 如找到大图中轮廓最大的数字、对送入模型的小图进行二值化 等操作注定了识别场景有限。
为了解决以上问题(并且luckfox已经有使用yolov5的demo工程了)所以决定使用yolov5完成手写数字的识别。
效果预览:
数据集准备
yolov5的输入图像分辨率是640x640,而MNIST数据集的尺寸是28x28,应该如何解决?
如果直接将MNIST小图拼接在一起,就好像这样,特征过于明显,颜色都是黑底白字、目标框的位置都是固定的。数据集容易过拟合。
想法:使用MNIST 28x28分辨率的小图 合成640x640的大图。大图包含若干小图,同时输出对应label
效果预览:
目标:数字的颜色随机(增强数据),大图背景随机并且小图背景(数字外的区域)跟随、小图的尺寸随机被resize、小图的位置随机。自动生成每张小图的label标签。
过程:
对图片,加载MNIST数据集(定义每张大图有12个小图)、创建一个640x640的背景(颜色随机)、对每一张小图有:检测数字位置与背景位置、改变对应位置的像素值、随机resize小图、随机摆放、最后将小图粘贴到背景上。
对标签label,可以大概设定每一张小图的预测框是28x28,为了避免预测框都是正方形,代码里对长宽都randint(20,28),因为20x20的框也差不多能框住这个小图了。再根据上面的resize值、随机拜访位置值求出对应的label。yolo系列的标签一个框对用5个值,分别是类别、中心点坐标x、中心点坐标y、宽w、高h。后四个指标是归一化之后的float。具体知识可以搜索yolo学习。
# 合成MNIST大图
import torch
import torchvision
import torchvision.transforms as transforms
from PIL import Image
import numpy as np
import random
import os
# 设置参数
NUM_IMAGES_TO_GENERATE = 400 # 生成图片的数量
IMAGE_SIZE = 640 # 输出的图片大小
Resize_ratios = [1, 1.5, 2, 2.5, 3, 3.5, 4] # resize的比例尺寸
NumOfPic = 12 # 每张大图的小图数量
backcolors = [
(255, 0, 0), # 红色
(0, 255, 0), # 绿色
(0, 0, 255), # 蓝色
(255, 255, 0), # 黄色
(0, 255, 255), # 青色
(255, 0, 255), # 品红色
(128, 128, 128),# 灰色
(255, 255, 255),# 白色
(0, 0, 0), # 黑色
(210, 105, 30) # 棕色
]
# 加载MNIST数据集
transform = transforms.Compose([
transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并缩放到[0.0, 1.0]
transforms.Normalize((0.5,), (0.5,)) # 归一化到[-1.0, 1.0]
])
trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=NumOfPic, shuffle=True)
# 创建用于保存生成的图片的目录
images_dir = 'dataset\\images'
if not os.path.exists(images_dir):
os.makedirs(images_dir)
# 创建用于保存生成的txt文件的目录
labels_dir = 'dataset\\labels'
if not os.path.exists(labels_dir):
os.makedirs(labels_dir)
# 初始化计数器
image_counter = 0
# 生成图片的循环
for data in trainloader:
if image_counter >= NUM_IMAGES_TO_GENERATE:
break
# 用于生成每张图片的label
curLable = np.zeros((NumOfPic, 5), dtype=float) # 5对应cls x y w h
img_tensor, label = data
label_array = label.numpy()
curLable[:, 0] = label_array
backcolorNum = random.randint(0, 9)
backcolor = backcolors[backcolorNum]
# 创建一个颜色随机的背景图片
background = Image.new('RGB', (IMAGE_SIZE, IMAGE_SIZE), color=backcolor)
# 将tensor转换回numpy数组
rgb_tensor = img_tensor.repeat(1, 3, 1, 1)
for i, img in enumerate(rgb_tensor):
img_np = img.numpy().squeeze().transpose(1, 2, 0) * 255 # [C, H, W] to [H, W, C] and scale to 0-255
# 创建一个空的彩色图像,大小与二值图像相同
colored_image = np.zeros((img_np.shape[0], img_np.shape[1], 3), dtype=np.uint8)
# 随机一个数字对应的颜色
while True: # 保证与背景颜色不同
numrandom = random.randint(0, 9)
if numrandom != backcolorNum:
break
colors = backcolors[numrandom]
# 获取所有白色像素的坐标
white_pixel_indices = np.where(img_np > 0)
black_pixel_indices = np.where(img_np == -255)
# 将随机颜色分配给白色像素
colored_image[white_pixel_indices[0], white_pixel_indices[1]] = colors
colored_image[black_pixel_indices[0], black_pixel_indices[1]] = backcolor
img_pil = Image.fromarray(colored_image, 'RGB')
# 随机resize
random_ratio = random.randint(0, len(Resize_ratios)-1)
img_pil = img_pil.resize((int(28*Resize_ratios[random_ratio]), int(28*Resize_ratios[random_ratio])))
w_random = random.randint(20, 28) # 避免框都是正方形
h_random = random.randint(21, 28)
curLable[i][3] = w_random/IMAGE_SIZE*Resize_ratios[random_ratio]
curLable[i][4] = h_random/IMAGE_SIZE*Resize_ratios[random_ratio]
# 随机放置位置(确保不超出背景边界)
left = random.randint(0, IMAGE_SIZE - img_pil.width)
top = random.randint(0, IMAGE_SIZE - img_pil.height)
curLable[i][1] = (left+img_pil.width/2)/IMAGE_SIZE # 框的正中心x坐标(归一化)
curLable[i][2] = (top + img_pil.height / 2) / IMAGE_SIZE # 正中心y坐标
background.paste(img_pil, (left, top))
# 保存图片
output_img_path = os.path.join(images_dir, f'image_{image_counter}.jpg')
background.save(output_img_path)
# 打开一个文件用于写入
txtPath = os.path.join(labels_dir, f'image_{image_counter}.txt')
with open(txtPath, 'w') as file:
# 遍历数组的每一行
for row in curLable:
# 对于每一行,首先取第一列的整数值,其余列保留浮点数,用空格连接后写入文件
# 注意:np.int32()用于将浮点数转换为整数,如果是非负数且不需要特别大的整数,np.int_或直接int()也可以
line = ' '.join([str(int(row[0]))] + [f'{x:.6f}' for x in row[1:]])
file.write(line + '\n') # 每一行结束后添加换行符
# 递增计数器
image_counter += 1
# 脚本结束
print(f'Generated {image_counter} images and labels.')
生成了200张图片及标签(其实还可以生成很多张),除此之外,我加了一百二十多张通用图片,自行标注了几十张图片,具体看附件。将他们按照8:1:1划分训练集、验证集、测试集。生成对应的txt指定对应图片的路径。
划分代码如下:
# -*- coding: utf-8 -*-
"""
将所有数据集按照 8:1:1的比例划分为训练集、验证集、测试集。输出为txt文本
使用方法:指定数据集的主目录为root_path
"""
from glob import glob
import os
from sklearn.model_selection import train_test_split
import random
import shutil
root_path = r'D:\Users\Yzhe\PycharmProjects\MNIST_Test\dataset'
def toTrainValTest(root_path):
ftrain = open(root_path + '/train.txt', 'w')
fval = open(root_path + '/val.txt', 'w')
ftest = open(root_path + '/test.txt', 'w')
# total_files为列表,images文件夹下所有后缀为 jpg.jpeg,png 的文件的路径
total_files = glob((os.path.join(root_path, "images/*[jpg.jpeg,png]")))
# split
train_files, val_test_files = train_test_split(total_files, test_size=0.2, random_state=42)
val_files, test_files = train_test_split(val_test_files, test_size=0.5, random_state=42)
# train
for file in train_files: # 遍历训练集中所有文件的路径
ftrain.write(file + "\n") # 将路径一条一行写入train.txt中,train.txt程序中对应的参数是ftrain
# val
for file in val_files:
fval.write(file + "\n")
# test
for file in test_files:
ftest.write(file +"\n")
ftrain.close()
fval.close()
ftest.close()
toTrainValTest(root_path)
生成了对应的3个txt。txt内容是这样的:
至此数据集准备工作完成。
yolov5训练
yolov5训练。(这部分仍然在windows完成,需要显卡。我是4060,8G显存)
环境配置部分网上教程挺多的,使用的anaconda+pycharm。使用anaconda创建了一个torch2的环境,这个环境满足运行yolov5.
下载yolov5代码(7.0版本)
https://github.com/ultralytics/yolov5/tree/v7.0
配置
2.1 在data目录下新建一个MNIST目录,在该目录下放置刚才生成的txt。
2.2 在此目录下新建一个yaml文件,内容如下:
train: data/MNIST/train.txt
val: data/MNIST/val.txt
test: data/MNIST/test.txt
# Classes
nc: 10 # number of classes
names: ['0','1','2','3','4','5','6','7','8','9']
2.3 在train.py文件下,找到parse_opt函数(用于指定参数)
照着这个配置即可,如果显存不大就调小batchsize。epochs是训练1000轮,实际上超过一百轮没提升就提前结束了。
训练结果保存在runs/train/exp目录下,权重文件选择weights/best.pt。训练过程如下。
导出为onnx模型(这部分参考https://wiki.luckfox.com/zh/Luckfox-Pico/Luckfox-Pico-RKNN-Test/#4-yolo-v5)还是在windows操作。
3.1 获取yolov5代码(这里用的是rockchip的代码,他在原yolov5的基础上增加了对rknn的支持)
git clone https://github.com/airockchip/yolov5.git
具体改动是:
3.2 激活第二步使用的环境torch2:conda activate torch2
3.3 将刚才训练好的best.pt复制到项目根目录下,并重命名为yolov5s.pt
3.4 在yolov5/python目录下,运行export.py
python export.py --rknpu --weight yolov5s.pt.
在根目录下生成了yolov5s.onnx
3.5 使用netron网站可以观察模型结构。
https://netron.app/
网站查看onnx模型结构
转rknn。这一步需要在Ubuntu环境下,使用前两期训练营配置的toolkit2_0.0环境
4.1 下载 rknn_model_zoo
git clone https://github.com/airockchip/rknn_model_zoo.git
4.2 将刚刚得到的ONNX模型文件拷贝至 rknn_model_zoo/examples/yolov5/model 目录
4.3 修改数据集内容(因为是自行训练的数据集,不是coco数据集),这一步是为了量化。
4.3.1 修改rknn_model_zoo/examples/yolov5/python/convert.py文件
4.3.2 在rknn_model_zoo/datasets/COCO目录下新建一个文件夹、一个txt。
4.3.3 txt内容如下:(用于指定数据集位置)
./myMNIST/image_1.jpg
./myMNIST/image_2.jpg
./myMNIST/image_3.jpg
./myMNIST/image_4.jpg
./myMNIST/image_5.jpg
./myMNIST/image_6.jpg
./myMNIST/image_7.jpg
./myMNIST/image_8.jpg
./myMNIST/image_9.jpg
./myMNIST/image_10.jpg
./myMNIST/image_11.jpg
./myMNIST/image_12.jpg
./myMNIST/image_13.jpg
./myMNIST/image_14.jpg
./myMNIST/image_15.jpg
4.4.4 在myMNIST目录下复制指定15张图片。(图片数量、具体哪一张图片应该是随意的)
4.4 执行 rknn_model_zoo/examples/yolov5/python 目录下的模型转换程序 convert.py,使用方法:
conda activate toolkit2_0.0
cd <实际为准>/rknn_model_zoo/examples/yolov5/python
python3 convert.py ../model/yolov5s.onnx rv1106
在rknn_model_zoo/examples/yolov5/model目录下生成了yolov5.rknn
配置最终运行的项目
5.1 下载luckfox的程序
git clone https://github.com/luckfox-eng29/luckfox_pico_rtsp_yolov5.git
5.2 修改model目录内容
5.2.1 替换刚才生成的yolov5.rknn文件
5.2.2 修改coco_80_labels_list.txt文件
其内容是:用于指定十种类别。
0
1
2
3
4
5
6
7
8
9
5.3 修改 include/rknn/postprocess.h的内容(这个bug找了一晚上),将类别数量改成10
5.4 编译:
export LUCKFOX_SDK_PATH=<Your Luckfox-pico SDK Path>
mkdir build
cd build
cmake ..
make && make install
5.5 将项目目录下的luckfox_rtsp_yolov5_demo上传到开发板。
5.6 开发板运行
在luckfox_rtsp_yolov5_demo目录下,
chmod 777 luckfox_rtsp_yolov5
./luckfox_rtsp_yolov5
5.7 使用vlc拉流查看摄像头画面。rtsp://172.32.0.93/live/0。这里我直接拍摄了LCD屏幕效果(LCD显示看我上一次分享帖子)
分析:
在检测精度上,检测结果已经相当可以了,不会乱框,性能的提升与数据集强相关。
存在的问题:
速度确实慢了,明显感觉比之前慢很多,而且vlc拉流缓冲时间不能设置太小,不然没法看。
模型提升方面。改进方法也只能是改进数据集,人工标注增加样本也不是不行,正所谓人工智能就是有多少人工就有多少智能。
- 2024-05-25
-
发表了主题帖:
#AI挑战营终点站# rv1106平台手写数字识别终端
本帖最后由 不语arc 于 2024-5-26 10:39 编辑
1. 背景
在收到开发板之后发现官方支持的内核中有st7789屏幕(链接:2.4寸tft液晶显示屏SPI串口彩色ST7789ILI9341 4IO口可驱动-淘宝网 (taobao.com)不带触摸即可),手上刚好有一块2.4寸屏,因此想将摄像头画面显示到屏幕上,无需rtsp推流就可以实时观察检测结果。
2. spi屏幕驱动
参考测评汇总:Luckfox幸狐 RV1106 Linux 开发板 - 测评中心专版 - 电子工程世界-论坛 (eeworld.com.cn)
2.1 引脚配置
GPIO2_A2 对应DC引脚,GPIO2_A3对应RESET引脚。(GPIO引脚如果在设备树中有其他作用,需要注释将其掉,这两个引脚不用管)
2.2 修改设备树
修改文件:<SDK目录>/sysdrv/source/kernel/arch/arm/boot/dts/rv1106g-luckfox-pico-pro-max.dts(可以先备份一下)
两处修改:
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2022 Rockchip Electronics Co., Ltd.
*/
/dts-v1/;
#include "rv1106.dtsi"
#include "rv1106-evb.dtsi"
#include "rv1106-luckfox-pico-pro-max-ipc.dtsi"
&pinctrl {
/*LCD DC*/
gpio2_dc_pa2 {
gpio2_dc_pa2:gpio2_dc_pa2 {
rockchip,pins = <2 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
/*LCD RESET*/
gpio2_reset_pa3 {
gpio2_reset_pa3:gpio2_reset_pa3 {
rockchip,pins = <2 RK_PA3 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
};
/ {
model = "Luckfox Pico Max";
compatible = "rockchip,rv1103g-38x38-ipc-v10", "rockchip,rv1106";
// lcd的DC和RESET管脚配置
// lcd的DC管脚配置
gpio2pa2: gpio2pa2 {
compatible = "regulator-fixed";
pinctrl-names = "default";
pinctrl-0 = <&gpio2_dc_pa2>;
regulator-name = "gpio2-dc-pa2";
regulator-always-on;
};
// lcd的RESET管脚配置
gpio2pa3: gpio2pa3 {
compatible = "regulator-fixed";
pinctrl-names = "default";
pinctrl-0 = <&gpio2_reset_pa3>;
regulator-name = "gpio2-reset-pa3";
regulator-always-on;
};
};
// /**********SPI**********/
&spi0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi0m0_pins &spi0m0_cs0>;
//cs-gpios = <&gpio1 RK_PC0 1>;
// cs-gpios = <&gpio1 26 1>;
#address-cells = <1>;
#size-cells = <0>;
st7789v: st7789v@0{
compatible = "sitronix,st7789v";
reg = <0>;
status = "okay";
spi-max-frequency = <48000000>;
dc = <&gpio2 RK_PA2 1>;
reset = <&gpio2 RK_PA3 1>;
rotation = <90>;
spi-cpol;
spi-cpha;
fps = <30>;
buswidth = <8>;
rgb;
};
};
2.3 修改编译内核的配置文件
除了设备树文件,还要修改<SDK目录>/sysdrv/source/kernel/arch/arm/configs/luckfox_rv1106_linux_defconfig,以添加FB文件支持。直接编辑文件可以省去make menuconfig的步骤。
2.4 编译内核
在sdk目录下
./build.sh clean kernel
./build.sh kernel
生成镜像文件: output/image/boot.img
2.5 烧录替换镜像
在windows烧录软件中,单独烧录boot.img即可。
3. 扩展板设计
杜邦线太多影响操作,所以简单设计了一个扩展板。
一个3.3v稳压输出用于驱动LCD背光、CH340N用于串口通信、扩展出了一个IIC接口。
4. 手写数字识别部署
参考#AI挑战营终点站#将模型部署到RV1106中通过摄像头采集进行手写数字识别 - 嵌入式系统 - 电子工程世界-论坛 (eeworld.com.cn)
#AI挑战营终点站# rv1106数字识别模型部署与性能优化 - 嵌入式系统 - 电子工程世界-论坛 (eeworld.com.cn)
感谢两位大佬的开源贡献,在他们发布之前我一直想在luckfox_yolov5的demo基础上改,整合工作一起没做好。
我在他们基础上将识别结果显示到LCD屏上。
目前模型仍在调试,后续有更好的效果会再更新。
4.1 显示代码:(这其中的rgb参数都是慢慢调出来的,确实搞不懂opencv采集的mat格式)
// spi显示头文件
#include <opencv2/opencv.hpp>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
// 将BGR格式图像转换为RGB565格式
void convertToRGB565(const cv::Mat& src, std::vector<uint16_t>& dst) {
int rows = src.rows;
int cols = src.cols;
dst.resize(rows * cols);
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
cv::Vec3b pixel = src.at<cv::Vec3b>(i, j);
uint16_t r = (255 - pixel[0]) >> 3;
uint16_t g = (255-pixel[1]) >> 2;
uint16_t b = (255 - pixel[2]) >> 3;
dst[i * cols + j] = (r << 11) | (g << 5) | b;
}
}
}
void displayImageOnFramebuffer(const cv::Mat& src, const char* fbDevice) {
// Step 1: 修改frame尺寸
cv::Mat scaledFrame;
cv::resize(src, scaledFrame, cv::Size(240, 320));
// Step 2 : 转换图像为RGB565格式
std::vector<uint16_t> rgb565Image;
convertToRGB565(scaledFrame, rgb565Image);
// Step 3: 写入到Frameuffer
int fbFd = open(fbDevice, O_RDWR);
if (fbFd == -1) {
perror("Failed to open framebuffer device");
return;
}
// 获取屏幕信息
struct fb_var_screeninfo vinfo;
if (ioctl(fbFd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
perror("Failed to get variable screen info");
close(fbFd);
return;
}
vinfo.xres = 240;
vinfo.yres = 320;
vinfo.bits_per_pixel = 16; // RGB565
if (ioctl(fbFd, FBIOPUT_VSCREENINFO, &vinfo) == -1) {
perror("Failed to put variable screen info");
close(fbFd);
return;
}
void *fbp = mmap(NULL, vinfo.xres_virtual * vinfo.yres_virtual * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fbFd, 0);
if (fbp == MAP_FAILED) {
perror("Failed to map framebuffer");
close(fbFd);
return;
}
// memcpy(fbp, rgb565Image.data(), 240 * 320 * sizeof(uint16_t)); // 注意这里使用sizeof(uint16_t)
memcpy(fbp, rgb565Image.data(), rgb565Image.size() * sizeof(uint16_t));
munmap(fbp, vinfo.xres_virtual * vinfo.yres_virtual * 2);
close(fbFd);
}
main.cc后面将检测结果绘制到frame之后,执行一句
4.2 检测效果
[localvideo]3af3a7ec95fd886173c63a7162724640[/localvideo]
模型是大佬训练的,根据后处理来看,目前支持 白底黑字且粗的手写数字。后续我会调试训练,希望马上能更新。
- 2024-05-09
-
回复了主题帖:
入围名单公布:嵌入式工程师AI挑战营(初阶),获RV1106 Linux 板+摄像头的名单
本帖最后由 不语arc 于 2024-5-9 16:32 编辑
个人信息已确认,领取板卡,可继续完成&分享挑战营第二站和第三站任务
- 2024-05-01
-
回复了主题帖:
#AI挑战营第二站# Ubuntu平台下ONNX模型转RKNN
qiao--- 发表于 2024-4-30 16:00
楼主有过这样的报错没
好像没遇到过。我觉得你可以先重新导出onnx版本为19的权重,再试试我提到的链接固定输入batchsize为1。
-
回复了主题帖:
#AI挑战营第二站# Ubuntu平台下ONNX模型转RKNN
2024-4-30 13:27 toolkit2汾onnx汾19 ν
用这个新配置好的高版本环境,重新将pth转成onnx,这个样就可以了
- 2024-04-29
-
回复了主题帖:
【AI挑战营第二站】算法工程化部署打包成SDK
本帖最后由 不语arc 于 2024-4-30 10:14 编辑
ONNX模型: ONNX(Open Neural Network Exchange)是一种开放的模型交换格式,设计用于深度学习和机器学习领域。它的主要目的是使不同框架(如PyTorch、TensorFlow、MXNet等)训练的模型能够在多个平台上无缝运行,提高了模型的可移植性和互操作性。
RKNN模型: RKNN模型是指针对瑞芯微(Rockchip)系列芯片优化的神经网络模型。RKNN(Rockchip Neural Network)是瑞芯微提供的一个工具包,用于将常见的深度学习模型格式(如Caffe、TensorFlow、ONNX等)转换为专为瑞芯微NPU(Neural Processing Unit)设计的高效运行格式。这种转换过程涉及模型的优化、量化以及可能的结构调整,以确保模型在瑞芯微芯片上运行时能实现高性能和低功耗。
#AI# UbuntuONNXRKNN - Linux - - (eeworld.com.cn)
-
发表了主题帖:
#AI挑战营第二站# Ubuntu平台下ONNX模型转RKNN
RKNN-Toolkit2 是为用户提供在 PC 平台上进行模型转换、 推理和性能评估的开发套件。
环境配置 (这一步我试了很久的Windows平台,发现python3.6版本安装不上,安装torch各种报错,遂放弃)
1.安装miniconda
# 更新软件包
sudo apt update
sudo apt upgrade
# 使用清华源下载
wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-Linux-x86_64.sh
# 安装 并添加-b参数表示静默安装,-u表示更新PATH变量,以及-p指定安装路径
bash Miniconda3-latest-Linux-x86_64.sh -b -u -p ~/miniconda3
# 清除脚本
rm Miniconda3-latest-Linux-x86_64.sh、
# 添加PATH到环境变量中
sudo gedit /etc/profile (在最后一行添加export PATH=~/miniconda3/bin:$PATH)
# 修改后注入环境
source /etc/profile
最后重启一下
2.新建环境
# 创建一个名为 toolkit2_0.0 的环境,并指定 python 版本,
conda create -n toolkit2_0.0 python=3.8
conda activate toolkit2_0.0
# 拉取 toolkit2 源码
git clone https://github.com/airockchip/rknn-toolkit2
# 配置 pip 源
pip3 config set global.index-url https://mirror.baidu.com/pypi/simple
# pip 安装指定版本的库
cd rknn-toolkit2
pip3 install -r packages/requirements_cp38-2.0.0b0.txt
# 安装 whl 文件,需要根据 python 版本和 rknn_toolkit2 版本
pip3 install packages/rknn_toolkit2-2.0.0b0+9bab5682-cp38-cp38-linux_x86_64.whl
进入python环境,导入rknn包正常:
3. 编写onnx转rknn代码
这里踩了很多坑,首先由于我安装的toolkit2版本很高,必须要求转出的onnx版本为19。其次导出的onnx的输入batchsize是一个动态的,会报错,这里我就固定了onnx的输入batchsize为1。参考链接:onnx模型输入输出维度修改_load_onnx: valueerror: the 'cls1' in outputs=['cls-CSDN博客
最后是量化rknn.build步骤,必须指定数据集,这里我在txt中只指定了一张照片路径(原始的MNIST照片是看不到的,所以我截图了一张再将其resize、灰度化了)。
from rknn.api import RKNN
DATASET_PATH = './dataset.txt'
# Create RKNN object
rknn = RKNN(verbose=True)
# pre-process config
print('--> config model')
rknn.config(target_platform='rv1106', mean_values=[[128]], std_values=[[128]])
print('done')
# Load model
print('--> Loading model')
ret = rknn.load_onnx(model='./mnist.onnx')
if ret != 0:
print('Load model failed!')
exit(ret)
print('done')
rknn.build(do_quantization=True, dataset=DATASET_PATH) # 构建RKNN模型,可选参数量化
if ret != 0:
print('Build model failed!')
exit(ret)
print('done')
ret = rknn.export_rknn('./mnist.rknn') # 导出RKNN模型文件
if ret != 0:
print('Export rknn model failed!')
exit(ret)
print('done')
# 释放 RKNN 对象
rknn.release()
输出日志
I rknn-toolkit2 version: 2.0.0b0+9bab5682
--> config model
done
--> Loading model
I Loading : 100%|█████████████████████████████████████████████████| 11/11 [00:00<00:00, 4360.40it/s]
done
E build: Dataset file ./dataset.txt not found!
done
E export_rknn: RKNN model does not exist, please load & build model first!
Export rknn model failed!
(toolkit2_0.0) yangzhe@yangzhe-pc:~/RKNN/MNIST$ python onnx2rknn.py
I rknn-toolkit2 version: 2.0.0b0+9bab5682
--> config model
done
--> Loading model
I Loading : 100%|█████████████████████████████████████████████████| 11/11 [00:00<00:00, 3765.70it/s]
done
D base_optimize ...
D base_optimize done.
D
D fold_constant ...
D fold_constant done.
D fold_constant remove nodes = ['/Concat', '/Unsqueeze', '/Gather', '/Shape']
D
D correct_ops ...
D correct_ops done.
D
D fuse_ops ...
D fuse_ops results:
D replace_reshape_gemm_by_conv: remove node = ['/Reshape', '/fc1/Gemm'], add node = ['/fc1/Gemm_2conv', '/fc1/Gemm_2conv_r eshape']
D convert_gemm_by_conv: remove node = ['/fc2/Gemm'], add node = ['/fc2/Gemm_2conv_reshape1', '/fc2/Gemm_2conv', '/fc2/Gemm _2conv_reshape2']
D fuse_two_reshape: remove node = ['/fc1/Gemm_2conv_reshape']
D remove_invalid_reshape: remove node = ['/fc2/Gemm_2conv_reshape1']
D fold_constant ...
D fold_constant done.
D fuse_ops done.
D
D sparse_weight ...
D sparse_weight done.
D
I GraphPreparing : 100%|████████████████████████████████████████████| 9/9 [00:00<00:00, 2840.60it/s]
I Quantizating : 100%|███████████████████████████████████████████████| 9/9 [00:00<00:00, 126.35it/s]
D
D quant_optimizer ...
D quant_optimizer results:
D adjust_relu: ['/layer2/layer2.1/Relu', '/layer1/layer1.1/Relu']
D quant_optimizer done.
D
W build: The default input dtype of 'input' 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 'output' 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: [22:53:27.541] compress = 0, conv_eltwise_activation_fuse = 1, global_fuse = 1, multi-core-model-mode = 7, output_opti mize = 1, layout_match = 1, enable_argb_group = 0
I RKNN: librknnc version: 2.0.0b0 (35a6907d79@2024-03-24T02:34:11)
D RKNN: [22:53:27.894] RKNN is invoked
D RKNN: [22:53:28.616] >>>>>> start: rknn::RKNNExtractCustomOpAttrs
D RKNN: [22:53:28.617] <<<<<<<< end: rknn::RKNNExtractCustomOpAttrs
D RKNN: [22:53:28.618] >>>>>> start: rknn::RKNNSetOpTargetPass
D RKNN: [22:53:28.618] <<<<<<<< end: rknn::RKNNSetOpTargetPass
D RKNN: [22:53:28.618] >>>>>> start: rknn::RKNNBindNorm
D RKNN: [22:53:28.618] <<<<<<<< end: rknn::RKNNBindNorm
D RKNN: [22:53:28.619] >>>>>> start: rknn::RKNNAddFirstConv
D RKNN: [22:53:28.619] <<<<<<<< end: rknn::RKNNAddFirstConv
D RKNN: [22:53:28.619] >>>>>> start: rknn::RKNNEliminateQATDataConvert
D RKNN: [22:53:28.619] <<<<<<<< end: rknn::RKNNEliminateQATDataConvert
D RKNN: [22:53:28.619] >>>>>> start: rknn::RKNNTileGroupConv
D RKNN: [22:53:28.619] <<<<<<<< end: rknn::RKNNTileGroupConv
D RKNN: [22:53:28.620] >>>>>> start: rknn::RKNNTileFcBatchFuse
D RKNN: [22:53:28.620] <<<<<<<< end: rknn::RKNNTileFcBatchFuse
D RKNN: [22:53:28.620] >>>>>> start: rknn::RKNNAddConvBias
D RKNN: [22:53:28.620] <<<<<<<< end: rknn::RKNNAddConvBias
D RKNN: [22:53:28.620] >>>>>> start: rknn::RKNNTileChannel
D RKNN: [22:53:28.622] <<<<<<<< end: rknn::RKNNTileChannel
D RKNN: [22:53:28.623] >>>>>> start: rknn::RKNNPerChannelPrep
D RKNN: [22:53:28.623] <<<<<<<< end: rknn::RKNNPerChannelPrep
D RKNN: [22:53:28.623] >>>>>> start: rknn::RKNNBnQuant
D RKNN: [22:53:28.623] <<<<<<<< end: rknn::RKNNBnQuant
D RKNN: [22:53:28.623] >>>>>> start: rknn::RKNNFuseOptimizerPass
D RKNN: [22:53:28.628] <<<<<<<< end: rknn::RKNNFuseOptimizerPass
D RKNN: [22:53:28.629] >>>>>> start: rknn::RKNNTurnAutoPad
D RKNN: [22:53:28.629] <<<<<<<< end: rknn::RKNNTurnAutoPad
D RKNN: [22:53:28.629] >>>>>> start: rknn::RKNNInitRNNConst
D RKNN: [22:53:28.629] <<<<<<<< end: rknn::RKNNInitRNNConst
D RKNN: [22:53:28.629] >>>>>> start: rknn::RKNNInitCastConst
D RKNN: [22:53:28.630] <<<<<<<< end: rknn::RKNNInitCastConst
D RKNN: [22:53:28.631] >>>>>> start: rknn::RKNNMultiSurfacePass
D RKNN: [22:53:28.631] <<<<<<<< end: rknn::RKNNMultiSurfacePass
D RKNN: [22:53:28.631] >>>>>> start: rknn::RKNNReplaceConstantTensorPass
D RKNN: [22:53:28.631] <<<<<<<< end: rknn::RKNNReplaceConstantTensorPass
D RKNN: [22:53:28.631] >>>>>> start: rknn::RKNNSubgraphManager
D RKNN: [22:53:28.632] <<<<<<<< end: rknn::RKNNSubgraphManager
D RKNN: [22:53:28.632] >>>>>> start: OpEmit
D RKNN: [22:53:28.639] <<<<<<<< end: OpEmit
D RKNN: [22:53:28.639] >>>>>> start: rknn::RKNNLayoutMatchPass
I RKNN: [22:53:28.641] AppointLayout: t->setNativeLayout(64), tname:[/layer1/layer1.1/Relu_output_0]
I RKNN: [22:53:28.641] AppointLayout: t->setNativeLayout(64), tname:[/layer1/layer1.2/MaxPool_output_0]
I RKNN: [22:53:28.641] AppointLayout: t->setNativeLayout(64), tname:[/layer2/layer2.1/Relu_output_0]
I RKNN: [22:53:28.641] AppointLayout: t->setNativeLayout(64), tname:[/layer2/layer2.2/MaxPool_output_0]
I RKNN: [22:53:28.641] AppointLayout: t->setNativeLayout(64), tname:[/fc1/Gemm_2conv_output]
I RKNN: [22:53:28.641] AppointLayout: t->setNativeLayout(64), tname:[output_conv]
I RKNN: [22:53:28.641] AppointLayout: t->setNativeLayout(0), tname:[output]
D RKNN: [22:53:28.641] <<<<<<<< end: rknn::RKNNLayoutMatchPass
D RKNN: [22:53:28.641] >>>>>> start: rknn::RKNNAddSecondaryNode
D RKNN: [22:53:28.642] <<<<<<<< end: rknn::RKNNAddSecondaryNode
D RKNN: [22:53:28.642] >>>>>> start: OpEmit
D RKNN: [22:53:28.663] finish initComputeZoneMap
D RKNN: [22:53:28.665] <<<<<<<< end: OpEmit
D RKNN: [22:53:28.665] >>>>>> start: rknn::RKNNSubGraphMemoryPlanPass
D RKNN: [22:53:28.665] <<<<<<<< end: rknn::RKNNSubGraphMemoryPlanPass
D RKNN: [22:53:28.665] >>>>>> start: rknn::RKNNProfileAnalysisPass
D RKNN: [22:53:28.665] node: Reshape:/fc2/Gemm_2conv_reshape2, Target: NPU
D RKNN: [22:53:28.665] <<<<<<<< end: rknn::RKNNProfileAnalysisPass
D RKNN: [22:53:28.667] >>>>>> start: rknn::RKNNOperatorIdGenPass
D RKNN: [22:53:28.667] <<<<<<<< end: rknn::RKNNOperatorIdGenPass
D RKNN: [22:53:28.667] >>>>>> start: rknn::RKNNWeightTransposePass
W RKNN: [22:53:28.710] Warning: Tensor /fc2/Gemm_2conv_reshape2_shape need paramter qtype, type is set to float16 by default!
W RKNN: [22:53:28.710] Warning: Tensor /fc2/Gemm_2conv_reshape2_shape need paramter qtype, type is set to float16 by default!
D RKNN: [22:53:28.710] <<<<<<<< end: rknn::RKNNWeightTransposePass
D RKNN: [22:53:28.710] >>>>>> start: rknn::RKNNCPUWeightTransposePass
D RKNN: [22:53:28.710] <<<<<<<< end: rknn::RKNNCPUWeightTransposePass
D RKNN: [22:53:28.710] >>>>>> start: rknn::RKNNModelBuildPass
D RKNN: [22:53:28.723] <<<<<<<< end: rknn::RKNNModelBuildPass
D RKNN: [22:53:28.723] >>>>>> start: rknn::RKNNModelRegCmdbuildPass
D RKNN: [22:53:28.728] ------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------
D RKNN: [22:53:28.728] Network Layer Information Table
D RKNN: [22:53:28.728] ------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------
D RKNN: [22:53:28.728] ID OpType DataType Target InputShape OutputShape C ycles(DDR/NPU/Total) RW(KB) FullName
D RKNN: [22:53:28.728] ------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------
D RKNN: [22:53:28.728] 0 InputOperator INT8 CPU \ (1,1,28,28) 0 /0/0 0 InputOperator:input
D RKNN: [22:53:28.728] 1 ConvRelu INT8 NPU (1,1,28,28),(32,1,5,5),(32) (1,32,28,28) 4 762/19600/19600 4 Conv:/layer1/layer1.0/Conv
D RKNN: [22:53:28.728] 2 MaxPool INT8 NPU (1,32,28,28) (1,32,14,14) 5 092/0/5092 24 MaxPool:/layer1/layer1.2/MaxPool
D RKNN: [22:53:28.728] 3 ConvRelu INT8 NPU (1,32,14,14),(64,32,5,5),(64) (1,64,14,14) 1 1451/20800/20800 56 Conv:/layer2/layer2.0/Conv
D RKNN: [22:53:28.728] 4 MaxPool INT8 NPU (1,64,14,14) (1,64,7,7) 2 546/0/2546 12 MaxPool:/layer2/layer2.2/MaxPool
D RKNN: [22:53:28.728] 5 Conv INT8 NPU (1,64,7,7),(1000,64,7,7),(1000) (1,1000,1,1) 5 11113/98784/511113 3073 Conv:/fc1/Gemm_2conv
D RKNN: [22:53:28.728] 6 Conv INT8 NPU (1,1000,1,1),(10,1000,1,1),(10) (1,10,1,1) 1 811/512/1811 10 Conv:/fc2/Gemm_2conv
D RKNN: [22:53:28.728] 7 Reshape INT8 NPU (1,10,1,1),(2) (1,10) 7 /0/7 0 Reshape:/fc2/Gemm_2conv_reshape2
D RKNN: [22:53:28.728] 8 OutputOperator INT8 CPU (1,10) \ 0 /0/0 0 OutputOperator:output
D RKNN: [22:53:28.728] ------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------
D RKNN: [22:53:28.728] <<<<<<<< end: rknn::RKNNModelRegCmdbuildPass
D RKNN: [22:53:28.728] >>>>>> start: rknn::RKNNFlatcModelBuildPass
D RKNN: [22:53:28.743] Export Mini RKNN model to /tmp/tmprfrugb2m/check.rknn
D RKNN: [22:53:28.743] >>>>>> end: rknn::RKNNFlatcModelBuildPass
D RKNN: [22:53:28.743] >>>>>> start: rknn::RKNNMemStatisticsPass
D RKNN: [22:53:28.743] ------------------------------------------------------------------------------------------------------- -----------------------------------
D RKNN: [22:53:28.743] Feature Tensor Information Table
D RKNN: [22:53:28.743] ------------------------------------------------------------------------------------------------------- -+---------------------------------
D RKNN: [22:53:28.743] ID User Tensor DataType DataFormat OrigShape NativeShape | [Start End) Size
D RKNN: [22:53:28.743] ------------------------------------------------------------------------------------------------------- -+---------------------------------
D RKNN: [22:53:28.743] 1 ConvRelu input INT8 NC1HWC2 (1,1,28,28) (1,1,28,28,1) | 0x00311000 0x00311380 0x00000380
D RKNN: [22:53:28.743] 2 MaxPool /layer1/layer1.1/Relu_output_0 INT8 NC1HWC2 (1,32,28,28) (1,2,28,28,16) | 0x00311380 0x00317580 0x00006200
D RKNN: [22:53:28.743] 3 ConvRelu /layer1/layer1.2/MaxPool_output_0 INT8 NC1HWC2 (1,32,14,14) (1,2,14,14,16) | 0x00317580 0x00318e00 0x00001880
D RKNN: [22:53:28.743] 4 MaxPool /layer2/layer2.1/Relu_output_0 INT8 NC1HWC2 (1,64,14,14) (1,4,14,14,16) | 0x00311000 0x00314100 0x00003100
D RKNN: [22:53:28.743] 5 Conv /layer2/layer2.2/MaxPool_output_0 INT8 NC1HWC2 (1,64,7,7) (1,4,7,7,16) | 0x00314100 0x00314e00 0x00000d00
D RKNN: [22:53:28.743] 6 Conv /fc1/Gemm_2conv_output INT8 NC1HWC2 (1,1000,1,1) (1,63,1,1,16) | 0x00311000 0x003113f0 0x000003f0
D RKNN: [22:53:28.743] 7 Reshape output_conv INT8 NC1HWC2 (1,10,1,1) (1,1,1,1,16) | 0x00311400 0x00311410 0x00000010
D RKNN: [22:53:28.743] 8 OutputOperator output INT8 UNDEFINED (1,10) (1,10) | 0x00311040 0x00311080 0x00000040
D RKNN: [22:53:28.743] ------------------------------------------------------------------------------------------------------- -+---------------------------------
D RKNN: [22:53:28.743] ------------------------------------------------------------------------------------------------------
D RKNN: [22:53:28.743] Const Tensor Information Table
D RKNN: [22:53:28.743] --------------------------------------------------------------------+---------------------------------
D RKNN: [22:53:28.743] ID User Tensor DataType OrigShape | [Start End) Size
D RKNN: [22:53:28.743] --------------------------------------------------------------------+---------------------------------
D RKNN: [22:53:28.743] 1 ConvRelu layer1.0.weight INT8 (32,1,5,5) | 0x00000000 0x00000c80 0x00000c80
D RKNN: [22:53:28.743] 1 ConvRelu layer1.0.bias INT32 (32) | 0x00000c80 0x00000d80 0x00000100
D RKNN: [22:53:28.743] 3 ConvRelu layer2.0.weight INT8 (64,32,5,5) | 0x00000d80 0x0000d580 0x0000c800
D RKNN: [22:53:28.743] 3 ConvRelu layer2.0.bias INT32 (64) | 0x0000d580 0x0000d780 0x00000200
D RKNN: [22:53:28.743] 5 Conv fc1.weight INT8 (1000,64,7,7) | 0x0000d780 0x0030b180 0x002fda00
D RKNN: [22:53:28.743] 5 Conv fc1.bias INT32 (1000) | 0x0030b180 0x0030d100 0x00001f80
D RKNN: [22:53:28.743] 6 Conv fc2.weight INT8 (10,1000,1,1) | 0x0030d100 0x0030f880 0x00002780
D RKNN: [22:53:28.743] 6 Conv fc2.bias INT32 (10) | 0x0030f880 0x0030f900 0x00000080
D RKNN: [22:53:28.743] 7 Reshape /fc2/Gemm_2conv_reshape2_shape INT64 (2) | 0x0030f900*0x0030f940 0x00000040
D RKNN: [22:53:28.743] --------------------------------------------------------------------+---------------------------------
D RKNN: [22:53:28.745] ----------------------------------------
D RKNN: [22:53:28.745] Total Internal Memory Size: 31.5KB
D RKNN: [22:53:28.745] Total Weight Memory Size: 3134.31KB
D RKNN: [22:53:28.745] ----------------------------------------
D RKNN: [22:53:28.745] <<<<<<<< end: rknn::RKNNMemStatisticsPass
I rknn buiding done.
done
done