基于FreeRTOS的STM32多功能手表

news2024/11/14 18:10:07

前言

项目背景

项目演示

使用到的硬件

项目原理图

目前版本实现的功能 

设计到的freertos知识

实现思路

代码讲解

初始化GPIO引脚、配置时钟

蜂鸣器初始化以及软件定时器创建

系统默认创建的defaultTaskHandle

 创建七个Task,代表七个功能

 ShowTimeTask

ShowMenuTask

ShowCalendarTask

ShowFlashLightTask

ShowDHT11Task

ShowClockTimeTask

ShowSetting_Task

总结

按键中断回调函数

蜂鸣器实现按键音效

buzzer_init

SystemSoundTimer_Func( TimerHandle_t xTimer )

void buzzer_buzz(int freq, int time_ms)

总结

前言

这是我第一篇关于FreeRTOS项目的博客,在这之前,我写了十几篇关于FreeRTOS的博客,从应用到底层原理都有讲解,如果复刻我这个项目的同学,遇到不会的话,可以跳转到我之前写的关于FreeRTOS博客。深入了解FreeRTOS:实时操作系统的核心概念和应用

项目背景

在更新完FreeRTOS的系列教程之后,迟迟没有通过一些项目来使用FreeRTOS,之前用裸机做过万年历,包括温湿度模块,以及做过一个多级菜单,正好可以拿来练手FreeRTOS,于是打算做一个多功能的手环,包括多级菜单、温湿度显示、闹钟设置、手电筒功能、日历显示、设置菜单。花费了几天,改了不少的BUG,终于把这几个功能集成到一起,做成了基于FREERTOS的STM32多功能手表。

项目演示

基于freertos的智能手表

使用到的硬件

  • Stm32f103c8t6最小系统板(20k RAM, 64KROM)
  • 0.96寸oled显示屏(u8g2库)
  • 四个独立按键(中断中写入队列与任务协调)
  • 无源蜂鸣器(按键或者电子时钟触发)
  • DHT11(显示温湿度信息)

项目原理图

目前版本实现的功能 

  • 时间显示
  • 多级菜单显示
  • 万年历(显示2024年份的日历)
  • 模拟手电
  • 温湿度显示
  • 电子闹钟
  • 设置(开关系统声音)
  • 补充:如果你有多的传感器当然可以继续扩展手环的功能,不过你要注意的是,c8t6只有20K的RAM,各个驱动运行的内存以及FreeRTOS的,如果超过了20K,那就无法继续扩展了。

设计到的freertos知识

  • 任务管理(创建,删除,状态转换)
  • 软件定时器(创建,启动,停止)
  • 队列(创建,写队列,读队列)
  • 二值信号量(创建,give,take)
  • 中断管理(中断与任务通信)
  • 资源管理(主要是堆栈大小的处理)
  • 补充:这些知识点我都有写博客,不会的可以跳转去仔细看看。
  • https://blog.csdn.net/m0_74676415/article/details/139202620

实现思路

   在freertos初始化时创建除默认任务之外的七个任务,分别是时间显示task,舵机菜单显示task以及五个功能(温湿度、手电筒、设置、日历、闹钟)task,在默认任务中创建两个软件定时器,分别是时间显示Timer和电子闹钟Timer,其中,时间显示的定时时间为1s,保证手表时钟显示时间的正确。电子闹钟Timer的定时时间为0.1s,因为我们闹钟定时是0.1s刷新一次,这样子可以实现一秒之内显示0-9这十个数字,在观感上有很大提升。另外在中断中向队列写入数据与各个任务完成通信并响应操作。

1、创建两个定时器。

2、创建七个任务。 

3、按键中断触发任务切换。

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{    
    /* key interrupt : send data to queue */
    
    /* some data maybe useless */
    extern BaseType_t end_flag;
    extern BaseType_t seclect_end;
    BaseType_t  RM_Flag, LM_Flag, EN_Flag, EX_Flag;
    Key_data key_data;
        
    if(GPIO_Pin == GPIO_PIN_11)
    {
        mdelay(15);
        
        if(end_flag == 1&&seclect_end == 0)
        {
            RM_Flag = 1;
            key_data.rdata = RM_Flag;
            xQueueSendToBackFromISR(g_xQueueMenu, &key_data, NULL);
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
            RM_Flag = 0;            
        }
    }
    if(GPIO_Pin == GPIO_PIN_10)
    { 
        mdelay(15);
        if(end_flag == 1&&seclect_end == 0)
        {
             LM_Flag = 1;
            key_data.ldata = LM_Flag;
            xQueueSendToBackFromISR(g_xQueueMenu, &key_data, NULL);
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
            LM_Flag = 0;
        }
    }
    if(GPIO_Pin == GPIO_PIN_1)
    {
        mdelay(15);
        if(end_flag == 1&&seclect_end == 0)
        {
            EN_Flag = 1;
            key_data.updata = EN_Flag;
            xQueueSendToBackFromISR(g_xQueueMenu, &key_data, NULL);
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
            EN_Flag = 0;
        }
    }
    if(GPIO_Pin == GPIO_PIN_0)
    {
        mdelay(15);
        if(end_flag == 1&&seclect_end == 0)
        {
            EX_Flag = 1;
            key_data.exdata = EX_Flag;
            if(end_flag == 1&&seclect_end == 0)xQueueSendToBackFromISR(g_xQueueMenu, &key_data, NULL);
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
            EX_Flag = 0;
        }
    }
}

