
简介面向STM8S103K3单片机开发者的完整资料包以最小系统板PDF原理图、IAR/STVD可运行例程和STM8官方标准外设库为核心兼顾入门学习与项目参考需求适合电子专业学生、嵌入式初学者及工程师用作设计蓝本。压缩包共143.57MB主要文件类型包括PDF文档与工程源码其中原理图便于硬件连线参考IAR与STVD工程例程可快速验证GPIO、定时器、串口等外设功能中文手册与教材帮助理解寄存器配置及调试方法。已有855人学习下载资料实用性得到初步验证。此外还收录了基于STM8的无刷直流电机控制、滴灌自动控制、无线远程海洋监测、新型光伏控制器等设计论文可借鉴完整项目思路与电路方案帮助读者从最小系统扩展到实际产品开发还可作为课程设计、毕业设计的参考资料。1. 这块 8 位芯片凭什么还在被量产STM32 几乎统治了教科书和培训班的讲台但真正让我在产线上吃过亏又长记性的反而是今天要拆的这颗 STM8S103K3。它没有 FPU、没有硬件除法器、主频最高也就 16MHz可它 1.8V 到 3.6V 的宽压供电、片内 640 字节 EEPROM、以及无比顽强的 IO 驱动能力让它至今还在小家电、电动工具、传感器变送器和汽车电子周边大量出货。这套资源合集的价值在于它把「入门路线」压缩成了一个压缩包IAR 与 STVD 两套工具链的例程源码、标准外设库 V2.1.0、最小系统板 PDF 原理图还有一批基于 STM8 的完整设计论文从滴灌控制器到无刷电机驱动全都有。你在 STM32 上练熟的那套 GPIO 翻转、定时器中断、PWM 输出思维在这颗芯片上要重新校准一遍。原因很简单寄存器变少了外设变简单了但中断向量、时钟树、选项字节这些底层概念反而被逼着必须弄懂。这篇文章会带着你从原理图上的最小系统三要素开始把两套工具链的工程差异、标准外设库的调度逻辑、以及参考论文里那些可以直接移植的模块设计逐个过一遍。2. 原理图上的最小系统三要素与供电设计2.1 从 PDF 里读出的复位、时钟和启动配置打开压缩包里的 STM8S103K3T6 原理图 PDF第一件事不是看芯片引脚连了什么而是找三样东西复位电路、时钟电路、启动配置。STM8S103K3 的 NRST 引脚是低电平复位原理图上通常是一个 100nF 电容对地配合内部上拉电阻就能工作成本敏感的设计直接省掉外部复位芯片。这一点和 STM32 的 NRST 电路逻辑一致但注意 STM8 没有 BOOT0/BOOT1 引脚启动模式由选项字节里的 BOOT 位决定原理图上你根本看不到跳线帽。晶振方面要注意一个常见误区STM8S103K3 的内部 RC 振荡器HSI出厂校准精度在 1% 以内对于串口通信波特率 9600 或 115200 完全够用。原理图上那颗 8MHz 或 16MHz 晶振通常不是必须的它的作用是把时钟精度从 1% 提升到 0.5% 以下用于 CAN、定时器精确延时或者对时钟要求严格的通信协议。外部晶振通过 OSCIN/OSCOUT 引脚接入配合两个负载电容典型值是 20pF 到 22pF具体值要看晶振手册而不是抄 STM32 的电路。VCAP 引脚是 STM8 特有的内部核心电压 1.8V 就是从这里输出的外部必须接一个 470nF 或 1μF 的陶瓷电容到地。这个电容被很多人忽略但它直接影响芯片能否稳定启动。数据手册上明确写的是 VCAP 电容不得小于 470nF且要尽量靠近引脚放置。如果你画 PCB 时把这个电容放远了高速运行或者温度变化时会出现莫名其妙的复位这是我在实际项目中踩过的坑。2.2 电源引脚滤波与去耦电容的摆位最小系统板原理图上的电源部分一般是一个 LDO 把 5V 降到 3.3VVDD 引脚接 100nF 去耦电容。这里要强调一点STM8S103K3 的每个 VDD 引脚都要单独配一个去耦电容不能共用一个。数据手册的电源章节给出了典型连接VDD 和 VSS 之间除了 100nF通常还会并联一个 1μF 到 10μF 的钽电容或电解电容做低频滤波。IO 引脚直接驱动 LED 时最好查一下数据手册的绝对最大额定值。STM8S103K3 的 IO 灌电流能力比 STM32 强很多单个引脚可以到 20mA 左右但这不是让你把 LED 直接挂上去的理由。我一般会在 LED 支路串一个 330Ω 或 470Ω 电阻把电流限制在 5mA 到 10mA既保证亮度又避免长时间满负荷运行导致芯片发热。2.3 把 PDF 原理图转成可复用的设计约束拿到别人画的原理图不能只看连接关系还要把里面的设计约束提取出来。比如原理图上如果有一颗 8MHz 晶振你就知道这个板子的固件大概率用了外部时钟模式。如果 NRST 引脚上除了电容还有一个按键到地那这个板子是便于调试的。这些信息比原理图本身的网络连接更有价值因为你要复用的是设计决策而不只是导线。提示STM8S103K3 的 PA1 引脚在最小系统板上通常直接引出但它兼作 SWIM 调试接口的 SWIM 信号。画原理图时注意不要把大电容或者强下拉电阻接到 PA1 上否则调试器可能连不上目标芯片。3. IAR 与 STVD 双工具链的例程源码对比3.1 IAR for STM8 工程的目录结构与编译选项压缩包里的 IAR103 例程用的是 IAR Embedded Workbench for STM8这个 IDE 的工程文件扩展名是 .ewp。第一次打开工程时IAR 会提示选择设备型号你要在设备列表里选 STM8S103K3 或者 STM8S103F3这两者的 Flash 容量和 RAM 大小一致只是引脚数和封装不同。选错型号的后果是链接器报地址溢出错误因为 F3 是 20 脚封装而 K3 是 28 脚但它们的存储空间是一样的所以这个问题在编译阶段通常不会暴露要到下载时才会发现。在 Debugger 设置里驱动程序要选 ST-LINK 或者 ST-LINK/TB取决于你手上的调试器版本。Protocol 选 SWIM接口速度可以先用默认值如果连接不稳定再降到 500kHz 以下。IAR 的工程选项里有个关键参数是__CSMC还是大小端模式STM8 是大端模式但这是编译器自动处理的你不需要手动配置。// main.c IAR 环境下最简工程的入口 #include stm8s.h void main(void) { // 默认时钟是 HSI/8 2MHz这里切换到 16MHz 全速运行 CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); GPIO_Init(GPIOC, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST); while (1) { GPIO_WriteReverse(GPIOC, GPIO_PIN_5); // 软件延时16MHz 下大约 100ms for (volatile long i 0; i 100000; i); } }这段代码的逻辑很简单CLK_HSIPrescalerConfig 把内部高速时钟从默认的 8 分频切换到 1 分频让 CPU 跑在 16MHz。GPIO_Init 配置 PC5 为推挽输出低电平初始状态高速翻转。GPIO_WriteReverse 是标准外设库里的翻转函数每调用一次引脚电平反转一次。如果你用的是寄存器操作对应代码是GPIOC-ODR ^ GPIO_PIN_5。3.2 STVD 与 Cosmic 编译器的兼容性处理STVDST Visual Develop是意法半导体官方的免费 IDE搭配的编译器是 Cosmic也有选择 Raisonance 的版本。这两套工具链的例程源码在语法上基本一致因为底层都是调用 ST 的标准外设库但在工程配置上差异明显。STVD 的工程文件是 .stp打开工程后第一件事是到 Project Settings 里确认 MCU 型号选的是 STM8S103K3Compiler 选 Cosmic。STVD 的编译选项里有个特别的坑内存模型Memory Model要选 small 还是 large。STM8S103K3 只有 1KB RAM代码也不大选 small 即可。如果你把默认的 memory model 改成 large编译器会为每个指针多生成一个字节的处理逻辑代码体积增大但运行正确性不受影响。// 在 STVD 中使用位段操作点亮 LED 的等价写法 #include stm8s.h void main(void) { // STVD 下同样调用标准外设库接口与 IAR 完全一致 CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST); while (1) { GPIO_WriteHigh(GPIOD, GPIO_PIN_0); // 长延时 for (volatile unsigned int i 0; i 50000; i); GPIO_WriteLow(GPIOD, GPIO_PIN_0); for (volatile unsigned int i 0; i 50000; i); } }对比两套工具链的代码可以发现标准外设库把底层差异完全屏蔽了。GPIO_Init、GPIO_WriteHigh、GPIO_WriteLow 这些函数的参数和实现在 V2.1.0 库里完全一致差异只存在于 IDE 的工程配置和编译器的启动文件里。所以如果你在 IAR 里调通了逻辑代码复制到 STVD 里重新编译就能跑前提是包含了正确的头文件路径和库文件路径。3.3 两套工具链的调试器连接与选项字节设置不管是 IAR 还是 STVD连接调试器的第一步都是选中 SWIM 协议。SWIM 是意法半导体自己的单线调试接口只需要一根信号线所以很多最小系统板上找不到标准的 4 针 SWD 接口取而代之的是 3 针 SWIM 排针SWIM、VDD、GND。运行时SWIM 引脚内部有上拉外部不需要再接。下载程序时如果提示SWIM error先查三处VCAP 电容是否焊接、NRST 引脚是否被外部电路强制拉高、PA1 上是否挂了过大的负载电容。我遇到过最隐蔽的问题是调试器线序SWIM 信号线如果和三根线中的 GND 接反了不会烧芯片但会一直报连接超时。表两套工具链核心差异对比项IAR EW for STM8STVD Cosmic工程文件后缀.ewp.stp编译器IAR 自带Cosmic / Raisonance标准外设库支持原生支持需要手动添加库路径代码优化等级可到 High免费版最高到 Medium调试器接入ST-LINK via SWIMST-LINK via SWIM / RST4. 标准外设库 V2.1.0 的例程源码结构4.1 库的文件夹体系与头文件引用关系压缩包里的 STM8S_StdPeriph_Lib_V2.1.0.zip 是 2014 年前后发布的版本解压后你会看到 Libraries 文件夹下分 STM8S_StdPeriph_Driver 和 CMSIS 两层。CMSIS 层里的 stm8s.h 是总头文件它根据你定义的STM8S103宏来决定具体包含哪个芯片的头文件。每个外设驱动由一个 .c 和一个 .h 组成比如 stm8s_gpio.c、stm8s_tim1.c这些文件的接口定义是全系列芯片通用的。例程源码分散在 Project/STM8S_StdPeriph_Examples 目录下每个外设一个子目录。最值得看的是 GPIO、TIM1、UART1 和 I2C 这四个例子因为它们覆盖了嵌入式开发里 80% 的常用操作。打开一个例程工程前要在编译器预处理选项里加上STM8S103这个宏定义否则 stm8s.h 会不知道怎么选择芯片型号直接编译报错。// stm8s.h 中通过宏选择芯片系列的逻辑节选 #if defined(STM8S103) #include stm8s103.h #elif defined(STM8S105) #include stm8s105.h #elif defined(STM8S207) #include stm8s207.h #endif这段预编译选择逻辑是理解整个库的关键。你在 IAR 的 Project - Options - C/C Compiler - Preprocessor 里定义的宏决定了整个库最终面向哪个寄存器映射文件。编译时如果出现unknown type name GPIO_TypeDef八成就是这个宏没定义或者被注释掉了。4.2 GPIO 初始化结构体与模式枚举的语义标准外设库的 IO 初始化遵循「先定义结构体再调用初始化函数」的模式但 STM8 库和 STM32 库有个显著区别STM8 没有单独的 GPIO_InitTypeDef 结构体而是直接在 GPIO_Init 函数里传引脚号、模式和速度三个参数。这是因为 STM8 的端口寄存器只有 4 个ODR、IDR、DDR、CR1、CR2比 STM32 的每个端口 7 个寄存器简单得多。GPIO_Mode 枚举值里有几个容易混淆的选项。GPIO_MODE_OUT_PP_LOW_FAST 表示推挽输出、初始电平为低、翻转速度为 fast对应寄存器设置是 DDR1输出模式、CR11推挽、CR21高速。GPIO_MODE_IN_FL_NO_IT 表示浮空输入、不使能外部中断这是读取按键或外部信号最常用的配置。读输入引脚电平用的是 GPIO_ReadInputPin它返回 Bit_SET 或 Bit_RESET不是直接返回 0 或 1。// 按键输入 LED 输出的完整读取逻辑 GPIO_Init(GPIOC, GPIO_PIN_3, GPIO_MODE_IN_FL_NO_IT); // PC3 作为按键输入 GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST); // PB5 驱动 LED if (GPIO_ReadInputPin(GPIOC, GPIO_PIN_3) RESET) { GPIO_WriteHigh(GPIOB, GPIO_PIN_5); // 按键按下时点亮 LED } else { GPIO_WriteLow(GPIOB, GPIO_PIN_5); }这里需要注意 GPIOC 的 3 脚在 STM8S103K3 上是否可用。K3 封装有 28 个引脚PA、PB、PC、PD 四个端口都有暴露但有些封装里特定引脚被电源或者晶振占用必须对照原理图确认你用的引脚没有和 SWIM、NRST 冲突。4.3 定时器 TIM1 与 TIM2 的库函数触发方式TIM1 是 16 位高级定时器有 4 个捕获比较通道可以输出互补 PWM 带死区这个外设在无刷电机控制里是主角。标准外设库提供了 TIM1_TimeBaseInit 来配置计数周期和预分频值。它的参数里有个 PWM 模式选择是 TIM1_OCMode_PWM1含义是计数到比较值时输出翻转PWM 的占空比由 TIM1_SetCompare1 函数设定。用库函数初始化 TIM1 输出频率为 1kHz、占空比可调的 PWM 波代码骨架如下。注意库函数的参数传递顺序是预分频、周期、重复计数比较容易记反的是前两个参数预分频值决定计数时钟周期值决定溢出时间。void Timer1_PWM_Init(void) { TIM1_TimeBaseInit(16 - 1, TIM1_COUNTERMODE_UP, 1000 - 1, 0); // 预分频 1516MHz / 16 1MHz 计数频率 // 计数到 999 溢出所以 PWM 输出频率是 1MHz / 1000 1kHz TIM1_OC1Init(TIM1_OCMODE_PWM1, 500, TIM1_OCPOLARITY_HIGH, TIM1_OCIDLESTATE_RESET); TIM1_OC1PreloadConfig(ENABLE); // 使能预装载更新占空比不产生毛刺 TIM1_Cmd(ENABLE); }这个代码里的 500 是初始比较值占空比就是 500/1000 50%。改动占空比的方法是调用 TIM1_SetCompare1(700) 让占空比变 70%。TIM1_OC1PreloadConfig 这行很关键不清它的话在 PWM 运行中修改比较值可能会出现一次异常的脉宽导致电机抖动一下。TIM2 是通用定时器没有互补输出和死区功能但用法和 TIM1 几乎一样。如果你只是做 LED 呼吸灯或者蜂鸣器频率控制用 TIM2 就够了没必要占用 TIM1。查询数据手册可以看到 STM8S103K3 只有一个 TIM1 和一个 TIM2所以高级定时器资源很宝贵设计时要提前分配。4.4 例程源码中 UART 中断接收的数据流逻辑UART 例程是很多人入门串口通信的起点。STM8S103K3 的 UART1 支持 115200 波特率标准外设库通过 UART1_Init 配置波特率、数据位数、停止位和校验方式。使能接收中断后数据到达会触发 UART1_IRQHandler中断服务函数里调用 UART1_ReceiveData8 把数据从 RDR 寄存器读出来。这里有个潜在问题如果接收中断使能但你没有及时读走数据RXNE 标志不会被清除中断会反复进入造成系统卡死。更稳妥的做法是在中断服务函数开头先读 UART1-SR 再读 UART1-DR这是 STM32 用户常用的读 SR 再读 DR清标志套路。标准外设库的 UART1_ReceiveData8 内部只做读 DR 这一步但它不会自动清 RXNE。实际上读 DR 这个动作本身就清了 RXNE所以只要你在中断里执行过一次读取标志位就会复位。// UART1 接收中断服务函数 void UART1_IRQHandler(void) __interrupt(18) { uint8_t data; if (UART1_GetFlagStatus(UART1_FLAG_RXNE) SET) { data UART1_ReceiveData8(); // 收到数据后原样发回即回声功能 UART1_SendData8(data); while (UART1_GetFlagStatus(UART1_FLAG_TXE) RESET); } }代码里__interrupt(18)这个语法是 IAR 的中断号写法18 对应 UART1 的中断向量。如果你在 STVD 里编译语法会变成far interrupt void UART1_IRQHandler(void)这也是两套工具链在代码层面最大的差异点之一。数据手册中断向量表的第 18 个中断向量就是 UART1这个序号建议对照查询不要凭记忆写。5. 参考设计论文中的实战模块与移植价值5.1 无刷直流电机控制系统里的 TIM1 应用压缩包里那篇采用 STM8S 的无刷直流电机控制系统设计论文把 STM8S103K3 在电机控制上的能力边界讲得非常清楚。无刷电机的方波驱动需要六步换相逻辑每一刻只有两相导通换相时刻由霍尔传感器反馈决定这部分计算量在 8 位机上完全扛得住。论文里的关键设计是用 TIM1 的通道 1、2、3 输出三路 PWM再用 GPIO 控制上桥和下桥的使能端。移植这篇论文的电路设计到自己的最小系统板时要特别检查 TIM1 的输出引脚是否和你的板子引出冲突。STM8S103K3 的 TIM1 通道映射在 PD2、PD3、PD4 或 PC6、PC7、PC3 上不同封装和重映射选项会改变引脚位置。标准外设库提供了 GPIO_ConfigPinRemap 函数来切换重映射但这个函数的参数和 STM32 不同它接收的是具体引脚而不是外设号。5.2 红外与超声波测距的定时器输入捕获模式基于 STM8 的红外与超声波测距仪设计论文用的是 TIM1 输入捕获来测量回波脉冲宽度。超声波模块发出的声波遇到障碍物返回ECHO 引脚输出一个宽度正比于距离的高电平用 TIM1 捕获这个高电平的持续时间就能算出距离。核心代码思路是TIM1 输入捕获上升沿启动计时下降沿读取计数值两次读数差值乘以声速除以 2 得到距离。// 超声波测距中定时器捕获的差值计算 uint16_t start_time, end_time, pulse_width; float distance_cm; // 上升沿捕获中断里记录起始时间 start_time TIM1_GetCapture1(); // 下降沿捕获中断里记录结束时间 end_time TIM1_GetCapture1(); pulse_width end_time - start_time; // 无符号减法自动处理溢出回绕 distance_cm (float)pulse_width * 0.017; // 单位换算系数这个 0.017 的系数是这么来的计数频率为 1MHz每个计数值代表 1 微秒声速 340m/s 相当于每微秒走 0.034cm距离是来回的一半所以 0.034 除以 2 等于 0.017。如果你的定时器预分频不是 15系数要重新算。无符号减法的溢出回绕特性在这里反而帮了忙它避免了手动判断谁先谁后的麻烦。5.3 光伏控制器与滴灌系统的 ADC 采样框架基于 STM8 的新型光伏控制器设计论文里ADC 采样的代码框架非常典型。STM8S103K3 有 5 个 ADC 输入通道10 位分辨率库函数 ADC1_Init 用来配置转换模式。用 ADC 采集电池电压时一般用连续转换模式配合 DMA 或者直接轮询读取。STM8 的 ADC 没有 DMA所以通常的做法是开启 EOC 中断在中断里读一次数据。// ADC 单次转换读取电池电压 uint16_t adc_value; float battery_voltage; ADC1_Init(ADC1_CONVERSIONMODE_SINGLE, ADC1_CHANNEL_3, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM1_DISABLE, ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_ALL_DISABLE); ADC1_StartConversion(); while (ADC1_GetFlagStatus(ADC1_FLAG_EOC) RESET); adc_value ADC1_GetConversionValue(); battery_voltage (float)adc_value * 3.3f / 1024.0f * 电压分压比;ADC1_Init 的前两个参数最容易搞错第一个是转换模式第二个是采样的通道号。通道号对应的是 ADC 输入引脚编号不是 GPIO 编号比如通道 3 对应的是 PB0 引脚具体要查数据手册的 ADC 引脚映射表。如果采样结果一直为 0先查这个映射关系大概率是通道号写错了。5.4 无线语音对讲和智能门镜中的低功耗设计无线语音对讲系统论文值得借鉴的是它的低功耗策略。STM8S103K3 支持 WFI等待中断和 HALT停机两种低功耗模式HALT 模式下电流可以降到微安级靠外部中断唤醒。语音对讲系统里麦克风采集需要 ADC 连续工作但射频模块发送时 CPU 大部分时间在等待这个间隙就可以进入 WFI 而不是空转。// 进入停机模式并等待外部中断唤醒 disableInterrupts(); // 配置外部中断唤醒源 GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_IN_PU_IT); EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOB, EXTI_SENSITIVITY_FALL_ONLY); enableInterrupts(); halt();halt() 是库函数里直接操作 HALT 指令的接口。执行之后 CPU 停止时钟所有外设也停止工作任何配置过的外部中断都可以把它唤醒。注意执行 halt() 之前必须先失能中断再重新使能否则中断标志位可能残留导致唤醒后立即再次中断这个细节论文里不会写但实际调试时一定会碰到。6. 用宏开关实现内部 RC 与外部晶振的代码级切换STM8S103K3 的时钟源选择在库函数里只有一行但真正工程化的时候你会希望硬件上不修改 PCB 就能在内部 RC 和外部晶振之间切换这就要用到条件编译。做法是在工程的头文件里定义一个宏比如USE_EXT_CLOCK然后在时钟初始化代码里根据这个宏编译不同的分支。// clock_config.h #define USE_EXT_CLOCK 0 // 1: 外部晶振 0: 内部 RC 16MHz // clock_config.c void SystemClock_Init(void) { #if (USE_EXT_CLOCK 1) CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE); #else CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); #endif }这种写法在批量交付固件时很有用。同一个 HEX 文件烧进不同硬件版本只要出厂前把宏改掉重新编译即可。要注意 CLK_SWITCHMODE_AUTO 是自动切换模式库函数会等外部晶振稳定后再切如果外部晶振没焊接或者不起振代码会卡在等待标志位清零那里。排查这个问题的方法是看晶振两脚波形没有波形就检查负载电容和晶振本身。时钟树从上往下看还有一层关系容易忽略外设时钟使能。调用 TIM1、UART1、ADC1 的库函数之前先要调用 CLK_PeripheralClockConfig 打开对应外设的时钟门控。STM8 的外设时钟默认全是关的这点和 STM32 一样但 STM8 的标准外设库 init 函数里不会自动做这件事你必须手动先开时钟再配外设。CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIM1, ENABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, ENABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC1, ENABLE);如果你发现某个外设的寄存器读出来全是 0xFF 或者配置不生效第一反应应该是查这个外设的时钟门控有没有打开而不是怀疑寄存器地址写错。这是从 STM32 转到 STM8 最容易踩的坑毕竟 STM32 的 RCC 外设时钟使能位在 RCC 寄存器里一眼能看到STM8 的 CLK 外设时钟控制位在 CLK_PCKENR1 和 CLK_PCKENR2 里位置更隐蔽。最后给出一个从零开始的工程搭建顺序参考先定义STM8S103宏再包含stm8s.h初始化时按「时钟切换 - 外设门控 - GPIO 配置 - 外设功能配置」的次序执行。这套顺序在 IAR 和 STVD 下都适用它和数据手册里推荐的启动流程一致按这个次序写出来的初始化代码排查问题时也容易按外设逐一隔离。本文还有配套的精品资源点击获取