STM32F4_RTC实时时钟

news2024/11/15 23:22:52

目录

1. RTC实时时钟简介

2. RTC框图

3. RTC初始化和配置

3.1 RTC和低功耗模式

3.2 RTC中断

4. RTC相关寄存器

4.1 时间寄存器:RTC_TR

4.2 日期寄存器:RTC_DR

4.3 亚秒寄存器:RTC_SSR

4.4 控制寄存器:RTC_CR

4.5 RTC初始化和状态寄存器:RTC_ISR

4.6 预分频寄存器:RTC_PRER

4.7 唤醒定时器寄存器:RTC_WUTR

4.8 闹钟A寄存器:RTC_ALRMAR

4.9 写保护寄存器:RTC_WPR

4.10 等同于EEPROM记忆芯片的备份寄存器:RTC_BKPxR

4.11 备份区域控制寄存器:RCC_BDCR

5. 库函数配置RTC

6. 实验程序

6.1 日历配置

6.2 闹钟配置

6.3 周期性自动唤醒配置

6.4 实验完整程序

6.4.1 main.c

6.4.2 RTC.c

6.4.3 RTC.h

6.4.4 总结


1. RTC实时时钟简介

        实时时钟RTC是一个独立的BCD定时器 / 计数器。RTC提供一个日历时钟、两个可编程闹钟中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC还包含用于管理低功耗模式的自动唤醒单元。

        两个32位寄存器包含二进码十进数格式(BCD)的秒、分钟、小时(12或24小时制)、星期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。同时系统可以自动将月份的天数补偿为28、29(闰年)、30和31天。并且还可以进行夏令时补偿。其他32位寄存器还包含可编程的闹钟亚秒、秒、分钟、小时、星期几和日期。

        此外,还可以使用数字校准功能对晶振精度的偏差进行补偿。上电复位后,所有RTC寄存器都会受到保护,以防止可能的非正常写访问。

        注意:无论器件是处于运行模式、低功耗模式或者复位模式,只要电源电压保持在正常工作范围,RTC就不会停止工作。

2. RTC框图

时钟和预分频器:

实时时钟和日历:

        STM32F4的RTC日历时间(RTC_TR)和日期(RTC_DR)寄存器,用于存储时间和日期,也可以用于设置时间和日期,可以通过与PCLK1同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问。

        每隔2个RTCCLK周期,当前日历值便会复制到影子寄存器,并置位RTC_ISR寄存器的RSF位。我们可以读取RTC_TR和RTC_DR来得到当前的时间和日期信息。

        注意:时间和日期都是以BCD码的格式存储的,读出来时需要转换为10进制数据,中间存在一个转换关系。

可编程闹钟:

STM32F4提供两个可编程闹钟:闹钟A(ALARM_A)和闹钟B (ALARM_B)。通过RTC_CR寄存器的ALRAE和ALRBE位置1来使能可编程闹钟功能。当日历的亚秒、秒、分、小时、日期分别与闹钟寄存器RTC_ALRMASSR/RTC_ALRMAR和RTC_ALRMBSSR/RTC_ALRMBR中的值匹配时,则可以产生闹钟。

如果配置A闹钟,则配置RTC_ALRMASSR/RTC_ALRMAR即可。

如果配置B闹钟,则配置RTC_ALRMBSSR/RTC_ALRMBR即可。

周期性自动唤醒:

周期性唤醒功能,由一个16位可编程自动重载递减计数器(RTC_WUTR)生成,可用于周期性中断/唤醒

唤醒定时器的时钟输入可以是:2 4 6 8或者16分频的RTC时钟(RTCCLK),也可以是ck_spre时钟。

当选择RTCCLK作为输入时钟时,可配置的唤醒中断周期介于122us和32s之间,分辨率最低为61us。

当选择ck_spre作为输入时钟时,可得到的唤醒时间为1s到36h左右,分辨率为1秒。 

        初始化完成后,定时器开始递减计数。在低功耗模式下,使能唤醒功能时,递减计数保持有效。当计数器计数到0时,RTC_ISR寄存器的WUTF标志会置1,并且唤醒寄存器会使用其重载值(RTC_WUTR寄存器值)重载,之后必须用软件清0  WUTF 标志;

3. RTC初始化和配置

RTC寄存器访问

        RTC寄存器为32位寄存器。除了当BYPSHAD=0时对日历影子寄存器执行的读访问之外,APB接口会在访问RTC寄存器时引入两个等待周期。

RTC寄存器写保护

        系统复位后,可通过PWR电源控制寄存器(PWR_CR)的DBP位保护RTC寄存器以防止非正常的写访问。必须将DBP位置1才能使能RTC寄存器写访问。在上电复位后,所有的RTC寄存器均受到写保护。通过向写保护寄存器(RTC_WPR)写入一个密钥来使能对RTC寄存器的写操作

日历初始化和配置

1. 将 RTC_ISR 寄存器中的 INIT 位置 1 进入初始化模式。在此模式下,日历寄存器停止工作并且这时候的值是可以更新的;

2. 等待RTC_ISR寄存器的INITF位置1,当该位置1时,进入初始化阶段模式,大约需要2个RTCLCK时钟周期。

3. 要为日历计数器生成 1 Hz 时钟,应首先编程 RTC_PRER 寄存器中的同步预分频系数, 然后编程异步预分频系数

4. 在影子寄存器(RTC_TR 和 RTC_DR)中加载初始时间和日期值,然后通过 RTC_CR 寄存器中的 FMT 位配置时间格式(12 或 24 小时制)

5. 通过清零 INIT 位退出初始化模式。随后,自动加载实际日历计数器值,在 4 个 RTCCLK 时钟周期后重新开始计数

注意:初始化序列结束以后,日历开始计数。

夏令时

