前言
这一期我们首先学习如何让蜂鸣器响起来,并且如何让蜂鸣器发出简单的歌曲,然后我们介绍RTC时钟,要想明白RTC时钟,我们还需要先介绍I2C总线和外部中断。接下来就开始这一期的学习吧!
蜂鸣器
简单介绍
蜂鸣器是一种能够产生固定频率的声音的电子元件。它通常由振膜、震荡器、放大器和声音反馈电路等部分组成。振膜是蜂鸣器中最核心的部分,它能够将电信号转换为机械振动,产生声音。震荡器提供稳定的电信号,用于驱动振膜产生振动。放大器用于放大电信号的幅度,以便产生足够的声音。声音反馈电路可以提供反馈信号,帮助系统稳定。
原理图
分析:由原理图可以看到这里用到的是NPN型三极管,当电压为高电压时三极管导通,并且是由引脚P00控制的。
代码演示
简单发声
cpp
#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测试发声
cpp
#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测试发声
cpp
#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
cpp
#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
cpp
#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
cpp
#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文件中改造。
cpp
#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读取数据
cpp
#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写入数据(数组封装)
cpp
#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
cpp
#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
cpp
#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
cpp
#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线是数据线,用于传输实际的数据.
设备的地址通常是由设备制造商确定的,并在设备的数据手册中公布。
- Master: 主设备。通常是主控MCU
- Slave:从设备。通常是功能芯片,例如RTC时钟,陀螺仪,温湿度等等。
- SCL:时钟线,控制数据传输的速度和时序。
- SDA:数据线。传输数据的。
- 地址:从设备地址。主设备通过地址进行访问。在总线中,每个从设备地址唯一。
STC8H内置了一组I2C接口
|-----------|---------|---------|
| I2C接口 | SCL | SDA |
| I2C1 | P1.5 | P1.4 |
| I2C1 | P2.5 | P2.4 |
| I2C1 | P3.2 | P3.3 |
外部中断(EXTI)
简单介绍
|------|------|-------------|
| 外部中断 | 引脚 | 备注 |
| INT0 | P3.2 | 支持上升沿和下降沿中断 |
| INT1 | P3.3 | 支持上升沿和下降沿中断 |
| INT2 | P3.6 | 只支持下降沿中断 |
| INT3 | P3.7 | 只支持下降沿中断 |
| INT4 | P3.0 | 只支持下降沿中断 |
在数字电路中,信号的电平变化分为上升沿和下降沿。
上升沿指的是信号从低电平变为高电平的瞬间,下降沿指的是信号从高电平变为低电平的瞬间。
例如,当一个开关被按下时,电路中的信号从低电平变为高电平,此时发生了一个上升沿;当开关被松开时,信号从高电平变为低电平,此时发生了一个下降沿。在数字电路中,上升沿和下降沿往往会被用作时序控制和数据传输等方面的参考信号。
代码演示
cpp
#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闹钟设置
cpp
#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
cpp
#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
cpp
#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
cpp
#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计时器设置
cpp
#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
cpp
#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
cpp
#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
cpp
#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计数器设置共存
我们只需要在执行中断处进行判断,判断是闹钟进行中断还是计时器进行中断。
cpp
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中的代码,请大家特别注意!!!封装部分代码完整。下期见!