问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 173 人浏览分享

开启左侧

[MLKPAI-F01-EG4D]FPGA程序设计基础实验连载-20读写I2C接口的RTC时钟芯片

[复制链接]
173 0
安路-FPGA课程
安路课程: 基础入门 » 新手入门实验
安路系列: EG4
本帖最后由 UT发布 于 2025-4-19 09:55 编辑

软件版本:TD_5.6.4_Release_97693
操作系统:WIN11 64bit
硬件平台:适用安路(Anlogic)FPGA
登录米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!
1概述

本节课继续利用I2C总线控制器实现对RTC时钟芯片DS1337的读写访问,进一步验证我们设计的i2c控制器的可靠性。有了前面的基础,这节课内容学习起来很轻松。本节课主要是基于我们编写的I2C控制器的应用,侧重点是应用,所以不再给出RTL级别的仿真结果,直接通过控制器访问DS1337芯片。

在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力:

1:完成了TD软件安装

2:完成了modelsim安装以及TD库的编译

3:掌握了TD仿真环境的设置

4:掌握了modelsim通过do文件启动仿真

1.1 RTC时钟DS1337介绍

DS1337是低功耗、两线制串行读写接口、日历和时钟数据按BCD码存取的时钟/日历芯片。它提供秒、分、小时、星期、日期、月和年等时钟日历数据。另外它还集成了如下几点功能:

156字节掉电时电池保持的SRAM 数据存储器

2)可编程的方波信号输出

3)掉电检测和自动切换电池供电模式

image.jpg image.jpg
引脚说明:

VCC:直流电源,输入范围在1.8~5.5V

GND:接地端

X1/X2:标准的32.768kHz石英晶振接入端

SCL:串行时钟输入

SDA:串行数据输入/输出

SQW/OUT:方波/中断输出

VBAT:中断输出端

寄存器地址空间:

DS1337的寄存器地址空间如下,我们的代码也就是读写以下地址空间。常用的时间寄存器地址为00H~06H,用这七个寄存器来存储秒、分、时、星期、日、月、年,通过写相应的寄存器字段来设置和初始化时间/日历,寄存器的数据格式以BCD码表示4二进制数来表示1十进制数0~9

image.jpg

地址空间中详细的参数定义如下表

image.jpg

00H/01H分寄存器:计数范围为00~59BIT0~BIT3表示个位。BIT4~BIT6表示十位。

02H小时寄存器BIT6被定义为12小时模式1”)24小时模式0”)选择位。在12小时模式时,计数范围为1~12BIT5AM0”)或PM1”)标志位当在24小时模式时,计数范围为00~23需要6位才能存下0~23的数据BIT5是第二个十位(表示2023小时)

03H星期寄存器:计数范围为1~7BIT0~BIT3表示个位。

04H日寄存器:计数范围为01~31BIT0~BIT3表示个位,BIT4~BIT5表示十位。

05H月寄存器BIT7是世纪位,当年寄存器从099溢出时,该位发生变化。

06H年寄存器:计数范围为00~99BIT0~BIT3表示个位,BIT4~BIT7表示十位。

注意:07H~0FH控制寄存器和状态寄存器,用于控制实时时钟、闹钟和方波的输出,本实验并未使用,详细介绍自行查阅芯片手册。


DS1337的读与写

写时序如下:

image.jpg

写时序很容易理解,和我们前面写EEPROM一样,器件发起开始条件先发送器件地址和写命令1101000,等待从器件的应答再发送寄存器的地址,等待从器件的应答之后是连续写数据,从器件应答,主器件发送结束信号,代表完成一次写数据过程。

读时序如下:

image.jpg

读时序我们采用上图的方式,器件发起开始条件先发送器件地址和写命令11010000,等待从器件的应答再发送寄存器的地址,等待从器件的应答,然后主器件再重新发送开始条件,再发送从器件的地址和读命令11010001等待从器件的应答,接收从器件发来的数据,主器件应答。这种方式比较方便,大家学习I2C的课程,如果和ZYNQ 部分SDK对比会发现SDK里面操作比较麻烦,而且采取的不是这种方式,这是因为SDK里面的I2C控制不能发送Repeated Start位的原因。

我们这里只针对时、分、秒的读写,而且上电后默认的控制寄存器不需要设置,采用默认参数就可以。


1.2 DS1337时序
image.jpg
image.jpg

