STC进阶开发(三)蜂鸣器、RTC时钟、I2C总线、外部中断、RTC闹钟设置、RTC计时器设置

前言

        这一期我们首先学习如何让蜂鸣器响起来,并且如何让蜂鸣器发出简单的歌曲,然后我们介绍RTC时钟,要想明白RTC时钟,我们还需要先介绍I2C总线和外部中断。接下来就开始这一期的学习吧!

蜂鸣器

简单介绍

        蜂鸣器是一种能够产生固定频率的声音的电子元件。它通常由振膜、震荡器、放大器和声音反馈电路等部分组成。振膜是蜂鸣器中最核心的部分,它能够将电信号转换为机械振动,产生声音。震荡器提供稳定的电信号,用于驱动振膜产生振动。放大器用于放大电信号的幅度,以便产生足够的声音。声音反馈电路可以提供反馈信号,帮助系统稳定。

原理图

分析:由原理图可以看到这里用到的是NPN型三极管,当电压为高电压时三极管导通,并且是由引脚P00控制的。

代码演示

简单发声

#include "Delay.h"
#include "GPIO.h"

#define BUZZER P00

void GPIO_config(void) {
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_0;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_OUT_PP;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化
}



void main(){
	
	GPIO_config();
	
	while(1){
		BUZZER = 1;
		delay_ms(1);
		BUZZER = 0;
		delay_ms(1);
		
	}
}

Timer测试发声

#include "GPIO.h"
#include "Delay.h"
#include "Timer.h"
#include "NVIC.h"


#define BUZZER P00


//小字二组: C`	   D`     E`   F`	  G`	A`	  B`    C``
u16 hz[] = {1047, 1175, 1319, 1397, 1568, 1760, 1976, 2093};

void GPIO_config(void) {
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_0;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_OUT_PP;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化
}



void	Timer_config(u16 hz)
{
	TIM_InitTypeDef		TIM_InitStructure;						//结构定义
	//定时器0做16位自动重装, 中断频率为1000HZ
	TIM_InitStructure.TIM_Mode      = TIM_16BitAutoReload;	//指定工作模式,   TIM_16BitAutoReload,TIM_16Bit,TIM_8BitAutoReload,TIM_16BitAutoReloadNoMask
	TIM_InitStructure.TIM_ClkSource = TIM_CLOCK_1T;		//指定时钟源,     TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_Ext
	TIM_InitStructure.TIM_ClkOut    = DISABLE;				//是否输出高速脉冲, ENABLE或DISABLE
	
	/*
			1. 这里使用  65536UL - (24M / 1000)  :  相当于是还要数24000下就数满,然后溢出触发中断函数
			2. MCU数24000 下,需要1ms时间,所以就等价于1ms触发一次中断函数。
			3. MAIN_Fosc / 1000 :  表示1ms 就触发一次中断,我们在中断里面完成蜂鸣器的发声和停止
				3.1 如果有1000次中断,那么就相当于是 发声的次数 + 停止的次数  = 1000次  ====> 500HZ
				3.2 如果有2000次中断,那么就相当于是 发声的次数 + 停止的次数  = 2000次  ====> 1000HZ
				3.3 如果有4000次中断,那么就相当于是 发声的次数 + 停止的次数  = 4000次  ====> 2000HZ
	*/
	TIM_InitStructure.TIM_Value     = 65536UL - (MAIN_Fosc / (hz * 2) );		//初值,
	TIM_InitStructure.TIM_Run       = ENABLE;				//是否初始化后启动定时器, ENABLE或DISABLE
	Timer_Inilize(Timer0,&TIM_InitStructure);				//初始化Timer0	  Timer0,Timer1,Timer2,Timer3,Timer4
	NVIC_Timer0_Init(ENABLE,Priority_0);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
}

// 这是定时器的中断函数, 每隔1ms 就调用一次:: 表示来1000次这个函数,就有500个启动,500次停--->500HZ 
void timer0_handler(){
	
		/*
			1ms来第一次的时候,让蜂鸣器发声,下一个1ms来的时候就让它停止,再来下一个1ms就让它发声,下一个1ms来的时候就让它停止....
		*/
		BUZZER = ~BUZZER;
}


void main(){
	u8 count = 0 ;
	
	//0. 中断总开关
	EA = 1;

	//1. IO模式
	GPIO_config();
	
	//2. 定时器配置 :: 不要在这里调用Timer的配置了,因为Timer的配置需要一个参数。参数还是变化的。
	//Timer_config();

	
	while(1){
		
		Timer_config(hz[count]);
		count++;
		
		if(count > 7){
			count = 0 ;
		}
		
		delay_ms(250);
		delay_ms(250);
	}
}

注:配置Timer初值是个需要注意的点,具体注释已经在代码中标明,请详细阅读。

PWM测试发声

#include "GPIO.h"
#include "Delay.h"
#include "STC8H_PWM.h"
#include "NVIC.h"
#include "Switch.h"

#define BUZZER P00

//小字二组: C`	   D`     E`   F`	  G`	A`	  B`    C``
u16 hz[] = {1047, 1175, 1319, 1397, 1568, 1760, 1976, 2093};

void GPIO_config(void) {
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_0;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_OUT_PP;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化
}

//#define PERIOD  MAIN_Fosc / 1000

void PWM_config(u16 hz){
	
		//周期时间
	// 分母是1000,即表示是1000HZ
	//u16 PERIOD = MAIN_Fosc / 1000;
	
	//如果你希望是多少hz, 那么就传递多少进来
	u16 PERIOD = MAIN_Fosc / hz;
	
	PWMx_InitDefine init;
	
	init.PWM_Mode = CCMRn_PWM_MODE2;			//模式,   CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2

	
	/*
		周期时间: 
			1. 上面使用时钟主频 / 1000 即表示 24000是一个周期,也就是1ms 是一个周期。
			2. 1s钟时间被切割成了1000份。 
	*/
	init.PWM_Period = PERIOD - 1;		//周期时间,   0~65535
	
	/*
		1. 占空比表示高低电平的占空比:
		2. 想让蜂鸣器发声:BUZZER = 1 (高电平) , 想让蜂鸣器不发声: BUZZER = 0 (低电平)
		3. 我们希望在一个周期内(1ms)内有一半时间发声,一把时间不发生。
		4. 如果周期是经过 MAIN_Fosc / 1000 得到的 24000, 这里占空比是 24000 * 0.5 : 
				表示 1ms之内高电平占 50% 低电平占 50%  : 这是1000HZ?
	*/
	init.PWM_Duty = PERIOD * 0.5;			//占空比时间, 0~Period
	init.PWM_DeadTime = 0;	//死区发生器设置, 0~255
	init.PWM_EnoSelect = ENO5P;		//输出通道选择,	ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8P
	init.PWM_CEN_Enable = ENABLE;		//使能计数器, ENABLE,DISABLE
	init.PWM_MainOutEnable = ENABLE;//主输出使能,  ENABLE,DISABLE
	
	//配置小分类 和 大分类
	PWM_Configuration(PWM5, &init );
	PWM_Configuration(PWMB, &init );
	
	//中断使能
	NVIC_PWM_Init(PWMB , DISABLE , Priority_1);
	
	//切换引脚
	PWM5_SW(PWM5_SW_P00);
}


void main(){
	u8 count = 0 ;
	//0. 中断总开关
	EAXSFR();
	EA = 1;

	//1. IO模式
	GPIO_config();
	

	while(1){
		
		PWM_config(hz[count]);
		count++;
		
		if(count > 7){
			count = 0 ;
		}
		
		delay_ms(250);
		delay_ms(250);
	}
}

封装代码

main.c
#include "GPIO.h"
#include "Delay.h"
#include "BUZZER.h"

//小字二组: C`	   D`     E`   F`	  G`	A`	  B`    C``
u16 hz[] = {1047, 1175, 1319, 1397, 1568, 1760, 1976, 2093};

void main(){
	u8 count = 0 ;
	
	//1. 初始化
	BUZZER_init();
		
	while(1){
		//PWM_config(hz[count]);
		BUZZER_play(hz[count]);
		count++;
		if(count > 7){

			count = 0 ;		
			
			//在停止前,必须确保当前的这个音还能正常的播放完 0.5s 的时间,才能去停止。
			delay_ms(250);
			delay_ms(250);
			
			//如果进入了这里,即表示已经播放完一轮次了。停止,歇一会。
			BUZZER_stop();
			
			//调用停止后,休息1.5秒。加上if后面的0.5秒 就相当于是停止了2秒钟,才去播放下一轮次。
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			
		}
		
		delay_ms(250);
		delay_ms(250);
	}
}
BUZZER.h
#ifndef	__BUZZER_H
#define	__BUZZER_H
#include "STC8H_PWM.h"
#include "GPIO.h"

#include "NVIC.h"
#include "Switch.h"

//1. 放置宏

#define BUZZER P00


//2. 声明具体的功能函数

void BUZZER_init();

void BUZZER_play(u16 hz);

void BUZZER_stop();


#endif
BUZZER.c

#include "BUZZER.h"

void ioconfig(){
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_0;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_OUT_PP;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化
}

//1. 初始化
void BUZZER_init(){
	ioconfig();
	EAXSFR();
}

//2. 播放声音,要求给进来具体的频率值
void BUZZER_play(u16 hz){
	//周期时间
	// 分母是1000,即表示是1000HZ
	//u16 PERIOD = MAIN_Fosc / 1000;
	
	//如果你希望是多少hz, 那么就传递多少进来
	u16 PERIOD = MAIN_Fosc / hz;
	
	PWMx_InitDefine init;
	
	init.PWM_Mode = CCMRn_PWM_MODE2;			//模式,   CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
	/*
		周期时间: 
			1. 上面使用时钟主频 / 1000 即表示 24000是一个周期,也就是1ms 是一个周期。
			2. 1s钟时间被切割成了1000份。 
	*/
	init.PWM_Period = PERIOD - 1;		//周期时间,   0~65535
	
	/*
		1. 占空比表示高低电平的占空比:
		2. 想让蜂鸣器发声:BUZZER = 1 (高电平) , 想让蜂鸣器不发声: BUZZER = 0 (低电平)
		3. 我们希望在一个周期内(1ms)内有一半时间发声,一把时间不发生。
		4. 如果周期是经过 MAIN_Fosc / 1000 得到的 24000, 这里占空比是 24000 * 0.5 : 
				表示 1ms之内高电平占 50% 低电平占 50%  : 这是1000HZ?
	*/
	init.PWM_Duty = PERIOD * 0.5;			//占空比时间, 0~Period
	init.PWM_DeadTime = 0;	//死区发生器设置, 0~255
	init.PWM_EnoSelect = ENO5P;		//输出通道选择,	ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8P
	init.PWM_CEN_Enable = ENABLE;		//使能计数器, ENABLE,DISABLE
	init.PWM_MainOutEnable = ENABLE;//主输出使能,  ENABLE,DISABLE
	
	//配置小分类 和 大分类
	PWM_Configuration(PWM5, &init );
	PWM_Configuration(PWMB, &init );
	
	//中断使能
	NVIC_PWM_Init(PWMB , DISABLE , Priority_1);
	
	//切换引脚
	PWM5_SW(PWM5_SW_P00);
}

