I2C通信

I2C基本通信规则

标准《NXP_UM10204_I2C-bus specification and user manual》

IIC总线是一个事实上的世界标准,现在已经在50多家公司生产的1000多种不同的IC中实施。

此外,多功能的IIC总线被用于各种控制架构,如系统管理总线(SMBus)、电源管理总线(PMBus)、智能平台管理接口(IPMI)、显示数据通道(DDC)和高级电信计算架构(ATCA)。

本文件帮助设备和系统设计者了解IIC总线的工作原理并实现工作应用。描述了各种操作模式。它包含了对IIC总线数据传输、握手和总线仲裁方案的全面介绍。详细的章节涵盖了IIC总线在每种工作模式下的时序和电气规范。

IIC兼容芯片的设计者应将该文件作为参考,并确保新设备满足该文件中规定的所有限制。包含IIC器件的系统的设计者应审查本文件,并参考各个组件的数据表。

——翻译自标准文件

IIC(Inter-Integrated Circuit)是一种通用的总线协议。它是由Philips(飞利浦)公司,现NXP(恩智浦)半导体开发的一种简单的双向两线制总线协议标准。

对于硬件设计人员来说,只需要2个管脚,极少的连接线和面积,就可以实现芯片间的通讯,对于软件开发者来说,可以使用同一个IIC驱动库,来实现实现不同器件的驱动,大大减少了软件的开发时间。极低的工作电流,降低了系统的功耗,完善的应答机制大大增强通讯的可靠性。

术语

image-20210514111033910

术语 释义
总线 传输数据的通路
传送者 传输数据至总线的设备
接收者 从总线上接受数据的设备
主(机) 主动开启传输、生成时钟信号 并 主动结束传输的设备
从(机) 由主机寻址的设备
多主(机) 在不损坏数据传输的情况下允许多主机可以试图同时控制总线
仲裁 确保在有多个主机同时试图控制总线的情况下,只允许一个主机这样做,而且仲裁成功的信息不会被(其他设备)破坏的程序。
同步 确保两个及以上设备其时钟信号的同步

以下这个图应该可以较好地解释“多主机”的概念,多个主机可以同时向外发出传输数据的申请,也可以主动结束其数据传输。虽然,IIC降低了总线复用的成本,但仍需要“仲裁”机制来解决多个主机之间的无序竞争状态。

image-20210514112114667

硬件要求

电气特性

根据要求,I2C的高电平有效值要达到输入参考电压(一般是VDD)的0.7倍以上,低电平有效值需要达到输入参考电压的0.3倍以下。

image-20210618143438565

总线硬件要求

通过 串行数据(SDA,Serial Data)和 串行时钟(SCL,Serial Clock)两条线路对设备进行连接。

每个设备都通过自身的通信设备地址进行互相辨别,且每个设备都可以当发送者或接收者,取决于设备自身的功能定义。

标准传输速率

各模式、速率及其传输方向见下方表格。

Data on the IIC-bus can be transferred at rates of up to 100 kbit/s in the Standard-mode, up to 400 kbit/s in the Fast-mode, up to 1 Mbit/s in Fast-mode Plus, or up to 3.4 Mbit/s in the High-speed mode.

MODE RATE TRANSFER DIRECTION DOWNWARD COMPATIBLE WITH
STANDARD 100 Kbit/s bidirectional /
FAST 400 Kbit/s bidirectional STANDARD
FAST-PLUS 1 Mbit/s bidirectional STANDARD / FAST
HIGH-SPEED 3.4 Mbit/s bidirectional STANDARD / FAST / FAST-PLUS
ULTRA-FAST 5 Mbit/s unidirectional /

编码协议

以下协议要求适用于双向通信模式(不包括ULTRA-FAST模式下的单向通信)

逻辑电平规定

由于IIC的接入设备众多(如CMOS、NMOS、Bipolar等),因此逻辑电平0和逻辑电平1(的电压)并不是固定不变的,这取决于VDD的电平。

输入参考电平会被控制在VDD电平的30%~70%之间。 VIL = 0.3 * VDD;VIH = 0.7 * VDD

某些传统设备的输入电平会被固定在1.5V(VIL)和3.0V(VIH),但新设备都要求是 30%VDD 及 70%VDD

注意:VIL、VIH、VOL、VOH都是以参考电压作为基准的,即要达到某个电压的百分之多少时,才能够被认为是 逻辑电平0逻辑电平1 。例如,可将VIL 视作当VDD下降到只有30%及以下时,为逻辑电平0,30%VDD是其逻辑电平的最高阈值;同理可得 VIH ;超过这个值而未到达70%的中间态则为不确定态。

Due to the variety of different technology devices (CMOS, NMOS, bipolar) that can be connected to the IIC-bus, the levels of the logical ‘0’ (LOW) and ‘1’ (HIGH) are not fixed and depend on the associated level of VDD . Input reference levels are set as 30 % and 70 % of VDD ; VIL is 0.3VDD and VIH is 0.7V DD .

See Figure 38, timing diagram. Some legacy device input levels were fixed at VIL = 1.5 V and VIH = 3.0 V, but all new devices require this 30 %/70 % specification.

image-20210514134830615

