当前位置: 首页 > news >正文

32HAL——万年历

一、需求分析

主控:STM32F103VET6

按键 * 1 -> 切换显示模式与设置模式

旋转编码器* 1 -> 设置时间

0.96OLED * 1 -> 显示

二、硬件分析

三、代码详解

1. 初始化函数

// 初始化函数
void MainTask_Init(void) {HAL_Delay(20);OLED_Init();myRTC_Init();Encoder_Init();Knob_SetForwardCallback(onKnobForward);Knob_SetBackwardCallback(onKnobBackward);Knob_SetPressedCallback(onKnobPressed);
}
void myRTC_Init(void) {// 先获取初始化标志位uint32_t initFlag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);// 如果初始化标志位是我们设定的值,说明已经初始化过一次了,则直接跳出初始化if (initFlag == RTC_INIT_FLAG)  return;// cubeMx生成的RTC初始化代码if (HAL_RTC_Init(&hrtc) != HAL_OK){Error_Handler();}// 如果第一次初始化则给时间赋初始值, 这里使用结构体, 跟time.h一样struct tm time = {.tm_year = 2025 - 1900,     // 注意这里的年是当前年与1900的差值.tm_mon = 11 - 1,            // 这里的月是0-11,所以实际月份要减1.tm_mday = 8,.tm_hour = 20,.tm_min = 12,.tm_sec = 30,};//将时间存入RTCmyRTC_SetTime(&time);//运行到这里说明是第一次初始化,所以要把设定好的初始化标志位存进BKP, 防止下次时间被重新赋值HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, RTC_INIT_FLAG);
}
// 编码器初始化
void Encoder_Init(void) {HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);setCounter(COUNTER_INIT_VALUE);
}
/*** @brief 设置旋钮正转回调函数* @param callback 回调函数指针*/
void Knob_SetForwardCallback(KnobCallback callback){onForwardCallback = callback;
}/*** @brief 设置旋钮反转回调函数* @param callback 回调函数指针*/
void Knob_SetBackwardCallback(KnobCallback callback){onBackwardCallback = callback;
}/*** @brief 设置旋钮按下回调函数* @param callback 回调函数指针*/
void Knob_SetPressedCallback(KnobCallback callback){onPressedCallback = callback;
}

2. RTC获取与设置时间

//设置RTC时间
HAL_StatusTypeDef myRTC_SetTime(struct tm *time) {uint32_t unixTime = mktime(time);   // 将实际时间转换为时间戳return RTC_WriteTimeCounter(&hrtc, unixTime);
}//获取RTC时间
struct tm *myRTC_GetTime(void) {time_t unixTime = RTC_ReadTimeCounter(&hrtc);   //将时间戳转换为实际时间return gmtime(&unixTime);
}

3. 重定义相关函数

// 设置编码器计数值
void setCounter(int value) {__HAL_TIM_SET_COUNTER(&htim1, value);
}// 读取编码器计数值
uint32_t getCounter(void) {return __HAL_TIM_GET_COUNTER(&htim1);
}// 获取按键状态
BtnState getBtnState(void) {// 读取IO电平,返回按键状态:如果低电平则为按下,否则为没按下return HAL_GPIO_ReadPin(Button_GPIO_Port, Button_Pin) == GPIO_PIN_RESET ? Pressed : Unpressed;
}// 获取系统时间
uint32_t getTick(void) {return HAL_GetTick();
}

4. 按键&旋转编码器处理函数

//旋钮&按键循环处理函数
void Encoder_loop(void) {uint32_t counter = getCounter();// 正转if (counter > COUNTER_INIT_VALUE) {// 正转回调函数if (onForwardCallback != NULL) {onForwardCallback();}}// 反转if (counter < COUNTER_INIT_VALUE) {// 反转回调函数if (onBackwardCallback != NULL) {onBackwardCallback();}}setCounter(COUNTER_INIT_VALUE);// 获取按键状态BtnState btnstate = getBtnState();// 记录按键按下时间static uint32_t pressedTime = 0;// 记录是否调用 0:未调用 1:已调用static uint8_t callbackState = 0;if (btnstate == Pressed) {if (pressedTime == 0) {pressedTime = getTick();// 防止重复检测按键按下 有效按下一次即可} else if (callbackState == 0 && getTick() - pressedTime > BTN_DEBOUNCE_TICKS) {// 按键回调函数if (onPressedCallback != NULL) {onPressedCallback();}callbackState = 1;}} else {pressedTime = 0;callbackState = 0;}
}

5. 旋转编码器正反转&按键回调函数