//3. 停止播放
void BUZZER_stop(){
	
	PWMx_InitDefine init;
	
	init.PWM_EnoSelect = 0;		//输出通道选择,	ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8P
	init.PWM_CEN_Enable = DISABLE;		//使能计数器, ENABLE,DISABLE
	init.PWM_MainOutEnable = DISABLE;//主输出使能,  ENABLE,DISABLE

	PWM_Configuration(PWM5, &init );
	PWM_Configuration(PWMB, &init );
}

播放两只老虎

BUZZER.h和BUZZER.c文件内容不变,只在main.c文件中改造。

#include "GPIO.h"
#include "Delay.h"
#include "UART.h"
#include "BUZZER.h"

//小字二组[哆来咪发唆拉西哆]: C`	   D`     E`   F`	  G`	A`	  B`    C``
u16 hz[] = {1047, 1175, 1319, 1397, 1568, 1760, 1976, 2093};

// 这是两只老虎的音普
u8 notes[] = {
	1, 2, 3, 1,				1, 2, 3, 1,				3, 4, 5,		3, 4, 5,
	5, 6, 5, 4, 3, 1,		5, 6, 5, 4, 3, 1,		1, 5, 1,		1, 5, 1
};

// 这是两只老虎的音长
u8 durations[] = {
	4, 4, 4, 4,				4, 4, 4, 4,				4, 4, 8,		4, 4, 8,
	3, 1, 3, 1, 4, 4,		3, 1, 3, 1, 4, 4,		4, 4, 8,		4, 4, 8
};

//给定一个值,让它休息这么多时间
void delay_X_ms(u16 time){
	int i;

	while(--time){
			delay_ms(1);
	}
	
//	for(i = 0 ; i < time ; i++){
//			delay_ms(1);
//	}
	
}

void UART_config(void) {
	// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

  	NVIC_UART1_Init(ENABLE,Priority_1);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}


void main(){
	
	u8 count = 0 ;
	int i = 0 , len = sizeof(notes) / sizeof(u8) ;
	
	//1. 初始化
	BUZZER_init();
	
	EA =1;
	UART_config();
	
	while(1){
		
			for(i = 0 ; i < len ; i++ ){
				//1. 播放什么音?
				
				//1.1 获取每一个音
				u8 note = notes[i];
				
				//1.2 根据音到赫兹数组里面取出它对应的频率
				u16 note_hz = hz[note - 1] ;
				
				//1.3 让蜂鸣器播放这个赫兹频率
				BUZZER_play(note_hz);
	
				//2. 这个音播放多少时间  休眠即可
				
				//2.1 同时取出来每一个音对应的音长:: 每一个音长就是100ms
				delay_X_ms(durations[i] * 100);
			}
			
			//播放完一次,停一下
			BUZZER_stop();
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			
	}
}

RTC时钟

简单介绍

        RTC时钟是一种实时时钟芯片,通常与微控制器或计算机等设备配合使用,提供高精度的时间和日期信息,以便于设备进行时间相关的操作,如记录数据、定时执行任务、闹钟提醒等。我们开发板中采用的是PCF8563。

原理图

分析:

        先看RTC时钟原理图,P37引脚是接的外部中断,而我们用到的RTC时钟的读取数据或者写入数据我们用到的是P32和P33引脚,通过I2C协议进行数据的读取或者数据的写入。BT1是一个外部电源,保证单片机断电后时钟仍然能继续工作。I2C我们下面一小章节介绍。

        数据是怎么读取或者写入时钟的呢,这里我们就要查阅PCF8563的数据手册了。

可以看到秒,分钟小时,日等都是BCD格式存储的,也就是它的高四位是数据的十位,第四位是数据的个位。所以我们写代码的时候要进行转化。

代码演示

RTC读取数据
#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "UART.h"
#include "I2C.h"
#include "Switch.h"

void GPIO_config(void) {
    GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
    GPIO_InitStructure.Pin  = GPIO_Pin_2| GPIO_Pin_3;		//指定要初始化的IO,
    GPIO_InitStructure.Mode = GPIO_OUT_OD;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
    GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}

void UART_config(void) {
    // >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

    NVIC_UART1_Init(ENABLE,Priority_1);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}

void I2C_config() {

    I2C_InitTypeDef init;

    /*
    	I2C总线支持的总线速度是: 100K ~ 400K
      400K = 24M/2/(Speed*2+4)
      400  = 24000 /2 / (Speed*2+4)
      4 = 240 / 2 / (Speed*2+4)
      4 = 120 / (Speed*2+4)

      (Speed*2+4) = 30  ===> Speed*2 = 26   ===> Speed = 13
    */
    init.I2C_Speed = 13;				// 总线速度=Fosc/2/(Speed*2+4),      0~63
    init.I2C_Enable = ENABLE;				//I2C功能使能,   ENABLE, DISABLE
    init.I2C_Mode = I2C_Mode_Master;					//主从模式选择,  I2C_Mode_Master,I2C_Mode_Slave
    init.I2C_MS_WDTA = DISABLE;				//主机使能自动发送,  ENABLE, DISABLE

    // 初始化
    I2C_Init(&init);

    //中断使能
    NVIC_I2C_Init(I2C_Mode_Master, DISABLE, Priority_1);

    //切换引脚
    I2C_SW(I2C_P33_P32);
}

void main() {
	
		u8 second =0, minute=0 , hour =0, day =30, month=0 ,  C = 0 , week = 0 ;
	  int year = 0 ;
	
		//I2C 总线从地址:读,0A3H;写,0A2H
		u8 dev_addr = 0xA2 ;
	  
		//表示给秒的寄存器地址进去,希望读取秒的数据
		u8 mem_addr = 0x02 ;
		
	//存数据,给一个数组 :: 0-秒 1-分 2-时 3-日 4-星期 5-月/世纪 6-年
		u8 dat[7] ;
		
		//就读一个长度
		u8 number = 7;
	
    //0. 总开关
    EA = 1 ;
    EAXSFR();

    //1. IO模式
    GPIO_config();


    //2. 串口配置
    UART_config();

    //3. I2C配置
    I2C_config();

    while(1) {
		
      /* 
				1. 读取时钟芯片里面的时间数据
				2. 参数解释:
						2.1 参数一: 设备地址,RTC时钟芯片的地址。
			      2.2 参数二: 具体读取的数据对应的寄存器地址
						2.3 参数三: 用来收取数据的地址。一般会声明一个数组出来。 一般是长度为7的数组 【年月日时分秒星期】
						2.4 参数四: 表示要读取几个数据
			*/
			
			I2C_ReadNbyte(dev_addr, mem_addr, dat , number);
			
			
			//解析数据 
			
			//获取秒
			second = ((dat[0] >> 4) & 0x07 ) * 10  +  (dat[0] & 0x0F);
			
			//获取分
			minute = (( dat[1] >> 4 ) & 0x07 ) * 10 + (dat[1] & 0x0F);
			
			//获取时
			hour =  ((dat[2] >> 4 ) & 0x03) * 10   +  (dat[2] & 0x0F);
			
			//获取日
			day =  ((dat[3] >> 4 ) & 0x03) * 10   +  (dat[3] & 0x0F);
			
			//获取星期
			week = dat[4] & 0x07 ;
				
			//获取月
			month = (( dat[5] >> 4 ) & 0x01 ) * 10 + (dat[5] & 0x0F);
			
			/*
				获取年
				  1. 年份的值有 千位 +  百位 +  十位 +  个位 构成
					2. 但是在年的寄存器里面的只有 十位和个位的值。缺少千位和百位的值
					3. 需要去判定月份的第7位的值,由它来决定千位和百位的值
							3.1 如果一会月份的第7位 (C) 是 0 :  2000 + 年的数据   2000 ~ 2099
							3.2 如果一会月份的第7位 (C) 是 1 :  2100 + 年的数据   2100 ~ 2199 
							
							
							月份:  ?111 1111
							       1000 0000
										 -------------
										 0000 0000
										 1000 0000
				*/
			
			// 得到1 或者的到 0 
			C = (dat[5] & 0x80) > 0 ? 1 : 0 ;
			
			//年份的数据需要配合前面的千位和百位
			year = ((dat[6] >> 4) & 0x0F) * 10   + (dat[6] & 0x0F)  ;
			
			//这里是为了判定是1 还是 0 
			if(C == 1){
			  year += 2100;
			}else{
				year += 2000;
			}
		
			printf("%d-%d-%d %d:%d:%d \n" ,  (int)year , (int)month , (int)day , (int)hour , (int)minute , (int)second );
			printf("week=%d \n" , week);
			
			//每间隔1s钟读取时间
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);

    }
}
RTC写入数据(数组封装)
#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "UART.h"
#include "I2C.h"
#include "Switch.h"

void GPIO_config(void) {
    GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
    GPIO_InitStructure.Pin  = GPIO_Pin_2| GPIO_Pin_3;		//指定要初始化的IO,
    GPIO_InitStructure.Mode = GPIO_OUT_OD;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
    GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}

void UART_config(void) {
    // >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

    NVIC_UART1_Init(ENABLE,Priority_1);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}

void I2C_config() {

    I2C_InitTypeDef init;

    /*
    	I2C总线支持的总线速度是: 100K ~ 400K
      400K = 24M/2/(Speed*2+4)
      400  = 24000 /2 / (Speed*2+4)
      4 = 240 / 2 / (Speed*2+4)
      4 = 120 / (Speed*2+4)

      (Speed*2+4) = 30  ===> Speed*2 = 26   ===> Speed = 13
    */
    init.I2C_Speed = 13;				// 总线速度=Fosc/2/(Speed*2+4),      0~63
    init.I2C_Enable = ENABLE;				//I2C功能使能,   ENABLE, DISABLE
    init.I2C_Mode = I2C_Mode_Master;					//主从模式选择,  I2C_Mode_Master,I2C_Mode_Slave
    init.I2C_MS_WDTA = DISABLE;				//主机使能自动发送,  ENABLE, DISABLE

    // 初始化
    I2C_Init(&init);

    //中断使能
    NVIC_I2C_Init(I2C_Mode_Master, DISABLE, Priority_1);

    //切换引脚
    I2C_SW(I2C_P33_P32);
}

#define DECIMAL2BCD(i) ((( i / 10 ) << 4)  |  (i % 10))

