鉴于网上铺天盖地的DS1302设备驱动都是使用GPIO口进行模拟通讯时序,在这里使用STM32片内SPI控制器进行读写通信,由此代码结构更简洁,通讯速率更快
DS1302芯片简介
DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。
通讯时序
DS1302的通信协议与标准SPI协议十分类似,不同的是片选引脚CE的上升沿选中芯片,开始建立通信,下降沿结束通信,这一点与标准SPI正相反,另外需要注意的是,DS1302的数据引脚是半双工模式的,主机发送的命令帧和从机回复的数据帧都经过这根信号线。
这里我们只实现DS1302的RTC功能,采用单步读,单步写的通讯方式,在单步写时序中,MCU先向DS1302发送控制字节,字节的MSB为0表示写方向,A0-A4决定要写入的寄存器地址,紧接着是发送所要写入的数据,从低位开始传输。通讯过程中,数据随着时间线的上升沿一位一位地锁存至DS1302的内部寄存器。 单步读时序中,MCU先发送控制字,这里与写时序类似,紧接着,在每时钟信号的下降沿,DS1302将RTC数据放入总线,而MCU在每个时钟信号的上升沿对总线进行采样,完成对数据的锁存,流程如下图
上代码
以下是DS1302头文件,我们这里使用SPI1外设,CE_H和CE_L表示片选信号的高低,将DS1302的数据线接入STM32的MOSI引脚,WRITE_MODE()和READ_MODE()是通讯过程中发射和接收的模式切换,先禁用SPI,切换完成后打开SPI.
#ifndef __DS1302_H
#define __DS1302_H
#include "stm32f4xx.h"
#define DS1302_GPIO GPIOA
#define DS1302_RCC_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DS1302_SPI SPI1
#define DS1302_SPI_RCC_APBx_CLK_FUN RCC_APB2PeriphClockCmd
#define DS1302_SPI_CLK RCC_APB2Periph_SPI1
#define DS1302_GPIO_AF GPIO_AF_SPI1
#define DS1302_GPIO_SPI_SCK_PinSource GPIO_PinSource5
#define DS1302_GPIO_SPI_MOSI_PinSource GPIO_PinSource7
#define DS1302_CE_PIN GPIO_Pin_4
#define DS1302_SCK_PIN GPIO_Pin_5
#define DS1302_MOSI_PIN GPIO_Pin_7
#define READ_MODE() SPI_Cmd(DS1302_SPI,DISABLE); \
SPI_BiDirectionalLineConfig(DS1302_SPI,SPI_Direction_Rx); \
SPI_Cmd(DS1302_SPI,ENABLE);
#define WRITE_MODE() SPI_Cmd(DS1302_SPI,DISABLE); \
SPI_BiDirectionalLineConfig(DS1302_SPI,SPI_Direction_Tx); \
SPI_Cmd(DS1302_SPI,ENABLE);
#define CE_H GPIO_SetBits(DS1302_GPIO,DS1302_CE_PIN);
#define CE_L GPIO_ResetBits(DS1302_GPIO,DS1302_CE_PIN);
void DS1302_Config(void);
void DS1302_init(void);
void DS1302_getRTC(void);
#endif
DS1302.c文件
#include "DS1302.h"
#include "delay.h"
unsigned char time_buf[]={46,59,23,0,0,0,0};
unsigned char ReadBuff[7];
static void DS1302_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd( DS1302_RCC_GPIO_CLK, ENABLE);
GPIO_PinAFConfig(DS1302_GPIO,DS1302_GPIO_SPI_SCK_PinSource,DS1302_GPIO_AF);
GPIO_PinAFConfig(DS1302_GPIO,DS1302_GPIO_SPI_MOSI_PinSource,DS1302_GPIO_AF);
GPIO_InitStructure.GPIO_Pin = DS1302_SCK_PIN|DS1302_MOSI_PIN|DS1302_CE_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = DS1302_CE_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
GPIO_ResetBits(DS1302_GPIO, DS1302_CE_PIN);
}
static void DS1302_SPI_Config(void)
{
SPI_InitTypeDef SPI_InitStructure;
DS1302_SPI_RCC_APBx_CLK_FUN(DS1302_SPI_CLK,ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_LSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(DS1302_SPI, &SPI_InitStructure);
SPI_Cmd(DS1302_SPI, ENABLE);
}
void DS1302_Config(void)
{
DS1302_GPIO_Config();
DS1302_SPI_Config();
}
void Write_Ds1302_Byte(u8 byte)
{
while (SPI_I2S_GetFlagStatus(DS1302_SPI, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(DS1302_SPI, byte);
while(SPI_I2S_GetFlagStatus(DS1302_SPI,SPI_I2S_FLAG_BSY));
}
void Write_Ds1302( unsigned char address,unsigned char dat )
{
WRITE_MODE();
CE_H;
Write_Ds1302_Byte(address);
Write_Ds1302_Byte((dat/10<<4)|(dat%10));
CE_L;
}
unsigned char Read_Ds1302 ( unsigned char address )
{
unsigned char temp=0x00,dat1,dat2;
WRITE_MODE();
CE_H;
Write_Ds1302_Byte(address);
delay_us(3);
READ_MODE();
while (SPI_I2S_GetFlagStatus(DS1302_SPI, SPI_I2S_FLAG_RXNE) == RESET);
CE_L;
temp = SPI_I2S_ReceiveData(DS1302_SPI);
SPI_Cmd(DS1302_SPI,DISABLE);
dat1=temp/16;
dat2=temp%16;
temp=dat1*10+dat2;
return temp;
}
void DS1302_init(void)
{
unsigned char i,add;
add=0x80;
Write_Ds1302(0x8e,0x00);
for(i=0;i<7;i++)
{
Write_Ds1302(add,time_buf[i]);
add=add+2;
}
Write_Ds1302(0x8e,0x80);
}
void DS1302_getRTC(void)
{
unsigned char i,add = 0x81;
Write_Ds1302(0x8e,0x00);
for(i=0;i<7;i++)
{
ReadBuff[i]=Read_Ds1302(add);
add = add+2;
}
Write_Ds1302(0x8e,0x80);
}
SPI外设初始化
我们可以参照DS1302的通讯协议来初始化STM32的SPI外设,这里我们先选用STM32F407为例,在SPI初始化结构体中,先配置为半双工发送模式,时钟相位为空闲时低电平,时钟信号的奇数边沿移出SPI移位寄存器中的数据,或对DS1302方向的数据进行采样,数据低位先行,时钟频率在供电3.3V的条件下配置为约1.3MHz,即对APB2总线时钟进行÷64分频
static void DS1302_SPI_Config(void)
{
SPI_InitTypeDef SPI_InitStructure;
DS1302_SPI_RCC_APBx_CLK_FUN(DS1302_SPI_CLK,ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; //先初始化为发送模式
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; //3.3V电源下1.3MHz左右
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_LSB; //低位先行
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(DS1302_SPI, &SPI_InitStructure);
SPI_Cmd(DS1302_SPI, ENABLE);
}
对底层读写函数的解释
1.void Write_Ds1302_Byte(u8 byte)
void Write_Ds1302_Byte(u8 byte)
{
while (SPI_I2S_GetFlagStatus(DS1302_SPI, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(DS1302_SPI, byte);
while(SPI_I2S_GetFlagStatus(DS1302_SPI,SPI_I2S_FLAG_BSY));
}
该函数实现对DS1302写一字节数据,首先等待SPI外设的发送寄存器为空,然后对发送寄存器写入将要发送的一字节,最后等待移位寄存器将发送寄存器的数据一位位移走,如果不等待BSY事件则是当数据转移至移位寄存器后立刻跳出函数执行其他操作,会导致通信失败。
2.unsigned char Read_Ds1302 ( unsigned char address )
unsigned char Read_Ds1302 ( unsigned char address )
{
unsigned char temp=0x00,dat1,dat2;
WRITE_MODE();
CE_H;
Write_Ds1302_Byte(address);
delay_us(3);
READ_MODE();
while (SPI_I2S_GetFlagStatus(DS1302_SPI, SPI_I2S_FLAG_RXNE) == RESET);
CE_L;
temp = SPI_I2S_ReceiveData(DS1302_SPI);
SPI_Cmd(DS1302_SPI,DISABLE);
dat1=temp/16;
dat2=temp%16;
temp=dat1*10+dat2;
return temp;
}
这里在给DS1302发送所要读取的寄存器地址,适当延时后,打开SPI的读模式,此时STM32将立刻产生1.3MHz的SPI时钟同步信号,因此紧接着我们需要立刻准备接收DS1302方向的RTC数据,即开始等待RXNE事件(接收寄存器非空),接收完一字节数据后立刻拉低片选线结束当前通信,在获得RTC数据后禁用SPI,掐断时钟信号,对DS1302的BCD码转为十进制数据后返回。
主函数代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "ds1302.h"
extern u8 ReadBuff[8];
int main(void)
{
delay_init(168); //延时初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
uart_init(115200); //串口初始化波特率为115200
LED_Init();
DS1302_Config();
DS1302_init();
while (1)
{
delay_ms(10);
DS1302_getRTC();
for(int8_t i=2;i>=0;i--)
printf("%d ",ReadBuff[i]);
putchar('\r');
putchar('\n');
}
}