惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
Stack Overflow Blog
Stack Overflow Blog
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
T
The Blog of Author Tim Ferriss
博客园 - 叶小钗
N
Netflix TechBlog - Medium
腾讯CDC
C
Check Point Blog
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
S
SegmentFault 最新的问题
F
Fortinet All Blogs
美团技术团队
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 司徒正美
F
Full Disclosure
Recorded Future
Recorded Future
D
DataBreaches.Net
博客园 - 【当耐特】
Martin Fowler
Martin Fowler
J
Java Code Geeks
I
InfoQ
Y
Y Combinator Blog
A
About on SuperTechFans
AI
AI
爱范儿
爱范儿
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Forbes - Security
Forbes - Security
W
WeLiveSecurity
M
MIT News - Artificial intelligence
雷峰网
雷峰网
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
Schneier on Security
Schneier on Security
The GitHub Blog
The GitHub Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
aimingoo的专栏
aimingoo的专栏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
G
GRAHAM CLULEY
Know Your Adversary
Know Your Adversary
Latest news
Latest news
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Docker
Recent Commits to openclaw:main
Recent Commits to openclaw:main
量子位
V2EX - 技术
V2EX - 技术
Project Zero
Project Zero

阿方的博客

[已下线] 博客分布式电源管理系统已上线 - 阿方的博客 嘉立创双十一金豆活动秒杀脚本 - 阿方的博客 GD32VW553-IoT 实现 SoftAP 配网 - 阿方的博客 24MHz 8CH 开源逻辑分析仪指南 - 阿方的博客 从光谱到 RGB - 阿方的博客 记一次 XSS 攻击非法钓鱼网站 - 阿方的博客 MM32:配置开发环境并点亮第一颗 LED - 阿方的博客 DG-LAB APP 郊狼波形导出二维码的解析 - 阿方的博客 新头像!福瑞 OC! - 阿方的博客 【补档】从零开始制作一个矿石作物 Addon - 阿方的博客 关于我 - 阿方的博客
在 MCU 部署边缘 AI —— 以 RA6E2 天气预测为例
Fang_Zhijian · 2025-12-30 · via 阿方的博客

在单片机上使用过去 24h 的天气数据(温度、湿度、大气压强)预测未来 24h 的平均天气

将 TinyMaix 移植到你的单片机

TinyMaix 是面向单片机的超轻量级的神经网络推理库,允许你在任意单片机上运行轻量级深度学习模型

Github:https://github.com/sipeed/TinyMaix

要将 TinyMaix 移植到你的单片机,首先将 TinyMaix 克隆/下载到 PC,在单片机运行 TinyMaix 只需要 includesrc 两个目录中的文件,将它们拷贝到你的工程中

这里以瑞萨的 e2 studio 工程为例,在 src 目录下新建 tinymaix 放置它们:

D:\RA_WORKSPACE\WEATHERSTATION\SRC
└─ tinymaix
    ├─ include
    └─ src

然后按需编辑 tm_port.hPORT CONFIG 部分,现在已经完成了移植

模型训练

数据集处理

为了让预测结果尽可能准确,需要使用本地天气数据的数据集,下面是我使用的原始数据,分为两张表:

日期气温气压湿度
2023-01-01 00:00-9.991024.5648.4
2023-01-01 01:00-10.591024.7450.97
2023-01-01 02:00-11.641025.255.42
日期天气代码
2023-01-01
2023-01-02
2023-01-03

可以规定天气代码:{"晴": 0, "多云": 1, "阴": 2, "雨": 3, "雪": 4}

使用 Python 将数据集分为训练集、验证集两张表,前 72 列是 24h 的天气特征,最后 1 列是天气标签

import pandas as pd
import numpy as np

RAW_H = '逐小时天气_2324.csv'
RAW_D = '逐日天气_2324.csv'
TRAIN_CSV = 'train_72x5.csv'
VAL_CSV = 'val_72x5.csv'

h = pd.read_csv(RAW_H, parse_dates=['日期'])
d = pd.read_csv(RAW_D, parse_dates=['日期'])
wcode = {'晴': 0, '多云': 1, '阴': 2, '雨': 3, '雪': 4}
d['code'] = d['天气代码'].map(wcode)

samples = []
for day in d['日期']:
    hrs = h[h['日期'].dt.date == day.date()]
    if len(hrs) < 24:
        continue
    next_day = day + pd.Timedelta(days=1)
    label_row = d[d['日期'] == next_day]
    if label_row.empty:
        continue
    y = label_row.iloc[0]['code']
    x = hrs.iloc[:24][['气温', '气压', '湿度']].values.flatten()
    samples.append(np.concatenate([x, [y]]))