void main() {
	
	  u8 new_year ;
	
		u8 second =55, minute=59 , hour =23, day =31, month=12 ,  C = 1 , week = 3 ;
	  int year = 2023 ;
	
		//I2C 总线从地址:读,0A3H;写,0A2H
		u8 dev_addr = 0xA2 ;
	  
		//表示给秒的寄存器地址进去,希望读取秒的数据
		u8 mem_addr = 0x02 ;
		
	//存数据,给一个数组 :: 0-秒 1-分 2-时 3-日 4-星期 5-月/世纪 6-年
		u8 dat[7] ;
		
		//就读一个长度
		u8 number = 7;
	
    //0. 总开关
    EA = 1 ;
    EAXSFR();

    //1. IO模式
    GPIO_config();


    //2. 串口配置
    UART_config();

    //3. I2C配置
    I2C_config();
		
		
		//4. 写入时间
	
	  //写入秒 (BCD格式) : 十位 +  个位
	  dat[0]  = DECIMAL2BCD(second) ;
		
		
		//写入分 (BCD格式) : 十位 +  个位  39
		dat[1] = DECIMAL2BCD(minute) ;
		
		
		//写入时 (BCD格式) : 十位 +  个位
		dat[2] = DECIMAL2BCD(hour) ;
		
		
		//写入日  (BCD格式) : 十位 +  个位
		dat[3] = DECIMAL2BCD(day) ;
		
		
		//写入星期 (BCD格式) : 十位 +  个位
		dat[4] = DECIMAL2BCD(week);
		
		
		//写入月 (BCD格式) : 十位 +  个位
		//月份寄存器里面不光有月份的数据,也有世纪的数据【世纪的数据影响着后面的年份的数据】
		//先准备月份的数据
		dat[5] = DECIMAL2BCD(month) ;  // 0001 0010
		//再添加世纪的数据到月份的字节里面:修改月份的最高位 【第7位】,看C是什么? C是0,月份的第7位就是0 ,C是1 ,月份的第7位就是1
		if(C == 0){ // C是0,月份的第7位就是0
		   dat[5] &= ~(1 << 7);
		}else{ // C是1 ,月份的第7位就是1
		   dat[5] |= (1 << 7);
		}
		
		
		//写入年 (BCD格式) : 十位 +  个位'
		// warning C182: pointer to different objects
		//比如年份是2023,先得到23这个值,
		new_year = year % 100;  // 23
		
		//再转换成 (BCD格式) : 十位 +  个位
		dat[6] = DECIMAL2BCD(new_year) ;
		
		
		//写数据
		I2C_WriteNbyte(dev_addr,mem_addr , dat , 7);
		
		

    while(1) {
		
      /* 
				1. 读取时钟芯片里面的时间数据
				2. 参数解释:
						2.1 参数一: 设备地址,RTC时钟芯片的地址。
			      2.2 参数二: 具体读取的数据对应的寄存器地址
						2.3 参数三: 用来收取数据的地址。一般会声明一个数组出来。 一般是长度为7的数组 【年月日时分秒星期】
						2.4 参数四: 表示要读取几个数据
			*/
			
			I2C_ReadNbyte(dev_addr, mem_addr, dat , number);
			
			
			#define BCD2DECIMAL(a , b)  (((a >> 4) & b ) * 10  +  (a & 0x0F))
			//解析数据 
			
			//获取秒
			//second = ((dat[0] >> 4) & 0x07 ) * 10  +  (dat[0] & 0x0F);
			second = BCD2DECIMAL(dat[0] , 0x07);
			
			//获取分
			//minute = (( dat[1] >> 4 ) & 0x07 ) * 10 + (dat[1] & 0x0F);
			minute = BCD2DECIMAL(dat[1] , 0x07);
			
			//获取时
			//hour =  ((dat[2] >> 4 ) & 0x03) * 10   +  (dat[2] & 0x0F);
			hour = BCD2DECIMAL(dat[2] , 0x03);
			
			//获取日
			//day =  ((dat[3] >> 4 ) & 0x03) * 10   +  (dat[3] & 0x0F);
			day = BCD2DECIMAL(dat[3] , 0x03);
			
			//获取星期
			week = dat[4] & 0x07 ;
				
			//获取月
			//month = (( dat[5] >> 4 ) & 0x01 ) * 10 + (dat[5] & 0x0F);
			month = BCD2DECIMAL(dat[5] , 0x01);
			
			/*
				获取年
				  1. 年份的值有 千位 +  百位 +  十位 +  个位 构成
					2. 但是在年的寄存器里面的只有 十位和个位的值。缺少千位和百位的值
					3. 需要去判定月份的第7位的值,由它来决定千位和百位的值
							3.1 如果一会月份的第7位 (C) 是 0 :  2000 + 年的数据   2000 ~ 2099
							3.2 如果一会月份的第7位 (C) 是 1 :  2100 + 年的数据   2100 ~ 2199 
							
							
							月份:  ?111 1111
							       1000 0000
										 -------------
										 0000 0000
										 1000 0000
				*/
			
			// 得到1 或者的到 0 
			C = (dat[5] & 0x80) > 0 ? 1 : 0 ;
			
			//年份的数据需要配合前面的千位和百位
			//year = ((dat[6] >> 4) & 0x0F) * 10   + (dat[6] & 0x0F)  ;
			year = BCD2DECIMAL(dat[6] , 0x0F);
			printf("year=%d\n" , year);
			
			//这里是为了判定是1 还是 0 
			if(C == 1){
			  year += 2100;
			}else{
				year += 2000;
			}
		
			printf("%d-%d-%d %d:%d:%d \n" ,  (int)year , (int)month , (int)day , (int)hour , (int)minute , (int)second );
			printf("week=%d \n" , (int)week);
			
			//每间隔1s钟读取时间
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);

    }
}
RTC封装
main.c
#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "UART.h"
#include "I2C.h"
#include "Switch.h"
#include "RTC.h"

void UART_config(void) {
	// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

  	NVIC_UART1_Init(ENABLE,Priority_1);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}

void main() {
	 RTC_Time write_time;
	
    //0. 总开关
    EA = 1 ;
	
	 //1. RTC初始化
	 RTC_init();
	
    //2. 串口配置
    UART_config();

		
	  //写入时间
	 
	  write_time.year=2023;
	  write_time.month=10;
	  write_time.day=31;
	  write_time.hour=23;
	  write_time.minute=59;
	  write_time.second=50;
	
	  write_time.week=5;
	  
		RTC_WriteTime(&write_time);
		

    while(1) {
		
			//读取时间
      RTC_Time time;
			RTC_ReadTime(&time);
			
			printf("%d-%d-%d %d:%d:%d\n" , time.year, (int)time.month, (int)time.day , (int)time.hour, (int)time.minute, (int)time.second );
			printf("week=%d\n" , (int)time.week);
			
			//每间隔1s钟读取时间
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);

    }
}
RTC.h
#ifndef	__RTC_H
#define	__RTC_H

#include "GPIO.h"
#include "NVIC.h"
#include "I2C.h"
#include "Switch.h"


/*
	1. 每一个支持I2C协议元件,手册都会提供两个地址: 读地址和写地址
	2. 这两个地址都不是设备地址,但是他们是经过设备地址变化得来的。
	3. 读地址和写地址,需要用到设备地址来扮演他们的高7位,它们的第0位,
			设置成 0 或者是 1就可以描述现在要和这个设备做什么动作: 写的动作? 读的动作
			
			写地址: 0xA2 : 1010 0010
			读地址: 0xA3 : 1010 0011
			
			得到设备地址:0101 0001  ---> 0x51
			
			从设备地址得到写的地址:  0x51 << 1 ==右边补0==> 1010 0010
			从设备地址得到读的地址:  (0x51 << 1) |1 = 1010 0011
*/


//1. 宏、结构体
#define DECIMAL2BCD(i) ((( i / 10 ) << 4)  |  (i % 10))
#define BCD2DECIMAL(a , b)  (((a >> 4) & b ) * 10  +  (a & 0x0F))

#define DEV_ADDRESS 0xA2  // 这里不要被迷惑了,放的应该是设备的地址 + 写的动作  =  写地址
#define MEM_ADDRESS 0x02
#define NUMBER 7

//I2C 总线从地址:读,0A3H;写,0A2H
//u8 dev_addr = 0xA2 ;

//表示给秒的寄存器地址进去,希望读取秒的数据
//u8 mem_addr = 0x02 ;

typedef struct
{
	 u8 second , minute , hour, day, month, week;
	 int year ;
} RTC_Time;

//2. 功能函数声明

//2.1 配置、初始化
void RTC_init();


//2.2 具体的功能:

// 读取时间
void RTC_ReadTime(RTC_Time * time);

//写入时间
void RTC_WriteTime(RTC_Time * time);


#endif
RTC.c
#include "RTC.h"

void IO_config(){
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
  GPIO_InitStructure.Pin  = GPIO_Pin_2| GPIO_Pin_3;		//指定要初始化的IO,
  GPIO_InitStructure.Mode = GPIO_OUT_OD;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
  GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}
	
void I2C_config(){
  I2C_InitTypeDef init;

    /*
    	I2C总线支持的总线速度是: 100K ~ 400K
      400K = 24M/2/(Speed*2+4)
      400  = 24000 /2 / (Speed*2+4)
      4 = 240 / 2 / (Speed*2+4)
      4 = 120 / (Speed*2+4)

      (Speed*2+4) = 30  ===> Speed*2 = 26   ===> Speed = 13
    */
    init.I2C_Speed = 13;				// 总线速度=Fosc/2/(Speed*2+4),      0~63
    init.I2C_Enable = ENABLE;				//I2C功能使能,   ENABLE, DISABLE
    init.I2C_Mode = I2C_Mode_Master;					//主从模式选择,  I2C_Mode_Master,I2C_Mode_Slave
    init.I2C_MS_WDTA = DISABLE;				//主机使能自动发送,  ENABLE, DISABLE

    // 初始化
    I2C_Init(&init);

    //中断使能
    NVIC_I2C_Init(I2C_Mode_Master, DISABLE, Priority_1);

    //切换引脚
    I2C_SW(I2C_P33_P32);
}

//配置、初始化
void RTC_init(){
	//打开外部寄存器使能
	EAXSFR();
	EA = 1;
	
	//基础配置
  IO_config();
	I2C_config();

}