char weeks[7][10] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};// 万年历状态枚举
typedef enum {CalendarState_Normal,CalendarState_Setting,
} CalendarState;// 设置状态枚举
typedef enum {Year = 0, Month, Day, Hour, Minute, Second} SettingState;// 设置时间结构体
struct tm settingTime;// 当前万年历状态
CalendarState calendarState = CalendarState_Normal;// 当前设置状态
SettingState settingState = Year;// 旋钮顺时针旋转回调函数
void onKnobForward(void) {if (calendarState == CalendarState_Setting){switch (settingState){case Year:settingTime.tm_year++;break;case Month:settingTime.tm_mon++;if (settingTime.tm_mon > 11){settingTime.tm_mon = 0;}break;case Day:settingTime.tm_mday++;if (settingTime.tm_mday > 31){settingTime.tm_mday = 1;}break;case Hour:settingTime.tm_hour++;if (settingTime.tm_hour > 23){settingTime.tm_hour = 0;}break;case Minute:settingTime.tm_min++;if (settingTime.tm_min > 59){settingTime.tm_min = 0;}break;case Second:settingTime.tm_sec++;if (settingTime.tm_sec > 59){settingTime.tm_sec = 0;}break;}}
}// 旋钮逆时针旋转回调函数
void onKnobBackward(void) {if (calendarState == CalendarState_Setting){switch (settingState){case Year:settingTime.tm_year--;if (settingTime.tm_year < 70){settingTime.tm_year = 70;}break;case Month:settingTime.tm_mon--;if (settingTime.tm_mon < 0){settingTime.tm_mon = 11;}break;case Day:settingTime.tm_mday--;if (settingTime.tm_mday < 0){settingTime.tm_mday = 31;}break;case Hour:settingTime.tm_hour--;if (settingTime.tm_hour < 0){settingTime.tm_hour = 23;}break;case Minute:settingTime.tm_min--;if (settingTime.tm_min < 0){settingTime.tm_min = 59;}break;case Second:settingTime.tm_sec--;if (settingTime.tm_sec < 0){settingTime.tm_sec = 59;}break;}}
}// 按键按下回调函数
void onKnobPressed(void) {if (calendarState == CalendarState_Normal) {settingTime = *myRTC_GetTime();settingState = Year;calendarState = CalendarState_Setting;} else {if (settingState == Second) {myRTC_SetTime(&settingTime);calendarState = CalendarState_Normal;} else {settingState ++;}}
}

6. 显示函数—光标&时间

// 时间显示函数
void showTime(struct tm* time) {char str[50];sprintf(str, "%d-%02d-%02d", time->tm_year + 1900, time->tm_mon + 1, time->tm_mday);OLED_PrintASCIIString(24, 0, str, &afont16x8, OLED_COLOR_NORMAL);sprintf(str, "%02d:%02d:%02d", time->tm_hour, time->tm_min, time->tm_sec);OLED_PrintASCIIString(16, 20, str, &afont24x12, OLED_COLOR_NORMAL);char *week = weeks[time->tm_wday];uint8_t x_week = (128 - strlen(week) * 8) / 2;OLED_PrintASCIIString(x_week, 48, week, &afont16x8, OLED_COLOR_NORMAL);
}// 显示光标函数
void showCursor(){static uint32_t startTime = 0;uint32_t difftime = HAL_GetTick() - startTime;if (difftime > 2 * CURSOR_FLASH_INTERVAL){startTime = HAL_GetTick();}else if (difftime > CURSOR_FLASH_INTERVAL){CursorPosition position = cursorPositions[settingState];OLED_DrawLine(position.x1, position.y1, position.x2, position.y2, OLED_COLOR_NORMAL);}
}

7. 任务处理函数

// 任务处理
void MainTask(void) {Encoder_loop();OLED_NewFrame();if (calendarState == CalendarState_Normal) {struct tm* now = myRTC_GetTime();showTime(now);} else {showTime(&settingTime);showCursor();}OLED_ShowFrame();
}

四、cubeMx配置

1. 时钟树

RTC使用外部低速晶振LSE 32.768Hz

2. 按键PE15为输入模式

3. RTC

4. 旋转编码器

5. I2C

6. USART

五、函数指针详解

1. 定义函数指针

typedef void (*KnobCallback)(void);

2. 存储回调函数地址&实际被调用函数

/** 旋钮正转回调函数指针 */
KnobCallback onForwardCallback = NULL;
/** 旋钮反转回调函数指针 */
KnobCallback onBackwardCallback = NULL;
/** 旋钮按下回调函数指针 */
KnobCallback onPressedCallback = NULL;

3. 回调函数注册接口&实际就是将我们写的应用层回调函数地址传递给上述实际被调用函数

/*** @brief 设置旋钮正转回调函数* @param callback 回调函数指针*/
void Knob_SetForwardCallback(KnobCallback callback){onForwardCallback = callback;
}/*** @brief 设置旋钮反转回调函数* @param callback 回调函数指针*/
void Knob_SetBackwardCallback(KnobCallback callback){onBackwardCallback = callback;
}/*** @brief 设置旋钮按下回调函数* @param callback 回调函数指针*/
void Knob_SetPressedCallback(KnobCallback callback){onPressedCallback = callback;
}