时序正确性

  • SDA上的数据必须在SCL高电平期间保持稳定,即不能在SCL高电平期间发生翻转(后见原因),只有在SCL低电平时可以翻转。
  • 时钟脉冲信号生成要先于数据传送。

The data on the SDA line must be stable during the HIGH period of the clock. The HIGH or LOW state of the data line can only change when the clock signal on the SCL line is LOW (see Figure 4). One clock pulse is generated for each data bit transferred.

image-20210514141512136

开始及结束标志

  • 传输开始时必须以一个 开始 信号作为标志,结束时必须以一个 结束 信号作为标志。

  • 开始信号 是以SCL为高电平时的 SDA电平的由高转低 作为标志,结束信号 是以SCL高电平时的 SDA电平的由低转高 作为标志。

  • 开始和结束标志必须由主设备控制。

All transactions begin with a START (S) and are terminated by a STOP (P) (see Figure 5). A HIGH to LOW transition on the SDA line while SCL is HIGH defines a START condition. A LOW to HIGH transition on the SDA line while SCL is HIGH defines a STOP condition.

image-20210514142116674

开始字节

image-20210515150526897

dummy acknowledge (HIGH) : 无声确认???

总线状态

总线具有两种状态,繁忙(Busy)和 空闲(Free),即 SDA电平 “开始” 后,总线进入繁忙状态, SDA电平 “结束” 后,总线进入空闲状态。

注意:在总线繁忙期间,发生重复的“开始”状态(而不是“结束”状态),由于“重复开始”和 “开始” 功能相同,为了方便,统称为 “开始”,除非特殊说明为 “重复开始”。

The bus stays busy if a repeated START (Sr) is generated instead of a STOP condition. In this respect, the START (S) and repeated START (Sr) conditions are functionally identical. For the remainder of this document, therefore, the S symbol is used as a generic term to represent both the START and repeated START conditions, unless Sr is particularly relevant.

传输比特位

  • SDA线上的字节传输必须满足8位长度。
  • 每次传输数据的字节数不受限制。
  • 每个字节数后都需要跟一个确认位(Acknowledge bit)
  • 当从机无法完整接收或传输一个完整的数据字节时,例如正在产生内部中断等,除非其完成内部的某些功能,否则该从机将能够一直拉低SCL的电平,强制让主机一直处于等待状态。只有当从机准备好接收下一个完整的字节并释放SCL时,数据传输才会继续。

Every byte put on the SDA line must be eight bits long. The number of bytes that can be transmitted per transfer is unrestricted. Each byte must be followed by an Acknowledge bit.

Data is transferred with the Most Significant Bit (MSB) first (see Figure 6). If a slave cannot receive or transmit another complete byte of data until it has performed some other function, for example servicing an internal interrupt, it can hold the clock line SCL LOW to force the master into a wait state. Data transfer then continues when the slave is ready for another byte of data and releases clock line SCL.

image-20210514144620803

确认标志

  • 每个字节(8比特)后都跟着一个确认位,即前8位数据,第9位确认。
  • SCL上的时钟信号由主机产生,包括确认位的的第九个时钟脉冲
  • SDA线并不是由主机一直占有的,每发送完一个完整字节后,主机都会释放SDA线,由从机进行数据确认。
  • 发送者在确认时钟脉冲期间释放SDA线,以便接收者可以将SDA线拉低,并且在该时钟脉冲的高电平期间保持稳定的低电平。
  • 对于设置(set-up)和保持(Hold)的时间,都应该依照表10中只针对各种不同模式的要求进行设置。
  • 如果SDA电平在第九个时钟脉冲时仍保持高电平,则被定义为 “未确认状态”。则此时主机可以发送一个“结束”标志来结束当前连接,或发送一个“开始”标志来开启一次新的传送。

即,此时(第9个脉冲时)SDA的控制权已经被mater释放了,但仍为高电平,slave可以在这第9个时钟脉冲高电平期间获得控制权,如果确认收到数据,则将电平拉低,否则不拉低。

image-20210607141115024

The acknowledge takes place after every byte. The acknowledge bit allows the receiver to signal the transmitter that the byte was successfully received and another byte may be sent. The master generates all clock pulses, including the acknowledge ninth clock pulse.

The Acknowledge signal is defined as follows: the transmitter releases the SDA line during the acknowledge clock pulse so the receiver can pull the SDA line LOW and it remains stable LOW during the HIGH period of this clock pulse (see Figure 4). Set-up and hold times (specified in Section 6) must also be taken into account.

When SDA remains HIGH during this ninth clock pulse, this is defined as the Not Acknowledge signal. The master can then generate either a STOP condition to abort the transfer, or a repeated START condition to start a new transfer.

以下为五种“未确认/未应答”的可能情况:There are five conditions that lead to the generation of a NACK:

  1. 传输地址错误:No receiver is present on the bus with the transmitted address so there is no device to respond with an acknowledge.
  2. 实时处理中:The receiver is unable to receive or transmit because it is performing some real-time function and is not ready to start communication with the master.
  3. 数据/代码未定义:During the transfer, the receiver gets data or commands that it does not understand.
  4. 正在传输中:During the transfer, the receiver cannot receive any more data bytes.
  5. 未收到结束信号(收发身份未转换):A master-receiver must signal the end of the transfer to the slave transmitter.

