MC68341串口与定时器驱动开发:寄存器配置、中断处理与调试实战
1. 项目概述与核心价值
在嵌入式系统开发,尤其是基于MC68341这类经典微控制器的项目中,串行通信(UART)和定时器模块是工程师必须啃下的两块硬骨头。它们一个是系统与外界对话的“嘴巴”和“耳朵”,另一个则是系统精准计时的“心跳”。手册上的寄存器描述往往冰冷而抽象,真正要把它们用起来,写成稳定、高效的驱动代码,中间隔着无数个需要踩的坑。我最近在为一个老旧的工业控制设备做维护升级,核心就是这颗MC68341,期间把它的串行和定时器模块翻来覆去研究了好几遍。这篇文章,我就结合手册里的原理图和代码片段,把我对这两个模块从寄存器位理解到驱动实现的全过程拆解清楚,重点分享那些手册上不会写,但实际调试中能救命的细节和心得。
无论你是正在学习MC68341的新手,还是需要为类似架构的微控制器(比如MC683xx系列或其他老牌MCU)编写底层驱动,这篇文章都能提供直接的参考。我们会从最根本的“为什么这个寄存器要这么配”开始,一直讲到可以“抄作业”的初始化代码和中断处理框架,目标是让你看完后,不仅能配置出一个能用的串口或定时器,更能理解其内部状态机是如何运转的,遇到问题时知道该往哪里看。
2. MC68341串行模块深度解析与编程实战
串行模块,本质上是一个高度集成的UART(通用异步收发传输器)。它的核心任务是把CPU内部的并行数据,转换成一位一位的串行数据流发送出去,同时把接收到的串行数据流,组装成完整的并行数据交给CPU。MC68341的串行模块提供了两个独立的通道(Channel A和B),结构上非常经典。
2.1 核心寄存器与状态机:理解数据流动的“阀门”
手册里提到了几个关键寄存器,它们是驱动程序的“控制面板”。我们得先弄明白它们是怎么联动的。
2.1.1 状态寄存器(SR)与FIFO机制
手册片段重点描述了FFULL和RxRDY这两个状态位,它们直接关联到接收数据流。这里隐藏着一个关键设计:三级FIFO(先进先出)缓冲区。
- RxRDY (Receiver Ready): 这是给CPU看的“有货”信号。只要接收FIFO里有一个或以上的字符,该位就置1。CPU通过读取接收缓冲区(RB)来取走数据。关键点在于:
RxRDY是在字符从接收移位寄存器转移到FIFO时被置位的。这意味着,即使FIFO还没满,只要来了新数据,CPU就能立刻知道。 - FFULL (FIFO Full): 这是FIFO的“满仓”警报。当FIFO的三个位置全部被占用时,该位置1。此时,如果接收移位寄存器又收完了一个字符,这个字符会卡在移位寄存器里,直到CPU读走FIFO中的一个字符,腾出空位,这个被卡住的字符才会“滑入”FIFO,并且
FFULL位会继续保持为1。这一点非常重要,它意味着FFULL=1是一个“持续满”的状态指示,而不是一个瞬间脉冲。
编程心得:在编写查询式(Polling)接收函数时,不能只查RxRDY。在高速或突发数据场景下,必须结合FFULL来判断。如果FFULL为1,说明FIFO已满且可能有数据在移位寄存器中等待,此时即使RxRDY为1(因为FIFO里有数据),系统也处于“濒临溢出”的状态,需要CPU加速读取。更稳健的中断驱动方式,通常会使能“FIFO满”中断和“接收就绪”中断,以确保数据流不中断。
2.1.2 发送器缓冲区(TB)与“双缓冲”
发送部分的结构是“发送保持寄存器” + “发送移位寄存器”的双缓冲结构。
- CPU检查状态寄存器中的
TxRDY位。如果为1,表示“发送保持寄存器”为空,可以写入新数据。 - CPU向TB写入数据,这个数据实际进入了“发送保持寄存器”。写入操作会清零
TxRDY位,防止CPU写入下一个数据造成覆盖。 - 当“发送移位寄存器”完成前一个字符的发送并变空时,它会自动从“发送保持寄存器”中加载这个新字符。一旦加载完成,
TxRDY位会重新置1,通知CPU可以发送下一个字符了。
注意事项:手册特别强调,当TxRDY为0(保持寄存器有数据未加载)或发送器被禁用时,向TB写入是无效的。同时,所有串行模块寄存器都必须以字节(.B)方式访问,这是硬件设计决定的,用字(.W)或长字(.L)访问会导致未定义行为。
2.2 串行模块初始化序列:从零搭建通信链路
手册7.5.1节给出了一个标准的初始化流程。我们把它翻译成更贴近编程的步骤,并解释每一步的意图。
2.2.1 模块级全局配置这部分配置影响整个串行模块(两个通道)。
- 模块配置寄存器(MCR):
STP位:必须清零,使能串行模块。这是总开关。FRZx位:决定在调试器发出FREEZE信号时,串行模块是否停止。在正常运行时通常忽略(设为1)。ICCS位:选择输入捕获时钟源,根据具体硬件连接决定。SUPV位:设置寄存器访问权限。通常设为0,允许用户模式访问,方便应用程序直接操作。IARB位:设置中断仲裁级别。在多模块系统中,用于决定中断优先级,需要根据系统中断设计来配置。
- 中断相关寄存器(IVR, ILR, IER):
IVR:写入一个向量号,当中断发生时,CPU会跳转到这个向量号对应的中断服务程序。ILR:设置该模块中断的优先级(0-7)。0为禁止,7为最高。IER:这是一个关键寄存器,用于使能具体的中断源。例如,使能“接收数据就绪”中断、“发送缓冲区空”中断、“接收错误”中断等。初始化时通常先全部关闭,在驱动准备好后再按需开启。
- 辅助控制寄存器(ACR)与输出端口控制寄存器(OPCR):
ACR的BRG位:选择波特率发生器组。MC68341通常有两组预分频器,用于产生不同的波特率。OPCR:配置与串口复用的输出引脚功能。例如,可以将某个引脚配置为RTS(请求发送)信号输出。
2.2.2 通道特定配置这部分对每个通道(A和B)需要单独设置。
- 时钟选择寄存器(CSR):为接收器和发送器选择时钟源。通常选择同一个波特率发生器。
- 模式寄存器1(MR1):
RxRTS:是否启用接收器自动控制RTS。启用后,当接收FIFO快满时,硬件自动拉高RTS通知对方暂停发送。R/F:选择中断条件。是“接收就绪”(FIFO中有任何数据)就中断,还是“FIFO满”才中断?根据数据流量和CPU处理能力选择。ERR:选择错误模式。是每个字符都检查错误,还是按块检查?PM/PT:奇偶校验模式和类型(奇校验、偶校验、无校验)。B/Cx:每个字符的数据位长度(5-8位)。
- 模式寄存器2(MR2):
CMx:通道操作模式。通常选择“正常”模式(异步UART)。TxRTS:发送器是否受CTS(清除发送)信号控制。用于硬件流控。TxCTS:同上,控制CTS是否影响发送。SBx:停止位长度(1, 1.5, 2位)。
- 命令寄存器(CR):最后一步,发送命令。
- 先发送“复位接收器”(
0x20)和“复位发送器”(0x30)命令,将通道置于已知状态。 - 然后发送“使能接收器”和“使能发送器”命令(例如
0x05和0x40的组合,手册示例代码为0x45),启��收发功能。
- 先发送“复位接收器”(
2.3 实战代码剖析与避坑指南
手册7.5.2节提供了一个初始化Channel A的汇编代码示例。我们逐段分析,并指出关键点。
; 定义基址和偏移量 MBAR EQU $0003FF00 ; SIM模块基址寄存器地址 MODBASE EQU $FFFFF000 ; 我们配置的MBAR值(映射到SIM基址) SERIAL EQU $700 ; 串行模块在SIM内的偏移 MCRH EQU $0 ; MCR高字节偏移 MCRL EQU $1 ; MCR低字节偏移 MR1A EQU $10 ; 通道A模式寄存器1 CSRA EQU $11 ; 通道A时钟选择寄存器(与SRA同偏移,写是CSR,读是SR) CRA EQU $12 ; 通道A命令寄存器 ACR EQU $14 ; 辅助控制寄存器 OPCR EQU $1D ; 输出端口控制寄存器 OP_BS EQU $1E ; 输出端口位置位寄存器(写1置位) OP_BR EQU $1F ; 输出端口位清零寄存器(写1清零) LEA MODBASE+SERIAL,A0 ; A0指向串行模块基址第一坑:地址映射。MC68341的片内外设都映射到CPU的地址空间,地址由SIM(系统集成模块)的MBAR决定。MODBASE的值必须与硬件设计或启动代码中配置的MBAR值严格一致。这里$FFFFF000是一个示例,你的实际地址可能不同。
; 配置MCR:使能模块,忽略FREEZE,选择晶振时钟,用户模式可访问,中断仲裁级别2 MOVE.B #$00,MCRH(A0) MOVE.B #$02,MCRL(A0)第二坑:字节操作。注意是MOVE.B,不是MOVE.W。
; 等待发送器空(或超时) MOVE.W #$2000,D0 ; 初始化循环计数器 XBMTWAIT: BTST #3,SRA(A0) ; 测试状态寄存器A的TX空位(假设是Bit 3) NOP DBNE D0,XBMTWAIT ; 循环直到位置位或超时关键操作:上电/复位后的等待。在配置串口前,等待发送器空闲是一个好习惯。手册代码通过查询状态位实现,并加入了超时机制(DBNE循环)。如果硬件故障导致发送器永远不空,这个循环能防止程序死锁。超时后应进行错误处理(代码中未体现,实际项目必须加)。
; 取消RTSA信号输出(假设RTSA复用在OP0) MOVE.B #0,OPCR(A0) ; 配置OP0-OP7为通用输出 MOVE.B #$01,OP_BR(A0) ; 写1到OP_BR的bit0,将OP0/RTSA输出清零第三坑:GPIO与功能复用。RTS这样的硬件流控信号,往往与通用I/O(OP0)复用。使用前,需通过OPCR将其配置为外设功能(此处代码先设为通用输出并拉低,是一种保守做法。更常见的做法是直接配置OPCR相应位为串口功能)。
; 复位接收器和发送器 MOVE.B #$20,CRA(A0) ; 复位接收器命令 MOVE.B #$30,CRA(A0) ; 复位发送器命令标准操作:在详细配置前,先将子模块复位到已知状态。
; 设置波特率发生器组2 MOVE.B #$80,ACR(A0) ; 设置ACR,选择BRG Set 2 ; 模式寄存器1:8位数据,无校验,自动RTS控制 MOVE.B #$93,MR1A(A0) ; 0x93 = 1001 0011 (具体位域需查手册) ; 模式寄存器2:正常模式,1位停止位 MOVE.B #$07,MR2A(A0) ; 0x07 ; 在时钟选择寄存器中设置波特率(9600) MOVE.B #$BB,CSRA(A0) ; 写入CSR选择时钟分频,对应9600波特率第四坑:波特率计算与寄存器值。#$BB这个值是如何得来的?它依赖于系统主频和波特率发生器(BRG)的设计公式。手册中会有表格或公式。例如,若系统时钟为3.6864MHz,选择BRG Set 2,要得到9600波特率,分频系数N = 时钟频率 / (波特率 * 16) - 1。计算后得到N的整数部分,再查表或按规则写入CSR。绝对不能直接抄这个值,必须根据你的实际晶振频率计算。
; 设置RTSA有效 MOVE.B #$01,OP_BS(A0) ; 写1到OP_BS的bit0,将OP0/RTSA输出置位(拉高) ; 使能端口(启动收发) MOVE.B #$45,CRA(A0) ; 复位错误状态,并使能接收器和发送器最后一步:拉高RTS表示本机准备就绪,然后发送使能命令(0x45= 0100 0101,即“错误复位”+“使能接收”+“使能发送”),串口开始工作。
2.4 串行模块驱动框架与中断处理
手册图7-10的流程图给出了一个完整的软件框架,包括初始化(SINIT)、I/O驱动(INCH, OUTCH, POUTCH)和中断处理(SIRQ)。这个框架非常经典,值得用C语言或更清晰的伪代码重构成现代嵌入式项目可用的形式。
2.4.1 初始化框架(SINIT/CHCHK)其核心思想是“回环测试”自检。
- 将串口配置为本地环回模式(发送端直接连接到接收端)。
- 发送一个已知的测试字符(如
0x55或0xAA,其位模式01010101有助于检查位错误)。 - 等待接收,并检查:
- 发送器永不就绪:是否超时都无法发送?
- 接收器永不就绪:是否超时都收不到?
- 奇偶校验错误/帧错误:接收数据是否有格式错误?
- 字符错误:收到的字符与发送的是否一致?
- 根据自检结果,决定是否启用该通道。这是一个提高系统可靠性的重要实践。
2.4.2 I/O驱动示例
INCH: 一个阻塞式接收函数。它循环查询RxRDY位,直到有数据可用,然后从接收缓冲区(RB)读取并返回。在实际系统中,为了避免CPU空转,通常会放在中断服务程序(ISR)中,或将查询与超时结合。OUTCH/POUTCH:阻塞式发送函数。循环查询TxRDY位,直到发送保持寄存器空闲,然后写入数据。注意代码中处理了回车符(\r, 0x0D)自动补全换行符(\n, 0x0A)的逻辑,这是为了兼容老式终端。
2.4.3 中断处理(SIRQ)示例中处理的是一个特殊中断:Break信号变化中断。Break是串口线上一个长时间的低电平状态,用于表示通信起始或协议帧边界。
- 中断发生(Break开始)。
SIRQISR被调用,首先清除中断源(读取状态寄存器或写特定命令寄存器)。这是中断服务程序的第一要务,防止重复进入同一中断。- 等待下一个Break变化中断(Break结束)。
- 再次清除中断源。
- 从接收FIFO中移除Break字符(因为Break不是有效数据)。
- 返回。
编程心得:中断服务程序(ISR)要短、快、准。只做最必要的操作:读取数据、清除标志、可能的话将数据放入队列。复杂的处理(如协议解析)应放到主循环或任务中。对于数据接收,更通用的ISR是处理“接收数据就绪”中断,在ISR中快速将FIFO数据读入一个环形缓冲区(Ring Buffer)。
3. MC68341定时器模块原理与应用模式详解
定时器模块是一个比串口更灵活但也更复杂的子系统。它不仅仅是一个简单的倒计时器,而是一个可以通过多种模式编程,实现输入捕获、输出比较、PWM(脉冲宽度调制)、频率测量等功能的强大外设。
3.1 模块架构与核心部件
如图8-2所示,定时器的核心是一个16位递减计数器,其前面可以级联一个8位预分频器,从而构成一个24位的计数器。时钟源可以选择外部引脚TIN或内部系统时钟(CLKOUT/2)。
- 预分频器与计数器(Prescaler & Counter):预分频器对输入时钟进行1-256分频。计数器从预加载值(PREL1或PREL2)开始递减,减到0时产生“超时”(Time-out)事件。这是一个关键概念:超时是计数器归零的时刻,是许多模式下的基准事件。
- 比较器(Comparator):持续将16位计数器的当前值与比较寄存器(COM)的值进行比较。当两者相等时,产生“比较匹配”(Compare Match)事件。这个事件可以用于在精确时刻触发输出引脚
TOUT的变化。 - 输出控制(Output Control):根据操作模式和比较匹配/超时事件,控制
TOUT引脚的电平变化(置高、置低、翻转)。 - 门控信号(TGATE):这是一个多功能输入引脚。在大多数模式下,它可以作为计数器的使能/禁用开关(低电平有效)。在输入捕获模式下,它有特殊用途。
3.2 六大操作模式实战解析
手册描述了6种模式,我们挑最常用的几种,结合时序图深入理解。
3.2.1 输入捕获/输出比较模式(Mode 000)
这是最通用的模式,一分为二看:
- 输出比较:你设定一个比较值(COM)。计数器自由递减,当计数值等于COM时,发生比较匹配,可以触发
TOUT引脚动作(如翻转),并产生中断。你可以用来生成精确的定时中断或方波。如图8-4,若PREL1=8,COM=7,则计数器从8开始减,减到7时发生一次比较匹配(TC置位),减到0时发生超时(TO置位),然后重载8。如果设置TOUT在比较匹配时翻转,就能产生一个周期为9个时钟、占空比可变的波形。 - 输入捕获:利用
TGATE引脚。当TGATE使能(TGE=1)且其上升沿到来时,会设置状态位TG,并冻结计数器寄存器(CNTR)的更新(即停止“影子”更新)。此时,CPU读取的CNTR值,就是TGATE上升沿瞬间计数器的精确值。这常用于测量外部脉冲的宽度或周期。CPU在读取后需要手动清除TG位来恢复影子更新。
注意事项:在此模式下,TGATE并不停止计数器本身,只停止CNTR对计数器的跟随。计数器仍在运行,这允许进行“捕捉-比较”的复杂操作。
3.2.2 方波发生器模式(Mode 001)
这是最简单的定时中断或方波生成模式。如图8-5。
- 设定PREL1 = N。
- 使能定时器。
- 计数器从N开始递减,到0超时,
TO置位。 - 计数器自动重载N,开始下一轮。
- 如果设置
TOUT在每次超时翻转,则输出一个周期为(N+1)*时钟周期的方波(因为从N减到0需要N+1个时钟沿)。
应用场景:产生固定的时基,用于系统心跳时钟(SysTick)、软件定时器、LED闪烁等。
3.2.3 可变占空比方波发生器模式(Mode 010)
这是PWM生成的雏形。如图8-6。
- 设定PREL1 = N1, PREL2 = N2。
- 使能定时器。
- 第一周期:计数器从N1递减到0超时,
TO置位,TOUT翻转(假设初始为低,翻高)。 - 计数器重载N2。
- 第二周期:计数器从N2递减到0超时,
TO再次置位,TOUT再次翻转(高翻低)。 - 计数器重载N1,如此循环。
- 输出波形:高电平持续
(N1+1)个时钟周期,低电平持续(N2+1)个时钟周期。占空比 =(N1+1) / (N1+N2+2)。
关键点:通过动态修改PREL1和PREL2,可以在运行时改变波形的频率和占空比。但手册警告:在计数器重载的瞬间(超时发生时)写入新的PREL值,可能会导致旧值被加载,造成脉冲宽度错误。安全的做法是在中断服务程序中,在计数器刚重载后(即新一轮计数开始时)更新下一个周期的值。
3.2.4 其他模式简述
- 可变宽度单脉冲(Mode 011):由
TGATE上升沿触发,输出一个宽度由PREL1设定的单脉冲。 - 脉冲宽度测量(Mode 100):利用
TGATE作为门控,测量其高电平或低电平期间,通过了多少个时钟周期。TGATE有效时计数器计数,无效时停止,读取CNTR即得脉冲宽度。 - 周期测量(Mode 101):测量
TGATE两个边沿之间的时钟数。 - 事件计数(Mode 110):
TIN作为外部事件时钟,TGATE作为使能,计数器对TIN的边沿进行计数。
3.3 定时器编程核心要点与寄存器配置
定时器的配置比串口更复杂,因为模式多样。一个稳健的配置流程如下:
- 停止定时器:在修改任何关键配置(尤其是CR中的模式位)前,确保
SWR(软件复位)位为0,停止定时器。 - 配置时钟源与预分频:通过CR的
CLK位选择TIN或CLKOUT/2。通过PS位选择是否使用及如何配置预分频器。预分频器可以极大地扩展定时范围。例如,16位计数器最大计数值65535,若系统时钟4MHz,则最长定时约16ms。加上8位256分频后,最长定时可达约4秒。 - 设置预加载与比较寄存器:
PREL1:主重载值,在超时后加载。PREL2:在可变占空比等模式下,作为交替重载值。COM:比较匹配值。在输出比较模式下使用。- 注意:这些寄存器是双缓冲的或具有特定的写入时机,写入时需参考当前模式,避免与硬件自动加载冲突。
- 配置输出与控制:
OC位(输出控制):决定TOUT在比较匹配或超时时的行为(置0、置1、翻转)。TGE位:是否使能TGATE引脚的功能(门控或捕获)。
- 设置中断:在IER中使能所需的中断源(
TO超时中断、TC比较匹配中断、TG门控中断等)。 - 启动定时器:设置CR的
CPE(计数器预分频器使能)和SWR位为1。如果使能了TGATE门控,还需要TGATE引脚为有效电平(低电平)计数器才会开始运行。
一个简单的方波发生器配置示例(C语言风格伪代码):
// 假设定时器基址为 TIMER_BASE volatile uint8 *tim_cr = (uint8*)(TIMER_BASE + CR_OFFSET); volatile uint8 *tim_sr = (uint8*)(TIMER_BASE + SR_OFFSET); volatile uint16 *tim_prel1 = (uint16*)(TIMER_BASE + PREL1_OFFSET); volatile uint16 *tim_com = (uint16*)(TIMER_BASE + COM_OFFSET); // 1. 确保定时器停止 *tim_cr = 0x00; // 清除SWR等所有控制位 // 2. 配置模式001(方波),时钟源为CLKOUT/2,无预分频,输出翻转,使能TGATE控制 *tim_cr = (0 << 5) | (1 << 4) | (0 << 3) | (1 << 2); // 假设位定义,需查手册 // 即 MODE=001, CLK=0 (CLKOUT/2), PS=00 (无预分频), OC=01 (翻转), TGE=1 // 3. 设置周期。目标生成1KHz方波,系统时钟2MHz。 // 时钟周期 = 0.5us。方波半周期 = 1/(1KHz*2) = 500us。 // 所需计数次数 = 500us / 0.5us = 1000。 // 预加载值 N = 1000 - 1 = 999 (因为从N减到0需要N+1个周期)。 *tim_prel1 = 999; // 4. (可选)设置比较值,若需要非50%占空比或利用比较中断 // *tim_com = 300; // 例如,在计数值为300时发生比较匹配 // 5. 使能超时中断(如果需要) // *(TIMER_BASE + IER_OFFSET) |= 0x80; // 假设TO中断使能位是bit7 // 6. 启动定时器 *tim_cr |= 0xC0; // 设置CPE=1, SWR=1 (假设位位置) // 此时,如果TGATE引脚为低,计数器开始从999递减,减到0时超时,TOUT翻转,产生中断(如果使能),并重载999。4. 调试经验与常见问题排查
无论是串口还是定时器,调试底层驱动,逻辑分析仪和示波器是你的最佳伙伴。光看代码和寄存器值是不够的,必须看到实际引脚上的波形。
4.1 串口通信不通?按这个顺序查:
- 电气层:用示波器量TX、RX引脚。发送数据时,TX是否有波形?波特率是否正确(测量位时间)?电压电平是否符合(RS-232是负逻辑,TTL是3.3V/5V)?
- 配置层:确认双方波特率、数据位、停止位、校验位完全一致。一个常见的错误是MCU初始化代码中的时钟配置错误,导致计算的波特率寄存器值不对。
- 软件流控:如果使能了RTS/CTS,确认硬件连线正确,且对方设备能正确��应流控信号。
- FIFO与中断:如果使用中断,确认中断向量表配置正确,ISR能正确进入。检查IER是否使能了正确的中断源。在ISR中,务必读取状态寄存器(SR)或数据寄存器来清除中断标志,否则会连续触发中断。
- 查询模式阻塞:如果使用
INCH这样的查询函数,确保它有超时退出机制,否则对方没发数据时程序会永远卡住。
4.2 定时器输出不对或不准?
- 时钟源:确认
TIN引脚是否有输入时钟?或者是否选择了正确的内部时钟(CLKOUT/2)?用示波器测量TOUT引脚,看输出频率是否与计算值相符。如果不符,首先检查系统主频配置和预分频设置。 - 门控信号:如果使用了
TGATE,用示波器看其电平。在门控模式下,TGATE为高电平时,计数器是停止的。 - 重载时机:在可变占空比模式下,如果你在运行时动态修改PREL1/PREL2,输出可能会偶尔“抖一下”。这是因为写入时机与计数器重载时机竞争。解决方法:在超时中断(TO)的服务程序中,在中断一开始就写入下一个周期的新值,确保在计数器重载前完成写入。
- 计数器寄存器读取:在输入捕获模式下,读取CNTR获取捕获值。注意,在
TG位被设置(影子更新停止)时,你读到的才是捕获瞬间的精确值。读完后需要软件清除TG位。 - 中断响应延迟:定时器中断用于精确定时。如果中断服务程序执行时间过长,或者系统有其他更高优先级的中断频繁发生,会导致定时“漂移”。对于高精度定时,需要考虑中断延迟,或者使用定时器的“连续”模式(如方波发生器),让硬件自动维持周期,中断只用于通知而非重新装载。
最后一点体会:阅读这类经典MCU的手册,不能只看代码片段,一定要结合时序图和寄存器位定义一起看。时序图告诉你“信号如何流动”,寄存器位告诉你“如何控制它”。自己动手画一画数据流和状态转换图,比读十遍文字描述都管用。把这些模块调通,你对嵌入式硬件如何与软件协同工作的理解,会上一个大台阶。