代码讲解

        我这里会介绍一下各个任务,让你有大体的认识。由于是个人开发,程序前后也没有进行严格的测试和优化,可能部分代码是没有作用的,但是不影响整体程序的运行就没有优化,如果你有某些不理解的地方,可以删掉该部分代码,如果现象依旧正常,说明我之前写的那堆就确实没用,但是,也不要随便去质疑,可能我这么做有我的考虑。

初始化GPIO引脚、配置时钟

我们使用CubeMX完成GPIO引脚的初始化,包括四个按键中断引脚,DHT11温湿度的Data引脚以及Oled屏幕的I2C引脚和无源蜂鸣器的电平触发引脚。

 

蜂鸣器初始化以及软件定时器创建

        这里创建了两个定时器,g_Timer和g_Clock_Timer,显示时间定时器,和电子闹钟定时器,注意显示时间的周期是1000tick,而电子闹钟的周期是100tick,另外按键音效也是通过软件定时器的。补充:定时器默认优先级很低,你需要到FreeRTOSConfig.h文件中手动提高软件定时器的优先级(高于所以创建的任务),我们设置为34,比我们其他任务的优先级都要高,不然定时器定时的时间会不准确。

系统默认创建的defaultTaskHandle

        我们直接在这里开启时间定时器,这样子,我们就可以每隔1s对时间加一次,保证复位上电第一时间闹钟可以正确显示时钟。我们还在这里创建了一个队列,这个队列是来处理按键的数据的,可以发现,这个队列的参数是1和4,1表示的是,这个队列只可以放一个数据,4表示的是,这个数据的大小是四个字节,正好对应我们按键结构体的四个字节,这样设置队列,可以有效防止按键抖动带来的影响,因为队列满的话,你中断里面写队列也会失败,但不会阻塞中断,不要太好用。

 创建七个Task,代表七个功能

        这七个任务,分别代表了时间显示、多级菜单显示、日历显示、手电功能显示、温湿度显示、闹钟显示、设置界面显示,这是这个小项目的核心,看懂这七个任务,了解好如何调度这七个任务,那这个项目你就拿捏了。

 ShowTimeTask

void ShowTimeTask(void *params)
{

    /* suspend_other_task */
    vTaskSuspend(xShowMenuTaskHandle);
    //vTaskSuspend(xShowWoodenFishTaskHandle);
    vTaskSuspend(xShowFlashLightTaskHandle);
    vTaskSuspend(xShowSettingTaskHandle);
    vTaskSuspend(xShowClockTaskHandle);
    vTaskSuspend(xShowCalendarTaskHandle);
    vTaskSuspend(xShowDHT11TaskHandle);

    /* u8g2 Start */
    u8g2_t u8g2;
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0, u8x8_byte_hw_i2c, u8g2_stm32_delay);
    u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
    u8g2_SetPowerSave(&u8g2, 0); // wake up display
    u8g2_ClearDisplay(&u8g2);
//    u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese1);
    u8g2_SetFont(&u8g2, u8g2_font_fur35_tf);
    
    struct Key_data    key_data;

    while(1)
    {    
        u8g2_ClearBuffer(&u8g2);
        
        /* draw */
        u8g2_DrawXBMP(&u8g2, 0, 0, 23, 10, ShowPower);
        u8g2_DrawXBMP(&u8g2, 105, 0, 23, 10, ShowGame);
        /* draw time */
        u8g2_DrawXBMP(&u8g2, time.x[3], time.y, time.w, time.h, BigNum[sec_unit]);
        u8g2_DrawXBMP(&u8g2, time.x[2], time.y, time.w, time.h, BigNum[sec_decade]);
        u8g2_DrawRBox(&u8g2, Box1.x, Box1.y, Box1.w, Box1.h, BOX_R);
        u8g2_DrawRBox(&u8g2, Box2.x, Box2.y, Box2.w, Box2.h, BOX_R);        
        u8g2_DrawXBMP(&u8g2, time.x[1], time.y, time.w, time.h, BigNum[min_unit]);
        u8g2_DrawXBMP(&u8g2, time.x[0], time.y, time.w, time.h, BigNum[min_decade]);

        u8g2_DrawXBMP(&u8g2, 56, 2, 6, 8, Num_6x8[hour_decade]);

        u8g2_DrawXBMP(&u8g2, 66, 2, 6, 8, Num_6x8[hour_unit]);
                
        u8g2_SendBuffer(&u8g2);

        vTaskDelay(250);
        /* handle queue data */
        if(time_flag == 0)
        {
            xQueueReceive(g_xQueueMenu, &key_data, 0);
        }
        /* task scheduling */
        if(key_data.updata == 1)
        {    
            buzzer_buzz(2500, 100);                                 
            vTaskResume(xShowMenuTaskHandle);
            vTaskSuspend(NULL);
            key_data.updata = 0;
        }            
    }
}

1、挂起其他的任务,因为默认上电之后,是显示时间的。

2、 初始化OLED屏幕,调用 u8g2 库。

3、不断的循环显示时间。

4、如果队列读到有效按键数据,就跳转到菜单界面。

ShowMenuTask

