(注:如果本文有代码 则均搜索于网络或本人编写仅供学习交流之用)
本文主要学习iic读写24cxx的编程方法和思想
本文建立在前面五篇日记的基础上
fpga学习日记14,实现UART发送
fpga学习日记15,实现IIC通信
fpga学习日记20,状态机 任务和并行设计思想
fpga学习日记22,代码阅读之实现UART
fpga学习日记23,代码阅读之UART时序分析
因为iic在通信程序设计上和uart有很大程度的类似
在日记15中已经详细描述了IIC的通信规范和协议以及24CXX的操作知识
这里再回顾下像这种串行通信的设计思路
-
首先还是时钟问题 通信时钟和数据收发辅助时钟
-
程序结构问题 状态机,并行执行和任务
-
收发器状态位的问题 忙检测 出错标志 收发标志位等
下面依次分析以上三个部分
1首先还是时钟问题
为了在SCL低电平的中点改变SDA上的数据 我们需要将IIC的时钟2倍或4倍频下以获取辅助时钟来监测SCL的中点
同时辅助时钟还可以在接受中判断scl高电平中点时刻来获取sda线上的数据 这点和uart的思想是一样的
先看下2倍频的例子
always @ (posedge clk or negedge rst_n)
if(!rst_n) cnt_delay <= 9'd0;
else if(cnt_delay == 9'd499) cnt_delay <= 9'd0; //计数到10us为scl的周期,即100KHz
else cnt_delay <= cnt_delay+1'b1; //时钟计数
always @ (posedge clk or negedge rst_n) begin //将IIC时钟分成四份-2倍频以产生辅助时钟
if(!rst_n) cnt <= 3'd5;
else begin
case (cnt_delay)
9'd124: cnt <= 3'd1; //cnt=1:scl高电平中间,用于数据采样
9'd249: cnt <= 3'd2; //cnt=2:scl下降沿
9'd374: cnt <= 3'd3; //cnt=3:scl低电平中间,用于数据变化
9'd499: cnt <= 3'd0; //cnt=0:scl上升沿
default: cnt <= 3'd5;
endcase
end
end
下面是4倍频的例子
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n ==1'b0)
clk_50k <= 10'b0;
else if ((counter_div >= 375) && (counter_div < 875))
clk_50k <= 10'b1;
else
clk_50k <= 10'b0;
end
// gen a 200K CLK for work counter count
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n ==1'b0)
clk_200k <= 10'b0;
else if ((counter_div >= 0 ) && (counter_div < 125))
clk_200k <= 10'b0;
else if ((counter_div >= 125) && (counter_div < 250))
clk_200k <= 10'b1;
else if ((counter_div >= 250) && (counter_div < 375))
clk_200k <= 10'b0;
else if ((counter_div >= 375) && (counter_div < 500))
clk_200k <= 10'b1;
else if ((counter_div >= 500) && (counter_div < 625))
clk_200k <= 10'b0;
else if ((counter_div >= 625) && (counter_div < 750))
clk_200k <= 10'b1;
else if ((counter_div >= 750) && (counter_div < 875))
clk_200k <= 10'b0;
else if ((counter_div >= 875) && (counter_div < 1000))
clk_200k <= 10'b1;
else ;
end
2程序结构问题(在此基础上在状态机中添加读数据即可)
Module eeprom_w
//输入输出定义
//变量定义
//参数定义
Assign SDA=(out_***==1)? Sda_buf[7] :1’bz; //控制IIC数据线接收发送
Initial
Begin
//参数初始化
End
Always @(negedge sys_clk) //为iic产生时钟
If(复位)
Scl<=0;
Else
Scl=~scl;
Always @(scl下降沿)
如果复位 初始化某些参数
否则begin
Casex(main_state) //状态机
状态1 空闲 准备跳入下一个状态
状态2 发开始信号 (调用任务) 准备跳入下一个状态
状态3 发设备地址(调用任务) 准备跳入下一个状态
状态4 发数据地址(调用任务) 准备跳入下一个状态
状态5 发数据(调用任务) 准备跳入下一个状态
状态6 发停止信号 (调用任务) 准备跳入下一个状态
状态7 发ACK应答信号(调用任务)准备跳入下一个状态
Default:
Endcase
End
任务1 发送IIC起始信号(代码实现)
任务2 将8位数据发到IIC总线(代码实现)
任务3 从IIC总线上读取8位数据 (代码实现)
任务4 发送IIC结束信号(代码实现)
任务5 发送IIC ack应答信号(代码实现)
Endmodule
也可以直接把任务写在状态机中
3收发器状态位的问题
有时我们还需设计一些状态标志位来表示iic的通信状态
状态标志:
─ 发送器/ 接收器模式标志
─ 字节发送结束标志
─ IiC 总线忙标志
错误标志
─ 主模式时的仲裁丢失
─ 地址/ 数据传输后的应答(ACK) 错误
─ 检测到错位的起始或停止条件
─ 禁止拉长时钟功能时的上溢或下溢
中断
─ 1个中断用于地址/ 数据通讯成功
─ 1个中断用于错误
最后附上两个读写eeprom的源代码供讨论学习
用户1418083 2016-3-23 09:57
用户442006 2015-5-6 16:46