1. 前言
上次实验实现了两个任务的切换。这次实现RTC时钟显示,并使用触摸按键+实体按键进行时钟的修改功能。
2. 硬件部分
RTC 时钟模块是一个时间外设,主要用于日期时间的存储和控制,有别于一般 MCU中的 Timer,RTC 时钟有两种计时模式,日期模式和计时模式,RTC 常见的操作包括设置时间、设置定时闹铃、配置周期性中断以及启动或停止操作。
3. 软件部分
将之前的实验05复制一份出来,重命名为06_RTC_and_Button_Set_Time。
3.1 配置RTC
在Stack页面,新建一个Stack为r_rtc。

修改时钟源和中断相关内容。

3.2 新建RTOS任务、二值信号量、队列
新建一个Thread,symbol是RTC_Task,Name是RTC,优先级2,Stack Size为512,内存分配为Dynamic.
新建一个二值信号量,symbol是semphr_rtc_1s,内存分配为Dynamic。
再增加一个二值信号量,symbol是semphr_Key2_Pressed,内存分配为Dynamic。
修改之前的queue_numData队列的symbol,改为queue_buttonState,大小为4,长度为1
然后生成代码。
3.3 新建rtc驱动
在src文件夹下新建文件夹命名为rtc
3.3.1 新建rtc.c
#include "rtc.h"
#include "FreeRTOS.h"
rtc_time_t init_time =
{ .tm_sec = 59,
.tm_min = 59,
.tm_hour = 23,
.tm_mday = 17,
.tm_mon = 6,
.tm_year = 125,
};
uint8_t month[13] = {0, 31,28,31,30,31,30,31,31,30,31,30,31};
bool inClockSettingMode = false;
void RTC_Init(void)
{
R_RTC_Open (&g_rtc0_ctrl, &g_rtc0_cfg);
RTC_setTime(&init_time);
R_RTC_PeriodicIrqRateSet(&g_rtc0_ctrl, RTC_PERIODIC_IRQ_SELECT_1_SECOND);
}
void RTC_setTime(rtc_time_t* time){
time->tm_mon-=1;
R_RTC_CalendarTimeSet(&g_rtc0_ctrl, time);
}
void RTC_getTime(rtc_time_t* time){
R_RTC_CalendarTimeGet(&g_rtc0_ctrl, time);
time->tm_mon+=1;
}
void rtc_callback(rtc_callback_args_t *p_args)
{
if (p_args->event == RTC_EVENT_PERIODIC_IRQ)
xSemaphoreGiveFromISR(semphr_rtc_1s, NULL);
}
3.3.2 新建rtc.h
#ifndef RTC_RTC_H_
#define RTC_RTC_H_
#include "hal_data.h"
void RTC_Init(void);
void RTC_setTime(rtc_time_t* time);
void RTC_getTime(rtc_time_t* time);
extern bool inClockSettingMode;
typedef enum {
CLOCK_SET_YEAR = 0,
CLOCK_SET_MONTH,
CLOCK_SET_DAY,
CLOCK_SET_HOUR,
CLOCK_SET_MIN,
CLOCK_SET_SEC,
CLOCK_SET_SAVE_EXIT,
CLOCK_SET_MAX
} clock_set_item_t;
extern uint8_t month[13];
#endif
3.4 修改touch_btn.h
增减一个枚举类型的变量,用于检测是触摸按键0还是触摸按键1按下了。
typedef enum {
TOUCH_BTN_NONE = 0,
TOUCH_BTN0_SHORT_PRESS,
TOUCH_BTN1_SHORT_PRESS,
} touchBtn_t;
3.5 修改lcd.c和lcd.h
lcd.c新增两个函数
void LCD_ClearOne(uint8_t position)
{
for (int i = 0; i < 7; i++)
{
uint8_t seg_index = digit_seg_map[position][i];
R_SLCDC_Modify (&g_slcdc0_ctrl, seg_index, 0x0, 0xF);
}
}
void LCD_ShowNumber_Position(uint32_t number, uint8_t position, uint8_t d)
{
for (char i = position + d - 1; i >= position; i--)
{
uint8_t t = number % 10;
LCD_ShowDigit ((uint8_t) i, t);
number /= 10;
}
}
lcd.h新增它们的声明,如下:
void LCD_ClearOne(uint8_t position);
void LCD_ShowNumber_Position(uint32_t number, uint8_t position, uint8_t d);
3.6 修改 Key_Switch_Task_entry.c
新增一个在修改设置时,禁用KEY1,在按下KEY2时,发送信号量。
void Key_Switch_Task_entry(void *pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
static bool adc_task_active = true;
static bool rtc_task_active = false;
uint8_t key1_state, key2_state;
vTaskSuspend (Touch_Button_Task);
xSemaphoreGive(semphr_taskChanged);
while (1)
{
if(inClockSettingMode == false){
R_IOPORT_PinRead (&g_ioport_ctrl, KEY1, &key1_state);
if (BSP_IO_LEVEL_LOW == key1_state)
{
vTaskDelay (5);
do
{
R_IOPORT_PinRead (&g_ioport_ctrl, KEY1, &key1_state);
vTaskDelay (10);
}
while (BSP_IO_LEVEL_LOW == key1_state);
if (adc_task_active)
{
vTaskSuspend (ADC_Task);
vTaskResume (Touch_Button_Task);
adc_task_active = false;
rtc_task_active = true;
}
else
{
vTaskSuspend (Touch_Button_Task);
vTaskResume (ADC_Task);
adc_task_active = true;
rtc_task_active = false;
}
xSemaphoreGive(semphr_taskChanged);
}
}
if (rtc_task_active == true)
{
R_IOPORT_PinRead (&g_ioport_ctrl, KEY2, &key2_state);
if (BSP_IO_LEVEL_LOW == key2_state)
{
vTaskDelay (5);
do
{
R_IOPORT_PinRead (&g_ioport_ctrl, KEY2, &key2_state);
vTaskDelay (10);
}
while (BSP_IO_LEVEL_LOW == key2_state);
xSemaphoreGive(semphr_Key2_Pressed);
}
}
vTaskDelay (20);
}
}
3.7 修改RTC_Task_entry.c
只是在此处做RTC的初始化工作。
#include "RTC_Task.h"
#include "rtc/rtc.h"
void RTC_Task_entry(void *pvParameters)
{
FSP_PARAMETER_NOT_USED (pvParameters);
RTC_Init();
while (1)
{
vTaskDelay (1);
}
}
3.8 修改Touch_Button_Task_entry.c
这里相对于之前的,删除了长按相关的处理,使用队列发送出去是哪个触摸按键短按。
#include "Touch_Button_Task.h"
#include "touch_btn/touch_btn.h"
void Touch_Button_Task_entry(void *pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
touchBtn_t btnState = 0;
TouchBtn_Init ();
while (1)
{
TouchBtn_Process ();
touchBtn_event_t e0 = TouchBtn_GetEvent (0);
touchBtn_event_t e1 = TouchBtn_GetEvent (1);
if (e0 == TOUCH_SHORT_PRESS)
{
btnState = TOUCH_BTN0_SHORT_PRESS;
xQueueSend(queue_buttonState, &btnState, 0);
}
if (e1 == TOUCH_SHORT_PRESS)
{
btnState = TOUCH_BTN1_SHORT_PRESS;
xQueueSend(queue_buttonState, &btnState, 0);
}
vTaskDelay (1);
}
}
3.9 修改LCD_Display_Task_entry.c
这里面功能相似的代码行数比较多,后期可能会做调整优化。相比上次实验,新增了进入时钟设置的功能,实现起来还是有点复杂的。
实现的功能有:在修改某一位时间/日期时将其闪烁显示;自动日期纠错;自动识别闰年;年月日显示4秒、时分秒显示7秒;小数点和冒号闪烁作为时间的分隔符。
#include "LCD_Display_Task.h"
#include "lcd/lcd.h"
#include "rtc/rtc.h"
#include "touch_btn/touch_btn.h"
void LCD_Display_Task_entry(void *pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
bool dot_flag = true, colon_flag = false;
bool adc_task_active = true;
double adcValue = 0.0;
uint8_t rtc_cnt = 0;
uint8_t key2_cnt = 0;
rtc_time_t time, temp_time;
touchBtn_t touchBtnState;
uint8_t blink_timer = 0;
bool blink_flag = false;
LCD_Init ();
if (xSemaphoreTake(semphr_taskChanged, 10000) == pdTRUE)
{
LCD_Clear ();
}
while (1)
{
if (xSemaphoreTake(semphr_taskChanged, 0) == pdTRUE)
{
adc_task_active = !adc_task_active;
if (adc_task_active == true)
{
dot_flag = true;
colon_flag = false;
}
else
{
dot_flag = true;
colon_flag = true;
}
LCD_Clear ();
}
if (xSemaphoreTake(semphr_Key2_Pressed, 0) && key2_cnt == 0)
{
key2_cnt = 1;
temp_time = time;
inClockSettingMode = true;
int sec = temp_time.tm_sec;
blink_timer = 0;
blink_flag = false;
while (key2_cnt != 7)
{
xQueueReceive (queue_buttonState, &touchBtnState, 0);
if (xSemaphoreTake(semphr_Key2_Pressed, 0))
{
key2_cnt++;
blink_flag = false;
blink_timer = 0;
uint8_t pos;
if (key2_cnt <= 3)
{
pos = (key2_cnt - 1) * 2;
switch (key2_cnt)
{
case 1:
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_year % 100, pos, 2);
break;
case 2:
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_mon, pos, 2);
break;
case 3:
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_mday, pos, 2);
break;
}
}
else if (key2_cnt <= 6)
{
pos = (key2_cnt - 4) * 2;
switch (key2_cnt)
{
case 4:
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_hour, pos, 2);
break;
case 5:
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_min, pos, 2);
break;
case 6:
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_sec, pos, 2);
break;
}
}
}
if (key2_cnt <= 3)
{
int month_max_days = 0;
switch (key2_cnt)
{
case 1:
if (touchBtnState == TOUCH_BTN0_SHORT_PRESS)
{
if (temp_time.tm_year > 100)
{
temp_time.tm_year--;
}
else
{
temp_time.tm_year = 199;
}
}
else if (touchBtnState == TOUCH_BTN1_SHORT_PRESS)
{
if (temp_time.tm_year < 199)
{
temp_time.tm_year++;
}
else
{
temp_time.tm_year = 100;
}
}
touchBtnState = TOUCH_BTN_NONE;
break;
case 2:
if (temp_time.tm_year % 4 == 0 && temp_time.tm_mon == 2)
{
month_max_days = month[2] + 1;
}
else
{
month_max_days = month[temp_time.tm_mon];
}
if (temp_time.tm_mday > month_max_days)
{
temp_time.tm_mday = month_max_days;
}
if (touchBtnState == TOUCH_BTN0_SHORT_PRESS)
{
if (temp_time.tm_mon > 1)
{
temp_time.tm_mon--;
}
else
{
temp_time.tm_mon = 12;
}
}
else if (touchBtnState == TOUCH_BTN1_SHORT_PRESS)
{
if (temp_time.tm_mon < 12)
{
temp_time.tm_mon++;
}
else
{
temp_time.tm_mon = 1;
}
}
touchBtnState = TOUCH_BTN_NONE;
break;
case 3:
if (touchBtnState == TOUCH_BTN0_SHORT_PRESS)
{
if (temp_time.tm_mday > 1)
{
temp_time.tm_mday--;
}
else if (temp_time.tm_year % 4 == 0 && temp_time.tm_mon == 2)
{
temp_time.tm_mday = month[2] + 1;
}
else
{
temp_time.tm_mday = month[temp_time.tm_mon];
}
}
else if (touchBtnState == TOUCH_BTN1_SHORT_PRESS)
{
if (temp_time.tm_year % 4 == 0 && temp_time.tm_mon == 2)
{
month_max_days = month[2] + 1;
}
else
{
month_max_days = month[temp_time.tm_mon];
}
if (temp_time.tm_mday < month_max_days)
{
temp_time.tm_mday++;
}
else
{
temp_time.tm_mday = 1;
}
}
touchBtnState = TOUCH_BTN_NONE;
break;
}
}
else if (key2_cnt <= 6)
{
switch (key2_cnt)
{
case 4:
if (touchBtnState == TOUCH_BTN0_SHORT_PRESS)
{
if (temp_time.tm_hour > 0)
{
temp_time.tm_hour--;
}
else
{
temp_time.tm_hour = 23;
}
}
else if (touchBtnState == TOUCH_BTN1_SHORT_PRESS)
{
if (temp_time.tm_hour < 23)
{
temp_time.tm_hour++;
}
else
{
temp_time.tm_hour = 0;
}
}
touchBtnState = TOUCH_BTN_NONE;
break;
case 5:
if (touchBtnState == TOUCH_BTN0_SHORT_PRESS)
{
if (temp_time.tm_min > 0)
{
temp_time.tm_min--;
}
else
{
temp_time.tm_min = 59;
}
}
else if (touchBtnState == TOUCH_BTN1_SHORT_PRESS)
{
if (temp_time.tm_min < 59)
{
temp_time.tm_min++;
}
else
{
temp_time.tm_min = 0;
}
}
touchBtnState = TOUCH_BTN_NONE;
break;
case 6:
if (touchBtnState == TOUCH_BTN0_SHORT_PRESS)
{
if (sec > 0)
{
sec--;
}
else
{
sec = 59;
}
}
else if (touchBtnState == TOUCH_BTN1_SHORT_PRESS)
{
if (sec < 59)
{
sec++;
}
else
{
sec = 0;
}
}
temp_time.tm_sec = sec;
touchBtnState = TOUCH_BTN_NONE;
break;
}
}
blink_timer++;
if (blink_timer >= 40)
{
blink_flag = !blink_flag;
blink_timer = 0;
}
uint8_t pos = 0;
if (key2_cnt <= 3)
{
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_year % 100, 0, 2);
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_mon, 2, 2);
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_mday, 4, 2);
pos = (key2_cnt - 1) * 2;
}
else if (key2_cnt <= 6)
{
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_hour, 0, 2);
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_min, 2, 2);
LCD_ShowNumber_Position ((uint32_t) temp_time.tm_sec, 4, 2);
pos = (key2_cnt - 4) * 2;
}
if (key2_cnt >= 1 && key2_cnt <= 6 && blink_flag)
{
LCD_ClearOne (pos);
LCD_ClearOne (pos + 1);
}
if (key2_cnt == 7)
{
RTC_setTime (&temp_time);
key2_cnt = 0;
inClockSettingMode = false;
break;
}
vTaskDelay (10);
}
}
if (adc_task_active == false && xSemaphoreTake(semphr_rtc_1s, 0))
{
RTC_getTime (&time);
if (rtc_cnt > 10)
{
rtc_cnt = 0;
}
if (rtc_cnt <= 3)
{
LCD_ShowNumber_Position ((uint32_t) time.tm_year % 100, 0, 2);
LCD_ShowNumber_Position ((uint32_t) time.tm_mon, 2, 2);
LCD_ShowNumber_Position ((uint32_t) time.tm_mday, 4, 2);
LCD_setDot (true);
LCD_setColon (true);
}
else
{
LCD_ShowNumber_Position ((uint32_t) time.tm_hour, 0, 2);
LCD_ShowNumber_Position ((uint32_t) time.tm_min, 2, 2);
LCD_ShowNumber_Position ((uint32_t) time.tm_sec, 4, 2);
LCD_setDot (dot_flag);
LCD_setColon (colon_flag);
colon_flag = !colon_flag;
dot_flag = !dot_flag;
}
rtc_cnt++;
}
if (xQueueReceive (queue_adcValue, &adcValue, 0) == pdTRUE && adc_task_active == true)
{
LCD_setDot (dot_flag);
LCD_ShowNumber ((uint32_t) (adcValue * 100), 3);
}
vTaskDelay (10);
}
}
4. 下载测试
下载调试,先显示的是ADC采集电压界面,点击KEY1,切换到时钟界面。此时点击KEY2,时间暂停,进入日期设置界面。
在日期设置界面,触摸两个触摸按键,来增减数值,当设置完某一位日期后,再次点击KEY2切换到下一个日期设置。在设置完"秒数"之后,点击KEY2,退出设置并保存。时钟继续走时。演示视频见文章顶部。