// 读取时间
void RTC_ReadTime(RTC_Time * time){
  
	 // 事先准备一个数组,让I2C把时间数据读取到这个数组里面来
	 u8 dat[7];
	
	   /* 
				1. 读取时钟芯片里面的时间数据
				2. 参数解释:
						2.1 参数一: 设备地址,RTC时钟芯片的地址。
			      2.2 参数二: 具体读取的数据对应的寄存器地址
						2.3 参数三: 用来收取数据的地址。一般会声明一个数组出来。 一般是长度为7的数组 【年月日时分秒星期】
						2.4 参数四: 表示要读取几个数据
			*/
			
			I2C_ReadNbyte(DEV_ADDRESS, MEM_ADDRESS, dat , NUMBER);
			
			
			//解析数据 
			
			//获取秒
			//second = ((dat[0] >> 4) & 0x07 ) * 10  +  (dat[0] & 0x0F);
			time->second = BCD2DECIMAL(dat[0] , 0x07);
	
	     //方式一: 对结构体指针的内部成员赋值
			//(*time).second = BCD2DECIMAL(dat[0] , 0x07);
			//方式二: 直接使用指针 配合 -> 对成员赋值
			//time->second = BCD2DECIMAL(dat[0] , 0x07);
			
			//获取分
			//minute = (( dat[1] >> 4 ) & 0x07 ) * 10 + (dat[1] & 0x0F);
			time->minute = BCD2DECIMAL(dat[1] , 0x07);
			
			//获取时
			//hour =  ((dat[2] >> 4 ) & 0x03) * 10   +  (dat[2] & 0x0F);
			time->hour = BCD2DECIMAL(dat[2] , 0x03);
			
			//获取日
			//day =  ((dat[3] >> 4 ) & 0x03) * 10   +  (dat[3] & 0x0F);
			time->day = BCD2DECIMAL(dat[3] , 0x03);
			
			//获取星期
			time->week = dat[4] & 0x07 ;
				
			//获取月
			//month = (( dat[5] >> 4 ) & 0x01 ) * 10 + (dat[5] & 0x0F);
			time->month = BCD2DECIMAL(dat[5] , 0x01);
			
			/*
				获取年
				  1. 年份的值有 千位 +  百位 +  十位 +  个位 构成
					2. 但是在年的寄存器里面的只有 十位和个位的值。缺少千位和百位的值
					3. 需要去判定月份的第7位的值,由它来决定千位和百位的值
							3.1 如果一会月份的第7位 (C) 是 0 :  2000 + 年的数据   2000 ~ 2099
							3.2 如果一会月份的第7位 (C) 是 1 :  2100 + 年的数据   2100 ~ 2199 
							
							
							月份:  ?111 1111
							       1000 0000
										 -------------
										 0000 0000
										 1000 0000
				*/
			
			// 得到1 或者的到 0 
			//C = (dat[5] & 0x80) > 0 ? 1 : 0 ;
			
			//年份的数据需要配合前面的千位和百位
			//year = ((dat[6] >> 4) & 0x0F) * 10   + (dat[6] & 0x0F)  ;
			time->year = BCD2DECIMAL(dat[6] , 0x0F);
			
			//这里是为了判定是1 还是 0 
			if(dat[5] & 0x80){
			  time->year += 2100;
			}else{
				time->year += 2000;
			}
}


//写入时间
void RTC_WriteTime(RTC_Time * time){
	  u8 dat[7]; 
	  u8 new_year;
  
	  //写入秒 (BCD格式) : 十位 +  个位
	  dat[0]  = DECIMAL2BCD(time->second) ;
		
		
		//写入分 (BCD格式) : 十位 +  个位  39
		dat[1] = DECIMAL2BCD(time->minute) ;
		
		
		//写入时 (BCD格式) : 十位 +  个位
		dat[2] = DECIMAL2BCD(time->hour) ;
		
		
		//写入日  (BCD格式) : 十位 +  个位
		dat[3] = DECIMAL2BCD(time->day) ;
		
		
		//写入星期 (BCD格式) : 十位 +  个位
		dat[4] = DECIMAL2BCD(time->week);
		
		
		//写入月 (BCD格式) : 十位 +  个位
		//月份寄存器里面不光有月份的数据,也有世纪的数据【世纪的数据影响着后面的年份的数据】
		//先准备月份的数据
		dat[5] = DECIMAL2BCD(time->month) ;  // 0001 0010
		//再添加世纪的数据到月份的字节里面:修改月份的最高位 【第7位】,看C是什么? C是0,月份的第7位就是0 ,C是1 ,月份的第7位就是1
	
		
		//关于世纪C的赋值,要参考年份的数据,如果是20xx年,那么C就赋值 0 , 如果是 21xx年,那么C就赋值 1
		if(time->year >= 2100){ // 如果是 21xx年,那么C就赋值 1
		  dat[5] |= (1 << 7);
		}else{ // 如果是20xx年,那么C就赋值 0 
		  dat[5] &= ~(1 << 7);
		}
		
		//if(C == 0){ // C是0,月份的第7位就是0
		//   dat[5] &= ~(1 << 7);
		//}else{ // C是1 ,月份的第7位就是1
		//   dat[5] |= (1 << 7);
		//}
		
		//写入年 (BCD格式) : 十位 +  个位'
		// warning C182: pointer to different objects
		//比如年份是2023,先得到23这个值,
		new_year = time->year % 100;  // 23
		
		//再转换成 (BCD格式) : 十位 +  个位
		dat[6] = DECIMAL2BCD(new_year) ;
		
		//写数据
		I2C_WriteNbyte(DEV_ADDRESS,MEM_ADDRESS , dat , NUMBER);
}

I2C总线

简单介绍

        I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在集成电路之间进行数据交换。它最初由飞利浦公司(Philips)开发,现已成为一种通用的串行通信协议,被广泛应用于各种电子设备和嵌入式系统中。

总线结构

        I2C总线包括两根信号线:SDA(串行数据线)和SCL(串行时钟线)。这两根信号线共用一个总线,因此在总线上可以连接多个设备。在I2C总线上,每个设备都有一个唯一的地址,用于标识设备。

        SCL线是时钟线,用于控制数据传输的速度和时序;SDA线是数据线,用于传输实际的数据.

设备的地址通常是由设备制造商确定的,并在设备的数据手册中公布。

  1. Master: 主设备。通常是主控MCU
  2. Slave:从设备。通常是功能芯片,例如RTC时钟,陀螺仪,温湿度等等。
  3. SCL:时钟线,控制数据传输的速度和时序。
  4. SDA:数据线。传输数据的。
  5. 地址:从设备地址。主设备通过地址进行访问。在总线中,每个从设备地址唯一。

STC8H内置了一组I2C接口

I2C接口

SCL

SDA

I2C1

P1.5

P1.4

P2.5

P2.4

P3.2

P3.3

外部中断(EXTI)

简单介绍

外部中断

引脚

备注

INT0

P3.2

支持上升沿和下降沿中断

INT1

P3.3

支持上升沿和下降沿中断

INT2

P3.6

只支持下降沿中断

INT3

P3.7

只支持下降沿中断

INT4

P3.0

只支持下降沿中断

在数字电路中,信号的电平变化分为上升沿和下降沿。

上升沿指的是信号从低电平变为高电平的瞬间,下降沿指的是信号从高电平变为低电平的瞬间。

例如,当一个开关被按下时,电路中的信号从低电平变为高电平,此时发生了一个上升沿;当开关被松开时,信号从高电平变为低电平,此时发生了一个下降沿。在数字电路中,上升沿和下降沿往往会被用作时序控制和数据传输等方面的参考信号。

代码演示

#include "GPIO.h"
#include "NVIC.h"
#include "UART.h"
#include "Switch.h"
#include "Exti.h"
void GPIO_config(void) {
	
	//INT0
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化

}

void UART_config(void) {
	// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

  	NVIC_UART1_Init(ENABLE,Priority_2);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}

void EXTI_config(){

	//创建结构体变量
	EXTI_InitTypeDef init;
	
	//给成员赋值: 配置中断触发模式: 上升沿和下降沿触发还是只有下降沿触发
	init.EXTI_Mode = EXT_MODE_RiseFall; //中断模式,  	EXT_MODE_RiseFall, EXT_MODE_Fall
	
	//初始化
	Ext_Inilize(EXT_INT0, &init);
	
	//中断使能
	NVIC_INT0_Init(ENABLE , Priority_1);
}

//如果触发了外部中断0,那么就执行这个函数
void INT0_handler(){
   if(P32){
		  printf("rise...\n"); //上升沿
	 }else{
			printf("fall...\n"); //下降沿
	 }
}

void main(){
	
	EA = 1 ;
	
	//1. IO
	GPIO_config();
	
	//2. 串口
	UART_config();
	
	//3. 外部中断
	EXTI_config();
	
	while(1){
		
	}
}

RTC闹钟设置

读取数据手册

介绍:地址为01H的寄存器有八个字节,TIE和TF为一组,用于控制是否进行计数器中断,AIE和AF为一组,用于控制是否进行闹钟中断。设置闹钟时,将AIE置为1,表示中断有效,当执行中断后,AF会自动置为1,并且不会重置,所以我们如果下次还要继续产生中断我们就必须在完成第一次中断后手动将AF置为0,确保下次中断能发生。TIE和TF使用原理与AF相同。

那么我们要怎么设置闹钟呢?我们要将数据写入哪个寄存器呢?

我们对地址为09H的报警计时器也就是闹钟计时器进行操作,这个寄存器的第7位置为1时代表报警无效,当他置为0时报警有效。0~6位存储要设置的数据,但仍然是BCD形式存储,所以我们要对齐进行转化。在操作工程中我们要将寄存器中的数据先读取出来,对其修改后在写入进去,从而对闹钟进行设置。

代码演示

RTC闹钟设置

#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "UART.h"
#include "I2C.h"
#include "Switch.h"
#include "RTC.h"
#include "Exti.h"

void GPIO_config(void) {
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_7;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}

void UART_config(void) {
	// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

  	NVIC_UART1_Init(ENABLE,Priority_2);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}

void EXTI_config(){
	EXTI_InitTypeDef init;
	init.EXTI_Mode = EXT_MODE_RiseFall;			//中断模式,  	EXT_MODE_RiseFall, EXT_MODE_Fall
	Ext_Inilize( EXT_INT3 , &init);
	
	//中断使能
	NVIC_INT3_Init(ENABLE,Priority_1);
}

// RTC时钟的中断处理: 闹钟和定时器发生中断都会调用这个函数
void RTC_ISR_Handle(){

	printf("tttt...\n");
	
}

void main() {
	 u8 alarm[4];
	 u8 dat , minute ;
	 RTC_Time write_time;
	
    //0. 总开关
    EA = 1 ;
		
		GPIO_config();
	
	  //1. RTC初始化
	  RTC_init();
	
    //2. 串口配置
    UART_config();
	
		//配置外部中断
		EXTI_config();

		
	  //写入时间
	  write_time.year=2023;
	  write_time.month=10;
	  write_time.day=31;
	  write_time.hour=13;
	  write_time.minute=55;
	  write_time.second=57;
	
	  write_time.week=5;
	  
		RTC_WriteTime(&write_time);
		
		//==================================闹钟设置 :: begin=============================
		
		//1. 设置闹钟的总开关:允许闹钟中断
		
		//1.1 先把原来的数据读取出来
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改过后,
		
		//a. 允许闹钟中断:: 把第1位置1
		dat |= 0x02;
		 
		//b. 清除闹钟发生过的标记 :: 把 第3位 置0
		dat &= ~0x08; 
		
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
		
		
		//2. 设置具体是什么时间触发闹钟。 30
		
		//2.1 时间必须是BCD格式,并且最高位第7位要置 0 
		//minute = DECIMAL2BCD(56) & ~0x80;
		
		alarm[0] = DECIMAL2BCD(56) & 0x7F; //分
		alarm[1] = DECIMAL2BCD(13) & 0x7F; // 时
		alarm[2] = DECIMAL2BCD(31) & 0x7F; // 日
		alarm[3] = DECIMAL2BCD(5) & 0x7F; // 星期
		
		//2.2 把分钟时间设置进去
		I2C_WriteNbyte(0xA2, 0x09, alarm, 4);
		
		
		//==================================闹钟设置 :: end=============================
    while(1) {
		
			//读取时间
      RTC_Time time;
			RTC_ReadTime(&time);
			
			printf("%d-%d-%d %d:%d:%d\n" , time.year, (int)time.month, (int)time.day , (int)time.hour, (int)time.minute, (int)time.second );
			printf("week=%d\n" , (int)time.week);
			
			//每间隔1s钟读取时间
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);

    }
}

