STM32F1系列的单片机本身自带的RTC实时时钟外设只是一个单纯的32位计数器,没有分立为年月日、小时、分钟、秒等寄存器,使用起来不是很方便。这时可以考虑使用外部RTC芯片,比如使用SPI接口的双向单数据线方式的DS1302实时时钟芯片,或是FSMC接口的带内部电池的DS12885、DS12887、DS12887A、DS12C887和DS12C887A等芯片。

【接线】

DS12C887的VCC接+5V,GND接地。MOT悬空,AD0~1接PD14~15,AD2~3接PD0~1,AD4~7接PE7~10。RESET引脚接PA8(这个可以随便接,与FSMC无关),DS接PD4,R/W接PD5,CS接PD7(这是100脚的单片机上唯一的FSMC片选引脚)。

PB7(NADV)必须通过一个反相器后才能接到AS,并且不可以用地址线A8~A25代替(DS12C887时序要求AS拉低后AD0~AD7上的地址信号才能撤销,不可同时撤销。如果AS接到AD8上,则地址信号AD8~AD0会被同时撤销,不符合时序要求)。其接法如下图所示:

特别注意74HC04的电源接的是3.3V,而DS12C887的电源接的是5V。

最好不要用一个三极管来代替74HC04反相器,因为三极管的切换速度太慢了,而且搞得不好功耗也会比74HC04高。例如,使用9012型的三极管,发射极接3.3V,基极通过一个10kΩ的电阻接PB7,集电极接PB12后再通过一个10kΩ的电阻接GND,运行下面的程序:

void test(void)
{uint8_t i;for (i = 0; i < 3; i++){printf("PB7=0, PB12=%d\n", GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12));GPIO_WriteBit(GPIOB, GPIO_Pin_7, Bit_SET);__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();printf("PB7=1, PB12=%d\n", GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12));GPIO_WriteBit(GPIOB, GPIO_Pin_7, Bit_RESET);__NOP();__NOP();__NOP();__NOP();__NOP();}
}

PB7为GPIO_Mode_Out_PP,PB12为GPIO_Mode_IN_FLOATING。输出结果:

PB7=0, PB12=1
PB7=1, PB12=1
PB7=0, PB12=1
PB7=1, PB12=1
PB7=0, PB12=1
PB7=1, PB12=1

可见输出端PB12全为1,短时间内根本无法反相。只有把NOP改成delay(1),降低速度后才能成功反相。若使用74HC04的话,即使程序中没有NOP也能完成反相。

【程序1(寄存器版)】

#include <stdio.h>
#include <stm32f10x.h>
#include <string.h>// 延时n毫秒
void delay(uint16_t nms)
{TIM6->ARR = 10 * nms - 1;TIM6->PSC = 7199;TIM6->EGR = TIM_EGR_UG;TIM6->CR1 = TIM_CR1_OPM | TIM_CR1_CEN;while (TIM6->CR1 & TIM_CR1_CEN);
}int fputc(int ch, FILE *fp)
{if (fp == stdout){if (ch == '\n'){while ((USART1->SR & USART_SR_TXE) == 0);USART1->DR = '\r';}while ((USART1->SR & USART_SR_TXE) == 0);USART1->DR = ch;}return ch;
}int main(void)
{char buf[20];RCC->AHBENR |= RCC_AHBENR_FSMCEN;RCC->APB1ENR = RCC_APB1ENR_TIM6EN;RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPDEN | RCC_APB2ENR_IOPEEN | RCC_APB2ENR_USART1EN;GPIOA->CRH = 0x444444b3; // PA8为RST复位引脚(默认输出低电平), PA9为串口1发送引脚GPIOB->CRL = 0xb4444444; // PB7为NADV, 取反后送到AS引脚上, 该引脚不可用地址线代替!GPIOD->CRL = 0xb4bb44bb; // PD0~1为AD2~3, PD4为NOE接DS引脚, PD5为NWE接RW引脚, PD7为NE1片选引脚接CSGPIOD->CRH = 0xbb444444; // PD14~15为AD0~1GPIOE->CRL = 0xb4444444; // PE7为AD4GPIOE->CRH = 0x44444bbb; // PE8~10为AD5~7USART1->BRR = 625; // 串口波特率为115200USART1->CR1 = USART_CR1_UE | USART_CR1_TE; // 串口1只允许发送// FSMC的Bank1, Subbank1设为8位NOR Flash地址/数据线复用模式, 关闭NWAIT引脚FSMC_Bank1->BTCR[0] &= ~(FSMC_BCR1_WAITEN | FSMC_BCR1_MWID);// 下面为可选配置, 用于加快访存速度// HCLK=72MHz时, DATAST的最小值为2, 即3xHCLK clock cyclesFSMC_Bank1->BTCR[1] = (FSMC_Bank1->BTCR[1] & ~(FSMC_BTR1_BUSTURN | FSMC_BTR1_DATAST | FSMC_BTR1_ADDHLD | FSMC_BTR1_ADDSET)) | FSMC_BTR1_DATAST_1 | FSMC_BTR1_ADDHLD_0;printf("STM32F103VE FSMC DS12C887\n");delay(200);GPIOA->BSRR = GPIO_BSRR_BS8; // RESET=1, 撤销复位信号// 读写自由SRAM区域strcpy((char *)0x60000033, "This is a string!");memcpy(buf, (char *)0x60000033, sizeof(buf));printf("str=%s\n", buf);// 读A~D寄存器printf("A=0x%02x B=0x%02x C=0x%02x D=0x%02x\n", *(__IO uint8_t *)0x6000000a, *(__IO uint8_t *)0x6000000b, *(__IO uint8_t *)0x6000000c, *(__IO uint8_t *)0x6000000d);while (1)__WFI();
}void HardFault_Handler(void)
{printf("Hard Error!\n");while (1);
}