根据手册我们可以知道I2C时钟频率、建立时间、保持时间、两次读写之间的时间间隔有没有限制等。图可知DS1337I2C支持快速和标准模式,串行时钟SCL频率可以位于0~400KHz。而建立时间Tsu和保持时间Thd在快速模式下都小于1usI2C接口在发送一个STOP条件后到发送下一个START条件之间的最小间隔时间(Bus Free Time Between a STOP and START Condition)在快速模式(Fast mode)下,不小于1.3微秒,而在标准模式(Standard mode)下,不小于4.7微秒,这段时间内总线上必须保持空闲。


1.3硬件电路分析

因为FPGA一般不需要中断功能,所以中断管脚和方波编程输出引脚没有使用,进行悬空处理。X1X2引脚接入32.768KHz的晶振构成一个振荡电路,生成芯片所需要的参考时钟信号。IIC总线需要上拉电阻。

image.jpg
2用户程序设计

本次实验读写IIC接口的RTC时钟,首先写入一个时间初始值到RTC相应的地址空间,再读取时间值,因为是实时时钟,时钟数据是一直变化的,所以我们需要一直不断的读取时钟的值,并将读取的时间通过串口发送到上位机。实验包含3个模块,I2C MASTER控制器驱动模块、UART发送控制器模块、用户控制模块。以下给出系统框图,关于IIC MASTER控制器驱动、UART发送控制器驱动的详细描述请看前面的实验,我们主要看用户控制模块关于状态机的部分。

image.jpg
2.1用户接口时序

先温习下前面课程内容中关于I2C控制器的功能模块可以接口信号:

IO_sdaI2C双向数据总线

O_sclI2C时钟

I_wr_cnt写数据字节长度,包含了器件地址,发送I_iic_req前,预设该值

I_rd_cnt读数据字节长度,仅包含读回有效部分,发送I_iic_req前,预设该值

I_wr_data写入的数据

O_rd_data读出的数据,如果是读请求,当O_iic_busy从高变低代表数据读回有效

I_iic_req I2C操作请求,根据I_rd_cnt是否大于0决定是否有读请求

I_iic_mode是否支持随机读写,发送I_iic_req前,预设该值

O_iic_busy总线忙

image.jpg

请求一次I2C传输的控制时序如下:

image.jpg

首先在O_iic_busy=0I2C总线空闲情况下,设置I_wr_cntI_rd_cntI_wr_data,并且设置I_iic_req=1启动I2C传输。当O_iic_busy=1说明I2C控制器开始传输,这时候可以设置I_iic_req=0结束本次请求,并且等待O_iic_busy=0O_iic_busy=0代表本次输结束.如果发送的是读请求(I_rd_cnt>0),则此时O_rd_data有效可以读走数据。

2.2 用户控制状态机

首先进行通过复位进行数据初始化。

TS_S=0:板子刚上电时,首先初始化RTC寄存器的时间,设置需要访问的寄存器起始地址,并进入到下一个状态;如果已经完成对RTC芯片初始化,等待1000ms定时使能信号拉高,再次读取RTC寄存器的数据。

TS_S=1:当总线非忙,设置iic_req=1,请求操作I2C控制器,并设置需要写入的字节数(此时不需要读操作)。

TS_S=2:总线忙,重置 iic_req =0,此时为写数据过程。

TS_S=3:等待总线非忙,代表前面写数据已完成,设置iic_req=1请求操作I2C控制。读操作前需要先写1BYTE器件地址,1BYTE 寄存器起始地址,再读取3个时间寄存器。

TS_S=4:等待总线非忙,表示一次读RTC寄存器操作完成。回到状态0

image.jpg
2.3 系统框图

本系统利用数码管显示,实时时钟,用到了前面使用中动态驱动数码管的方案,系统框图如下:

image.jpg
2.4程序源码
  1. module rtc_clock_ds1337#
  2. (
  3. parameter SYSCLKHZ   =  25_000_000 //定义系统时钟
  4. )
  5. (
  6. input  wire I_sysclk, //系统时钟输入
  7. output wire O_iic_scl,  //I2C总线,SCL时钟
  8. inout  wire IO_iic_sda, //I2C总线,SDA数据
  9. output wire O_uart_tx   //UART串行发送总线
  10. );
  11. localparam T1000MS_CNT   =  (SYSCLKHZ-1); //定义访问RTC的时间间隔为1000MS
  12. localparam [7:0] RTC_DEV_ADDR =  8'b1101_0000;
  13. reg [8 :0]  rst_cnt       = 9'd0;//上电延迟复位
  14. reg [29:0]  t_cnt = 30'd0;//定时计数器
  15. wire t_en = (t_cnt==T1000MS_CNT);//定时使能  
  16. wire [23:0] wr_data;//写数据信号
  17. wire [23:0] rd_data;//读数据信号
  18. wire        iic_busy;//I2C总线忙
  19. reg  [7 :0] wr_cnt = 8'd0;//写数据计数器
  20. reg  [7 :0] rd_cnt = 8'd0;//读数据计数器
  21. reg         iic_req = 1'b0;//i2c 控制器请求信号
  22. reg  [2 :0] TS_S   = 3'd0;//状态机
  23. reg  [7 :0] rtc_addr;//RTC的寄存器地址
  24. reg         wr_done = 1'b0; //写RTC初值完成信号
  25.          
  26. //初始化时间的BDC码,12:00:00
  27. wire [7 :0] WSecond = {4'd0,4'd0};//妙
  28. wire [7 :0] WMinute = {4'd0,4'd0};//分
  29. wire [7 :0] WHour   = {4'd1,4'd2};//时
  30. reg  [23:0] rtime   = 24'd0; //用于保存读取的时间,格式为BCD码
  31. assign wr_data   = {WHour,WMinute,WSecond};//写数据初值
  32. //**********上电延迟复位***************************/
  33. always@(posedge I_sysclk) begin
  34.     if(!rst_cnt[8])
  35.         rst_cnt <= rst_cnt + 1'b1;
  36. end
  37. //**********500ms定时计数器**********************/
  38. always@(posedge I_sysclk) begin
  39.     if(t_cnt == T1000MS_CNT)
  40.         t_cnt <= 0;
  41.     else
  42.         t_cnt <= t_cnt + 1'b1;
  43. end
  44. //读写RTC时钟芯片状态机
  45. always@(posedge I_sysclk) begin
  46.     if(!rst_cnt[8])begin//复位初始化寄存器
  47.         rtc_addr <= 8'd0;
  48.         iic_req  <= 1'b0;
  49.         wr_done  <= 1'b0;
  50.         rd_cnt   <= 8'd0;
  51.         wr_cnt   <= 8'd0;
  52.         TS_S     <= 2'd0;   
  53.     end
  54.     else begin
  55.         case(TS_S)
  56.         0:if(wr_done == 1'b0)begin//上电后,wr_done=0,对RTC时间寄存器初始化,给定初始时间
  57.             wr_done  <= 1'b1;//设置wr_done=1
  58.             rtc_addr <= 8'd0;//设置需要访问的寄存器起始地址
  59.             TS_S     <= 3'd1;//下一个状态
  60.         end
  61.         else begin //已经对RTC芯片初始化完成
  62.             iic_req  <= 1'b0; //重置 iic_req =0
  63.             if(t_en)//每间隔1000ms进行一次读操作
  64.             TS_S     <= 3'd3;//下一个状态,进入读寄时间寄存器状态机
  65.         end
  66.         1:if(!iic_busy)begin//当总线非忙,才可以操作I2C控制器
  67.             iic_req  <= 1'b1;//请求操作I2C控制器
  68.             rd_cnt   <= 8'd0;//由于本操作是写数据,不需要读数据,读数据寄存器设置0
  69.             wr_cnt   <= 8'd5;//需要写入5 BYTES,包括1字节的器件地址,1字节的寄存器起始地址,3字节的BCD时间参数
  70.             TS_S     <= 3'd2;//下一个状态机
  71.         end
  72.         2:if(iic_busy)begin//等待总线忙
  73.             iic_req  <= 1'b0;//重置 iic_req =0
  74.             TS_S     <= 3'd3;//下一个状态机   
  75.         end
  76.         3:if(!iic_busy)begin//该状态读RTC时间寄存器
  77.             iic_req  <= 1'b1;//请求操作I2C控制器
  78.             rtc_addr <= 8'd0;//读RTC寄存器的起始地址
  79.             wr_cnt   <= 8'd2;//读操作需要些1BYTE器件地址,1BYTE 寄存器起始地址
  80.             rd_cnt   <= 8'd3;//读取3个时间寄存器
  81.             TS_S     <= 3'd4;//下一个状态   
  82.         end
  83.         4:if(iic_busy)begin//等待总线空闲
  84.             iic_req  <= 1'b0;//重置 iic_req =0
  85.             TS_S     <= 3'd0;//下一个状态   
  86.         end   
  87.         default: TS_S    <= 3'd0;//default状态回到0
  88.     endcase
  89.    end
  90. end
  91. //***********保存从RTC读取到的时间寄存器,时间为BCD格式***********//
  92. always@(posedge I_sysclk) begin
  93.     if(!rst_cnt[8])
  94.         rtime <=0;
  95.    else if(TS_S == 3)
  96.         rtime[23: 0] <= rd_data;//读取的时间包括 时:分:秒,BCD格式
  97. end
  98. //例化I2C控制模块
  99. uii2c#
  100. (
  101. .WMEN_LEN(5),//最大支持一次写入4BYTE(包含器件地址)
  102. .RMEN_LEN(3),//最大支持一次读出3BYTE
  103. .CLK_DIV(SYSCLKHZ/100000)//100KHZ I2C总线时钟
  104. )
  105. uii2c_inst
  106. (
  107. .I_clk(I_sysclk),//系统时钟
  108. .I_rstn(rst_cnt[8]),//系统复位
  109. .O_iic_scl(O_iic_scl),//I2C SCL总线时钟
  110. .IO_iic_sda(IO_iic_sda),//I2C SDA数据总线
  111. .I_wr_data({wr_data,rtc_addr,RTC_DEV_ADDR}),//写数据寄存器
  112. .I_wr_cnt(wr_cnt),//需要写的数据BYTES
  113. .O_rd_data(rd_data), //读数据寄存器
  114. .I_rd_cnt(rd_cnt),//需要读的数据BYTES
  115. .I_iic_req(iic_req),//I2C控制器请求
  116. .I_iic_mode(1'b1),//读模式
  117. .O_iic_busy(iic_busy)//I2C控制器忙
  118. //.iic_bus_error(iic_bus_error),//总线错误信号标志
  119. //.IO_iic_sda_dg(IO_iic_sda_dg)//debug IO_iic_sda
  120. );
  121. //以下完成BCD码赚ASCII码,这样通过串口打印可以方便观察
  122. function signed[7:0] ascii ;   //定义ascii码转换函数,只需要转换BCD数据
  123.    
  124. input[7:0] bcd; //输入参数  
  125. begin                                                   
  126.     case(bcd)
  127.     0 :     ascii   =   {8'h30};//ascii 码0  
  128.     1 :     ascii   =   {8'h31};//ascii 码1      
  129.     2 :     ascii   =   {8'h32};//ascii 码2  
  130.     3 :     ascii   =   {8'h33};//ascii 码3
  131.     4 :     ascii   =   {8'h34};//ascii 码4  
  132.     5 :     ascii   =   {8'h35};//ascii 码5         
  133.     6 :     ascii   =   {8'h36};//ascii 码6  
  134.     7 :     ascii   =   {8'h37};//ascii 码7  
  135.     8 :     ascii   =   {8'h38};//ascii 码8  
  136.     9 :     ascii   =   {8'h39};//ascii 码9
  137.     default:ascii   =   {8'h00};   
  138.     endcase                                
  139. end  
  140.                                                    
  141. endfunction   
  142. //例化UART发送模块
  143. uart_tx_block u_uart_tx_block
  144. (
  145. .I_sysclk(I_sysclk),//系统时钟输入
  146. .O_uart_tx(O_uart_tx),//UART 串行总线数据发送
  147. //高位,8'h0a,8'h0d,为回车+换行控制字符
  148. .I_uart_tx_buf({8'h0a,8'h0d,ascii(rtime[3:0]),ascii(rtime[7:4]),8'h2d,ascii(rtime[11:8]),ascii(rtime[15:12]),8'h2d,ascii(rtime[19:16]),ascii(rtime[23:20])}),
  149. .I_uart_tx_buf_en(t_en)//t_en也是发送使能
  150. );
  151. endmodule
复制代码
3 FPGA工程

fpga工程的创建过程不再重复

image.jpg

米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹

01_rtl:放用户编写的rtl代码

02_sim:仿真文件或者工程

03_ip:放使用到的ip文件

04_pin:fpgapin脚约束文件或者时序约束文件

05_boot:放编译好的bit或者bin文件(一般为空)

06_doc:放本一些相关文档(一般为空)

4下载演示4.1硬件连接

(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)

请确保下载器和开发板已经正确连接,并且开发板已经上电(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)

image.jpg
4.2运行结果

在配套demo路径下我们也提供了可以用串口打印的demo

image.jpg




































您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

0

粉丝

293

主题
精彩推荐
热门资讯
网友晒图
图文推荐

  • 微信公众平台

  • 扫描访问手机版