这个之前是在word编辑的,复制过来时图片无法显示,需要详细的请下载附件。 3.2IIC协议 3.2.1IIC总线简介 IIC总线是PHILIPS公司推出的一种串口总线,是具备多主机系统所需的包括总线裁诀和高低速器件同步功能的高性能串口总线。 IIC总线是通过 串行数据(SDA)线和 串行时钟 (SCL)线在连接到总线的器件间传递信息。每个器件都有一个 唯一的地址识别(无论是微控制器——MCU、存储器等),而且每个设备都可以作为一个发送器或接收器(由器件的功能决定)。比较重要的概念是 主机和 从机的概念:主机是初始化总线的数据传输并产生允许传输的时钟信号的器件(一般为MCU,或者 FPGA)。此时,任何被寻址的器件都被认为是从机。
下图阐述IIC的硬件结构图:
IIC总线有三种数据传输速度:标准,快速模式和高速模式。标准的是100Kbps,快速模式为400Kbps,高速模式支持快至3.4Mbps的速度。这里使用的是100kps,PCF8563支持快速模式。 IIC总线设备地址的寻址方式有7位和10位,PCF8563使用的是7位。 7位寻址方式:
上图为PCF8563的地址,可以看到,(这里的A0=1)最低位是读写的命令,通知设备主机下次发的数据是读还是写。
MSB和LSB: a. MSB 数据和地址最高位在前,比如上图最左边一位就是最高位,PCF8563使用的是MSB b. LSB数据和地址最低位在前。 3.2.2位时序和实现 主机和从机在 iic总线上通信的时候, 它们之间有用类似 “ hello” ( 起始信号)和 “ Goodbye”(结束信号)来同步每次的通信。如下图,当某个主机要对从机访问之前,
主机必须先在 iic 总线上广播(即发送)“ 起始信号”,以便表示主机正在开始发送或者地址,当总线上的从机接收到“起始信号”,从机便就绪好,看看主机即将跟哪个设备通信(通过地址)。
从机匹配地址后,需要产生一个特殊的信号来告诉主机已经有设备应答,主机可以开始发送数据了。这个就是应答信号,在scl 上升沿前拉低,下降沿后拉高表示应答。
有了通信数据同步,接下来就是读写的顺序了: 写一个字节的时序: (一) 首先主机产生一个“ 起始信号”; (二) 然后主机产生“ 写一个字节步骤”,字节的内容是设备地址; (三) 接着主机等待从机“ 应答位”; (四) 随后主机产生“ 写一字节步骤”,字节的内容是字地址(寄存器地址); (五) 然后主机等待从机“ 应答位步骤”; (六) 跟着就是主机产生“ 写一字节步骤”,字节内容是写入信息; (七) 随之主机等待从机“ 应答位步骤”; (八) 最后主机产生“ 结束位步骤”
读一个字节的时序:
(一) 首先主机产生一个“ 起始信号”; (二) 然后主机产生“ 写一个字节步骤”,字节的内容是设备地址; (三) 接着主机等待从机“ 应答位”; (四) 随后主机产生“ 写一字节步骤”,字节的内容是字地址(寄存器地址); (五) 然后主机等待从机“ 应答位步骤”; (六) 跟着就是主机产生“ 起始信号”,通知设备获得总线的控制权; (七) 随之主机产生“ 写一字节步骤”,字节的内容是设备地址; (八) 再接着主机产生“读一字节步骤”; (九) 主机产生一个“非应答信号”; (十) 最后主机产生一个“停止信号”;
3.2.2.2代码实现 关于IIC的通信时序,许多参考代码使用的方法都是直接对系统时钟分频产生SCL, 随后又把一个SCL周期分成 4个关键部分:a.SCL信号上升沿后不久; b.是 SCL信号保持在高电平最中间(最稳定)的时候; c.是SCL信号下降沿后不久; d.是 SCL信号处于低电平。这个方法很容易,但是写法的伸缩信不好而且扩展性又有限,这里参考了《Verilog 那些事-整合篇》,把时序信息整合到状态机里面,使用了单进程状态机。通过使能信号和完成信号来实现握手通信: 下面是IIC通信模块的RTL图,start_sig[1..0]负责选择读写使能,done_flag在读写完成后输出一个与clk信号同步的高脉冲,在顶层里状态机,只要使能了读写,然后等待done_flag的高脉冲即可进行下一个状态:
顶层状态机握手通信示例:
when x"6"=>--读示例 if(IIc_done_flag= '1') then -- Years_port<= rdata;--读取数据 IIC_rd_wr_start <="00";--关闭使能读取数据 i<=i+1;--下一个 else IIC_rd_wr_start <="10";--使能读取数据 raddr<= x"08";--字地址 end if; 起始信号:
在起始信号状态下(10us),拉高scl 整个周期,前半个周期拉高SDA,后半个周期拉低SDA。在读时序中有个重新起始位,实现方式大体一致但是要求严格点,会决定最后时序的成功与否,故在scl和sda拉高之前保证全是低,仅是为了保险作用。 代码实现: whenx"00" => sda_link<='1';--输出 rscl<='1'; if(div_cnt = 10#0#) then rsda <='1'; elsif(div_cnt = freq_2) thenrsda<='0';--5us后拉低 end if; if(div_cnt = freq-1) then --10us后切换下一个状态 div_cnt <= (others =>'0'); i<=i+1; else div_cnt<=div_cnt+1; end if;
停止信号及代码实现:
代码实现和起始信号同样的想法:在起始信号状态下(10us),后7.5个拉高scl 整个周期,前半个周期拉低SDA,后半个周期拉高SDA。前面2.5us是为了保险作用。
主机和从机应答位及代码实现:
在时钟高电平期间判断sda的输入0:应答,1:非应答,硬件上SCL和SDA的上拉,如若总线上没有设备,或者没有主机的点名要的设备,这时检测到的就是高电平。 主机检测应答位代码: whenx"11"=>-- wai ting for acknowledge sda_link<='0'; if(div_cnt =freq_3) then isAck <= sda; end if;--读入应答位 if(div_cnt = 10#0#) then rscl <='0'; elsif div_cnt = freq_2 then rscl<='1'; end if; if(div_cnt = freq -1) then div_cnt<=(others =>'0'); i<=i+1; else div_cnt <= div_cnt+1; end if;
写一个字节:
拉低scl的同时改变数据,拉高scl时设备会读入数据。 代码实现: whenx"09"|x"0a"|x"0b"|x"0c"|x"0d"|x"0e"|x"0f"|x"10"=> sda_link<='1'; rsda <= data_buf(n); if(div_cnt = 10#0#) then rscl <='0'; elsif div_cnt = freq_2 then rscl<='1'; end if; if(div_cnt = freq-1) then div_cnt<=(others=>'0'); i<=i+1; n:=n-1; else div_cnt<=div_cnt+1; end if;
读一个字节:
代码实现和写字节的方式一致。 3.2.3IIC协议总结: 有了起始位,写字节,读字节,应答位,停止位,重新起始位,我们就可以使用单进程状态机进行任意的拼合。只要照着datasheet给出的步骤来拼合就可以读取到任何IIC器件的数据。经实际验证已经成功读写PCF8563寄存器的内容。
|