可通过RTC_CR寄存器的SUB1H、ADD1H、BKP位管理夏令时。

利用SUB1H和ADD1H,软件只需要单次操作便可在日历中减去或增加一个小时,无需执行整个初始化步骤。

编译闹钟

要对可编程的闹钟(闹钟 A 或闹钟 B)进行编程或更新

1. 将 RTC_CR 寄存器中的 ALRAE 或 ALRBE 位清零以禁止闹钟 A 或闹钟 B

2. 轮询 RTC_ISR 寄存器中的 ALRAWF 或 ALRBWF 位,直到其中一个置 1,以确保闹钟 寄存器可以访问。大约需要 2 个 RTCCLK 时钟周期(由于时钟同步)。

3. 编程闹钟 A 或闹钟 B 寄存器(RTC_ALRMASSR/RTC_ALRMAR 或 RTC_ALRMBSSR/RTC_ALRMBR)

4. 将 RTC_CR 寄存器中的 ALRAE 或 ALRBE 位置 1 以再次使能闹钟 A 或闹钟 B

编程唤醒寄存器

1.清零 RTC_CR 中的 WUTE 以禁止唤醒定时器。

2. 轮询 RTC_ISR 中的 WUTWF,直到该位置 1,以确保可以访问唤醒自动重载定时器和 WUCKSEL[2:0] 位。大约需要 2 个 RTCCLK 时钟周期(由于时钟同步)。

3. 编程唤醒自动重载值 WUT[15:0],并选择唤醒时钟(RTC_CR 中的 WUCKSEL[2:0] 位)。 将 RTC_CR 寄存器中的 WUTE 位置 1 以再次使能定时器。唤醒定时器重新开始递减 计数。

3.1 RTC和低功耗模式

3.2 RTC中断

所有的RTC中断均与EXTI控制器相连:

要使能RTC闹钟中断,须

1. 将EXTI线17配置为中断模式并将其使能,然后选择上升沿有效。

2. 配置NVIC中的RTC_Alarm IRQ通道并将其使能。

3. 配置RTC以生成RTC闹钟(闹钟A或者闹钟B)。

要使能RTC唤醒中断,须

1. 将EXTI线22配置为中断模式并将其使能,然后选择上升沿有效。

2. 配置NVIC中的RTC_WKUP IRQ通道并将其使能。

3. 配置RTC以生成RTC唤醒定时器事件。

要使能RTC入侵中断,须

1. 将EXTI线21配置为中断模式并将其使能,然后选择上升沿有效。

2. 配置NVIC中的TAMP_STAMP IRQ通道并将其使能。

3. 配置RTC以检测RTC入侵事件。

要使能RTC时间戳中断,须

1. 将EXTI线21配置为中断模式并将其使能,然后选择上升沿有效。

2. 配置NVIC中的TAMP_STAMP IRQ通道并将其使能。

3. 配置RTC以检测RTC时间戳事件。

4. RTC相关寄存器

4.1 时间寄存器:RTC_TR

时间寄存器:RTC_TR(RTC time register)   RTC_TR是日历时间影子寄存器。只能在初始化模式下对该寄存器执行写操作。

注意该寄存器下数据保存是BCD格式的,读取之后需要进行转换(这也是STM32的优越性,51单片机必须手动转换成BCD码;而STM32单片机只需要通过该寄存器就可以完成二进制到BCD码的转换),才是10进制的时分秒数据,在初始化模式下,对该寄存器进行写操作,可以设置时间。

4.2 日期寄存器:RTC_DR

日期寄存器:RTC_DR(RTC data register)RTC_DR是日历日期影子寄存器。只能在初始化模式下对该寄存器执行写操作。

注意该寄存器的数据同样采用BCD码格式,在初始化模式下,对该寄存器进行写操作,可以设置日期。

4.3 亚秒寄存器:RTC_SSR

亚秒寄存器:RTC_SSR(RTC sub second register)

很显然,该寄存器用于设置更精确的时间。

4.4 控制寄存器:RTC_CR

控制寄存器:RTC_CR(RTC control register)  下图为配置闹钟A,配置闹钟B原理一样,只需要配置相应的位A-->B即可。

4.5 RTC初始化和状态寄存器:RTC_ISR

RTC初始化和状态寄存器:RTC_ISR(RTC initialization and status register)

4.6 预分频寄存器:RTC_PRER

预分频寄存器:RTC_PRER(RTC prescaler register)

注意: 该寄存器必须在初始化模式下INITF=1下,才可以进行。

4.7 唤醒定时器寄存器:RTC_WUTR

唤醒定时器寄存器:RTC_WUTR(RTC wakeup timer register)

注意: 该寄存器必须在RTC_ISR的WUTWF=1下,才可以进行。

4.8 闹钟A寄存器:RTC_ALRMAR

闹钟A寄存器:RTC_ALRMAR(RTC alarm A register)

该寄存器用于设置闹钟A,当位30 WDSEL选择1时,使用星期制闹钟;

该寄存器的配置,必须等待RTC_ISR的ALRAWF为 1 才可以进行。

4.9 写保护寄存器:RTC_WPR

写保护寄存器:RTC_WPR(RTC write protection register)

写保护寄存器的低八位有效。上电后,除了RTC_ISR[13:8]、RTC_TAFCR和RTC_BKPxR寄存器,必须依次写入:0xCA、0x53两个关键字到写保护寄存器RTC_WPR,才可以解锁。写一个错误的关键字将再次激活RTC的寄存器写保护。

4.10 等同于EEPROM记忆芯片的备份寄存器:RTC_BKPxR

备份寄存器:RTC_BKPxR(RTC backup registers)(很重要)

        该寄存器组总共有20个,每个寄存器都是32位,可以存储80个字节的用户数据,这些寄存器在备份域中实现,可在VDD电源关闭时通过VBAT保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在MCU从待机模式唤醒时复位。