void ShowMenuTask(void *params)
{

    /*u8g2_config*/
    u8g2_config();
    /* ShowUI */
//    u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese1);
    u8g2_FirstPage(&u8g2);
    do {
    u8g2_SendBuffer(&u8g2);
       } while (u8g2_NextPage(&u8g2));
    
    struct Key_data    key_data;
    
    while(1)
    {
        u8g2_ClearBuffer(&u8g2);
        ShowUI();
        u8g2_SendBuffer(&u8g2);
        /* receive queue data and keep waitting */
        if(queue_flag == 0)
        {
            xQueueReceive(g_xQueueMenu, &key_data, portMAX_DELAY);
        }
        /* handle data */
        if(key_data.rdata == 1)
        {    
            end_flag = 0;
            if(dock_pos != 0)
            {
                ui_right(&cleder.x, 2);ui_right(&torch.x, 2);ui_right(&hum.x, 2);ui_right(&clock.x, 2);ui_right(&setting.x, 2);
                /* state_machine */
                if(dock_status==0)dock_status=1;
                dock_status--;
                switch(dock_pos)
                {
                    case 2:if(dock_status!=0){ui_up(&cleder.y, 1);ui_up(&torch.y, 1);ui_down(&hum.y, 1);ui_down(&clock.y, 1);}break;
                    case 1:if(dock_status!=0){ui_up(&cleder.y, 1);ui_up(&setting.y, 1);ui_down(&torch.y, 1);ui_down(&hum.y, 1);}break;
                    case 4:if(dock_status!=0){ui_up(&clock.y, 1);ui_up(&hum.y, 1);ui_down(&setting.y, 1);ui_down(&cleder.y, 1);}break;
                    case 3:if(dock_status!=0){ui_up(&hum.y, 1);ui_up(&torch.y, 1);ui_down(&setting.y, 1);ui_down(&clock.y, 1);}break;
                }
            }
            queue_flag++;    
            if(queue_flag == 20)
            {
                dock_status=10;
                end_flag = 1;
                if(dock_pos != 0){dock_pos--;str_flag--;}
                queue_flag = 0;
                key_data.rdata = 0;
                key_data.ldata = 0;
            }
            if(end_flag == 1)buzzer_buzz(2000, 100);
        }
        else if(key_data.ldata == 1)
        {
            end_flag = 0;
            if(dock_pos < 4)
            {        
                ui_left(&cleder.x, 2);ui_left(&torch.x, 2);ui_left(&hum.x, 2);ui_left(&clock.x, 2);ui_left(&setting.x, 2);
                /* state_machine */
                if(dock_status==0)dock_status=1;
                dock_status--;
                switch(dock_pos)
                {
                    case 0:if(dock_status!=0){ui_up(&hum.y, 1);ui_up(&torch.y, 1);ui_down(&cleder.y, 1);ui_down(&setting.y, 1);}break;
                    case 1:if(dock_status!=0){ui_up(&hum.y, 1);ui_up(&clock.y, 1);ui_down(&torch.y, 1);ui_down(&cleder.y, 1);}break;
                    case 2:if(dock_status!=0){ui_up(&clock.y, 1);ui_up(&setting.y, 1);ui_down(&hum.y, 1);ui_down(&torch.y, 1);}break;
                    case 3:if(dock_status!=0){ui_up(&setting.y, 1);ui_up(&cleder.y, 1);ui_down(&clock.y, 1);ui_down(&hum.y, 1);}break;
                }                
            }
            queue_flag++;    
            if(queue_flag == 20) 
            {
                dock_status = 10;
                end_flag = 1;
                if(dock_pos < 4){dock_pos++;str_flag++;}
                queue_flag = 0;
                key_data.ldata = 0;
                key_data.rdata = 0;
            }
            if(end_flag == 1)buzzer_buzz(2000, 100);
        }
        /* ststus machine : task scheduling  */
        else if(key_data.exdata == 1)
        {
            buzzer_buzz(2000, 100);
            switch(dock_pos)
            {
                case 0: vTaskResume(xShowCalendarTaskHandle);vTaskSuspend(NULL);key_data.exdata = 0;break;
                case 1: vTaskResume(xShowFlashLightTaskHandle);vTaskSuspend(NULL);key_data.exdata = 0;break;
                case 2: vTaskResume(xShowDHT11TaskHandle);vTaskSuspend(NULL);key_data.exdata = 0;break;
                case 3: vTaskResume(xShowClockTaskHandle);vTaskSuspend(NULL);key_data.exdata = 0;break;
                case 4: vTaskResume(xShowSettingTaskHandle);vTaskSuspend(NULL);key_data.exdata = 0;break;
            }
        }
        else if(key_data.updata == 1)
        {
            /* SysSound */
            buzzer_buzz(2000, 100);
            vTaskResume(xShowTimeTaskHandle);
            vTaskSuspend(NULL);
            key_data.updata = 0;
        }
    }
}

1、初始化OLED屏幕,调用 u8g2 库。

2、显示UI界面。

3、读取队列,如果读不到就阻塞,直到中断中写入队列之后唤醒任务。

4、根据不同的按键处理不同的事件。

ShowCalendarTask