时钟同步

任意几个主机都可能同时发起数据传输请求,此时只有一个主机和一个从机可以掌握总线进行通信,此时需要通过 时间同步仲裁 完成对总线的调配。

在单机系统(Single Master System)中,不需要时钟同步和仲裁。

时钟同步是通过IIC接口与SCL线的有线-AND连接进行的。

这意味着SCL线上的高电平到低电平的转换会导致相关的主站开始对其低电平周期进行计数(如图7的CLK2),一旦一个主站时钟 CLK1 变为低电平,它就会将SCL线电平拉低,并保持在这个状态,直到另一个时钟 CLK2 达到高电平状态。

然而,如果时钟 CLK2 仍在其低电平周期内,CLK1 的低电平到高电平转换可能不会改变SCL线的状态。

因为SCL线被具有最长的低电平周期的主站 CLK2 保持为低电平,而在这段时间内,低电平周期较短的主站 CLK1 进入高电平等待状态。

Clock synchronization is performed using the wired-AND connection of IIC interfaces to the SCL line. This means that a HIGH to LOW transition on the SCL line causes the masters concerned to start counting off their LOW period and, once a master clock has gone LOW, it holds the SCL line in that state until the clock HIGH state is reached (see Figure 7). However, if another clock is still within its LOW period, the LOW to HIGH transition of this clock may not change the state of the SCL line. The SCL line is therefore held LOW by the master with the longest LOW period. Masters with shorter LOW periods enter a HIGH wait-state during this time.

当所有相关的主控器都结束了他们的低电平周期,时钟线被释放并变成高电平。

然后,在主站时钟和SCL线的状态之间没有差异,所有的主站开始计算他们的高电平周期。

第一个完成其高电平周期的主站再次将SCL线拉到低电平。这样,就产生了一个同步的SCL时钟其低电平周期由具有最长时钟低电平周期的主站决定其高电平周期由具有最短时钟高电平周期的主站决定

image-20210514170202184

竞争仲裁

仲裁和同步一样,是指只有在系统中使用一个以上的主站时才需要的协议的一个部分。注意,从机不参与仲裁程序。

一个主站只有在总线空闲时才可以开始传输。两个主站可以在START条件的最小保持时间(tHD;STA)内产生一个START条件,从而在总线上产生一个有效的START条件。然后需要进行仲裁,以确定哪个主站将完成其传输。

仲裁是逐位进行的。在每个比特期间,当SCL为高电平时,每个主站检查SDA电平是否与其所发送的内容相符,期间可能需要很多比特,只要传输的内容相同,两个主站实际上可以无误地完成整个信息传输过程。

当一个主站第一次试图发送一个高电平,但检测到SDA电平为低电平时,主站知道它已经失去了仲裁,并关闭其SDA输出驱动器。在其他主站被仲裁过程中,正在传输其信息的主站将继续其传输,没有信息丢失。

失去仲裁的主站可以产生时钟脉冲,直到它失去仲裁的字节结束,并且必须在总线空闲时重新尝试传输。

如果一个主站也包含了一个从站功能,并且它在寻址阶段失去了仲裁,有可能获胜的主站正试图对它进行寻址。因此,失败的主站必须立即切换到其从站模式。

图8显示了两个主站的仲裁程序。可能涉及更多,这取决于有多少个主站连接到总线上。

当产生DATA1的主站的内部数据电平与SDA线上的实际电平有差异时,DATA1的输出被关闭。这并不影响由获胜主站发起的数据传输。

image-20210515142633906

通用寻址

image-20210515172907284
image-20210515172945861
image-20210515172958184

软复位

在发送general call (0000 0000)后,再发送一个 0000 0110 (06h) ,这样两个具有先后顺序的字节来定义软复位。

首先,这种软复位是可选 ,并不是所有设备都会对软复位指令进行响应。

在收到这2个字节的序列时,所有被设计为响应一般调用地址的设备都会复位,并接收其地址的可编程部分。

On receiving this 2-byte sequence, all devices designed to respond to the general call address reset and take in the programmable part of their address.

必须采取预防措施,确保设备在施加电源电压后没有拉低SDA或SCL线,因为这些低电平会阻断总线。

总线复位

SCK信号一直卡在低电平的事件较少发生。

  • 如果IIC总线设备拥有硬件复位输入,则可以通过优先程序启用硬件中断复位来重置总线。
  • 如果没有硬件复位输入,则通过给设备循环供电,激活强制性的内部开机复位(POR)电路。

SDA信号如果一直卡在低电平。

  • 则主机应该发送九次SCL时钟脉冲,此时令SDA保持低电平的设备在收到九次脉冲后应主动释放总线。

  • 如果九次脉冲没有用,则启用硬件中断复位或循环供电来清除总线。

设备ID

可选的3字节只读设备ID编码(an optional 3-byte read-only ):

  • 12位是设备制造商编号,具有独一无二性
  • 9位零件ID,由设备制造商写入
  • 3位模具/晶元版本,由设备制造商写入

image-20210515152141963

  1. 启动条件

  2. 主站发送保留设备ID的 IIC 总线地址,后面的R/W位设置为0(写):1111 1000

  3. 主站发送从属地址。LSB是一个 "不关心"的值,只有一个设备必须确认这个字节(拥有IIC总线从属地址的设备)。

  4. 主站发送一个 Re-START 条件。