我们可以用BKP来存储一些重要信息,相当于一个EEPROM(类似于51上的记忆存储芯片),不过这个EEPROM需要电池来维持他的数据。

4.11 备份区域控制寄存器:RCC_BDCR

RTC的时钟源选择及使能设置是通过这个寄存器来实现的,所以我们在RTC操作之前先要通过这个寄存器选择RTC时钟源,然后才能开始其他操作。

5. 库函数配置RTC

1. 使能电源时钟,并使能RTC及RTC后备寄存器写访问

电源时钟使能,通过RCC_APB1ENR寄存器来设置;RTC及RTC备份寄存器的写访问,通过PWR_CR寄存器的DBP位设置;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);    //使能PWR时钟         -----------前面的标志就是对应需要使用的寄存器

PWR_BackupAccessCmd(ENABLE);      //使能后备寄存器访问 

2. 开启外部低速振荡器,选择RTC时钟,并使能

这一步骤,只需要在RTC初始化的时候执行一次即可,不需要每次上电都执行,这些操作都是通过 4.11 介绍的备份区域控制寄存器RCC_BDCR 来实现的。

RCC_LSEConfig(RCC_LSE_ON);     //LSE 开启 

RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);      //设选择LSE作为RTC时钟 

RCC_RTCCLKCmd(ENABLE);      //使能RTC时钟

3. 初始化RTC,设置RTC的分频,以及配置RTC函数

该步骤同以往学习的初始化结构体一样,设置结构体变量,分别去配置结构体成员变量即可。

ErrorStatus RTC_Init(RTC_InitTypeDef* RTC_InitStruct);       //RTC初始化函数,RTC初始化参数结构体为RTC_InitTypeDef定义;

typedef struct 
{  
uint32_t RTC_HourFormat;  //设置RTC时间格式
//也就是控制寄存器CR的FMT位。可以选择为24小时/12小时格式;RTC_HourFormat_24/RTC_HourFormat_12;
uint32_t RTC_AsynchPrediv;  //设置RTC的异步分频系数
//也就是设置RTC_PRER寄存器的PREDIV_A位的值。
//注意:异步预分频系数为7位,所以最大值为0x7F,不能超过这个值
uint32_t RTC_SynchPrediv;    //设置RTC同步预分频系数
//也就是设置RTC_PRER寄存器的PREDIV_S位的值
//注意:同步预分频系数为15位,所以最大值为0x7FFF,不能超过这个值
}RTC_InitTypeDef; 
//初始化函数时:
//RTC_Init函数在设置RTC相关参数之前,会先取消RTC写保护,这个操作通过向寄存器RTC_WPR写入
//0xCA和0X53两个数据来实现


RTC->WPR=0XCA;
RTC->WPR=0X53;



//先取消写保护之后,通过上述寄存器的学习,我们知道想要对寄存器进行写操作
//必须先进入RTC初始化模式,才可以进行

ErrorStatus RTC_EnterInitMode(void); //进入RTC初始化模式的函数

//当进去初始化模式之后,RTC_Init函数才去设置RTC->CR和RTC->PRER寄存器的值。在设置完成以后
//我们还需要通过程序退出初始化模式

void RTC_ExitInitMode(void)  //退出RTC初始化模式的函数

//最后开启RTC写保护

RTC->WPR=0XFF;

4. 设置RTC时间

ErrorStatus RTC_SetTime(uint32_t RTC_Format, RTC_TimeTypeDef* RTC_TimeStruct);   //设置RTC时间的函数 , 其实质上是设置RTC_TR      寄存器相关位的值

该函数第一个参数RTC_Format用来设置输入的时间格式;可选择为BIN和BCD,RTC_Format_BIN或者RTC_Format_BCD;

//第二个参数为初始化结构体RTC_TimeTypeDef

typedef struct 
{  
uint8_t RTC_Hours;  //时间参数的小时
uint8_t RTC_Minutes;  //分钟
uint8_t RTC_Seconds;  //秒
uint8_t RTC_H12;   //AM/PM符号
}RTC_TimeTypeDef; 

5. 设置RTC日期

ErrorStatus RTC_SetDate(uint32_t RTC_Format, RTC_DateTypeDef* RTC_DateStruct);    //设置RTC日期函数 其实质是访问RTC_DR寄存器来设置相关位;

第一个参数也是设置时间格式,BCD还是BIN;

//第二个初始化参数为结构体RTC_DateTypeDef

typedef struct 
{  
uint8_t RTC_WeekDay; //设置日期为星期几
 uint8_t RTC_Month;  //月份
uint8_t RTC_Date;  //日期
uint8_t RTC_Year;   //年份
}RTC_DateTypeDef; 

6. 获取RTC当前日期和时间

void RTC_GetTime(uint32_t RTC_Format, RTC_TimeTypeDef* RTC_TimeStruct);    //获取当前RTC时间的函数

void RTC_GetDate(uint32_t RTC_Format, RTC_DateTypeDef* RTC_DateStruct);     //获取当前RTC日期的函数

这两个函数的实质就是读取RTC_TR和RTC_DR寄存器的时间和日期的值,然后将值存放到相应的结构体中。