void ShowCalendarTask(void *params)
{
    u8g2_t u8g2;
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0, u8x8_byte_hw_i2c,     u8g2_stm32_delay);
    u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
    u8g2_SetPowerSave(&u8g2, 0); // wake up display
    u8g2_ClearDisplay(&u8g2);
    u8g2_SetFont(&u8g2, u8g2_font_spleen5x8_mf);

    u8g2_SendBuffer(&u8g2);
    struct Key_data    key_data;

    const char ucMonthDay[32][3] = {"0","1","2","3","4","5","6","7","8","9","10","11","12","13","14",

"15","16","17","18","19","20","21","22","23","24","25","26",

"27","28","29","30","31"};    
    const char ucWeekHeader[7][3] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};
    uint16_t usWeekX[7] = {0,  17, 34 , 51, 68, 85, 102};
    uint16_t usWeekY[6] = {17, 26, 35 ,44, 53, 62};
    uint16_t usLineY[12] = {0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121};
    uint8_t line_pos  = 0;

    uint8_t week_pos  = 0;
    uint32_t week_temp, week_temp_temp, month_temp, enter_temp;
    
    uint32_t month = 1;
    uint16_t wee = 0;    //记录当前月的第一天是星期几
    
    while(1)
    {    
        u8g2_ClearBuffer(&u8g2);            
            
        for(int i=0; i<=6; i++){
            u8g2_DrawStr(&u8g2, usWeekX[i], 8, ucWeekHeader[i]);            
        }
        
        /* month对应月份 */
        month_temp = month_run(month);//获取天数        
        week_temp = judge_week(2024);//获取1月1日是星期几                
        wee = week_temp;
        
        for(int m=1; m<month; m++){
            wee = (wee+month_run(m))%7;        //二月记录当前月的第一天是星期几        
        }
        
        week_temp = wee;        
        week_temp_temp = week_temp;
        
        /* 绘制当前月的日历 */
        for(int k=1; k<=month_temp; k++){            
            enter_temp  = week_temp%7;
            week_temp++;        
            if(k<=(7-week_temp_temp)){
                week_pos=0;
            }else if(enter_temp == 0){
                week_pos = week_pos+1;            
            }
            u8g2_DrawStr(&u8g2, usWeekX[enter_temp], usWeekY[week_pos], ucMonthDay[k]);    
        }
        
        u8g2_DrawLine(&u8g2, 115, 0, 115, 62);
        u8g2_DrawStr(&u8g2, 117, 32, ucMonthDay[line_pos+1]);    
        u8g2_DrawHLine(&u8g2, usLineY[line_pos], 63, 11);
        
        u8g2_SendBuffer(&u8g2);

        /* 读按键中断队列 */
        xQueueReceive(g_xQueueMenu, &key_data, portMAX_DELAY);

        if(key_data.rdata == 1)
        {
            buzzer_buzz(2500, 100);
            month++;
            line_pos++; 
            if(line_pos>11)line_pos=0;
            if(month>12)month=1;
            key_data.rdata = 0;
        }
        else if(key_data.exdata == 1)
        {
            buzzer_buzz(2500, 100);
            vTaskResume(xShowMenuTaskHandle);
            key_data.exdata = 0;
            vTaskSuspend(NULL);

        }
        else if(key_data.ldata==1)
        {
            
            buzzer_buzz(2500, 100);
            month--;
            line_pos--; 
            if(line_pos>=11)line_pos=11;
            if(month==0)month=12;
            key_data.ldata = 0;
            
        }
        else if(key_data.updata==1)
        {    
            memset(&key_data, 0, sizeof(Key_data));
        }
    }
}
 

1、初始化OLED屏幕,调用 u8g2 库。

2、根据全局变量month以及line_pos,显示出这个月份的特定日历。

3、读取按键中断队列,进入阻塞状态,直到按键中断写入队列,唤醒任务。

4、根据读取到的键值去进行不同事件的处理。

ShowFlashLightTask

void ShowFlashLightTask(void *params)
{
    /* u8g2 Start */
    u8g2_t u8g2;
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0, u8x8_byte_hw_i2c,         u8g2_stm32_delay);
    u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
    u8g2_SetPowerSave(&u8g2, 0); // wake up display
    u8g2_ClearDisplay(&u8g2);
//    u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese1);
//    u8g2_SetFont(&u8g2, u8g2_font_spleen32x64_mf);    
    u8g2_SetFont(&u8g2, u8g2_font_fur35_tf);
    
    //u8g2_ClearBuffer(&u8g2);
    u8g2_DrawXBMP(&u8g2, 48, 16, 30, 30, light);
    u8g2_SendBuffer(&u8g2);

    uint8_t light_flag = 0;
    struct Key_data    key_data;
    
    while(1)
    {
        u8g2_ClearBuffer(&u8g2);    
        /* 读按键中断队列 */
        xQueueReceive(g_xQueueMenu, &key_data, portMAX_DELAY);
        
        if(key_data.updata == 1)
        {
            buzzer_buzz(2500, 100);
            switch(light_flag)
            {
                case 0: u8g2_DrawBox(&u8g2, 0, 0, 128, 64);light_flag++;break;
                case 1: u8g2_DrawXBMP(&u8g2, 48, 16, 30, 30, light);light_flag--;break;
            }
        }        
        if(key_data.exdata == 1)
        {
            buzzer_buzz(2500, 100);
            vTaskResume(xShowMenuTaskHandle);
            vTaskSuspend(NULL);
            u8g2_ClearBuffer(&u8g2);    
            u8g2_DrawXBMP(&u8g2, 48, 16, 30, 30, light);
        }
        else if(key_data.ldata==1||key_data.rdata==1)
        {
            switch(light_flag)
            {
                case 1: u8g2_DrawBox(&u8g2, 0, 0, 128, 64);break;
                case 0: u8g2_DrawXBMP(&u8g2, 48, 16, 30, 30, light);break;
            }
        
        }
        u8g2_SendBuffer(&u8g2);
    }
}

1、 初始化OLED屏幕,调用 u8g2 库。