【程序1运行结果】

STM32F103VE FSMC DS12C887
str=This is a string!
A=0x00 B=0x82 C=0x00 D=0x00

D寄存器的最高位为0,看来DS12C887芯片里面的电池早就没电了。。。。。

地址0x33~0x7f这一区域为自由SRAM,可以任意读写,不影响芯片功能。

以下为连线的实物图。我用的是带8MHz晶振的微雪STM32F103VET6核心板做的实验。

左边的芯片是DS12C887,右边那个小的芯片是74HC04反相器。

【程序2(库函数版)】

#include <stdio.h>
#include <stm32f10x.h>
#include <string.h>typedef __packed struct
{__IO uint8_t SEC;__IO uint8_t SECALR;__IO uint8_t MIN;__IO uint8_t MINALR;__IO uint8_t HOUR;__IO uint8_t HOURALR;__IO uint8_t DAY;__IO uint8_t DATE;__IO uint8_t MONTH;__IO uint8_t YEAR;__IO uint8_t CR1;__IO uint8_t CR2;__IO uint8_t CR3;__IO uint8_t CR4;__IO uint8_t RAM1[36]; // 0x0e-0x31__IO uint8_t CENTURY;__IO uint8_t RAM2[77]; // 0x33-0x7f
} DS12C887_TypeDef;#define RTC2 ((DS12C887_TypeDef *)0x60000000)// 延时n毫秒
void delay(uint16_t nms)
{TIM_TimeBaseInitTypeDef tim;TIM_TimeBaseStructInit(&tim);tim.TIM_Period = 10 * nms - 1;tim.TIM_Prescaler = 7199;TIM_TimeBaseInit(TIM6, &tim);TIM_ClearFlag(TIM6, TIM_FLAG_Update);TIM_SelectOnePulseMode(TIM6, TIM_OPMode_Single);TIM_Cmd(TIM6, ENABLE);while (TIM_GetFlagStatus(TIM6, TIM_FLAG_Update) == RESET);
}int fputc(int ch, FILE *fp)
{if (fp == stdout){if (ch == '\n'){while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, '\r');}while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, ch);}return ch;
}int main(void)
{FSMC_NORSRAMInitTypeDef fsmc;FSMC_NORSRAMTimingInitTypeDef fsmc_timing;GPIO_InitTypeDef gpio;USART_InitTypeDef usart;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_USART1, ENABLE);// PA8为RST复位引脚(默认输出低电平)gpio.GPIO_Mode = GPIO_Mode_Out_PP;gpio.GPIO_Pin = GPIO_Pin_8;gpio.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOA, &gpio);// PA9为串口1发送引脚gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Pin = GPIO_Pin_9;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio);// PB7为NADV, 取反后送到AS引脚上, 该引脚不可用地址线代替gpio.GPIO_Pin = GPIO_Pin_7;GPIO_Init(GPIOB, &gpio);// PD0~1为AD2~3, PD4为NOE接DS引脚, PD5为NWE接RW引脚, PD7为NE1片选引脚接CS, PD14~15为AD0~1gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_14 | GPIO_Pin_15;GPIO_Init(GPIOD, &gpio);// PE7~10为AD4~7gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;GPIO_Init(GPIOE, &gpio);// 初始化串口1USART_StructInit(&usart);usart.USART_BaudRate = 115200;usart.USART_Mode = USART_Mode_Tx;USART_Init(USART1, &usart);USART_Cmd(USART1, ENABLE);// FSMC的Bank1, Subbank1设为8位NOR Flash地址/数据线复用模式, 关闭NWAIT引脚fsmc.FSMC_ReadWriteTimingStruct = &fsmc_timing;fsmc.FSMC_WriteTimingStruct = &fsmc_timing;FSMC_NORSRAMStructInit(&fsmc);fsmc.FSMC_MemoryType = FSMC_MemoryType_NOR; // 存储器类型为NOR Flashfsmc.FSMC_WaitSignal = FSMC_WaitSignal_Disable; // 不使用NWAIT引脚fsmc_timing.FSMC_AddressHoldTime = 1;fsmc_timing.FSMC_AddressSetupTime = 0;fsmc_timing.FSMC_BusTurnAroundDuration = 0;fsmc_timing.FSMC_DataSetupTime = 2; // HCLK=72MHz时, DATAST的最小值为2, 即3xHCLK clock cyclesFSMC_NORSRAMInit(&fsmc);FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); // 虽然Subbank1默认是启用的, 但执行FSMC_NORSRAMInit函数时会被关闭, 所以需要再次开启printf("STM32F103VE FSMC DS12C887\n");delay(200);GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET); // RESET=1, 撤销复位信号// 读写自由SRAM区域printf("General-purpose RAM1 addr: 0x%08x\n", (uint32_t)RTC2->RAM1);printf("General-purpose RAM2 addr: 0x%08x\n", (uint32_t)RTC2->RAM2);strcpy((char *)RTC2->RAM1, "Flexible static memory controller");strcpy((char *)RTC2->RAM2, "Muxed mode - multiplexed asynchronous access to NOR Flash memory");printf("str1=%s\n", RTC2->RAM1);printf("str2=%s\n", RTC2->RAM2);// 读A~D寄存器printf("A=0x%02x B=0x%02x C=0x%02x D=0x%02x\n", RTC2->CR1, RTC2->CR2, RTC2->CR3, RTC2->CR4);while (1)__WFI();
}void HardFault_Handler(void)
{printf("Hard Error!\n");while (1);
}