RTC闹钟设置(封装)

main.c
#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "UART.h"
#include "I2C.h"
#include "Switch.h"
#include "RTC.h"
#include "Exti.h"

void GPIO_config(void) {
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_7;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}

void UART_config(void) {
	// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

  	NVIC_UART1_Init(ENABLE,Priority_2);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}

void EXTI_config(){
	EXTI_InitTypeDef init;
	init.EXTI_Mode = EXT_MODE_RiseFall;			//中断模式,  	EXT_MODE_RiseFall, EXT_MODE_Fall
	Ext_Inilize( EXT_INT3 , &init);
	
	//中断使能
	NVIC_INT3_Init(ENABLE,Priority_1);
}

// RTC时钟的中断处理: 闹钟和定时器发生中断都会调用这个函数
void RTC_ISR_Handle(){

	printf("tttt...\n");
	
	//来到这里之后,为了让下一次的闹钟到点了还能来这里,需要手动清理标记为
	RTC_Alarm_Clear_Flag();
	
}

void main() {
	 RTC_Time write_time;
		RTC_Alarm alarm;
    //0. 总开关
    EA = 1 ;
		
		GPIO_config();
	
	  //1. RTC初始化
	  RTC_init();
	
    //2. 串口配置
    UART_config();
	
		//配置外部中断
		EXTI_config();

		
	  //写入时间
	  write_time.year=2023;
	  write_time.month=10;
	  write_time.day=31;
	  write_time.hour=13;
	  write_time.minute=55;
	  write_time.second=57;
	
	  write_time.week=5;
	  
		RTC_WriteTime(&write_time);
		
		
		
		//设置闹钟
	

		alarm.minute = 56;  //56分的时候响闹钟
		alarm.minute_enable = 1;  // 启用分钟闹钟
		
		RTC_Alarm_Start(alarm);
		
		
    while(1) {
		
			//读取时间
      RTC_Time time;
			RTC_ReadTime(&time);
			
			printf("%d-%d-%d %d:%d:%d\n" , time.year, (int)time.month, (int)time.day , (int)time.hour, (int)time.minute, (int)time.second );
			printf("week=%d\n" , (int)time.week);
			
			//每间隔1s钟读取时间
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);

    }
}
RTC.h
#ifndef	__RTC_H
#define	__RTC_H

#include "GPIO.h"
#include "NVIC.h"
#include "I2C.h"
#include "Switch.h"


/*
	1. 每一个支持I2C协议元件,手册都会提供两个地址: 读地址和写地址
	2. 这两个地址都不是设备地址,但是他们是经过设备地址变化得来的。
	3. 读地址和写地址,需要用到设备地址来扮演他们的高7位,它们的第0位,
			设置成 0 或者是 1就可以描述现在要和这个设备做什么动作: 写的动作? 读的动作
			
			写地址: 0xA2 : 1010 0010
			读地址: 0xA3 : 1010 0011
			
			得到设备地址:0101 0001  ---> 0x51
			
			从设备地址得到写的地址:  0x51 << 1 ==右边补0==> 1010 0010
			从设备地址得到读的地址:  (0x51 << 1) |1 = 1010 0011
*/


//1. 宏、结构体
#define DECIMAL2BCD(i) ((( i / 10 ) << 4)  |  (i % 10))
#define BCD2DECIMAL(a , b)  (((a >> 4) & b ) * 10  +  (a & 0x0F))

#define DEV_ADDRESS 0xA2  // 这里不要被迷惑了,放的应该是设备的地址 + 写的动作  =  写地址
#define MEM_ADDRESS 0x02
#define NUMBER 7

//I2C 总线从地址:读,0A3H;写,0A2H
//u8 dev_addr = 0xA2 ;

//表示给秒的寄存器地址进去,希望读取秒的数据
//u8 mem_addr = 0x02 ;

typedef struct
{
	 u8 second , minute , hour, day, month, week;
	 int year ;
} RTC_Time;

typedef struct
{
	u8  minute, hour, day,  week ;
	u8  minute_enable , hour_enable ,  day_enable , week_enable;
} RTC_Alarm;

//2. 功能函数声明

//2.1 配置、初始化
void RTC_init();


//2.2 具体的功能:

//====================时间功能函数===============================
// 读取时间
void RTC_ReadTime(RTC_Time * time);

//写入时间
void RTC_WriteTime(RTC_Time * time);


//====================闹钟功能函数===============================

//设置闹钟(开始闹钟)
void RTC_Alarm_Start(RTC_Alarm alarm);

//停止闹钟(不设置闹钟了...)
void RTC_Alarm_Stop();

// 清除闹钟发生过的标记,以便闹钟在一次到点了还能触发
void RTC_Alarm_Clear_Flag();



#endif
RTC.c
#include "RTC.h"

void IO_config(){
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
  GPIO_InitStructure.Pin  = GPIO_Pin_2| GPIO_Pin_3;		//指定要初始化的IO,
  GPIO_InitStructure.Mode = GPIO_OUT_OD;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
  GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}
	


void I2C_config(){
  I2C_InitTypeDef init;

    /*
    	I2C总线支持的总线速度是: 100K ~ 400K
      400K = 24M/2/(Speed*2+4)
      400  = 24000 /2 / (Speed*2+4)
      4 = 240 / 2 / (Speed*2+4)
      4 = 120 / (Speed*2+4)

      (Speed*2+4) = 30  ===> Speed*2 = 26   ===> Speed = 13
    */
    init.I2C_Speed = 13;				// 总线速度=Fosc/2/(Speed*2+4),      0~63
    init.I2C_Enable = ENABLE;				//I2C功能使能,   ENABLE, DISABLE
    init.I2C_Mode = I2C_Mode_Master;					//主从模式选择,  I2C_Mode_Master,I2C_Mode_Slave
    init.I2C_MS_WDTA = DISABLE;				//主机使能自动发送,  ENABLE, DISABLE

    // 初始化
    I2C_Init(&init);

    //中断使能
    NVIC_I2C_Init(I2C_Mode_Master, DISABLE, Priority_1);

    //切换引脚
    I2C_SW(I2C_P33_P32);
}



//配置、初始化
void RTC_init(){
	//打开外部寄存器使能
	EAXSFR();
	EA = 1;
	
	
	//基础配置
  IO_config();
	I2C_config();

}


// 读取时间
void RTC_ReadTime(RTC_Time * time){
  
	 // 事先准备一个数组,让I2C把时间数据读取到这个数组里面来
	 u8 dat[7];
	
	   /* 
				1. 读取时钟芯片里面的时间数据
				2. 参数解释:
						2.1 参数一: 设备地址,RTC时钟芯片的地址。
			      2.2 参数二: 具体读取的数据对应的寄存器地址
						2.3 参数三: 用来收取数据的地址。一般会声明一个数组出来。 一般是长度为7的数组 【年月日时分秒星期】
						2.4 参数四: 表示要读取几个数据
			*/
			
			I2C_ReadNbyte(DEV_ADDRESS, MEM_ADDRESS, dat , NUMBER);
			
			
			
			//解析数据 
			
			//获取秒
			//second = ((dat[0] >> 4) & 0x07 ) * 10  +  (dat[0] & 0x0F);
			time->second = BCD2DECIMAL(dat[0] , 0x07);
	
	     //方式一: 对结构体指针的内部成员赋值
			//(*time).second = BCD2DECIMAL(dat[0] , 0x07);
			//方式二: 直接使用指针 配合 -> 对成员赋值
			//time->second = BCD2DECIMAL(dat[0] , 0x07);
			
			//获取分
			//minute = (( dat[1] >> 4 ) & 0x07 ) * 10 + (dat[1] & 0x0F);
			time->minute = BCD2DECIMAL(dat[1] , 0x07);
			
			//获取时
			//hour =  ((dat[2] >> 4 ) & 0x03) * 10   +  (dat[2] & 0x0F);
			time->hour = BCD2DECIMAL(dat[2] , 0x03);
			
			//获取日
			//day =  ((dat[3] >> 4 ) & 0x03) * 10   +  (dat[3] & 0x0F);
			time->day = BCD2DECIMAL(dat[3] , 0x03);
			
			//获取星期
			time->week = dat[4] & 0x07 ;
				
			//获取月
			//month = (( dat[5] >> 4 ) & 0x01 ) * 10 + (dat[5] & 0x0F);
			time->month = BCD2DECIMAL(dat[5] , 0x01);
			
			/*
				获取年
				  1. 年份的值有 千位 +  百位 +  十位 +  个位 构成
					2. 但是在年的寄存器里面的只有 十位和个位的值。缺少千位和百位的值
					3. 需要去判定月份的第7位的值,由它来决定千位和百位的值
							3.1 如果一会月份的第7位 (C) 是 0 :  2000 + 年的数据   2000 ~ 2099
							3.2 如果一会月份的第7位 (C) 是 1 :  2100 + 年的数据   2100 ~ 2199 
							
							
							月份:  ?111 1111
							       1000 0000
										 -------------
										 0000 0000
										 1000 0000
				*/
			
			// 得到1 或者的到 0 
			//C = (dat[5] & 0x80) > 0 ? 1 : 0 ;
			
			//年份的数据需要配合前面的千位和百位
			//year = ((dat[6] >> 4) & 0x0F) * 10   + (dat[6] & 0x0F)  ;
			time->year = BCD2DECIMAL(dat[6] , 0x0F);
			
			//这里是为了判定是1 还是 0 
			if(dat[5] & 0x80){
			  time->year += 2100;
			}else{
				time->year += 2000;
			}
}


//写入时间
void RTC_WriteTime(RTC_Time * time){
	  u8 dat[7]; 
	  u8 new_year;
  
	  //写入秒 (BCD格式) : 十位 +  个位
	  dat[0]  = DECIMAL2BCD(time->second) ;
		
		
		//写入分 (BCD格式) : 十位 +  个位  39
		dat[1] = DECIMAL2BCD(time->minute) ;
		
		
		//写入时 (BCD格式) : 十位 +  个位
		dat[2] = DECIMAL2BCD(time->hour) ;
		
		
		//写入日  (BCD格式) : 十位 +  个位
		dat[3] = DECIMAL2BCD(time->day) ;
		
		
		//写入星期 (BCD格式) : 十位 +  个位
		dat[4] = DECIMAL2BCD(time->week);
		
		
		//写入月 (BCD格式) : 十位 +  个位
		//月份寄存器里面不光有月份的数据,也有世纪的数据【世纪的数据影响着后面的年份的数据】
		//先准备月份的数据
		dat[5] = DECIMAL2BCD(time->month) ;  // 0001 0010
		//再添加世纪的数据到月份的字节里面:修改月份的最高位 【第7位】,看C是什么? C是0,月份的第7位就是0 ,C是1 ,月份的第7位就是1
	
		
		
		//关于世纪C的赋值,要参考年份的数据,如果是20xx年,那么C就赋值 0 , 如果是 21xx年,那么C就赋值 1
		if(time->year >= 2100){ // 如果是 21xx年,那么C就赋值 1
		  dat[5] |= (1 << 7);
		}else{ // 如果是20xx年,那么C就赋值 0 
		  dat[5] &= ~(1 << 7);
		}
		
		//if(C == 0){ // C是0,月份的第7位就是0
		//   dat[5] &= ~(1 << 7);
		//}else{ // C是1 ,月份的第7位就是1
		//   dat[5] |= (1 << 7);
		//}
		
		
		//写入年 (BCD格式) : 十位 +  个位'
		// warning C182: pointer to different objects
		//比如年份是2023,先得到23这个值,
		new_year = time->year % 100;  // 23
		
		//再转换成 (BCD格式) : 十位 +  个位
		dat[6] = DECIMAL2BCD(new_year) ;
		
		
		//写数据
		I2C_WriteNbyte(DEV_ADDRESS,MEM_ADDRESS , dat , NUMBER);
}