//RTC初始化
//返回值:0初始化成功
//		  1 LSE开启失败
u8 My_RTC_Init(void)
{
	u16 retry=0x1FFF;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能PWR时钟
	PWR_BackupAccessCmd(ENABLE);//使能后备寄存器
	
	if(RTC_ReadBackupRegister(RTC_BKP_DR0)!=0x5051)//判断是否为第一次配置,这里的0x5051完全是自己设置的一个标志位
		//RTC_ReadBackupRegister叫做读取后备寄存器,RTC_BKP_DR0为寄存器的标志位,如果读取标志位不为我们设置的0x5051
    //(0x5051是我们随便构想的一个32位值,如果是第一次配置该寄存器,不可能第一次的值就是我们随便构想的值)
	//则表示是第一次配置,执行下述程序;如果不是第一次配置,断电从启后会跳过该代码
	{
		RCC_LSEConfig(RCC_LSE_ON);//LSE开始,打开时钟
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)//while循环判断LSE开启的状态位是否置1
			//显然while循环离开的标志是获取的状态位RCC_FLAG_LSERDY等于1,离开也就代表这LSE开启成功
		{
			retry++;
			delay_ms(10);
		}
		if(retry==0)
			return 1;//LSE开启失败
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//既然程序没有返回1能来到这,一定表示LSE开启成功了,那么设置LSE作为RTC时钟
		RCC_RTCCLKCmd(ENABLE);//设置完RTC时钟以后,使能该时钟
		
		RTC_InitTypeDef RTC_InitStructure;
		
		RTC_InitStructure.RTC_AsynchPrediv=0x7F;//异步预分频系数 1~0x7F
		RTC_InitStructure.RTC_HourFormat=RTC_HourFormat_24; //24小时格式
		RTC_InitStructure.RTC_SynchPrediv=0xFF;//同步预分频系数 0~7FFF
		
		RTC_Init(&RTC_InitStructure);  //RTC_Init是库函数定义好的结构体,直接调用即可,也正因为如此,我们的初始化函数不可以设置为RTC_Init
		
		RTC_Set_Time(23,59,59,RTC_H12_AM);//根据上面自己设置的函数,设置时间,因为这个是第一次配置才会调用该程序,所以SetTime会供主函数GetTime调用
		RTC_Set_Date(23,1,21,6);  	
		
		RTC_WriteBackupRegister(RTC_BKP_DR0,0x5051);//程序能来到这里,表示上面的初始化已经完成了,并且是第一次配置的,这里将后备寄存器的状态位设置为0x5051
		//断电重启以后,不在重复执行该程序;0x5051对应if判断中设置的状态位;
		//在这里,如果想要每次重启以后都能执行上述时间,可以通过每次都设置新的状态位值来实现
	}
	return 0;//初始化成功
}

6. 实验程序

该实验的实验现象是在LCD屏上呈现日历;

仔细分析RTC框图,任何配置都是基于该概况图的;

6.1 日历配置

//时间设置函数
//hour,min,sec 小时 分钟 秒
//ampm:可供选择的范围为 RTC_H12_AM/RTC_H12_PM
//返回值:SUCCED(1)成功
//		  ERROR(0)失败
ErrorStatus RTC_Set_Time(u8 hour,u8 min,u8 sec,u8 ampm)
{
	RTC_TimeTypeDef RTC_TimeTypeInitStructure;
	
	RTC_TimeTypeInitStructure.RTC_H12=ampm;
	RTC_TimeTypeInitStructure.RTC_Hours=hour;
	RTC_TimeTypeInitStructure.RTC_Minutes=min;
	RTC_TimeTypeInitStructure.RTC_Seconds=sec;
	
	return RTC_SetTime(RTC_Format_BIN,&RTC_TimeTypeInitStructure);
	//参数设置相关时间,把建立的时间SetTime返回,供主函数GetTime获取
}

//日期设置函数
//year,month,date:年月日
//week:星期1~7有效 0非法
//返回值:SUCCED(1)成功
//		  ERROR(0)失败
ErrorStatus RTC_Set_Date(u8 year,u8 month,u8 date,u8 week)
{
	RTC_DateTypeDef RTC_DateTypeInitStructure;
	
	RTC_DateTypeInitStructure.RTC_Date=date;
	RTC_DateTypeInitStructure.RTC_Month=month;
	RTC_DateTypeInitStructure.RTC_WeekDay=week;
	RTC_DateTypeInitStructure.RTC_Year=year;
	
	return RTC_SetDate(RTC_Format_BIN,&RTC_DateTypeInitStructure);
}

6.2 闹钟配置

//设置闹钟时间
//week:星期1~7有效 
//hour,min,sec 小时 分钟秒
void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec)//STM32实时时钟具有2个可编程闹钟A和B,这里使用闹钟A,闹钟B只要设置相应寄存器的相关位即可
{
	RTC_AlarmCmd(RTC_Alarm_A,DISABLE);//关闭闹钟A
	
	RTC_TimeTypeDef RTC_TimeTypeInitStructure;
	RTC_TimeTypeInitStructure.RTC_H12=RTC_H12_AM;
	RTC_TimeTypeInitStructure.RTC_Hours=hour;
	RTC_TimeTypeInitStructure.RTC_Minutes=min;
	RTC_TimeTypeInitStructure.RTC_Seconds=sec;
	
	RTC_AlarmTypeDef RTC_AlarmTypeInitStructure;
	RTC_AlarmTypeInitStructure.RTC_AlarmDateWeekDay=week;//星期
	RTC_AlarmTypeInitStructure.RTC_AlarmDateWeekDaySel=RTC_AlarmDateWeekDaySel_WeekDay;//按星期闹
	RTC_AlarmTypeInitStructure.RTC_AlarmMask=RTC_AlarmMask_None;//精确匹配到星期 时分秒
	RTC_AlarmTypeInitStructure.RTC_AlarmTime=RTC_TimeTypeInitStructure;
	RTC_SetAlarm(RTC_Format_BIN,RTC_Alarm_A,&RTC_AlarmTypeInitStructure);//设置闹钟参数
	
	RTC_ClearITPendingBit(RTC_IT_ALRA);//清除RTC闹钟A的标志
	EXTI_ClearITPendingBit(EXTI_Line17);//清除LINE17上的中断标志位
	
	RTC_ITConfig(RTC_IT_ALRA,ENABLE);//开启闹钟A中断
	RTC_AlarmCmd(RTC_Alarm_A,ENABLE);//开启闹钟A
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line17;//LINE17
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);//配置中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=RTC_Alarm_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//子优先级
	NVIC_Init(&NVIC_InitStructure);//配置中断优先级
}