备注:在 STOP 条件之后,再加上一个 START 条件,就会重置从属状态机,无法进行设备ID读取。另外,在 STOP 条件或 Re-START 条件之后,对另一个从属设备的访问也会重置从属状态机,无法执行Device ID读取。

  1. 主站发送保留设备ID的 IIC 总线地址,后面的R/W位设置为 1(读):1111 1001

  2. 可以进行设备ID读取,从12个制造商位开始(第一个字节+第二个字节的4个MSB),然后是9个部件识别位(第二个字节的4个LSB+第三个字节的5个MSB),然后是三个芯片修订位(第三个字节的3个LSB)。

  3. 主站通过NACKing最后一个字节来结束读取序列,从而重置从属设备的状态机,允许主站发送 STOP 条件。

    备注:设备ID的读取可以随时通过发送NACK来停止。

如果主站在第三个字节之后继续ACK,从站就会滚回第一个字节,并继续发送设备ID序列,直到检测到 NACK。

ADI-Codec I2C开发

ADI

物理接口

存在由ADI官方提供的评估板(和MediaWorks的板) 和 开发第一板(量产板)之间的引脚定义差别,一开始使用的是评估板的DPI 11 和 12引脚,即 DPI_P11SDADPI_P12SCLK。后来发现作为时钟引脚的DPI无法被控制,转而使用 DPI 11 和 13。即 DPI_P11SDADPI_P13SCLK

image-20210524112141809

image-20210615083241938

MediaWorks核心板J3引脚排示意图

以下描述均以评估板为例,DIY板不一定适用。

插口(Jumper)定义

image-20210427165847934

时钟及PLL

  1. 1772的工作频率为12.288兆赫,而如果过要设置PLL,PLL输出一定是24.576兆赫。
  2. 通过 MCLKIN 端为codec提供核心时钟信号,可接入8至27兆赫信号。
  3. 通过对 0x00000x0005 的寄存器进行写入修改PLL。
  4. PLL模式要被设置为分数或整数,则取决于MCLK的输入频率。
  5. 在时钟信号达到PLL前,时钟信号会经过整数时间分频器以确保时钟频率符合要求;可以通过设置 0x0005 的bit[2:1]来设置其分频率。
  6. 要使用PLL时,首先要明确PLL输出频率是多少,当PLL信号与时钟信号是整数倍关系时才需要使用整数分频器。
image-20210513093158863

以下列举了整数型及分数型分频器的参数设置:

image-20210513093047973

评估板电路图

image-20210513092007130

输入输出

评估板电路图

image-20210513090414605

第0及第1通道

以下为codec芯片对引脚的定义,在提供一个接地端,构成TRS是完全没有问题,但是codec的定义和其评估板的外围电路设计有所区别。

image-20210513083636462

正常3.5mm音频接口为TRS接口,其中T虽然为VCC(通常是),R作为音频信号输入,而S为接地端,且T和R可共同作为左右声道分别进行输入。(也有T和S定义相反的设计)

但是按上述电路进行接线时,相当于TS接口(单声道)接线模式,却又不完全相同:

  • 使用 MICBIASx 对14接口进行供电,此时Tip端是VCC。
  • Ring端接13接口,充当Sleeve端(而实际上的Sleeve端因已经接地,可以不进行连接)。
  • 由Ring(AIN1REF)和Tip(AIN1)共同构成了TS单声道接线模式。

第2及第3通道

  • 2/3输入通道是复用立体声通道,即1通道和0通道都可以单独接入音源,在物理音频接口上独立,但第2/3通道是在一起的,共用一个物理通道。UG第7页中也有提到。

image-20210427104818097

image-20210427105655639

  • 有四个单端输入通道可以设置为麦克风信号或线路信号,一个双立体声数字输入信号通道和也可被用于单端输出的两个差分(differential)输出。

I2C通信

时序

图85展示了 单字主写模式 的时序。每9个时钟脉冲,1772都会拉低一次SDA的电平以表示确认收到。

图86展示了 多字主写模式 的时序。此图展示了2个字节大小数据(例如程序)的写入过程。1772会在每两个字节后增加一次子地址寄存器,因为被使用的子地址对应着 2字节 长度的寄存器或内存区域

图87展示了 单字主写模式后转变为 单字主读模式 的时序。1772作为从机接受数据后发送确认标志,主机立即发起重复开始的 单字主读模式,由从机发送至主机。

图88展示了 多字主读模式 的时序,展示了读取目标字节为2bytes的示例。此处上述黄底字重复。其他地址范围可能有不同的字长,从一个到四个字节不等。ADAU1772总是对子地址进行解码,并设置自动递增电路,使地址在适当的字节数后递增。

image-20210518105918652

S = 开始 标志

P = 停止 标志

AM = 主机确认标志

AS = 从机确认标志

接口

USBi接口

​ 《SigmaStudio_USBi_to_EZ-Board_Adapter_Schematic-Rel_0.1.pdf》中对接口的定义,总共需要4个引脚,分别是1、3、4、10,对应着 SCL / SDA / VCC / GND;

image-20210518112823329

1772接口

