1. 项目概述与核心价值
在嵌入式系统开发中,串行通信是连接微控制器与外部世界的“血管”。无论是调试信息输出、传感器数据采集,还是多芯片间的协同工作,都离不开UART和I2C这两大基石。很多工程师在项目初期,面对芯片手册里繁杂的寄存器描述和时序图,常常感到无从下手,要么是通信不稳定,数据时有时无;要么是效率低下,CPU大量时间浪费在等待上。我当年调试第一块带串口通信的板子时,就曾因为对“双缓冲”和“中断时序”理解不透彻,导致数据丢失,排查了整整两天。
今天,我们就以经典的Philips(现NXP)P89LPC932A1这款8位微控制器为例,把它的UART和I2C接口掰开揉碎了讲清楚。这不仅仅是一篇寄存器手册的翻译,而是结合我十多年摸爬滚打的经验,告诉你这些功能 在实际项目中怎么用、为什么这么用、以及有哪些手册里没写的“坑” 。我们会重点剖析UART的 双缓冲机制 如何大幅提升发送效率, 多机通信 如何用硬件简化软件协议,以及I2C的 四种主从模式 如何灵活切换。无论你是正在评估这款老牌芯片,还是想深入理解串行通信的底层机制,这篇文章都能给你带来可以直接“抄作业”的实操指南和避坑心得。
2. UART接口深度解析与双缓冲机制
2.1 UART基础与P89LPC932A1的模式概览
UART,即通用异步收发器,其通信的本质是双方约定好相同的波特率,在没有统一时钟线的情况下,依靠起始位和停止位来框定一帧数据。P89LPC932A1的UART兼容标准的80C51 UART,但功能更强。它支持4种工作模式:
- 模式0 :同步移位寄存器模式,通常用于扩展I/O,速率固定为Fosc/12。
- 模式1 :8位数据、可变波特率的异步通信模式。这是我们最常用的模式,一帧数据包括1位起始位、8位数据位、1位停止位。
- 模式2和3 :9位数据、可变波特率的异步通信模式。比模式1多了一个可编程的第9位数据(TB8/RB8),这个多出来的位是实现 多机通信 和 硬件地址识别 的关键。
很多新手会困惑,模式2和模式3看起来都是9位数据,有什么区别?关键在于波特率的生成源。模式2的波特率固定为Fosc/32或Fosc/64(由SMOD位决定),而模式3的波特率是可变的,通常由定时器T1或T2的溢出率决定,这给了我们根据系统需求灵活调整通信速率的能力。在P89LPC932A1中,模式2和3的接收逻辑完全一致,区别仅在于波特率发生器。
注意 :在模式1下,SM2位(多机通信控制位)必须为0,否则会影响正常接收。这是一个常见的配置错误点。
2.2 革命性的双缓冲发送机制
这是P89LPC932A1 UART相对于标准51单片机一个巨大的优势,也是提升通信效率的关键。我们得先理解传统UART发送的“痛点”:当你向发送缓冲器SBUF写入一个字节后,硬件开始将这个字节移位发送。在 整个字节发送完成之前 ,SBUF是“忙”的,如果你此时写入第二个字节,会覆盖掉正在发送的数据,导致错误。因此,软件必须等待一个字节发送完毕(检测TI标志位)后才能写入下一个字节,这期间CPU只能干等或频繁查询,效率很低。
双缓冲(Double Buffering)机制就是为了解决这个问题。当双缓冲使能(SSTAT寄存器的DBMOD位=1)时,UART内部实际上有两个缓冲区:一个 移位寄存器 负责将数据一位一位地推到TxD引脚;另一个 双缓冲寄存器 (或者说第二个SBUF)用于缓存下一个要发送的字节。
它的工作流程可以这样理解:
- 初始状态,移位寄存器和双缓冲寄存器都为空。
- 你写入第一个字节到SBUF。这个字节 立即 被加载到移位寄存器,并开始发送。同时,TI中断标志可能会被立即置位(取决于INTLO和DBISEL位的配置),通知CPU“双缓冲寄存器已空,可以准备下一个数据了”。
- 在第一个字节的移位发送过程中(比如在发送第5位数据时),你就可以将第二个字节写入SBUF。这个字节会被存入 双缓冲寄存器 ,而不会干扰正在移位的第一个字节。
- 当第一个字节的停止位发送开始时(或结束时,由INTLO位决定),双缓冲寄存器中的第二个字节会自动、无缝地载入移位寄存器,紧接着第一个字节的停止位之后开始发送第二个字节的起始位。这样,两个字节之间就 只间隔了一个标准的停止位时间 ,实现了近乎连续的流式发送。
这个过程可以参考手册中的图29。 双缓冲带来的最大好处是,大大放宽了软件响应发送中断的时间窗口 。你不需要在第一个字节发送完成的瞬间立刻写入第二个字节,只要在它发送结束前的某个时间点写入即可。这使得你可以用更宽松的中断服务程序,或者将更多的CPU时间用于其他任务。
2.3 双缓冲下的中断与第9位数据(TB8)处理
双缓冲机制也改变了中断产生的逻辑。在传统模式下,TI标志在停止位开始时置位。而在双缓冲使能时,中断行为由
INTLO
(SSTAT.6)和
DBISEL
(SSTAT.4)两个位精细控制。
-
INTLO(中断时机位):0 = 在停止位 开始 时产生中断;1 = 在停止位 结束 时产生中断。 -
DBISEL(双缓冲中断选择位):这个位主要影响 最后一个字节 发送后的行为。当它为0时,发送完最后一个字节后不会产生额外的TI中断(因为后面没数据了,不需要通知CPU填充缓冲区)。当它为1时,即使发送队列结束,也会在最后一个字节的停止位(根据INTLO决定头或尾)产生一个TI中断。这个设计是为了让软件能统一地处理“发送完成”事件,但要注意,如果在这个中断里你又写了新数据,可能会产生时序歧义。
对于模式2和3,第9位数据(TB8)的写入时机至关重要。 在双缓冲使能的情况下,TB8必须在新数据写入SBUF之前被更新 。因为当写入SBUF时,当前的TB8值会和SBUF中的8位数据一起,被锁存到双缓冲寄存器中。如果你先写SBUF再改TB8,那么被发送出去的第9位将是旧的、错误的TB8值。这是一个非常隐蔽的bug来源。我的经验是,在发送函数中,养成“先设置TB8,再写入SBUF”的固定顺序。
2.4 多机通信与硬件自动地址识别
这是9位数据模式的王牌应用。想象一个主机(Master)要控制多个从机(Slave)的场景,比如一个主控板连接多个传感器模块。如果没有硬件支持,通常的软件做法是:主机发送一个包含目标从机ID的数据包,所有从机都接收并解析这个ID,匹配的从机响应,不匹配的从机忽略后续数据。这要求每个从机的软件都要参与解析,增加了复杂度和开销。
P89LPC932A1的硬件多机通信功能可以优雅地解决这个问题。其核心是利用了第9位数据(RB8)作为“地址/数据标识位”。我们这样配置从机:
-
将所有从机的UART设置为模式2或3,并置位
SM2位(SCON.5)。 -
当
SM2=1时,UART硬件有一个关键行为: 只有当接收到的第9位数据(RB8)为1时,才会在接收完成后置位RI中断标志;如果RB8为0,则直接丢弃本帧数据,不产生中断 。
基于此,我们可以设计通信协议:
- 地址帧 :主机发送的第一帧数据,其第9位(TB8)置1。数据位内容为目标从机的地址。
- 数据帧 :主机后续发送的数据,其第9位(TB8)清零。
从机的工作流程就变得非常简单:
-
初始化后,所有从机
SM2=1,都只对“地址帧”(RB8=1)产生中断。 - 主机广播地址帧后,所有从机均收到并进入中断。在中断服务程序中,从机读取SBUF,得到地址字节,并与自身预设的地址比较。
- 被寻址的从机 ,在中断服务程序中 清除自己的SM2位(设为0) 。这意味着它现在将对所有帧(无论RB8是1还是0)都产生中断,从而可以接收后续的数据帧。
-
未被寻址的从机
,保持
SM2=1,对后续RB8=0的数据帧“视而不见”,不产生任何中断,CPU无需干预。 -
通信结束后,被寻址的从机重新置位
SM2=1,恢复监听地址帧的状态。
这样一来,未被寻址的从机其软件完全不用处理数据帧,大大节省了CPU资源。这个功能在构建多节点、低功耗的传感网络时非常有用。
更强大的是
自动地址识别
(Automatic Address Recognition)功能。它通过
SADDR
(从机地址寄存器)和
SADEN
(从机地址掩码寄存器)两个寄存器,在硬件层面完成地址匹配。
SADEN
用于定义地址中的哪些位需要严格匹配,哪些位是“无关位”(Don‘t Care)。硬件会自动将接收到的地址与
(SADDR & SADEN)
的结果比较,如果匹配,并且RB8=1,则自动置位RI。这甚至省去了从机软件中地址比较的步骤,进一步降低了CPU负载和中断响应延迟。这在需要快速响应的系统中优势明显。
3. I2C接口工作原理与四种操作模式实战
3.1 I2C总线基础与P89LPC932A1的硬件框架
I2C(Inter-Integrated Circuit)总线是飞利浦(现NXP)发明的两线制同步串行总线,凭借其简单的连线(仅SCL时钟线和SDA数据线)、支持多主多从、硬件地址寻址等优点,成为嵌入式系统内部芯片间通信的事实标准。P89LPC932A1集成了一个完整的、字节型的I2C接口,支持高达400kHz的标准模式。
理解I2C,首先要吃透它的几个核心机制:
- 开漏输出与线与 :I2C总线上的SCL和SDA均采用开漏输出,必须外接上拉电阻。这意味着任何设备都可以将总线拉低(输出0),但释放总线时(输出1)实际上是高阻态,由上拉电阻拉高。这种“线与”特性是实现 总线仲裁 和 时钟同步 的基础。
- 主从架构 :发起传输、产生时钟信号的设备是主设备(Master),被寻址的设备是从设备(Slave)。一个总线上可以有多个主设备(多主模式),通过仲裁决定谁占用总线。
- 地址帧+数据帧 :每次传输都由主设备发起,以一个START条件开始,紧接着发送一个7位(或10位)从机地址加1位读写方向位(R/W)。从机地址匹配的设备会回应一个ACK(拉低SDA),然后开始数据传输。传输以STOP条件或重复的START条件结束。
P89LPC932A1的I2C模块通过6个特殊功能寄存器(SFR)与CPU交互,它们是软件编程的核心:
-
I2CON:控制寄存器,用于使能接口、发起START/STOP、控制中断和应答。 -
I2DAT:数据寄存器,读写传输的数据都通过它。 -
I2STAT:状态寄存器,只读,包含了当前I2C硬件的详细状态码(26种),是编写中断服务程序的“地图”。 -
I2ADR:从机地址寄存器,当芯片作为从机时,用它来设置自己的7位地址。 -
I2SCLH/I2SCLL:SCL高电平和低电平时间寄存器,用于配置通信速率。
3.2 时钟源选择与波特率计算
P89LPC932A1的I2C模块时钟源有两种选择,由
I2CON
寄存器的
CRSEL
位决定:
-
CRSEL = 0:使用内部独立的SCL时钟发生器。此时,SCL的频率由I2SCLH和I2SCLL两个寄存器的值决定。计算公式为:比特率 = fPCLK / [2 * (I2SCLH + I2SCLL)]其中fPCLK是外设时钟频率。I2SCLH定义SCL高电平持续的PCLK周期数,I2SCLL定义低电平持续的周期数。通过调整这两个值,不仅可以设置频率,还能调整SCL信号的占空比(不一定非是50%)。这是最常用的方式,灵活且不占用定时器资源。 -
CRSEL = 1:使用定时器1(Timer1)的溢出率作为时钟源。此时Timer1必须工作在8位自动重载模式(模式2)。I2C的比特率等于Timer1溢出率的一半。计算公式为:比特率 = Timer1溢出率 / 2 = fPCLK / [2 * (256 - TH1重载值)]这种方式通常用于需要非常精确的波特率,或者与其他需要Timer1定时的功能进行同步的场景。但会占用一个定时器。
实操心得 :在绝大多数应用中,选择
CRSEL=0,使用内部时钟发生器即可。配置I2SCLH和I2SCLL时,务必确保计算出的比特率不超过400kHz(标准模式上限)。例如,当fPCLK = 12MHz时,若要设置100kHz的I2C时钟,代入公式:100000 = 12000000 / [2 * (I2SCLH + I2SCLL)],解得I2SCLH + I2SCLL = 60。我们可以设置为I2SCLH = I2SCLL = 30,得到标准的50%占空比。手册建议每个寄存器的值都大于3,以保证波形质量。
3.3 主模式实战:发送器与接收器
I2C的主模式操作是主动发起通信的一方,流程上具有明确的步骤性,强烈建议使用
状态机
思维,在中断服务程序中根据
I2STAT
的状态码来驱动流程。
3.3.1 主发送器模式(Master Transmitter) 在此模式下,主设备向从设备写入数据。流程如下:
-
初始化
:配置
I2CON(使能I2C,I2EN=1;AA位通常设为0,因为主设备在接收数据时才控制ACK;STA,STO,SI清零),配置I2SCLH/L设置波特率。 -
启动传输
:软件置位
STA位。硬件会检测总线空闲后,发出START条件。成功后,状态码变为0x08,并产生中断(如果中断使能)。 -
发送从机地址+写方向
:在状态
0x08的中断服务程序中,向I2DAT写入 7位从机地址左移一位,并将最低位置0(表示写) 。例如,向地址0x50的器件写数据,则写入(0x50 << 1) | 0x00 = 0xA0。然后清除SI位以继续。 -
等待地址应答
:从机如果存在并应答,硬件会返回状态码
0x18(收到ACK)。如果无应答,则状态为0x20。在0x18状态的中断服务程序中,你可以开始发送第一个数据字节(写入I2DAT),然后清除SI位。 -
发送数据
:每成功发送一个字节并收到ACK,状态码会变为
0x28。在0x28的中断服务程序中,重复“写数据到I2DAT-> 清SI”的步骤,直到所有数据发送完毕。 -
结束传输
:发送完最后一个字节后,在
0x28状态的中断服务程序中,不再写入新数据,而是置位STO位(产生STOP条件),并清除SI位。硬件会自动发出STOP信号并结束传输。
3.3.2 主接收器模式(Master Receiver) 在此模式下,主设备从从设备读取数据。前几步与主发送类似:
-
初始化,发出START(状态
0x08)。 -
发送从机地址+读方向(最低位置1)。例如读地址0x50,写入
(0x50 << 1) | 0x01 = 0xA1。进入状态0x40(收到ACK)或0x48(无ACK)。 -
在状态
0x40的中断服务程序中,你需要 提前设置好ACK策略 。通过配置I2CON中的AA位:-
如果还要接收
多个字节
,则置
AA=1,表示接收本字节后回复ACK,从机会继续发送下一个字节。清除SI位后,硬件会自动接收下一个字节,状态变为0x50。 -
如果这是
最后一个字节
,则置
AA=0,表示接收本字节后回复NACK(非应答),通知从机停止发送。清除SI位后,硬件接收最后一个字节,状态变为0x58。
-
如果还要接收
多个字节
,则置
-
在状态
0x50(接收数据字节并回复了ACK)或0x58(接收数据字节并回复了NACK)的中断服务程序中, 第一件事是从I2DAT读取刚刚收到的数据 。然后根据是否还要继续接收,设置AA位,并清除SI位。 -
接收完成后,在
0x58状态的中断服务程序中,置位STO产生STOP条件,结束读取。
关键细节 :主接收模式下, ACK/NACK的控制是由主设备在接收一个字节之前设置的 ,而不是在收到之后。这是一个常见的理解误区。你需要在状态
0x40(发送地址后)和每次状态0x50(收到一个字节后)决定对 下一个字节 的应答策略。
3.4 从模式实战:接收器与发送器
从模式是被动响应的一方,其初始化相对简单,但需要处理好地址匹配和数据的收发。
3.4.1 从接收器模式(Slave Receiver)
-
初始化
:将自己的7位从机地址写入
I2ADR寄存器。配置I2CON:I2EN=1,AA=1(使能对自己地址的应答),STA,STO,SI清零。 -
等待寻址
:芯片进入空闲状态,持续监测总线上的地址帧。当收到与
I2ADR匹配的地址且方向位为写(0)时,硬件会自动回复ACK,并产生中断,状态码为0x60(或0x68,如果之前仲裁丢失)。 -
接收数据
:在状态
0x60的中断服务程序中,你可以选择是否继续接收。如果继续,保持AA=1并清SI,下一个数据字节到来且被接收后,状态变为0x80,同时数据已在I2DAT中,读取即可。如果这是最后一个需要的字节,则在状态0x60或0x80中,将AA位清零,这样在收到下一个数据字节后,从机会回复NACK,状态变为0x88。 -
传输结束
:当主设备发出STOP条件或重复START条件时,从机会进入状态
0xA0。这是一个重要的状态,标志着本次传输会话结束。你应该在此状态的中断服务程序中,进行必要的处理(如处理接收到的数据包),并重置接收状态,通常将AA重新置1,准备下一次寻址。
3.4.2 从发送器模式(Slave Transmitter)
-
初始化
:与从接收模式相同,写入
I2ADR,配置I2CON(AA=1)。 -
等待寻址
:当收到与自身地址匹配且方向位为读(1)的地址帧时,硬件回复ACK,并产生中断,状态码为
0xA8。 -
发送数据
:在状态
0xA8的中断服务程序中,你需要将 第一个要发送的数据字节 写入I2DAT,然后清除SI位。硬件会自动发送这个字节。 -
后续发送
:主设备每接收一个字节,会回复一个ACK。当从机发送一个字节并收到ACK后,状态变为
0xB8。在0xB8的中断服务程序中,你需要判断是否还有后续数据:-
如果还有数据,则写入下一个字节到
I2DAT,保持AA=1,清SI。 -
如果这是最后一个数据,则
不向
I2DAT写新数据 ,并将AA位清零。这样,当主设备收到最后一个字节后,会回复NACK,随后主设备会发出STOP条件,从机将再次进入0xA0状态。
-
如果还有数据,则写入下一个字节到
从模式的核心在于,作为从机,你无法控制传输的开始和结束(START/STOP),也无法控制每个字节后的ACK(除非在最后一个字节前将
AA
清零发NACK)。你的任务就是在正确的中断状态(
0x60
,
0x80
,
0xA8
,
0xB8
)下,进行正确的数据读写和
AA
位操作。
4. 状态码详解与中断服务程序编写指南
I2C编程的精髓,在于理解那26个状态码(
I2STAT
)并据此编写中断服务程序。状态码精确地告诉你总线当前进行到了哪一步。你的中断服务程序本质上就是一个大的
switch-case
状态机。
4.1 关键状态码解析与动作表
手册中的表68至表71是绝对的“圣经”,但直接看容易懵。我将其核心逻辑提炼为以下几个最常见、最关键的状态组:
| 模式 | 状态码 (I2STAT) | 硬件状态描述 | 软件必须执行的动作 | 清SI后硬件的下一步动作 |
|---|---|---|---|---|
| 主发送 | 0x08 | START条件已成功发出。 |
向
I2DAT
写入“从机地址+写位(0)”。
| 发送地址帧,等待ACK。 |
| 0x18 | 从机地址+写位已发送,并收到ACK。 |
向
I2DAT
写入
第一个数据字节
。
| 发送该数据字节,等待ACK。 | |
| 0x28 | 一个数据字节已发送,并收到ACK。 |
判断:
1. 还有数据:写下一个字节到
I2DAT
。
2. 发送完毕:置位
STO
(发STOP)。
|
1. 发送下一个字节。
2. 产生STOP条件。 | |
| 0x20 | 从机地址+写位已发送,但收到NACK(从机无应答)。 |
通常置位
STO
结束传输,或置位
STA
发起重复START重试。
| 产生STOP或重复START。 | |
| 主接收 | 0x08 | START条件已成功发出。 |
向
I2DAT
写入“从机地址+读位(1)”。
| 发送地址帧,等待ACK。 |
| 0x40 | 从机地址+读位已发送,并收到ACK。 |
设置ACK策略
:
- 继续收:置
AA=1
。
- 收最后一个:置
AA=0
。
| 开始接收第一个数据字节。 | |
| 0x50 |
一个数据字节已接收,且之前
AA=1
(主回复了ACK)。
|
1.
从
I2DAT
读取数据
。
2. 设置对 下一个字节 的ACK策略(
AA=1
或
0
)。
| 继续接收下一个字节。 | |
| 0x58 |
一个数据字节已接收,且之前
AA=0
(主回复了NACK)。
|
1.
从
I2DAT
读取数据
。
2. 通常置位
STO
结束接收。
| 产生STOP条件。 | |
| 从接收 | 0x60 | 自身的从机地址+写位(0)被识别,已回复ACK。 |
准备接收数据。通常保持
AA=1
以继续接收。
| 等待接收第一个数据字节。 |
| 0x80 |
一个数据字节已接收,并回复了ACK(因之前
AA=1
)。
|
1.
从
I2DAT
读取数据
。
2. 决定是否继续接收(保持
AA=1
)或停止(设
AA=0
)。
| 等待接收下一个字节。 | |
| 0xA0 | 检测到STOP或重复START条件,传输结束。 |
处理接收到的完整数据包。重置状态,通常设
AA=1
。
| 返回空闲,等待下一次寻址。 | |
| 从发送 | 0xA8 | 自身的从机地址+读位(1)被识别,已回复ACK。 |
向
I2DAT
写入
第一个要发送的数据字节
。
| 发送该数据字节,等待主设备ACK。 |
| 0xB8 | 一个数据字节已发送,并收到主设备的ACK。 |
判断:
1. 还有数据:写下一个字节到
I2DAT
。
2. 最后一个数据: 不写
I2DAT
,并置
AA=0
。
|
1. 发送下一个字节。
2. 发送最后一个字节后,将回复NACK。 |
4.2 中断服务程序编写框架与示例
基于状态机的编程模型非常清晰。下面是一个 主设备发送N个字节 的中断服务程序伪代码框架,假设使用查询方式(也可以基于中断):
// 假设定义全局变量
unsigned char I2C_State = 0;
unsigned char I2C_Data[32];
unsigned char I2C_Index = 0;
unsigned char I2C_Length = 0;
void I2C_ISR(void) interrupt X { // X为I2C中断号
unsigned char status = I2STAT; // 读取状态码
switch(status) {
case 0x08: // START已发出
I2DAT = SLAVE_ADDR_W; // 写入从机地址+写
I2CON &= ~0x08; // 清除SI位
break;
case 0x18: // 地址已应答,准备发数据
case 0x28: // 上一字节已应答,准备发下一字节
if(I2C_Index < I2C_Length) {
I2DAT = I2C_Data[I2C_Index++]; // 发送数据
} else {
I2CON |= 0x10; // 发送完成,置位STO
}
I2CON &= ~0x08; // 清除SI位
break;
case 0x20: // 地址无应答
// 错误处理,例如重试或置错误标志
I2CON |= 0x10; // 发STOP结束
I2CON &= ~0x08;
I2C_State = ERROR;
break;
case 0x30: // 数据无应答(从机可能忙)
// 错误处理
I2CON |= 0x10;
I2CON &= ~0x08;
I2C_State = ERROR;
break;
case 0x38: // 仲裁丢失
// 在多主系统中处理,可尝试重发
I2C_State = IDLE;
break;
default:
// 未处理的状态,进行错误恢复
I2CON |= 0x10; // 强制STOP
I2CON &= ~0x08;
I2C_State = ERROR;
break;
}
}
// 主函数中启动传输
void Master_Transmit(unsigned char addr, unsigned char *data, unsigned char len) {
// 等待总线空闲(简单实现可延时)
I2C_State = BUSY;
I2C_Index = 0;
I2C_Length = len;
// 将data复制到I2C_Data...
SLAVE_ADDR_W = (addr << 1) | 0x00;
I2CON |= 0x20; // 置位STA,启动传输
while(I2C_State == BUSY); // 等待传输完成
}
4.3 常见问题与深度排查技巧
问题1:通信完全无反应,用逻辑分析仪或示波器看不到任何波形。
-
检查要点
:
- 引脚配置 :P89LPC932A1的I2C引脚是P1.2 (SCL) 和 P1.3 (SDA)。确保这两个引脚已配置为开漏模式(Open-Drain)或准双向口,并且 外部上拉电阻(通常4.7kΩ-10kΩ)已正确连接至VCC 。这是最常见的问题!
-
模块使能
:
I2CON寄存器中的I2EN位是否置1? -
时钟配置
:如果使用内部时钟发生器(
CRSEL=0),I2SCLH和I2SCLL寄存器是否已写入有效值(>3)?计算出的频率是否合理? - 总线冲突 :确保没有其他设备将总线持续拉低。可以尝试断开所有从设备,只留主设备,看能否产生START条件。
问题2:能发出START和地址,但收不到ACK(状态码卡在0x20或0x48)。
-
检查要点
:
-
从机地址
:7位地址左移一位后,最低位是R/W位。确认写入
I2DAT的地址值是否正确。例如,器件手册标明地址为0x68,则写地址应为0x68 << 1 = 0xD0,读地址为0xD1。 - 从机电源与连接 :从设备是否正常供电?SDA/SCL线是否连接牢固?
- 从机应答能力 :有些从设备在完成内部操作(如EEPROM写入)期间会拉低SDA(Clock Stretching),主设备必须等待。P89LPC932A1的I2C模块支持时钟延长,但需确保软件没有超时。
- 总线电容 :总线过长或负载过多导致上升沿太慢,可能被误认为低电平。尝试减小上拉电阻值(如从10kΩ改为4.7kΩ)或降低通信速率。
-
从机地址
:7位地址左移一位后,最低位是R/W位。确认写入
问题3:通信时好时坏,偶尔数据错误。
-
检查要点
:
- 电源噪声 :在电源引脚增加去耦电容(如100nF陶瓷电容紧贴芯片)。
- 信号完整性 :使用示波器观察SCL和SDA波形,看是否有过冲、振铃或毛刺。过长走线可能需串联小电阻(如22Ω-100Ω)进行阻抗匹配。
- 中断冲突 :确保I2C中断服务程序执行时间足够短,避免错过下一个字节的响应。如果中断服务程序太长,可以考虑在中断中只设置标志,在主循环中处理状态机。
- 状态机逻辑错误 :仔细核对你的中断服务程序,是否在每个状态都进行了正确的操作并清除了SI位?是否遗漏了某些状态的处理? 状态0x38(仲裁丢失)和0x00(总线错误)必须被处理 ,否则一旦出现,I2C模块可能锁死。
问题4:作为从机时,无法被主机寻址。
-
检查要点
:
-
从机地址寄存器
:
I2ADR是否已正确写入自身的7位地址(注意,是7位,不是8位)? -
应答使能
:
I2CON中的AA位是否置1?从机必须置AA=1才能对自身地址进行应答。 -
通用呼叫地址
:如果主机使用通用呼叫地址(0x00),需要将
I2ADR的GC位(位0)置1,才能使能对该地址的响应。
-
从机地址寄存器
:
问题5:UART双缓冲使能后,发送速度没有提升,甚至出现数据错乱。
-
检查要点
:
-
双缓冲使能位
:确认
SSTAT寄存器的DBMOD位已设置为1。 -
中断时机
:检查
INTLO和DBISEL位的配置。如果DBISEL=0,发送完最后一个字节后不会产生TI中断,你的发送完成判断逻辑需要相应调整(例如,在发送最后一个字节后,等待一个超时或检查发送移位寄存器空闲标志)。 -
TB8写入顺序
:在模式2/3下,如果使用了第9位,务必确保在写
SBUF之前写TB8。一个良好的编程习惯是,将设置TB8和写SBUF封装成一个函数。 - 缓冲区管理 :双缓冲只是提供了一个硬件缓冲区。如果你的软件发送数据的速度仍然慢于波特率,瓶颈就在软件。需要优化数据准备流程,或者使用更大的软件FIFO(先入先出队列)来配合硬件双缓冲。
-
双缓冲使能位
:确认
调试I2C和UART,一个 逻辑分析仪 是必不可少的工具。它能直观地展示出START、STOP、ACK、数据位的每一个波形,以及精确的时序,对于定位“有没有信号”、“信号对不对”、“时序准不准”这类问题,效率远超printf调试。

230

被折叠的 条评论
为什么被折叠?



