简介:面向中微半导体BAT32G137的嵌入式项目开发,这份1.00.4版资源包提供完整例程与CMSIS库函数,覆盖SCI、IIC、SPI、USB、定时器等外设的典型应用,既适合初学者快速掌握MCU开发流程,也能为有经验的工程师提供可移植代码。压缩包共591个文件,约1.62MB,以179个C源文件、132个头文件、63个汇编启动文件为主体,另含53套uvprojx/uvoptx Keil工程配置、46个readme文档、30个map文件、3个flm烧录算法及lib静态库等;配合这些文件,可在Keil中直接加载工程并编译调试,readme提供使用说明,map文件便于分析内存布局。已有188人学习下载,通过其中串口通信、I2C读写、USB海量存储、PWM输出等示例,可快速理解外设驱动与用户层的交互方式,结合CMSIS标准接口和STM32兼容生态,能显著缩短底层配置与排查时间,是评估BAT32G137特性、开展低功耗产品研发的实用参考资料。
1. 一个 1.00.4 版本的 SDK 包:BAT32G157 第一次点亮到底要多久
刚拿到 BAT32G157 的 1.00.4 版本 SDK 时,我原本以为它和很多国外大厂的库一样,解压、打开例程、编译下载就能跑。实际折腾下来,最花时间的反而不是芯片本身,而是这个包里“东西太多”,例程基于官方开发板,我自己画的最小系统板一个都用不上。BAT32G157 是一颗 Cortex-M0+ 内核的通用低功耗 MCU,面向家电、电机控制和工业传感这类场景,而 1.00.4 这个版本号对应的整套软件包,包含芯片支持文件、外设驱动库和一批示例工程。这篇文章想说的是:这颗芯片的 SDK 该怎么拆、工程怎么从零搭、外设怎么调,以及那些“例程能跑、自己板子翻车”的坑到底在哪。适合刚选型这颗芯片、或者正在评估这个系列值不值得用的开发者。
2. 在开发板上跑通最小工程:先把 1.00.4 这个包拆开看
2.1 解压之后先认清四类文件:启动文件、系统初始化、驱动库与例程
拿到 1.00.4 版本的压缩包,我建议你不要急着打开例程,先把目录结构过一遍。这类国产 MCU 的 SDK 目录说不上统一,但大体上离不开四类内容:启动文件、系统初始化文件、外设驱动库、示例工程。启动文件(常见名字是 startup_xxx.s 或类似)负责定义中断向量表、初始化堆栈、调用 SystemInit 再跳进 main,这部分一般不用动。系统初始化文件(常见名字是 system_xxx.c/h)负责时钟配置,比如外部晶振还是内部 RC、主频跑到多少、总线分频怎么分。外设驱动库则是 UART、ADC、PWM、定时器这些模块的封装接口。示例工程往往带了一整套 IDE 工程文件,能直接编译,但问题也在这里:官方例程用的是官方开发板的引脚分配,跟你自己画的最小系统板大概率对不上。
我习惯先建一个表格,把包里的文件按“要不要改、什么时候改、我实际改了什么”分类。比如启动文件标记为“不用改”,系统初始化标记为“只改时钟源和主频”,驱动库标记为“按需添加 .c 文件”。这样做的原因是,后面只要出现编译报错或外设不工作,排查范围一下子就能缩小。
2.2 从零手动建工程:必加三个文件与两个必改配置
不依赖官方例程工程,从零手动建一个最小工程,是理解 1.00.4 包结构最好的方式。打开你习惯用的 IDE(我用的是常用的 ARM Cortex-M 工程环境),新建一个空工程,芯片型号选 BAT32G157。然后向工程里添加三类文件:启动文件、系统初始化文件、以及一个自己写的 main.c。外设驱动库暂时可以不加,先让芯片跑起来、能下载、能复位,再逐步加外设。
#include "bat32g157.h" int main(void) { /* 1. 先关看门狗,避免初始化还没做完就被复位打断 */ WDT_Disable(); /* 2. SystemInit() 已经在启动文件里被调过,这里不需要重复调用 */ /* 3. 简单地翻转一个 GPIO,验证工程链路 */ GPIO_SetMode(PIN_LED, GPIO_MODE_OUTPUT); while (1) { GPIO_TogglePin(PIN_LED); /* 空转延时,够人眼看到闪烁即可 */ for (volatile uint32_t i = 0; i < 200000; i++); } }这段代码里,WDT_Disable() 很关键。部分型号的看门狗在上电后默认就是开启的,如果不先在 main 最前面关掉,初始化慢一点就会触发复位。GPIO_SetMode 是把某个引脚设置为输出模式,具体引脚号要根据你自己的板子原理图来定。空转延时是一种最原始的延时方式,不依赖定时器,方便验证工程是否跑通。两个必改配置分别是:芯片头文件的包含路径,以及编译器的宏定义。宏定义里通常需要写上芯片型号对应的宏,比如 BAT32G157,驱动库头文件靠它来开开关。
2.3 调试器第一次下载:连接失败先查这四处
最小工程编译通过后,第一次用下载器连接芯片,是最容易卡住的地方。初次下载失败的案例里,我总结下来主要是四个位置的问题。第一是供电,BAT32G157 的调试接口需要目标板稳定供电,有些调试器虽然能从 SWD 口供电,但电流有限,板子上带了额外负载就会拉低电压,导致连接不稳定。第二是复位引脚,复位脚上如果接了很大的下拉电容,调试器握手时拉不低电平,连接就会失败。第三是 SWD 时钟速率,新手习惯用默认的高速档,但线长、杜邦线接触不良都会导致时序不稳,这时候把速率降到 1MHz 以下往往就好了。第四是芯片已经被读写保护,这种情况多见于二手芯片或之前烧过带保护选项的固件,此时连接会失败,但可以通过调试器的“连接复位”选项或全片擦除来解锁。
我自己的习惯是:第一次下载前,先把下载速率调低,并勾选连接时复位这个选项。这个习惯帮我避免了很多“芯片变砖”的假象,其实芯片没坏,只是握手时序没对上。这一步搞定之后,程序能跑、灯能闪,最小工程就算立住了。
3. 把外设逐个点亮:UART、PWM、ADC 的配置顺序与验证
3.1 UART 打头:配好引脚和波特率,跑通第一行日志
外设调试我建议从 UART 开始,因为串口日志是后续所有调试的“眼睛”。BAT32G157 的 UART 配置顺序,常见做法是先使能外设时钟、再配置引脚复用、然后设置波特率和帧格式,最后使能发送接收。1.00.4 驱动库里通常已经把这套流程封装成接口,你只需要传入对应参数。
void uart0_init(uint32_t baud) { /* 1. 使能 UART0 外设时钟,不同型号的时钟门控寄存器位置不同 */ CLK_EnablePeripheral(CLK_PERIPH_UART0); /* 2. 引脚复用:PA0/UART0_TX、PA1/UART0_RX 为常见搭配,以数据手册为准 */ PIN_SetMode(PIN_PA0, PIN_MODE_UART0_TX); PIN_SetMode(PIN_PA1, PIN_MODE_UART0_RX); /* 3. 波特率:库内部按 PCLK 计算分频值,传 Hz 即可 */ UART_SetBaud(UART0, baud); /* 4. 帧格式:8 数据位、无校验、1 停止位,串口助手里最常见 */ UART_Config(UART0, UART_DATA_8BITS | UART_PARITY_NONE | UART_STOP_1BIT); /* 5. 使能发送和接收 */ UART_Enable(UART0, UART_TX | UART_RX); }参数说明里最值得留意的是波特率那一项,UART_SetBaud 的第二个参数直接传 115200 这种值就行,库内部会根据系统时钟 PCLK 计算分频寄存器。但前提是 PCLK 的值和实际配置一致,如果系统初始化里改了时钟源但没改库里的时钟宏,波特率就会算错,表现就是串口乱码。引脚复用这一步也容易踩坑,改完 pin 模式后最好读一下寄存器确认,有些库的 PIN_SetMode 只改了方向没改功能选择,需要另外配 AF 寄存器,如果串口没有输出,先查这一处。
3.2 PWM:用定时器产出一路可控占空比
PWM 在 BAT32G157 上通常由一个通用定时器产生,配置思路是:选定时器通道、配时钟分频、设周期计数值、设比较值,最后启动输出。这一套流程在电机控制里就是占空比和频率的来源,在家电场景里也能用来调 LED 亮度或驱动蜂鸣器。1.00.4 的驱动库对 PWM 的封装方式各有不同,有的直接提供 PWM 接口,有的只提供定时器底层接口,前者更省事,后者更灵活。
void pwm1_init(uint16_t period_us, uint16_t duty_percent) { /* 1. 定时器时钟不分频,直接用 PCLK 计数 */ TIM_Enable(TIM1, TIM_CLOCK_DIV_1); /* 2. 周期:把微秒换算成计数值,period_count 上限要小于定时器位宽 */ uint16_t period_count = (uint16_t)(PCLK / 1000000UL * period_us); TIM_SetPeriod(TIM1, period_count); /* 3. 占空比:比较值 = 周期值 × 百分比 */ uint16_t compare = (uint16_t)(period_count * duty_percent / 100UL); TIM_SetCompare(TIM1, compare); /* 4. 输出极性:高电平有效,电机驱动时注意互补通道是否要死区 */ TIM_PWM_Output(TIM1, TIM_CH_A, TIM_POLARITY_HIGH); /* 5. 启动定时器,引脚开始输出波形 */ TIM_Start(TIM1); }这里的换算逻辑是:周期计数值等于时钟频率乘以周期时间,比如 PCLK 是 32MHz,想要 1kHz 的 PWM,周期就是 1000 微秒,period_count 就是 32000,比较值设为 16000 就是 50% 占空比。两个容易忽略的地方:一是 period_count 不要超过定时器的位宽上限,否则溢出后波形完全不对;二是 TIM_SetCompare 的时机,建议在定时器启动之前设置,启动后再改也能生效,但有些型号需要触发更新事件才加载新值,这点在数据手册里会写,遇到“改了占空比没反应”时优先查这个。
3.3 ADC:读取模拟量之前,先做一次自校准
BAT32G157 的 ADC 是 12 位逐次逼近型,常用于采集电池电压、温度、电流采样电阻上的压降。ADC 配置看起来简单,但容易忽略校准这一步。芯片出厂时内部比较器存在 offset,不做校准的话,采集结果在低量程段会出现固定偏差,尤其是采集接近零伏的信号时,软件上表现为“读数一直不归零”。
void adc_init(void) { /* 1. ADC 时钟分频:采样时钟太高会增大误差,通常建议不超过 1MHz */ ADC_SetClockDiv(ADC_CLOCK_DIV_8); /* 2. 参考电压:常用 VDD 作为参考,内部基准适合精度要求更高的场景 */ ADC_SetReference(ADC_REF_VDD); /* 3. 采集通道:AN0 是第一个模拟输入脚,具体引脚见数据手册 */ ADC_SelectChannel(ADC_CH_AN0); /* 4. 上电后先做一次校准,校准动作必须在首次转换之前完成 */ ADC_Calibrate(); } uint16_t adc_read(void) { /* 启动一次转换,轮询等待完成标志,再读结果寄存器 */ ADC_StartConversion(); while (!ADC_IsConversionFinished()); return ADC_GetResult(); }参数上面最需要注意的是 ADC_SetClockDiv。如果系统时钟是 32MHz,选 DIV_8 就是 4MHz,还是偏高,再看数据手册确认上限。ADC_Calibrate 必须在第一次转换之前做,这是因为校准要占用转换通道。连续采集场景下,建议每启动一次转换就等一下,不要在转换还没完成时去读结果寄存器,否则会拿到上次或垃圾数据。模拟输入的引脚如果悬空,读出来往往是一个漂移的随机值,排查时可以先把引脚对地短路,看读数是否接近零,这是判断 ADC 链路是否正常的快招。
4. 低功耗才是 BAT32G157 的主场:模式选择、唤醒与实测功耗
4.1 三种低功耗模式的取舍:睡眠、深度睡眠与掉电
BAT32G157 这类芯片的定位就是低功耗,数据手册里会给出几个电流档位。选型时容易被“微安级”这个数字吸引,但真正做产品时你会发现:数字是芯片的极限值,不是你的板子的电流。常见模式无非三种:睡眠模式、深度睡眠模式、掉电模式。
睡眠模式关掉内核时钟,外设时钟还在走,唤醒速度最快,适合需要频繁响应的场景,代价是唤醒后外设状态基本保留。深度睡眠模式把大部分外设时钟都停了,只保留唤醒源和少数维持电路,电流比睡眠低一到两个数量级,适合传感器数据采集,间隔几秒醒一次。掉电模式最省,但唤醒源受限,恢复时间也最长,通常作为最后的保底手段。1.00.4 驱动库里一般会有对应的低功耗接口,命名多为 PWR 开头,选哪种模式,取决于你的唤醒频率和可接受的唤醒时间。
4.2 唤醒之后第一件事:时钟重新稳定后才能碰外设
低功耗模式下最容易踩的坑是:唤醒后马上操作外设,结果第一次访问就卡住或读到错误数据。原因不是外设坏了,而是时钟还没稳定。BAT32G157 从深度睡眠切换到正常运行,内部振荡器需要重新起振和锁定,这个时间虽然短,但确实存在。如果在时钟稳定前就去操作 UART 或 ADC,总线的时钟树还没就绪,就会表现为“读回来全是 FF”或者“写寄存器没反应”。
/* 进入深度睡眠前,把不需要的外设时钟关掉,减少唤醒来路 */ CLK_DisablePeripheral(CLK_PERIPH_ADC); CLK_DisablePeripheral(CLK_PERIPH_UART0); /* 使能唤醒源:外部按键中断或 RTC 定时唤醒,二选一即可 */ EXTI_Enable(EXTI_CH0, EXTI_TRIGGER_FALLING); RTC_EnableAlarm(RTC_ALARM_INTERRUPT); /* 进入深度睡眠,代码停在这里,直到唤醒事件发生 */ PWR_EnterSleepMode(PWR_MODE_DEEP_SLEEP); /* 唤醒后先等时钟稳定,再重新初始化用到的外设 */ CLK_WaitOscillatorReady(); uart0_init(115200); adc_init();唤醒后的初始化顺序,我一般坚持“时钟、中断标志、外设”三步走。先确认时钟稳定,再清掉唤醒源的中断标志,最后重新初始化外设。这个顺序在低功耗场景里是硬规矩,别省。中断标志不清的话,唤醒后会立刻再次进入中断,表现为“一醒就睡、一睡就醒”的死循环,功耗反而比不睡还高。
4.3 实测功耗偏差大:板级问题比芯片问题更常见
数据手册标注的电流值,是在最理想条件下测出来的,你的板子测出来高十倍,大多数时候不是芯片翻车,而是板级外围电路在漏电。常见的漏电路径有:LED 指示灯的限流电阻没去掉,哪怕软件把 GPIO 拉低,只要 LED 供电回路走的是直接接到电源的路径,电流就在那摆着;电源芯片本身的静态电流就比 MCU 高,MCU 做得再低,整板功耗也降不下来;去耦电容漏电虽然小,但批量时电容材质选错,也会贡献微安级差异。
实测时我一般用万用表串联在电源输入端测平均电流,再用示波器看唤醒瞬间的峰值。如果平均电流偏高,优先检查硬件上是否有额外负载。软件侧也有一个容易忽略的坑:GPIO 未使用的引脚没有统一配置,悬空输入脚会因为电位漂移产生漏电。处理办法是把不用的引脚全部设为输出低或输入上拉,1.00.4 库里的 GPIO 配置接口可以在上电后统一跑一遍。
5. BAT32G157 调试与下载避坑指南:5 个翻车现场复盘
5.1 下载一次后芯片连不上
现象:第一次烧录正常,第二次打开调试器连接,提示无法建立连接,芯片像变砖了一样。原因:程序里把 SWD 调试引脚复用成了普通 GPIO,或者在上电初始化里禁用了调试接口。解决:把连接模式改成“连接时复位”,让调试器在芯片启动前先拉住复位脚;如果不行,在全片擦除模式下再次连接,通常会成功。这套操作能救回绝大多数“锁死”的芯片,这也是我眼里最重要的后悔药。
5.2 串口乱码但程序确实在跑
现象:LED 正常闪烁,程序逻辑明显在执行,但串口助手收到的全是乱码。原因:波特率算错、时钟源不一致、或者串口助手的波特率与代码不一致。解决:先核对系统时钟宏,确保它和实际启动文件里配置的时钟源一致,然后再看 PCLK 的分频配置。最常见的是外部晶振没焊或焊错,程序回退到内部 RC,但库还按外部晶振的速率算波特率,差好几倍,怎么调助手的波特率都对不上。先用逻辑分析仪抓一下 TX 引脚的实际波形,量一下一个 bit 的宽度,能直接反推出实际波特率,排查速度最快。
5.3 一进低功耗模式就整机复位
现象:进入深度睡眠的指令一执行,电流还没降下来,芯片就复位了,反复循环。原因:两个高频嫌疑,一是看门狗没有在睡前进喂狗或暂停,睡眠时间超过看门狗超时周期;二是低压检测 LVD 把睡眠时的电压降误判为欠压,触发了复位。解决:睡眠时间超过看门狗周期时,要么暂时关掉看门狗,要么用定时器在睡眠期间周期性唤醒喂狗;同时把 LVD 的阈值调到低于最低工作电压并确认进入睡眠时电压没掉过阈值。这个坑在电池供电设备里特别常见,环境一抖就复位。
5.4 看门狗在初始化完成前就触发
现象:程序还在启动阶段就反复复位,调试器单步跟到一半,芯片自己跑了。原因:部分型号的看门狗在复位后默认使能,启动文件里如果不先关闭,它就在后台计时,初始化代码稍微长一点就超时。解决:在 main 函数第一行关闭看门狗,或者在选项字节里把看门狗上电默认状态改为禁用。特别提醒,选项字节不是普通寄存器,用调试器写入时要勾选对应选项,否则掉电重启后又回老样子,这也是我把 WDT_Disable 放在 main 第一行的原因。
5.5 例程正常,自己的工程一跑就进 HardFault
现象:官方例程编译运行都正常,从零搭的工程一开中断就进 HardFault。原因:中断向量表没有正确重定位,或启动文件、链接脚本与芯片型号不匹配。解决:先查工程有没有定义正确的芯片宏,再确认启动文件里中断向量表是否按 1.00.4 包的版本添加;可以把 HardFault_Handler 里加个死循环和断点,然后看反汇编窗口里的 PC 和 LR 寄存器,定位是哪一步跳飞的。用 MAP 文件配合全局查找,是定位这类问题最快的方式。
/* 调试期用:在 HardFault_Handler 里打断点,等效于当场暂停 */ void HardFault_Handler(void) { while (1) { /* 在这里打断点,查看 MSP、LR、PC 寄存器 */ } }6. 进阶:把 1.00.4 的驱动库拆开,拼一个自己的工程骨架
6.1 裁到只剩用得到的驱动文件
官方 SDK 包为了覆盖全系列型号和应用场景,驱动库里塞了几十个 .c 文件,全量加进工程编译没问题,但会让你的工程变得笨重。我一般会对照数据手册的模块列表,把用不到的外设驱动直接从工程里移除。比如只做低功耗传感器节点,UART、ADC、RTC、电源管理、GPIO 这几个文件留着,PWM、比较器、运放、通信接口的驱动文件全部删掉。删完编译一次,遇到头文件互相引用的报错,就把对应依赖加回来,通常不会超过三个。
6.2 固化一个自己的工程模板
当裁剪稳定到能正常编译、正常烧录后,我会把这个工程固化成模板,后续项目都从它复制。模板里固定放几个文件:统一的 main.c 骨架、一个板级初始化文件、一个存放所有引脚定义的头文件。引脚定义的头文件是关键,所有外设和引脚的对应关系集中在里面,后续换板子只改这个文件,不碰业务代码。我自己的习惯是:在引脚头文件里给每个引脚写清楚注释,比如“PA0——UART0_TX——接调试串口”,这样间隔几个月再回来看项目,不用翻原理图就能知道原来怎么接的。
这个工程骨架做出来后,新项目的启动时间能从一两天缩短到半天。你可能会发现,1.00.4 这个版本号的包其实没有想象中那么黑匣子,它只是个基础底座,真正决定项目顺不顺的,是你自己的目录结构、引脚管理和外设初始化顺序。把这三样东西沉淀下来,比反复去抄官方例程管用得多。希望帮到你。
本文还有配套的精品资源,点击获取