正点原子 eeprom FPGA(verilog)编程

简介: 出于积累Verilog编程和调试经验的需求,用这个东西练手使用的是正点原子领航者zynq7020的板子本人博客,csdn搬运

eeprom

出于积累Verilog编程和调试经验的需求,用这个东西练手
使用的是正点原子领航者zynq的板子 实验环境过于优越了 导致进度慢就会很自责 ,程序一天出来了 。。 写个总结算是对得起我这两天的debug,
eeprom 用的是板子上自带的AT24C64

iic时序

先分析下这货的iic时序,后面再说这货的iic读写时序
这里提前说一下,虽然很多设备都叫iic但是iic和iic之间也是有区别的,所以在写时序的时候一般最好还是看一眼手册和你之前用的iic是不是一样的
在这里插入图片描述

简单说两句这个图,你生成的SCL时钟高电平的时候,SDA给一个下降沿,就会生成一个起始信号,起始信号之后进入输出传输bit,在你生成的时钟(SCL)下降沿之后,SDA的输出可以变化,也就是可以更改输出的数据,而时钟(SCL)上升沿之后,数据不论是高电平还是低电平,都应该是稳定的,在数据传输结束时,最后一个时钟的上升沿之后(SCL为高电平时)一个SDA的上升沿代表通讯结束
关于我这部分的描述,原手册都有对应英文,只不过分成了好几段,我截图在本节的最后了
至于这个图中的一堆带角标的t,就是一些通讯细节,规定得是设备能反应的一些极限速度,如果你操作的比它的快就会导致一些问题,但是一般低速操作不会有影响,这里附赠一个基础知识点
IIC通用频率也就400K ,不要上M ,否则有可能会出问题,当然 不同设备 ,极限情况就不好说了
在这里插入图片描述

原手册对这部分有更清晰的描述和附图,这里我就不一一翻译了,应该说明白了

AT24C64 通讯时序

写入时序

下图中Figure1 展示了设备的地址定义,Figure2 展示的是主机发送单byte写的时候的时序
在这里插入图片描述

英文描述我就不截图了,简单说下这个图的意思,你要往eeprom的一个地址写一byte的东西,那么就需要把以下的步骤重复一遍:
起始位==>发1byte(8bit)的设备地址==>再发2byte(16bit)的写入地址==>再发1byte的数据,结束位 ==>写入成功
这里有一个ACK ,在发每个byte结束的时候都会收到一个设备发回来的低电平ACK表示它收到了这个byte的数据
设备地址:高四位是1010 固定的 ,中间三位是用户可以根据设置芯片三个管脚电平控制的,这里原子都给接地了 所以我也只能给000,最后一位是读写位 如果是写就置0,读就置1
写地址:可以看到只有13位是有效的,带 的是任意的,因为AT24c64是64K的eeprom,存储空间只有64 1024个bit,也就是64 * 1024/8=8192个byte,写作二进制就是0010_0000_0000_0000,由于0位的存在,所以表示这么多的数据位的地址最多也就到第13位,所以最高的三位是无效的
MSB:数据的高位
LSB:数据的低位
了解到MSB和LSB可以确定数据时序先发高位,这个在不同的设备用有时候也会有些微的不同,有的手册不说明白,可能就一句话告诉你MSB First ,也就是先发高位
因为不想连片写,所以也没分析连续写入时序,也没差太多

这里涉及到了引脚SDA既有输入,又有输出,也就是说它是个inout 的管脚,当场查了一下这样的管教在Verilog里面怎么描述,因为你输出的话还好,输入的话怎么能让自己不占用,然后发现高阻态是可以直接赋值的,这样通过下面的一个操作,就可以比较方便的操作sda

inout引脚(sda)描述

因为in 的时候需要把引脚置为高阻态,out 的时候要assign到寄存器
所以有了如下的方式,设置一个使能输出位 sda_en

inout            iic_sda//定义一个inout
r_sdi_shift<={r_sdi_shift[:],io_sdio}; //先接收到的为高位读取数据拼合成一个data 
assign iic_sda = sda_en ? 1'bz : iic_sdo_reg; //输出

读取时序