df = pd.DataFrame(np.array(samples))
# 前 72 列是特征,最后 1 列是标签
train = df[df.index % 10 < 8]  # 简单 8:2 划分
val = df[df.index % 10 >= 8]
train.to_csv(TRAIN_CSV, index=False, header=False)
val.to_csv(VAL_CSV, index=False, header=False)
print('train:', len(train), 'val:', len(val))

训练模型

import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


def load(csv):
    arr = pd.read_csv(csv, header=None).values.astype('float32')
    return arr[:, :72], arr[:, 72].astype('int32')


x_train, y_train = load('train_72x5.csv')
x_val, y_val = load('val_72x5.csv')

mean = x_train.mean(axis=0)
std = x_train.std(axis=0) + 1e-5


def standardize(x):
    return (x - mean) / std


model = tf.keras.Sequential([
    tf.keras.layers.Input(shape=(72,)),
    tf.keras.layers.Dense(128, activation=tf.nn.relu6),
    tf.keras.layers.Dropout(0.3),
    tf.keras.layers.Dense(64, activation=tf.nn.relu6),
    tf.keras.layers.Dropout(0.3),
    tf.keras.layers.Dense(64, activation=tf.nn.relu6),
    tf.keras.layers.Dropout(0.3),
    tf.keras.layers.Dense(5, activation='softmax')
])