2、显示手电筒图标。

3、读取按键中断队列,进入阻塞状态,直到按键中断写入队列之后,唤醒任务。

4、根据不同的键值进行不同事件的处理。

ShowDHT11Task

void ShowDHT11Task(void *params)
{
    DHT11_Init();
    u8g2_t u8g2;
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0, u8x8_byte_hw_i2c, u8g2_stm32_delay);
    u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
    u8g2_SetPowerSave(&u8g2, 0); // wake up display
    u8g2_ClearDisplay(&u8g2);
    u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese1);

    u8g2_SendBuffer(&u8g2);
    
    struct Key_data    key_data;
    int hum, temp;
    int hum1, hum2,temp1, temp2;
    
    while(1)
    {    
        u8g2_ClearBuffer(&u8g2);            
        if (DHT11_Read(&hum, &temp) !=0 ){
            //printf("\n\rdht11 read err!\n\r");
            DHT11_Init();
        }
        else{
            temp1 = temp%10;    //low bit
            temp2 = temp/10;   //high bit
                            
            hum1 = hum%10;        //low bit    
            hum2 = hum/10;    //high bit
            
            u8g2_DrawXBMP(&u8g2, 10, 20, 20, 40, BigNum[temp2]);
            u8g2_DrawXBMP(&u8g2, 35, 20, 20, 40, BigNum[temp1]);
            
            u8g2_DrawXBMP(&u8g2, 75, 20, 20, 40, BigNum[hum2]);
            u8g2_DrawXBMP(&u8g2, 100, 20, 20, 40, BigNum[hum1]);
        }
        u8g2_DrawStr(&u8g2, 15, 15, "temp");
        u8g2_DrawStr(&u8g2, 85, 15, "Hum");
        
        u8g2_SendBuffer(&u8g2);
        vTaskDelay(500);//0.5s刷新一次
    
        /* 读按键中断队列 */
        xQueueReceive(g_xQueueMenu, &key_data, 0);
        
        if(key_data.exdata == 1)
        {
            buzzer_buzz(2500, 100);
            vTaskResume(xShowMenuTaskHandle);
            vTaskSuspend(NULL);
            key_data.exdata = 0;
        }
        else if(key_data.ldata==1||key_data.rdata==1||key_data.updata==1)
        {
            memset(&key_data, 0, sizeof(Key_data));
        }
    }
}
 

1、温湿度模块初始化。

2、初始化OLED屏幕,调用 u8g2 库。

3、不断循环,读取温湿度,并把数据刷新到OLED屏幕上面。

4、直到从队列中读取到有用的数据,切换回去多级菜单界面。

ShowClockTimeTask

void ShowClockTimeTask(void *params)
{

    /* u8g2 Start */
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0, u8x8_byte_hw_i2c, u8g2_stm32_delay);
    u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
    u8g2_SetPowerSave(&u8g2, 0); // wake up display
    u8g2_ClearDisplay(&u8g2);
    u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese1);
//    u8g2_SetFont(&u8g2, u8g2_font_spleen32x64_mf);    
//    u8g2_SetFont(&u8g2, u8g2_font_fur35_tf);
    
    struct Key_data    key_data;    
    while(1)
    {
        u8g2_ClearBuffer(&u8g2);
        ShowClock();        
        //u8g2_DrawXBMP(&u8g2, 0, 0, 20, 40, BigNum[temp]);
        u8g2_SendBuffer(&u8g2);
        
        /* 读按键中断队列 */
        if(clock_flag == 0)
        {
            xQueueReceive(g_xQueueMenu, &key_data, portMAX_DELAY);
        }
        /* seclect */
        if(key_data.rdata == 1)
        {
            buzzer_buzz(2500, 100);
            seclect_flag++;
            if(seclect_flag>4)seclect_flag=0;
        }
        if(key_data.ldata == 1)
        {
            buzzer_buzz(2500, 100);
            seclect_flag--;
            if(seclect_flag<0)seclect_flag=4;
        }
        /* handle_data */
        if(key_data.updata == 1)
        {
            buzzer_buzz(2500, 100);
            if(seclect_flag == 4)
            {
                /*启动定时器*/
                if(g_Clock_Timer != NULL)
                {
                    xTimerStart(g_Clock_Timer, 0);
                    clock_flag = 1;
                    key_data.updata = 0;
                }
            }
            else{
                g_clock_num[seclect_flag]++;
                if(g_clock_num[seclect_flag]>9)g_clock_num[seclect_flag]=0;                
            }
        }        
        if(key_data.exdata == 1)
        {
            buzzer_buzz(2500, 100);
            clock_flag = 0;
            xTimerStop(g_Clock_Timer, 0);
            vTaskResume(xShowMenuTaskHandle);
            vTaskSuspend(NULL);
        }    
        /* circle_run */
        if(clock_flag == 1)
        {
            /* time_stop */
            if(g_clock_num[0]==g_real_time[0]&&g_clock_num[1]==g_real_time[1]&&g_clock_num[2]==g_real_time[2]&&g_clock_num[3]==g_real_time[3])
            {
                clock_flag = 0;
                xTimerStop(g_Clock_Timer, 0);        
                memset(g_real_time, 0, sizeof(g_real_time));
                
                /* music */
                buzzer_buzz(2500, 1000);
            }
        }
    }
}

1、初始化OLED屏幕,调用 u8g2 库。

2、显示当前的闹钟界面。

