毕设 (4) - 完成了宿舍环境声音和车辆声音的分类识别
将项目推送到gitcode:https://gitcode.com/qq_72569odp/VIMotion.git
还有个阳台的训练,但是可能识别不是很准确没有去测试
加载过慢请开启缓存 浏览器默认开启
将项目推送到gitcode:https://gitcode.com/qq_72569odp/VIMotion.git
还有个阳台的训练,但是可能识别不是很准确没有去测试
功能并没有改变,依然只是装了一个土壤湿度传感器和水泵
如果完善了的话,应该挺有意思,一个可爱的盆栽,能说话的盆栽,会自己动的盆栽…
对话界面展示:

图1:OpenClaw智能对话界面,实现自然语言交互
实时数据监控:

图2:通过串口实时监控传感器数据
定时时数据监控:

图3:通过openclaw定时向微控制器发送请求
原来的代码
/**
* @brief 打印校准后的传感器数据(物理单位)
*/
#define INT_PART(x) ((int)(x))
#define FRAC_PART(x) (abs((int)(((x) - (int)(x)) * 100 + 0.5)) % 100)
void mpu6050_print_calibrated(void)
{
mpu6050_raw_data_t raw_data;
mpu6050_calibrated_data_t calibrated_data, corrected_data;
if (mpu6050_read_raw(&raw_data) == ERRCODE_SUCC) {
mpu6050_convert_to_physical(&raw_data, &calibrated_data);
mpu6050_apply_calibration(&calibrated_data, &corrected_data);
osal_printk("=== MPU6050 Data ===\r\n");
osal_printk("Temperature: %d C\r\n", (int)corrected_data.temperature_c);
osal_printk("Accel: X=%d.%03d g, Y=%d.%03d g, Z=%d.%03d g\r\n",
(int)corrected_data.accel_x_g, (int)(abs(corrected_data.accel_x_g * 1000 + 0.5) % 1000),
(int)corrected_data.accel_y_g, (int)(abs(corrected_data.accel_y_g * 1000 + 0.5) % 1000),
(int)corrected_data.accel_z_g, (int)(abs(corrected_data.accel_z_g * 1000 + 0.5) % 1000));
osal_printk("Gyro: X=%d.%02d dps, Y=%d.%02d dps, Z=%d.%02d dps\r\n\r\n",
INT_PART(corrected_data.gyro_x_dps), FRAC_PART(corrected_data.gyro_x_dps),
INT_PART(corrected_data.gyro_y_dps), FRAC_PART(corrected_data.gyro_y_dps),
INT_PART(corrected_data.gyro_z_dps), FRAC_PART(corrected_data.gyro_z_dps));
osal_printk("====================\r\n");
}
}
修改后的代码
void mpu6050_print_calibrated(mpu6050_calibrated_data_t *corrected_data)
{
// 将浮点数转换为整数(毫g单位和百分度每秒)
int16_t accel_x_mg = (int16_t)(corrected_data->accel_x_g * 1000 + 0.5f);
int16_t accel_y_mg = (int16_t)(corrected_data->accel_y_g * 1000 + 0.5f);
int16_t accel_z_mg = (int16_t)(corrected_data->accel_z_g * 1000 + 0.5f);
int16_t gyro_x_cdps = (int16_t)(corrected_data->gyro_x_dps * 100 + 0.5f); // 百分度每秒
int16_t gyro_y_cdps = (int16_t)(corrected_data->gyro_y_dps * 100 + 0.5f);
int16_t gyro_z_cdps = (int16_t)(corrected_data->gyro_z_dps * 100 + 0.5f);
osal_printk("=== MPU6050 Data ===\r\n");
osal_printk("Temperature: %d C\r\n", (int)corrected_data->temperature_c);
// 打印加速度计(单位:g)
osal_printk("Accel: X=%d.%03d g, Y=%d.%03d g, Z=%d.%03d g\r\n", accel_x_mg / 1000, abs(accel_x_mg) % 1000,
accel_y_mg / 1000, abs(accel_y_mg) % 1000, accel_z_mg / 1000, abs(accel_z_mg) % 1000);
// 打印陀螺仪(单位:dps)
osal_printk("Gyro: X=%d.%02d dps, Y=%d.%02d dps, Z=%d.%02d dps\r\n", gyro_x_cdps / 100, abs(gyro_x_cdps) % 100,
gyro_y_cdps / 100, abs(gyro_y_cdps) % 100, gyro_z_cdps / 100, abs(gyro_z_cdps) % 100);
osal_printk("====================\r\n\r\n");
}
貌似两个代码都是同一个AI生成的。。
#define INT_PART(x) ((int)(x))
#define FRAC_PART(x) (abs((int)(((x) - (int)(x)) * 100 + 0.5)) % 100)
osal_printk("Accel: X=%d.%03d g, Y=%d.%03d g, Z=%d.%03d g\r\n",
(int)corrected_data.accel_x_g, (int)(abs(corrected_data.accel_x_g * 1000 + 0.5) % 1000),
(int)corrected_data.accel_y_g, (int)(abs(corrected_data.accel_y_g * 1000 + 0.5) % 1000),
(int)corrected_data.accel_z_g, (int)(abs(corrected_data.accel_z_g * 1000 + 0.5) % 1000));
int16_t accel_z_mg = (int16_t)(corrected_data->accel_z_g * 1000.0f + 0.5f);
osal_printk("Accel: X=%d.%03d g, Y=%d.%03d g, Z=%d.%03d g\r\n",
accel_x_mg / 1000, abs(accel_x_mg) % 1000,
accel_y_mg / 1000, abs(accel_y_mg) % 1000,
accel_z_mg / 1000, abs(accel_z_mg) % 1000);
您的代码:在格式化字符串中直接计算,每次打印都重新计算
我的代码:先计算好整数结果,再打印
您的代码:corrected_data.accel_z_g * 1000 + 0.5 中的 corrected_data.accel_z_g 是浮点数,直接用在 abs() 中
我的代码:先将浮点数转换为整数,后续全部用整数运算
您的代码:abs(corrected_data.accel_z_g * 1000 + 0.5) - abs() 是整数函数,但传入的是浮点数表达式
我的代码:abs(accel_z_mg) - accel_z_mg 已经是整数
当 corrected_data.accel_z_g = 0.1000 时:
您的代码执行过程:
corrected_data.accel_z_g * 1000 + 0.5 = 100.5(浮点数)
将这个浮点数传入 abs() - 但 abs() 期望整数参数
编译器可能隐式转换:(int)100.5 = 100
abs(100) = 100
100 % 1000 = 100 ✓ 按理应该显示正确
但为什么显示0? 可能是编译器在处理浮点数到整数的隐式转换时出了问题,或者浮点精度导致 100.5 变成了 99.999,取整后变成99。
我的代码执行过程:
corrected_data.accel_z_g * 1000.0f + 0.5f = 100.5f
显式转换为 (int16_t)100.5f = 100(四舍五入正确)
accel_z_mg = 100
accel_z_mg / 1000 = 0
abs(accel_z_mg) % 1000 = 100
打印 “0.100 g” ✓
您的代码逻辑上是正确的,但可能因为:
隐式类型转换的问题
浮点数精度损失
编译器优化导致的问题
模块的DI连接mcu的DO,DOUT连接模块的DI
又因为奇怪的原因浪费三四天时间
es8311音频播放代码
/*使用本案例前先把sio_porting.h和sio_porting.c中的FREQ_OF_NEED改成16*/
#include "i2s.h"
#include "watchdog.h"
#include "hal_sio.h"
#include "hal_dma.h"
#include "soc_osal.h"
#include "app_init.h"
#include "converted_audio_data.h"
#include "common_def.h"
#include "osal_debug.h"
#include "cmsis_os2.h"
#include "pinctrl.h"
#include "i2c.h"
#define I2S_DIV_NUMBER 16
#define I2S_CHANNEL_NUMBER 2
#define I2S_TX_INT_THRESHOLD 7
#define I2S_RX_INT_THRESHOLD 1
#define I2S_DMA_SRC_WIDTH 2
#define I2S_DMA_DEST_WIDTH 2
#define I2S_DMA_BURST_LENGTH 0
#define I2S_DMA_TRANS_STEP 2
#define I2S_TASK_PRIO 24
#define I2S_TASK_STACK_SIZE 0xc00
extern const int16_t my_converted_audio[];
#define MY_AUDIO_SAMPLES_LEN 56842
int16_t my_txdata[256] = {0};
#define I2S_SAMPLE_DELAY_MS 1000
/* ES8311 I2C配置 */
#define ES8311_I2C_BUS 1 /* I2C总线号 */
#define ES8311_I2C_ADDR 0x18 /* ES8311的7位I2C地址 */
#define ES8311_I2C_BAUDRATE 400000 /* I2C波特率 400kHz */
#define AUDIO_PLAY_BUF_SIZE 4096
#define ES8311_RESET_REG00 0x00 /* reset digital,csm,clock manager etc. */
#define ES8311_CLK_MANAGER_REG01 0x01 /* select clk src for mclk, enable clock for codec */
#define ES8311_CLK_MANAGER_REG02 0x02 /* clk divider and clk multiplier */
#define ES8311_CLK_MANAGER_REG03 0x03 /* adc fsmode and osr */
#define ES8311_CLK_MANAGER_REG04 0x04 /* dac osr */
#define ES8311_CLK_MANAGER_REG05 0x05 /* clk divier for adc and dac */
#define ES8311_CLK_MANAGER_REG06 0x06 /* bclk inverter and divider */
#define ES8311_CLK_MANAGER_REG07 0x07 /* tri-state, lrck divider */
#define ES8311_CLK_MANAGER_REG08 0x08 /* lrck divider */
#define ES8311_SDPIN_REG09 0x09 /* dac serial digital port */
#define ES8311_SDPOUT_REG0A 0x0A /* adc serial digital port */
#define ES8311_SYSTEM_REG0B 0x0B /* system */
#define ES8311_SYSTEM_REG0C 0x0C /* system */
#define ES8311_SYSTEM_REG0D 0x0D /* system, power up/down */
#define ES8311_SYSTEM_REG0E 0x0E /* system, power up/down */
#define ES8311_SYSTEM_REG0F 0x0F /* system, low power */
#define ES8311_SYSTEM_REG10 0x10 /* system */
#define ES8311_SYSTEM_REG11 0x11 /* system */
#define ES8311_SYSTEM_REG12 0x12 /* system, Enable DAC */
#define ES8311_SYSTEM_REG13 0x13 /* system */
#define ES8311_SYSTEM_REG14 0x14 /* system, select DMIC, select analog pga gain */
#define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */
#define ES8311_ADC_REG16 0x16 /* ADC */
#define ES8311_ADC_REG17 0x17 /* ADC, volume */
#define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */
#define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */
#define ES8311_ADC_REG1A 0x1A /* ADC, alc automute */
#define ES8311_ADC_REG1B 0x1B /* ADC, alc automute, adc hpf s1 */
#define ES8311_ADC_REG1C 0x1C /* ADC, equalizer, hpf s2 */
#define ES8311_DAC_REG31 0x31 /* DAC, mute */
#define ES8311_DAC_REG32 0x32 /* DAC, volume */
#define ES8311_DAC_REG33 0x33 /* DAC, offset */
#define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */
#define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */
#define ES8311_DAC_REG37 0x37 /* DAC, ramprate */
#define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */
#define ES8311_GP_REG45 0x45 /* GP CONTROL */
#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */
#define ES8311_CHD2_REGFE 0xFE /* CHIP ID2 */
#define ES8311_CHVER_REGFF 0xFF /* VERSION */
#define ES8311_MAX_REGISTER 0xFF
#define ES8311_PDN_DAC_BIT (1 << 1)
#define CODEC_DEVICE_ADDR 0x30 /* 0011 00x */
typedef enum {
HI_CODEC_SAMPLE_RATE_8K = 8,
HI_CODEC_SAMPLE_RATE_16K = 16,
HI_CODEC_SAMPLE_RATE_32K = 32,
HI_CODEC_SAMPLE_RATE_48K = 48,
} hi_codec_sample_rate;
typedef enum {
HI_CODEC_RESOLUTION_16BIT = 16,
HI_CODEC_RESOLUTION_24BIT = 24,
} hi_codec_resolution;
typedef struct {
hi_codec_sample_rate sample_rate;
hi_codec_resolution resolution;
} hi_codec_attribute;
/**
* @brief ES8311写寄存器
* @param reg_addr 寄存器地址
* @param data 写入数据
* @return ERRCODE_SUCC成功,其他失败
*/
static errcode_t es8311_write_reg(uint8_t reg_addr, uint8_t data)
{
uint8_t tx_buf[2] = {reg_addr, data};
i2c_data_t i2c_data = {.send_buf = tx_buf, .send_len = 2, .receive_buf = NULL, .receive_len = 0};
return uapi_i2c_master_write(ES8311_I2C_BUS, ES8311_I2C_ADDR, &i2c_data);
}
/**
* @brief ES8311读寄存器
* @param reg_addr 寄存器地址
* @param data 读取数据指针
* @return ERRCODE_SUCC成功,其他失败
*/
static errcode_t __attribute__((unused)) es8311_read_reg(uint8_t reg_addr, uint8_t *data)
{
errcode_t ret;
uint8_t tx_buf[1] = {reg_addr};
uint8_t rx_buf[1] = {0};
i2c_data_t i2c_data;
/* 先写寄存器地址 */
i2c_data.send_buf = tx_buf;
i2c_data.send_len = 1;
i2c_data.receive_buf = NULL;
i2c_data.receive_len = 0;
ret = uapi_i2c_master_write(ES8311_I2C_BUS, ES8311_I2C_ADDR, &i2c_data);
if (ret != ERRCODE_SUCC) {
return ret;
}
/* 再读寄存器数据 */
osal_msleep(1);
i2c_data.send_buf = NULL;
i2c_data.send_len = 0;
i2c_data.receive_buf = rx_buf;
i2c_data.receive_len = 1;
ret = uapi_i2c_master_read(ES8311_I2C_BUS, ES8311_I2C_ADDR, &i2c_data);
if (ret == ERRCODE_SUCC) {
*data = rx_buf[0];
}
return ret;
}
__attribute__((unused)) static uint32_t codec_set_gain(void)
{
uint32_t ret = ERRCODE_SUCC;
ret |= es8311_write_reg(ES8311_SYSTEM_REG0E, 0x02);
ret |= es8311_write_reg(ES8311_SYSTEM_REG0F, 0x44);
ret |= es8311_write_reg(ES8311_ADC_REG15, 0x40);
ret |= es8311_write_reg(ES8311_ADC_REG1B, 0x0A);
ret |= es8311_write_reg(ES8311_ADC_REG1C, 0x6A);
ret |= es8311_write_reg(ES8311_ADC_REG17, 0xBF);
ret |= es8311_write_reg(ES8311_DAC_REG37, 0x48);
ret |= es8311_write_reg(ES8311_DAC_REG32, 0x84);
ret |= es8311_write_reg(ES8311_ADC_REG16, 0x22);
ret |= es8311_write_reg(ES8311_ADC_REG17, 0xDF);
ret |= es8311_write_reg(ES8311_ADC_REG18, 0x87);
ret |= es8311_write_reg(ES8311_ADC_REG19, 0xFB);
ret |= es8311_write_reg(ES8311_ADC_REG1A, 0x03);
ret |= es8311_write_reg(ES8311_ADC_REG1B, 0xEA);
return ret;
}
/**
* @brief 初始化ES8311音频编解码器
* @param codec_attr 编解码器属性配置
* @return ERRCODE_SUCC成功,其他失败
*/
__attribute__((unused)) static errcode_t es8311_init(const hi_codec_attribute *codec_attr)
{
uint32_t ret;
osal_printk("========== ES8311 Init Start ==========\r\n");
if (codec_attr == NULL) {
osal_printk("codec_attr is NULL!\r\n");
return ERRCODE_FAIL;
}
/* 配置采样率 */
osal_printk("ES8311 sample_rate: %dK, resolution: %dbit\r\n", codec_attr->sample_rate, codec_attr->resolution);
ret = es8311_write_reg(ES8311_GPIO_REG44, 0x08);
osal_msleep(5);
ret |= es8311_write_reg(ES8311_DAC_REG31, 0x40);
ret |= es8311_write_reg(ES8311_RESET_REG00, 0x1F);
ret |= es8311_write_reg(ES8311_GP_REG45, 0x00);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG01, 0x30);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG02, 0x10);
if (codec_attr->sample_rate == HI_CODEC_SAMPLE_RATE_8K) {
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG02, 0xA0);
} else if (codec_attr->sample_rate == HI_CODEC_SAMPLE_RATE_16K) {
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG02, 0x40);
} else if (codec_attr->sample_rate == HI_CODEC_SAMPLE_RATE_32K) {
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG02, 0x48);
} else if (codec_attr->sample_rate == HI_CODEC_SAMPLE_RATE_48K) {
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG02, 0x00);
}
if ((codec_attr->resolution == HI_CODEC_RESOLUTION_16BIT)) {
ret |= es8311_write_reg(ES8311_SDPIN_REG09, 0x0C);
ret |= es8311_write_reg(ES8311_SDPOUT_REG0A, 0x0C);
} else {
ret |= es8311_write_reg(ES8311_SDPIN_REG09, 0x00);
ret |= es8311_write_reg(ES8311_SDPOUT_REG0A, 0x00);
}
return ERRCODE_SUCC;
}
static void i2s_dma_master_init(void)
{
uapi_i2s_deinit(SIO_BUS_0);
uapi_i2s_init(SIO_BUS_0, NULL);
sio_porting_i2s_pinmux();
uapi_pin_set_mode(7, PIN_MODE_4);
i2s_config_t config = {
.drive_mode = MASTER,
.transfer_mode = STD_MODE,
.data_width = SIXTEEN_BIT,
.channels_num = TWO_CH,
.timing = NONE_TIMING_MODE,
.clk_edge = RISING_EDGE,
.div_number = I2S_DIV_NUMBER,
.number_of_channels = I2S_CHANNEL_NUMBER,
};
i2s_dma_attr_t attr = {
.tx_dma_enable = 1,
.tx_int_threshold = I2S_TX_INT_THRESHOLD,
.rx_dma_enable = 0,
.rx_int_threshold = I2S_RX_INT_THRESHOLD,
};
uapi_i2s_set_config(SIO_BUS_0, &config);
uapi_i2s_dma_config(SIO_BUS_0, &attr);
}
static void *i2s_dma_master_task(const char *arg)
{
/* 配置I2C引脚:GPIO15为SCL,GPIO16为SDA,设置为模式2(I2C功能) */
uapi_pin_set_mode(15, PIN_MODE_2); /* I2C_SCL */
uapi_pin_set_mode(16, PIN_MODE_2); /* I2C_SDA */
/* 初始化I2C主机 */
uapi_i2c_master_init(ES8311_I2C_BUS, ES8311_I2C_BAUDRATE, 0);
/* 配置编解码器属性 */
__attribute__((unused)) hi_codec_attribute codec_attr = {
.sample_rate = HI_CODEC_SAMPLE_RATE_16K, .resolution = HI_CODEC_RESOLUTION_16BIT, /* 16位分辨率 */
};
/* 初始化ES8311 */
// es8311_init(&codec_attr);
unused(arg);
int32_t ret = 128;
#if defined(CONFIG_SIO_USING_V151)
ret = ERRCODE_SUCC;
#endif
osal_printk("ret=%d\r\n", ret);
uapi_dma_deinit();
i2s_dma_master_init();
/* DMA init. */
uapi_dma_init();
uapi_dma_open();
i2s_dma_config_t dma_cfg = {
.src_width = I2S_DMA_SRC_WIDTH,
.dest_width = I2S_DMA_DEST_WIDTH,
.burst_length = I2S_DMA_BURST_LENGTH,
.priority = 0,
};
osal_printk("playing threading start.\r\n");
int p = 0;
while (1) {
uapi_watchdog_kick();
while (1) {
for (int i = 0; i < 64; i++) {
for (int j = 0; j < 4; j++) {
my_txdata[i * 4 + j] = my_converted_audio[p + i];
}
}
int16_t length = 128;
if (uapi_i2s_merge_write_by_dma(SIO_BUS_0, &my_txdata, length, &dma_cfg, (uintptr_t)NULL, true) != ret) {
osal_printk("master uapi_i2s_merge_write_by_dma error.\r\n");
}
p += 64; // 更新 p 以指向下一个数据块
if (p > MY_AUDIO_SAMPLES_LEN - 64) {
osal_printk("master uapi_i2s_merge_write_by_dma end.\r\n");
p = 0; // 重置 p 以循环播放音频数据
break; // 跳出循环,等待下一次播放
}
}
}
return NULL;
}
static void i2s_entry(void)
{
osal_task *task_handle = NULL;
osal_kthread_lock();
task_handle =
osal_kthread_create((osal_kthread_handler)i2s_dma_master_task, 0, "I2sDmaMasterTask", I2S_TASK_STACK_SIZE);
if (task_handle != NULL) {
osal_kthread_set_priority(task_handle, I2S_TASK_PRIO);
}
osal_kthread_unlock();
}
/* Run the i2s_entry. */
app_run(i2s_entry);
下一步准备开始数据采集打标签
做了一点不想做了,看了一些毕设的评论突然很着急,为什么他们进度那么快啊,我们学校都没什么通知,只是在1月30号让我们申请选题。为什么有的三月已经准备完成了,为什么有的上学期完成了,有点急啊。
唉,昨天发现一个喜欢的up主注销账号了,明明14号的时候才关注我,那时候真的很开心。新年也结束了,悲伤的氛围。又要找工作。又要准备毕设。真的烦死了。
在开始部署之前,需要准备以下环境:
WS63 (Hi3863) 开发环境
TensorFlow Lite Micro 源码
CMake 构建工具
set(TFLM_SRC_PATH "G:/HiSpark_SDK/fbb_ws63/tflite-micro")
set(TFLM_CORE_PATH "${TFLM_SRC_PATH}/tensorflow/lite")
set(FLATBUFFERS_DIR "G:/HiSpark_SDK/fbb_ws63/tflite-micro/tensorflow/lite/micro/tools/make/downloads/flatbuffers")
set(TFLM_COMPILER_PATH "${TFLM_SRC_PATH}/tensorflow/compiler")
set(RUY_PATHS
"${TFLM_SRC_PATH}/tensorflow/lite/micro/tools/make/downloads/ruy"
)
set(GEMMLOWP_FIXEDPOINT_PATHS
"${TFLM_SRC_PATH}/tensorflow/lite/micro/tools/make/downloads/gemmlowp"
"${TFLM_SRC_PATH}/tmp/tflm-ws63/third_party/gemmlowp"
"${TFLM_SRC_PATH}/third_party/gemmlowp"
)
set(FOUND_GEMMLOWP FALSE)
foreach(path ${GEMMLOWP_FIXEDPOINT_PATHS})
if(EXISTS "${path}/fixedpoint/fixedpoint.h")
set(GEMMLOWP_PATH ${path})
set(FOUND_GEMMLOWP TRUE)
message(STATUS "✓ Found gemmlowp at: ${path}")
break()
endif()
endforeach()
unset(PUBLIC_HEADER)
if(FOUND_GEMMLOWP)
list(APPEND PUBLIC_HEADER
${GEMMLOWP_PATH}
)
endif()
# 设置 PUBLIC_HEADER 变量以包含所有需要的头文件路径
list(APPEND PUBLIC_HEADER
${TFLM_SRC_PATH}
${TFLM_SRC_PATH}/tensorflow
${TFLM_SRC_PATH}/tensorflow/lite/kernels
${TFLM_CORE_PATH}
${TFLM_CORE_PATH}/micro
${TFLM_CORE_PATH}/micro/kernels
${TFLM_CORE_PATH}/micro/memory_planner
${TFLM_CORE_PATH}/micro/testing
${TFLM_CORE_PATH}/schema
${TFLM_CORE_PATH}/c
${TFLM_CORE_PATH}/core/api
${FLATBUFFERS_DIR}/include
${TFLM_CORE_PATH}/micro/kernels/cmsis_nn
CACHE INTERNAL ""
)
list(APPEND PUBLIC_HEADER ${RUY_PATHS})
# 设置源文件
set(CORE_API_SOURCES
"${TFLM_CORE_PATH}/core/api/flatbuffer_conversions.cc"
"${TFLM_CORE_PATH}/micro/tflite_bridge/flatbuffer_conversions_bridge.cc"
"${TFLM_CORE_PATH}/core/api/tensor_utils.cc"
)
set(SCHEMA_SOURCES
"${TFLM_COMPILER_PATH}/mlir/lite/schema/schema_utils.cc"
"${TFLM_CORE_PATH}/micro/flatbuffer_utils.cc"
)
set(C_API_SOURCES
"${TFLM_CORE_PATH}/core/c/common.cc"
)
set(CORE_INTERPRETER_SOURCES
"${TFLM_CORE_PATH}/micro/micro_allocator.cc"
"${TFLM_CORE_PATH}/micro/micro_interpreter.cc"
"${TFLM_CORE_PATH}/micro/micro_utils.cc"
"${TFLM_CORE_PATH}/micro/micro_op_resolver.cc"
"${TFLM_CORE_PATH}/micro/micro_context.cc"
"${TFLM_CORE_PATH}/micro/micro_interpreter_context.cc"
"${TFLM_CORE_PATH}/micro/micro_interpreter_graph.cc"
)
set(MEMORY_SOURCES
"${TFLM_CORE_PATH}/micro/memory_planner/greedy_memory_planner.cc"
"${TFLM_CORE_PATH}/micro/memory_planner/linear_memory_planner.cc"
"${TFLM_CORE_PATH}/micro/arena_allocator/single_arena_buffer_allocator.cc"
"${TFLM_CORE_PATH}/micro/arena_allocator/persistent_arena_buffer_allocator.cc"
"${TFLM_CORE_PATH}/micro/arena_allocator/non_persistent_arena_buffer_allocator.cc"
"${TFLM_CORE_PATH}/micro/arena_allocator/recording_single_arena_buffer_allocator.cc"
"${TFLM_CORE_PATH}/micro/recording_micro_allocator.cc"
"${TFLM_CORE_PATH}/micro/memory_helpers.cc"
"${TFLM_CORE_PATH}/micro/micro_allocation_info.cc"
)
set(KERNEL_REGISTRY_SOURCES
"${TFLM_CORE_PATH}/micro/kernels/kernel_runner.cc"
"${TFLM_CORE_PATH}/micro/kernels/kernel_util.cc"
)
set(KERNEL_COMMON_SOURCES
"${TFLM_CORE_PATH}/micro/kernels/fully_connected_common.cc"
"${TFLM_CORE_PATH}/micro/kernels/softmax_common.cc"
"${TFLM_CORE_PATH}/micro/kernels/conv_common.cc"
"${TFLM_CORE_PATH}/micro/kernels/depthwise_conv_common.cc"
"${TFLM_CORE_PATH}/micro/kernels/pooling_common.cc"
"${TFLM_CORE_PATH}/micro/kernels/add_common.cc"
"${TFLM_CORE_PATH}/micro/kernels/mul_common.cc"
)
set(KERNEL_IMPLEMENTATION_SOURCES
"${TFLM_CORE_PATH}/micro/kernels/fully_connected.cc"
"${TFLM_CORE_PATH}/micro/kernels/softmax.cc"
"${TFLM_CORE_PATH}/micro/kernels/reshape.cc"
"${TFLM_CORE_PATH}/micro/kernels/quantize.cc"
"${TFLM_CORE_PATH}/micro/kernels/dequantize.cc"
"${TFLM_CORE_PATH}/micro/kernels/dequantize_common.cc"
"${TFLM_CORE_PATH}/micro/kernels/logistic.cc"
"${TFLM_CORE_PATH}/micro/kernels/add.cc"
"${TFLM_CORE_PATH}/micro/kernels/mul.cc"
"${TFLM_CORE_PATH}/micro/kernels/concatenation.cc"
"${TFLM_CORE_PATH}/micro/kernels/pooling.cc"
)
set(LOG_SOURCES
"${TFLM_CORE_PATH}/micro/micro_log.cc"
"${TFLM_CORE_PATH}/micro/tflite_bridge/micro_error_reporter.cc"
"${TFLM_COMPILER_PATH}/mlir/lite/core/api/error_reporter.cc"
"${TFLM_CORE_PATH}/micro/micro_time.cc"
)
set(TENSOR_UTILS_SOURCES
"${TFLM_CORE_PATH}/kernels/internal/tensor_utils.cc"
"${TFLM_CORE_PATH}/kernels/internal/quantization_util.cc"
"${TFLM_CORE_PATH}/kernels/internal/common.cc"
"${TFLM_CORE_PATH}/kernels/internal/portable_tensor_utils.cc"
"${TFLM_CORE_PATH}/kernels/kernel_util.cc"
)
set(OTHER_SOURCES
"${TFLM_CORE_PATH}/micro/micro_profiler.cc"
"${TFLM_CORE_PATH}/micro/micro_resource_variable.cc"
"${TFLM_CORE_PATH}/micro/test_helpers.cc"
)
# 合并所有源文件
set(ALL_TFLM_SOURCES
${CORE_API_SOURCES}
${SCHEMA_SOURCES}
${C_API_SOURCES}
${CORE_INTERPRETER_SOURCES}
${MEMORY_SOURCES}
${KERNEL_REGISTRY_SOURCES}
${KERNEL_COMMON_SOURCES}
${KERNEL_IMPLEMENTATION_SOURCES}
${LOG_SOURCES}
${TENSOR_UTILS_SOURCES}
${OTHER_SOURCES}
)
set(PUBLIC_HEADER "${PUBLIC_HEADER}" PARENT_SCOPE)
set(SOURCES "${SOURCES}"
${ALL_TFLM_SOURCES}
"${CMAKE_CURRENT_SOURCE_DIR}/tflm_hello_world.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/hello_world_model.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/tflm_debug_log.cc"
PARENT_SCOPE)
在 WS63 嵌入式平台上验证 TensorFlow Lite Micro (TFLM) 的功能,确保 TFLM 库能够正确编译和运行。
src/application/samples/tflm_hello_world/
├── CMakeLists.txt # 构建配置,引用 TFLM 源码
├── tflm_hello_world.cc # 主验证程序 ⭐
├── tflm_debug_log.cc # 平台日志实现 ⭐
├── hello_world_model.cc # TFLite 模型数据
├── test_cpp.cc # C++ 编译测试
└── models/
└── hello_world_model.h # 模型头文件
问题:TFLM 的 micro_log.cc 需要平台相关的 DebugLog 函数。
// ❌ 错误:使用可变参数
extern "C" void DebugLog(const char* format, ...) {
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
osal_printk("%s", buffer);
va_end(args);
}
问题表现:输出乱码
Tensor arena size: 10534308 bytes(应该是 6000)
Status: 恖5(应该是 PASS/FAIL)
// TFLM 的 micro_log.cc 调用方式
void VMicroPrintf(const char* format, va_list args) {
DebugLog(format, args); // ❌ 传递的是 va_list,不是 ...
}
// ✅ 正确:接受 va_list 参数
extern "C" void DebugLog(const char* format, va_list args) {
char buffer[512];
vsnprintf(buffer, sizeof(buffer), format, args);
osal_printk("%s", buffer);
}
// TFLM 还需要这个函数
extern "C" int DebugVsnprintf(char* buffer, size_t buf_size,
const char* format, va_list vlist) {
return vsnprintf(buffer, buf_size, format, vlist);
}
核心流程:
加载模型 (GetModel)
创建 OpResolver (注册 FullyConnected)
创建 Interpreter (分配 TensorArena)
分配张量 (AllocateTensors)
获取输入/输出张量
执行推理测试 (Invoke)
验证结果
关键代码结构:
// 使用 int8 量化模型
input->data.int8[0] = golden_inputs_int8[i];
interpreter.Invoke();
float y_pred = (output->data.int8[0] - zero_point) * scale;
静态内存分配(嵌入式环境要求):
constexpr int kTensorArenaSize = 6000;
static uint8_t tensor_arena[kTensorArenaSize];
避免使用 new/delete,防止动态内存分配问题。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| undefined reference to ‘DebugLog’ | TFLM 需要平台日志函数 | 实现 DebugLog(const char*, va_list) |
| 输出乱码、数值错误 | 函数签名不匹配(... vs va_list) |
修正为接受 va_list 参数 |
| 编译时未发现源文件 | CMakeLists.txt 缺少文件 | 添加 tflm_debug_log.cc 到 SOURCES |
测试 4 个输入值的 sin(x) 预测:
| x (输入) | sin(x) 期望值 | 预测值 | 误差 | 状态 |
|---|---|---|---|---|
| 0.77 | 0.6961 | 0.6964 | 0.00031 | ✅ PASS |
| 1.57 | 1.0000 | 0.9949 | 0.00508 | ✅ PASS |
| 2.30 | 0.7457 | 0.7296 | 0.01610 | ✅ PASS |
| 3.14 | 0.0016 | -0.0083 | 0.00988 | ✅ PASS |
所有测试误差 < 0.05(容差),验证成功!
仔细阅读 TFLM 头文件:debug_log.h 明确定义了 DebugLog 的签名
理解调用链:MicroPrintf → VMicroPrintf → DebugLog(va_list)
嵌入式适配要点:
DebugLog 和 DebugVsnprintfosal_printk 而非 printf量化模型处理:理解 scale 和 zero_point 的反量化公式