需要用到的接口是 SCL/SCLKSDA/MISO

image-20210518192233133

ADDR0 和 ADDR1 分别对应的是控制接口(CONTROL PORT)上的 CLATCHCDATA 针脚。

image-20210607145600052

image-20210518112823329

BIT_6 BIT_5 BIT_4 BIT_3 BIT_2 BIT_1 / ADDR1 / CDATA BIT_0 / ADDR0 / CLATCH ADDRESS
0 1 1 1 1 0 0 0x3C
0 1 1 1 1 0 1 0x3D
0 1 1 1 1 1 0 0x3E
0 1 1 1 1 1 1 0x3F

21479接口

《ADSP-21479 EZ-Board Evaluation System Manual》,需要用到的接口是 SPI_CLK(DPI_P3)SPI_MISO(DPI_P2)

image-20210518113240492

对应评估板上的针脚接口如下图示 24(DPI_P3) 和 27(DPI_P2) 接口。

image-20210519114333234

设备地址

主机每次发送8位数据,其中最低的一位由主机发送标识读/写位。逻辑电平1是读,逻辑电平0是写。

通过I2C方式进行连接的从机,其设备地址是7位数(1-7,对应表21是bit0-bit6),表21提供了1772的设备地址,ADDR1ADDR0 位置可以通过接入pin的方式自由设置,即输入引脚的电平不同,每个1772都能够拥有4个独特的地址。此类方式允许多块1772接入同一I2C总线。

  • 即,默认情况下从机地址为 0x3C ,可以设置成其他:0x3D , 0x3E, 0x3F
  • 每个SDA和SCL线都应该接入一个2.0KΩ的升压电阻,但不应该高过IOVDD。
image-20210519093557617

image-20210519094454381

相关问答

Q: what is the macro SIGMA_WRITE_DELAY supposed to do? Is it a simple delay? Why are there parameters regarding the device I2C address, data and length? A: This macro is used for applications that need to pause the data writes for a specific purpose. For example, if you need time for the PLL to be set before starting to write to the rest of the registers. It could be used as a simple delay, you’ll need to specify the device address always because some applications can require more than one DSP; the length is just the address byte length (could be 1 or 2 byte length) and data is the value in hex that represents the total time in milliseconds (that is up to the micro controller clock speed). The reason length is used, is that some DSPs work with one and some other require two address bytes.

<SigmaStudioFW.h> contains a lot of macros to be defined. What I found by examinating all the code is that what macros need to be defined depend on what you want to perform with your microcontroller.

If you only need to load a DSP program with a call to default_download(), well, you only need (at least for the ADAU1761)

to implement the two macros:

  • SIGMA_WRITE_REGISTER_BLOCK

  • SIGMA_WRITE_DELAY

The first one is the basis of all the loading of data into program ram, parameter ram, and registers. The second one is only a delay (to be exact it serves to wait for the PLL to lock, in any case most of the times it can be implemented as a simple delay).

If you want to use sequences, that is to implement some control of the DSP running with your microcontroller, you will probably need to implement the other macros. In particular if you will control volume sliders you will need to implement the conversion macros for integer or float to the internal 5.23 representation. You can search elsewhere on the forum, I saw some of this code.

开发代码

ADI和Codec之间的I2C通信可以通过两种方式进行,第一种是操作21478上面的TWI寄存器,需要查看《HWR》以获取更多信息。第二种是学会控制引脚高低电平,通过高低电平模拟寄存器操控来读取和写入I2C设备。

在开发过程中,先使用的第一种方式对TWI寄存器进行操控,但发现21479中有某个寄存器无法写入,故放弃。下面提供第二种方式中需要用到的三个文件,分别是 TWI.hTWI.cSigmaStudioFW.h 。经调试,以下代码可以正常收发数据,实现对从设备的读/写。

TWI.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
/*
* TWI.C
*
* Created on: 2021年6月16日
* Author: 431240
*/


#include"TWI.H"

//宏定义
#define HWDELAY {HWDelay(MINDT);} //延时函数
#define MTWACK {TWIMWaitAck();} //Master Transmitter Wait Ack


// ------------------------------------------------------------------------------------
// 全局变量声明
DADDR tempDevAddr = 0x00; //临时设备地址存储比对变量,及其初始化
RADDR tempRegAddr = 0x00; //临时寄存器地址存储对比变量,及其初始化
TWIDATA bit = 0;

static TWIDATA dataRead = 0; //用于观察数据写入后再读取的状态
//static TWIDATA addrAckRead = 0; //调试观察用
//static TWIDATA regAckRead = 0; //调试观察用
//static TWIDATA dataAckRead = 0; //调试观察用

static DADDR targetAddr = 0x00;

// ------------------------------------------------------------------------------------
// 函数定义
extern void TWITimerIsr(int delayTime){ //用于产生中断高/低电平延时的定时器中断(句柄)
//需要自定义,涉及到定时器调用相关函数,请自行查阅手册
}


extern void HWDelay(float hwDelayTime){ // 定义硬件延时(单位微秒)

#ifdef HWDELAY_CORETIMER
timer_set(6400*hwDelayTime, 6400*hwDelayTime);
timer_on();
timer_off();
#endif

#ifdef HWDELAY_SYSWAITNOP
SysWaitNOP(HW_SYSWAITNOP_TIME); //此处使用循环函数进行延时
#endif

}


