本章继上节iic通信协议,在理论学习之后,找到一块iic接口的片子——PCF8591,它是一款AD-DA集成芯片。所以本节对iic通信协议不做过多的介绍,重心放在iic的rtl建模,本次通过iic控制PCF8591实现DAC输出功能。
PART1:建模前的准备
包括两部分,一是芯片手册阅读,二是建模思路;
一、阅读PCF8591芯片手册,获得以下信息:
>PCF8591的指令控制顺序:主机先向其发送地址指令(包括sda方向选择指令);然后是控制字指令;最后是数据指令(dac对应8bit的dac二进制数据,adc对应为sda方向操作控制指令)
>PCF8591具有adc和dac两种功能,选择哪一种功能需要通过指令(2、3指令功能决定)进行确定。
>模块硬件地址应确定,为000(A2A1A0)。
>具体的寄存器配置(指令)就不详细介绍,总结如下:
adc功能配置,先后发送0x90、0x40+channel(取值0、1、2、3,总共有四个通道)、0x91即可;
dac功能配置,先后发送0x90、0x40、user_data(8bit)即可;
>要注意iic通信速率及相关时间的限制,总结如下:
DAC模式输出频率Fmax=11.1KHz;
Fsclk_max=100kHz;(这就对sclk的高低电平持续时间有要求);
开始、结束、数据的建立保持时间(如下表);
【相关参数表格】
【iic时序示意图】
总结一下:
建立时间和保持时间大都与4.7us有关,且Fsclk_max=100K,即Tmin=10us,取5us作为标准,保持时间和建立时间都为5us;
输出信号频率有全范围输出的时间间隔决定(1111_1111à0000_0000),由数据手册知,Tscale=90us,则fout_max=11.1KHz。
二、建模思路
意思就是RTL代码应该怎么设计,包括哪些模块,各模块之间该怎么连接,有哪些关键的信号,等等,此类的都属于建模思路范畴。
对于iic通信的建模。此前没有写过iic的通信协议,所以现在的思路,只能给一个大致的思路(整体框架),然后希望方向不要错,之后在具体设计实现的过程中不断完善,逐步实现既定功能。
具体的思路如下:
首先考虑实现状态机作为整体框架实现通信协议的主脉络;然后要确定状态机的状态数,各个状态之间是如何切换的(一般采用单向转移方式),以及转换的条件是啥;最后就是要明确不同的状态状态机系统的输出是啥(注意,输出可能并不只有一个变量)。
NOTE:目前我喜欢用三段式的FSM的书写方式,优点是比较容易理解,且实现起来格式比较固定。在本次设计中,每一段的功能描述如下:
段1:采用组合逻辑罗列个状态转移的条件和转移规律;
段2:采用同步always块描述状态转移,以减小毛刺,提高稳定性;
段3:状态机的输出描述,也是状态机灵魂之所在,相对前两段,这一段的设计比较灵活,不同的功能,建模代码差异很大。通常这里的一段并不是狭义的1段代码(1个always块),而是指很多always块的有机集合,他们共同支撑起状态机的实体功能(真正看得到的目标功能)。
故而称为3段式状态机。
简要说明一下:本状态机设定4个状态,包括空闲态、起始态、停止态和工作态,iic协议对应的sda和sclk输出部分就在这一部分中描述,故该状态的输出设计是关键。大致整个建模的思路就是这样。
以上,就是建模前的准备工作。
PART2:第一次建模(失败)
如下,是第一次建模的仿真波形,整体上sclk和sda的时序都是满足起始和停止条件的,但下载到FPGA中并没有正确的结果。
仿真波形(第一次):
所以究竟是什么原因呢?于是我用signaltap对板子上的信号进行波形抓取,确实,在signaltap中确实抓到了波形,且与仿真的波形基本一致。这时,我一度认为是芯片坏了(寻求一下自我安慰,实际上这个模块是新买的移植没有使用过)。还是不甘心,于是我下老本了,找到一台示波器,我要亲自看看这个数据波形到底是不是真的有(提醒一下大家,signaltap上抓取的波形基本上就是实际用示波器探测到的波形,所以大家不用怀疑signaltap欺骗了你,大家也不用向我这样真的去用示波器看波形)。以下给出示波器波形:
>sda波形:
>sclk波形:
就在这万般无奈之际,闹钟突然闪过一条信息——iic的速度不会非常快。这才意识到,我最开始sclk的频率取得是clk(50MHz)的4分频,也就是接近12.5MHz,而从手册上看到Fsclk_max=100KHz。于是,欣喜地修改了分频系数,本以为可以成功输出目标电压值,然后事实是并没有。不幸,Round1失败。
PART3:建模修改
第二天,并没有花时间去修改代码,而是在思考原因何在?在没有对策之后,我决定重新阅读英文版芯片手册,主要看其通信协议及其注意事项,这才有了本文最开始的图和表,也就引出一个很重要的概念——信号的建立时间和保持时间。
所以,我是走了弯路的,这里希望大家引以为戒。我犯了一个错误,一开始值看了中文版的芯片手册,而里面并没有给出相关的通信协议的注意事项(因为中文版的资料是出自他人之手的加工品,它不可能像原版手册那么全面地介绍了芯片使用的方方面面),所以,大家以后还是尽量阅读英文原版手册(或者至少出问题时请及时去参看原文手册)。
以上是建议,此外,就是建立时间和保持时间,这个概念真的非常重要,是任何一个数字设计者必须熟悉的概念。所以,在进行通信协议建模或者数字设计时,心中时刻要有这两个的概念。此后,我也将继续学习相关知识,可能的话建立相关专题,为大家分享的我的学习经历。
回归主题,意识到问题所在,对照最开始的建模代码,不满足时序要求的最主要的两点:一是start的相关时序;二是stop的相关时序。主要就是时间过短,最终的解决方法是折中取5us左右作为相关时序的持续时间。对症下药之后,一遍出电压,成功驱动。以下给出dac=1100_1111时的电压输出值,V_the=4.0V左右(基准4.95V),实际输出V_act=3.95V。这样的误差在正常范围内。
仿真波形如下:(dac_reg=1111_1111)
SignalTap_ii中的波形:
其中sda线上在第8bit数据输出后会出现一个小的脉冲,这是与仿真波形不一致的地方,但这并没有影响最终的输出。(因为sclk为1时数据时有效的)
以上波形对照iic时序示意图,起始和停止的建立时间和保持时间均满足极限要求。
NOTE:特此提出本次所用模块为某宝购买,按照上述设计,实际输出应该是接近Vdd(5V),但实际输出只有4.2V左右,原因是,模块的Aout外接了负载(指示灯),故按照芯片手册所说,当输出负载为10K欧时,Vaout_max为0.9Vdd。
模块Aout实际连接的负载:(led串接1K欧负载,两端5V电压时led的等效电阻大概是500欧姆左右,故该模块Aout实际负载比10K欧还要重,因此输出最大值达不到0.9Vdd=4.5V,只有0.84Vdd),特此说明,以免大家误会。
最后,给出整个工程的RTL视图:
总结一下:
<1>iic协议是一种半双工通信协议,sda是双向接口。用verilog设计,可以采用以下方式:定义reg变量表示sda的输出方向,若是输入则定义为高组态,输入由外部连接所决定;正常时作输出即可。
reg sda_out; //sda输出寄存器
reg io_link; //sda方向寄存器,1表示输出(dac),0表示输入(adc)
assign sda = io_link ? sda_out : 1'bz; //sda作输入时,将其设置为高阻状态,则sda的实际状态由实际线上的状态决定
<2>使用状态机实现,考虑好是单个状态机实现还是使用嵌套,不是说越多就越高大上,功能稳定是关键;
<3>数字设计特别是涉及到时序相关的,心中一定时刻要有建立时间和保持时间的概念。
以上就是运用PCF8951学习iic通信协议的相关总结。
本设计的相关代码链接:
https://download.csdn.net/download/huigeyu/11263770