读取的时序如下,解读方式和写入相似,简单说一下
它一共支持三种读法,和连片写一样,我也没做解析连片读,只说下面两个Fig
Fig4 读取当前地址,这种应该是上次写入时候写的那个地址,直接就可以返回当前的地址数据,这种时候只需要写入设备地址2'b10100001,然后收到一个低电平ACK ,之后收到1Byte的数据。发送结束后有一位的缓冲但是它不会给你ACK,然后等你发Stop。
Fig5 说的是读一个随机地址,我只实现了这个
在这样的读取的时候顺序如下:
起始位==>1Byte地址(1010_0000)==> ACK==>2Byte地址 都有ACK==> 起始位==>1Byte地址(1010_0001)==>ACK ==>读取1Byte数据,没有ACK==>STOP 停止位

其中
起始位==>1Byte地址(1010_0000)==> ACK==>2Byte地址 都有ACK
这几个步骤叫做虚写 (dummy write) 最开始看漏了这个 主要看漏了中间的一次写设备地址,导致一开始通讯不正常。
在这里插入图片描述

这样就读到了1byte的数据

实现思路

还没有学习状态机的N种写法,所以写的可能稚嫩一些,简单说下思路,如果有感兴趣的可以移步下载,让我赚个零花钱
工程文件有三个:
tb_iic.v (仿真文件)
rw_test.v (定层文件)
e2prom.v (底层实现文件)

一共分了5个状态,IDLE,START,DWR,ACK,STOP
这里读写放到了一个状态下,所以导致有的读时序很混乱,主要是一开始读手册没看懂读时序是先进行虚写,后面又重发了一次device 设备地址,如果看懂了把读写分开应该逻辑会更清晰

下面是状态机,具体的思路就是状态之间的转换,先说写时序的生成状态:
在clk检测,当SCL 的上升沿触发状态修改操作。
首先进入IDLE待机状态,当en使能时候从IDLE出去进入START时序,在其他always块中实现sda的从低到高
当进入start后,en如果还没有置低,进入下一状态 DWR,在此生成8byte的读写时序,这里只考虑了写任意地址,也就是发送4bytes。此时在sda生成块中对对应的计数情况进行反应,发送指定的bit位
每次发送完8bit之后进入1bit的ACK位,这时在对应位置将sda的状态置为高阻,让他能发出来ack。
在ACK状态下对不同位数的写进行反应,当写了4bytes时进入下一状态,STOP,然后在上升沿之后生成一个scl高电平下的sda的从低到高,至此,一次写操作结束

读操作也差不多,区别在于进入了两次start ,所以多了一个read_cnt用于记录是否进行dummy write