//RTC闹钟中断
void RTC_Alarm_IRQHandler(void)
{
	if(RTC_GetFlagStatus(RTC_FLAG_ALRAF)==SET)//闹钟A中断状态位是否为1
	{
		RTC_ClearFlag(RTC_FLAG_ALRAF);//清除中断标志
		printf("helloworld!\r\n"); //一旦中断来了,在串口上打印helloworld
	}
	EXTI_ClearITPendingBit(EXTI_Line17);//清除中断线17的中断标志
}

6.3 周期性自动唤醒配置

//周期性唤醒定时器设备
//psr:预分频值,库函数配置的预分频值如下
//arr:自动重装载值,计数器CNT值减到0产生中断
//#define RTC_WakeUpClock_RTCCLK_Div16        ((uint32_t)0x00000000)
//#define RTC_WakeUpClock_RTCCLK_Div8         ((uint32_t)0x00000001)
//#define RTC_WakeUpClock_RTCCLK_Div4         ((uint32_t)0x00000002)
//#define RTC_WakeUpClock_RTCCLK_Div2         ((uint32_t)0x00000003)
//#define RTC_WakeUpClock_CK_SPRE_16bits      ((uint32_t)0x00000004)
//#define RTC_WakeUpClock_CK_SPRE_17bits      ((uint32_t)0x00000006)
void RTC_Set_WakeUp(u32 psr,u16 arr)
{
	RTC_WakeUpCmd(DISABLE);//关闭唤醒设备WakeUp
	
	RTC_WakeUpClockConfig(psr);//配置时钟分频系数
	RTC_SetWakeUpCounter(arr);//设置Wake Up自动重装载寄存器
	
	RTC_ClearITPendingBit(RTC_IT_WUT);//清除RTC Wake Up中断标志位
	EXTI_ClearITPendingBit(EXTI_Line22);//清除LINE22上中断标志位
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line22;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);//配置相应的中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=RTC_WKUP_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;
	NVIC_Init(&NVIC_InitStructure);
}

//RTC WAKE UP中断服务函数
void RTC_WKUP_IRQHandler(void)
{
	if(RTC_GetFlagStatus(RTC_FLAG_WUTF)==SET)//判断WK_UP中断状态位是否为1
	{
		RTC_ClearFlag(RTC_FLAG_WUTF);//清空中断标志
		LED1=!LED1; //一旦进入中断,LED1闪烁
	}
	EXTI_ClearITPendingBit(EXTI_Line22);//清除中断线22的中断标志 	
}

6.4 实验完整程序

6.4.1 main.c

#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "RTC.h"
#include "usmart.h"

//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
	LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
	led_set(sta);
}
int main(void)
{
	RTC_TimeTypeDef RTC_TimeStructure;
	RTC_DateTypeDef RTC_DateStructure;
	
	u8 tbuf[40];//设置一个数组存储时间,日期,打印数组即可
	u8 t=0;//作为时间计数作用,类似于Count
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级
	uart_init(115200); //设置串口
	usmart_dev.init(84);
	LED_Init();
	delay_init(168);
	LCD_Init();
	My_RTC_Init();
	
	RTC_Set_WakeUp(RTC_WakeUpClock_CK_SPRE_16bits,0); //配置WAKE UP中断,1秒钟中断一次
	
	POINT_COLOR=RED;
	LCD_ShowString(30,50,200,16,16,"Hello");
	LCD_ShowString(30,70,200,16,16,"RTC TEST");
	LCD_ShowString(30,90,200,16,16,"Strive");
	LCD_ShowString(30,110,200,16,16,"2023/20/23");
	while(1)
	{
		t++;
		if((t%10)==0)//每100ms更新一次数据
		{
			RTC_GetTime(RTC_Format_BIN,&RTC_TimeStructure);//获取时间
			sprintf((char*)tbuf,"Time:%02d:%02d:%02d",RTC_TimeStructure.RTC_Hours,RTC_TimeStructure.RTC_Minutes,RTC_TimeStructure.RTC_Seconds);//打印时分秒
			LCD_ShowString(30,140,210,16,16,tbuf);//LCD显示出来,打印的结果
			
			RTC_GetDate(RTC_Format_BIN,&RTC_DateStructure);//获取日期
			sprintf((char*)tbuf,"Date:20%02d-%02d-%02d",RTC_DateStructure.RTC_Year,RTC_DateStructure.RTC_Month,RTC_DateStructure.RTC_Date);//打印年月日
			LCD_ShowString(30,160,210,16,16,tbuf);//LCD显示出来,打印的结果
			
			sprintf((char*)tbuf,"Week:%d",RTC_DateStructure.RTC_WeekDay);//打印日期
			LCD_ShowString(30,180,210,16,16,tbuf);//LCD显示出来,打印的结果
		}
		if((t%20)==0)//每200ms,LED0翻转一次
			LED0=!LED0;
		delay_ms(10);
	}
}


6.4.2 RTC.c

#include "stm32f4xx.h"                  
#include "RTC.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"