opt = tf.keras.optimizers.Adam(learning_rate=1e-4)
model.compile(optimizer=opt,
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
es = tf.keras.callbacks.EarlyStopping(patience=50,
                                      restore_best_weights=True)
history = model.fit(standardize(x_train), y_train,
                    validation_data=(standardize(x_val), y_val),
                    epochs=1000,
                    batch_size=64,
                    callbacks=[es],
                    verbose=1)


def representative_dataset():
    for x in tf.data.Dataset.from_tensor_slices(x_train).batch(1).take(300):
        yield [tf.cast(standardize(x), tf.float32)]


converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8
tflite_model = converter.convert()
open('weather_int8.tflite', 'wb').write(tflite_model)
print('tflite saved, size:', len(tflite_model))

np.save('mean.npy', mean)
np.save('std.npy', std)

plt.figure(figsize=(6, 4))
plt.plot(history.history['loss'], label='train loss')
plt.plot(history.history['val_loss'], label='val loss')
plt.title('Loss curve')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

导出量化参数

使用 Python 脚本导出量化参数 meanstdscalezp

import numpy as np
import tensorflow as tf

mean = np.load('mean.npy')
std = np.load('std.npy')
model = tf.lite.Interpreter(model_path="weather_int8.tflite")
model.allocate_tensors()


def to_c_arr(name, vec):
    print(f'static const float {name}[72] = {{')
    print('  ', end='')
    for i, v in enumerate(vec):
        print(f'{v:.6f}f', end='')
        if i != len(vec) - 1:
            print(',', end='')
        if (i + 1) % 3 == 0:
            print('\n  ', end='')
        else:
            print(' ', end='')
    print('\n};')


input_details = model.get_input_details()[0]
scale = input_details['quantization_parameters']['scales'][0]
zp = input_details['quantization_parameters']['zero_points'][0]

to_c_arr('mean', mean)
to_c_arr('std', std)
print(f'static const float scale = {scale:.6f}f;')
print(f'static const int zp = {zp};')

运行后输出:

static const float mean[72] = {
  6.828718f, 1009.098938f, 68.643417f,
  // ...
};
static const float std[72] = {
  12.587724f, 9.960853f, 19.852306f,
  // ...
};
static const float scale = 0.024126f;
static const int zp = 5;

导出 TinyMiax 模型

导出 TinyMiax 模型需要使用 TinyMiax 的 tflite2tmdl 工具,位于 tools 目录,可以将之前训练得到的 weather_int8.tflite 复制到 TinyMiax 的根目录,执行:

python .\tools\tflite2tmdl.py .\weather_int8.tflite .\weather.tmdl int8 1 72 5

执行结果将生成下面两个文件:

weather_day5.h
weather_day5.tmdl

部署模型

首先将 weather_day5.h 复制到工程中,我放置在了 tinymaix\model 中:

C:\USERS\HP\DESKTOP\DEV\RA_WORKSPACE\WEATHERSTATION\SRC
└─tinymaix
    ├─include
    ├─model
    │      weather.h
    └─src

然后将 meanstdscalezp 粘贴到 hal_entry.c 中,下面就可以开始编写推理程序部分了:

static tm_mat_t outs[1];
static tm_err_t res;
static tm_mat_t in_mat;
static int8_t input[72] = {0};

static void parse_output(tm_mat_t* outs)
{
    tm_mat_t out = outs[0];
    char w_tags[5][10] = {"Sunny", "Cloudy", "Overcast", "Rainy", "Snowy"};
    float* data  = out.dataf;
    float maxp = 0;
    int maxi = -1;
    for(int i=0; i<5; i++){
        printf("%d: %.6f\n", i, data[i]);
        if(data[i] > maxp) {
            maxi = i;
            maxp = data[i];
        }
    }
    w_maxi = maxi;
    printf("### Predict output is: %s, prob %.6f\r\n", w_tags[maxi], maxp);
    return;
}

void tinyML_run(void)
{
    TM_DBGT_INIT();
    tm_mdl_t mdl;

    // 加载模型
    tm_stat((tm_mdlbin_t*)mdl_data);

    res = tm_load(&mdl, mdl_data, NULL, NULL, &in_mat);

    if(res != TM_OK) {
        printf("tm model load err %d\r\n", res);
    }

    // 量化
    for(int i = 0; i < 72; i++){
        float v = (weather[i/3][i%3] - mean[i]) / std[i];
        int q = (int)round(v / scale) + zp;
        if(q > 127)  q = 127;
        if(q < -128) q = -128;
        input[i] = (int8_t)q;
    }

    // 导入输入数据
    in_mat.data = (mtype_t*)input;

    // 开始推理
    TM_DBGT_START();
    res = tm_run(&mdl, &in_mat, outs);
    TM_DBGT("tm_run");

    // 输出结果
    if(res==TM_OK) parse_output(outs);
    else printf("tm run error: %d\n", res);

    // 卸载模型
    tm_unload(&mdl);
}

hal_entry() 中调用 tinyML_run() 即可

完整 hal_entry.c

#include "hal_data.h"
#include "uart/bsp_uart.h"
#include "tinymaix.h"
#include "tinymaix/model/weather.h"

static tm_mat_t outs[1];
static tm_err_t res;
static tm_mat_t in_mat;
static int8_t input[72] = {0};

FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER

static const float mean[72] = {
                               6.828718f, 1009.098938f, 68.643417f,
                               6.503504f, 1008.989441f, 69.627861f,
                               // ...
};
static const float std[72] = {
                              12.587724f, 9.960853f, 19.852306f,
                              12.558513f, 10.011790f, 19.922518f,
                              // ...
};
static const float scale = 0.024126f;
static const int zp = 5;

float weather[24][3] = {
                        {8.66f, 1018.67f, 90.3f},
                        {8.61f, 1018.47f, 89.99f},
                        // ...
};

static const char w_tags[5][10] = {"Sunny", "Cloudy", "Overcast", "Rainy", "Snowy"};
static int8_t  w_maxi = -1;

/*******************************************************************************************************************//**
 * 格式化模型输出
 *
 * @param[in]  outs    模型输出
 **********************************************************************************************************************/
static void parse_output(tm_mat_t* outs)
{
    tm_mat_t out = outs[0];
    float* data  = out.dataf;
    float maxp = 0;
    int maxi = -1;
    for(int i=0; i<5; i++){
        printf("%d: %.6f\n", i, data[i]);
        if(data[i] > maxp) {
            maxi = i;
            maxp = data[i];
        }
    }
    w_maxi = maxi;
    printf("### Predict output is: %s, prob %.6f\r\n", w_tags[maxi], maxp);
    return;
}

/*******************************************************************************************************************//**
 * 执行 TinyML 推理
 **********************************************************************************************************************/
void tinyML_run(void)
{
    TM_DBGT_INIT();
    tm_mdl_t mdl;

    tm_stat((tm_mdlbin_t*)mdl_data);

    res = tm_load(&mdl, mdl_data, NULL, NULL, &in_mat);

    if(res != TM_OK) {
        printf("tm model load err %d\r\n", res);
    }

    for(int i=0;i<72;i++){
        float v = (weather[i/3][i%3] - mean[i]) / std[i];
        int q = (int)round(v / scale) + zp;
        if(q > 127)  q = 127;
        if(q < -128) q = -128;
        input[i] = (int8_t)q;
    }

    in_mat.data = (mtype_t*)input;

    TM_DBGT_START();
    res = tm_run(&mdl, &in_mat, outs);
    TM_DBGT("tm_run");

    if(res==TM_OK) parse_output(outs);
    else printf("tm run error: %d\n", res);

    tm_unload(&mdl);
}

/*******************************************************************************************************************//**
 * main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used.  This function
 * is called by main() when no RTOS is used.
 **********************************************************************************************************************/
void hal_entry(void)
{
    UART0_Init();
    R_IOPORT_Open(&g_ioport_ctrl, g_ioport.p_cfg);

    tinyML_run();

    while(1) {
        R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_13, BSP_IO_LEVEL_HIGH);
        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
        R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_13, BSP_IO_LEVEL_LOW);
        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
    }