/*状态机 状态从0-4 暂时不支持连续写 只支持按照地址写 一次发
每次写传输都有:
起始位
一个字节的设备地址 Ack 等待 1010000 0 
两个字节的byte地址 每个字节都有Ack 等待 写入地址 0000 0000 0000 0010
一个字节的写入数据 Ack 等待 写入数据 0000 1000 
停止位 
*/
always @ (posedge clk or negedge rst) begin
    if (!rst||!en) begin 
        next_state <= IDLE;
        byte_cnt <=5'd0;
        bit_cnt <=5'd0;  
        read_cnt<=3'd0;   
    end
    else if(next_state ==IDLE && en && iic_posedge  )
        next_state <= START ;
    else if(next_state ==START  && iic_nedge ) begin
        next_state <= DWR  ;
        byte_cnt <=8'd0;
        bit_cnt <=5'd0;
    end 
    else if(next_state ==DWR  && iic_nedge && bit_cnt < 7 ) begin
        next_state <= DWR;
        bit_cnt <= bit_cnt + 8'd1;       
    end
    else if(next_state ==DWR  && iic_nedge && bit_cnt == 7 ) begin
        next_state <= ACK ;
        bit_cnt    <= 8'd0;
    end
    else if(next_state == ACK && iic_nedge && byte_cnt == 1 && ren&&read_cnt ==3'd1) begin
        next_state <= STOP;
        byte_cnt   <= 8'd0000;
        read_cnt   <= 3'd0;
    end
    else if(next_state == ACK && iic_nedge && byte_cnt < 3&& wen) begin
        next_state <= DWR;
        byte_cnt   <= byte_cnt+8'd1;
    end
    else if(next_state == ACK && iic_nedge && byte_cnt < 2&& ren) begin
        next_state <= DWR;
        byte_cnt   <= byte_cnt+8'd1;
    end
    else if(next_state == ACK && iic_nedge && byte_cnt == 2 && ren&&read_cnt ==3'd0) begin
        next_state <= IDLE;
        byte_cnt   <= 8'd0000;
        read_cnt   <= 3'd1;
    end
    else if(next_state == ACK && iic_nedge && byte_cnt == 3)
        next_state <= STOP;
    else if(next_state == STOP && iic_nedge)
        next_state <= IDLE;
end

iic时钟沿的检测时序

// en=0 没有使能或者复位时,不操作 否则缓存2位iic时钟,用于之后做 起始位 数据发送的判定信号
assign iic_po = iic_scl_d0 & (~iic_scl_d1);   
assign iic_ne = iic_scl_d1 & (~iic_scl_d0);  
always @ (posedge clk or negedge rst) begin
    if (!rst||!en) begin 
        iic_scl_d0 <= 1'b0;
        iic_scl_d1 <= 1'b0;          
    end
    else begin
        iic_scl_d0  <= iic_scl;                   
        iic_scl_d1  <= iic_scl_d0;
    end   
end

为了防止我对下降沿或者上升沿反应过快 ,还给他们做了个延时。。 应该0可以写的好一点,当时脑子不大好,就用了很多寄存器


//给 iic_posedge iic_nedge 写一个延长的时钟防止操作过快,垃圾设备反应不过来
//因为周期是500拍 所以不可能同时发生上下沿 
//所以写一起了
always @ (posedge clk or negedge rst) begin
    if(!rst||!en)
        delay_cnt  <= 11'd0;
    else if(po_delay||ne_delay&&delay_cnt<200)
        delay_cnt <= delay_cnt + 11'd1;
    else 
        delay_cnt <= 11'd0; 
end
//给 iic_posedge iic_nedge 写一个延长的时钟防止操作过快,垃圾设备反应不过来
always @ (posedge clk or negedge rst) begin
    if(!rst||!en) begin
        po_delay<=0;
        ne_delay<=0;
    end
    else if(iic_po) begin
        po_delay<=1;
    end
    else if(iic_ne) begin
        ne_delay<=1;
    end
    else if(po_delay == 1 && delay_cnt ==200)
        po_delay <=0;
    else if(ne_delay == 1 && delay_cnt ==200)
        ne_delay <=0; 
end
always @ (posedge clk or negedge rst) begin
    if(!rst||!en) begin
        iic_posedge<=0;
        iic_nedge<=0;
    end
    else if(po_delay == 1&& delay_cnt ==198) begin
        iic_posedge<=1;
    end
    else if(po_delay == 1&& delay_cnt ==199) begin
        iic_posedge<=0;
    end
    else if(ne_delay == 1&& delay_cnt ==198) begin
        iic_nedge<=1;
    end
    else if(ne_delay == 1&& delay_cnt ==199) begin
        iic_nedge<=0;
    end
end

另外在调试的时候发现,使用ila读取iic_sda数据时候会在我给他置位为高阻时读到FF,也就是全高,这是因为什么我不大确定,但是解决方法是给iic_sda 来一个读取缓存,如下,然后读取这个sda_in ,在子模块下例化ila,就可以看到正确的输入数据
另外,在写下面这个assign之后,ila直接读取sda的操作会报错,说是缺buf还是什么,解决方案同上 ,不要看sda,看sda_in.

assign sda_in = iic_sda;

工程文档上传到了csdn,如果有需要自取
https://download.csdn.net/download/qq_31764341/85425598

目录
相关文章
|
6月前
|
机器学习/深度学习 算法 异构计算
m基于FPGA的多通道FIR滤波器verilog实现,包含testbench测试文件
本文介绍了使用VIVADO 2019.2仿真的多通道FIR滤波器设计。展示了系统RTL结构图,并简述了FIR滤波器的基本理论,包括单通道和多通道的概念、常见结构及设计方法,如窗函数法、频率采样法、优化算法和机器学习方法。此外,还提供了Verilog核心程序代码,用于实现4通道滤波器模块,包含时钟、复位信号及输入输出接口的定义。
191 7
|
6月前
|
存储 算法 数据处理
FPGA:可编程逻辑设备的领军者及其在数字信号处理中的应用
FPGA:可编程逻辑设备的领军者及其在数字信号处理中的应用
107 2
|
6月前
|
存储 算法 测试技术
FPGA(现场可编程门阵列)技术概述及其应用实例
FPGA(现场可编程门阵列)技术概述及其应用实例
|
6月前
|
算法 异构计算
m基于FPGA的电子钟verilog实现,可设置闹钟,包含testbench测试文件
该文介绍了基于FPGA的电子钟设计,利用Vivado2019.2平台进行开发并展示测试结果。电子钟设计采用Verilog硬件描述语言,核心包括振荡器、分频器和计数器。时间显示为2个十进制格式,闹钟功能通过存储器和比较器实现,当当前时间等于设定时间时触发。文中给出了Verilog核心程序示例,展示了时钟信号、设置信号及输出的交互。
209 2
|
6月前
|
编解码 算法 异构计算
基于FPGA的NC图像质量评估verilog实现,包含testbench和MATLAB辅助验证程序
在Vivado 2019.2和Matlab 2022a中测试的图像质量评估算法展示了效果。该算法基于NC指标,衡量图像与原始图像的相似度,关注分辨率、色彩深度和失真。提供的Verilog代码段用于读取并比较两个BMP文件,计算NC值。
|
6月前
|
算法 异构计算
m基于FPGA的MPPT最大功率跟踪算法verilog实现,包含testbench
该内容包括三部分:1) 展示了Vivado 2019.2和Matlab中关于某种算法的仿真结果图像,可能与太阳能光伏系统的最大功率点跟踪(MPPT)相关。2) 简述了MPPT中的爬山法原理,通过调整光伏电池工作点以找到最大功率输出。3) 提供了一个Verilog程序模块`MPPT_test_tops`,用于测试MPPT算法,其中包含`UI_test`和`MPPT_module_U`两个子模块,处理光伏电流和电压信号。
70 1
|
4月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的MSK调制解调系统verilog开发,包含testbench,同步模块,高斯信道模拟模块,误码率统计模块
升级版FPGA MSK调制解调系统集成AWGN信道模型,支持在Vivado 2019.2中设置不同SNR仿真误码率。示例SNR值从0到15,结果展示解调质量随SNR提升。MATLAB仿真验证了MSK性能,图片显示了仿真结果。 ### 理论概要 研究聚焦于软件无线电中的MSK调制解调,利用Verilog实现。MSK是一种相位连续、恒包络的二进制调制技术,优点包括频谱效率高。系统采用无核设计,关键模块包括调制器、解调器和误码检测。复位、输入数据、中频信号等关键信号通过Verilog描述,并通过Chipscope在线观察。
105 6
基于FPGA的MSK调制解调系统verilog开发,包含testbench,同步模块,高斯信道模拟模块,误码率统计模块
|
6月前
|
算法 异构计算
m基于FPGA的RS+卷积级联编译码实现,RS用IP核实现,卷积用verilog实现,包含testbench测试文件
m基于FPGA的RS+卷积级联编译码实现,RS用IP核实现,卷积用verilog实现,包含testbench测试文件
67 0
|
4月前
|
C语言 芯片 异构计算
FPGA新起点V1开发板(六-语法篇)——verilog简介+基础语法
FPGA新起点V1开发板(六-语法篇)——verilog简介+基础语法
|
5月前
|
算法 计算机视觉 异构计算
基于FPGA的图像一维FFT变换IFFT逆变换verilog实现,包含tb测试文件和MATLAB辅助验证
```markdown ## FPGA 仿真与 MATLAB 显示 - 图像处理的 FFT/IFFT FPGA 实现在 Vivado 2019.2 中仿真,结果通过 MATLAB 2022a 展示 - 核心代码片段:`Ddddddddddddddd` - 理论:FPGA 实现的一维 FFT/IFFT,加速数字信号处理,适用于高计算需求的图像应用,如压缩、滤波和识别 ```

热门文章

最新文章

下一篇
无影云桌面