发新帖我要提问
12
返回列表
打印
[应用相关]

STM32 RTC 串口打印

[复制链接]
楼主: gaoke231
手机看帖
扫描二维码
随时随地手机跟帖
21
gaoke231|  楼主 | 2018-8-31 22:31 | 只看该作者 回帖奖励 |倒序浏览
main函数:
struct rtc_time systmtime;
int main(void)
{
/串口配置/
USART1_Config();
/配置RTC秒中断优先级/
NVIC_Configuration();
//RTC检测及配置
RTC_CheckAndConfig(&systmtime);
//刷新时间
Time_Show(&systmtime);
}

使用特权

评论回复
22
gaoke231|  楼主 | 2018-8-31 22:31 | 只看该作者
main函数流程:
1,用到了串口,配置好串口(代码和之前的例程一样);
2,配置RTC秒中断优先级,这里设置主优先级为1,次优先级为0(只用到一个RTC,中断随便写都可以).(代码和之前的中断例程相似,只不过中断通道不一样,这里使用的中断通道是RTC_IRQn);
3,查看RTC外设是否在本次VDD上电前被配置过,如果没有被配置过,则需要输入当前时间,重新初始化RTC和配置时间;
4,配置好RTC后,根据秒中断设置的标志位,每隔1秒向终端更新一次;

使用特权

评论回复
23
gaoke231|  楼主 | 2018-8-31 22:32 | 只看该作者
事件管理结构体 rtc_time
struct rtc_time
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
}
这个类型的结构体有时,分,秒,日,月,年及星期7个成员.当需要给RTC的计时器重新配置时间时(更改时间戳),肯定不会询问用户现在距离UNIX计时元年过了多少秒,而是向用户询问现在的公元纪年,以及所在时区的事件.根据RTC计时器向用户输出时间.
这就是 rtc_time 这个结构体的作用,配置RTC时,保存用户输入的时间,其它函数通过它求出UNIX时间戳,写入RTC,RTC正常运行后,需要输出时间时,其它函数通过RTC获取UNIX时间戳,转化成用友好的时间表示方式保存在这个结构体上.

使用特权

评论回复
24
gaoke231|  楼主 | 2018-8-31 22:32 | 只看该作者
检查RTC RTC_CheckAndConfig()

void RTC_CheckAndConfig(struct rtc_time *tm)
{
/检查备份寄存器BKP_DR1,内容不为0xA5A5,则需要重新配置时间并且询问用户调整时间/
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
printf(“\r\n\r\n RTC not yet configured….”);
/* RTC 配置 */
RTC_Configuration();
printf(“\r\n\r\n RTC configured….”);
/* 用户输入时间*/
Time_Adjust(tm);
/再往备份寄存器BKP_DR1写入0xA5A5/
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
/启动无需设置新时钟/
else
{
/检查是否掉电重启/
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{
printf(“\r\n\r\n Power On Reset occurred….”);
}
/检查是否Reset复位/
else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{
printf(“\r\n\r\n External Reset occurred….”);
}
printf(“\r\n No need to configure RTC….”);
/等待寄存器同步/
RTC_WaitForSynchro();
/允许RTC秒中断/
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/等待上次RTC寄存器写操作完成/
RTC_WaitForLastTask();
}
/定义了时钟输出宏,则配置校正时钟输出到 PC13,用于RTC时钟频率的校准或调整时间补偿/
#ifdef RTCClockOutput_Enable
/使能PWR和BKP的时钟/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/允许访问BKP备份域/
PWR_BackupAccessCmd(ENABLE);
/输出64分频时钟/
BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
#endif
RCC_ClearFlag();
}
if语句调用BKP_ReadBackupRegister()读取RTC备份域寄存器里面的值,判断备份寄存器里面的是否正确,根据后面代码,如果配置成功,会向备份域寄存器写入数值0xA5A5.
(这个数值在VDD掉电后仍然会保存,如果VBAT也掉电,那么备份域,RTC所有寄存器将被复位,这时这个寄存器的值就不会等于0xA5A5了,RTC的计数器的值也是无效的.
简单的说,就是写入的这个数值用作标志RTC是否从未被配置或配置是否已经失效,然后写入任何数值到任何一个备份域寄存器,只要检查的时候与写入值匹配就行了)

使用特权

