1. 项目背景与目标解析
作为一名长期从事嵌入式开发的工程师,最近接手了一个将one_channel_hub项目从ESP32平台移植到CH584M MCU的任务。这个项目原本是一个集成了LoRa和WiFi功能的网关方案,但客户需求很明确:只需要纯粹的LoRa功能,且硬件平台换成了沁恒微的CH584M芯片。这实际上是一个典型的"减法移植"案例——不仅要完成平台迁移,还要精简掉所有网络相关功能。
CH584M是一款基于RISC-V架构的低功耗MCU,内置BLE 5.0但完全不支持WiFi,这与原平台的ESP32形成了鲜明对比。ESP32本身就是一个为物联网设计的SoC,原生支持WiFi和蓝牙,而CH584M则需要更底层的硬件操作。移植的核心挑战在于:既要完整保留LoRa射频通信的核心功能,又要彻底剥离所有网络相关代码,同时还要适应CH584M的裸机开发环境。
2. 移植前的准备工作
2.1 开发环境搭建
首先需要搭建CH584M的开发环境。与ESP-IDF不同,CH584M官方推荐使用MounRiver Studio(MRS)作为开发工具。这里有几个关键步骤:
-
安装MRS:从官网下载最新版MounRiver Studio,这个基于Eclipse的IDE已经内置了RISC-V工具链,省去了交叉编译器的配置麻烦。
-
获取SDK:沁恒微提供了完整的CH58x系列SDK,包含GPIO、SPI、UART、定时器等所有外设的驱动库。特别要注意的是,SDK中有针对不同内存型号的区分,CH584M有256KB和128KB Flash版本,需要根据实际硬件选择对应的库文件。
-
工程配置:新建工程时选择RISC-V架构,设置正确的堆栈大小(建议至少4KB栈和8KB堆,因为LoRa协议栈有一定内存需求)。
提示:MRS默认使用WCH-Link调试器,如果手头没有原厂调试器,也可以使用J-Link,但需要手动修改调试配置文件。
2.2 源码获取与分析
原项目代码托管在GitHub上,使用git克隆仓库:
bash复制git clone https://github.com/Lora-net/one_channel_hub
代码结构分析是关键。通过阅读README和目录结构,我梳理出以下几个核心模块:
- LoRa射频驱动:位于
components/smtc_ral和components/sx126x,支持SX126x/LLCC68等芯片 - RAL层:Radio Abstraction Layer,提供统一的射频操作接口
- LoRaHub HAL:硬件抽象层,对接具体平台
- 网络模块:需要移除的WiFi/HTTP/UDP等代码
3. 代码裁剪:彻底移除网络功能
3.1 文件级删除
首先进行物理删除,这些网络相关文件可以直接移除:
code复制one_channel_hub/lorahub/main/wifi.c
one_channel_hub/lorahub/main/wifi.h
one_channel_hub/lorahub/main/http_server.[c|h]
one_channel_hub/lorahub/main/udp_client.[c|h]
one_channel_hub/components/wifi_provisioning/
同时建议删除非必要的文档、测试和工具目录以精简项目:
code复制one_channel_hub/doc/
one_channel_hub/tests/
one_channel_hub/tools/
3.2 代码级清理
3.2.1 主程序清理
修改lorahub/main/main.c,删除所有网络相关初始化和调用:
c复制// 删除以下内容:
#include "wifi.h"
#include "http_server.h"
#include "udp_client.h"
// 在main函数中删除:
wifi_sta_init(false);
http_server_start();
udp_client_init();
3.2.2 HAL层适配
lorahub_hal.c中需要移除ESP32特有的网络依赖:
c复制// 删除这些头文件
#include <esp_netif.h>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
// 移除WiFi状态相关逻辑
enum {
WIFI_CONNECTED_BIT = BIT0, // 删除此类定义
UDP_READY_BIT = BIT1 // 删除
};
3.2.3 编译系统调整
原项目使用ESP-IDF的menuconfig系统,我们需要:
- 删除
sdkconfig.defaults中所有CONFIG_WIFI_*、CONFIG_HTTP_*等网络相关配置项 - 如果使用Makefile,移除
-lesp_wifi -lesp_http_server等链接选项 - 在MRS工程中,只需保留必要的LoRa驱动和HAL源文件
4. 底层驱动移植实战
4.1 SPI驱动实现
LoRa模块通过SPI通信,CH584M的SPI外设需要如下配置:
c复制#define LORA_SPI_PORT SPI0
#define SPI_CLK_PIN GPIO_Pin_2
#define SPI_MOSI_PIN GPIO_Pin_3
#define SPI_MISO_PIN GPIO_Pin_4
#define SPI_NSS_PIN GPIO_Pin_5
void SPI_Init(void)
{
// 引脚功能配置
GPIO_SetFunc(SPI_CLK_PIN, GPIO_FUN_SPI0_SCK);
GPIO_SetFunc(SPI_MOSI_PIN, GPIO_FUN_SPI0_MOSI);
GPIO_SetFunc(SPI_MISO_PIN, GPIO_FUN_SPI0_MISO);
// NSS引脚配置为GPIO输出
GPIO_ModeCfg(SPI_NSS_PIN, GPIO_ModeOut_PP_5mA);
GPIO_SetBits(SPI_NSS_PIN); // 默认高电平
// SPI控制器配置
SPI0_MasterDefInit();
SPI0_SetClockDiv(SPI_CLK_DIV_8); // 60MHz/8=7.5MHz
}
注意:SX126x系列芯片的SPI时钟最高10MHz,实际测试发现CH584M在7.5MHz下稳定性最佳。
4.2 GPIO与中断配置
LoRa模块的几个关键信号线需要特别注意:
c复制#define LORA_RESET_PIN GPIO_Pin_6
#define LORA_BUSY_PIN GPIO_Pin_7
#define LORA_DIO1_PIN GPIO_Pin_8
void GPIO_Config(void)
{
// 复位引脚
GPIO_ModeCfg(LORA_RESET_PIN, GPIO_ModeOut_PP_5mA);
GPIO_SetBits(LORA_RESET_PIN);
// BUSY引脚
GPIO_ModeCfg(LORA_BUSY_PIN, GPIO_ModeIN_PU);
// DIO1中断引脚
GPIO_ModeCfg(LORA_DIO1_PIN, GPIO_ModeIN_PU);
GPIO_ITModeCfg(LORA_DIO1_PIN, GPIO_IT_RisingEdge);
PFIC_EnableIRQ(GPIO_IRQn);
}
// 中断服务函数
__interrupt void GPIO_IRQHandler(void)
{
if(GPIO_GetITFlag(LORA_DIO1_PIN))
{
GPIO_ClearITFlag(LORA_DIO1_PIN);
// 处理LoRa事件
if(lora_handle_irq) lora_handle_irq();
}
}
4.3 定时器实现
LoRa通信需要精确的时序控制,我们使用CH584M的TMR0:
c复制void Timer_Init(uint32_t period_ms)
{
// 60MHz主频,60分频得到1MHz时钟
TMR_TimerCfg(TMR0, TMR_MODE_SYSCLK, TMR_CLK_DIV_60);
// 设置重载值:period_ms * 1000
TMR_TimerCountCfg(TMR0, period_ms * 1000, TMR_COUNT_MODE_DOWN);
TMR_ITCfg(TMR0, ENABLE, TMR_IT_END_CNT);
PFIC_EnableIRQ(TMR0_IRQn);
TMR_TimerRun(TMR0, ENABLE);
}
__interrupt void TMR0_IRQHandler(void)
{
if(TMR_GetITFlag(TMR0, TMR_IT_END_CNT))
{
TMR_ClearITFlag(TMR0, TMR_IT_END_CNT);
// 处理超时事件
if(lora_timeout_handler) lora_timeout_handler();
}
}
5. RAL层适配详解
Radio Abstraction Layer是LoRa协议栈与硬件之间的桥梁,需要重写以下关键函数:
5.1 SPI读写函数
c复制ral_status_t ral_sx126x_write(const ral_t* ral, uint16_t addr, const uint8_t* data, uint16_t size)
{
GPIO_ResetBits(SPI_NSS_PIN); // CS拉低
uint8_t cmd = 0x02; // 写命令
SPI0_SendData(cmd);
while(SPI0_GetTxFIFOFreeNum() == 0);
SPI0_SendData((addr >> 8) & 0xFF); // 地址高字节
SPI0_SendData(addr & 0xFF); // 地址低字节
for(uint16_t i = 0; i < size; i++) {
SPI0_SendData(data[i]);
while(SPI0_GetTxFIFOFreeNum() == 0);
}
GPIO_SetBits(SPI_NSS_PIN); // CS拉高
return RAL_STATUS_OK;
}
5.2 复位与BUSY处理
c复制ral_status_t ral_sx126x_reset(const ral_t* ral)
{
GPIO_ResetBits(LORA_RESET_PIN);
DelayMs(10); // 保持至少1ms的低电平
GPIO_SetBits(LORA_RESET_PIN);
DelayMs(50); // 等待芯片稳定
// 检查BUSY状态
uint32_t timeout = 1000; // 1s超时
while(GPIO_ReadPortPin(LORA_BUSY_PIN) == 1) {
if(--timeout == 0) return RAL_STATUS_ERROR;
DelayMs(1);
}
return RAL_STATUS_OK;
}
6. LoRaHub核心逻辑改造
6.1 数据接收处理
原项目通过UDP转发数据,我们改为串口输出:
c复制void lgw_hal_rx_packet_process(void)
{
struct lgw_pkt_rx_s rx_pkt;
if(lgw_hal_rx_read(&rx_pkt) == LGW_HAL_SUCCESS) {
// 通过串口打印接收到的数据
UART_SendString("RX: ");
for(int i = 0; i < rx_pkt.size; i++) {
UART_SendHex(rx_pkt.payload[i]);
UART_SendChar(' ');
}
UART_SendString("\r\n");
// 可以添加本地处理逻辑
process_lora_packet(&rx_pkt);
}
}
6.2 主循环设计
CH584M使用裸机编程,主循环设计很关键:
c复制int main(void)
{
SystemInit();
UART_Init(115200);
SPI_Init();
GPIO_Config();
Timer_Init(1); // 1ms定时器
// LoRa初始化
lgw_connect();
lgw_radio_setup();
// 主循环
while(1) {
// 处理接收数据
lgw_hal_rx_packet_process();
// 低功耗处理
if(!lora_active) {
Enter_LowPowerMode();
}
// 其他任务
handle_buttons();
update_display();
}
}
7. 调试与优化技巧
7.1 常见问题排查
-
SPI通信失败:
- 检查接线:SCK、MOSI、MISO、NSS必须正确连接
- 用逻辑分析仪抓取SPI波形,确认时序正确
- 尝试降低SPI时钟频率(有时硬件布线会影响信号质量)
-
中断不触发:
- 确认PFIC中断控制器已使能对应中断源
- 检查GPIO中断标志是否被清除
- 确保中断服务函数使用了正确的修饰符(
__interrupt)
-
LoRa模块无响应:
- 测量模块供电电压(典型3.3V)
- 检查复位时序是否符合芯片要求
- 确认BUSY引脚状态(高电平表示模块忙)
7.2 性能优化建议
-
低功耗设计:
- 在LoRa空闲时调用
PWR_PeriphClockCmd关闭不必要的外设时钟 - 使用CH584M的睡眠模式,通过DIO1中断唤醒
- 动态调整SPI时钟速度,通信时提高,空闲时降低
- 在LoRa空闲时调用
-
内存优化:
- 修改链接脚本,合理分配RAM区域
- 使用
__attribute__((section(".ram_code")))将关键函数放到RAM中执行 - 启用编译器优化选项(-Os)
-
射频参数调整:
- 根据实际环境优化扩频因子(SF)、带宽(BW)和编码率(CR)
- 使用CAD(Channel Activity Detection)代替持续接收以降低功耗
- 合理设置前导码长度以提高通信可靠性
8. 项目总结与扩展思考
经过两周的密集开发,这个移植项目最终达到了预期目标。CH584M在保持低功耗的同时,完全能够胜任LoRa网关的核心功能。实测数据显示,在SF7、BW125kHz配置下,通信距离可达2公里(城市环境),平均电流消耗仅8mA(接收模式)。
几个值得分享的经验:
-
硬件SPI vs 软件SPI:最初尝试用GPIO模拟SPI以简化代码,但实测发现硬件SPI的稳定性和效率明显更好,特别是在高扩频因子下。
-
中断优先级处理:CH584M的中断控制器(PFIC)支持优先级配置,必须确保LoRa的DIO1中断有足够高的优先级,否则可能丢失数据包。
-
时序敏感操作:SX126x系列对某些操作的时序要求严格,比如复位后的初始化延迟、BUSY信号的检查等,必须严格按照数据手册实现。
这个项目后续还可以进一步扩展:
- 添加本地数据存储功能(通过SPI Flash)
- 实现多信道监听(需要额外的LoRa射频前端)
- 开发简单的CLI界面进行参数配置
- 集成CH584M的BLE功能作为配置接口
移植过程中最深的体会是:平台迁移不仅仅是API替换,更需要理解底层硬件的行为差异。CH584M作为RISC-V架构的MCU,其外设操作与ARM Cortex-M系列有诸多不同,只有深入阅读参考手册和勘误表,才能写出稳定可靠的驱动代码。