3、读取按键中断队列,等待按键中断唤醒。

4、根据不同的键值处理不同的事件。(左移、右移、确认、退出)

5、如果确认了开始计时,会启动g_Clock_Timer定时器,时间到了之后,蜂鸣器发生播报,并且回归最开始的循环。

ShowSetting_Task

void ShowSetting_Task(void)
{
    u8g2_config();
    u8g2_SetFont(&u8g2, u8g2_font_7x13_mf);    
    u8g2_FirstPage(&u8g2);
    do {
    ShowSetiing();
    u8g2_SendBuffer(&u8g2);
       } while (u8g2_NextPage(&u8g2));
    for(int i = 0; i<5; i++)
    {
        width[i] = u8g2_GetStrWidth(&u8g2, &strs[i][10]);
    }
    struct Key_data    key_data;

    while(1)
    {
        u8g2_ClearBuffer(&u8g2);
                 
        switch(seclect)
        {
            case 0: u8g2_DrawStr(&u8g2, 64, 25,"@moyiji");u8g2_DrawStr(&u8g2, 61, 50,"2021/05/27");break;
            case 1: ShowSwitch(power_button);break;
            case 2: ShowSwitch(power_button);break;
            case 3: ShowSwitch(power_button);break;
            case 4: ShowAbout();break;
        }
        ShowSetiing();
        u8g2_SendBuffer(&u8g2);
        if(seclect_end == 0)
        {
            pdPASS == xQueueReceive(g_xQueueMenu, &key_data, portMAX_DELAY);
        }
        /* move_down */
        if(key_data.rdata == 1)
        {
            seclect_end++;
            if(seclect!=4)
            {
                /* status_machine */
                switch(seclect)
                {
                    case 0: ui_run(&seclect_w[0], &seclect_w[1], 1);ui_run(&seclect_y[0], &seclect_y[1], 1);break;
                    case 1: ui_run(&seclect_w[0], &seclect_w[2], 1);ui_run(&seclect_y[0], &seclect_y[2], 1);break;
                    case 2: ui_run(&seclect_w[0], &seclect_w[3], 1);ui_run(&seclect_y[0], &seclect_y[3], 1);break;
                    case 3: ui_run(&seclect_w[0], &seclect_w[4], 1);ui_run(&seclect_y[0], &seclect_y[4], 1);break;
                    case 4: ui_run(&seclect_w[0], &seclect_w[5], 1);ui_run(&seclect_y[0], &seclect_y[5], 1);break;                
                }
            }
            if(seclect_end == 20)
            {
                if(seclect!=4)seclect++;
                seclect_end = 0;
                key_data.rdata = 0;
            }
            if(seclect_end == 0)buzzer_buzz(2500, 100);                                 
        }
        /* move_up */
        else if(key_data.ldata == 1)
        {
            seclect_end++;
            if(seclect!=0)
            {
                switch(seclect)
                {
                    case 0: break;
                    case 1: ui_run(&seclect_w[0], &seclect_w[5], 1);ui_run(&seclect_y[0], &seclect_y[5], 1);break;
                    case 2: ui_run(&seclect_w[0], &seclect_w[1], 1);ui_run(&seclect_y[0], &seclect_y[1], 1);break;
                    case 3: ui_run(&seclect_w[0], &seclect_w[2], 1);ui_run(&seclect_y[0], &seclect_y[2], 1);break;
                    case 4: ui_run(&seclect_w[0], &seclect_w[3], 1);ui_run(&seclect_y[0], &seclect_y[3], 1);break;                
                }                
            }
            if(seclect_end == 20)
            {
                if(seclect!=0)seclect--;
                seclect_end = 0;
                key_data.ldata = 0;
            }
            if(seclect_end == 0)buzzer_buzz(2500, 100);                                             
        }
        if(key_data.updata == 1)
        {
            buzzer_buzz(2500, 100);        
            if(seclect!=0&&seclect!=4)
            {
                power_button++;
                power_button = power_button%2;
            }
        }        
        /* task scheduling */
        if(key_data.exdata == 1)
        {
            buzzer_buzz(2500, 100);                                 
            vTaskResume(xShowMenuTaskHandle);
            vTaskSuspend(NULL);
        }
    }
}

1、初始化OLED屏幕,调用 u8g2 库。

2、显示设置界面,如何读取按键中断队列,进入阻塞状态,等待唤醒。

3、根据读取到的键值进行不同的事件处理。

总结

        这就是这七个任务的具体代码,我讲每个任务的主要流程给读者列举了出来,具体如何实现,需要读者细细揣摩,多看几遍就读懂了。