【程序2运行结果】

STM32F103VE FSMC DS12C887
General-purpose RAM1 addr: 0x6000000e
General-purpose RAM2 addr: 0x60000033
str1=Flexible static memory controller
str2=Muxed mode - multiplexed asynchronous access to NOR Flash memory
A=0x00 B=0x82 C=0x00 D=0x00

【程序3:实际走时测试】

#include <stdio.h>
#include <stm32f10x.h>
#include "DS12C887.h"// 延时n毫秒
void delay(uint16_t nms)
{TIM_TimeBaseInitTypeDef tim;TIM_TimeBaseStructInit(&tim);tim.TIM_Period = 10 * nms - 1;tim.TIM_Prescaler = 7199;TIM_TimeBaseInit(TIM6, &tim);TIM_ClearFlag(TIM6, TIM_FLAG_Update);TIM_SelectOnePulseMode(TIM6, TIM_OPMode_Single);TIM_Cmd(TIM6, ENABLE);while (TIM_GetFlagStatus(TIM6, TIM_FLAG_Update) == RESET);
}int fputc(int ch, FILE *fp)
{if (fp == stdout){if (ch == '\n'){while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, '\r');}while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, ch);}return ch;
}void DS12C887_Init(void)
{FSMC_NORSRAMInitTypeDef fsmc;FSMC_NORSRAMTimingInitTypeDef fsmc_timing;GPIO_InitTypeDef gpio;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE);// PA8为RST复位引脚(默认输出低电平)gpio.GPIO_Mode = GPIO_Mode_Out_PP;gpio.GPIO_Pin = GPIO_Pin_8;gpio.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOA, &gpio);// PB7为NADV, 取反后送到AS引脚上, 该引脚不可用地址线代替gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Pin = GPIO_Pin_7;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &gpio);// PD0~1为AD2~3, PD4为NOE接DS引脚, PD5为NWE接RW引脚, PD7为NE1片选引脚接CS, PD14~15为AD0~1gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_14 | GPIO_Pin_15;GPIO_Init(GPIOD, &gpio);// PE7~10为AD4~7gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;GPIO_Init(GPIOE, &gpio);// FSMC的Bank1, Subbank1设为8位NOR Flash地址/数据线复用模式, 关闭NWAIT引脚fsmc.FSMC_ReadWriteTimingStruct = &fsmc_timing;fsmc.FSMC_WriteTimingStruct = &fsmc_timing;FSMC_NORSRAMStructInit(&fsmc);fsmc.FSMC_MemoryType = FSMC_MemoryType_NOR; // 存储器类型为NOR Flashfsmc.FSMC_WaitSignal = FSMC_WaitSignal_Disable; // 不使用NWAIT引脚fsmc_timing.FSMC_AddressHoldTime = 1;fsmc_timing.FSMC_AddressSetupTime = 0;fsmc_timing.FSMC_BusTurnAroundDuration = 0;fsmc_timing.FSMC_DataSetupTime = 2; // HCLK=72MHz时, DATAST的最小值为2, 即3xHCLK clock cyclesFSMC_NORSRAMInit(&fsmc);FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); // 虽然Subbank1默认是启用的, 但执行FSMC_NORSRAMInit函数时会被关闭, 所以需要再次开启delay(200);GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET); // RESET=1, 撤销复位信号
}int main(void)
{GPIO_InitTypeDef gpio;USART_InitTypeDef usart;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);// PA9为串口1发送引脚gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Pin = GPIO_Pin_9;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio);// 初始化串口1USART_StructInit(&usart);usart.USART_BaudRate = 115200;usart.USART_Mode = USART_Mode_Tx;USART_Init(USART1, &usart);USART_Cmd(USART1, ENABLE);DS12C887_Init();printf("DS12C887 RTC\n");// 写入时间if (RTC2->CR2 & RTC2_CR2_SET){RTC2->CR2 |= RTC2_CR2_DM | RTC2_CR2_24_12;RTC2->CENTURY = 20;RTC2->YEAR = 17;RTC2->MONTH = 9;RTC2->DATE = 27;RTC2->HOUR = 21;RTC2->MIN = 6;RTC2->SEC = 30;RTC2->DAY = 3; // 星期必须手动写入, 无法自动计算RTC2->CR1 = RTC2_CR1_DV1; // 打开晶振RTC2->CR2 &= ~RTC2_CR2_SET; // 时钟开始走时}// 第一次启动后需要等较长的时间时钟才能开始走时while (1){printf("%02d%02d-%02d-%02d", RTC2->CENTURY, RTC2->YEAR, RTC2->MONTH, RTC2->DATE);if ((RTC2->CR4 & RTC2_CR4_VRT) == 0)printf("!"); // 如果电池已耗尽, 则显示感叹号printf(" %02d:%02d:%02d ", RTC2->HOUR, RTC2->MIN, RTC2->SEC);printf("%c", "?MTWTFSS"[RTC2->DAY]); // 星期的第一个字母printf("%c", "?ouehrau"[RTC2->DAY]); // 星期的第二个字母printf("%c\n", "?neduitn"[RTC2->DAY]); // 星期的第三个字母// 等待时间更新完毕while ((RTC2->CR1 & RTC2_CR1_UIP) == 0);while (RTC2->CR1 & RTC2_CR1_UIP);}
}void HardFault_Handler(void)
{printf("Hard Error!\n");while (1);
}

