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

news2024/11/19 22:44:55

前言

        这一期我们首先学习如何让蜂鸣器响起来,并且如何让蜂鸣器发出简单的歌曲,然后我们介绍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.coloradmin.cn/o/1351552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

geemap学习笔记039:分析地理空间数据--合成无云影像

前言 本节介绍的内容是对于众多的原始Landsat数据&#xff0c;利用ee.Algorithms.Landsat.simpleComposite()将其处理为TOA数据&#xff0c;并且合成无云影像。 1 导入库并显示地图 import ee import geemap ee.Initialize()2 无云影像合成 Map geemap.Map()collection e…

王道考研计算机组成原理——数据的表示和运算

数制转换 任意进制》十进制&#xff1a;位权*位数即可 整数部分补0是补在头部&#xff0c;小数部分补0是补在尾部 一般都是先把十进制》二进制&#xff1b;然后二进制再转换成8/16进制这样子 一种更快的方法->拼凑法&#xff1a;小数部分整数部分都可以这样求 一般都是先…

TypeError: unsupported operand type(s) for +: ‘NoneType‘ and ‘str‘

报错 找到出错代码&#xff0c;发现默认值是None 解决 改为‘’即可

vba抓取网页数据

哈喽&#xff0c;哈喽&#xff0c;大家好&#xff01;大家2024发大财啦&#xff01; 不知道&#xff0c;平时大家爱不爱看电影呢&#xff1f;从今年的贺岁档的拍片来看&#xff0c;今年的电影还挺多&#xff0c;而且国产优秀电影居多&#xff0c;元旦假期期间我也去看了部喜剧…

【BCC动态跟踪PostgreSQL】

BPF Compiler Collection (BCC)是基于eBPF的Linux内核分析、跟踪、网络监控工具。其源码存放于GitCode - 开发者的代码家园 想要监控PostgreSQL数据库的相关SQL需要在编译PostgreSQL的时候开启dtrace。下文主要介绍几个和PostgreSQL相关的工具,其他工具可根据需求自行了解。 …

ChatGPT 进行 SEO的使用技巧

搜索引擎优化 (SEO) 是使网站对搜索引擎友好的一种不断发展的实践。 自搜索引擎和新兴技术的发展以来&#xff0c;它从未保持不变。 最近发布的 ChatGPT 是一种人工智能对话工具&#xff0c;似乎在搜索引擎优化方面有很好的应用。 从创建吸引人的标题到只需一个简短的提示就可…

Latex宏包gbt7714的格式问题:去掉OL

问题 采用bibtex来格式化文献&#xff0c;文献的格式采用gbt7714宏包来格式化。感谢宏包的作者和一种贡献者&#xff0c;效果非常好&#xff0c;用起来也很方便。 唯一一个我自己的问题&#xff0c;看不得文献索引后面[J/OL]中的OL。 网上搜索一圈&#xff0c;有一些办法&am…

echarts点击柱子、线节点、横坐标标签

echarts点击柱子、线节点、横坐标标签 x轴标签要为标签文字添加点击事件&#xff0c;必须先在xAxis里设置响应事件 xAxis: {triggerEvent: true, },然后直接监听 thisChart.on("click" ,function (params) {console.log(params) });通过params.componentSubType来…

三、C语言中的分支与循环—break和continue语句(8)循环结构 完

本章分支结构的学习内容如下&#xff1a; 三、C语言中的分支与循环—if语句 (1) 三、C语言中的分支与循环—关系操作符 (2) 三、C语言中的分支与循环—条件操作符 与逻辑操作符(3) 三、C语言中的分支与循环—switch语句&#xff08;4&#xff09;分支结构 完 本章循环结构的…

720VR全景通微信小程序商业运营版+多用户+云储存+大图切图效率高+完整的代码包以及搭建教程 功能强大

随着科技的飞速发展&#xff0c;虚拟现实技术已经逐渐融入我们的日常生活。其中&#xff0c;720VR全景技术以其独特的视角和沉浸式体验&#xff0c;受到了广泛的关注和应用。为了满足市场需求&#xff0c;春哥团队推出了720VR全景通微信小程序商业运营版&#xff0c;集多用户、…