按键中断回调函数

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{    
    /* key interrupt : send data to queue */
    
    /* some data maybe useless */
    extern BaseType_t end_flag;
    extern BaseType_t seclect_end;
    BaseType_t  RM_Flag, LM_Flag, EN_Flag, EX_Flag;
    Key_data key_data;
        
    if(GPIO_Pin == GPIO_PIN_11)
    {
        mdelay(15);
        
        if(end_flag == 1&&seclect_end == 0)
        {
            RM_Flag = 1;
            key_data.rdata = RM_Flag;
            xQueueSendToBackFromISR(g_xQueueMenu, &key_data, NULL);
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
            RM_Flag = 0;            
        }
    }
    if(GPIO_Pin == GPIO_PIN_10)
    { 
        mdelay(15);
        if(end_flag == 1&&seclect_end == 0)
        {
             LM_Flag = 1;
            key_data.ldata = LM_Flag;
            xQueueSendToBackFromISR(g_xQueueMenu, &key_data, NULL);
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
            LM_Flag = 0;
        }
    }
    if(GPIO_Pin == GPIO_PIN_1)
    {
        mdelay(15);
        if(end_flag == 1&&seclect_end == 0)
        {
            EN_Flag = 1;
            key_data.updata = EN_Flag;
            xQueueSendToBackFromISR(g_xQueueMenu, &key_data, NULL);
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
            EN_Flag = 0;
        }
    }
    if(GPIO_Pin == GPIO_PIN_0)
    {
        mdelay(15);
        if(end_flag == 1&&seclect_end == 0)
        {
            EX_Flag = 1;
            key_data.exdata = EX_Flag;
            if(end_flag == 1&&seclect_end == 0)xQueueSendToBackFromISR(g_xQueueMenu, &key_data, NULL);
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
            EX_Flag = 0;
        }
    }
}

1、我们给按键中断回调函数里面加了一点点延迟,可以减小按键抖动带来的影响。

2、 往g_xQueueMenu队列里面,不断的写入按键按下的数据,这里由于队列是一位数据的,面对按键抖动的情况,在任务读取队列的时候,中断虽然会进行写队列,但是都是不成功的,有效的减少了按键抖动。

蜂鸣器实现按键音效

buzzer_init

void buzzer_init(void)
{
    /* 初始化蜂鸣器 */
    PassiveBuzzer_Init();    
    
    /* 创建定时器 */

    g_TimerSound = xTimerCreate( "SystemSound", 
                            200,
                            pdFALSE,
                            NULL,
                            SystemSoundTimer_Func);
}

1、初始化蜂鸣器,将蜂鸣器引脚配置为定时器pwm输出模式,因为无源蜂鸣器的发声,需要给特定的频率的pwm。

2、创建定时器 SystemSound,实现如何只b一声,实现按键的音效。

SystemSoundTimer_Func( TimerHandle_t xTimer )

static void SystemSoundTimer_Func( TimerHandle_t xTimer )
{
    PassiveBuzzer_Control(0);
}
 

1、这个定时器的任务就是关闭pwm生成,从而导致蜂鸣器停止发生。 

void buzzer_buzz(int freq, int time_ms)

void buzzer_buzz(int freq, int time_ms)
{
    if(power_button == 0)
    {
        PassiveBuzzer_Set_Freq_Duty(freq, 50);        
        /* 启动定时器 */
        xTimerChangePeriod(g_TimerSound, time_ms, 0);        
    }

}

1、如果 power_button == 0,我们就可以触发按键音效,这个值可以在设置菜单里面设置。

2、我们先开启pwm生成,这个时候蜂鸣器会叫,同时开启了g_TimerSound定时器,0.2s之后就会去关闭掉蜂鸣器,从而实现了按键音效的功能。

总结 

这就是我这篇基于FreeRTOS的STM32多功能手表的博客,这是个十分简单的小项目,如果你很了解FreeRTOS,并且裸机开发过类似的功能,那不需要一天,你就能完美复刻我这个小项目。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2069809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录算法训练营 | 动态规划 part08

121. 买卖股票的最佳时机 121. 买卖股票的最佳时机 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最…

轻松入门大模型:150页精炼指南,简化你的学习之路

如果问个问题&#xff1a;有哪些产品曾经创造了伟大的奇迹&#xff1f;ChatGPT 应该会当之无愧入选。仅仅发布 5 天&#xff0c;ChatGPT 就吸引了 100 万用户——当然&#xff0c;数据不是关键&#xff0c;关键是其背后的技术开启了新的 AI 狂潮&#xff0c;成为技术变革的点火…

ctfshow-web入门-sql注入(web231-web236)update 注入

目录 1、web231 2、web232 3、web233 4、web234 5、web235 6、web236 1、web231 拼接的是 update 语句 //分页查询$sql "update ctfshow_user set pass {$password} where username {$username};"; password 和 username 可控&#xff0c;注入地方还是在 …

laravel发送邮件的使用方法?有哪些技巧?

laravel发送邮件怎么实现&#xff1f;如何使用Laravel发送邮件&#xff1f; Laravel&#xff0c;作为一个流行的PHP框架&#xff0c;提供了强大且灵活的邮件发送功能&#xff0c;使得开发者可以轻松地集成邮件服务到他们的应用中。AokSend将详细介绍如何在Laravel中使用larave…

conda虚拟环境中安装cuda和cudnn

目录 一、cuda安装步骤 1&#xff09;cuda的安装 1、查看conda支持的cuda版本 2、下载cuda并安装cuda 2&#xff09;cudnn的安装 1、查看cuda对应的cudnn版本 2、下载cudnn并安装 二、torch的安装和tensorflow的安装 1&#xff09;安装tensorflow 1、确定安装版本并安…

Java入门:06.Java中的方法--进阶02.03

2 可变参数 方法调用时&#xff0c; 传递的实参数量&#xff0c;是由被调用方法的参数列表数列决定的。 一般来讲&#xff0c;传递的实参数量必须与形参变量数量相同&#xff0c;但是也有一种特殊的参数&#xff0c;允许调用时传递的实参数量是可变&#xff0c;这种参数就称为…

《黑神话·悟空》游戏开发教程来了,全部开源。。。

