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

相关推荐
Groundwork Explorer4 小时前
异步框架+POLL混合方案应对ESP32 MPY多任务+TCP多连接
python·单片机
车载测试工程师5 小时前
CAPL学习-SOME/IP交互层-符号数据库访问类函数
学习·tcp/ip·以太网·capl·canoe
暗然而日章5 小时前
C++基础:Stanford CS106L学习笔记 13 特殊成员函数(SMFs)
c++·笔记·学习
小智RE0-走在路上5 小时前
Python学习笔记(6)--列表,元组,字符串,序列切片
笔记·python·学习
d111111111d6 小时前
什么是内存对齐?在STM32上面如何通过编辑器指令来实现内存对齐。
笔记·stm32·单片机·嵌入式硬件·学习·编辑器
蒙奇D索大6 小时前
【数据结构】考研408 | 伪随机探测与双重散列精讲:散列的艺术与均衡之道
数据结构·笔记·学习·考研
bai5459366 小时前
STM32 CuberIDE 中断
stm32·单片机·嵌入式硬件
小叶子来了啊6 小时前
5Arduino 程序结构
单片机·嵌入式硬件
舞动青春887 小时前
Ubuntu安装QEMU过程及问题记录
linux·学习·ubuntu
知识分享小能手7 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04的基本配置 (3)
linux·学习·ubuntu