extern void TWIInit(void){ //初始化函数
#ifdef EVA_21479
SRU(FLAG13_O,DPI_PB13_I);
SRU(FLAG11_O,DPI_PB11_I);

SRU(HIGH,DPI_PBEN13_I);
SRU(HIGH,DPI_PBEN11_I);

sysreg_bit_set( sysreg_FLAGS, (FLG11O|FLG13O) );
sysreg_bit_clr( sysreg_FLAGS, (FLG11|FLG13) );

#else
SRU(FLAG8_O,DPI_PB08_I);
SRU(FLAG7_O,DPI_PB07_I);

SRU(HIGH,DPI_PBEN08_I);
SRU(HIGH,DPI_PBEN07_I);

sysreg_bit_set( sysreg_FLAGS, (FLG7O|FLG8O) );
sysreg_bit_clr( sysreg_FLAGS, (FLG7|FLG8) );
#endif

SDA_OUT;
SCL_OUT;

}


extern int TWIStart(void){ //启动函数
SDA_OUT; //设置为数据输出方向

S_SDA;
S_SCL;
HWDELAY;

C_SDA;
HWDELAY;

return 1;
}


extern void TWIReStart(void){ //重启动函数
SDA_OUT;

C_SCL;
HWDELAY;

S_SDA;
S_SCL;
HWDELAY;

C_SDA;
HWDELAY;
}


extern void TWIStop(void){ //停止
SDA_OUT;

C_SCL; //modified by liewzheng
C_SDA; //在时钟信号拉高之前先拉低数据信号
HWDELAY; //modified by liewzheng

S_SCL;
HWDELAY;

S_SDA; //在时钟信号高电平时拉高数据信号
HWDELAY;
}


extern void SendWithoutAck(void){ //发送无需确认
SDA_OUT;

C_SDA;
HWDELAY;

S_SCL;
HWDELAY;

C_SCL;

SDA_IN;
}


extern TWIDATA TWIMWaitAck(void){ //等待从机确认并读取确认信号
C_SCL;
SDA_IN;
HWDELAY;

S_SCL;
if(!READ_SDA){
HWDELAY;
return 0;
}
else{
HWDELAY;
return 1;
}

}


extern void TWISendByte(TWIDATA SentByte){ //发送一个字节
TWIDATA bit_count = 8;
SDA_OUT;

while(bit_count--){
if(SentByte & 0x80){
C_SCL; //先拉低SCL
S_SDA; //1则置高
}
else{
C_SCL; //先拉低SCL
C_SDA; //0则置低
}
SentByte <<= 1; //前移一位
HWDELAY;

S_SCL;
HWDELAY;
}
}


extern void TWIWriteByte(DADDR DevAddr, RADDR RegAddr, TWIDATA *Data){
if(TWIStart() != 0){
TWISendByte(DevAddr);
MTWACK;
// addrAckRead = TWIMWaitAck(); //测试时使用

TWISendByte(RegAddr>>8); //采用16bit寻址空间,先发送高8位地址,在发送低8位地址
MTWACK;
// regAckRead = TWIMWaitAck();

TWISendByte(RegAddr);
MTWACK;
// regAckRead = TWIMWaitAck();

TWISendByte(*(Data+0));
MTWACK;
// dataAckRead = TWIMWaitAck();
}
TWIStop();
}


extern void TWIWriteByteContinuly(DADDR DevAddr, RADDR RegAddr, int length, TWIDATA *Data){ //连续发送数据,相同设备无暂停
TWIStart();

TWISendByte(DevAddr);
MTWACK;

TWISendByte(RegAddr>>8); //采用16bit寻址空间,先发送高8位地址,在发送低8位地址
MTWACK;

TWISendByte(RegAddr);
MTWACK;

for(int i = 0;i <length; i++){
TWISendByte(*(Data+i));
MTWACK;
}

TWIStop();
}


extern DADDR TWIDAddrTest(void){ // 设备地址测试验证
DADDR MAXADDR = 0x7F; //设置最大地址为0x7F,此处仅测试7bit设备地址,10bit不测试
DADDR tempAddress = 0x00; //设置设备最小值

for(; tempAddress <= MAXADDR; tempAddress++) //设置for循环
{
TWISendByte((TWIDATA) tempAddress);//发送设备地址
if(TWIMWaitAck()) return tempAddress; //等待应答,如果应答,则返回正确的设备地址
}
return 0xFF; //否则(运行结束时)返回0xFF。
}


extern void TWISCL(void){ //Only used in Test mode
C_SCL;
S_SDA;
HWDELAY;
S_SCL;
C_SDA;
HWDELAY;
}



// ------------------------------------------------------------------------------------
// 以下函数未使用。
extern void MRSendAck(void){ //Only in Master-receiver mode, 发送确认信号给slave,仅在主-收模式时使用
SDA_OUT;

C_SDA;
HWDELAY;

S_SCL;
HWDELAY;

C_SCL;

SDA_IN;
}