【头文件DS12C887.h】

typedef __packed struct
{__IO uint8_t SEC;__IO uint8_t SECALR;__IO uint8_t MIN;__IO uint8_t MINALR;__IO uint8_t HOUR;__IO uint8_t HOURALR;__IO uint8_t DAY;__IO uint8_t DATE;__IO uint8_t MONTH;__IO uint8_t YEAR;__IO uint8_t CR1;__IO uint8_t CR2;__IO uint8_t CR3;__IO uint8_t CR4;__IO uint8_t RAM1[36]; // 0x0e-0x31__IO uint8_t CENTURY;__IO uint8_t RAM2[77]; // 0x33-0x7f
} DS12C887_TypeDef;#define RTC2 ((DS12C887_TypeDef *)0x60000000)#define RTC2_CR1_UIP 0x80
#define RTC2_CR1_DV2 0x40
#define RTC2_CR1_DV1 0x20
#define RTC2_CR1_DV0 0x10
#define RTC2_CR1_RS3 0x08
#define RTC2_CR1_RS2 0x04
#define RTC2_CR1_RS1 0x02
#define RTC2_CR1_RS0 0x01#define RTC2_CR2_SET 0x80
#define RTC2_CR2_PIE 0x40
#define RTC2_CR2_AIE 0x20
#define RTC2_CR2_UIE 0x10
#define RTC2_CR2_SQWE 0x08
#define RTC2_CR2_DM 0x04
#define RTC2_CR2_24_12 0x02
#define RTC2_CR2_DSE 0x01#define RTC2_CR3_IRQF 0x80
#define RTC2_CR3_PF 0x40
#define RTC2_CR3_AF 0x20
#define RTC2_CR3_UF 0x10#define RTC2_CR4_VRT 0x80void DS12C887_Init(void);

【程序3~5的运行结果】

