软件版本:VIVADO2017.4
操作系统:WIN10 64bit
硬件平台:适用米联客 ZYNQ系列开发板
米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!
12.1 概述
趁热打铁,我们刚刚在上一节课掌握了I2C利用ZYNQ I2C总线控制器读写EEPROM,本节课继续利用I2C总线控制器实现对RTC时钟芯片,DS1307的读写访问。有了前面的基础,这节课内容学习起来很轻松。
12.2 RTC时钟DS1307介绍
DS1307是低功耗、两线制串行读写接口、日历和时钟数据按BCD码存取的时钟/日历芯片。它提供秒、分、小时、星期、日期、月和年等时钟日历数据。另外它还集成了如下几点功能:
(1)56 字节掉电时电池保持的NV SRAM 数据存储器
(2)可编程的方波信号输出
(3)掉电检测和自动切换电池供电模式
S1307的寄存器地址空间如下,我们的代码也就是读写一下地址空间。
地址空间中详细的参数定义如下表
写时序如下:
写时序很容易理解,和我们前面写EEPROM一样,先发送器件地址为1101000,再发送寄存器的地址,之后是连续写数据。
读时序如下:
这里读的时序有点没描述清楚,读时序前首先还要进行一次写寄存器起始地址的设置。比如代码,首先是写器件地址,并且指定标记读寄存器的其实寄存器地址为0x00,然后从标记的0x00读7个字节的数据。后面时序分析的时候再配合以上读写时序图介绍。
12.3 FPGA BD工程
这节课的FPGA BD工程和上一节课是一样的,我们依然把关键部分描述下。
做这个实验必须勾选支持I2C控制器,通过EMIO的方式引出I2C总线。RTC模块连线就可以完成实验。对于初学者需要注意,EMIO是FPGA的PIN脚因此需要添加XDC文件约束FPGA PIN脚。
另外为了完本课程实验,需要选择购买RTC模块。对于MZ7XA-7010(mini)/MZ7XA-7020/MZ7XB-7020开发板具有板载的IO扩展,只要正确和EEPROM模块对接就能完成此实验。11.3 I2C Polled方式读写EEPROM
12.4 I2C Polled方式读写RTC时钟芯片
12.4.1 I2c 控制器
PS支持两个具有以下主要功能的I2C设备:
I2C总线规范版本2
支持16字节FIFO
可编程的正常和快速总线数据速率
主模式
-写转移
-读取转移
-扩展地址支持
-支持缓慢处理器服务的HOLD
-支持中断
从模式
12.4.2 I2cPs_Polled.c
我们米联客(MSXBO)编写了两种读写EEPROM的方式,并且封装成子函数方便用户调用。我们先看代码。
include "I2cPs_Polled.h" #include "sleep.h" int I2cPs_init(XIicPs *I2C_Ptr,u16 DeviceId) { int Status; XIicPs_Config *Config;
/* * Initialize the IIC driver so that it's ready to use * Look up the configuration in the config table, then initialize it. */ Config = XIicPs_LookupConfig(DeviceId); if (NULL == Config) { return XST_FAILURE; }
Status = XIicPs_CfgInitialize(I2C_Ptr, Config, Config->BaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; }
/* * Perform a self-test to ensure that the hardware was built correctly. */ Status = XIicPs_SelfTest(I2C_Ptr); if (Status != XST_SUCCESS) { return XST_FAILURE; }
/* * Set the IIC serial clock rate. */ XIicPs_SetSClk(I2C_Ptr, IIC_SCLK_RATE);
return XST_SUCCESS; }
void I2cPs_write(XIicPs *I2C_Ptr, u8 *MsgPtr, s32 ByteCount, u16 SlaveAddr) {
XIicPs_MasterSendPolled(I2C_Ptr, MsgPtr, ByteCount, SlaveAddr);
while (XIicPs_BusIsBusy(I2C_Ptr)) { } usleep(2000);
}
void I2cPs_read(XIicPs *I2C_Ptr, u8 *MsgPtr, s32 ByteCount, u16 SlaveAddr) { XIicPs_MasterRecvPolled(I2C_Ptr, MsgPtr, ByteCount, SlaveAddr); while (XIicPs_BusIsBusy(I2C_Ptr)) { } usleep(2000);
}
|
I2cPs_init
函数负责初始化I2C控制器,关键是初始化了I2C控制的速度XIicPs_SetSClk(I2C_Ptr, IIC_SCLK_RATE)。
I2cPs_write
函数顾名思义是实现I2C的写数据
I2cPs_read
函数顾名思义是实现I2C的读数据
12.4.3 RTC_DS1307.c
#include "I2cPs_Polled.h" #include "RTC_DS1307.h"
u8 DS1307_decToBcd(u8 val) { return ( (val/10*16) + (val%10) ); }
//Convert binary coded decimal to normal decimal numbers u8 DS1307_bcdToDec(u8 val) { return ( (val/16*10) + (val%16) ); }
void DS1307_startClock(void) // set the ClockHalt bit low to start the rtc { //point to register ds1307_wbuf[0]=0x00;// Register 0x00 holds the oscillator start/stop bit I2cPs_write(&Iic,ds1307_wbuf, 1, DS1307_I2C_ADDRESS);
//read register I2cPs_read (&Iic,ds1307_rbuf, 1, DS1307_I2C_ADDRESS); rtc.second =ds1307_rbuf[0] & 0x7f;// save actual seconds and AND sec with bit 7 (sart/stop bit) = clock started
//write register ds1307_wbuf[0]=0x00; ds1307_wbuf[1]=rtc.second; I2cPs_write(&Iic,ds1307_wbuf, 2, DS1307_I2C_ADDRESS);// write seconds back and start the clock }
void DS1307_stopClock(void) // set the ClockHalt bit high to stop the rtc { //point to register ds1307_wbuf[0]=0x00;// Register 0x00 holds the oscillator start/stop bit I2cPs_write(&Iic,ds1307_wbuf, 1, DS1307_I2C_ADDRESS);
//read register I2cPs_read (&Iic,ds1307_rbuf, 1, DS1307_I2C_ADDRESS); rtc.second =ds1307_rbuf[0] | 0x80;// save actual seconds and AND sec with bit 7 (sart/stop bit) = clock started
//write register ds1307_wbuf[0]=0x00; ds1307_wbuf[1]=rtc.second; I2cPs_write(&Iic,ds1307_wbuf, 2, DS1307_I2C_ADDRESS);// write seconds back and start the clock }
/****************************************************************/ /*Function: Read time and date from RTC */ void DS1307_getTime() { //point to register ds1307_wbuf[0]=0x00;// Register 0x00 holds the oscillator start/stop bit I2cPs_write(&Iic,ds1307_wbuf, 1, DS1307_I2C_ADDRESS); //read register I2cPs_read (&Iic,ds1307_rbuf, 7, DS1307_I2C_ADDRESS); // A few of these need masks because certain bits are control bits
rtc.second = DS1307_bcdToDec(ds1307_rbuf[0] & 0x7f); rtc.minute = DS1307_bcdToDec(ds1307_rbuf[1]); rtc.hour = DS1307_bcdToDec(ds1307_rbuf[2] & 0x3f);// Need to change this if 12 hour am/pm rtc.dayOfWeek = DS1307_bcdToDec(ds1307_rbuf[3]); rtc.dayOfMonth = DS1307_bcdToDec(ds1307_rbuf[4]); rtc.month = DS1307_bcdToDec(ds1307_rbuf[5]); rtc.year = DS1307_bcdToDec(ds1307_rbuf[6]); }
/*******************************************************************/ /*Function: Write the time that includes the date to the RTC chip */ void DS1307_setTime() { ds1307_wbuf[0]=0x00; ds1307_wbuf[1]=DS1307_decToBcd(rtc.second);// 0 to bit 7 starts the clock ds1307_wbuf[2]=DS1307_decToBcd(rtc.minute); ds1307_wbuf[3]=DS1307_decToBcd(rtc.hour);// If you want 12 hour am/pm you need to set bit 6 ds1307_wbuf[4]=DS1307_decToBcd(rtc.dayOfWeek); ds1307_wbuf[5]=DS1307_decToBcd(rtc.dayOfMonth); ds1307_wbuf[6]=DS1307_decToBcd(rtc.month); ds1307_wbuf[7]=DS1307_decToBcd(rtc.year);
I2cPs_write(&Iic,ds1307_wbuf, 8, DS1307_I2C_ADDRESS); }
void DS1307_fillByHMS(u8 _hour, u8 _minute, u8 _second) { // assign variables rtc.hour = _hour; rtc.minute = _minute; rtc.second = _second; } void DS1307_fillByYMD(u16 _year, u8 _month, u8 _day) {
rtc.year = _year-2000; rtc.month = _month; rtc.dayOfMonth = _day; } void DS1307_fillDayOfWeek(u8 _dow) { rtc.dayOfWeek = _dow; } |
DS1307_startClock
函数启动RTC时钟芯片运行
DS1307_stopClock
函数启动RTC停止芯片运行
DS1307_getTime
函数读取RTC时钟芯片时间
DS1307_setTime
函数设置RTC时钟芯片时间
DS1307_fillByHMS
初始化年时分秒时间变量
DS1307_fillByYMD
初始化年月日时间变量
DS1307_fillDayOfWeek
初始化周几时间变量
DS1307_decToBcd
十进制转BCD码
DS1307_bcdToDec
BCD码转10进制
写时序很容易理解,和我们前面写EEPROM一样,先发送器件地址为1101000,再发送寄存器的地址,之后是连续写数据。
写时序如下:
设置时间的函数如下:
void DS1307_setTime() { ds1307_wbuf[0]=0x00; ds1307_wbuf[1]=DS1307_decToBcd(rtc.second);// 0 to bit 7 starts the clock ds1307_wbuf[2]=DS1307_decToBcd(rtc.minute); ds1307_wbuf[3]=DS1307_decToBcd(rtc.hour);// If you want 12 hour am/pm you need to set bit 6 ds1307_wbuf[4]=DS1307_decToBcd(rtc.dayOfWeek); ds1307_wbuf[5]=DS1307_decToBcd(rtc.dayOfMonth); ds1307_wbuf[6]=DS1307_decToBcd(rtc.month); ds1307_wbuf[7]=DS1307_decToBcd(rtc.year);
I2cPs_write(&Iic,ds1307_wbuf, 8, DS1307_I2C_ADDRESS); } |
这里读的时序有点没描述清楚,读时序前首先还要进行一次写寄存器起始地址的设置。比如代码,首先是写器件地址,并且指定标记读寄存器的其实寄存器地址为0x00,然后从标记的0x00读7个字节的数据。
读时序如下:
获取实现的函数,如下:
/****************************************************************/ /*Function: Read time and date from RTC */ void DS1307_getTime() { //point to register ds1307_wbuf[0]=0x00;// Register 0x00 holds the oscillator start/stop bit I2cPs_write(&Iic,ds1307_wbuf, 1, DS1307_I2C_ADDRESS); //read register I2cPs_read (&Iic,ds1307_rbuf, 7, DS1307_I2C_ADDRESS); // A few of these need masks because certain bits are control bits
rtc.second = DS1307_bcdToDec(ds1307_rbuf[0] & 0x7f); rtc.minute = DS1307_bcdToDec(ds1307_rbuf[1]); rtc.hour = DS1307_bcdToDec(ds1307_rbuf[2] & 0x3f);// Need to change this if 12 hour am/pm rtc.dayOfWeek = DS1307_bcdToDec(ds1307_rbuf[3]); rtc.dayOfMonth = DS1307_bcdToDec(ds1307_rbuf[4]); rtc.month = DS1307_bcdToDec(ds1307_rbuf[5]); rtc.year = DS1307_bcdToDec(ds1307_rbuf[6]); } |
12.4.4 mian.c
#include "I2cPs_Polled.h" #include "RTC_DS1307.h"
void setup() { DS1307_startClock(); DS1307_fillByYMD(2019,5,10);//Jan 19,2013 DS1307_fillByHMS(11,20,30);//15:28 30" DS1307_fillDayOfWeek(FRI);//Saturday DS1307_setTime();//write time to the RTC chip
}
int main(void) {
I2cPs_init(&Iic,IIC_DEVICE_ID); setup(); while(1) {
DS1307_getTime(); xil_printf("%d:%d:%d-%d-%d-%d-",rtc.hour,rtc.minute,rtc.second,rtc.month,rtc.dayOfMonth,rtc.year+2000);
switch (rtc.dayOfWeek)// Friendly printout the weekday { case MON: xil_printf("MON"); break; case TUE: xil_printf("TUE"); break; case WED: xil_printf("WED"); break; case THU: xil_printf("THU"); break; case FRI: xil_printf("FRI"); break; case SAT: xil_printf("SAT"); break; case SUN: xil_printf("SUN"); break; }
xil_printf("\r\n");
sleep(1); } return 0; }
|
在mian.c文件中,初始化I2C控制器,设置RTC时钟芯片的时间,之后每间隔1S读取一次时间值,并且通过串口打印。
12.5 硬件连线
通过外扩的FPGA GPIO 连接 RTC模块,MZ7XA、MZ7XB自带此IO
对于没有这组IO的开发板通过FEP转NEP转接卡实现,此转接卡需要单独购买