//时间设置函数
//hour,min,sec 小时 分钟 秒
//ampm:可供选择的范围为 RTC_H12_AM/RTC_H12_PM
//返回值:SUCCED(1)成功
//		  ERROR(0)失败
ErrorStatus RTC_Set_Time(u8 hour,u8 min,u8 sec,u8 ampm)
{
	RTC_TimeTypeDef RTC_TimeTypeInitStructure;
	
	RTC_TimeTypeInitStructure.RTC_H12=ampm;
	RTC_TimeTypeInitStructure.RTC_Hours=hour;
	RTC_TimeTypeInitStructure.RTC_Minutes=min;
	RTC_TimeTypeInitStructure.RTC_Seconds=sec;
	
	return RTC_SetTime(RTC_Format_BIN,&RTC_TimeTypeInitStructure);
	//参数设置相关时间,把建立的时间SetTime返回,供主函数GetTime获取
}

//日期设置函数
//year,month,date:年月日
//week:星期1~7有效 0非法
//返回值:SUCCED(1)成功
//		  ERROR(0)失败
ErrorStatus RTC_Set_Date(u8 year,u8 month,u8 date,u8 week)
{
	RTC_DateTypeDef RTC_DateTypeInitStructure;
	
	RTC_DateTypeInitStructure.RTC_Date=date;
	RTC_DateTypeInitStructure.RTC_Month=month;
	RTC_DateTypeInitStructure.RTC_WeekDay=week;
	RTC_DateTypeInitStructure.RTC_Year=year;
	
	return RTC_SetDate(RTC_Format_BIN,&RTC_DateTypeInitStructure);
}

//RTC初始化
//返回值:0初始化成功
//		  1 LSE开启失败
u8 My_RTC_Init(void)
{
	u16 retry=0x1FFF;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能PWR时钟
	PWR_BackupAccessCmd(ENABLE);//使能后备寄存器
	
	if(RTC_ReadBackupRegister(RTC_BKP_DR0)!=0x5050)//判断是否为第一次配置,这里的0x5051完全是自己设置的一个标志位
		//RTC_ReadBackupRegister叫做读取后备寄存器,RTC_BKP_DR0为寄存器的标志位,如果读取标志位不为我们设置的0x5051
	//则表示是第一次配置,执行下述程序;如果不是第一次配置,断电从启后会跳过该代码
	{
		RCC_LSEConfig(RCC_LSE_ON);//LSE开始,打开时钟
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)//while循环判断LSE开启的状态位是否置1
			//显然while循环离开的标志是获取的状态位RCC_FLAG_LSERDY等于1,离开也就代表这LSE开启成功
		{
			retry++;
			delay_ms(10);
		}
		if(retry==0)
			return 1;//LSE开启失败
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//既然程序没有返回1能来到这,一定表示LSE开启成功了,那么设置LSE作为RTC时钟
		RCC_RTCCLKCmd(ENABLE);//设置完RTC时钟以后,使能该时钟
		
		RTC_InitTypeDef RTC_InitStructure;
		
		RTC_InitStructure.RTC_AsynchPrediv=0x7F;//异步预分频系数 1~0x7F
		RTC_InitStructure.RTC_HourFormat=RTC_HourFormat_24; //24小时格式
		RTC_InitStructure.RTC_SynchPrediv=0xFF;//同步预分频系数 0~7FFF
		
		RTC_Init(&RTC_InitStructure);  //RTC_Init是库函数定义好的结构体,直接调用即可,也正因为如此,我们的初始化函数不可以设置为RTC_Init
		
		RTC_Set_Time(23,59,59,RTC_H12_AM);//根据上面自己设置的函数,设置时间,因为这个是第一次配置才会调用该程序,所以SetTime会供主函数GetTime调用
		RTC_Set_Date(23,1,21,7);  	
		
		RTC_WriteBackupRegister(RTC_BKP_DR0,0x5050);//程序能来到这里,表示上面的初始化已经完成了,并且是第一次配置的,这里将后备寄存器的状态位设置为0x5051
		//断电重启以后,不在重复执行该程序;0x5051对应if判断中设置的状态位;
		//在这里,如果想要每次重启以后都能执行上述时间,可以通过每次都设置新的状态位值来实现
	}
	return 0;//初始化成功
}

//设置闹钟时间
//week:星期1~7有效 
//hour,min,sec 小时 分钟秒
void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec)//STM32实时时钟具有2个可编程闹钟A和B,这里使用闹钟A,闹钟B只要设置相应寄存器的相关位即可
{
	RTC_AlarmCmd(RTC_Alarm_A,DISABLE);//关闭闹钟A
	
	RTC_TimeTypeDef RTC_TimeTypeInitStructure;
	RTC_TimeTypeInitStructure.RTC_H12=RTC_H12_AM;
	RTC_TimeTypeInitStructure.RTC_Hours=hour;
	RTC_TimeTypeInitStructure.RTC_Minutes=min;
	RTC_TimeTypeInitStructure.RTC_Seconds=sec;
	
	RTC_AlarmTypeDef RTC_AlarmTypeInitStructure;
	RTC_AlarmTypeInitStructure.RTC_AlarmDateWeekDay=week;//星期
	RTC_AlarmTypeInitStructure.RTC_AlarmDateWeekDaySel=RTC_AlarmDateWeekDaySel_WeekDay;//按星期闹
	RTC_AlarmTypeInitStructure.RTC_AlarmMask=RTC_AlarmMask_None;//精确匹配到星期 时分秒
	RTC_AlarmTypeInitStructure.RTC_AlarmTime=RTC_TimeTypeInitStructure;
	RTC_SetAlarm(RTC_Format_BIN,RTC_Alarm_A,&RTC_AlarmTypeInitStructure);//设置闹钟参数
	
	RTC_ClearITPendingBit(RTC_IT_ALRA);//清除RTC闹钟A的标志
	EXTI_ClearITPendingBit(EXTI_Line17);//清除LINE17上的中断标志位
	
	RTC_ITConfig(RTC_IT_ALRA,ENABLE);//开启闹钟A中断
	RTC_AlarmCmd(RTC_Alarm_A,ENABLE);//开启闹钟A
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line17;//LINE17
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);//配置中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=RTC_Alarm_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//子优先级
	NVIC_Init(&NVIC_InitStructure);//配置中断优先级
}