extern TWIDATA TWI_read_byte(DADDR DevAddr, RADDR RegAddr){
static TWIDATA RetVal = 0;

TWIStart();

TWISendByte( DevAddr ); //发送设备地址和W标志位,等待确认,据Figure87
MTWACK;

TWISendByte(RegAddr>>8); //发送MSB寄存器地址
MTWACK;
TWISendByte(RegAddr); //放LSB寄存器地址
MTWACK;

TWIReStart(); //重启动并发送设备地址

TWISendByte( DevAddr+1 ); //发送设备地址和R标志位,等待确认
MTWACK;

RetVal = TWIReceiveByte();
SendWithoutAck();

TWIStop();

return RetVal; //返回目标寄存器的值
}


extern void TWI_write_2_byte(DADDR DevAddr, RADDR RegAddr, TWIDATA Data1, TWIDATA Data2){
if (TWIStart() != 0){
TWISendByte(DevAddr); //1、发送设备地址
MTWACK;
TWISendByte(RegAddr); //2、发送寄存器地址
MTWACK;
TWISendByte(Data1); //3、发送寄存器数据 for PORT 0
MTWACK;
TWISendByte(Data2); //4、发送下一个寄存器数据 for PORT 1
MTWACK
}
TWIStop();

}


extern TWIDATA TWIReceiveByte(void){ //作为主机,读取从机字节
TWIDATA Byte_count = 8;
TWIDATA RetVal = 0;

C_SCL;
HWDELAY;

SDA_IN;

while (Byte_count--){
S_SCL;
RetVal <<= 1;
if (READ_SDA){
RetVal = RetVal | 1;
}
HWDELAY;
C_SCL;
HWDELAY;
}

return RetVal;
}

extern DADDR TWIDevAddrTest(char addressBit){
switch(addressBit){
case (7):
for(targetAddr = 0x00 ; targetAddr < 0x7F; targetAddr++){
TWIStart();
TWISendByte(targetAddr<<1); //尝试过target和target<<1都不行
if(!TWIMWaitAck() ) {
TWIStop();
return targetAddr;
}
else TWIStop();
}
break;
case (10): //此处需要自定义
break;
default:
return (0x00);
}
return (0x00);
}

TWI.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
* TWI.H
*
* Created on: 2021年6月16日
* Author: 431240
*/

#pragma once

#include <sru21479.h>
#include <def21479.h>
#include <cdef21479.h>
#include <signal.h>
#include <21479.h>
//#include "Common.h"
#include "ADDS_21479_EzKit.h"

/*-----------------------------------------------------------------------------------
* 调试用宏定义
-----------------------------------------------------------------------------------*/

#define NACK 0 //不用从机确认
#define ACK 1 //需要从机进行确认

//#define HWDELAY_CORETIMER
#define HWDELAY_SYSWAITNOP

//#define EVA_21479 1 //如果是评估板


/*-----------------------------------------------------------------------------------
* 数据类型定义
-----------------------------------------------------------------------------------*/
typedef unsigned int DADDR; //设备地址数据类型定义
typedef unsigned int RADDR; //寄存器地址数据类型定义
typedef unsigned char TWIDATA; //数据发送的数据类型定义

// ------------------------------------------------------------------------------------
// 引脚定义:RST 定义为 DPI_12 , SDA 定义为 DPI_11 , SCL 定义为 DPI_13
// 在试制板中,两块codec均与21479的第7、8引脚连接,DPI_07定义为SDA, DPI_08定义为SCL

#define RST_OUT {SRU(HIGH, DPI_PBEN12_I); } //设置复位信号输出方向
#define S_RST {SRU(LOW, DPI_PB12_I); } //拉低电平以设置复位信号
#define C_RST {SRU(HIGH, DPI_PB12_I);} //拉高电平以释放复位信号

#ifdef EVA_21479
#define SDA_OUT {SRU(HIGH, DPI_PBEN11_I); } //设置SDA输出方向
#define SDA_IN {SRU(LOW, DPI_PBEN11_I); } //设置SDA输入方向
#define C_SDA {SRU(LOW, DPI_PB11_I); } //拉低SDA以清除数据
#define S_SDA {SRU(HIGH, DPI_PB11_I); } //拉高SDA

#define SCL_OUT { SRU(HIGH, DPI_PBEN13_I); } //SCL设定为输出方向的定义
#define C_SCL {SRU(LOW, DPI_PB13_I); } //拉低SCL以清除时钟
#define S_SCL {SRU(HIGH, DPI_PB13_I); } //拉高SCL以设置时钟

#define READ_SDA (*pDPI_PIN_STAT & DPI_PB11) //从SDA读取数据
#define READ_SCL (*pDPI_PIN_STAT & DPI_PB13) //从SCL读取数据

#else //量产版
#define SDA_OUT {SRU(HIGH, DPI_PBEN07_I); } //设置SDA输出方向
#define SDA_IN {SRU(LOW, DPI_PBEN07_I); } //设置SDA输入方向
#define C_SDA {SRU(LOW, DPI_PB07_I); } //拉低SDA以清除数据
#define S_SDA {SRU(HIGH, DPI_PB07_I); } //拉高SDA

#define SCL_OUT { SRU(HIGH, DPI_PBEN08_I); } //SCL设定为输出方向的定义
#define C_SCL {SRU(LOW, DPI_PB08_I); } //拉低SCL以清除时钟
#define S_SCL {SRU(HIGH, DPI_PB08_I); } //拉高SCL以设置时钟