评论回复
25
gaoke231|  楼主 | 2018-8-31 22:36 | 只看该作者
RTC未被配置或者配置已经失效的情况:
1,如果RTC从未被配置或者配置已经失效(备份域寄存器写入值等于0xA5A5)这两种情况其中一种为真的话,则调用RTC_Configuration()来初始化RTC,配置RTC外设的控制参数,时钟分频等,并往电脑的超级终端打印出相应的调试信息;
2,初始化好RTC之后,调用函数 Time_Adjust() 让用户键入(通过超级终端输入)时间值;
3,输入时间值后,Time_Adjust() 函数把用户输入的北京时间转化为UNIX时间戳,并把这个UNIX时间戳写入到RTC外设的计数寄存器RTC_CNT.接着RTC外设在这个时间戳的基础上,每秒对RTC_CNT加1,RTC时钟就运行起来了,并且在VDD掉电还运行,以后需要知道时间就直接读取RTC的计时值,就可以计算出时间了;
4,设置好时间后,调用BKP_WriteBackupRegister()把0xA5A5这个值写入备份域寄存器,作为配置成功的标志;

使用特权

评论回复
26
gaoke231|  楼主 | 2018-8-31 22:37 | 只看该作者
确认RTC曾经被配置过的情况:
1,调用RCC_GetFlagStatus检测是上电复位还是按键复位,根据不同的复位情况在超级终端中打印出不同的调试信息(两种复位都不需要重新设置RTC里面的时间值);
2,调用RTC_WaitForSynchro等待APB1接口与RTC外设同步,上电后第一次通过APB1接口访问RTC时必须要等待同步;
3,同步完成后调用RTC_ITConfig()使能RTC外设的秒中断(使能RTC的秒中断是一个对RTC外设寄存器的写操作);
4,进行写操作以后,必须调用RTC_WaitForLastTask()来等待,确保写操作完成;

使用特权

评论回复
27
gaoke231|  楼主 | 2018-8-31 22:38 | 只看该作者
初始化RTC RTC_Configuration():
void RTC_Configuration(void)
{
/使能PWR和BKP时钟/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/对备份域进行软件复位/
PWR_BackupAccessCmd(ENABLE);
/对备份域进行软件复位/
BKP_DeInit();
/* 使能低速外部时钟 LSE */
RCC_LSEConfig(RCC_LSE_ON);
/* 等待LSE起振稳定 */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{}
/* 选择LSE作为 RTC 外设的时钟*/
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
/* 使能RTC时钟 */
RCC_RTCCLKCmd(ENABLE);
/* 等待RTC寄存器与APB1同步*/
RTC_WaitForSynchro();
/* 等待对RTC的写操作完成*/
RTC_WaitForLastTask();
/* 使能RTC秒中断 */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* 等待对RTC的写操作完成 */
RTC_WaitForLastTask();
/* 设置RT 时钟分频: 使RTC定时周期为1秒 */
RTC_SetPrescaler(32767);
/* RTC 周期 = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */
/等待对RTC的写操作完成 /
RTC_WaitForLastTask();
}

使用特权

评论回复
28
gaoke231|  楼主 | 2018-8-31 22:39 | 只看该作者
时间调节Time_Adjust():
void Time_Adjust(struct rtc_time *tm)
{
/* 等待前面可能的 RTC 写操作完成 */
RTC_WaitForLastTask();
/* 利用串口,在终端向用户询问当前北京时间(年月日时分秒),
写入到 rtc_time 型结构体 */
Time_Regulate(tm);
/* 计算输入的日期是星期几,把rtc_time型结构体填充完整 */
GregorianDay(tm);
/* 根据输入日期,计算出 UNIX 时间戳,修改当前 RTC 计数寄存器内容*/
RTC_SetCounter(mktimev(tm));
/* 等待 RTC 写操作完成 */
RTC_WaitForLastTask();
}

使用特权

评论回复
29
gaoke231|  楼主 | 2018-8-31 22:40 | 只看该作者
获取时间Time_Regulate():
void Time_Reglate(struct rtc_time *tm)
{
u32 Tmp_YY = 0xFF, Tmp_MM = 0xFF, Tmp_DD = 0xFF, Tmp_HH =0xFF, Tmp_MI = 0xFF, Tmp_SS = 0xFF;
}