//周期性唤醒定时器设备
//psr:预分频值,库函数配置的预分频值如下
//arr:自动重装载值,计数器CNT值减到0产生中断
//#define RTC_WakeUpClock_RTCCLK_Div16        ((uint32_t)0x00000000)
//#define RTC_WakeUpClock_RTCCLK_Div8         ((uint32_t)0x00000001)
//#define RTC_WakeUpClock_RTCCLK_Div4         ((uint32_t)0x00000002)
//#define RTC_WakeUpClock_RTCCLK_Div2         ((uint32_t)0x00000003)
//#define RTC_WakeUpClock_CK_SPRE_16bits      ((uint32_t)0x00000004)
//#define RTC_WakeUpClock_CK_SPRE_17bits      ((uint32_t)0x00000006)
void RTC_Set_WakeUp(u32 psr,u16 arr)
{
	RTC_WakeUpCmd(DISABLE);//关闭唤醒设备WakeUp
	
	RTC_WakeUpClockConfig(psr);//配置时钟分频系数
	RTC_SetWakeUpCounter(arr);//设置Wake Up自动重装载寄存器
	
	RTC_ClearITPendingBit(RTC_IT_WUT);//清除RTC Wake Up中断标志位
	EXTI_ClearITPendingBit(EXTI_Line22);//清除LINE22上中断标志位
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line22;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);//配置相应的中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=RTC_WKUP_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;
	NVIC_Init(&NVIC_InitStructure);
}

//RTC闹钟中断
void RTC_Alarm_IRQHandler(void)
{
	if(RTC_GetFlagStatus(RTC_FLAG_ALRAF)==SET)//闹钟A中断状态位是否为1
	{
		RTC_ClearFlag(RTC_FLAG_ALRAF);//清除中断标志
		printf("helloworld!\r\n");
	}
	EXTI_ClearITPendingBit(EXTI_Line17);//清除中断线17的中断标志
}

//RTC WAKE UP中断服务函数
void RTC_WKUP_IRQHandler(void)
{
	if(RTC_GetFlagStatus(RTC_FLAG_WUTF)==SET)//判断WK_UP中断状态位是否为1
	{
		RTC_ClearFlag(RTC_FLAG_WUTF);//清空中断标志
		LED1=!LED1;
	}
	EXTI_ClearITPendingBit(EXTI_Line22);//清除中断线22的中断标志 	
}

6.4.3 RTC.h

#ifndef _RTC__H_
#define _RTC__H_


ErrorStatus RTC_Set_Time(u8 hour,u8 min,u8 sec,u8 ampm);
ErrorStatus RTC_Set_Date(u8 year,u8 month,u8 date,u8 week);
u8 My_RTC_Init(void);
void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec);
void RTC_Set_WakeUp(u32 psr,u16 arr);
void RTC_Alarm_IRQHandler(void);
void RTC_WKUP_IRQHandler(void);

#endif

6.4.4 总结

        1. RTC.c中外部中断的闹钟事件和唤醒事件之所以要使用外部中断线17和22,是因为STM32支持22个外部中断/事件;每个中断都有状态位;具体可以看:STM32F4_外部中断详解(EXTI)_light_2025的博客-CSDN博客

        STM32F4的每个IO口都可以作为外部中断的中断输入口,这点也是STM32F4的强大之处(相比于51只有5个中断,2个外部中断、2个定时器中断、1个串口中断)STM32F4的中断控制器支持22个外部中断/事件请求。每个中断都有状态位,每个中断/事件都有独立的触发和屏蔽设置。

STM32F4的22个外部中断为:

EXTI线0-15:对应外部IO口的输入中断。

EXTI线16:连接到PVD输出。

EXTI线17:连接到RTC闹钟事件。

EXTI线18:连接到USB OTG FS唤醒事件。

EXTI线19:连接到以太网唤醒事件。

EXTI线20:连接到USB OTG HS(在FS中配置)唤醒事件。

EXTI线21:连接到RTC入侵和时间戳事件。

EXTI线22:连接到RTC唤醒事件。

        2. 如果发现download上述程序之后,开发板上只是显示了Time、Date、Week,而没有像时钟一样变化;那么可能是RTC的驱动文件的晶振与开发板对应的晶振不一致导致的;我在实验的过程中也遇到类似的问题;可以通过改变驱动文件中对应的晶振去改变;

        3. 上述实验程序只是通过RTC实时时钟显示了日历,闹钟和唤醒事件基本一致,只需要结合外部中断线在上述程序中相应的做出修改即可;通过外部中断服务函数即可证实闹钟和唤醒事件是否发生,有兴趣者可自行实验,这里不再给出相应的程序!

        4. 关于上述程序,如果还有其他问题,欢迎留言!一起学习,共同进步!

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

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

相关文章

【LoadRunner】教你快速编写一个性能测试脚本 demo

目录 LoadRunner 工具介绍 UI性能测试步骤 编写性能测试脚本(VUG) 创建测试场景(Controller) 生成测试报告(Analysis) LoadRunner 工具介绍 我们使用以上三个工具针对我们的项目进行性能测试。 a&am…

3U VPX 总线架构+ 2片国防科大银河飞腾 FT-M6678 多核浮点运算 DSP 设计资料--VPX303

板卡概述 VPX303 是一款基于 3U VPX 总线架构的高性能信号处理板,板载 2 片国防科大银河飞腾 FT-M6678 多核浮点运算 DSP,可以实现各 种实时性要求较高的信号处理算法。 板卡每个 DSP 均支持 5 片 DDR3 SDRAM 实现数据缓存,两片 DSP 之…