//===============闹钟功能函数实现===================
//设置闹钟(开始闹钟)
void RTC_Alarm_Start(RTC_Alarm rtc_alarm){
	u8 dat;
	u8 alarm[4];
	u8 count= 0 ;
	
   //1. 设置闹钟的总开关:允许闹钟中断
		
		//1.1 先把原来的数据读取出来
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改过后,
		
		//a. 允许闹钟中断:: 把第1位置1
		dat |= 0x02;
		 
		//b. 清除闹钟发生过的标记 :: 把 第3位 置0
		dat &= ~0x08; 
		
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
		
		
		//2. 设置具体是什么时间触发闹钟。 30
		
		//2.1 时间必须是BCD格式,并且最高位第7位要置 0 
		//minute = DECIMAL2BCD(56) & ~0x80;
		
		if(rtc_alarm.minute_enable){
			alarm[0] = DECIMAL2BCD(rtc_alarm.minute) & 0x7F; //分
			count++;
		}
		if(rtc_alarm.hour_enable) {
			alarm[1] = DECIMAL2BCD(rtc_alarm.hour) & 0x7F; // 时
			count++;
		}
		if(rtc_alarm.day_enable){
			alarm[2] = DECIMAL2BCD(rtc_alarm.day) & 0x7F; // 日
		  count++;	
		}
		if(rtc_alarm.week_enable){
			alarm[3] = DECIMAL2BCD(rtc_alarm.week) & 0x7F; // 星期
			count++;
		}
		
		//2.2 把分钟时间设置进去
		I2C_WriteNbyte(0xA2, 0x09, alarm, count);
}

//停止闹钟(不设置闹钟了...)
void RTC_Alarm_Stop(){
		u8 dat;
  	//1.1 先把原来的数据读取出来
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改过后,
		
		//a. 不允许闹钟中断:: 把第1位置0
		dat &= ~0x02;
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
}

// 清除闹钟发生过的标记,以便闹钟在一次到点了还能触发
void RTC_Alarm_Clear_Flag(){
   u8 dat;
  	//1.1 先把原来的数据读取出来
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
	
	   //1.2. 清除闹钟发生过的标记 :: 把 第3位 置0
	
		dat &= ~0x08; 
	 
	  //1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
}

RTC计时器设置

读取数据手册

介绍:地址为0EH的计时器寄存器,第7位置为1时代表定时器有效,为0时代表计时器无效。它的第0位和第1位表示时钟频率选择,也就是一秒数多少个数,地址为0FH的寄存器代表最终要设置多久才会执行中断,举个简单例子,如果我们设置的时钟频率为64Hz,那么我们在0FH地址中设置128则代表两秒会进行中断。

代码演示

RTC计时器设置

#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "UART.h"
#include "I2C.h"
#include "Switch.h"
#include "RTC.h"
#include "Exti.h"

void GPIO_config(void) {
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_7;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}

void UART_config(void) {
	// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

  	NVIC_UART1_Init(ENABLE,Priority_2);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}

void EXTI_config(){
	EXTI_InitTypeDef init;
	init.EXTI_Mode = EXT_MODE_RiseFall;			//中断模式,  	EXT_MODE_RiseFall, EXT_MODE_Fall
	Ext_Inilize( EXT_INT3 , &init);
	
	//中断使能
	NVIC_INT3_Init(ENABLE,Priority_1);
}

// RTC时钟的中断处理: 闹钟和定时器发生中断都会调用这个函数
void RTC_ISR_Handle(){
  u8 dat;
	printf("time...\n");
	
	//来到这里之后,为了让下一次的闹钟到点了还能来这里,需要手动清理标记位
	//RTC_Alarm_Clear_Flag();
	
	
	//来到这里折后,为了让下一次的定时到点了还能来这里,需要手动清理标记位
	//1.1 先读取
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改之后
		//a.清除定时器已经触发的标记 == 0
		dat &= ~0x04;
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
	
}

void main() {
	  u8 dat , hz , hz_count = 64 * 2;
	  RTC_Time write_time;
		RTC_Alarm alarm;
    //0. 总开关
    EA = 1 ;
		
		GPIO_config();
	
	  //1. RTC初始化
	  RTC_init();
	
    //2. 串口配置
    UART_config();
	
		//配置外部中断
		EXTI_config();

		
	  //写入时间
	  write_time.year=2023;
	  write_time.month=10;
	  write_time.day=31;
	  write_time.hour=13;
	  write_time.minute=55;
	  write_time.second=57;
	
	  write_time.week=5;
	  
		RTC_WriteTime(&write_time);
		
		
		
		/*
		//设置闹钟
		alarm.minute = 56;  //56分的时候响闹钟
		alarm.minute_enable = 1;  // 启用分钟闹钟
		
		RTC_Alarm_Start(alarm);
		*/
		
		
		//=======================定时器::begin=================================
		
		//1. 设置总开关
		
		//1.1 先读取
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改之后
		//a. 允许定时器中断 == 1
		dat |= 0x01; 
		
		//b. 清除定时器已经触发的标记 == 0
		dat &= ~0x04;
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
		
		
		//2. 设置频率(数数频率,1s钟能数多少个数)
		hz = HZ_64 | 0x80; //  |0x80 表示把第7位置为 0 ,也就是要启用这个频率
		I2C_WriteNbyte(0xA2, 0x0E, &hz, 1);
		
		
		//3. 设置具体数多少个数就触发中断 (其实就是过去了多久要出发中断)
		I2C_WriteNbyte(0xA2, 0x0F, &hz_count, 1);
		
		
		//=======================定时器::end=================================
		
		
    while(1) {
		
			//读取时间
      RTC_Time time;
			RTC_ReadTime(&time);
			
			printf("%d-%d-%d %d:%d:%d\n" , (int)time.year, (int)time.month, (int)time.day , (int)time.hour, (int)time.minute, (int)time.second );
	
			
			//每间隔1s钟读取时间
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);

    }
}

RTC计时器设置(封装)

main.c
#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "UART.h"
#include "I2C.h"
#include "Switch.h"
#include "RTC.h"
#include "Exti.h"

void GPIO_config(void) {
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_7;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}

void UART_config(void) {
	// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<
    COMx_InitDefine		COMx_InitStructure;					//结构定义
    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

  	NVIC_UART1_Init(ENABLE,Priority_2);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}

void EXTI_config(){
	EXTI_InitTypeDef init;
	init.EXTI_Mode = EXT_MODE_RiseFall;			//中断模式,  	EXT_MODE_RiseFall, EXT_MODE_Fall
	Ext_Inilize( EXT_INT3 , &init);
	
	//中断使能
	NVIC_INT3_Init(ENABLE,Priority_1);
}

// RTC时钟的中断处理: 闹钟和定时器发生中断都会调用这个函数
// 由于定时器和闹钟只要到点了都会触发中断,那么这个函数都会被执行,此时就需要去分辨现在是谁引发了中断,然后各走各的代码。
void RTC_ISR_Handle(){
	
	//printf("alarm...\n");

	printf("timer...\n");
	
	//来到这里之后,为了让下一次的闹钟到点了还能来这里,需要手动清理标记位
	//RTC_Alarm_Clear_Flag();
	
	
	//来到这里折后,为了让下一次的定时到点了还能来这里,需要手动清理标记位
	RTC_Timer_Clear_Flag();
	
}

void main() {
	 
	  RTC_Time write_time;
		RTC_Alarm alarm;
    //0. 总开关
    EA = 1 ;
		
		GPIO_config();
	
	  //1. RTC初始化
	  RTC_init();
	
    //2. 串口配置
    UART_config();
	
		//配置外部中断
		EXTI_config();

		
	  //写入时间
	  write_time.year=2023;
	  write_time.month=10;
	  write_time.day=31;
	  write_time.hour=13;
	  write_time.minute=55;
	  write_time.second=57;
	
	  write_time.week=5;
	  
		RTC_WriteTime(&write_time);
		
		
		
		/*
		//设置闹钟
		alarm.minute = 56;  //56分的时候响闹钟
		alarm.minute_enable = 1;  // 启用分钟闹钟
		
		RTC_Alarm_Start(alarm);
		*/
		
		
		//设置定时器
		RTC_Timer_Start( HZ_64, 64);
		
		
    while(1) {
		
			//读取时间
      RTC_Time time;
			RTC_ReadTime(&time);
			
			printf("%d-%d-%d %d:%d:%d\n" , time.year, (int)time.month, (int)time.day , (int)time.hour, (int)time.minute, (int)time.second );
	
			
			//每间隔1s钟读取时间
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);
			delay_ms(250);

    }
}
RTC.h
#ifndef	__RTC_H
#define	__RTC_H

#include "GPIO.h"
#include "NVIC.h"
#include "I2C.h"
#include "Switch.h"


/*
	1. 每一个支持I2C协议元件,手册都会提供两个地址: 读地址和写地址
	2. 这两个地址都不是设备地址,但是他们是经过设备地址变化得来的。
	3. 读地址和写地址,需要用到设备地址来扮演他们的高7位,它们的第0位,
			设置成 0 或者是 1就可以描述现在要和这个设备做什么动作: 写的动作? 读的动作
			
			写地址: 0xA2 : 1010 0010
			读地址: 0xA3 : 1010 0011
			
			得到设备地址:0101 0001  ---> 0x51
			
			从设备地址得到写的地址:  0x51 << 1 ==右边补0==> 1010 0010
			从设备地址得到读的地址:  (0x51 << 1) |1 = 1010 0011
*/


//1. 宏、结构体
#define DECIMAL2BCD(i) ((( i / 10 ) << 4)  |  (i % 10))
#define BCD2DECIMAL(a , b)  (((a >> 4) & b ) * 10  +  (a & 0x0F))

#define DEV_ADDRESS 0xA2  // 这里不要被迷惑了,放的应该是设备的地址 + 写的动作  =  写地址
#define MEM_ADDRESS 0x02
#define NUMBER 7

//I2C 总线从地址:读,0A3H;写,0A2H
//u8 dev_addr = 0xA2 ;

//表示给秒的寄存器地址进去,希望读取秒的数据
//u8 mem_addr = 0x02 ;

typedef struct
{
	 u8 second , minute , hour, day, month, week;
	 int year ;
} RTC_Time;

typedef struct
{
	u8  minute, hour, day,  week ;
	u8  minute_enable , hour_enable ,  day_enable , week_enable;
} RTC_Alarm;