#define READ_SDA (*pDPI_PIN_STAT & DPI_PB07) //从SDA读取数据
#define READ_SCL (*pDPI_PIN_STAT & DPI_PB08) //从SCL读取数据
#endif

//提示:靠近功放的codec,其ADDR1为1电平,ADDR0为高电平,其I2C地址是0x3E

/*-----------------------------------------------------------------------------------
* 数据宏定义
-----------------------------------------------------------------------------------*/
#define MINDT 1 //定义延迟最小时间 Minimal delay time

#ifdef HWDELAY_SYSWAITNOP
#define HW_SYSWAITNOP_TIME 100 //定义使用SYSWAITNOP的延迟时间,默认90
#endif

/*-----------------------------------------------------------------------------------
* 函数声明
-----------------------------------------------------------------------------------*/
extern void TWITimerIsr(int); //用于产生中断高/低电平延时的定时器中断(句柄)
extern void HWDelay(float); // 定义硬件延时(单位微秒)

extern void TWIInit(void); //初始化函数

extern int TWIStart(void); //启动函数
extern void TWIReStart(void); //重启动函数
extern void TWIStop(void); //停止

extern void SendWithoutAck(void); //发送无需确认
extern TWIDATA TWIMWaitAck(void); //等待从机确认并读取确认信号
extern void TWISendByte(TWIDATA); //发送一个字节


extern void TWIWriteByte(DADDR, RADDR, TWIDATA*); //单次发送数据
extern void TWIWriteByteContinuly(DADDR, RADDR , int, TWIDATA *); //连续发送数据,相同设备无暂停
extern DADDR TWIDAddrTest(void); // 设备地址测试验证
extern void TWISCL(void); //Only used in Test mode

// 以下函数未使用。
extern void MRSendAck(void); //Only in Master-receiver mode, 发送确认信号给slave,仅在主-收模式时使用
extern TWIDATA TWI_read_byte(DADDR, RADDR);
extern void TWI_write_2_byte(DADDR, RADDR, TWIDATA, TWIDATA);
extern TWIDATA TWIReceiveByte(void); //作为主机,读取从机字节

extern DADDR TWIDevAddrTest(char addressBit); // 从机地址遍历测试

SigmaStudioFW.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

// Modified by Liewzheng in June 9th, 2021

#ifndef __SIGMASTUDIOFW_H__
#define __SIGMASTUDIOFW_H__


/*-----------------------------------------------------------------------------------
* 头文件
-----------------------------------------------------------------------------------*/
#include "TWI.h"
#include "ADDS_21479_EzKit.h"

/*-----------------------------------------------------------------------------------
* 数据类型定义
-----------------------------------------------------------------------------------*/
typedef unsigned short ADI_DATA_U16;
typedef unsigned char ADI_REG_TYPE;


/*-----------------------------------------------------------------------------------
* 宏定义
-----------------------------------------------------------------------------------*/
#define SIGMASTUDIOTYPE_FIXPOINT 0
#define SIGMASTUDIOTYPE_INTEGER 1

// 设置时钟分频,有且仅有一个能被启用
//#define CLK_DIV_1
//#define CLK_DIV_2
#define CLK_DIV_3
//#define CLK_DIV_4

unsigned char testPLL[5] = {0, 0, 0, 0, 0};

/*-----------------------------------------------------------------------------------
* 宏定义——函数声明
-----------------------------------------------------------------------------------*/
#define SIGMA_WRITE_REGISTER( devAddress, address, dataLength, data ) {/*TODO: implement macro or define as function*/}

#define SIGMA_READ_REGISTER( devAddress, address, length, pData ) {}

#define SIGMA_SET_REGSITER_FIELD( regVal, fieldVal, fieldMask, fieldShift ) \
{ (regVal) = (((regVal) & (~(fieldMask))) | (((fieldVal) << (fieldShift)) && (fieldMask))) }

#define SIGMA_GET_REGSITER_FIELD( regVal, fieldMask, fieldShift ) \
{ ((regVal) & (fieldMask)) >> (fieldShift) }

#define SIGMASTUDIOTYPE_FIXPOINT_CONVERT( _value ) {}

#define SIGMASTUDIOTYPE_INTEGER_CONVERT( _value ) {}


/*-----------------------------------------------------------------------------------
* 函数定义
-----------------------------------------------------------------------------------*/
// modified by liewzheng, in June 15th, 2021
void SIGMA_WRITE_REGISTER_BLOCK( unsigned int devAddress,
unsigned int address,
unsigned char length,
ADI_REG_TYPE *pData ) {
if(length == 1){
TWIWriteByte(devAddress, address, pData);

}
else if(length > 1){
TWIWriteByteContinuly(devAddress, (address), length, pData);
}
else{
;
}

SysWaitNOP(100); //可以注释掉
}


// modified by liewzheng, in June 15th, 2021
void SIGMA_WRITE_DELAY( unsigned int devAddress,
unsigned char length,
unsigned char *pData ) {
SysWaitNOP(10000000);
SysWaitNOP(10000000);
}


#endif

参考

  1. 《NXP_UM10204_I2C-bus specification and user manual》