#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

/*******************************************************************************************************************//**
 * This function is called at various points during the startup process.  This implementation uses the event that is
 * called right before main() to set up the pins.
 *
 * @param[in]  event    Where at in the start up process the code is currently at
 **********************************************************************************************************************/
void R_BSP_WarmStart(bsp_warm_start_event_t event)
{
    if (BSP_WARM_START_RESET == event)
    {
#if BSP_FEATURE_FLASH_LP_VERSION != 0

        /* Enable reading from data flash. */
        R_FACI_LP->DFLCTL = 1U;

        /* Would normally have to wait tDSTOP(6us) for data flash recovery. Placing the enable here, before clock and
         * C runtime initialization, should negate the need for a delay since the initialization will typically take more than 6us. */
#endif
    }

    if (BSP_WARM_START_POST_C == event)
    {
        /* C runtime environment and system clocks are setup. */

        /* Configure pins. */
        R_IOPORT_Open (&IOPORT_CFG_CTRL, &IOPORT_CFG_NAME);

#if BSP_CFG_SDRAM_ENABLED

        /* Setup SDRAM and initialize it. Must configure pins first. */
        R_BSP_SdramInit(true);
#endif
    }
}

#if BSP_TZ_SECURE_BUILD

FSP_CPP_HEADER
BSP_CMSE_NONSECURE_ENTRY void template_nonsecure_callable ();

/* Trustzone Secure Projects require at least one nonsecure callable function in order to build (Remove this if it is not required to build). */
BSP_CMSE_NONSECURE_ENTRY void template_nonsecure_callable ()
{

}
FSP_CPP_FOOTER

#endif

烧录程序后串口输出:

================================ model stat ================================
mdl_type=0 (int8))
out_deq=1 
input_cnt=1, output_cnt=1, layer_cnt=5
input 1dims: (1, 1, 72)
output 1dims: (1, 1, 5)
main buf size 200; sub buf size 0
//Note: PARAM is layer param size, include align padding

Idx    Layer             outshape    inoft    outoft    PARAM    MEMOUT OPS
---    Input          1,  1, 72    -       0        0     72     0
###L71: body oft = 64
###L72: type=2, is_out=0, size=10304, in_oft=0, out_oft=72, in_dims=[1,1,1,72], out_dims=[1,1,1,128], in_s=0.024, in_zp=5, out_s=0.015, out_zp=-128
###L96: FC: ws_oft=64, w_oft=576, b_oft=9792
000    FC            1,  1,128    0    72    10240    128    9216
###L71: body oft = 10368
###L72: type=2, is_out=0, size=8768, in_oft=72, out_oft=0, in_dims=[1,1,1,128], out_dims=[1,1,1,64], in_s=0.015, in_zp=-128, out_s=0.013, out_zp=-128
###L96: FC: ws_oft=64, w_oft=320, b_oft=8512
001    FC            1,  1, 64    72    0    8704    64    8192
###L71: body oft = 19136
###L72: type=2, is_out=0, size=4672, in_oft=0, out_oft=136, in_dims=[1,1,1,64], out_dims=[1,1,1,64], in_s=0.013, in_zp=-128, out_s=0.012, out_zp=-128
###L96: FC: ws_oft=64, w_oft=320, b_oft=4416
002    FC            1,  1, 64    0    136    4608    64    4096
###L71: body oft = 23808
###L72: type=2, is_out=0, size=432, in_oft=136, out_oft=0, in_dims=[1,1,1,64], out_dims=[1,1,1,5], in_s=0.012, in_zp=-128, out_s=0.034, out_zp=-6
###L96: FC: ws_oft=64, w_oft=88, b_oft=408
003    FC            1,  1,  5    136    0    368    5    320
###L71: body oft = 24240
###L72: type=3, is_out=1, size=48, in_oft=0, out_oft=168, in_dims=[1,1,1,5], out_dims=[1,1,1,5], in_s=0.034, in_zp=-6, out_s=0.004, out_zp=-128
004    Softmax            1,  1,  5    0    168    0    5    30

Total param ~23.4 KB, OPS ~0.02 MOPS, buffer 0.2 KB

===tm_run use 0.000 ms
0: 0.152344
1: 0.843750
2: 0.003906
3: 0.003906
4: 0.003906
### Predict output is: Cloudy, prob 0.843750