typedef enum
{
	
  //HZ_4096 = 0x00, HZ_64 =0x01, HZ_1 =0x02 , HZ_1_60 = 0x03
	//HZ_4096 = 0, HZ_64 =1, HZ_1 =2 , HZ_1_60 = 3
	HZ_4096, HZ_64, HZ_1 , HZ_1_60
}RTC_HZ;



//2. 功能函数声明

//2.1 配置、初始化
void RTC_init();


//2.2 具体的功能:

//====================时间功能函数===============================
// 读取时间
void RTC_ReadTime(RTC_Time * time);

//写入时间
void RTC_WriteTime(RTC_Time * time);


//====================闹钟功能函数===============================

//设置闹钟(开始闹钟)
void RTC_Alarm_Start(RTC_Alarm alarm);

//停止闹钟(不设置闹钟了...)
void RTC_Alarm_Stop();

// 清除闹钟发生过的标记,以便闹钟在一次到点了还能触发
void RTC_Alarm_Clear_Flag();

//====================定时器功能函数===============================
//启用定时器:: 参数一:频率值,  参数二:多少个这样的hz就执行触发定时
void RTC_Timer_Start(RTC_HZ hz_value , u8 hz_count);

//禁用定时器
void RTC_Timer_Stop();

// 清除定时器发生过的标记,以便定时器在一次到点了还能触发
void RTC_Timer_Clear_Flag();



#endif
RTC.c
#include "RTC.h"


void IO_config(){
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
  GPIO_InitStructure.Pin  = GPIO_Pin_2| GPIO_Pin_3;		//指定要初始化的IO,
  GPIO_InitStructure.Mode = GPIO_OUT_OD;	//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
  GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化
}
	
void I2C_config(){
  I2C_InitTypeDef init;

    /*
    	I2C总线支持的总线速度是: 100K ~ 400K
      400K = 24M/2/(Speed*2+4)
      400  = 24000 /2 / (Speed*2+4)
      4 = 240 / 2 / (Speed*2+4)
      4 = 120 / (Speed*2+4)

      (Speed*2+4) = 30  ===> Speed*2 = 26   ===> Speed = 13
    */
    init.I2C_Speed = 13;				// 总线速度=Fosc/2/(Speed*2+4),      0~63
    init.I2C_Enable = ENABLE;				//I2C功能使能,   ENABLE, DISABLE
    init.I2C_Mode = I2C_Mode_Master;					//主从模式选择,  I2C_Mode_Master,I2C_Mode_Slave
    init.I2C_MS_WDTA = DISABLE;				//主机使能自动发送,  ENABLE, DISABLE

    // 初始化
    I2C_Init(&init);

    //中断使能
    NVIC_I2C_Init(I2C_Mode_Master, DISABLE, Priority_1);

    //切换引脚
    I2C_SW(I2C_P33_P32);
}



//配置、初始化
void RTC_init(){
	//打开外部寄存器使能
	EAXSFR();
	EA = 1;
	
	
	//基础配置
  IO_config();
	I2C_config();

}


// 读取时间
void RTC_ReadTime(RTC_Time * time){
  
	 // 事先准备一个数组,让I2C把时间数据读取到这个数组里面来
	 u8 dat[7];
	
	   /* 
				1. 读取时钟芯片里面的时间数据
				2. 参数解释:
						2.1 参数一: 设备地址,RTC时钟芯片的地址。
			      2.2 参数二: 具体读取的数据对应的寄存器地址
						2.3 参数三: 用来收取数据的地址。一般会声明一个数组出来。 一般是长度为7的数组 【年月日时分秒星期】
						2.4 参数四: 表示要读取几个数据
			*/
			
			I2C_ReadNbyte(DEV_ADDRESS, MEM_ADDRESS, dat , NUMBER);
			
			
			
			//解析数据 
			
			//获取秒
			//second = ((dat[0] >> 4) & 0x07 ) * 10  +  (dat[0] & 0x0F);
			time->second = BCD2DECIMAL(dat[0] , 0x07);
	
	     //方式一: 对结构体指针的内部成员赋值
			//(*time).second = BCD2DECIMAL(dat[0] , 0x07);
			//方式二: 直接使用指针 配合 -> 对成员赋值
			//time->second = BCD2DECIMAL(dat[0] , 0x07);
			
			//获取分
			//minute = (( dat[1] >> 4 ) & 0x07 ) * 10 + (dat[1] & 0x0F);
			time->minute = BCD2DECIMAL(dat[1] , 0x07);
			
			//获取时
			//hour =  ((dat[2] >> 4 ) & 0x03) * 10   +  (dat[2] & 0x0F);
			time->hour = BCD2DECIMAL(dat[2] , 0x03);
			
			//获取日
			//day =  ((dat[3] >> 4 ) & 0x03) * 10   +  (dat[3] & 0x0F);
			time->day = BCD2DECIMAL(dat[3] , 0x03);
			
			//获取星期
			time->week = dat[4] & 0x07 ;
				
			//获取月
			//month = (( dat[5] >> 4 ) & 0x01 ) * 10 + (dat[5] & 0x0F);
			time->month = BCD2DECIMAL(dat[5] , 0x01);
			
			/*
				获取年
				  1. 年份的值有 千位 +  百位 +  十位 +  个位 构成
					2. 但是在年的寄存器里面的只有 十位和个位的值。缺少千位和百位的值
					3. 需要去判定月份的第7位的值,由它来决定千位和百位的值
							3.1 如果一会月份的第7位 (C) 是 0 :  2000 + 年的数据   2000 ~ 2099
							3.2 如果一会月份的第7位 (C) 是 1 :  2100 + 年的数据   2100 ~ 2199 
							
							
							月份:  ?111 1111
							       1000 0000
										 -------------
										 0000 0000
										 1000 0000
				*/
			
			// 得到1 或者的到 0 
			//C = (dat[5] & 0x80) > 0 ? 1 : 0 ;
			
			//年份的数据需要配合前面的千位和百位
			//year = ((dat[6] >> 4) & 0x0F) * 10   + (dat[6] & 0x0F)  ;
			time->year = BCD2DECIMAL(dat[6] , 0x0F);
			
			//这里是为了判定是1 还是 0 
			if(dat[5] & 0x80){
			  time->year += 2100;
			}else{
				time->year += 2000;
			}
}


//写入时间
void RTC_WriteTime(RTC_Time * time){
	  u8 dat[7]; 
	  u8 new_year;
  
	  //写入秒 (BCD格式) : 十位 +  个位
	  dat[0]  = DECIMAL2BCD(time->second) ;
		
		
		//写入分 (BCD格式) : 十位 +  个位  39
		dat[1] = DECIMAL2BCD(time->minute) ;
		
		
		//写入时 (BCD格式) : 十位 +  个位
		dat[2] = DECIMAL2BCD(time->hour) ;
		
		
		//写入日  (BCD格式) : 十位 +  个位
		dat[3] = DECIMAL2BCD(time->day) ;
		
		
		//写入星期 (BCD格式) : 十位 +  个位
		dat[4] = DECIMAL2BCD(time->week);
		
		
		//写入月 (BCD格式) : 十位 +  个位
		//月份寄存器里面不光有月份的数据,也有世纪的数据【世纪的数据影响着后面的年份的数据】
		//先准备月份的数据
		dat[5] = DECIMAL2BCD(time->month) ;  // 0001 0010
		//再添加世纪的数据到月份的字节里面:修改月份的最高位 【第7位】,看C是什么? C是0,月份的第7位就是0 ,C是1 ,月份的第7位就是1
	
		
		
		//关于世纪C的赋值,要参考年份的数据,如果是20xx年,那么C就赋值 0 , 如果是 21xx年,那么C就赋值 1
		if(time->year >= 2100){ // 如果是 21xx年,那么C就赋值 1
		  dat[5] |= (1 << 7);
		}else{ // 如果是20xx年,那么C就赋值 0 
		  dat[5] &= ~(1 << 7);
		}
		
		//if(C == 0){ // C是0,月份的第7位就是0
		//   dat[5] &= ~(1 << 7);
		//}else{ // C是1 ,月份的第7位就是1
		//   dat[5] |= (1 << 7);
		//}
		
		
		//写入年 (BCD格式) : 十位 +  个位'
		// warning C182: pointer to different objects
		//比如年份是2023,先得到23这个值,
		new_year = time->year % 100;  // 23
		
		//再转换成 (BCD格式) : 十位 +  个位
		dat[6] = DECIMAL2BCD(new_year) ;
		
		
		//写数据
		I2C_WriteNbyte(DEV_ADDRESS,MEM_ADDRESS , dat , NUMBER);
}


//===============闹钟功能函数实现===================
//设置闹钟(开始闹钟)
void RTC_Alarm_Start(RTC_Alarm rtc_alarm){
	u8 dat;
	u8 alarm[4];
	u8 count= 0 ;
	
   //1. 设置闹钟的总开关:允许闹钟中断
		
		//1.1 先把原来的数据读取出来
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改过后,
		
		//a. 允许闹钟中断:: 把第1位置1
		dat |= 0x02;
		 
		//b. 清除闹钟发生过的标记 :: 把 第3位 置0
		dat &= ~0x08; 
		
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
		
		
		//2. 设置具体是什么时间触发闹钟。 30
		
		//2.1 时间必须是BCD格式,并且最高位第7位要置 0 
		//minute = DECIMAL2BCD(56) & ~0x80;
		
		if(rtc_alarm.minute_enable){
			alarm[0] = DECIMAL2BCD(rtc_alarm.minute) & 0x7F; //分
			count++;
		}
		if(rtc_alarm.hour_enable) {
			alarm[1] = DECIMAL2BCD(rtc_alarm.hour) & 0x7F; // 时
			count++;
		}
		if(rtc_alarm.day_enable){
			alarm[2] = DECIMAL2BCD(rtc_alarm.day) & 0x7F; // 日
		  count++;	
		}
		if(rtc_alarm.week_enable){
			alarm[3] = DECIMAL2BCD(rtc_alarm.week) & 0x7F; // 星期
			count++;
		}
		
		//2.2 把分钟时间设置进去
		I2C_WriteNbyte(0xA2, 0x09, alarm, count);
}

//停止闹钟(不设置闹钟了...)
void RTC_Alarm_Stop(){
		u8 dat;
  	//1.1 先把原来的数据读取出来
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改过后,
		
		//a. 不允许闹钟中断:: 把第1位置0
		dat &= ~0x02;
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
}

// 清除闹钟发生过的标记,以便闹钟在一次到点了还能触发
void RTC_Alarm_Clear_Flag(){
   u8 dat;
  	//1.1 先把原来的数据读取出来
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
	
	   //1.2. 清除闹钟发生过的标记 :: 把 第3位 置0
	
		dat &= ~0x08; 
	 
	  //1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
}

//===============定时功能函数实现===================