DS12C887 RTC
2017-09-27! 21:06:41 Wed
2017-09-27! 21:06:42 Wed
2017-09-27! 21:06:43 Wed
2017-09-27! 21:06:44 Wed
2017-09-27! 21:06:45 Wed
2017-09-27! 21:06:46 Wed
2017-09-27! 21:06:47 Wed
2017-09-27! 21:06:48 Wed
2017-09-27! 21:06:49 Wed
2017-09-27! 21:06:50 Wed
2017-09-27! 21:06:51 Wed
2017-09-27! 21:06:52 Wed
2017-09-27! 21:06:53 Wed
2017-09-27! 21:06:54 Wed
2017-09-27! 21:06:55 Wed
2017-09-27! 21:06:56 Wed
2017-09-27! 21:06:57 Wed
2017-09-27! 21:06:58 Wed
2017-09-27! 21:06:59 Wed
2017-09-27! 21:07:00 Wed
2017-09-27! 21:07:01 Wed
2017-09-27! 21:07:02 Wed
2017-09-27! 21:07:03 Wed
2017-09-27! 21:07:04 Wed
2017-09-27! 21:07:05 Wed

【程序4:利用DS12C887的中断输出引脚IRQ唤醒处于STOP模式的STM32单片机,并输出当前时间】

中断引脚IRQ接到单片机的PB1引脚上。该程序的运行结果和上面的程序相同。

#include <stdio.h>
#include <stm32f10x.h>
#include "DS12C887.h"// 延时n毫秒
void delay(uint16_t nms)
{TIM_TimeBaseInitTypeDef tim;TIM_TimeBaseStructInit(&tim);tim.TIM_Period = 10 * nms - 1;tim.TIM_Prescaler = 7199;TIM_TimeBaseInit(TIM6, &tim);TIM_ClearFlag(TIM6, TIM_FLAG_Update);TIM_SelectOnePulseMode(TIM6, TIM_OPMode_Single);TIM_Cmd(TIM6, ENABLE);while (TIM_GetFlagStatus(TIM6, TIM_FLAG_Update) == RESET);
}int fputc(int ch, FILE *fp)
{if (fp == stdout){if (ch == '\n'){while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, '\r');}while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, ch);}return ch;
}void DS12C887_Init(void)
{FSMC_NORSRAMInitTypeDef fsmc;FSMC_NORSRAMTimingInitTypeDef fsmc_timing;GPIO_InitTypeDef gpio;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE);// PA8为RST复位引脚(默认输出低电平)gpio.GPIO_Mode = GPIO_Mode_Out_PP;gpio.GPIO_Pin = GPIO_Pin_8;gpio.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOA, &gpio);// PB1为中断引脚gpio.GPIO_Mode = GPIO_Mode_IPU; // 必须要设为带上拉电阻的输入gpio.GPIO_Pin = GPIO_Pin_1;GPIO_Init(GPIOB, &gpio);// PB7为NADV, 取反后送到AS引脚上, 该引脚不可用地址线代替gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Pin = GPIO_Pin_7;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &gpio);// PD0~1为AD2~3, PD4为NOE接DS引脚, PD5为NWE接RW引脚, PD7为NE1片选引脚接CS, PD14~15为AD0~1gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_14 | GPIO_Pin_15;GPIO_Init(GPIOD, &gpio);// PE7~10为AD4~7gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;GPIO_Init(GPIOE, &gpio);// FSMC的Bank1, Subbank1设为8位NOR Flash地址/数据线复用模式, 关闭NWAIT引脚fsmc.FSMC_ReadWriteTimingStruct = &fsmc_timing;fsmc.FSMC_WriteTimingStruct = &fsmc_timing;FSMC_NORSRAMStructInit(&fsmc);fsmc.FSMC_MemoryType = FSMC_MemoryType_NOR; // 存储器类型为NOR Flashfsmc.FSMC_WaitSignal = FSMC_WaitSignal_Disable; // 不使用NWAIT引脚fsmc_timing.FSMC_AddressHoldTime = 1;fsmc_timing.FSMC_AddressSetupTime = 0;fsmc_timing.FSMC_BusTurnAroundDuration = 0;fsmc_timing.FSMC_DataSetupTime = 2; // HCLK=72MHz时, DATAST的最小值为2, 即3xHCLK clock cyclesFSMC_NORSRAMInit(&fsmc);FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); // 虽然Subbank1默认是启用的, 但执行FSMC_NORSRAMInit函数时会被关闭, 所以需要再次开启delay(200);GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET); // RESET=1, 撤销复位信号
}void DS12C887_EnableIT(void)
{EXTI_InitTypeDef exti;RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);exti.EXTI_Line = EXTI_Line1;exti.EXTI_LineCmd = ENABLE;exti.EXTI_Mode = EXTI_Mode_Interrupt;exti.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_Init(&exti);NVIC_EnableIRQ(EXTI1_IRQn);
}// 根据当前时钟情况设置串口1的波特率寄存器
void init_usart1(void)
{USART_InitTypeDef usart;USART_StructInit(&usart);usart.USART_BaudRate = 115200;usart.USART_Mode = USART_Mode_Tx;USART_Init(USART1, &usart);
}int main(void)
{GPIO_InitTypeDef gpio;RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_TIM6, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);// PA9为串口1发送引脚gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Pin = GPIO_Pin_9;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio);init_usart1();USART_Cmd(USART1, ENABLE);DS12C887_Init();DS12C887_EnableIT();printf("DS12C887 RTC\n");// 写入时间if (RTC2->CR2 & RTC2_CR2_SET){RTC2->CR2 |= RTC2_CR2_DM | RTC2_CR2_24_12;RTC2->CENTURY = 20;RTC2->YEAR = 17;RTC2->MONTH = 9;RTC2->DATE = 27;RTC2->HOUR = 21;RTC2->MIN = 6;RTC2->SEC = 30;RTC2->DAY = 3; // 星期必须手动写入, 无法自动计算RTC2->CR1 = RTC2_CR1_DV1; // 打开晶振RTC2->CR2 &= ~RTC2_CR2_SET; // 时钟开始走时}RTC2->CR2 |= RTC2_CR2_UIE; // 开时钟更新中断NVIC_SystemLPConfig(NVIC_LP_SLEEPONEXIT, ENABLE); // 进入中断并退出中断后, 立即进入低功耗模式while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); // 等待串口字符发送完毕PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 进入STOP模式, 等待外部中断到来while (1); // 这里面的内容永远也不会执行
}// 第一次启动后需要等较长的时间时钟才能开始走时
void EXTI1_IRQHandler(void)
{EXTI_ClearFlag(EXTI_Line1); // 清除STM32的外部中断标志位RTC2->CR3; // 读C寄存器可清除中断标志位init_usart1(); // 从STOP模式唤醒后, 外部高速晶振已停振, 需重新设置波特率寄存器printf("%02d%02d-%02d-%02d", RTC2->CENTURY, RTC2->YEAR, RTC2->MONTH, RTC2->DATE);if ((RTC2->CR4 & RTC2_CR4_VRT) == 0)printf("!"); // 如果电池已耗尽, 则显示感叹号printf(" %02d:%02d:%02d ", RTC2->HOUR, RTC2->MIN, RTC2->SEC);printf("%c", "?MTWTFSS"[RTC2->DAY]); // 星期的第一个字母printf("%c", "?ouehrau"[RTC2->DAY]); // 星期的第二个字母printf("%c\n", "?neduitn"[RTC2->DAY]); // 星期的第三个字母while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); // 进入STOP模式前先等待串口字符发送完毕
}void HardFault_Handler(void)
{printf("Hard Error!\n");while (1);
}

