电子大神的日记本,供应链专家的功夫茶盘,在这里记录、分享与共鸣。

登录以开始

fpga学习日记24,代码阅读之实现IIC

(注:如果本文有代码 则均搜索于网络或本人编写仅供学习交流之用)

本文主要学习iic读写24cxx的编程方法和思想

本文建立在前面五篇日记的基础上

fpga学习日记14,实现UART发送

fpga学习日记15,实现IIC通信

fpga学习日记20,状态机 任务和并行设计思想

fpga学习日记22,代码阅读之实现UART

fpga学习日记23,代码阅读之UART时序分析

 

因为iic在通信程序设计上和uart有很大程度的类似

在日记15中已经详细描述了IIC的通信规范和协议以及24CXX的操作知识

这里再回顾下像这种串行通信的设计思路

  1. 首先还是时钟问题  通信时钟和数据收发辅助时钟
  2. 程序结构问题       状态机,并行执行和任务
  3. 收发器状态位的问题   忙检测 出错标志 收发标志位等

 

下面依次分析以上三个部分

1首先还是时钟问题

为了在SCL低电平的中点改变SDA上的数据 我们需要将IIC的时钟2倍或4倍频下以获取辅助时钟来监测SCL的中点

同时辅助时钟还可以在接受中判断scl高电平中点时刻来获取sda线上的数据 这点和uart的思想是一样的

先看下2倍频的例子

verilog语言: 高亮代码由发芽网提供

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倍频的例子

verilog语言: 高亮代码由发芽网提供

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的源代码供讨论学习

 

 

博主
liang890319@163.com
专注的力量
专注于嵌入式硬件开发。
点击跳转