//启用定时器:: 参数一:频率值,  参数二:多少个这样的hz就执行触发定时
void RTC_Timer_Start(RTC_HZ hz_value , u8 hz_count){
		//1. 设置总开关
		u8 dat , hz;
	
		//1.1 先读取
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改之后
		//a. 允许定时器中断 == 1
		dat |= 0x01; 
		
		//b. 清除定时器已经触发的标记 == 0
		dat &= ~0x04;
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
		
		
		//2. 设置频率(数数频率,1s钟能数多少个数)
		hz = hz_value | 0x80; //  |0x80 表示把第7位置为 0 ,也就是要启用这个频率
		I2C_WriteNbyte(0xA2, 0x0E, &hz, 1);
		
		
		//3. 设置具体数多少个数就触发中断 (其实就是过去了多久要出发中断)
		I2C_WriteNbyte(0xA2, 0x0F, &hz_count, 1);
}

//禁用定时器
void RTC_Timer_Stop(){
		u8 dat;
	
		//1.1 先读取
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改之后
		//a. 进制定时器中断 == 0
		dat &= ~0x01; 
	
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
}

// 清除定时器发生过的标记,以便定时器在一次到点了还能触发
void RTC_Timer_Clear_Flag(){
		u8 dat;
	//1.1 先读取
		I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
		
		//1.2 修改之后
		//a.清除定时器已经触发的标记 == 0
		dat &= ~0x04;
		
		//1.3 再写进去
		I2C_WriteNbyte(0xA2, 0x01, &dat, 1);
	
}

RTC闹钟设置和RTC计数器设置共存

我们只需要在执行中断处进行判断,判断是闹钟进行中断还是计时器进行中断。

void RTC_ISR_Handle(){
	
	u8 dat;
	
	//1. 为了分辨到底是谁导致了中断发生,需要去读取控制寄存器
	I2C_ReadNbyte(0xA2, 0x01, &dat, 1);
	
	//printf("dat==%d\n" , (int)dat); // 0000 0111
	
	//2. 判定这个字节的第2位是否是 1 ,如果是1 即表示是 Timer触发了中断
	// dat: 							0000 0111
	// 鉴别第2位是否是1		0000 0100   ---- 0x04
	//-----------------------------------
	//										0000 0100 ---> 如果这个位是1,则出现的结果一定会大于 0 最终转出来的十进制不是 0
	//printf("dat & 0x04=%d \n" , (int)(dat & 0x04) );
	if(dat & 0x04){
	  	printf("timer...\n");
		
		//来到这里折后,为了让下一次的定时到点了还能来这里,需要手动清理标记位
		RTC_Timer_Clear_Flag();
	}
	
	
	//3. 判定这个字节的第3位是否是1 , 如果是1,即表示是Alarm触发了中断
	// dat: 							0000 0111
	// 鉴别第3位是否是1		0000 1000 --- 0x08
	//-----------------------------------
	// 										0000 0000  ---> 如果这个位是1,则出现的结果一定会大于 0 最终转出来的十进制不是 0
	//printf("dat & 0x08=%d \n" , (int)(dat & 0x08) );
	if(dat & 0x08){
	   printf("alarm...\n");
		//来到这里之后,为了让下一次的闹钟到点了还能来这里,需要手动清理标记位
		RTC_Alarm_Clear_Flag();
	}
	
	
}

总结

        这样RTC时钟我们就介绍完了,这里提示一下,后两个部分的RTC闹钟设置和RTC计时器设置

中的基础代码都是在上期已经封装好的代码中继续编写得到的,本期只展示了main.c中的代码,请大家特别注意!!!封装部分代码完整。下期见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/286800.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

论文笔记:CellSense: Human Mobility Recovery via Cellular Network Data Enhancement

1 intro 1.1 背景 1.1.1 蜂窝计费记录&#xff08;CBR&#xff09; 人类移动性在蜂窝网络上的研究近些年得到了显著关注&#xff0c;这主要是因为手机的高渗透率和收集手机数据的边际成本低蜂窝服务提供商收集蜂窝计费记录&#xff08;CBR&#xff09;用于计费目的&#xf…

哲学家进餐问题-第三十二天

目录 问题描述 解决问题 结论 问题描述 解决问题 1、 关系分析&#xff1a;找出题目中描述的各个进程&#xff0c;分析它们之间的同步、互斥关系 系统中有5个哲学家进程&#xff0c;5位哲学家与左右邻居对其中间筷子的访问是互斥关系 2、整理思路&#xff1a;根据各进程的操…

ubuntu terminator 非常好用的护眼配置

安装 sudo apt install terminator 配置文件&#xff1a;sudo gedit ~/.config/terminator/config &#xff08;如果没有就创建&#xff09; 配置如下&#xff1a; [global_config] handle_size -3 title_transmit_fg_color "#000000" title_trans…

KBDNO1.DLL文件缺失,软件或游戏无法启动运行,怎样快速修复

不少小伙伴&#xff0c;求助电脑报错“KBDNO1.DLL文件缺失&#xff0c;软件或游戏无法启动或运行”&#xff0c;应该怎么办&#xff1f; 首先&#xff0c;我们先来了解“KBDNO1.DLL文件”是什么&#xff1f; KBDNO1.DLL是Windows操作系统中的一个动态链接库文件&#xff0c;主…

Java基础进阶(学习笔记)

注&#xff1a;本篇的代码和PPT图片来源于黑马程序员&#xff0c;本篇仅为学习笔记 static static 是静态的意思&#xff0c;可以修饰成员变量&#xff0c;也可以修饰成员方法 修饰成员的特点&#xff1a; 被其修饰的成员, 被该类的所有对象所共享 多了一种调用方式, 可以通过…

MACOS Atrust服务异常

MAC版Atrust服务异常 点击进入办公后出现提示其一&#xff1a; 核心服务未启动&#xff0c;部分功能存在异常&#xff0c;确定重新启动吗&#xff1f; 可能的原因&#xff1a; 1.上次已完全退出客户端 2.核心服务被其他程序优化禁用 点击重新启动后&#xff0c;出现提示&#x…

Spring Boot 如何使用 Maven 实现多环境配置管理

Spring Boot 如何使用 Maven 实现多环境配置管理 实现多环境配置有以下几个重要原因&#xff1a; 适应不同的部署环境&#xff1a;在实际部署应用程序时&#xff0c;通常会有多个不同的部署环境&#xff0c;如开发环境、测试环境、生产环境等。每个环境可能需要不同的配置&…

虹科方案丨从困境到突破:TigoLeap方案引领数据采集与优化变革

来源&#xff1a;虹科工业智能互联 虹科方案丨从困境到突破&#xff1a;TigoLeap方案引领数据采集与优化变革 原文链接&#xff1a;https://mp.weixin.qq.com/s/H3pd5G8coBvyTwASNS_CFA 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 导读 在数字化工厂和智能制造时…

【新手小白的xsslab靶场学习】

第1关 最开始页面源代码 直接输入<script>alert(1)</script> 第2关 页面源代码 先尝试<script>alert(1)</script>看页面源代码 <h2>里面尖括号被编码&#xff0c;<input>里面没有编码,直接双引号闭合&#xff0c; 修改payload&…

Python 数据库(一):使用 mysql-connector-python 操作 MySQL 数据库

大家好&#xff0c;我是水滴~~ 当涉及到使用 Python 操作 MySQL 数据库时&#xff0c;mysql-connector-python 库是一个强大而常用的选择。该库提供了与 MySQL 数据库的交互功能&#xff0c;使您能够执行各种数据库操作&#xff0c;如连接数据库、执行查询和插入数据等。在本文…

Unity ShaderGraph 技能冷却转圈效果

Unity ShaderGraph 技能冷却转圈效果 前言项目场景布置代码编写ShaderGraph 连线总结 参考 前言 遇到一个需求&#xff0c;要展示技能冷却的圆形遮罩效果。 项目 场景布置 代码编写 Shader核心的就两句 // 将uv坐标系的原点移到纹理中心 float2 uv i.uv - float2(0.5, 0…

安装VS Code到AWS EC2 Linux 2

文章目录 小结问题及解决参考 小结 本文记录了安装VS Code到AWS EC2 Linux 2。 问题及解决 安装VS Code到AWS EC2 Linux 2采取了以下步骤&#xff1a; 更新VS Code的YUM源&#xff1a; [ec2-userip-100-92-28-119 ~]$ sudo rpm --import https://packages.microsoft.com/k…

Typora+PicGo+Gitee构建云存储图片

创建Gitee仓库 首先&#xff0c;打开工作台 - Gitee.com&#xff0c;自行注册一个账户 注册完后&#xff0c;新建一个仓库&#xff08;记得仓库要开源&#xff09; 然后创建完仓库后&#xff0c;鼠标移动到右上角头像位置&#xff0c;选择设置&#xff0c;并点击&#xff…

深度生成模型之GAN基础 ->(个人学习记录笔记)

文章目录 深度生成模型之GAN基础生成对抗网络1. 生成对抗网络如何生成数据2. 生成对抗原理3. GAN的核心优化目标4. D的优化5. GAN的理想状态6. GAN的训练7. 梯度不稳定与模式崩塌(collapse mode)问题8. 梯度消失问题 深度生成模型之GAN基础 生成对抗网络 1. 生成对抗网络如何…

安全数据交换系统:有效提升网间文件交换能力

各级政府部门和金融、能源、电力这些行业&#xff0c;以及一些大中型企业组织&#xff0c;为了保护内部的重要数据不外泄&#xff0c;普遍都采用了多网络并行的方式&#xff0c;也是做了网络隔离划分&#xff0c;不同的网络拥有不同的密级以及人员权限。然后再通过安全数据交换…

机器人动力学一些笔记

动力学方程中&#xff0c;Q和q的关系(Q是sita) Q其实是一个向量&#xff0c;q(Q1&#xff0c;Q2&#xff0c;Q3&#xff0c;Q4&#xff0c;Q5&#xff0c;Q6)&#xff08;假如6个关节&#xff09; https://zhuanlan.zhihu.com/p/25789930 举个浅显易懂的例子&#xff0c;你在房…

大文件断点下载Range下载zip包显示文件损坏

问题&#xff1a;大文件下载&#xff0c;其它格式的文件及rar格式的压缩包正常下载但是 之后zip包下载后解压失败 原因分析: 1. 查看上传文件的属性值 如图&#xff0c;10.4kb是已经约去小数点的值&#xff0c;准确的大小应该是10663字节10.4130859375KB&#xff0c;所以用10.…

微服务-理论(CAP,一致性协议)

CAP理论 关于CAP理论的介绍可以直接看这篇文章 CAP分别是什么&#xff1f; 一致性&#xff08;Consistency 一致性包括强一致性&#xff0c;弱一致性&#xff0c;最终一致性。 一致性其实是指数据的一致性&#xff0c;为什么数据会不一致呢&#xff1f; 如上面这张图&…

R语言——reshape2包、tidyr包、dplyr包(五)

目录 一、数据转换之reshape2包&#xff1a;melt与dcast函数 二、数据转换之tidyr包&#xff1a;gather与spread函数&#xff0c;separate与unite函数 三、据转换之dplyr包 四、参考 一、数据转换之reshape2包&#xff1a;melt与dcast函数 merge 函数 使用merge函数 x &l…

每日一练:LeeCode-739. 每日温度(中)【单调栈】

本文是力扣LeeCode-739. 每日温度&#xff08;中&#xff09; 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其…
最新文章