EM773 SPI与定时器实战:Microwire协议与PWM生成详解 1. 项目概述与核心价值在嵌入式开发领域尤其是基于NXP EM773这类微控制器的项目中与外设进行可靠、高效的通信以及实现精准的定时控制是两项最基础也最核心的任务。前者通常由SPISerial Peripheral Interface这类串行总线协议承担而后者则依赖于芯片内置的定时器/计数器模块。你可能已经无数次地在数据手册里见过SPI和定时器的章节但当你真正需要驱动一个特殊的传感器或者生成一组精确的PWM波形时才会发现手册里那些时序图和寄存器描述背后藏着许多决定成败的细节。这次我们就以EM773微控制器为蓝本深入它的SPI0带SSP功能模块和16/32位定时器。我们不仅要看懂手册更要弄明白在实际编程中如何让SPI支持像Microwire这样的特殊帧格式以及如何将定时器的匹配、捕获功能玩出花样最终稳定地输出PWM。我会结合自己调试这类外设时踩过的坑把寄存器配置背后的逻辑、时序上的微妙要求以及代码编写时的注意事项掰开揉碎讲清楚。无论你是刚开始接触EM773还是正在为某个外设的异常时序头疼这篇文章都能给你提供一套可直接落地的思路和避坑指南。2. EM773 SPI0 with SSP模块深度解析EM773的SPI0模块并非一个简单的标准SPI控制器它集成了SSPSynchronous Serial Port的特性使其协议支持能力更加灵活。这意味着它不仅能处理标准的全双工SPI通信还能适配像Microwire这样的半双工变种协议。理解这一点是正确配置和使用该模块的前提。2.1 SPI基础与SSP增强特性回顾标准的SPI通信是一种全双工、同步的串行通信方式通常包含四根线SCK (Serial Clock): 串行时钟由主设备产生。MOSI (Master Out Slave In): 主设备数据输出从设备数据输入。MISO (Master In Slave Out): 主设备数据输入从设备数据输出。SSEL (Slave Select): 从设备片选低电平有效。通信以数据帧为单位进行时钟极性(CPOL)和时钟相位(CPHA)共同定义了数据采样和驱动的边沿。EM773的SPI0模块通过SSP框架增强了对帧格式、数据长度4-16位和FIFO缓冲的支持为兼容更多串行协议打下了基础。注意在配置任何通信外设前务必先通过系统时钟控制寄存器如SYSAHBCLKCTRL打开对应模块的时钟源并通过I/O配置寄存器IOCONFIG将相关引脚功能复用到SPI模式。这是很多初学者容易遗漏的第一步导致读写寄存器无反应或引脚无输出。2.2 Microwire协议帧格式与EM773的实现机制Microwire协议可以看作是SPI的一个子集或变体其最大特点是半双工和基于命令-响应的通信模型。这对于许多需要先发送一个控制字节如寄存器地址、读/写命令再接收或发送数据的从设备如某些EEPROM、温度传感器来说非常高效。根据手册描述EM773的SPI0模块对Microwire帧格式的支持体现在以下几个关键时序和行为上2.2.1 单次传输帧格式一次完整的Microwire单帧传输包含两个阶段命令阶段 (8-bit Control Word): 主设备EM773通过SO线向从设备发送一个8位的控制字。在此期间SI线被置为高阻态三态主设备不接收任何数据。这意味着在硬件上你需要将MISO引脚配置为输入模式并且软件上在这个阶段忽略接收到的数据通常为无效值。响应阶段 (4 to 16 bits Data): 在8位控制字发送完毕并经过一个SCK时钟的等待状态后从设备开始将响应数据通过SI线发回。主设备在SCK的上升沿锁存数据。响应数据的长度可以是4到16位因此整个帧长度在13到25位之间。关键时序细节空闲状态: CS为高电平SCK强制为低电平SO强制为低电平。这个特性很重要它定义了通信线的初始状态。传输启动: 当主设备将控制字节写入发送FIFO后CS线的下降沿会触发传输。发送移位寄存器加载FIFO底部的数据并从最高位(MSB)开始移出。传输结束: 对于单次传输在最后一个数据位被接收移位寄存器锁存后再过一个SCK周期CS线会被拉高。这个上升沿会导致接收到的数据被传输到接收FIFO中并且从设备可以释放SI线。2.2.2 连续背靠背传输连续传输模式用于需要快速读写多个数据字的场景。其与单次传输的核心区别在于CS线的状态在连续传输中CS线在整个多帧传输期间始终保持低电平有效状态。下一帧的8位控制字紧接在当前帧响应数据的最后一位(LSB)之后立即开始发送中间没有CS线的高电平间隔。每一帧接收到的数据都会在其LSB被锁存后的SCK下降沿从接收移位寄存器传输到接收FIFO。这种模式极大地提高了连续读写的效率但要求主从设备双方都能正确处理这种无间隔的帧衔接。2.3 关键配置与实操要点要让EM773的SPI0工作在Microwire模式并稳定通信你需要关注以下几个超越基础配置的要点1. 时钟与数据相位配置: 根据手册中的时序图在Microwire模式下命令发送阶段: 从设备在SCK上升沿锁存控制位。这对应SPI模式(CPOL, CPHA) (0, 0)或(1, 1)具体取决于SCK空闲电平。由于手册指出空闲时SCK为低因此通常采用模式(0,0)即SCK空闲为低数据在上升沿采样。数据接收阶段: 从设备在SCK下降沿驱动数据主设备在SCK上升沿采样。这依然符合模式(0,0)的规则。2. 片选(CS)信号的建立与保持时间: 这是Microwire模式下极易出错的点。手册特别强调了当SCK自由运行时即主设备持续提供时钟CS信号相对于SCK上升沿的时序要求。建立时间(t_SETUP): 在采样第一个接收数据位的SCK上升沿之前CS信号必须已经保持低电平至少2个SCK周期。保持时间(t_HOLD): 在采样第一个接收数据位的SCK上升沿的前一个SCK上升沿之后CS信号必须保持低电平至少1个SCK周期。这意味着什么你不能简单地在使能SPI模块的同时拉低CS。一个稳妥的软件操作顺序是配置SPI模块为主机模式设置好时钟频率、数据位宽设为8位对应控制字阶段和帧格式。将CS引脚配置为GPIO并手动控制。在启动传输前先手动拉低CS引脚。等待足够的时间通常通过几个NOP指令或短延时函数以满足2个SCK周期的建立时间要求。这个延时时间需要根据你的SCK频率计算。例如若SCK1MHz周期为1μs则至少需要延时2μs。将控制字写入SPI数据寄存器启动传输。在传输结束后可通过查询状态寄存器或中断再手动拉高CS引脚。3. 数据长度与FIFO操作:Microwire帧的总长度是可变的。在EM773中你需要通过寄存器设置数据帧长度。对于“8位控制字N位数据”的帧你需要将数据长度设置为总位数8N。模块会连续发送/接收这么多位。充分利用发送和接收FIFO。在连续传输模式下你可以预先向发送FIFO写入多个控制字模块会自动处理帧与帧之间的衔接。同样需要及时从接收FIFO中读取数据避免溢出。实操心得调试Microwire设备时逻辑分析仪是你的最佳伙伴。务必同时捕获CS、SCK、SO、SI四根线对照手册的时序图逐一检查CS的建立/保持时间、控制字和数据位的对应关系、等待状态是否满足。很多通信失败都是因为CS时序或数据长度设置错误导致的。3. EM773 16/32位定时器模块应用实战定时器是嵌入式系统的“心跳”。EM773提供了16位CT16B0和32位CT32B0/1两种定时器其架构和功能高度相似主要区别在于计数器的宽度决定了最大定时周期。我们以CT16B0为例进行深入剖析其原理完全适用于32位定时器。3.1 定时器核心架构与工作模式从框图看EM773的定时器是一个结构清晰、功能强大的模块。其核心是一个Timer Counter它会在Prescale Counter达到预设值时递增。这种预分频器结构允许你在定时器分辨率和溢出周期之间取得平衡。3.1.1 定时器模式 vs. 计数器模式这是两个基本工作模式由CTCR寄存器控制定时器模式: TC在每个PCLK外设时钟的上升沿递增实际上是在预分频器溢出后。这是最常用的模式用于产生精确的时间间隔。计数器模式: TC在外部捕获引脚CT16B0_CAP0的边沿上升、下降或双边沿上递增。这可以用来测量外部脉冲的频率或数量。重要提示当选择计数器模式时外部输入信号的频率不能超过PCLK频率的一半。因为模块需要两个连续的PCLK上升沿来检测一次CAP输入的电平变化。例如如果PCLK50MHz则外部计数信号频率需低于25MHz。3.1.2 预分频器(PR)的精确计算预分频器是控制定时精度的关键。TC递增的实际频率是F_tc F_pclk / (PR 1)。当PR 0时TC每个PCLK周期加1分辨率最高但溢出最快。当PR 6553516位最大值时TC每65536个PCLK周期才加1定时周期最长但分辨率降低。举例假设系统PCLK为50MHz我们需要一个10ms的定时中断。先确定TC的计数频率。10ms 0.01s如果让TC直接计数到某个值MR后产生中断则MR / F_tc 0.01。为了获得合适的MR值不宜过小以减小误差我们假设设置PR 4999。则F_tc 50MHz / (49991) 10kHz。即TC每0.1ms加1。那么MR 0.01s / 0.0001s 100。将匹配寄存器MR0设置为100并配置为匹配时复位TC并产生中断即可得到精确的10ms定时。3.2 匹配(Match)功能定时器的灵魂匹配功能是定时器最强大的特性之一。四个匹配寄存器MR0-MR3可以独立编程当TC的值与某个MRn的值相等时可以触发三种动作通过MCR寄存器配置产生中断通知CPU处理定时事件。复位TC让定时器重新从0开始计数用于产生周期性信号。停止TC和PC暂停定时器用于单次定时。3.2.1 生成PWM波形PWM是匹配功能的典型应用。EM773支持单边沿控制的PWM输出。配置步骤如下选择PWM周期选择一个匹配寄存器通常用没有引脚输出的MR3来设定PWM周期。将其值设置为PWM周期对应的计数值并在MCR寄存器中使能“匹配时复位TC”MR3R1。这样每当TC计数到MR3的值时就会清零重新开始形成一个周期。配置PWM占空比使用其他匹配寄存器MR0,MR1,MR2它们对应外部输出引脚MAT0-MAT2来设置占空比。在PWMCON寄存器中使能对应通道的PWM模式。理解PWM规则每个PWM周期开始时所有PWM输出为低电平除非其匹配值设为0。当TC计数到某个通道的匹配值时该通道的PWM输出变为高电平。当TC被MR3复位时周期结束所有高电平的PWM输出被拉低。因此PWM占空比 MRn/MR3。例如MR31000,MR0300则MAT0输出占空比为30%的PWM波。避坑指南务必注意用于设置占空比的匹配寄存器MR0-MR2其MCR中的“复位”和“停止”位必须设为0。只有用于设定周期的那个匹配寄存器如MR3才需要使能“复位”功能。如果设置错误PWM将无法正常产生周期信号。3.3 捕获(Capture)功能测量外部信号捕获功能用于“抓拍”外部事件发生时的精确时刻。当配置的捕获引脚CT16B0_CAP0上发生指定的边沿上升、下降或双边沿时当前TC的值会被瞬间锁存到对应的捕获寄存器CR0中并可选择产生中断。典型应用——测量脉冲宽度配置捕获控制寄存器CCR使能在CAP0引脚上捕获上升沿和下降沿并都产生中断。在上升沿中断服务程序中读取CR0的值记为t1。在下降沿中断服务程序中再次读取CR0的值记为t2。脉冲高电平宽度 (t2 - t1) * (PR1) / F_pclk。注意事项捕获功能依赖于TC的计数。如果TC在两次捕获之间发生了溢出从最大值翻转到0那么简单的t2-t1计算就会出错。在编写代码时需要引入一个溢出计数器来扩展定时器的有效位数或者在每次捕获后结合TC的溢出中断来修正计算。3.4 外部匹配输出当不用于PWM时匹配功能可以直接控制引脚输出实现精确的数字波形生成。通过EMR寄存器可以配置当匹配发生时对应的MAT引脚执行以下四种操作之一置低、置高、翻转、无操作。应用场景生成一个精确的方波。假设我们需要一个频率为F_pclk/(2*(MR1))的方波。将MR0设置为目标计数值。在MCR中配置为“匹配时复位TC”MR0R1。在EMR中配置MAT0为“匹配时翻转”EMC011。这样每当TC计数到MR0时TC复位同时MAT0引脚电平翻转从而产生一个50%占空比的方波。4. 系统集成与编程实践理解了各个模块后如何将它们集成到一个实际项目中并写出稳定可靠的驱动代码是更大的挑战。4.1 驱动层设计要点1. 初始化序列 任何外设的初始化都必须遵循严格的顺序对于EM773的SPI和定时器一个稳健的序列如下// 以SPI0和CT16B0为例 void Peripherals_Init(void) { // 1. 使能外设时钟 (SYSAHBCLKCTRL) SYS-AHBCLKCTRL | (1 7); // 使能CT16B0时钟 SYS-AHBCLKCTRL | (1 11); // 使能SPI0时钟 (假设位11需查手册确认) // 2. 配置I/O引脚功能 (IOCONFIG) // 将PIO0_x配置为SPI0_SCK, SPI0_MOSI, SPI0_MISO, SPI0_SSEL // 将PIO0_y配置为CT16B0_MAT0, CT16B0_CAP0等 IOCON-PIO0_x ...; // 具体配置值参考数据手册引脚复用表 // 3. 外设模块具体配置 SPI0_Init(); TIMER16B0_Init(); } void SPI0_Init(void) { // 配置SPI0为Microwire主机模式时钟极性相位数据长度等 SPI0-CR0 ...; // 控制寄存器0设置数据长度、帧格式等 SPI0-CR1 ...; // 控制寄存器1设置主从模式、使能等 SPI0-CPSR ...; // 时钟预分频设置SCK频率 // 注意SSEL引脚可能需要配置为GPIO并手动控制 } void TIMER16B0_Init(void) { // 停止定时器 TIMER16B0-TCR 0; // 设置预分频器PR TIMER16B0-PR 4999; // 例如产生10kHz的TC计数频率 // 设置匹配寄存器MR3为PWM周期 TIMER16B0-MR3 1000; // PWM周期 1000 * (1/10kHz) 100ms // 设置匹配寄存器MR0为PWM占空比 TIMER16B0-MR0 300; // 占空比30% // 配置匹配控制寄存器MCR TIMER16B0-MCR (1 10); // MR3匹配时复位TC (MR3R1) // 注意MR0的MCR位保持为0不中断不复位不停止 // 配置PWM控制寄存器PWMCON TIMER16B0-PWMCON (1 0); // 使能MAT0为PWM输出 // 配置外部匹配寄存器EMR对于PWM模式此寄存器影响较小但可初始化 TIMER16B0-EMR 0; // 启动定时器 TIMER16B0-TCR 1; }2. 中断服务程序优化 定时器的匹配、捕获中断和SPI的发送/接收完成中断是实时系统的关键。编写ISR时务必遵循快进快出ISR中只做最必要的操作如设置标志、拷贝数据繁重的处理放到主循环中。清除中断标志在退出ISR前必须向中断寄存器如TMR16B0IR的对应位写1来清除中断标志否则会连续触发中断。防止重入对于可能被频繁触发的中断考虑使用临界区保护或确保ISR执行时间远小于中断间隔。4.2 调试技巧与常见问题排查问题1SPI通信无反应或数据错误。检查时钟和电源确认SYSAHBCLKCTRL寄存器中对应位已置1。检查引脚复用用逻辑分析仪或示波器检查SCK、MOSI引脚是否有输出。如果没有首先检查IOCONFIG寄存器配置是否正确。检查CS时序对于Microwire用逻辑分析仪测量CS下降沿到第一个SCK上升沿的时间是否满足至少2个SCK周期的建立时间要求。检查数据位序确认主从设备的数据传输位序MSB first / LSB first是否一致。EM773的SPI模块通常可配置。检查FIFO在连续传输时确保在发送FIFO满之前写入数据在接收FIFO非空时读取数据避免溢出或欠载。问题2PWM输出频率或占空比不对。计算预分频器PR和匹配寄存器MR重新核对F_pclk、PR、MR值的计算。一个常见的错误是忽略了PR的1因子。确认PWM周期寄存器检查用于设置周期的匹配寄存器如MR3是否在MCR中配置了“复位TC”MRnR1。检查引脚输出使能确认MAT引脚是否已在IOCONFIG中正确配置为定时器匹配输出功能而非普通的GPIO。使用示波器测量直接观察波形是最直观的。测量周期和脉冲宽度反推实际的计数值。问题3捕获功能测量的脉冲宽度值跳动很大。消抖处理如果捕获的是机械开关等信号需要在硬件RC滤波或软件多次采样判断上做消抖处理。处理计数器溢出如果脉冲宽度可能超过TC的计数周期对于16位定时器当PR0时在50MHz下约1.3ms必须在ISR中维护一个溢出计数器。计算宽度时实际计数值 溢出次数 * (65536) (t2 - t1)。中断优先级与延迟如果系统中断繁忙捕获中断可能被延迟响应造成误差。对于高精度测量需要提高捕获中断的优先级并确保ISR执行时间极短。问题4功耗异常。关闭未使用的外设时钟在低功耗应用中初始化完成后检查SYSAHBCLKCTRL寄存器确保只使能了真正需要的外设时钟。不用的SPI、定时器模块一定要关闭其时钟门控。配置未使用引脚将未使用的GPIO引脚设置为确定的输出状态高或低或配置为带上拉的输入模式避免浮空输入导致漏电流。5. 进阶应用与性能考量当你掌握了基础功能后可以尝试一些更复杂的应用这对系统的稳定性和性能提出了更高要求。5.1 使用DMA配合SPI对于需要高速、大批量传输SPI数据的场景如驱动TFT屏、读写大容量SPI Flash频繁的CPU中断会成为瓶颈。EM773可能支持DMA直接存储器访问可以让DMA控制器自动将内存中的数据搬运到SPI发送FIFO或从接收FIFO搬运到内存无需CPU干预。这能极大解放CPU并提高传输效率。你需要配置DMA的源地址、目标地址、传输数据量并链接到SPI的外设请求。5.2 定时器串联与同步单个16位定时器的最大定时长度有限。如果需要更长的定时可以采用“预分频器软件扩展”或“定时器串联”的方法。软件扩展使能定时器的溢出中断在中断服务程序中对一个全局变量如timer_overflow_cnt加1。这样有效定时长度就从16位扩展到了16 N位N取决于全局变量的位数。硬件串联将一个定时器的匹配输出MAT连接到另一个定时器的捕获输入CAP。例如用CT16B0产生一个固定的低频时钟如1kHz方波输出到MAT0引脚并将该引脚连接到CT16B1或CT32B0的CAP0引脚。将CT16B1配置为在CAP0的上升沿计数。这样CT16B1就变成了一个对CT16B0时钟进行计数的“二级定时器”可以实现非常长的定时周期。5.3 实时性保障在复杂的嵌入式系统中多个外设中断可能同时发生。确保关键任务如电机控制的PWM更新、通信协议的应答的实时性至关重要。合理分配中断优先级给SPI接收完成、定时器捕获等对时间敏感的中断分配更高的优先级。避免在中断中长时间阻塞绝对禁止在ISR中使用delay()之类的忙等待函数。使用RTOS对于多任务系统考虑使用实时操作系统来管理任务调度和资源同步比裸机前后台系统更能保证实时性。最后我想分享一个在调试EM773定时器PWM时遇到的具体问题。当时我需要生成一个非常低频的PWM约0.5Hz占空比需要精细调节。我直接使用了32位定时器CT32B0将PR设置到最大MR也设置了一个很大的值。但发现PWM输出极不稳定偶尔会丢失脉冲。排查后发现是因为用于设定周期的MR3值过于接近32位计数器的最大值而用于设定占空比的MR0值也很大在计算MR0和MR3的比较时由于代码中的一些非原子操作和中断干扰导致了比较逻辑的瞬时错误。解决方案是对于极低频PWM不要追求单次定时器周期达到目标时长而是利用一个周期较短如10ms的定时器中断在中断中通过软件计数和操作GPIO来模拟低频PWM。硬件PWM更适合中高频场合。这个经历告诉我数据手册给出的功能边界有时需要结合具体应用场景和芯片实际性能来谨慎评估。