4. 应用层回调函数

// 旋钮顺时针旋转回调函数
void onKnobForward(void) {if (calendarState == CalendarState_Setting){switch (settingState){case Year:settingTime.tm_year++;break;case Month:settingTime.tm_mon++;if (settingTime.tm_mon > 11){settingTime.tm_mon = 0;}break;case Day:settingTime.tm_mday++;if (settingTime.tm_mday > 31){settingTime.tm_mday = 1;}break;case Hour:settingTime.tm_hour++;if (settingTime.tm_hour > 23){settingTime.tm_hour = 0;}break;case Minute:settingTime.tm_min++;if (settingTime.tm_min > 59){settingTime.tm_min = 0;}break;case Second:settingTime.tm_sec++;if (settingTime.tm_sec > 59){settingTime.tm_sec = 0;}break;}}
}// 旋钮逆时针旋转回调函数
void onKnobBackward(void) {if (calendarState == CalendarState_Setting){switch (settingState){case Year:settingTime.tm_year--;if (settingTime.tm_year < 70){settingTime.tm_year = 70;}break;case Month:settingTime.tm_mon--;if (settingTime.tm_mon < 0){settingTime.tm_mon = 11;}break;case Day:settingTime.tm_mday--;if (settingTime.tm_mday < 0){settingTime.tm_mday = 31;}break;case Hour:settingTime.tm_hour--;if (settingTime.tm_hour < 0){settingTime.tm_hour = 23;}break;case Minute:settingTime.tm_min--;if (settingTime.tm_min < 0){settingTime.tm_min = 59;}break;case Second:settingTime.tm_sec--;if (settingTime.tm_sec < 0){settingTime.tm_sec = 59;}break;}}
}// 按键按下回调函数
void onKnobPressed(void) {if (calendarState == CalendarState_Normal) {settingTime = *myRTC_GetTime();settingState = Year;calendarState = CalendarState_Setting;} else {if (settingState == Second) {myRTC_SetTime(&settingTime);calendarState = CalendarState_Normal;} else {settingState ++;}}
}

5. 初始化

    Knob_SetForwardCallback(onKnobForward);Knob_SetBackwardCallback(onKnobBackward);Knob_SetPressedCallback(onKnobPressed);

6. 驱动层调用

// 正转回调函数
if (onForwardCallback != NULL) {onForwardCallback();
}// 反转回调函数
if (onBackwardCallback != NULL) {onBackwardCallback();
}// 按键回调函数
if (onPressedCallback != NULL) {onPressedCallback();
}

注:上述代码原作者:keysking

http://www.dtcms.com/a/589351.html

相关文章:

  • 面向边缘智能的稳健医疗AI:模型性能衰减监控与自适应微调机制深度解析(上)
  • 专业网站发展趋势wordpress html模式
  • 最简单的手机网站制作最常用最齐全wordpress插件大全
  • 【Mybatis笔记】- 1 - MyBatis入门
  • Spring AI Alibaba 提示词入门:从零开始掌握AI对话技巧
  • AI 实战篇:用 LangGraph 串联 RAG+MCP Server,打造能直接操控 Jira 的智能体
  • 爱丽丝的人偶
  • 一个网站里面只放一个图片怎么做的交互式网站有哪些功能
  • 永川区门户网站建设轨迹开发流程管理
  • web中间件——Nginx
  • 读诗的时候我却使用了自己研发的Chrome元素截图插件
  • MyBatis框架如何处理字符串相等的判断条件
  • 搭建网站需要程序WordPress中文替换布
  • 【云运维】Python基础(一)
  • 自己动手造轮子:用Requests和线程池构建一个轻量级高并发爬虫框架
  • 养生网站策划成都行业网站建设那里好
  • 网站设计 韩国做一个模板网站多少钱
  • 【QT笔记】信号和槽
  • 农村电子商务网站建设wordpress4.9.4 安装
  • 【MATLAB第120期】基于MATLAB的SOBOL全局敏感性分析模型运用插件(含UI界面)
  • 【Rust 探索之旅】Rust 核心特性完全指南:所有权、生命周期与模式匹配从入门到精通
  • 2023年INS SCI2区,演化状态驱动的多群体合作粒子群算法用于复杂优化问题,深度解析+性能实测
  • 淮北市网站制作公司网页设计怎么样
  • 平面网站设计公司logo形象墙
  • AG32 系列MCU集成了CPLD,有何优势呢
  • 37.关注推送
  • iis网站重定向设置微信公众号页面设计模板
  • Go的GRPC框架:Kitex
  • 从Webpack迁移到Rspack
  • 导购分享网站模板了解宿迁建设网站