[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA基础篇连载-22读写I2C接口的RTC时钟芯片

文档创建者:FPGA课程
浏览次数:270
最后更新:2024-08-30
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
本帖最后由 FPGA课程 于 2024-8-30 16:47 编辑

​ 软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!



1概述
本节课继续利用I2C总线控制器实现对RTC时钟芯片,DS1337的读写访问,进一步验证我们设计的i2c控制器的可靠性。有了前面的基础,这节课内容学习起来很轻松。本节课主要是基于我们编写的I2C控制器的应用,侧重点是应用,所以不再给出RTL级别的仿真结果,直接通过控制器访问DS1337芯片。
2RTC时钟DS1337介绍
DS1337是低功耗、两线制串行读写接口、日历和时钟数据按BCD码存取的时钟/日历芯片。它提供秒、分、小时、星期、日期、月和年等时钟日历数据。另外它还集成了如下几点功能:
(1)56字节掉电时电池保持的SRAM 数据存储器
(2)可编程的方波信号输出
(3)掉电检测和自动切换电池供电模式
d6708b04c7a14bbd806accccfce36a02.jpg
DS1337的寄存器地址空间如下,我们的代码也就是读写一下地址空间。
51fa27558f8d40c4baa59576ccb637e9.jpg
地址空间中详细的参数定义如下表
db7f80caebdc4486acb958ffa2303b93.jpg
写时序如下:
9d921f8194814d85900823d7b5e4f5b8.jpg
写时序很容易理解,和我们前面写EEPROM一样,先发送器件地址为1101000,再发送寄存器的地址,之后是连续写数据。
读时序如下:
86c8050445964e85aa89028a4cc38cfb.jpg
读时序我们采用上图的方式,这种方式比较方便,大家学习I2C的课程,如果和ZYNQ 部分SDK对比会发现SDK里面操作比较麻烦,而且采取的不是这种方式,这是因为SDK里面的I2C控制不能发送Repeated Start位的原因。其实作为FPGA开发者,感觉FPGA编程熟练后,写这种接口代码还是非常方便。
我们这里只针对时、分、秒的读写,而且上电后默认的控制寄存器不需要设置,采用默认参数就可以。
3用户程序设计3.1用户接口时序
先温习下前面课程内容中关于I2C控制器的功能模块可以接口信号:
IO_sda为I2C双向数据总线
O_scl为I2C时钟
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总线忙
bd2a7312e8f24e168a2e3e69a60f6082.jpg
请求一次I2C传输的控制时序如下:
8f6c22cfb67a4cb19cf77bf9f9e3c64d.jpg
首先在O_iic_busy=0即I2C总线空闲情况下,设置I_wr_cnt,I_rd_cnt,I_wr_data,并且设置I_iic_req=1,启动I2C传输。当O_iic_busy=1说明I2C控制器开始传输,这时候可以设置I_iic_req=0,结束本次请求,并且等待O_iic_busy=0,当O_iic_busy=0代表本次传
传输结束.如果发送的是读请求(当I_rd_cnt>0),则此时O_rd_data有效可以读走数据。
3.2 RTC读写程序设计3.2.1状态机介绍
f17fcb33fd154f4d8704504d36ee736e.jpg
3.2.2RTC读写用户程序源码
  1. /*******************************rtc_clock_ds1337*********************
  2. --1.本实验目基于米联客I2C控制器,实现对RTC时钟芯片的读写访问
  3. --2.本实验通过串口打印读取的RTC时钟时间值
  4. *********************************************************************/
  5. `timescale 1ns / 1ns//仿真时间刻度/精度

  6. module rtc_clock_ds1337(
  7. input  wire I_sysclk_p,
  8. input  wire I_sysclk_n,//系统时钟输入
  9. output wire O_iic_scl, //I2C总线,SCL时钟
  10. inout  wire IO_iic_sda, //I2C总线,SDA数据
  11. output wire O_uart_tx, //UART串行发送总线
  12. output wire O_card_power_en
  13. );
  14. localparam SYSCLKHZ     =  100_000_000; //定义系统时钟
  15. localparam T1000MS_CNT   =  (SYSCLKHZ-1); //定义访问RTC的时间间隔为1000MS
  16. localparam [7:0] RTC_DEV_ADDR =  8'b1101_0000;

  17. wire I_clk;
  18. IBUFGDS CLK_U(
  19. .I(I_sysclk_p),
  20. .IB(I_sysclk_n),
  21. .O(I_clk)
  22. );
  23. reg [8 :0]  rst_cnt       = 9'd0;//上电延迟复位
  24. reg [29:0]  t_cnt = 30'd0;//定时计数器
  25. wire t_en = (t_cnt==T1000MS_CNT);//定时使能  

  26. wire [23:0] wr_data;//写数据信号
  27. wire [23:0] rd_data;//读数据信号
  28. wire        iic_busy;//I2C总线忙
  29. reg  [7 :0] wr_cnt = 8'd0;//写数据计数器
  30. reg  [7 :0] rd_cnt = 8'd0;//读数据计数器
  31. reg         iic_req = 1'b0;//i2c 控制器请求信号
  32. reg  [2 :0] TS_S   = 3'd0;//状态机
  33. reg  [7 :0] rtc_addr;//RTC的寄存器地址
  34. reg         wr_done = 1'b0; //写RTC初值完成信号
  35.          
  36. //初始化时间的BDC码,12:00:00
  37. wire [7 :0] WSecond = {4'd0,4'd0};//妙
  38. wire [7 :0] WMinute = {4'd0,4'd0};//分
  39. wire [7 :0] WHour   = {4'd1,4'd2};//时
  40. reg  [23:0] rtime   = 24'd0; //用于保存读取的时间,格式为BCD码

  41. assign wr_data   = {WHour,WMinute,WSecond};//写数据初值
  42. assign O_card_power_en = 1'b1; //ascii
  43. //**********上电延迟复位***************************/
  44. always@(posedge I_clk) begin
  45.     if(!rst_cnt[8])
  46.         rst_cnt <= rst_cnt + 1'b1;
  47. end

  48. //**********500ms定时计数器**********************/
  49. always@(posedge I_clk) begin
  50.     if(t_cnt == T1000MS_CNT)
  51.         t_cnt <= 0;
  52.     else
  53.         t_cnt <= t_cnt + 1'b1;
  54. end

  55. //读写RTC时钟芯片状态机
  56. always@(posedge I_clk) begin
  57.     if(!rst_cnt[8])begin//复位初始化寄存器
  58.         rtc_addr <= 8'd0;
  59.         iic_req  <= 1'b0;
  60.         wr_done  <= 1'b0;
  61.         rd_cnt   <= 8'd0;
  62.         wr_cnt   <= 8'd0;
  63.         TS_S     <= 2'd0;   
  64.     end
  65.     else begin
  66.         case(TS_S)
  67.         0:if(wr_done == 1'b0)begin//上电后,wr_done=0,对RTC时间寄存器初始化,给定初始时间
  68.             wr_done  <= 1'b1;//设置wr_done=1
  69.             rtc_addr <= 8'd0;//设置需要访问的寄存器起始地址
  70.             TS_S     <= 3'd1;//下一个状态
  71.         end
  72.         else begin //已经对RTC芯片初始化完成
  73.             iic_req  <= 1'b0; //重置 iic_req =0
  74.             if(t_en)//每间隔1000ms进行一次读操作
  75.             TS_S     <= 3'd3;//下一个状态,进入读寄时间寄存器状态机
  76.         end
  77.         1:if(!iic_busy)begin//当总线非忙,才可以操作I2C控制器
  78.             iic_req  <= 1'b1;//请求操作I2C控制器
  79.             rd_cnt   <= 8'd0;//由于本操作是写数据,不需要读数据,读数据寄存器设置0
  80.             wr_cnt   <= 8'd5;//需要写入5 BYTES,包括1字节的器件地址,1字节的寄存器起始地址,3字节的BCD时间参数
  81.             TS_S     <= 3'd2;//下一个状态机
  82.         end
  83.         2:if(iic_busy)begin//等待总线忙
  84.             iic_req  <= 1'b0;//重置 iic_req =0
  85.             TS_S     <= 3'd3;//下一个状态机   
  86.         end
  87.         3:if(!iic_busy)begin//该状态读RTC时间寄存器
  88.             iic_req  <= 1'b1;//请求操作I2C控制器
  89.             rtc_addr <= 8'd0;//读RTC寄存器的起始地址
  90.             wr_cnt   <= 8'd2;//读操作需要些1BYTE器件地址,1BYTE 寄存器起始地址
  91.             rd_cnt   <= 8'd3;//读取3个时间寄存器
  92.             TS_S     <= 3'd4;//下一个状态   
  93.         end
  94.         4:if(iic_busy)begin//等待总线空闲
  95.             iic_req  <= 1'b0;//重置 iic_req =0
  96.             TS_S     <= 3'd0;//下一个状态   
  97.         end   
  98.         default: TS_S    <= 3'd0;//default状态回到0
  99.     endcase
  100.    end
  101. end
  102. //***********保存从RTC读取到的时间寄存器,时间为BCD格式***********//
  103. always@(posedge I_clk) begin
  104.     if(!rst_cnt[8])
  105.         rtime <=0;
  106.    else if(TS_S == 3)
  107.         rtime[23: 0] <= rd_data;//读取的时间包括 时:分:秒,BCD格式
  108. end

  109. //例化I2C控制模块
  110. uii2c#
  111. (
  112. .WMEN_LEN(5),//最大支持一次写入4BYTE(包含器件地址)
  113. .RMEN_LEN(3),//最大支持一次读出3BYTE
  114. .CLK_DIV(SYSCLKHZ/100000)//100KHZ I2C总线时钟
  115. )
  116. uii2c_inst
  117. (
  118. .I_clk(I_clk),//系统时钟
  119. .I_rstn(rst_cnt[8]),//系统复位
  120. .O_iic_scl(O_iic_scl),//I2C SCL总线时钟
  121. .IO_iic_sda(IO_iic_sda),//I2C SDA数据总线
  122. .I_wr_data({wr_data,rtc_addr,RTC_DEV_ADDR}),//写数据寄存器
  123. .I_wr_cnt(wr_cnt),//需要写的数据BYTES
  124. .O_rd_data(rd_data), //读数据寄存器
  125. .I_rd_cnt(rd_cnt),//需要读的数据BYTES
  126. .I_iic_req(iic_req),//I2C控制器请求
  127. .I_iic_mode(1'b1),//读模式
  128. .O_iic_busy(iic_busy)//I2C控制器忙
  129. //.O_iic_bus_error(iic_bus_error),//总线错误信号标志
  130. //.IO_iic_sda_dg(IO_iic_sda_dg)//debug IO_iic_sda
  131. );

  132. //以下完成BCD码赚ASCII码,这样通过串口打印可以方便观察
  133. function signed[7:0] ascii ;   //定义ascii码转换函数,只需要转换BCD数据
  134.    
  135. input[7:0] bcd; //输入参数  

  136. begin                                                   
  137.     case(bcd)
  138.     0 :     ascii   =   {8'h30};//ascii 码0  
  139.     1 :     ascii   =   {8'h31};//ascii 码1      
  140.     2 :     ascii   =   {8'h32};//ascii 码2  
  141.     3 :     ascii   =   {8'h33};//ascii 码3
  142.     4 :     ascii   =   {8'h34};//ascii 码4  
  143.     5 :     ascii   =   {8'h35};//ascii 码5         
  144.     6 :     ascii   =   {8'h36};//ascii 码6  
  145.     7 :     ascii   =   {8'h37};//ascii 码7  
  146.     8 :     ascii   =   {8'h38};//ascii 码8  
  147.     9 :     ascii   =   {8'h39};//ascii 码9
  148.     default:ascii   =   {8'h00};   
  149.     endcase                                
  150. end  
  151.                                                    
  152. endfunction   

  153. //例化UART发送模块
  154. uart_tx_block #
  155. (
  156. .TX_BYTES(10),//设置需要发送的字节数
  157. .BAUD_DIV(SYSCLKHZ/115200 -1) //设置串口波特率
  158. )
  159. u_uart_tx_block
  160. (
  161. .I_sysclk(I_clk),//系统时钟输入
  162. .O_uart_tx(O_uart_tx),//UART 串行总线数据发送
  163. //高位,8'h0a,8'h0d,为回车+换行控制字符
  164. .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])}),
  165. .I_uart_tx_buf_en(t_en)//t_en也是发送使能
  166. );
  167. endmodule
复制代码

4FPGA工程
fpga工程的创建过程不再重复,如有不清楚的请看前面实验
1a4213cafb7e47c1a62ba1bcbee3b4e2.jpg
米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹
01_rtl:放用户编写的rtl代码
02_sim:仿真文件或者工程
03_ip:放使用到的ip文件
04_pin:放fpga的pin脚约束文件或者时序约束文件
05_boot:放编译好的bit或者bin文件(一般为空)
06_doc:放本一些相关文档(一般为空)
ab36c70f3d5d414d842256aa0eeb9c40.jpg
5下载演示5.1硬件连接
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
请确保下载器和开发板已经正确连接,另外需要把核心板上的2P模式开关设置到JTAG模式,即ON ON,并且开发板已经上电。(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)
6e6938e9c9d0492d8a51c31c5a7b2576.jpg

5.2运行结果
使用串口打印软件进行打印
1051dc21e2c34ea0aec68ad6ad598ca9.jpg



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

本版积分规则