ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32底层理论:从时钟树到寄存器映射的硬核实践

STM32底层理论:从时钟树到寄存器映射的硬核实践 1. “STM32理论”不是空泛概念而是嵌入式开发的底层操作系统思维很多人第一次看到“STM32理论”四个字下意识觉得这是本教材目录里的章节名——枯燥、抽象、离实际项目十万八千里。我刚带实习生时也这么想直到有天凌晨三点被一个GPIO翻转延迟超时的问题卡住反复查寄存器手册才发现问题根本不在代码逻辑而在对APB2总线时钟分频机制的理解偏差。那一刻我才真正明白“理论”在STM32世界里从来不是纸上谈兵它是一套可执行、可验证、可调试的硬件行为模型。你写的每一行HAL库函数背后都对应着至少3个寄存器的位操作你配置的每一个PWM通道实际是定时器计数器、预分频器、自动重装载寄存器三者协同的物理过程你触发的一次外部中断本质是NVIC控制器对EXTI线路电平变化的采样、去抖、优先级仲裁与堆栈压入动作。这些不是“应该知道”的知识而是你按下下载键后芯片真实发生的物理事件链。所以本文不讲“什么是STM32”不罗列“STM32有哪几款芯片”更不会复述数据手册第一页的营销话术。我们只聚焦一件事当你面对一块F103C8T6最小系统板没有任何现成例程仅凭芯片手册和原理图如何从零构建出能稳定运行、可调试、可扩展的最小可行系统这个过程所依赖的就是真正的STM32理论——它由时钟树、存储映射、外设挂载、中断向量表、启动流程这五大支柱构成。它们不是孤立知识点而是一个严密咬合的机械齿轮组改一个时钟分频比可能让USART波特率漂移2%动一个向量表偏移地址整个中断服务函数就永远无法响应误配一个SYSCFG_EXTICR寄存器按键中断就变成随机触发。我在深圳做工业HMI固件时曾因忽略AFIO时钟使能顺序导致SPI引脚复用功能在冷启动时失效产线连续报废37块PCB。这种代价远比花三天啃完《STM32权威指南》第一章来得深刻。你不需要记住所有寄存器地址但必须理解为什么0x4002 1000是RCC基地址、为什么0x0800 0000起始是Flash、为什么NVIC的基地址固定在0xE000 E100。这些地址不是随意分配的它们由ARM Cortex-M3内核的存储器映射规范ARMv7-M Architecture Reference Manual和ST公司对STM32F1系列的外设布局定义共同决定。当你在Keil中看到__main函数跳转到SystemInit()那不是魔法而是启动文件startup_stm32f10x_md.s里一段汇编代码它把SP初始化为栈顶地址然后调用C语言的SystemInit()——而这个函数第一件事就是配置RCC_CR寄存器的HSION位打开内部高速振荡器。没有这一步后续所有外设时钟都无从谈起。这就是理论它让你看清代码执行路径背后的硬件因果链。接下来的内容我会带你亲手拆解这个链条每一步都给出可验证的实操证据比如用示波器抓取PA0引脚翻转波形来反推SysTick实际频率用J-Link RTT实时打印NVIC-ICPR寄存器值来确认中断清除状态。理论的价值永远体现在你能否用它解释异常现象、预测修改后果、定位隐藏缺陷。2. 时钟树STM32运行的绝对心脏90%的疑难杂症根源在此几乎所有初学者遇到的第一个“玄学问题”都与时钟树有关串口发不出数据、ADC采样值全为0、定时器中断频率不对……最后发现全是时钟没配对。这不是巧合因为STM32F103的时钟树是整个系统运行的能源中枢它决定了每个外设的工作节拍。你不能把它当成一个开关——“打开时钟就行”而必须理解它的拓扑结构、信号路径和约束条件。F103的时钟源有三个内部RC振荡器HSI8MHz、外部晶振HSE1-25MHz、PLL倍频输出。它们通过RCC_CFGR寄存器中的SW[1:0]位选择哪个作为系统时钟SYSCLK而SYSCLK又通过AHB预分频器HPRE和APB预分频器PPRE1/PPRE2分频最终供给不同总线上的外设。关键在于APB1最大频率为36MHzAPB2最大为72MHz且APB1分频系数必须≥2即PCLK1≤36MHzAPB2分频系数可为1PCLK2≤72MHz。这个硬性限制直接决定了你能给USART1挂APB2和USART2挂APB1设置的最高波特率。举个真实案例某客户要求用USART1实现2Mbps通信我们按公式USARTDIV (f_PCLK / (16 * baudrate))计算得到USARTDIV2.25于是配置USARTDIV2DIV_Fraction4即0.25。结果通信误码率极高。用逻辑分析仪抓波形发现实际波特率只有1.78Mbps。原因我们忽略了PCLK2的实际频率——原理图上HSE是8MHz晶振但RCC_CFGR寄存器中PLLMUL9所以PLLCLK72MHzSYSCLK72MHzPCLK272MHzPPRE20b000不分频。但问题出在RCC_CFGR的HPRE位我们设为0b1000HCLK SYSCLK/236MHz却忘了PCLK2 HCLKPPRE20b000时所以PCLK2实际是36MHz而非72MHz重新配置HPRE0b0000HCLKSYSCLKPCLK2恢复72MHzUSARTDIV重新计算为72000000/(16*2000000)2.25配置正确后误码率归零。这个错误暴露了对时钟树层级关系的误解PCLK2不是直接来自SYSCLK而是经过HCLK再分频而HCLK又受HPRE控制。要真正掌握时钟树必须动手验证。我推荐一个不可替代的实操方法用PA8MCO引脚输出系统时钟信号。在SystemInit()之后添加RCC-CFGR | RCC_CFGR_MCO_SYSCLK; // 选择SYSCLK作为MCO输出 RCC-APB2ENR | RCC_APB2ENR_IOPAEN; // 使能GPIOA时钟 GPIOA-CRH ~(0xF 32); // 清除PA8模式位 GPIOA-CRH | (0x2 32); // PA8复用推挽输出然后用示波器接PA8就能直观看到SYSCLK频率。再修改RCC_CFGR中的PPRE2位观察波形频率变化。同理用PB10MCO2可输出PLLCLK或HSE信号。这种“眼见为实”的验证比背诵寄存器手册有效十倍。另一个致命陷阱是外设时钟使能顺序。比如要使用SPI1必须先使能RCC_APB2ENR中的SPI1EN位但SPI1的SCK引脚复用功能由AFIO控制而AFIO时钟在RCC_APB2ENR中是AFIOEN位——它必须在SPI1EN之前使能否则即使SPI寄存器配置正确SCK引脚也不会输出波形。我在调试一款电机驱动板时SPI读取编码器数据失败查了两天寄存器最后发现RCC-APB2ENR | RCC_APB2ENR_AFIOEN;这行代码被注释掉了。这种细节只有亲手搭建最小系统并逐个验证外设时钟链路才能刻进肌肉记忆。提示STM32F103的时钟树存在一个隐性约束——当使用HSE作为PLL输入源时HSE必须稳定后才能使能PLL。数据手册明确要求在RCC_CR中置位PLLEN前必须等待RCC_CR的HSERDY位为1。很多初学者直接写RCC-CR | RCC_CR_PLLON;忽略等待循环导致PLL未锁定SYSCLK切换失败系统跑飞。正确做法是RCC-CR | RCC_CR_HSEON; while(!(RCC-CR RCC_CR_HSERDY)); // 等待HSE稳定 RCC-CR | RCC_CR_PLLON; while(!(RCC-CR RCC_CR_PLLRDY)); // 等待PLL锁定 RCC-CFGR ~RCC_CFGR_SW; // 清除SW位 RCC-CFGR | RCC_CFGR_SW_PLL; // 切换SYSCLK为PLL while((RCC-CFGR RCC_CFGR_SWS) ! RCC_CFGR_SWS_PLL); // 等待切换完成3. 存储映射与外设挂载理解地址空间如何决定你的代码能否运行当你在Keil中写GPIOA-ODR ^ GPIO_ODR_ODR0;你以为只是翻转PA0电平但CPU实际执行的是将立即数0x00000001写入地址0x4001080C。这个地址不是随机生成的它是STM32F103存储映射Memory Map的精确坐标。整个4GB的Cortex-M3地址空间被严格划分为多个区域Code区0x00000000-0x1FFFFFFF用于Flash和SRAMPeripheral区0x40000000-0x5FFFFFFF专供外设寄存器Private Peripheral区0xE0000000-0xEFFFFFFF留给内核外设如NVIC、SysTick。其中APB2外设GPIOA-GPIOE、AFIO、EXTI等基地址是0x40010000而GPIOA的寄存器组从0x40010800开始ODROutput Data Register偏移量为0x0C所以GPIOA-ODR地址0x40010800 0x0C 0x4001080C。如果你把GPIOA指针错误地指向0x40010000那是AFIO基地址那么写ODR就变成了配置AFIO的EVCR寄存器结果完全不可预测。这种地址映射的刚性直接决定了你的工程能否链接成功。比如你在.sct分散加载文件中把RW_IRAM1RAM区起始地址设为0x20000000大小0x500020KB但STM32F103C8T6实际SRAM只有20KB0x20000000-0x20004FFF。如果代码中定义了一个全局数组uint32_t buffer[10000];占40KB链接器会报错region RW_IRAM1 overflowed by 20480 bytes。更隐蔽的问题是堆栈溢出默认启动文件中_estack 0x20005000SRAM末尾但如果main()函数局部变量过多或递归过深栈指针SP越过0x20000000进入未映射区域就会触发HardFault。我在调试一款低功耗传感器节点时发现设备在采集第17次数据后死机用J-Link查看SP值发现已跌至0x1FFFFF00明显越界。原因是printf函数在重定向到USART时内部缓冲区和格式化字符串消耗了大量栈空间而我没有在syscalls.c中优化其栈使用。解决方案不是增大栈空间会挤占RAM而是改用轻量级snprintf替代printf并将日志缓冲区移到全局RAM。外设挂载Peripheral Mapping则决定了引脚功能如何被激活。以USART1为例它默认映射到PA9TX和PA10RX但通过AFIO_MAPR寄存器可以重映射到PB6TX和PB7RX。这个重映射不是简单的“换引脚”而是硬件层面的信号路由切换当AFIO_MAPR的USART1_REMAP位为1时芯片内部将USART1的TX/RX信号线从GPIOA切换到GPIOB的复用功能单元。但关键前提是——你必须先使能AFIO时钟RCC-APB2ENR | RCC_APB2ENR_AFIOEN;否则AFIO_MAPR寄存器写无效。我在移植一个旧项目到新PCB时发现USART1在PB6/PB7上始终无输出查遍寄存器最后发现RCC-APB2ENR中AFIOEN位为0。这种“寄存器写了但没生效”的问题根源就是对外设挂载机制缺乏理解挂载是硬件资源需要独立时钟使能且配置顺序有严格依赖。注意STM32F103的存储映射存在一个易被忽略的细节——Bit-Banding位带操作。在0x40000000-0x400FFFFF外设区和0x20000000-0x200FFFFFSRAM区上方各有一个1MB的位带别名区Bit-Band Alias。例如要原子操作GPIOA的ODR0位可直接写*(uint32_t*)(0x42200000 (0x4001080C - 0x40000000)*32 0*4) 1;其中0x42200000是外设位带别名区起始地址。这种方式避免了读-改-写操作但在高频率操作时可能引入额外周期。实践中除非对原子性有严苛要求如多任务抢占否则直接操作ODR寄存器更高效。4. 中断向量表与启动流程从复位到main()的每一步都是精密 choreography当你按下开发板的复位键STM32并非直接跳到main()函数。它遵循ARM Cortex-M3规定的复位向量流程首先从地址0x00000000读取初始栈指针MSP值然后从0x00000004读取复位向量地址跳转执行。这个过程看似简单但每个环节都充满陷阱。F103的向量表默认位于Flash起始地址0x08000000其中0x08000000存放MSP初始值通常是0x20005000即SRAM末尾0x08000004存放复位处理函数地址Reset_Handler。Reset_Handler在启动文件中定义它首先初始化栈指针然后调用SystemInit()最后跳转到__main标准C库入口。__main再调用main()。这个链条中任何一环断裂系统都无法启动。最常见的启动失败场景是向量表偏移错误。当使用IAPIn-Application Programming升级固件时新程序通常烧录到Flash的0x08002000之后避开Bootloader此时必须将向量表重定位到新地址。方法是设置SCB-VTOR 0x08002000;。但很多人忽略了一个前提VTOR寄存器只有在SCB-AIRCR的VECTKEY字段正确写入0x05FA后才可写入。否则写VTOR无效。我在开发一款支持OTA升级的智能锁时升级后设备无法启动用J-Link查看SCB-VTOR发现仍是0x08000000。原因就是重定位代码中漏写了SCB-AIRCR ((0x05FA 16) | (SCB-AIRCR 0x00FF));。这个VECTKEY是ARM内核的安全机制防止意外修改向量表。中断向量表的另一大坑是优先级分组Preemption Priority Subpriority。STM32F103使用NVIC的AIRCR寄存器配置优先级分组PRIGROUP[2:0]位决定抢占优先级和子优先级的位数分配。例如PRIGROUP0b100分组3表示抢占优先级占3位0-7子优先级占1位0-1。关键点在于同一分组下抢占优先级高的中断可以打断抢占优先级低的中断抢占优先级相同时子优先级高的中断先响应但子优先级不能实现抢占。我在调试一个电机控制蓝牙通信的项目时发现PWM更新中断TIM1_UP偶尔被蓝牙UART接收中断USART2打断导致电机电流波动。检查发现两者抢占优先级都是2但TIM1_UP的子优先级是0USART2是1按理应TIM1_UP先响应。问题根源是NVIC配置NVIC-IP[IRQn]寄存器的高4位才是抢占优先级低4位是子优先级。我错误地将NVIC-IP[TIM1_UP_IRQn] 0x20;二进制00100000以为抢占优先级是2实际0010是抢占优先级20000是子优先级0而NVIC-IP[USART2_IRQn] 0x30;00110000抢占优先级是3高于2所以USART2能抢占TIM1_UP。修正为NVIC-IP[TIM1_UP_IRQn] 0x20; NVIC-IP[USART2_IRQn] 0x10;后问题解决。启动流程的终极验证是HardFault调试。当系统跑飞时HardFault_Handler会被触发。标准启动文件中该函数是死循环但我们可以改造它来捕获故障信息。在HardFault_Handler中添加void HardFault_Handler(void) { __asm volatile ( tst lr, #4\n\t // 检查EXC_RETURN是否为线程模式 ite eq\n\t mrseq r0, msp\n\t // 线程模式用MSP mrsne r0, psp\n\t // 异常模式用PSP ldr r1, [r0, #24]\n\t // 获取BFAR总线故障地址 ldr r2, [r0, #16]\n\t // 获取HFSR硬故障状态寄存器 bkpt #0\n\t // 断点方便调试器查看r0,r1,r2 ); }当HardFault发生时调试器会在bkpt处暂停r1寄存器显示触发故障的内存地址如0xFFFFFFF0说明访问了非法地址r2的FORCED位为1表示是硬故障。我在调试一款基于FreeRTOS的任务调度器时发现vTaskDelay()调用后系统死机用此方法捕获到BFAR0x20000000HFSR0x40000000说明访问了SRAM起始地址结合代码发现是队列指针未初始化就使用。这种精准定位能力正是深入理解启动与中断机制带来的直接收益。5. GPIO工作模式与寄存器映射8种模式背后的电气真相GPIO是STM32与物理世界交互的第一道门但它的8种工作模式模拟、浮空输入、上拉/下拉输入、开漏/推挽输出、复用开漏/推挽输出绝非菜单选项而是对引脚内部电路结构的精确描述。以PA0为例当你配置为“上拉输入”实际硬件动作是开启内部上拉电阻约40kΩ断开下拉电阻关闭输出驱动器使能输入缓冲器。此时PA0引脚电压由外部电路决定若悬空则被上拉至VDD若接地则为0V输入缓冲器将此电平转换为数字信号送入AFIO或EXTI。如果误配为“推挽输出”则输出驱动器会强制拉高或拉低引脚与外部电路形成短路风险——我在调试一个温湿度传感器SHT30时将其SDA引脚错误配置为推挽输出结果与主控I2C总线冲突导致总线锁死。理解GPIO寄存器映射是安全操作的前提。F103的GPIO寄存器组包括CRL低8位配置、CRH高8位配置、IDR输入数据、ODR输出数据、BSRR置位/复位、BRR复位、LCKR锁定。其中CRL/CRH每4位控制一个引脚模式MODEy[1:0]决定速度10MHz/2MHz/50MHzCNFy[1:0]决定模式输入/输出/复用。关键陷阱在于CRL/CRH是32位寄存器但只使用低16位每4位一组共4组高16位保留。很多初学者用GPIOA-CRL 0x44444444;试图配置所有引脚结果MODE0被设为0x4非法值导致PA0行为不可预测。正确做法是按位操作GPIOA-CRL ~(0xF 0); // 清除PA0的CNF0/MODE0 GPIOA-CRL | (0x8 0); // PA0: 输入模式上拉CNF0b00, MODE0b00? 不对 // 实际上上拉输入需CNF0b00输入模式且在ODR写1使能上拉 GPIOA-ODR | GPIO_ODR_ODR0; // 设置PA0上拉这里暴露了另一个误区上拉/下拉不是CRL/CRH配置的而是通过ODR寄存器控制的。当引脚为输入模式时ODR的对应位为1则启用上拉为0则启用下拉若下拉使能。因此配置上拉输入的完整步骤是1)CRL/CRH设为输入模式CNF0b002)ODR对应位置1。最易被忽视的模式是“复用开漏输出”。I2C总线要求SDA/SCL为开漏结构允许多主设备共享总线。配置时需CRL/CRH设为复用功能CNF0b10ODR对应位置0开漏并外接上拉电阻。如果误设为推挽输出当两个设备同时驱动SDA时一个拉高一个拉低形成直流通路可能烧毁IO口。我在设计一款多节点CAN总线网关时曾因CAN_TX引脚配置为推挽而非复用推挽导致总线电平异常用示波器测得TX波形畸变。复用模式的本质是将引脚信号路由到片上外设如USART、SPI、I2C的输入/输出端口而非直接连接到GPIO数据寄存器。经验提示GPIO的“模拟输入”模式常被用于ADC采样但它要求关闭所有数字功能以降低噪声。配置时需确保CRL/CRH的CNFy[1:0]0b00模拟模式且ODR对应位不影响模拟模式下ODR无效。此外ADC通道与GPIO引脚的映射关系由数据手册“Alternate Function Mapping”表格定义例如PA0可作为ADC1_IN0但PB0是ADC1_IN8不可混淆。我在调试一款电池电压监测电路时误将PB0ADC1_IN8当作PA0ADC1_IN0使用结果采样值始终为0因为PB0并未连接到电池分压网络。6. PWM与定时器不只是占空比调节而是时间精度的物理实现PWM在STM32中并非一个独立外设而是通用定时器TIM2-TIM5或高级控制定时器TIM1/TIM8的功能模块。它的核心是“计数器比较寄存器”的物理机制定时器计数器CNT在时钟驱动下递增当CNT等于比较寄存器CCR值时输出通道CH1-CH4电平翻转当CNT达到自动重装载寄存器ARR值时CNT清零并触发更新事件UEV。这个过程的精度完全取决于定时器时钟源PCLK1/PCLK2经分频后的稳定性和ARR/CCR寄存器的分辨率。以TIM2挂APB1产生1kHz PWM为例。假设PCLK136MHzTIM2预分频器PSC设为35则TIM2时钟频率36MHz/(351)1MHz。要得到1kHz周期ARR值1MHz/1kHz1000。若占空比50%则CCR500。但问题在于ARR和CCR是16位寄存器最大值65535因此最低PWM频率受限于PCLK1和PSC。若PCLK136MHzPSC0ARR最大65535则最低频率36MHz/65535≈549Hz若需1Hz PWM则需PSC≥3599936MHz/1Hz/65535≈549此时定时器溢出周期长达1秒但计数精度只有1μs1MHz时钟无法满足亚毫秒级控制需求。解决方案是使用更高频率的时钟源如PLLCLK分频或选择TIM1挂APB2PCLK272MHz。PWM的另一个关键维度是死区时间Dead Time控制这在电机驱动中至关重要。当TIM1的CH1/CH1N互补通道同时驱动H桥上下管时必须插入死区防止直通短路。TIM1_BDTR寄存器的DTG[7:0]位定义死区时间单位为TIM1时钟周期。例如TIM1时钟72MHzDTG0x1F31则死区时间31/72MHz≈430ns。但死区时间过长会减小有效占空比范围过短则无法避免直通。我在调试一款BLDC电机控制器时死区设为200ns结果在高负载下仍出现功率管击穿。用示波器测量上下管驱动波形发现重叠时间约50ns于是将DTG增至0x3F63死区达875ns问题解决。这说明PWM理论必须结合实际功率器件的开关特性来调整。高级PWM功能如“单脉冲模式”One Pulse Mode常被用于精确控制电磁阀开启时间。配置TIM1为单脉冲TIM1-CR1 | TIM_CR1_OPM;然后启动定时器CNT从0开始计数到达CCR值时CH1翻转到达ARR值时自动关闭CH1并停止计数。这种模式无需软件干预确保脉冲宽度绝对精准。我在设计一款高精度气动执行器时要求阀门开启时间误差1μs采用单脉冲模式配合72MHz时钟ARR72实现1μs脉冲实测误差±0.2μs。相比之下用普通PWM加软件延时受中断延迟影响误差达10μs以上。实操心得PWM输出的电平极性可通过TIMx_CCER寄存器的CCxP位控制。默认CCxP0为高有效CNTCCR时输出高但某些场合需要低有效如驱动共阳极LED此时设CCxP1。注意极性切换不影响占空比计算只改变输出逻辑。另外PWM频率与占空比的权衡需考虑负载特性。例如驱动直流电机时低频PWM1-2kHz易引起 audible noise高频10-20kHz可消除噪音但增加MOSFET开关损耗。我通常选择16kHz兼顾静音与效率并在PCB上为MOSFET添加RC缓冲电路抑制dv/dt。7. I2C与SPI同步通信协议的硬件实现差异与调试铁律I2C和SPI虽同为同步串行通信但硬件实现逻辑截然不同导致调试策略根本不同。I2C是开漏总线依赖外部上拉电阻具有多主、多从、地址寻址、ACK/NACK握手等复杂协议SPI是推挽总线主从固定无地址概念靠片选CS区分从机协议简单但引脚更多。F103的I2C1挂APB1SPI1挂APB2这意味着它们的时钟源和最大速率不同I2C1最高400kHz快速模式SPI1最高36MHzPCLK272MHz分频后。I2C调试的首要铁律是总线电平与上拉电阻匹配。标准I2C要求SDA/SCL在400kHz时上升时间≤300ns。上升时间tr ≈ 0.69 * Rpullup * Cbus其中Cbus为总线电容含PCB走线、器件引脚电容通常20-40pF。若Rpullup4.7kΩCbus30pF则tr≈0.69*4700*30e-12≈97ns符合要求。但若Rpullup10kΩtr≈207ns仍可接受若Rpullup47kΩtr≈1000ns则I2C时序违规通信失败。我在调试一款环境光传感器TSL2561时发现ACK信号丢失用示波器测得SCL上升时间达1.2μs更换为2.2kΩ上拉电阻后恢复正常。这说明I2C问题80%源于硬件设计而非软件。SPI调试的核心是时钟相位CPHA与时钟极性CPOL匹配。CPOL决定空闲时SCK电平0低1高CPHA决定数据采样时刻0上升沿1下降沿。F103的SPI1默认CPOL0, CPHA0即SCK空闲为低数据在SCK上升沿采样。但某些Flash芯片如W25Q80要求CPOL0, CPHA1数据在下降沿采样。若配置错误MISO数据在错误边沿被采样读取全为0xFF。解决方案是查阅从机数据手册的时序图严格匹配SPI_InitTypeDef结构体中的SPI_CPOL和SPI_CPHA字段。我在移植一款SPI NOR Flash驱动时因未修改CPHA导致读ID命令返回0x0000用逻辑分析仪抓波形发现MISO数据在SCK下降沿才稳定而MCU在上升沿采样故读错。另一个致命陷阱是DMA与SPI的协同。当使用DMA发送SPI数据时必须确保DMA传输完成中断DMA1_Channel3_IRQn在SPI传输完成中断SPI1_IRQn之前触发否则可能在DMA未结束时就修改SPI寄存器。正确做法是1) 启用SPI的SPI_I2S_IT_TXE发送缓冲区空中断和SPI_I2S_IT_RXNE接收缓冲区非空中断2) 在TXE中断中写入下一个字节3) 在RXNE中断中读取接收字节4) 当SPI_SR的BSY位清零时表示传输完成。DMA方式则需配置SPI_I2S_DMAReq_Tx并在DMA传输完成回调中关闭SPI。我在开发一款高速图像采集模块时因DMA中断优先级低于SPI中断导致DMA传输未完成时SPI被关闭图像数据错乱。将DMA中断优先级设为高于SPI中断后解决。调试工具建议I2C必备工具是逻辑分析仪如Saleae可直观显示起始/停止条件、地址、数据、ACK/NACK波形SPI则推荐示波器抓SCK
返回列表