【程序5:外部事件唤醒STM32单片机并输出当前时间】

DS12C887的中断引脚连接到EXTI_Line1上,但配置为事件方式(Event)而不是中断方式(Interrupt)唤醒STM32单片机。该程序的运行结果和上面的程序相同。

#include <stdio.h>
#include <stm32f10x.h>
#include "DS12C887.h"// 延时n毫秒
void delay(uint16_t nms)
{TIM_TimeBaseInitTypeDef tim;TIM_TimeBaseStructInit(&tim);tim.TIM_Period = 10 * nms - 1;tim.TIM_Prescaler = 7199;TIM_UpdateRequestConfig(TIM6, TIM_UpdateSource_Regular);TIM_TimeBaseInit(TIM6, &tim);TIM_SelectOnePulseMode(TIM6, TIM_OPMode_Single);TIM_Cmd(TIM6, ENABLE);// 进入睡眠模式, 等待定时器唤醒TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);NVIC_EnableIRQ(TIM6_IRQn);__WFI();
}int fputc(int ch, FILE *fp)
{if (fp == stdout){if (ch == '\n'){while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, '\r');}while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, ch);}return ch;
}void DS12C887_Init(void)
{FSMC_NORSRAMInitTypeDef fsmc;FSMC_NORSRAMTimingInitTypeDef fsmc_timing;GPIO_InitTypeDef gpio;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE);// PA8为RST复位引脚(默认输出低电平)gpio.GPIO_Mode = GPIO_Mode_Out_PP;gpio.GPIO_Pin = GPIO_Pin_8;gpio.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOA, &gpio);// PB1为中断引脚gpio.GPIO_Mode = GPIO_Mode_IPU; // 必须要设为带上拉电阻的输入gpio.GPIO_Pin = GPIO_Pin_1;GPIO_Init(GPIOB, &gpio);// PB7为NADV, 取反后送到AS引脚上, 该引脚不可用地址线代替gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Pin = GPIO_Pin_7;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &gpio);// PD0~1为AD2~3, PD4为NOE接DS引脚, PD5为NWE接RW引脚, PD7为NE1片选引脚接CS, PD14~15为AD0~1gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_14 | GPIO_Pin_15;GPIO_Init(GPIOD, &gpio);// PE7~10为AD4~7gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;GPIO_Init(GPIOE, &gpio);// FSMC的Bank1, Subbank1设为8位NOR Flash地址/数据线复用模式, 关闭NWAIT引脚fsmc.FSMC_ReadWriteTimingStruct = &fsmc_timing;fsmc.FSMC_WriteTimingStruct = &fsmc_timing;FSMC_NORSRAMStructInit(&fsmc);fsmc.FSMC_MemoryType = FSMC_MemoryType_NOR; // 存储器类型为NOR Flashfsmc.FSMC_WaitSignal = FSMC_WaitSignal_Disable; // 不使用NWAIT引脚fsmc_timing.FSMC_AddressHoldTime = 1;fsmc_timing.FSMC_AddressSetupTime = 0;fsmc_timing.FSMC_BusTurnAroundDuration = 0;fsmc_timing.FSMC_DataSetupTime = 2; // HCLK=72MHz时, DATAST的最小值为2, 即3xHCLK clock cyclesFSMC_NORSRAMInit(&fsmc);FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); // 虽然Subbank1默认是启用的, 但执行FSMC_NORSRAMInit函数时会被关闭, 所以需要再次开启delay(200);GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET); // RESET=1, 撤销复位信号
}void DS12C887_EnableIT(void)
{EXTI_InitTypeDef exti;RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);exti.EXTI_Line = EXTI_Line1;exti.EXTI_LineCmd = ENABLE;exti.EXTI_Mode = EXTI_Mode_Event; // Event模式exti.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_Init(&exti);
}int main(void)
{GPIO_InitTypeDef gpio;USART_InitTypeDef usart;RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_TIM6, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);// PA9为串口1发送引脚gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Pin = GPIO_Pin_9;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio);// 初始化并启用串口1USART_StructInit(&usart);usart.USART_BaudRate = 115200;usart.USART_Mode = USART_Mode_Tx;USART_Init(USART1, &usart);USART_Cmd(USART1, ENABLE);DS12C887_Init();DS12C887_EnableIT();printf("DS12C887 RTC\n");// 写入时间if (RTC2->CR2 & RTC2_CR2_SET){RTC2->CR2 |= RTC2_CR2_DM | RTC2_CR2_24_12;RTC2->CENTURY = 20;RTC2->YEAR = 17;RTC2->MONTH = 9;RTC2->DATE = 27;RTC2->HOUR = 21;RTC2->MIN = 6;RTC2->SEC = 30;RTC2->DAY = 3; // 星期必须手动写入, 无法自动计算RTC2->CR1 = RTC2_CR1_DV1; // 打开晶振RTC2->CR2 &= ~RTC2_CR2_SET; // 时钟开始走时}// 第一次启动后需要等较长的时间时钟才能开始走时RTC2->CR2 |= RTC2_CR2_UIE; // 开时钟更新中断while (1){while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); // 等待串口字符发送完毕PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFE); // 进入STOP模式, 等待外部中断到来// 唤醒后, 输出当前时间RTC2->CR3; // 读C寄存器可清除中断标志位USART_Init(USART1, &usart); // 从STOP模式唤醒后, 外部高速晶振已停振, 需重新设置波特率寄存器printf("%02d%02d-%02d-%02d", RTC2->CENTURY, RTC2->YEAR, RTC2->MONTH, RTC2->DATE);if ((RTC2->CR4 & RTC2_CR4_VRT) == 0)printf("!"); // 如果电池已耗尽, 则显示感叹号printf(" %02d:%02d:%02d ", RTC2->HOUR, RTC2->MIN, RTC2->SEC);printf("%c", "?MTWTFSS"[RTC2->DAY]); // 星期的第一个字母printf("%c", "?ouehrau"[RTC2->DAY]); // 星期的第二个字母printf("%c\n", "?neduitn"[RTC2->DAY]); // 星期的第三个字母}
}void HardFault_Handler(void)
{printf("Hard Error!\n");while (1);
}void TIM6_IRQHandler(void)
{TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
}

