这一节,我将给大家讲解实时时钟部分的内容,我在黑金板上用的实时时钟芯片是DS1302,这块芯片很常见,性价比也很高。我们主要来讲如何在NIOS中实现其功能,所以DS1302功能介绍我简单概括一下,有问题的百度一下就都知道了。
DS1302是DALLAS公司推出的涓流充电实时时钟芯片,内含一个实时时钟/日历和31字节静态RAM,仅需要三根线:RES(复位),I/O(数据线),SCLK(串行时钟)。时钟/RAM 的读/写数据以一个字节或多达 31 个字节的字符组方式通信 DS1302 工作时功耗很低,保持数据和时钟信息时功率小于 1mW。下面看一下电路图吧,下图所示,很简单,三根线就可以搞定了。
首先,我们需要在软核中构建三个PIO模块,方法跟以前讲的一样。需要注意的是RTC_DATA这个PIO,在构建的过程中,我们将其选择为双向的IO口,因为它是数据线,既要输入也需要输出,如下图所示,红圈处就是我们需要注意的地方,其他两个IO口设置为仅输出。
看看构建好以后的样子吧,如下图是所示
接下来就是自动分配地址,中断,然后开始编译,等待……
回到Quartus后,分配引脚,还是需要注意数据线,也是双向的,分配引脚的时候,要构建双向引脚(bidir),如下图所示。
都设置好以后,我们运行TCL脚本文件,然后开始编译,又是等待……
编译好后,我们打开NIOS II IDE,首先,还是需要编译一下,CTRL+b,编译之后,我们看看system.h有什么变化。观察后可以看出,里面对了,RTC部分的代码,如下表所示,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#define RTC_DATA_NAME "/dev/RTC_DATA"
#define RTC_DATA_TYPE "altera_avalon_pio"
#define RTC_DATA_BASE 0x00201030
……
/*
* RTC_SCLK configuration
*
*/
#define RTC_SCLK_NAME "/dev/RTC_SCLK"
#define RTC_SCLK_TYPE "altera_avalon_pio"
#define RTC_SCLK_BASE 0x00201040
……
/*
* RTC_nRST configuration
*
*/
#define RTC_NRST_NAME "/dev/RTC_nRST"
#define RTC_NRST_TYPE "altera_avalon_pio"
#define RTC_NRST_BASE 0x00201050
……
/*
|
在这些代码中,我们需要用到的是以下部分
1
2
3
|
#define RTC_DATA_BASE 0x00201030
#define RTC_SCLK_BASE 0x00201040
#define RTC_NRST_BASE 0x00201050
|
好的,接下来,我们就开始写程序吧
第一步,修改sopc.h文件,加入以下代码到sopc.h中
1
2
3
4
5
6
7
|
#define _RTC
#ifdef _RTC
#define RTC_SCLK ((PIO_STR *) RTC_SCLK_BASE)
#define RTC_DATA ((PIO_STR *) RTC_DATA_BASE)
#define RTC_RST ((PIO_STR *) RTC_NRST_BASE)
#endif /* _RTC */
|
没什么可说的,接下来我们在inc文件夹下建立ds1302.h,在其中加入以下内容,跟串口程序一样,里面也有个结构体,用这种方式整合所有的函数和变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/*
* ===============================================================
*
* Filename: ds1302.h
* Description:
* Version: 1.0
* Created:
* Revision: none
* Compiler: Nios II 9.0 IDE
* Author: AVIC
* Company: 金沙滩工作室
* ==============================================================
*/
#ifndef DS1302_H_
#define DS1302_H_
#include "../inc/sopc.h"
//对于双向的IO,操作的过程中要注意改变IO口的方向,置1为输出,置0为输入
#define RTC_DATA_OUT RTC_DATA->DIRECTION = 1
#define RTC_DATA_IN RTC_DATA->DIRECTION = 0
typedef
struct
{
void
(* set_time)(unsigned
char
*ti);
void
(* get_time)(
char
* ti);
}DS1302;
extern
DS1302 ds1302;
#endif /*DS1302_H_*/
|
准备工作都做好以后,接下来我们要做的就是写ds1302的驱动了,根据DS1302的时序图来进行编写,首先我来给看看时序图吧,如下图所示,这个是读数据的时序图,
这个是写数据时序图
还有一个有关寄存器的表格,大家也要注意看一下,如下所示,前面两列是读和写的地址,每次操作时,都先写地址,再传数据。
现在,我们就根据时序图来编写ds1302的驱动,在driver文件夹下建ds1302.c文件,然后添加以下内容,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
/*
* =============================================================
* Filename: ds1302.c
* Description:
* Version: 1.0
* Created: 2009-11-23
* Revision: none
* Compiler: Nios II 9.0 IDE
* Author: AVIC
* Company: 金沙滩工作室
*
* =============================================================
*/
#include "../inc/ds1302.h"
//函数声明
static
void
delay(unsigned
int
dly);
static
void
write_1byte_to_ds1302(unsigned
char
da);
static
unsigned
char
read_1byte_from_ds1302(
void
);
static
void
write_data_to_ds1302(unsigned
char
addr, unsigned
char
da);
static
unsigned
char
read_data_from_ds1302(unsigned
char
addr);
void
set_time(unsigned
char
*ti);
void
get_time(
char
*ti);
//对DS1302结构体进行初始化,注意结构体中函数指针的初始化方式
DS1302 ds1302={
.set_time = set_time,
.get_time = get_time
};
/*
* === FUNCTION ====================================================
* Name: delay
* Description: 延时函数
* ==================================================================
*/
void
delay(unsigned
int
dly)
{
for
(;dly>0;dly--);
}
/*
* === FUNCTION =================================================
* Name: write_1byte_to_ds1302
* Description: 向ds1302写入1 byte数据
* ===============================================================
*/
void
write_1byte_to_ds1302(unsigned
char
da)
{
unsigned
int
i;
//写数据的时候,RTC_DATA为输出,先设置其为输出
RTC_DATA_OUT;
//以下步骤是处理串行数据的的典型方法,一个位一个位的来判断
for
(i=8; i>0; i--)
{
if
((da&0x01)!= 0)
RTC_DATA->DATA = 1;
else
RTC_DATA->DATA = 0;
//根据芯片手册,适当加些延时,不是精确延时
delay(10);
RTC_SCLK->DATA = 1;
delay(20);
RTC_SCLK->DATA = 0;
delay(10);
da >>= 1;
}
}
/*
* === FUNCTION ==================================================
* Name: read_1byte_from_ds1302
* Description: 从ds1302读取1 byte数据
* ================================================================
*/
unsigned
char
read_1byte_from_ds1302(
void
)
{
unsigned
char
i;
unsigned
char
da = 0;
//当读数据的时候,我们要将数据IO设置为输入
RTC_DATA_IN;
//以下是典型的读串行数据的方法
for
(i=8; i>0; i--)
{
delay(10);
da >>= 1;
if
(RTC_DATA->DATA !=0 )
da += 0x80;
RTC_SCLK->DATA = 1;
delay(20);
RTC_SCLK->DATA = 0;
delay(10);
}
RTC_DATA_OUT;
return
(da);
}
/*
* === FUNCTION =================================================
* Name: write_data_to_ds1302
* Description: 向ds1302写入数据
* ===============================================================
*/
void
write_data_to_ds1302(unsigned
char
addr, unsigned
char
da)
{
RTC_DATA_OUT;
RTC_RST->DATA = 0;
//复位,低电平有效
RTC_SCLK->DATA = 0;
delay(40);
RTC_RST->DATA = 1;
//先写地址,再写数据,每次写1字节
write_1byte_to_ds1302(addr);
// 地址,命令
write_1byte_to_ds1302(da);
// 写1Byte数据
RTC_SCLK->DATA = 1;
RTC_RST->DATA = 0;
delay(40);
}
/*
* === FUNCTION ===================================================
* Name: read_data_from_ds1302
* Description: 从ds1302读取数据
* =================================================================
*/
unsigned
char
read_data_from_ds1302(unsigned
char
addr)
{
unsigned
char
da;
RTC_RST->DATA = 0;
RTC_SCLK->DATA = 0;
delay(40);
RTC_RST->DATA = 1;
//先写地址,再读数据
write_1byte_to_ds1302(addr);
da = read_1byte_from_ds1302();
RTC_SCLK->DATA = 1;
RTC_RST->DATA = 0;
delay(40);
return
(da);
}
/*
* === FUNCTION ==================================================
* Name: set_time
* Description: 设置时间
* ================================================================
*/
void
set_time(unsigned
char
*ti)
{
unsigned
char
i;
unsigned
char
addr = 0x80;
write_data_to_ds1302(0x8e,0x00);
// 控制命令,WP=0,写操作
for
(i =7;i>0;i--)
{
write_data_to_ds1302(addr,*ti);
// 秒 分 时 日 月 星期 年
ti++;
addr +=2;
}
write_data_to_ds1302(0x8e,0x80);
// 控制命令,WP=1,写保护
}
/*
* === FUNCTION ==================================================
* Name: get_time
* Description: 获取时间 ,读取的时间为BCD码,需要转换成十进制
* ================================================================
*/
void
get_time(
char
*ti)
{
unsigned
char
i;
unsigned
char
addr = 0x81;
char
time
;
for
(i=0;i<7;i++){
time
=read_data_from_ds1302(addr);
//读取的时间为BCD码
ti[i] =
time
/16*10+
time
%16;
//格式为: 秒 分 时 日 月 星期 年
addr += 2;
}
}
|
OK,我们的驱动写好了,现在我们来写一个main函数来验证一下我们的驱动是否好用吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#include <unistd.h>
#include "../inc/uart.h"
#include "../inc/ds1302.h"
#include <stdio.h>
unsigned
char
time
[7] = {0x00,0x19,0x14,0x17,0x03,0x17,0x10};
//格式为: 秒 分 时 日 月 星期 年
int
main()
{
unsigned
char
buffer[50]=
"\0"
;
//设置时间
ds1302.set_time(
time
);
while
(1){
//获取时间
ds1302.get_time(
time
);
//将我们要的时间格式化一下,如2010-4-4 15:25:00
sprintf
(buffer,
"20%d-%d-%d %d:%d:%d\n"
,
time
[6],
time
[4],
time
[3],
time
[2],
time
[1],
time
[0]);
//通过串口发送出去
uart.send_string(
sizeof
(buffer),buffer);
//延时1秒
usleep(1000000);
}
return
0;
}
|
在上面的程序中,我们获取时间后通过串口发送到上位机,这样也复习了我们上一节讲的串口程序。当然,大家也可以直接通过printf()打印出来。
在操作ds1302的时候有一点需要注意,ds1302的输入和输出都是8421BCD码进行的,所以我们需要对其进行转换。不过,输入的时候我是直接输入16进制,比如,我们设置分钟为10的话,我直接输入十六进制的0x10,这样就不需要转化了。而在输出的时候是必须要转化的,大家在写程序的时候注意这一点。
好了,我们来看看我们的劳动果实,看看串口传出的数据吧。