基于PI控制的PMSM永磁同步电机控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 PMSM数学模型 4.2 矢量控制策略 4.3 PI控制器设计 4.4 控制系统实现 5.完整工程文件 1.课题概述 基于PI控制的PMSM永磁同步电机控制系统simulink建模与仿真。其中&#xff0c;基于PI&#xff08;…

使用UDP和JSON在C#中高效发送结构体数据

使用UDP和JSON在C#中高效发送结构体数据 引言 在许多网络编程场景中&#xff0c;我们经常需要在不同的应用程序或服务之间发送和接收数据。UDP&#xff08;用户数据报协议&#xff09;因其低延迟和少开销的特点&#xff0c;在需要快速数据传输的场景中非常有用。本文介绍了如何…

【FPGA/verilog -入门学习15】vivado FPGA 数码管显示

1&#xff0c;需求&#xff1a;使用xc720 开发板的8个数码管显示12345678 2&#xff0c;需求分析&#xff1a; 75hc595 1&#xff0c;74hc595驱动&#xff0c;将串行数据转换成并行输出。对应研究手册 2&#xff0c;发送之前将要发的数据&#xff0c;合并成高8位:SEG,低8位&…

【算法】利用分治思想解算法题:快排、归并、快速选择实战(C++)

1. 分治思想 介绍 分治法将问题划分成多个相互独立且相同或类似的子问题&#xff0c;然后递归地解决每个子问题&#xff0c;并将结果合并以得到原始问题的解。 分治思想通常包含以下三个步骤&#xff1a; 分解&#xff1a;将原始问题划分成多个规模较小、相互独立且类似的子…

如何恢复 iPhone 上永久删除的照片?

2007年&#xff0c;苹果公司推出了一款惊天动地的智能手机&#xff0c;也就是后来的iPhone。你会惊讶地发现&#xff0c;迄今为止&#xff0c;苹果公司已经售出了 7 亿部 iPhone 设备。根据最新一项调查数据&#xff0c;智能手机利润的 95% 都进了苹果公司的腰包。 如此受欢迎…

用户管理第2节课--idea 2023.2 后端--实现基本数据库操作(操作user表) -- 自动生成 --【本人】

一、插件安装 1.1 搜索插件 mybatis 安装 1.2 接受安装 1.3 再次进入&#xff0c;说明安装好了 1.4 与鱼皮不同点 1&#xff09;mybatis 版本不一致 鱼皮&#xff1a; 本人&#xff1a; 2&#xff09;鱼皮需重启安装 本人不需要 1.5 【需完成 三、步骤&#xff0c;再来看】 …

webpack 5 loader

webpack 本身不能识别js&#xff0c;json外的资源&#xff0c;所以我们需要借助其他loader来处理对应的文件 CSS Loader&#xff0c;处理css 安装 npm i css-loader style-loader -D css-loader 负责讲css编译成webpack能识别的模块内容style-loader 动态创建<style&g…

SQL之CASE WHEN用法详解

目录 一、简单CASE WHEN函数&#xff1a;二、CASE WHEN条件表达式函数三、常用场景 场景1&#xff1a;不同状态展示为不同的值场景2&#xff1a;统计不同状态下的值场景3&#xff1a;配合聚合函数做统计场景4&#xff1a;CASE WHEN中使用子查询场景5&#xff1a;经典行转列&am…

【PTA-C语言】实验八-函数与指针II

如果代码存在问题&#xff0c;麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 目录——实验八-函数与指针II 6-1 移动字母&#xff08;分数 10&#xff09;6-2 删除字符&#xff08;分数 10&#xff09;6-3 函数实现字符串逆序&#xff08;分数 10&#xff09;6-4 递归计算Ackermenn函数…

CTFshow web入门web127-php特性30

开启环境: extract() 函数从数组中将变量导入到当前的符号表&#xff0c;使用数组键名作为变量名&#xff0c;使用数组键值作为变量值 举例就是?a2&#xff0c;就会变成$a2&#xff0c;这里ctf_show有个_需要构造&#xff0c;前面说过php中变量名只有数字字母下划线&#xff…