100脚的STM32F103VE单片机通过FSMC接口读写DS12C887时钟芯片中的寄存器相关推荐

  1. 基于微型计算机系统的实时时钟设计,基于单片机的DS12C887时钟芯片应用研究

    摘 要: 新型时钟芯片DS12C887具有计时精确.断电情况下运行十年以上不丢失资料等优点,在单片机计时系统中有着广泛应用.本文分析了DS12C887的功能特性,介绍了DS12C887与AT89C51 ...

  2. STM32F103ZE单片机FSMC接口读取NAND Flash芯片K9F1G08U0E的数据时出现数据丢失的解决办法

    [问题] STM32单片机使用FSMC读取K9F1G08U0E NAND Flash时,出现部分字节丢失的情况.例如:Flash存储器中存储有连续的0xff字节,则在进行连续读(Page Read)操 ...

  3. 【STemWin】STM32F103VE单片机用FSMC驱动ILI9341彩色触摸屏(触控芯片XPT2046),并裸机移植STemWin图形库(采用LCDConf_FlexColor.c模板)

    本程序采用的是STM32F103VE单片机,外部晶振的大小为8MHz,使用HAL库编写程序. 程序下载地址:https://pan.baidu.com/s/1-Q4LX3DkMjDcLod1m3r1o ...

  4. 51单片机DS12C887时钟芯片和1602液晶显示完善前人例程版本

    1.实现显示年月日,星期,时间 2.功能键,调整加,调整减,闹钟设置键4个按键 3.改进按键误入时钟或闹钟调整界面 ,时钟会停止计时. 改进后,如果不发生实际调整,时间不会停止计时. 4.改进闹钟没打 ...

  5. 利用51单片机和DS12C887时钟芯片制作万年历

    半个学期51的学习之后在终于动手敲了人生中第一个模块,期间出现了很多的bug,但还好的是最后都改正了 希望对大家有一点用处. (单片机小白一个,欢迎大佬指点.如果有大佬碰巧看见的话能不能帮我找一下相关 ...

  6. 单片机入门(三)----DS1302时钟芯片 可调时钟

    DS1302实现可调时钟 本代码以DS1302.定时器.独立按键.LCD1602实现了可调时钟. 原理图上的DS1302 按键在上一讲讲过了 LCD1602的代码以后再写 下面展示一些 内联代码片. ...

  7. 【蓝桥杯单片机(9)】DS1302时钟芯片学习笔记

    备赛目录 目录 1.DS1302简介 1.1功能 1.2通信方式 1.3电路连接图 2.DS1302通信时序 3.编写读取函数 3.1地址 3.2BCD码与十进制互换 4.DS1302时间读取实验 1 ...

  8. 使用DS12C887时钟芯片设计高精度时钟(单片机)

    一.项目要求 **1.**在1602液晶上显示年.月.日.星期.时.分.秒,并且按秒实时更新显示. **2.**具有闹铃设定及到时报警功能 **3.**能够使用板上的按键调节各个参数. **4.**每 ...

  9. 使用51单片机和DS1302时钟芯片做一个简易的电子时钟

    简易的电子时钟实验 一.前言 二.DS1302模块介绍 三.驱动DS1302的代码 3.1 初始化DS1302时钟芯片 3.2 读取DS1302时钟芯片的时间 3.3 设置DS1302时钟芯片的时间 ...

最新文章

  1. 新国立尤洋:夸父AI——用于大规模并行训练的统一深度学习系统
  2. 机器学习&AI之c++随笔(1)-配置tensorflow并运行第一个C++程序
  3. 130 道 K8s + Docker + DevOps 大厂面试题及知识点详解
  4. 第十节: 利用SQLServer实现Quartz的持久化和双机热备的集群模式 :
  5. 行健设计_行健要闻|“第四届‘天行健创新创业设计大赛”培训班成功举办
  6. 一键关闭android应用程序工具类
  7. CodeForces 617 E. XOR and Favorite Number
  8. html的字母u代表什么意思,html元素 u 标签的使用方法及作用
  9. IDEA 配置-XX:-RestrictContended参数
  10. 单片机音乐倒数计时器c语言,基于单片机音乐倒数计时器设计.doc
  11. cJSON使用详细教程 | 一个轻量级C语言JSON解析器
  12. C语言简单编程之一元二次方程求解
  13. 传送网发展与各种网元的功能
  14. pgsql 日期转换
  15. java单根结构_java“单根继承结构”
  16. java图书管理系统目的_java_图书管理系统java版,本文的目的就是通过图书管理 - phpStudy...
  17. 那些改变过世界的代码都是怎么写的?
  18. Servlet、HTTP协议、Request
  19. android碰撞动画,Android实战打飞机游戏之子弹生成与碰撞以及爆炸效果(5)
  20. 8位颜色Hex码 定义颜色透明度

热门文章

  1. Android Camera之Deferred Surface
  2. ajax如何传递josn数据,jq之ajax以及json数据传递
  3. Mac版 Adobe PS CC 2019 安装教程
  4. 低成本搭建Time Machine 服务器
  5. 最简单DIY基于51单片机的舵机控制器
  6. 说好的30天直播生死呢?--第一章 我的小伙伴们(一)
  7. JAVA_OPTS讲解【转】
  8. Learning Enriched Features for Real Image Restoration and Enhancement Paddle模型复现93号(1)
  9. 山寨电子以改良式研发谋求蜕变
  10. 使用Hutool工具完成发(带模板)邮件功能