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,
    };
    //将时间存入RTC
    myRTC_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

  1. 按键PE15为输入模式
  1. RTC
  1. 旋转编码器
  1. I2C
  1. 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

相关推荐
老神在在0013 分钟前
Mybatis01
后端·学习·spring·java-ee·mybatis
Y***89089 分钟前
Neo4j图数据库学习(二)——SpringBoot整合Neo4j
数据库·学习·neo4j
理人综艺好会17 分钟前
MySQL学习之go-mysql
学习·mysql·golang
想要成为计算机高手23 分钟前
π*0.6: 从实践中学习 -- 2025.11.17 -- Physical Intelligence (π) -- 未开源
人工智能·学习·机器人·多模态·具身智能·vla
偶像你挑的噻29 分钟前
15-Linux驱动开发-PWM子系统
linux·驱动开发·stm32·嵌入式硬件
黑客思维者32 分钟前
LLM底层原理学习笔记:模型评估的基准测试体系与方法论
人工智能·笔记·神经网络·学习·模型评估·基准测试
小猪佩奇TONY43 分钟前
OpenGL-ES 学习(17) ---- CubeMap 纹理
学习
硅农深芯1 小时前
是时候跟GitBucket说再见了
git·单片机
Sandman6z1 小时前
快速上手:国内通过 Gitee 学习使用在线托管平台
学习·gitee
未来之窗软件服务1 小时前
幽冥大陆(三十八)P50酒店门锁SDK C#仙盟插件——东方仙盟筑基期
开发语言·单片机·c#·东方仙盟·东方仙盟sdk·东方仙盟vos智能浏览器