昨天把github上面arduino驱动DS1302的代码移植到了STM32的程序当中。网址是https://github.com/msparks/arduino-ds1302
今天早上上机发现没数据,于是只能调试代码。
目录
1、硬件连接:
2、DS1302规格书注意点
3、代码
4、结果
STM32F103_PA10 ---> DS1302_RST(时能信号)
STM32F103_PA9 -------> DS1302_I/O(输入输出信号)
STM32F103_PB13 --------> DS1302_SCLK(时钟信号)
由于板子引出的引脚有限,引出的IO口只有UART1的PA10和PA9,PB13是板上和LED指示灯连接的,要飞线出来和DS1302模块连接。
第一是命令格式。要想把时间信息写入IC,或者从IC中读取时间信息。需要把IC中寄存器的值读出来。首先是要在命令中,加入寄存器的地址且说明是读操作还是写操作。数据要从低位开始传输。即如果传输一个控制命令,要先传输b0,而后再分别是b1、b2、b3、b4、b5、b6、b7。
第二是对RTC(real time clock)寄存器:有9个寄存器。每个寄存器位数是8位。如果要读秒这个寄存器,需要把控制命令置为0x81,然后读取1个字节的数据。
第三是读写寄存器的时序。对于DS1302这款IC来说,时钟上升沿时候,DS1302读取I/O的引脚的电压。STM32编程时候,最好当I/O电压稳定时,即SCLK上升沿到下一个下降沿之间,再让STM32读取I/O引脚电压。
而若要让DS1302输出寄存器的数据,要把时钟引脚拉低,DS1302会根据寄存器的数据,操作I/O引脚的电压。STM32编程时候,要在SCLK在高电平时候,把I/O引脚设为输入,在SCLK下降沿到下一个上升沿之间,读取I/O口数据。
tb店上面模块附送的代码是51的。初步一看,读和写中,SCLK、IO口两线的时序不太正确。
而arduino上面用clock burst的方式,IO口信号一直为0.
于是将代码修改为单次只读取一个寄存器的数据。
/*
* author: xxJian
* date: 2018-9-3
* ported from https://github.com/msparks/arduino-ds1302
*
*/
#include "DS1302Drv.h"
uint8_t time_buf_reg[8] = {0x20,0x10,0x09,0x14,0x23,0x59,0x50,0x02};//³õʼʱ¼ä
uint32_t b_date, b_month, b_year, b_hour, b_minute, b_second, b_day;
// Returns the decoded decimal value from a binary-coded decimal (BCD) byte.
// Assumes 'bcd' is coded with 4-bits per digit, with the tens place digit in
// the upper 4 MSBs.
uint8_t bcdToDec(const uint8_t bcd)
{
return (10 * ((bcd & 0xF0) >> 4) + (bcd & 0x0F));
}
// Returns the binary-coded decimal of 'dec'. Inverse of bcdToDec.
uint8_t decToBcd(const uint8_t dec)
{
uint8_t tens, ones;
tens = dec / 10;
ones = dec % 10;
return (tens << 4) | ones;
}
// Returns the hour in 24-hour format from the hour register value.
uint8_t hourFromRegisterValue(const uint8_t value) {
uint8_t adj;
if (value & 128) // 12-hour mode
adj = 12 * ((value & 32) >> 5);
else // 24-hour mode
adj = 10 * ((value & (32 + 16)) >> 4);
return (value & 15) + adj;
}
void DS1302_writeOut(const uint8_t value, uint8_t readAfter)
{
GPIO_InitTypeDef GPIO_InitStruct;
uint32_t i;
//pinMode(io_pin_, OUTPUT);
GPIO_InitStruct.Pin = DS1302_IO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(DS1302_IO_GPIO_Port, &GPIO_InitStruct);
for (i = 0; i < 8; i++) {
//digitalWrite(io_pin_, (value >> i) & 1);
if( (value >> i) & 1 ) {
HAL_GPIO_WritePin(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_PIN_RESET);
}
//HAL_Delay(1);
HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_RESET); //digitalWrite(sclk_pin_, HIGH);
HAL_Delay(2);
if (readAfter && i == 7) {
// We're about to read data -- ensure the pin is back in input mode
// before the clock is lowered.
//pinMode(io_pin_, INPUT);
HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_SET);
HAL_Delay(1);
HAL_GPIO_WritePin(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = DS1302_IO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(DS1302_IO_GPIO_Port, &GPIO_InitStruct);
//HAL_Delay(1);
} else {
HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_SET); //digitalWrite(sclk_pin_, LOW);
HAL_Delay(2); //delayMicroseconds(1);
}
}
}
uint8_t DS1302_readIn(void)
{
uint8_t input_value;
uint8_t bit;
uint32_t i;
input_value = 0;
bit = 0;
GPIO_InitTypeDef GPIO_InitStruct;
HAL_GPIO_WritePin(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_PIN_RESET);
//pinMode(io_pin_, INPUT);
GPIO_InitStruct.Pin = DS1302_IO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(DS1302_IO_GPIO_Port, &GPIO_InitStruct);
// Bits from the DS1302 are output on the falling edge of the clock
// cycle. This is called after readIn (which will leave the clock low) or
// writeOut(..., true) (which will leave it high).
for (i = 0; i < 8; i++) {
HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_SET); //digitalWrite(sclk_pin_, HIGH);
HAL_Delay(2); //delayMicroseconds(1);
HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_RESET); //digitalWrite(sclk_pin_, LOW);
HAL_Delay(1); //delayMicroseconds(1);
bit = HAL_GPIO_ReadPin(DS1302_IO_GPIO_Port, DS1302_IO_Pin); //bit = digitalRead(io_pin_);
HAL_Delay(1);
input_value |= (bit << i); // Bits are read LSB first.
}
return input_value;
}
uint8_t DS1302_readRegister(const uint8_t reg)
{
//const SPISession s(ce_pin_, io_pin_, sclk_pin_);
uint8_t cmd_byte;
uint8_t result;
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET);
cmd_byte = (0x81 | (reg << 1));
DS1302_writeOut(cmd_byte, DEF_true);
result = DS1302_readIn();
HAL_Delay(1);
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET);
return result;
}
void DS1302_writeRegister(const uint8_t reg, const uint8_t value)
{
//const SPISession s(ce_pin_, io_pin_, sclk_pin_);
uint8_t cmd_byte;
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET);
cmd_byte = (0x80 | (reg << 1));
DS1302_writeOut(cmd_byte, DEF_false);
DS1302_writeOut(value, DEF_false);
HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET);
}
void DS1302_writeProtect(const uint8_t enable)
{
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET);
DS1302_writeRegister(kWriteProtectReg, (enable << 7));
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET);
}
void DS1302_halt(const uint8_t enable)
{
uint8_t sec;
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET);
sec = DS1302_readRegister(kSecondReg);
sec &= ~(1 << 7);
sec |= (enable << 7);
DS1302_writeRegister(kSecondReg, sec);
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET);
}
void DS1302_writeRam(const uint8_t address, const uint8_t value)
{
if (address >= kRamSize) {
return;
}
DS1302_writeRegister(kRamAddress0 + address, value);
}
uint8_t DS1302_readRam(const uint8_t address)
{
if (address >= kRamSize) {
return 0;
}
return DS1302_readRegister(kRamAddress0 + address);
}
void DS1302_writeRamBulk(const uint8_t* const data, int len)
{
if (len <= 0) {
return;
}
if (len > kRamSize) {
len = kRamSize;
}
//const SPISession s(ce_pin_, io_pin_, sclk_pin_);
DS1302_writeOut(kRamBurstWrite, DEF_false);
for (int i = 0; i < len; ++i) {
DS1302_writeOut(data[i], DEF_false);
}
}
void DS1302_readRamBulk(uint8_t* const data, int len)
{
if (len <= 0) {
return;
}
if (len > kRamSize) {
len = kRamSize;
}
//const SPISession s(ce_pin_, io_pin_, sclk_pin_);
DS1302_writeOut(kRamBurstRead, DEF_true);
for (int i = 0; i < len; ++i) {
data[i] = DS1302_readIn();
}
}
void DS1302_timeRead()
{
//const SPISession s(ce_pin_, io_pin_, sclk_pin_);
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET);
//Time t(2099, 1, 1, 0, 0, 0, Time::kSunday);
//DS1302_writeOut(kClockBurstRead, DEF_true);
time_buf_reg[7] = DS1302_readRegister(kSecondReg)& 0x7F; //sec
time_buf_reg[6] = DS1302_readRegister(kMinuteReg); //min
time_buf_reg[5] = DS1302_readRegister(kHourReg); //hr
time_buf_reg[4] = DS1302_readRegister(kDateReg); //date
time_buf_reg[3] = DS1302_readRegister(kMonthReg); //mon
time_buf_reg[2] = DS1302_readRegister(kDayReg); //day
time_buf_reg[1] = DS1302_readRegister(kYearReg); //yr
/*
time_buf_reg[7] = DS1302_readIn() & 0x7F; //sec
time_buf_reg[6] = DS1302_readIn(); //min
time_buf_reg[5] = DS1302_readIn(); //hr
time_buf_reg[4] = DS1302_readIn(); //date
time_buf_reg[3] = DS1302_readIn(); //mon
time_buf_reg[2] = DS1302_readIn(); //day
time_buf_reg[1] = DS1302_readIn(); //yr
*/
b_year = 2000 + bcdToDec(time_buf_reg[1]);
b_day = bcdToDec(time_buf_reg[2]);
b_month = bcdToDec(time_buf_reg[3]);
b_date = bcdToDec(time_buf_reg[4]);
b_hour = hourFromRegisterValue(time_buf_reg[5]);
b_minute = bcdToDec(time_buf_reg[6]);
b_second = bcdToDec(time_buf_reg[7]);
HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET);
}
void DS1302_timeWrite(void)
{
// We want to maintain the Clock Halt flag if it is set.
uint8_t ch_value;
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET);
ch_value = DS1302_readRegister(kSecondReg) & 0x80;
//const SPISession s(ce_pin_, io_pin_, sclk_pin_);
//DS1302_writeRegister(kSecondReg, decToBcd(b_second)); //DS1302¸Õ°²×°Éϵç³Ø£¬ÔËÐÐÕâÐС£·ñÔòÔËÐÐÏÂÃæÄÇÐÐ
DS1302_writeRegister(kSecondReg,ch_value | decToBcd(b_second));
DS1302_writeRegister(kMinuteReg,decToBcd(b_minute));
DS1302_writeRegister(kHourReg,decToBcd(b_hour));
DS1302_writeRegister(kDateReg,decToBcd(b_date));
DS1302_writeRegister(kMonthReg,decToBcd(b_month));
DS1302_writeRegister(kDayReg ,decToBcd(b_day));
DS1302_writeRegister(kYearReg,decToBcd(b_year - 2000));
/*
DS1302_writeOut(kClockBurstWrite, DEF_false);
DS1302_writeOut(ch_value | decToBcd(b_second), DEF_false);
DS1302_writeOut(decToBcd(b_minute), DEF_false);
DS1302_writeOut(decToBcd(b_hour), DEF_false);
DS1302_writeOut(decToBcd(b_date), DEF_false);
DS1302_writeOut(decToBcd(b_month), DEF_false);
DS1302_writeOut(decToBcd(b_day), DEF_false);
DS1302_writeOut(decToBcd(b_year - 2000), DEF_false);
// All clock registers *and* the WP register have to be written for the time
// to be set.
DS1302_writeOut(0, DEF_false); // Write protection register.
DS1302_writeOut(0, DEF_false); // Trickle Charge register.
*/
HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET);
}
#ifndef __DS1302DRV_H__
#define __DS1302DRV_H__
#include "stm32f1xx_hal.h"
#define DEF_true 1
#define DEF_false 0
#define kRamSize 31
#define DS1302_SCLK_GPIO_Port LED5_GPIO_Port
#define DS1302_SCLK_Pin LED5_Pin
typedef enum
{
kSecondReg = 0,
kMinuteReg = 1,
kHourReg = 2,
kDateReg = 3,
kMonthReg = 4,
kDayReg = 5,
kYearReg = 6,
kWriteProtectReg = 7,
kChargeReg = 8,
// The RAM register space follows the clock register space.
kRamAddress0 = 32
} DS1302_Register;
typedef enum
{
kClockBurstRead = 0xBF,
kClockBurstWrite = 0xBE,
kRamBurstRead = 0xFF,
kRamBurstWrite = 0xFE
} DS1302_Command;
typedef enum
{
kSunday = 1,
kMonday = 2,
kTuesday = 3,
kWednesday = 4,
kThursday = 5,
kFriday = 6,
kSaturday = 7
} DS1302_Day;
extern uint8_t time_buf_reg[8];
uint8_t bcdToDec(const uint8_t bcd) ;
uint8_t decToBcd(const uint8_t dec) ;
uint8_t hourFromRegisterValue(const uint8_t value);
void DS1302_writeOut(const uint8_t value, uint8_t readAfter) ;
uint8_t DS1302_readIn(void) ;
uint8_t DS1302_readRegister(const uint8_t reg) ;
void DS1302_writeRegister(const uint8_t reg, const uint8_t value) ;
void DS1302_writeProtect(const uint8_t enable) ;
void DS1302_halt(const uint8_t enable) ;
void DS1302_writeRam(const uint8_t address, const uint8_t value) ;
uint8_t DS1302_readRam(const uint8_t address) ;
void DS1302_writeRamBulk(const uint8_t* const data, int len) ;
void DS1302_readRamBulk(uint8_t* const data, int len) ;
//Top layer function
void DS1302_timeRead(void) ;
void DS1302_timeWrite(void) ;
#endif /* __DS1302DRV_H__ */
需要注意的是,在主函数我们需要使用的两个函数为:DS1302_timeRead()和DS1302_timeWrite()
这张图是用逻辑分析仪观察了DS1302_timeRead()函数的数据波形。
第一通道是时钟SCLK。第二通道是IO,第三通道是CE/RST。程序中,每读写完一个字节,即拉低了CE。让IC重新到位。
读取到的寄存器分别是:秒(0-59)、分(0-59)、时(0-23)、日期(1-31)、月份(1-12)、星期(1-7)、年份(00-99)
以下是读取秒寄存器的时序。
首先控制命令是0x81,SCLK是上升沿有效。而8位后是DS1302控制IO口,是SCLK下降沿IO口电压变化。顺序上,数据是10001100。由于数据传输是LSB开始,因此需要转换一下,是00110001即0x31。即当时是31秒。
由于DS1302数据存储,是用BCD码。
time_buf_reg[8]这个数据中,分别存放了DS1302中7个寄存器的读取值。见一下代码。
time_buf_reg[7] = DS1302_readRegister(kSecondReg); //sec
time_buf_reg[6] = DS1302_readRegister(kMinuteReg); //min
time_buf_reg[5] = DS1302_readRegister(kHourReg); //hr
time_buf_reg[4] = DS1302_readRegister(kDateReg); //date
time_buf_reg[3] = DS1302_readRegister(kMonthReg); //mon
time_buf_reg[2] = DS1302_readRegister(kDayReg); //day
time_buf_reg[1] = DS1302_readRegister(kYearReg); //yr
time_buf_reg[0]存放的是0x20。主要因为DS1302用1个8位的寄存器存放年份,范围只有0-99。也就是配合了0x20,所表达的范围是2000-2099。
下图是实时仿真观察到time_buf_reg的数据。今天是2018年星期二,9月4日,当时时间为15:13:31。可以见到,DS1302存放数据的格式,是BCD码。用16进制,肉眼很方便的看到当前的日期。
用github那套代码,使用了bcdToDec()函数,可方便的转换为16进制数据。便于计算、也便于转为字符串发送到上位机显示。
下图中,时间已经变为了14:39:56
看来三天内搞好这个也是有点压力的。好在根据时序图来编,没出现大问题。另外由于不是做产品,就先用IO口,暂时没用SPI。
2019年5月11日更新:有一段时间DS1302模块没有安装纽扣电池,重新使用本次代码发现有点问题。经调试发现,DS1302_timeWrite() 函数内,有一行代码是专门判断Clock Halt(时间停止)标志位是否设置的。估计是DS1302初安装纽扣电池上电时,这个时钟是停止计算的。因此,上纽扣电池的第一次执行,需要使用这行:
DS1302_writeRegister(kSecondReg, decToBcd(b_second));
随后,才可以使用这行:
DS1302_writeRegister(kSecondReg,ch_value | decToBcd(b_second));