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

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片选引脚接CS
	GPIOD->CRH = 0xbb444444; // PD14~15为AD0~1
	GPIOE->CRL = 0xb4444444; // PE7为AD4
	GPIOE->CRH = 0x44444bbb; // PE8~10为AD5~7
	
	USART1->BRR = 625; // 串口波特率为115200
	USART1->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 cycles
	FSMC_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~1
	gpio.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~7
	gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
	GPIO_Init(GPIOE, &gpio);
	
	// 初始化串口1
	USART_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 Flash
	fsmc.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 cycles
	FSMC_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~1
	gpio.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~7
	gpio.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 Flash
	fsmc.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 cycles
	FSMC_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);
	
	// 初始化串口1
	USART_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 0x80

void 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~1
	gpio.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~7
	gpio.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 Flash
	fsmc.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 cycles
	FSMC_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~1
	gpio.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~7
	gpio.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 Flash
	fsmc.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 cycles
	FSMC_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);
	
	// 初始化并启用串口1
	USART_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);
}


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值