printf("\r\n==========Time Settings==================");

printf("\r\n 请输入年份(Please Set Years): 20");
while (Tmp_YY == 0xFF)
{
Tmp_YY = USART_Scanf(99);
}
printf("\n\r 年份被设置为: 20%0.2d\n\r", Tmp_YY);
tm->tm_year = Tmp_YY+2000;

Tmp_MM = 0xFF;
printf("\r\n 请输入月份(Please Set Months): ");
while (Tmp_MM == 0xFF)
{
Tmp_MM = USART_Scanf(12);
}
printf("\n\r 月份被设置为: %d\n\r", Tmp_MM);
tm->tm_mon= Tmp_MM;

Tmp_DD = 0xFF;
printf("\r\n 请输入日期(Please Set Dates): ");
while (Tmp_DD == 0xFF)
{
Tmp_DD = USART_Scanf(31);
}
printf("\n\r 日期被设置为: %d\n\r", Tmp_DD);
tm->tm_mday= Tmp_DD;

Tmp_HH = 0xFF;
printf("\r\n 请输入时钟(Please Set Hours): ");
while (Tmp_HH == 0xFF)
{
Tmp_HH = USART_Scanf(23);
}
printf("\n\r 时钟被设置为: %d\n\r", Tmp_HH );
tm->tm_hour= Tmp_HH;

Tmp_MI = 0xFF;
printf("\r\n 请输入分钟(Please Set Minutes): ");
while (Tmp_MI == 0xFF)
{
Tmp_MI = USART_Scanf(59);
}
printf("\n\r 分钟被设置为: %d\n\r", Tmp_MI);
tm->tm_min= Tmp_MI;

Tmp_SS = 0xFF;
printf("\r\n 请输入秒钟(Please Set Seconds): ");
while (Tmp_SS == 0xFF)
{
Tmp_SS = USART_Scanf(59);
}
printf("\n\r 秒钟被设置为: %d\n\r", Tmp_SS);
tm->tm_sec= Tmp_SS;


使用特权

评论回复
30
gaoke231|  楼主 | 2018-8-31 22:43 | 只看该作者
补上USART_Scanf()的代码,之前串口篇的时候好像没有附上
static uint8_t USART_Scanf(uint32_t value)
{
uint32_t index = 0;
uint32_t tmp[2] = {0, 0};
while (index < 2)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) ==RESET)
{}
tmp[index++] = (USART_ReceiveData(USART1));
    /*数字0到9的ASCII码为0x30至0x39*/
    if((tmp[index - 1] < 0x30) || (tmp[index -1] > 0x39))
    {
        printf("\n\rPlease enter valid number between 0 and 9 -->: ")
        index--;
    }
}
/* 计算输入字符的 ASCII 码转换为数字*/
index = (tmp[1] - 0x30) + ((tmp[0] - 0x30) * 10);

if (index > value)
{
    printf("\n\rPlease enter valid number between 0 and %d", value);
    return 0xFF;
}
return index;


使用特权

评论回复
31
gaoke231|  楼主 | 2018-8-31 22:46 | 只看该作者
计算UNIX时间戳mktimev():
从用户端获取了北京时间后,就可以用它换成 UNIX 时间戳了,但不能忽略一个重要的问题——时差.UNIX时间戳的计时元年是以标准时间(GMT 时区)为准的,而北京时间为 GMT+8,即时差为+8小时.为了保证我们写入到RTC_CNT的是标准的UNIX时间戳(主要是为了兼容),以北京时间转化出的秒数要减去8*60*60才是标准的UNIX时间戳.
u32 mktimev(struct rtc_time *tm)
{
if (0 >= (int) (tm->tm_mon -= 2))
{
tm->tm_mon += 12;
tm->tm_year -= 1;
}

使用特权

评论回复
32
gaoke231|  楼主 | 2018-8-31 22:46 | 只看该作者
/计算出输入的北京时间的一共的秒数/
return((( (u32)(tm->tm_year/4 - tm->tm_year/100 + tm->tm_year/400 + 367*tm->tm_mon/12 + tm->tm_mday)
+ tm->tm_year*365 - 719499)*24 + tm->tm_hour)*60 + tm->tm_min)*60 + tm->tm_sec-8*60*60;
/8*60*60把输入的北京时间转换为标准时间在写入计时器中,确保计时器的数据为标准UNIX时间戳/
}
8*60*60把输入的北京事件转换为标准事件在写入计时器中,确保计时器的数据为标准UNIX时间戳,如果向使用其他时区,则根据不同哟的时区修改这个值.
返回值最终被写入到RTC_CNT计数器中RTC_SetCounter(mktimev(tm));