( 位运算 ) 268. 丢失的数字 ——【Leetcode每日一题】

❓268. 丢失的数字 难度:简单 给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。 示例 1: 输入:nums [3,0,1] 输出:2 解释:n 3,因为有 3 个数…

正则表达式 - 匹配 Unicode 和其他字符

目录 一、匹配 Unicode 字符 1. 匹配 emoji 符号 (1)确定 emoji 符号的 Unicode 范围 (2)emoji 符号的存储 (3)正则表达式匹配 2. 匹配中文 (1)确定中文的 Unicode 范围 &am…

PostgreSQL-HA 高可用集群在 Rainbond 上的部署方案

PostgreSQL 是一种流行的开源关系型数据库管理系统。它提供了标准的SQL语言接口用于操作数据库。 repmgr 是一个用于 PostgreSQL 数据库复制管理的开源工具。它提供了自动化的复制管理,包括: 故障检测和自动故障切换:repmgr 可以检测到主服…

二叉搜索树中的插入操作

1题目 给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。 注意,可能存在多种有效的插入…

YOLOv3论文解读/总结

本章论文: YOLOv3论文(YOLOv3: An Incremental Improvement)(原文+解读/总结+翻译) 系列论文: YOLOv1论文解读/总结_yolo论文原文_耿鬼喝椰汁的博客-CSDN博客 YOLOv2论文解读/总…

try(){}用法try-with-resources、try-catch-finally

属于Java7的新特性。 经常会用try-catch来捕获有可能抛出异常的代码。如果其中还涉及到资源的使用的话,最后在finally块中显示的释放掉有可能被占用的资源。 但是如果资源类已经实现了AutoCloseable这个接口的话,可以在try()括号中可以写操作资源的语句(…

cocos 项目实践总结

文章目录 组件文档的理解:开发过程顺序问题:1、获取锚点坐标2、事件监听3、批量生成选词按钮4、Button可以自定义边界、边界颜色、及弧度一些问题记录1、button点击过后重新恢复页面渲染,button的状态偶发还是点击态而非正常态2、偶发事件绑定…

【自动驾驶基础入门】SLAM应该怎么学习?

1、SLAM是什么? SLAM是Simultaneous Localization and Mapping的缩写,即同时定位与地图构建。也称为自主机器人导航或者无人车等领域的基本任务之一。 简单来说,SLAM是指在未知环境中,通过移动机器人并利用其搭载的各种传感器数据…

Yolov5训练日记~如何用Yolov5训练识别自己想要的模型~

目录 一.数据集准备 二.标签设置 三.模型训练 四.模型测试 最近尝试了Yolov5训练识别人体,用的是自己尝试做的训练集。见识到Yolo的强大后,决定分享给大家。 一.数据集准备 数据集是从百度图片上下载的,我当然不可能一个一个下载&#xff…

还没用过这12款建筑设计软件?你OUT了

每个建筑设计软件都针对不同的需求。选择最好的一个取决于许多因素,例如成本、与其他程序的兼容性以及您愿意花在绘图过程上的时间。它还取决于您在设计过程中所处的位置——我们可能都开始在纸上画草图,然后转向建筑软件。我们甚至需要图形设计软件来说…

Haoop集群的搭建(小白教学)

搭建hadoop集群我们必须拥有自己的虚拟机,下列我会给大家奉上超详细的集群搭建以及我在搭建的时候碰到的问题以及对应解决办法,正所谓自己走过的错路是曲折的,也是防止大家做弯路,不仅浪费时间还心态爆炸,下面带走入ha…

蜘蛛池搭建需要多少域名?全面解析!

蜘蛛池是指为搜索引擎爬虫提供优质、可靠的页面,从而提高网站的收录和排名。在蜘蛛池搭建过程中,域名数量是一个非常重要的问题。那么,蜘蛛池搭建需要多少域名呢?本文将对这个问题进行全面解析。 首先,我们需要了解什么…

SQL教程

1、基础 演示数据库,下面是选自 “Websites” 表的数据: SELECT 语句用于从数据库中选取数据。 SELECT name,country FROM Websites;SELECT DISTINCT 语句用于返回唯一不同的值。 SELECT DISTINCT country FROM Websites;WHERE 子句用于提取那些满足…

二十三种设计模式第五篇--原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建…

Linux网络——Shell编程之循环

Linux网络——Shell编程之循环 一、循环1.循环与遍历2.循环的优势 二、for 循环语句1.for 语句的结构2.for语句应用示例 三、while 循环语句1.while 语句的结构2.while语句应用示例 四、until 循环五、跳出循环1.break2.continue3.exit 六、死循环 一、循环 定义:循…

2023年适合营销公司使用的十大「社交媒体管理」工具

在遍地都是数字营销公司的时代,对品牌来说,拥有强大的社交媒体影响力以保持竞争力从未如此重要。 而对于管理一个或多个品牌的数字营销公司来说,从内容创作到执行报告,使用正确的工具可以帮助你做到这一点。从规划、管理和跟踪社…

idb使用教程(一)

概述 iOS开发桥(idb)是一个多功能的工具,用于自动化iOS模拟器和设备。它在一个一致的、对人友好的界面中暴露了很多分布在苹果工具中的功能。 安装 idb由两个部分组成,每个部分都需要单独安装。 idb伴侣 每个目标&#xff08…

《花雕学AI》ChatMind:与AI对话,轻松梳理思路并创建思维导图

引言: 思维导图是一种有效的思维工具,可以帮助用户整理和表达自己的思路,提高学习和工作的效率和质量。然而,传统的思维导图工具往往需要用户花费大量的时间和精力,学习和操作复杂的界面和功能,而且很难根据…