STM8S105C6开发板库函数例程全解析:从GPIO到矩阵键盘
简介博越实体电子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 工程文件组织而不是 STVDCosmic 的 .stp 工程。第二个有用信息是调试链路。STM8S105C6 没有 JTAG只有 SWIM 单线调试接口IAR 里对应 ST-LINK 调试器。把 ST-Link 的 SWIM、RST、GND、3.3V 四根线接到开发板在 Project Options Debugger 里选择 ST-LINKInterface 选 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_MDstm8s_conf.h 决定编译哪些外设模块注释掉不用的模块能明显缩短编译时间stm8s_it.c 是全部中断服务函数的落点库函数版本把向量表在编译期绑定到这个文件里函数名写错或者放错文件中断就静默失效。文件作用移植时注意stm8s.h型号宏、全局数据类型STM8S105C6 必须打开 STM8S_MDstm8s_conf.h外设模块开关注释掉无用模块减少编译与告警stm8s_it.c中断向量与 ISR 定义函数名必须和宏绑定一致stm8s_clk.c/h时钟树配置复位后 CPU 实际只跑 2MHzstm8s_gpio.c/hGPIO 模式与读写模式枚举集中在这份头文件里这套结构的价值在于不需要从零搭工程。任意挑一个 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 升到 16MHzCLK_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 验证系统时钟是否真实跑到 16MHzTest4_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/128125kHz125 次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 的定位完全不同定时器位数计数上限典型用途TIM216 位65535毫秒级延时、PWM、输入捕获TIM48 位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_1I2C 引脚与显存刷新OLED 例程在光盘里通常走 I2C 接口。STM8S105C6 的硬件 I2C 引脚是 PB4SCL和 PB5SDA外接 OLED 模块时这两根线需要上拉电阻多数模块自带上拉不带的要补 4.7kΩ引脚功能连接 OLEDPB4I2C_SCLSCKPB5I2C_SDASDAVDD3.3VVCCGNDGNDGNDOLED 是 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 共用同一个重载值否则改一处时基另一处全部偏移。把库函数当配置工具、把寄存器当性能手段是这套配套源码能教给你的最后一层用法。本文还有配套的精品资源点击获取