使用特权

评论回复
33
gaoke231|  楼主 | 2018-8-31 22:47 | 只看该作者
输出时间到终端Time_Show():
void Time_Show(struct rtc_time *tm)
{
while (1)
{
/每个1s/
if(TimeDisplay == 1)
{
/显示时间/
Time_Display(RTC_GetCounter(),tm);
TimeDisplay = 0;
}
}
}
TimeDisplay是RTC秒中断标志,RTC的秒中断被触发后,进入中断服务函数,把这个变量 TimeDisplay置1.这个函数是死循环检查这个标志,变为1时,调用Time_Display()显示最新时间,实现每隔1秒向终端更新一次时间,更新完后再把 TimeDisplay置0,等待下次秒中断.

使用特权

评论回复
34
gaoke231|  楼主 | 2018-8-31 22:48 | 只看该作者
RTC秒中断服务函数:
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
/* 清除秒中断标志 */
RTC_ClearITPendingBit(RTC_IT_SEC);
/* 把标志位置 1 */
TimeDisplay = 1;
/* 等待写操作完成 */
RTC_WaitForLastTask();
}
}

使用特权

评论回复
35
gaoke231|  楼主 | 2018-8-31 22:50 | 只看该作者
显示时间Time_Display():
void Time_Display(uint32_t TimeVar,struct rct_time *tm)
{
static uint32_t FirstDisplay = 1;
uint32_t BJ_TimeVar;
uint8_t str[15]; // 字符串暂存
/* 把标准时间转换为北京时间*/
BJ_TimeVar =TimeVar + 8*60*60;
/*利用时间戳转换为北京时间*/
to_tm(BJ_TimeVar, tm);

if((!tm->tm_hour && !tm->tm_min && !tm->tm_sec) || (FirstDisplay))
{
    GetChinaCalendar((u16)tm->tm_year, (u8)tm->tm_mon, (u8)tm->tm_mday, str);

    printf("\r\n\r\n 今天农历:%0.2d%0.2d,%0.2d,%0.2d", str[0], str[1], str[2], str[3]);

    GetChinaCalendarStr((u16)tm->tm_year,(u8)tm->tm_mon,(u8)tm->tm_mday,str);
    printf(" %s", str);

    if(GetJieQiStr((u16)tm->tm_year, (u8)tm->tm_mon, (u8)tm->tm_mday, str))
    {
        printf(" %s\n\r", str);
    }
    FirstDisplay = 0;
}
printf("\r UNIX 时间戳 = %d ,当前时间为: %d 年(%s 年) %d 月 %d日 (星期%s) %0.2d:%0.2d:%0.2d",TimeVar,tm->tm_year, zodiac_sign[(tm->tm_year-3)%12], tm->tm_mon, tm->tm_mday,WEEK_STR[tm->tm_wday], tm->tm_hour,tm->tm_min, tm->tm_sec);

}


使用特权

评论回复
36
gaoke231|  楼主 | 2018-8-31 22:51 | 只看该作者
这里的第一个输入参数为UNIX时间戳,在Time_Show()调用的时候,利用库函数RTC_GetCounter()读取了RTC_CNT的当前数值,并把这个计数值作为Time_Dispaly()的输入参数.
根据配置,RTC_CNT的计数值是标准时间GMT的UNIX时间戳,为了计算北京时间,在使用RTC_CNT计数值转换北京时间时,要加上时差(BJ_TimeVar =TimeVar + 8*60*60;).之后,把这个变量 BJ_TimeVar作为函数 to_tm()的输入参数,把时间戳转换成年,月,日,时,分,秒的格式,并保存到时间结构体中.

使用特权

评论回复
37
磨砂| | 2018-9-1 15:54 | 只看该作者
gaoke231 发表于 2018-8-31 22:15
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准

感谢楼主分享

使用特权

评论回复
38
guanjiaer| | 2018-9-3 10:40 | 只看该作者
非常感谢楼主分享

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则