黑神话悟空游戏&#xff0c;官网说明使用UE5引擎开发. 这篇文章发一个UE5游戏开发的系统教程。 1 开发悟空游戏的UE5 UE5&#xff0c;即 Unreal Engine 5&#xff0c;是由 Epic Games 开发的一款高度先进的游戏引擎。 UE5 是 Unreal Engine 系列的最新版本&#xff0c;它提供…

Qt第十八章 XML和Json格式解析

文章目录 JSON格式解析Json生成案例 XML简介与HTML的区别格式XML解析流的方式DOM XML生成 JSON与XML的区别比较 JSON 格式 JSON是一个标记符的序列。这套标记符包含六个构造字符、字符串、数字和三个字面名 六个构造字符 开始和结束数组&#xff1a;[ ]开始和结束对象&#x…

应用商场的搭建

前言&#xff1a; 使用mysql做主从设置&#xff0c;redis数据库做热门访问数据库。mycat读写分离&#xff0c;zookeeperkafka做数据收集&#xff0c;使用三台虚拟机 关掉防火墙和selinux,做时间同步 192.168.121.11 node1 mycat.example.com 192.168.121.21 node2 …

提高LabVIEW电机控制速度

在LabVIEW中执行绝对位移命令时&#xff0c;其运行时间主要与以下电机参数有关&#xff1a; 电机加速度和减速度&#xff1a; 加速度和减速度参数决定了电机从静止到达到目标速度&#xff0c;以及从高速到停止的时间。这些参数直接影响电机响应的迅速程度。如果加速度和减速度设…

如何使用ssm实现基于bs模式的医院在线挂号预约系统的设计与实现

TOC ssm072基于bs模式的医院在线挂号预约系统的设计与实现jsp 第1章 绪论 1.1 课题背景 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人…

B/S架构和C/S架构的区别

B/S架构、C/S架构区别 1. B/S架构 1.1 什么是B/S架构 B/S架构的全称为Browser/Server&#xff0c;即浏览器/服务器结构。Browser指的是Web浏览器&#xff0c;极少数事务逻辑在前端实现&#xff0c;但主要事务逻辑在服务器端实现。B/S架构的系统无须特别安装&#xff0c;只需要…

实战OpenCV之绘制图形

基础入门 OpenCV除了用于图像显示之外&#xff0c;还提供了一系列接口和工具&#xff0c;以帮助开发者在图像上绘制各种图形。这里的图形包括&#xff1a;直线、矩形、圆形、椭圆、多边形等。另外&#xff0c;OpenCV还支持在图像上添加文字&#xff0c;对多张图像进行叠加操作。…

NACOS 2.4.1如何开启账号密码登录功能

Nacos Server 2.4.0+已正式发布取消管理员用户 nacos 的默认密码支持初始化指定密码。 本章教程,主要介绍如何给nacos加上鉴权,支持通过用户名和密码的方式进行访问nacos页面。 NACOS 2.4.1版本下载地址:https://github.com/alibaba/nacos/releases/tag/2.4.1 1、开启认证功…

驱动:insmod

一、驱动模块的加载 1. 静态 编译进内核2. 动态 编译成模块 如下&#xff1a; 解决方法 结果 led电灯例子 创建一个led.c 修改Makefile、Kconfigmake modulescp drivers/char/led.ko /home/linux/nfs/rootfs开发板 insmod led.ko去 /home/linux/nfs/rootfs 上 写程序.carm-l…

每日掌握一个科研绘图|区域图·24-08-23

小罗碎碎念 为了方便大家获取对应的代码和源数据&#xff0c;从本期推文开始&#xff0c;我将把对应的文件上传至Github仓库&#xff0c;感兴趣的同学自行获取。 仓库地址 https://github.com/Lxltxpku/Share 一、区域图 区域图是一种数据可视化工具&#xff0c;它通过在坐标…

嵌入式人工智能ESP32(7-OLED显示中英文)

1、OLED显示英文 我们之前通过树莓派开发板做过OLED显示&#xff0c;这里就不再赘述OLED显示屏了。直接上接线图与代码。 &#xff08;1&#xff09;Adafruit Adafruit是一家成立于2005年的私营企业&#xff0c;主要业务是设计和制造开源电子硬件。Adafruit在美国设计和制造其…

全栈杂谈第一期:什么是计算机中的并发

什么是计算机中的并发 计算机中的“并发”是一个听起来很复杂的词汇&#xff0c;但我们可以把它简单理解为“同时做很多事情”。想象一下你正在做晚饭&#xff1a;你可以在等水烧开的时候切菜&#xff0c;还可以在等待炖汤时洗碗。尽管你只有一双手&#xff0c;但通过合理安排…

芋道cloud v2.2.0发布,支持模块选配,丢弃简易版

大家知道&#xff0c;芋道cloud拥有商城、CRM、ERP、微信相关等模块&#xff0c;很显然我们在日常开发中不可能一个项目同时拥有这么多模块。但是从gitee上获取代码的时候&#xff0c;只提供了简易版和完整版。简易版本只有最基础的功能&#xff0c;如果想要微信相关的模块&…

浅谈Llama3.1,从结构、训练过程、影响到数据合成

Llama3.1系列模型的开源&#xff0c;真让大模型格局大震&#xff0c;指标上堪比最好的闭源模型比如GPT 4o和Claude3.5&#xff0c;让开源追赶闭源成为现实。 这里给大家分享一篇俊林兄&#xff08;知乎张俊林&#xff09;的一篇解读&#xff0c;主要对LLaMA3.1的模型结构、训练…