基于FreeRTOS+STM32的多功能手表(单片机毕设)

前言

源码都在资源包里面,下载即可烧录,需要的自行下载即可:

目录

一、项目演示

二、硬件实现

1. 硬件清单

2. 原理图

3. 实现功能

三、软件实现

1. FreeRTOS知识

2. 实现思路

3. 代码解析

3.1 初始化GPIO引脚、配置时钟

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

3.3 系统默认创建的 defaultTaskHandle

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

3.4.1 ShowTimeTask

3.4.2 ShowMenuTask

3.4.3 ShowCalendarTask

3.4.4 ShowFlashLightTask

3.4.5 ShowDHT11Task

3.4.6 ShowClockTimeTask

3.4.7 ShowSetting_Task

3.5 按键中断回调函数

3.6 蜂鸣器实现按键音效

3.6.1 buzzer_init

3.6.2 SystemSoundTimer_Func( TimerHandle_t xTimer )

3.6.3 void buzzer_buzz(int freq, int time_ms)


一、项目演示

多功能手环项目,包括多级菜单、温湿度显示、闹钟设置、手电筒功能、日历显示、设置菜单。

演示视频:

多功能手表 (万年历)

二、硬件实现

1. 硬件清单

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

2. 原理图

在这里原理图用嘉立创EDA绘制:

3. 实现功能

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

三、软件实现

1. FreeRTOS知识

项目涉及到的RTOS知识如下:

  • 任务管理(创建,删除,状态转换)
  • 软件定时器(创建,启动,停止)
  • 队列(创建,写队列,读队列)
  • 二值信号量(创建,give,take)
  • 中断管理(中断与任务通信)
  • 资源管理(主要是堆栈大小的处理)

2. 实现思路

   在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;
        }
    }
}

3. 代码解析

3.1 初始化GPIO引脚、配置时钟

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

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

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

3.3 系统默认创建的 defaultTaskHandle

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

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

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

3.4.1 ShowTimeTask
  • 1、初始化OLED屏幕,调用 u8g2 库。
  • 2、显示UI界面。
  • 3、读取队列,如果读不到就阻塞,直到中断中写入队列之后唤醒任务。
  • 4、根据不同的按键处理不同的事件。

代码如下:

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;
        }            
    }
}
3.4.2 ShowMenuTask
  • 1、挂起其他的任务,因为默认上电之后,是显示时间的。
  • 2、初始化OLED屏幕,调用 u8g2 库。
  • 3、不断的循环显示时间。
  • 4、如果队列读到有效按键数据,就跳转到菜单界面。

代码如下:

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;
        }
    }
}
3.4.3 ShowCalendarTask
  • 1、初始化OLED屏幕,调用 u8g2 库。
  • 2、根据全局变量month以及line_pos,显示出这个月份的特定日历。
  • 3、读取按键中断队列,进入阻塞状态,直到按键中断写入队列,唤醒任务。
  • 4、根据读取到的键值去进行不同事件的处理。

代码如下:

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));
        }
    }
}
3.4.4 ShowFlashLightTask
  • 1、初始化OLED屏幕,调用 u8g2 库。
  • 2、显示手电筒图标。
  • 3、读取按键中断队列,进入阻塞状态,直到按键中断写入队列之后,唤醒任务。
  • 4、根据不同的键值进行不同事件的处理。

代码如下:

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);
    }
}
3.4.5 ShowDHT11Task
  • 1、温湿度模块初始化。
  • 2、初始化OLED屏幕,调用 u8g2 库。
  • 3、不断循环,读取温湿度,并把数据刷新到OLED屏幕上面。
  • 4、直到从队列中读取到有用的数据,切换回去多级菜单界面。

代码如下:

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));
        }
    }
}
3.4.6 ShowClockTimeTask
  • 1、初始化OLED屏幕,调用 u8g2 库。
  • 2、显示当前的闹钟界面。
  • 3、读取按键中断队列,等待按键中断唤醒。
  • 4、根据不同的键值处理不同的事件。(左移、右移、确认、退出)
  • 5、如果确认了开始计时,会启动g_Clock_Timer定时器,时间到了之后,蜂鸣器发生播报,并且回归最开始的循环。

代码如下:

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);
            }
        }
    }
}
3.4.7 ShowSetting_Task
  • 1、初始化OLED屏幕,调用 u8g2 库。
  • 2、显示设置界面,如何读取按键中断队列,进入阻塞状态,等待唤醒。
  • 3、根据读取到的键值进行不同的事件处理。

代码如下:

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);
        }
    }
}

3.5 按键中断回调函数

  • 1、我们给按键中断回调函数里面加了一点点延迟,可以减小按键抖动带来的影响。
  • 2、 往g_xQueueMenu队列里面,不断的写入按键按下的数据,这里由于队列是一位数据的,面对按键抖动的情况,在任务读取队列的时候,中断虽然会进行写队列,但是都是不成功的,有效的减少了按键抖动。

代码如下:

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;
        }
    }
}

3.6 蜂鸣器实现按键音效

3.6.1 buzzer_init
  • 1、初始化蜂鸣器,将蜂鸣器引脚配置为定时器pwm输出模式,因为无源蜂鸣器的发声,需要给特定的频率的pwm。
  • 2、创建定时器 SystemSound,实现如何只b一声,实现按键的音效。

代码如下:

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

    g_TimerSound = xTimerCreate( "SystemSound", 
                            200,
                            pdFALSE,
                            NULL,
                            SystemSoundTimer_Func);
}
3.6.2 SystemSoundTimer_Func( TimerHandle_t xTimer )
  • 这个定时器的任务就是关闭pwm生成,从而导致蜂鸣器停止发生。 

代码如下:

static void SystemSoundTimer_Func( TimerHandle_t xTimer )
{
    PassiveBuzzer_Control(0);
}
3.6.3 void buzzer_buzz(int freq, int time_ms)
  • 1、如果 power_button == 0,我们就可以触发按键音效,这个值可以在设置菜单里面设置。
  • 2、我们先开启pwm生成,这个时候蜂鸣器会叫,同时开启了g_TimerSound定时器,0.2s之后就会去关闭掉蜂鸣器,从而实现了按键音效的功能。

代码如下:

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

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱嵌入式的小佳同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值