简介:博越实体电子STM8S105C6开发板配套光盘资料,核心为STM8S标准库函数版本,面向准备上手STM8S105C6的嵌入式开发者与单片机学习者,尤其适合需要从零搭建工程、理解中断与定时器机制的入门阶段。压缩包共含1149个文件、约4.28MB,以.c/.h源码文件为主,另含.lst/.o等编译中间文件、.hex可烧录文件、.ewp/.eww工程文件以及.bat批处理脚本,文件类型齐全,便于还原真实工程结构;其中C源码与头文件可直接阅读,HEX文件可用于固件烧写,工程文件则帮助快速打开和重新编译。已有621人浏览学习。内容覆盖定时器延时、定时器中断、外部中断输入、GPIO输入、蜂鸣器、4×4矩阵键盘与OLED显示等典型外设例程,可配合实物板逐项验证。通过对库函数调用、中断服务程序编写、串口调试及工程配置的逐步分析,读者能获得从原理到落地的完整实践路径;目录按功能模块划分,例程间相互独立又渐进衔接,适合按序学习或按需查阅,也可作为二次开发的基础模板。
1. 博越STM8S105C6开发板光盘包:能直接编译的库函数工程列表
博越实体电子的 STM8S105C6 开发板光盘包,解压后是完整可编译的 STM8S 库函数版本工程列表,20 多个 Test 编号工程从 GPIO 输入、TIM2/TIM4 定时器中断、USART3 重定向 printf 一路做到 4×4 矩阵键盘与 OLED 驱动,把 STM8S105 中密度芯片的外设基本过了一遍。光盘里的 .cspy.bat 是 IAR EWSTM8 调试器生成的启动脚本,说明配套源码按 IAR 工程组织,配合 ST-Link 的 SWIM 接口下载调试。它适合两类人:想从寄存器思维切换到库函数开发、追求可移植性的嵌入式工程师,以及刚拿到开发板、需要能直接跑通中断与串口模板的入门者。这套光盘不只是一堆 Demo,而是一套能照着改的工程骨架转存。
2. 环境速建:从 .cspy.bat 反推 IAR 工程与 STM8S 库函数版本骨架
2.1 .cspy.bat 说明什么
光盘目录里 Test3_TIM2Interrupt.cspy.bat、Test13_USART3_Printf.cspy.bat 这类文件,是 IAR Embedded Workbench for STM8 每次点击 Debug 后由 C-SPY 调试器在工作目录生成的命令行启动脚本。脚本里通常写死 cspy.exe 的绝对路径、目标配置文件以及调试探针参数。拿到这一堆 .bat,第一个有用信息是:这套源码当初是用 IAR 建的工程,按 .ewp 工程文件组织,而不是 STVD+Cosmic 的 .stp 工程。
第二个有用信息是调试链路。STM8S105C6 没有 JTAG,只有 SWIM 单线调试接口,IAR 里对应 ST-LINK 调试器。把 ST-Link 的 SWIM、RST、GND、3.3V 四根线接到开发板,在 Project > Options > Debugger 里选择 ST-LINK,Interface 选 SWIM,就能直接下载和单步。这里有个容易忽视的环节:老版本 ST-Link V2 遇到 Windows 10/11 驱动冲突时,先更新到 ST 官网的 ST-Link Upgrade 工具再继续,否则反复卡在连接阶段,很容易误判成开发板损坏。
2.2 库函数版本(SPL)的文件骨架
STM8S 库函数版本的核心是 stm8s.h、stm8s_conf.h、stm8s_it.c 三个文件,外加按外设拆分的 stm8s_xxx.c/h。stm8s.h 里通过宏选择芯片型号,STM8S105C6 属于中密度(Medium Density),对应宏是 STM8S_MD;stm8s_conf.h 决定编译哪些外设模块,注释掉不用的模块能明显缩短编译时间;stm8s_it.c 是全部中断服务函数的落点,库函数版本把向量表在编译期绑定到这个文件里,函数名写错或者放错文件,中断就静默失效。
| 文件 | 作用 | 移植时注意 |
|---|---|---|
| stm8s.h | 型号宏、全局数据类型 | STM8S105C6 必须打开 STM8S_MD |
| stm8s_conf.h | 外设模块开关 | 注释掉无用模块,减少编译与告警 |
| stm8s_it.c | 中断向量与 ISR 定义 | 函数名必须和宏绑定一致 |
| stm8s_clk.c/h | 时钟树配置 | 复位后 CPU 实际只跑 2MHz |
| stm8s_gpio.c/h | GPIO 模式与读写 | 模式枚举集中在这份头文件里 |
这套结构的价值在于不需要从零搭工程。任意挑一个 Test 工程复制出来改,替换 stm8s_conf.h 的模块开关,就能得到新工程,这也是配套源码比单文件 Demo 更值得留档的原因。config.h.bak 和 includes.h.bak 是厂商调板时手工备份的工程配置头文件,当工程打开报错或者编译宏错乱时,把 .bak 改回原名覆盖当前文件,通常能恢复出厂可编译状态。
2.3 环境搭建与下载链路
以 IAR 为例,常见做法是直接双击 Test 工程目录下的 .ewp 文件打开工程。如果厂商只给了 .cspy.bat 而没有 .ewp,就新建空工程,把 library/inc、library/src 和用户目录下的 .c 文件按照原目录结构添加进去,编译前在 Preprocessor 的 include 路径里加入库头文件目录,这是新手最容易漏掉的一步。
# ST-Link V2 + SWIM 的 IAR 配置路径 Project > Options > General Options > Target: STM8S105C6 Project > Options > C/C++ Compiler > Preprocessor: 添加库头文件路径 Project > Options > Debugger > Driver: ST-LINK Project > Options > Debugger > ST-LINK: Interface = SWIM库函数版本的例程大多不做代码优化,默认 Debug 模式即可跑通。烧录时如果报 SWIM Communication Error,先确认供电是否稳定、复位引脚有没有被其它外设占用,再把 ST-Link 的连线控制在 20cm 以内。SWIM 对线材和接触电阻比 JTAG 敏感得多,杜邦线太长是这类报错最常见的原因。链路理清后,后面所有 Test 工程的验证方式就统一了。
3. 时钟与 GPIO:库函数版本初始化的两个基础坑
3.1 复位后的 2MHz 陷阱与 CLK 配置
STM8S105C6 上电后默认使用 HSI 内部 16MHz RC 振荡器,但 CPU 时钟被复位值预分频为 HSI/8,实际只有 2MHz。直接跑例程往往看不出问题,一旦写延时、算波特率、量 PWM 频率,全部对不上。库函数版本里正确的打开方式是先配 HSI 分频,再配 CPU 分频:
#include "stm8s.h" void SystemClock_Init(void) { CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); /* HSI 16MHz,不再 8 分频 */ CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1); /* CPU 时钟 1 分频 */ }CLK_HSIPrescalerConfig 的作用是把 HSI 输出从复位默认的 2MHz 升到 16MHz;CLK_SYSCLKConfig 再设置 CPU 与主时钟的分频比。库函数版本这两个 API 都接受 CLK_PRESCALER_xxx 枚举,参数看枚举名就能对上意图。注意 STM8S105C6 的工作电压与主频相关,3.3V 供电跑到 16MHz 没有问题,但用电池或 DCDC 把电压压到 3.0V 以下时,建议降回 8MHz 或 4MHz,否则极端温度下闪存读取可能出错,这类问题在库函数版本代码里没有任何提示,只能靠实测发现。
3.2 GPIO_Init 模式参数与按键输入
GPIO 在库函数版本里没有 STM32 HAL 那套端口时钟使能概念,端口时钟默认打开,核心工作全在 GPIO_Init 的模式参数上。下面是一个 LED 加按键的标准写法:
void Gpio_Init(void) { GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST); /* LED 推挽输出 */ GPIO_Init(GPIOC, GPIO_PIN_1, GPIO_MODE_IN_PU_NO_IT); /* 按键上拉输入 */ } void Key_Poll(void) { if (GPIO_ReadInputDataBit(GPIOC, GPIO_PIN_1) == RESET) /* 按下为低 */ { GPIO_WriteReverse(GPIOB, GPIO_PIN_0); /* 翻转 LED */ } }GPIO_MODE_OUT_PP_LOW_FAST 的含义是推挽输出、初始低电平、IO 翻转速度最快;GPIO_MODE_IN_PU_NO_IT 是上拉输入不带中断。Test4_GPIOInput 这个工程演示的就是这段逻辑:读 PC 口按键电平,反转 PB 口 LED。按下没反应时,先查按键另一端接的是 GND 还是 VCC;接 GND 的按键必须用上拉输入,接 VCC 的按键要用下拉或浮空输入,这是按键电路与 GPIO 模式匹配的第一原则。
库函数版本里 GPIO 模式枚举不少,还有开漏输出(GPIO_MODE_OUT_OD_xxx)和带中断输入(GPIO_MODE_IN_PU_IT),建议把 stm8s_gpio.h 里的枚举整理进自己的代码注释,选型时对照一次。实际项目里按键输入建议串 100Ω 电阻并加 100nF 电容,硬件上先滤掉一部分高频噪声,软件再配合消抖。
3.3 用 Test4_GPIOInput 验证系统时钟是否真实跑到 16MHz
Test4_GPIOInput 里一般会有一个循环翻转 IO 的动作。把这段代码改成在主循环里以固定次数翻转 PB0,用万用表频率档或逻辑分析仪测量 PB0 的方波频率:如果实测频率和按 2MHz 时钟计算出来的值差 8 倍,基本可以判定 CLK 配置没生效。这个方法比反复读寄存器直观得多,还能顺带确认库函数版本的 CLK API 是否真正写入了时钟树寄存器。频率验证通过后,再去做串口波特率、定时器周期才有意义,否则所有外设的时基都是错的。
提示:库函数版本的 CLK 配置经常被放在 main 函数最前面,单步调试时很容易被跳过。想确认时钟配置是否执行,直接查看 CLK_CKDIVR 寄存器的值即可。
4. 定时器与中断:TIM2、TIM4 与外部中断的库函数写法
4.1 TIM2 16 位定时器中断:1ms 节拍的实现
Test3_TIM2Interrupt 是这套光盘里最值得先跑通的工程,它覆盖了从库函数版本配置定时器到中断服务函数落点的完整链路。TIM2 是 16 位通用定时器,库函数版本用 TIM2_TimeBaseInit 一步完成预分频和自动重载:
void Tim2_Init(void) { TIM2_TimeBaseInit(TIM2_PRESCALER_128, 125); /* 16MHz/128=125kHz,125 次=1ms */ TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE); /* 使能更新中断 */ TIM2_Cmd(ENABLE); /* 启动定时器 */ }计算逻辑:系统时钟 16MHz 经过 128 分频后是 125kHz,计 125 个时钟周期溢出一次,得到 1ms。TIM2 是 16 位计数器,自动重载值最大 65535,需要 10ms 就把重载值改成 1250,改的时候要确认计数器不会溢出。中断服务函数的写法有硬约束,必须放在 stm8s_it.c 里,使用库函数版本预定义的名字:
INTERRUPT_HANDLER(TIM2_UPD_OVF_BRK_IRQHandler, 13) { TIM2_ClearITPendingBit(TIM2_IT_UPDATE); /* 先清标志,再处理业务 */ TimeTick++; if (TimeTick >= 500) { TimeTick = 0; GPIO_WriteReverse(GPIOB, GPIO_PIN_0); /* 500ms 翻转一次 LED */ } }这块最容易踩的坑是把 ISR 写到其它 .c 文件并自定义函数名,编译能过但中断永远不进来。库函数版本用 INTERRUPT_HANDLER 宏把函数绑定到向量表,位置和函数名配对是硬约束。另外注意先清中断挂起位再执行业务逻辑,否则中断反复进入,TimeTick 计数会乱掉。
4.2 TIM4 8 位定时器与定时器选型
Test9_TIM4Interrupt 对应 8 位基本定时器 TIM4。TIM4 只有 8 位计数器,自动重载值上限 255,配不出长周期,它和 TIM2 的定位完全不同:
| 定时器 | 位数 | 计数上限 | 典型用途 |
|---|---|---|---|
| TIM2 | 16 位 | 65535 | 毫秒级延时、PWM、输入捕获 |
| TIM4 | 8 位 | 255 | 短周期扫描、系统 tick |
以 16MHz 时钟、128 分频为例,TIM4 计 125 个数溢出一次也是 1ms,但如果想用同样分频得到 10ms,重载值 1250 已经超出 8 位计数范围,必须降低分频比或者引入软件计数变量。常见做法是在 TIM4 中断里维护一个 uint16_t 的软件计数器,中断里只做自增,主循环或其它中断去查询这个值,实现多级时间片调度。把 Test3 的 TIM2 做精确时基、Test9 的 TIM4 做软件分频,两个工程合起来就是一个可用的调度骨架。
4.3 Test7_ExtAdvancedInterruptInput:外部中断灵敏度配置
Test7 工程名里的 Advanced 指的不是高级定时器,而是外部中断的进阶用法。库函数版本里配置比想象中简单:
void ExtInt_Init(void) { GPIO_Init(GPIOC, GPIO_PIN_1, GPIO_MODE_IN_PU_IT); /* 输入加中断 */ EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOC, EXTI_SENSITIVITY_FALL_ONLY); /* 下降沿触发 */ }GPIO_Init 里的 GPIO_MODE_IN_PU_IT 只负责把引脚配成可中断输入,真正决定触发条件的是 EXTI_SetExtIntSensitivity。STM8S105C6 每个端口对应一组外部中断灵敏度寄存器,可选上升沿、下降沿、双边沿和低电平触发。下降沿触发对应机械按键按下的瞬间,如果外部信号本身上升缓慢,建议改用上升沿或双边沿,并在中断里做消抖。
机械按键的抖动通常在 5 到 15ms,光盘例程为了演示简洁一般不做消抖。实际使用可以在外部中断里启动 TIM4 做 20ms 定时,定时到后再读一次引脚电平确认,这是库函数版本下标准的软件消抖做法。另外注意库函数版本的外部中断标志位不需要手动清,但如果在中断里读取了数据寄存器,要防止读取时序造成误触发,调试时遇到莫名多进一次中断,先怀疑这里。
5. 外设联动:USART3 重定向 printf、OLED I2C 与 4×4 矩阵键盘
5.1 USART3 重定向 printf 的坑
Test13_USART3_Printf 演示的是嵌入式调试最常用的手段:串口打印。库函数版本里 USART3 初始化用 USART3_Init,之后最关键的一步是把 printf 的输出重定向到串口:
int fputc(int ch, FILE *f) { USART3_SendData8((uint8_t)ch); /* 写发送数据寄存器 */ while (USART3_GetFlagStatus(USART3_FLAG_TXE) == RESET); /* 等发送寄存器空 */ return ch; }这个函数在 IAR EWSTM8 下会被 printf 家族自动调用。参数上 USART3_Init 用 115200、8 数据位、1 停止位、无校验是最常见的组合。库函数版本的波特率生成器是整数分频,16MHz 时钟下 115200 存在一定误差,但用于串口调试没有问题。fputc 里 while 等待 TXE 标志的步骤不能省,否则连续打印时字符会互相覆盖。如果发现 printf 只输出第一个字符就卡住,多半是 USART3 的外设时钟没有提前打开,需要在 Init 之前调用 CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART3, ENABLE),这个细节最容易和 STM32 的使用习惯混淆。
5.2 Test21_OLED_1:I2C 引脚与显存刷新
OLED 例程在光盘里通常走 I2C 接口。STM8S105C6 的硬件 I2C 引脚是 PB4(SCL)和 PB5(SDA),外接 OLED 模块时这两根线需要上拉电阻,多数模块自带上拉,不带的要补 4.7kΩ:
| 引脚 | 功能 | 连接 OLED |
|---|---|---|
| PB4 | I2C_SCL | SCK |
| PB5 | I2C_SDA | SDA |
| VDD | 3.3V | VCC |
| GND | GND | GND |
OLED 是 128×64 点阵,每页 8 行共 8 页,刷新时按页地址和列地址写 DDRAM,显存数据一次写一整页。库函数版本里如果例程用 GPIO 模拟 I2C,就没有时钟使能和复用配置问题,直接改延时即可移植;如果用硬件 I2C,需要先 CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE)。跑 Test21 时如果屏幕全亮或全黑,先确认 SDA 波形有没有毛刺,再用逻辑分析仪抓 ACK 位。OLED 的 I2C 地址通常在 0x78 和 0x7A 之间切换,例程写死地址而模块地址拨码不一致时,显示必然异常。
5.3 Test29_4X4MatrixKeyboard:扫描算法
4×4 矩阵键盘例程是这套光盘里综合度最高的外设练习,同时用到 GPIO 的双向配置和扫描逻辑。矩阵键盘把 16 个按键排在 4 行 4 列上,行线和列线交叉处接按键,扫描的基本思路是逐列拉低、读行电平:
#define ROW_PORT GPIOC #define COL_PORT GPIOB const uint8_t ROW_PIN[4] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3}; const uint8_t COL_PIN[4] = {GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7}; uint8_t Key_Scan(void) { uint8_t row, col; for (row = 0; row < 4; row++) { GPIO_WriteLow(COL_PORT, (GPIO_Pin_TypeDef)COL_PIN[row]); /* 当前列拉低 */ for (col = 0; col < 4; col++) { if (GPIO_ReadInputDataBit(ROW_PORT, (GPIO_Pin_TypeDef)ROW_PIN[col]) == RESET) { while (GPIO_ReadInputDataBit(ROW_PORT, (GPIO_Pin_TypeDef)ROW_PIN[col]) == RESET); return row * 4 + col + 1; /* 松手后返回键值 */ } } GPIO_WriteHigh(COL_PORT, (GPIO_Pin_TypeDef)COL_PIN[row]); /* 恢复高电平 */ } return 0; }这段代码先把所有列输出高,然后逐列拉低,读行输入。如果某行引脚读到低电平,说明该行该列的按键被按下。return 前的 while 循环是松手检测,防止一次按键返回多个键值。实际工程里扫描周期要避开按键抖动窗口,一般在主循环里每 10ms 调用一次,比在中断里反复扫描更稳妥。库函数版本的 GPIO_Pin_TypeDef 是枚举类型,数组元素直接传给 GPIO_WriteLow 需要强转,因为函数期望的是位掩码组合,这个细节在 IAR 编译告警里看得很清楚。
6. 光盘源码的验证与升级:三个比跑通 Demo 更值钱的技巧
6.1 用 config.h.bak 做工程配置回溯
光盘里的 config.h.bak、includes.h.bak 是厂商定制例程时留下的备份,比网上下载的干净版本信息量更大。把它们和当前 config.h 做一次 diff,能看出厂商改了什么、为什么改:
diff config.h config.h.bak diff includes.h includes.h.bakdiff 结果只在某个外设宏上有差异,说明厂商为这个 Test 工程定制过外设开关;两者完全一致,说明备份是编译前的原始状态。把这些差异记成自己的移植笔记,后续换芯片、换板子时对照这份笔记,就能快速定位"这个工程为什么在这里加了宏"。
6.2 用引脚翻转加频率计校验节拍
跑通例程只是第一步,验证配置是否正确要靠测量。把 PB0 放在 TIM2 中断里翻转,1ms 节拍对应一个完整方波周期 2ms,频率计读数应该是 500Hz。测出这个值,就同时验证了时钟树、预分频、重载值三条链路。这个方法零成本、可复现、不依赖调试器,适合对光盘里所有 Test 工程做快速回归。
6.3 库函数版本与寄存器混用的边界
官方对 STM8S 标准外设库已经停止功能更新,光盘自带的库函数版本能覆盖绝大多数应用。但中断服务函数里要求极致响应时,库函数的调用开销可能成为瓶颈。常见做法是在 ISR 里直接操作寄存器,例如用TIM2_SR1 &= ~TIM2_SR1_UIF代替 TIM2_ClearITPendingBit,省掉一次函数调用栈的压栈出栈。库函数版本和寄存器混用时,注意库里用 TIM4 实现的延时函数不要和系统 tick 共用同一个重载值,否则改一处时基,另一处全部偏移。把库函数当配置工具、把寄存器当性能手段,是这套配套源码能教给你的最后一层用法。
本文还有配套的精品资源,点击获取