很久没有更新了,最近情绪有点低落(感情受挫呜呜呜),遂来此跟新一下博客,还是经典存货HH
该项目完结于半年以前,所以有些细节现在记不清了,其中关键的最佳按键消抖技术会在下一篇博客里专门讲解。
本文完全原创,初始版本为课程仿真报告,转载与作业使用请务必注明出处!
目 录
第一章 整体方案及性能指标
本工程采用 PROTEUS 仿真和 CUBEMX 生成初始代码, KEIL DEBUG+VSCODE 编译 进行开发使用HAL 库(由于已经掌握标准库用HAL 库练手、易于移植)和FREERTOS 实现 OLED PROTEUS 内部编号 OLED12863I2C)的多级菜单(采用 U8G2 图形库),功能包含通过外部中断进行操作, RTOS 保证实时性,实现多任务系统,蜂鸣器在音乐菜单进行简单音乐播放(共两首用于展示,可自由添加),同时实现流水灯(共三个动画用于展示,其余自由添加)操作与变换。
实物硬件平台为 STM32H723ZGT6
仿真硬件平台为 STM32F103C8
硬件问题:电路板上的元件可能会损坏或者失效,导致设备无法正常工作。此外,电路板的设计可能存在问题,例如电路板上的元件之间的连接可能不正确,或者电路板上的元件可能不兼容。
软件问题:软件可能存在错误或者缺陷,导致设备无法正常工作,最常见的是任务调度的实时性,按键检测的实时性,任务间通信的解耦
供电问题:设备可能无法正常工作,因为它没有足够的电源。例如,如果使用的是电池供电,电池可能会耗尽电量,导致设备无法正常工作。
见流程图
播放流畅度(人体难以感知变换),按键消抖(99%消抖,日常实验没有一次多跳转),响应时间<10ms,CPU 占用率(使用 FREERTOS 的反馈函数打印),任务调度,信息传输,屏幕刷新速度。CPU 占用率(无论是音乐的重装还是 LED 变换对CPU 占用率都极低,所以主要还是更新屏幕更耗时间)堆栈使用优先级等调用串口与内部函数生成实时调用与堆栈占用
Task name Run Count usage
PrintCPU 41658 13%
defaultTask 58020 18%
Tmr Svc 0 <1 %
IDLE 0 <1%
LEDPLAYTask 0 <1%
MUSICPLAYTask 0 <1%
OLEDSendBuffer 205789 67%
InitTask 74 <1%
EXITCHECK 0 <1%
Task Task status Priority Number of the remaining
stack task PrintCPU X 40 187 7
OLEDSendBuffer R 9 433 4
defaultTask R 8 245 1
Tmr Svc R 2 246 9
IDLE R 0 118 8
MUSICPLAYTask B 993 3
LEDPLAYTask B 9 483 2
InitTask D 40 198 6
EXITCHECK S 11 231 5
其中音乐播放因为要存储歌曲,占用堆栈较大,LED要存储播放效果,堆栈大一些。初始化任务只在一开始运行,运行结束之后就会关闭。
第二章 仿真原理
STM32单片机主要是由意法半导体公司设计的微控制器,其具有低功耗、低成本和高性能的特点,适用于嵌入式应用。其采用ARM Cortex-O内核,根据其内核架构的不同,可以将其分成一系列产品,当前主流的产品包括STM32F0、STM32F1、STM32F3,具有超低功耗的产品包括STM32L0、STM32L1、STM32L4等。由于STM32单片机中应用的内核具有先进的架构,使其在实施性能以及功耗控制等方面都具有较强表现,因此在整合和集成方面就有较大的优势,开发起来较为方便,该类型的单片机能非常迅速地实现开发和投入市场,当前市场中这种类型的单片机十分常见,类型多样,包括基础型、智能型和高级型等,应用都比较广泛。
说明:总线与外设均以 H723 为标准
硬件使用类:
- GPIO
GPIO(AHB4)输出(在F1 上使用模拟I2C-开漏输出—PROTUES不支持硬件I2C)(点亮 LED)GPIO 输入(外部中断)挂在于 APB1 总线
GPIO配置
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, LED2_Pin|LED3_Pin, GPIO_PIN_SET);
/*Configure GPIO pins : PAPin PAPin PAPin PAPin */
GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin|KEY3_Pin|KEY4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LED1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PAPin PAPin */
GPIO_InitStruct.Pin = LED2_Pin|LED3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);
HAL_NVIC_SetPriority(EXTI2_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(EXTI2_IRQn);
HAL_NVIC_SetPriority(EXTI3_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
HAL_NVIC_SetPriority(EXTI4_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(EXTI4_IRQn);
}
LED动画
#include "stm32h7xx.h"
#include "led.h"
#include "main.h"
#include "freertos.h"
#include "menu.h"
void led_cartoon1(void)
{
for(;;)
{
LED1(1);
LED2(1);
LED3(1);
osDelay(30);
if(led_play_use==3 || led_play_use==2)
{
LEDOFF;
break;
}
LED1(0);
LED2(1);
LED3(1);
osDelay(80);
if(led_play_use==3 || led_play_use==2)
{
LEDOFF;
break;
}
LED1(1);
LED2(0);
LED3(1);
osDelay(10);
LED1(1);
LED2(1);
LED3(0);
osDelay(100);
if(led_play_use==3 || led_play_use==2)
{
LEDOFF;
break;
}
}
xSemaphoreGive(xSemaphore);//释放互斥量
if(led_playflag!=1)
{
LEDOFF;
}
}
void led_cartoon2(void)
{
for(;;)
{
LED1(1);
LED2(0);
LED3(0);
osDelay(50);
if(led_play_use==3 || led_play_use==1)
{
LEDOFF;
break;
}
LED1(0);
LED2(1);
LED3(0);
osDelay(50);
if(led_play_use==3 || led_play_use==1)
{
LEDOFF;
break;
}
LED1(0);
LED2(0);
LED3(1);
osDelay(10);
LED1(1);
LED2(1);
LED3(1);
osDelay(30);
}
xSemaphoreGive(xSemaphore);//释放互斥量
if(led_playflag!=2)
{
LEDOFF;
}
}
void led_cartoon3(void)
{
for(;;)
{
LED1(1);
LED2(1);
LED3(1);
osDelay(10);
LED1(0);
LED2(0);
LED3(0);
osDelay(10);
LED1(1);
LED2(0);
LED3(1);
if(led_play_use==2 || led_play_use==1)
{
LEDOFF;
break;
}
osDelay(50);
LED1(0);
LED2(1);
LED3(0);
if(led_play_use==2 || led_play_use==1)
{
LEDOFF;
break;
}
osDelay(35);
}
xSemaphoreGive(xSemaphore);//释放互斥量
if(led_playflag!=3)
{
LEDOFF;
}
}
- TIM3(APB1)时钟进行 PWM 输出,使蜂鸣器播放音乐
# include "buzzed.h"
#include "stm32h7xx.h"
#include "tim.h"
#include "menu.h"
extern volatile char music_flag,music_playflag,music_playflag_pre;
extern volatile char led_flag,led_playflag;
const int wind_rise[]=
{
M2,50,M2,50,M1,25,M2,50,M2,50,M1,25,M2,50,M3,50,M5,50,M3,50, M2,50,M2,50,M1,25,M2,50,M2,50,M1,25,M2,25,M3,25,M2,25,M1,25,L6,100,Z0,10,
//迈出车站的前一刻 竟有些犹豫
M2,50,M2,50,M1,25,M2,50,M2,50,M1,25,M2,50,M3,50,M5,50,M3,50, M2,50,M2,50,M3,25,M2,50,M1,50,M2,100,Z0,50,
//不仅笑着这近乡情怯 仍无法避免
M2,50,M2,50,M1,25,M2,50,M2,50,M1,25,M2,50,M3,50,M5,50,M3,50, M2,50,M2,50,M3,25,M2,50,M1,50,L6,100,Z0,10,
};
void Wind_Rises(void)
{
int length = sizeof(wind_rise)/sizeof(wind_rise[0]);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
for(int i=0;i<(length/2) && music_playflag==1;i++)
{
if(playflag==2)
{
buzzer_off();
break;
}
buzzer_on(wind_rise[i*2],260);
}
const int solitary_brave[]=
{
M6,50,M7,50,H1,50,H2,50,M7,50,H1,50,H1,100,Z0,10, //爱你孤身走暗巷
H1,50,M7,50,H1,50,H2,50,M7,50,H1,50,H1,100,Z0,10, //爱你不跪的模样
H1,50,H2,50,H3,50,H2,50,H3,50,H2,50,H3,100,H3,50,H3,50,H2,50,H3,100,H5,100,H3,100,Z0,10 //爱你对峙过绝望不肯哭一场
};
void Solitary_brave(void)
{
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
int length = sizeof(solitary_brave)/sizeof(solitary_brave[0]);
for(int i=0;i<(length/2) && music_playflag==2;i++)
{
if(playflag==1)
{
buzzer_off();
break;
}
buzzer_on(solitary_brave[i*2],500);
osDelay(5*solitary_brave[i*2+1]);
}
if(music_playflag!=2)
{
buzzer_off();
}
}
void buzzer_on(uint16_t psc, uint16_t pwm)
{
__HAL_TIM_PRESCALER(&htim3, psc);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwm);
}
void buzzer_off(void)
{
HAL_TIM_PWM_Stop_IT(&htim3, TIM_CHANNEL_1);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 0);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);
osDelay(1);
}
TIM4(APB1)+USART1(APB2)进行 CPU 利用率和任务调度
串口配置
#include "usart.h"
/* USER CODE BEGIN 0 */
int fputc(int ch, FILE *f)
{
uint8_t temp[1] = {ch};
HAL_UART_Transmit(&huart1, temp, 4, 10);//huart1需要根据你的配置修改
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
/* USER CODE END 0 */
UART_HandleTypeDef huart1;
/* USART1 init function */
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
if(uartHandle->Instance==USART1)
{
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInitStruct.Usart16ClockSelection = RCC_USART16910CLKSOURCE_D2PCLK2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
进行统计并发送至上位机
SYSTICK 作为 FREERTOS 时钟基
HAL 时钟基为 TIM1(挂在于 APB2 总线)
硬件I2C1(FAST MODE PLUS)(APB1)
软件使用类:
HAL 库和CUBEMX 进行可视化配置(含配置 FREERTOS)
引脚配置和中断配置
-
- FREERTOS 任务调度
核心代码(去除部分代码减小篇幅)如下:
主函数
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
MX_TIM4_Init();
osKernelInitialize(); /* Call init function for freertos objects (in freertos.c) */
MX_FREERTOS_Init();
/* Start scheduler */
osKernelStart();
/* 任务调度开启,不会执行到后面We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
}
}
FREERTOS任务文件:
#include "oled_app.h"
#include "driver_oled.h"
#include "stm32h7xx.h"
#include "u8g2.h"
#include "MENU.H"
#include "gpio.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
#include "queue.h"
#include "semphr.h"
#include "tim.h"
SemaphoreHandle_t xSemaphore = NULL;
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 256 * 4,
.priority = (osPriority_t) osPriorityLow,
};
/* Definitions for LED_PLAY_Task */
osThreadId_t LED_PLAY_TaskHandle;
const osThreadAttr_t LED_PLAY_Task_attributes = {
.name = "LED_PLAY_Task",
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityLow1,
};
/* Definitions for MUSIC_PLAY_Task */
osThreadId_t MUSIC_PLAY_TaskHandle;
const osThreadAttr_t MUSIC_PLAY_Task_attributes = {
.name = "MUSIC_PLAY_Task",
.stack_size = 1024 * 4,
.priority = (osPriority_t) osPriorityLow1,
};
/* Definitions for OLED_SendBuffer */
osThreadId_t OLED_SendBufferHandle;
const osThreadAttr_t OLED_SendBuffer_attributes = {
.name = "OLED_SendBuffer",
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityLow1,
};
/* Definitions for EXIT_CHECK */
osThreadId_t EXIT_CHECKHandle;
const osThreadAttr_t EXIT_CHECK_attributes = {
.name = "EXIT_CHECK",
.stack_size = 256 * 4,
.priority = (osPriority_t) osPriorityLow3,
};
/* Definitions for InitTask */
osThreadId_t InitTaskHandle;
const osThreadAttr_t InitTask_attributes = {
.name = "InitTask",
.stack_size = 256 * 4,
.priority = (osPriority_t) osPriorityHigh,
};
/* Definitions for Print_CPU */
osThreadId_t Print_CPUHandle;
const osThreadAttr_t Print_CPU_attributes = {
.name = "Print_CPU",
.stack_size = 256 * 4,
.priority = (osPriority_t) osPriorityHigh,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
void MyTask2(void *argument);
void MyTask3(void *argument);
void MyTask1(void *argument);
void MyTask4(void *argument);
void MyInitTask(void *argument);
void StartTask07(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* Hook prototypes */
void configureTimerForRunTimeStats(void);
unsigned long getRunTimeCounterValue(void);
/* USER CODE BEGIN 1 */
/* Functions needed when configGENERATE_RUN_TIME_STATS is on */
__weak void configureTimerForRunTimeStats(void)
{
CPU_RunTime=0;
}
__weak unsigned long getRunTimeCounterValue(void)
{
return CPU_RunTime;
}
/* USER CODE END 1 */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
sys_cache_enable();
HAL_UART_Transmit(&huart1,(uint8_t*)"HAL_TIM_Base_Start_IT \r\n", 20, HAL_MAX_DELAY);
HAL_TIM_Base_Start_IT(&htim4);
OLED_Init();
OLED_Clear();
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 0);
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* creation of LED_PLAY_Task */
LED_PLAY_TaskHandle = osThreadNew(MyTask2, NULL, &LED_PLAY_Task_attributes);
/* creation of MUSIC_PLAY_Task */
MUSIC_PLAY_TaskHandle = osThreadNew(MyTask3, NULL, &MUSIC_PLAY_Task_attributes);
/* creation of OLED_SendBuffer */
OLED_SendBufferHandle = osThreadNew(MyTask1, NULL, &OLED_SendBuffer_attributes);
/* creation of EXIT_CHECK */
EXIT_CHECKHandle = osThreadNew(MyTask4, NULL, &EXIT_CHECK_attributes)
/* creation of InitTask */
InitTaskHandle = osThreadNew(MyInitTask, NULL, &InitTask_attributes);
/* creation of Print_CPU */
Print_CPUHandle = osThreadNew(StartTask07, NULL, &Print_CPU_attributes);
/* USER CODE BEGIN RTOS_THREADS */
Exit_Queue = xQueueCreate(1,2);
xSemaphore = xSemaphoreCreateBinary();
if(xSemaphore == NULL)
{
while(1)
{
printf("error");/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
HAL_Delay(100);
}
}
/* 先释放一次,将初始值改为 1,利用二值信号量实现互斥功能 */
xSemaphoreGive(xSemaphore);
}
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;)
{
portYIELD_WITHIN_API();
// osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
/* USER CODE BEGIN Header_MyTask2 */
/**
* @brief Function implementing the LED_PLAY_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_MyTask2 */
void MyTask2(void *argument)
{
/* USER CODE BEGIN MyTask2 */
/* Infinite loop */
for(;;)
{
led_play();
osDelay(1);
}
/* USER CODE END MyTask2 */
}
/* USER CODE BEGIN Header_MyTask3 */
/**
* @brief Function implementing the MUSIC_PLAY_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_MyTask3 */
void MyTask3(void *argument)
{
/* USER CODE BEGIN MyTask3 */
static cnt =0;
/* Infinite loop */
for(;;)
{
music_play();
if(cnt<3)
{
cnt++;
}
else{
cnt = 0;
osDelay(1);
}
}
/* USER CODE END MyTask3 */
}
/* USER CODE BEGIN Header_MyTask1 */
/**
* @brief Function implementing the OLED_SendBuffer thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_MyTask1 */
void MyTask1(void *argument)
{
/* USER CODE BEGIN MyTask1 */
/* Infinite loop */
for(;;)
{
int j=0;
j++;
u8g2_SendBuffer(&u8g2);
if (func_index != last_index)
{
current_operation_index = table[func_index].current_operation;//执行当前操作函数
u8g2_ClearBuffer(&u8g2);
(*current_operation_index)();
u8g2_SendBuffer(&u8g2);
last_index = func_index;
}
xSemaphoreTake(xSemaphore, 5);//等待互斥量
switch(music_playflag)
{
case 0:buzzer_off();
}
switch(led_playflag)
{
case 0:LEDOFF;
}
xSemaphoreGive(xSemaphore);
osDelay(4);//根据CPU占用率统计知这个任务占用高达90%改良后占60%
}
/* USER CODE END MyTask1 */
}
/* USER CODE BEGIN Header_MyTask4 */
/**
* @brief Function implementing the EXIT_CHECK thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_MyTask4 */
void MyTask4(void *argument)
{
//line1: /* USER CODE BEGIN MyTask4 */
uint16_t GPIO_Pin;
static Pin4_cnt=0;
/* Infinite loop */
for(;;)
{
if(xQueueReceive(Exit_Queue,&GPIO_Pin,100)==pdTRUE)
{
int cnt;
cnt = 0;
line1:
while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,GPIO_Pin) == RESET)
{
cnt++;
osDelay(10);
}
if(cnt<3)
{
vTaskSuspend(EXIT_CHECKHandle);
goto line1;
}
switch(GPIO_Pin)
{
case GPIO_PIN_1:
func_index = table[func_index].up;// 向上翻
break;
case GPIO_PIN_2:
func_index = table[func_index].down; //向下翻
break;
case GPIO_PIN_3:
func_index = table[func_index].enter;//确认
break;
case GPIO_PIN_4:
Pin4_cnt++;
if(Pin4_cnt == 1)
{
vTaskDelete( Print_CPUHandle );
}
if(Pin4_cnt >1)
{
Pin4_cnt = 0;
taskENTER_CRITICAL(); /* 进入临界区 */
Print_CPUHandle = osThreadNew(StartTask07, NULL, &Print_CPU_attributes);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
break;
}
}
vTaskSuspend(EXIT_CHECKHandle);
}
/* USER CODE END MyTask4 */
}
/* USER CODE BEGIN Header_MyInitTask */
/**
* @brief Function implementing the InitTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_MyInitTask */
void MyInitTask(void *argument)
{
/* USER CODE BEGIN MyInitTask */
/* Infinite loop */
u8g2Init(&u8g2);//
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);//要用创建任务式解决问题 此处延时有问题
u8g2_ClearBuffer(&u8g2);
vTaskSuspend(LED_PLAY_TaskHandle);//挂起任务
vTaskSuspend(MUSIC_PLAY_TaskHandle);//挂起任务
vTaskSuspend(EXIT_CHECKHandle);//挂起任务
vTaskDelete(InitTaskHandle);//删除初始化任务
/* USER CODE END MyInitTask */
}
/* USER CODE BEGIN Header_StartTask07 */
/**
* @brief Function implementing the Print_CPU thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask07 */
void StartTask07(void *argument)
{
/* USER CODE BEGIN StartTask07 */
char *pbuffer = (char *)malloc(1, 1024);
char *qbuffer = (char *)malloc(1, 1024);
/* Infinite loop */
for(;;)
{
vTaskGetRunTimeStats(pbuffer);
printf("Task name Run Count usage\r\n");
printf("%s", pbuffer);
osDelay(600);
vTaskList(qbuffer);
printf("Task Task status Priority Number of the remaining stack task\r\n");
printf("%s", qbuffer);
free(pbuffer);
free(qbuffer);
osDelay(600);
}
/* USER CODE END StartTask07 */
}
通过挂起(唤醒)任务和任务阻塞延时以及任务优先级实现极佳的任务调度,此外通过消息队列和单任务内全局变量实现稳定的通信效果
采用较为简单的数组法(缺点是不易于增加菜单项)具体代码实现:
#include "driver_oled.h"
//#include "ascii_font.c"
#include "MENU.H"
#include "stm32h7xx.h"
#include "OLED_APP.H"
#include "freertos.h"
#include "main.h"
#include "PERSONAL_FONT.H"
volatile char music_flag = 0,music_playflag = 0,music_playflag_pre = 0,playflag = 0;;
volatile char led_flag = 0,led_playflag = 0,led_play_use = 0;
key_table table[]=
{
//第0层
{0,0,0,1,(*fun_0)},
//第1层 流水灯 音乐 返回
{1,3,2,4,(*fun_a1)},
{2,1,3,8,(*fun_b1)},
{3,2,1,0,(*fun_c1)},
//第2层 一、二、三动画 返回
{4,7,5,11,(*fun_a21)},
{5,4,6,11,(*fun_a22)},
{6,5,7,11,(*fun_a23)},
{7,6,4, 1,(*fun_a24)},
//一、二音乐 返回
{8,10,9,14,(*fun_b21)},
{9,8,10,14,(*fun_b22)},
{10,9,8,2,(*fun_b23)},
//第3层
//播放 暂停
//动画
{11,13,12,17,(*fun_a2play)},
{12,11,13,18,(*fun_a2stop)},
{13,12,11,5,(*fun_a2return)},
//音乐
{14,16,15,19,(*fun_b2play)},
{15,14,16,20,(*fun_b2stop)},
{16,15,14,9,(*fun_b2return)},
//第四层 应用层
{17,11,11,11,(*fun_a2play_app)},
{18,11,11,12,(*fun_a2stop_app)},
{19,14,14,14,(*fun_b2play_app)},
{20,14,14,15,(*fun_b2stop_app)},
};
/*********第0层***********/
void fun_0()
{
u8g2_DrawStr(&u8g2,10,16,"QJC RTOS MP3");
// u8g2_DrawStr(&u8g2,10,32,"QJC MP3 RTOS");
// // u8g2_DrawStr(&u8g2,10,48,"");
u8g2_DrawStr(&u8g2,52,32,"Enter to");
u8g2_DrawStr(&u8g2,68,48,"Start");
u8g2_DrawXBM(&u8g2,0, 16, 48, 48, LUNA);
}
/*********第1层***********/
void fun_a1()
{
u8g2_DrawStr(&u8g2,0,16,">");
u8g2_DrawStr(&u8g2,24,16,"waterfall light");
u8g2_DrawStr(&u8g2,24,32,"Music");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16, 0x76);/* dec 0x3032 电视图标 */
u8g2_DrawGlyph(&u8g2,8,32, 0x69);/* dec 0x69 音乐图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
void fun_b1()
{
u8g2_DrawStr(&u8g2,0,32,">");
u8g2_DrawStr(&u8g2,24,16,"waterfall light");
u8g2_DrawStr(&u8g2,24,32,"Music");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16, 0x76);/* dec 0x3032 电视图标 */
u8g2_DrawGlyph(&u8g2,8,32, 0x69);/* dec 0x69 音乐图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
void fun_c1()
{
u8g2_DrawStr(&u8g2,0,48,">");
u8g2_DrawStr(&u8g2,24,16,"waterfall light");
u8g2_DrawStr(&u8g2,24,32,"Music");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16, 0x76);/* dec 0x3032 电视图标 */
u8g2_DrawGlyph(&u8g2,8,32, 0x69);/* dec 0x69 音乐图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
/*********第2层***********/
void fun_a21()
{
u8g2_DrawStr(&u8g2,0,16,">");
u8g2_DrawStr(&u8g2,16,16,"cartoon 1");
u8g2_DrawStr(&u8g2,16,32,"cartoon 2");
u8g2_DrawStr(&u8g2,16,48,"cartoon 3");
u8g2_DrawStr(&u8g2,16,64,"return");
led_flag = 1;
}
void fun_a22()
{
u8g2_DrawStr(&u8g2,0,32,">");
u8g2_DrawStr(&u8g2,16,16,"cartoon 1");
u8g2_DrawStr(&u8g2,16,32,"cartoon 2");
u8g2_DrawStr(&u8g2,16,48,"cartoon 3");
u8g2_DrawStr(&u8g2,16,64,"return");
led_flag = 2;
}
void fun_a23()
{
u8g2_DrawStr(&u8g2,0,48,">");
u8g2_DrawStr(&u8g2,16,16,"cartoon 1");
u8g2_DrawStr(&u8g2,16,32,"cartoon 2");
u8g2_DrawStr(&u8g2,16,48,"cartoon 3");
u8g2_DrawStr(&u8g2,16,64,"return");
led_flag = 3;
}
void fun_a24()
{
u8g2_DrawStr(&u8g2,0,64,">");
u8g2_DrawStr(&u8g2,16,16,"cartoon 1");
u8g2_DrawStr(&u8g2,16,32,"cartoon 2");
u8g2_DrawStr(&u8g2,16,48,"cartoon 3");
u8g2_DrawStr(&u8g2,16,64,"return");
}
void fun_b21()
{
u8g2_DrawStr(&u8g2,0,16,">");
u8g2_DrawStr(&u8g2,16,16,"Wind Rises");
u8g2_DrawStr(&u8g2,16,32,"Solitary brave");
u8g2_DrawStr(&u8g2,16,48,"return");
music_flag = 1;//第一首音乐
}
void fun_b22()
{
u8g2_DrawStr(&u8g2,0,32,">");
u8g2_DrawStr(&u8g2,16,16,"Wind Rises");
u8g2_DrawStr(&u8g2,16,32,"Solitary brave");
u8g2_DrawStr(&u8g2,16,48,"return");
music_flag = 2;//第二首音乐
}
void fun_b23()
{
u8g2_DrawStr(&u8g2,0,48,">");
u8g2_DrawStr(&u8g2,16,16,"Wind Rises");
u8g2_DrawStr(&u8g2,16,32,"Solitary brave");
u8g2_DrawStr(&u8g2,16,48,"return");
}
void fun_a2play()
{
u8g2_DrawStr(&u8g2,0,16,">");
u8g2_DrawStr(&u8g2,24,16,"play");
u8g2_DrawStr(&u8g2,24,32,"stop");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16,0x54);/* dec 0x45 播放图标 */
u8g2_DrawGlyph(&u8g2,8,32,0x45);/* dec 0x45 暂停图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
void fun_a2stop()
{
u8g2_DrawStr(&u8g2,0,32,">");
u8g2_DrawStr(&u8g2,24,16,"play");
u8g2_DrawStr(&u8g2,24,32,"stop");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16,0x54);/* dec 0x45 播放图标 */
u8g2_DrawGlyph(&u8g2,8,32,0x45);/* dec 0x45 暂停图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
void fun_a2return()
{
u8g2_DrawStr(&u8g2,0,48,">");
u8g2_DrawStr(&u8g2,24,16,"play");
u8g2_DrawStr(&u8g2,24,32,"stop");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16,0x54);/* dec 0x45 播放图标 */
u8g2_DrawGlyph(&u8g2,8,32,0x45);/* dec 0x45 暂停图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
void fun_b2play()
{
u8g2_DrawStr(&u8g2,0,16,">");
u8g2_DrawStr(&u8g2,24,16,"play");
u8g2_DrawStr(&u8g2,24,32,"stop");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16,0x54);/* dec 0x45 播放图标 */
u8g2_DrawGlyph(&u8g2,8,32,0x45);/* dec 0x45 暂停图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
void fun_b2stop()
{
u8g2_DrawStr(&u8g2,0,32,">");
u8g2_DrawStr(&u8g2,24,16,"play");
u8g2_DrawStr(&u8g2,24,32,"stop");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16,0x54);/* dec 0x45 播放图标 */
u8g2_DrawGlyph(&u8g2,8,32,0x45);/* dec 0x45 暂停图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
void fun_b2return()
{
u8g2_DrawStr(&u8g2,0,48,">");
u8g2_DrawStr(&u8g2,24,16,"play");
u8g2_DrawStr(&u8g2,24,32,"stop");
u8g2_DrawStr(&u8g2,24,48,"return");
u8g2_SetFont(&u8g2,u8g2_font_iconquadpix_m_all);
u8g2_DrawGlyph(&u8g2,8,16,0x54);/* dec 0x45 播放图标 */
u8g2_DrawGlyph(&u8g2,8,32,0x45);/* dec 0x45 暂停图标 */
u8g2_DrawGlyph(&u8g2,8,48,0x6E);/* dec 0x6E 返回图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
}
void fun_a2play_app()
{
u8g2_DrawStr(&u8g2,8,48,"ANY KEY RETURN");
u8g2_SetFont(&u8g2,u8g2_font_emoticons21_tr);
u8g2_DrawGlyph(&u8g2,50,24,0x36);/* dec 微笑图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
led_play_use = led_flag;
// led_playflag = led_flag;
vTaskResume(LED_PLAY_TaskHandle);//恢复任务
}
void fun_a2stop_app()
{
u8g2_DrawStr(&u8g2,8,48,"ANY KEY RETURN");
u8g2_SetFont(&u8g2,u8g2_font_emoticons21_tr);
u8g2_DrawGlyph(&u8g2,50,24,0x35);/* dec 笑图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
led_play_use = 0;
led_playflag = 0;
vTaskSuspend(LED_PLAY_TaskHandle);
// led_play();
}
void fun_b2play_app()
{
u8g2_DrawStr(&u8g2,8,48,"ANY KEY RETURN");
u8g2_SetFont(&u8g2,u8g2_font_emoticons21_tr);
u8g2_DrawGlyph(&u8g2,50,24,0x36);/* dec 微笑图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
playflag = music_flag;
// music_playflag = music_flag;//确认播放
vTaskResume(MUSIC_PLAY_TaskHandle);
// music_play();//此处不直接调用歌曲是为了方便FREERTOS的部署
}
void fun_b2stop_app()
{
u8g2_DrawStr(&u8g2,8,48,"ANY KEY RETURN");
u8g2_SetFont(&u8g2,u8g2_font_emoticons21_tr);
u8g2_DrawGlyph(&u8g2,50,24,0x35);/* dec 笑图标 */
u8g2_SetFont(&u8g2,u8g2_font_DigitalDiscoThin_tf);
playflag = 0;
music_playflag = 0;
vTaskSuspend(MUSIC_PLAY_TaskHandle);
}
APP级
#include "driver_oled.h"
#include "stm32h7xx.h"
#include "menu.h"
#include "LED.h"
#include "buzzed.h"
void music_play(void)
{
music_playflag_pre = music_playflag;//更新
music_playflag = music_flag;
switch(music_playflag)
{
// case 0: buzzer_off();break;//放在OLED更新函数进行
case 1: Wind_Rises();break;
case 2: Solitary_brave();break;
}
}
void led_play(void)
{
xSemaphoreTake(xSemaphore, 5);//等待互斥量
led_playflag = led_flag;
switch(led_playflag)
{
// case 0:LEDOFF;break;
case 1:led_cartoon1();break;
case 2:led_cartoon2();break;
case 3:led_cartoon3();break;
default:xSemaphoreGive(xSemaphore);//释放互斥量
}
}
由于在 FREERTOS 里中断是一种特别的形式,需调用专门的API 函数,并且通信比较复杂,需要使用队列以下是核心代码:最优化按键消抖:使用外部中断后唤醒相应任务函数,在任务函数里调用OSDELAY进行消抖,此举比在中断内延时提高了中断响应,比定时器扫描减少了资源占用,提高了CPU运行效率。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static uint16_t GPIO_PIN_USE;
//确保是否产生了EXTI Line中断
uint32_t ulReturn;
// /* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
GPIO_PIN_USE = GPIO_Pin;
xQueueOverwriteFromISR(Exit_Queue,&GPIO_PIN_USE,&pxHigherPriorityTaskWoken);
xTaskResumeFromISR(EXIT_CHECKHandle);//任务恢复
//如果需要的话进行一次任务切换
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
外部中断的功能可以配置六个寄存器;
中断屏蔽寄存器(EXTI_IMR)
事件屏蔽寄存器(EXTI_EMR)
上升沿触发选择寄存器(EXTI_RTSR)
下降沿触发选择寄存器(EXTI_FTSR)
软件中断事件寄存器(EXTI_SWIER)
挂起寄存器(EXTI_PR)
EXTI支持配置20个中断和事件屏蔽位;
GPIO端口以下图的方式连接到16个外部中断/事件线上;EXTI_Line0 — EXTI_Line15;
EXTI_Line16 连接到PVD输出 ;
EXTI_Line17连接到RTC闹钟事件;
EXTI_Line18连接到USB唤醒事件;
EXTI_Line19连接到以太网唤醒事件(只适用于互联型产品);
EXTI的配置,EXTI_Trigger这里支持三种模式;
EXTI_Trigger_Rising 上升沿触发;
EXTI_Trigger_Falling 下降沿触发;
EXTI_Trigger_Rising_Falling 上升沿和下降沿都可以触发;
寄存器的操作
以下摘自《STM32参考手册》
产生中断的步骤,必须先配置好并使能中断线。根据需要的边沿检测设置2个触发寄存器,同时在**中断屏蔽寄存器(EXTI_IMR)的相应位写1允许中断请求。当外部中断线上发生了期待的边沿时,将产生一个中断请求,对应的挂起位也随之被置1。在挂起寄存器(EXTI_PR)的对应位写1,将清除该中断请求。
产生事件的步骤:必须先配置好并使能事件线。根据需要的边沿检测通过设置2个触发寄存器,同时在中断屏蔽寄存器(EXTI_IMR)**的相应位写1允许事件请求。当事件线上发生了需要的边沿时,将产生一个事件请求脉冲,对应的挂起位不被置1。通过在软件中断/事件寄存器写1,也可以通过软件产生中断/事件请求。
中断屏蔽寄存器(EXTI_IMR)
事件屏蔽寄存器(EXTI_EMR)
上升沿触发选择寄存器(EXTI_RTSR)
下降沿触发选择寄存器(EXTI_FTSR)
软件中断事件寄存器(EXTI_SWIER)
挂起寄存器(EXTI_PR)
STM32H7 的每个 IO 都可以作为外部中断的中断输入口
每个PIN 有一个外部中断线,两个相同的 PIN 号不可同时外部中断
以线 0 为例: 它对应了 GPIOA.0、 GPIOB.0、 GPIOC.0、 GPIOD.0、 GPIOE.0、GPIOF.0、 GPIOG.0,GPIOH.0,GPIOI.0,GPIOJ.0,GPIOK.0。而中断线每次只
能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。
一般步骤
使能 IO 口时钟
设置 IO 口模式,触发条件,开启 SYSCFG 时钟,设置 IO
口与中断线的映射关系
因为我们这里初始化的是 PA0,调用该函数后中断线 0 会自动连接到 PA0。如果某个时间,我们又同样的方式初始化了PB0,那么 PA0 与中断线的链接将被清除,而直接链接 PB0到中断线 0。
配置中断优先级( NVIC),并使能中断。
设置好中断线和 GPIO 映射关系,然后又设置好了中断的触发模式等初始化参数。既然是外部中断,涉及到中断我们当然还要设置 NVIC 中断优先级。这个在前面已经讲解过,这里我们就接着上面的范例, 设置中断线 0 的中断优先级并使能外部中断 0 的方法为:
HAL_NVIC_SetPriority(EXTI0_IRQn,2,1); //抢占优先级为 2,子优先级为 1 HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线 2 |
编写中断服务函数。
中断服务函数的名字是在 HAL 库中事先有定义的。这里需要说明一下, STM32H7 的 IO 口外部中断函数只有 7 个,分别为:
void EXTI0_IRQHandler(); void EXTI1_IRQHandler(); void EXTI2_IRQHandler(); void EXTI3_IRQHandler(); void EXTI4_IRQHandler(); void EXTI9_5_IRQHandler(); void EXTI15_10_IRQHandler();
中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数EXTI9_5_IRQHandler,中断线 10-15 共用中断函数 EXTI15_10_IRQHandler
编写中断处理回调函数 HAL_GPIO_EXTI_Callback
在使用 HAL 库的时候,我们也可以跟使用标准库一样,在中断服务函数中编写控制逻辑。但 是 HAL 库 为 了 用 户 使用 方 便 , 它 提 供 了 一 个 中 断 通 用 入 口 函 数 HAL_GPIO_EXTI_IRQHandler,在该函数内部直接调用回调函数 HAL_GPIO_EXTI_Callback。
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { if( HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET) { HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); HAL_GPIO_EXTI_Callback(GPIO_Pin); } } |
在所有的外部中断服务函数中直接调用外部中断共用处理函数 HAL_GPIO_EXTI_IRQHandler,然后在回调函数 HAL_GPIO_EXTI_Callback 中通过判断中断是来自哪个 IO口编写相应的中断服务控制逻辑。
如果是程序运行期间的引脚状态切换,最好采用下面的方式或者直接寄存器操作:
GPIO_InitStruct.Pin = GPIO_PIN_0 |GPIO_PIN_1 | GPIO_PIN_2 ; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); //这里会执行 16 次 for 查询 |
然后在 NVIC 配置里使能中断
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
HAL_GPIO_EXTI_Callback(GPIO_PIN_4);
第三章 仿真现象与结果讨论
开始界面按下 KEY1 进入一级菜单
按下向下键实现功能选择,改变函数指针并刷新屏幕
在流水灯选项按下确认
一样可以选择想要的动画,选择好后按确定进入选择播放,此时会读取按键信息并更新显示,执行变化后的函数指针的函数,同时唤醒相应任务
此时最下方 LED 点亮
- 音乐选项同理
可以看出此项目与 MP3 功能相近,实际上经过长达两天的 DEBUG 已经使整个稳定性达到较高水准,该项目可以用于新能源汽车的多媒体中心,也可以用于随身听,此外,该项目也可以用于文艺汇演的灯光音响控制,其 FREERTOS 的调度使其易于移植于需要交互的对及时性要求高的地方,如电机控制显示
结 语
在长达一个星期的写代码,DEBUG 后发现了一些问题,比如播放一个音乐暂停再播放会卡住很久,比如音乐播放直接卡死,又比如按键按下对其他任务有影响,又比如播放一个音乐不暂停直接播放另外一首,又比如在播放音乐的时候播放动画是否会改变动画效果(LED 的闪烁时间),但是这些问题都被我一一解决了,这也是我第一次用 FREERTOS 写较大型的应用程序,也是第一次写多级菜单,用图形库,使用 HAL 库和 CUBEMX 进行较大项目开发。
其中不得不提 PROTEUS 仿真实在是糟糕,很多硬件上有效果的代码仿真不出来,不支持硬件 I2C 是大问题,此外还在图形显示方面有一些问题,不能显示中文,开机不可以清屏,按 RESET 会大概率直接卡死。不过图片界面和 UI 界面都在实物上有效显示。
总的回顾起来还是很高兴能够实现这个项目,该项目已经比较完善,但是在以下几个地方可以提升:1.使用 RGB 触摸屏增强交互性与显示效果 2.采用 ENWIN 进行界面绘制 3.使用动画进行切换 4.多级菜单采用链表结构 5.对于实时性要求不高的系统应用改用LINUX 系统6.增加MP3和WAV格式的解码,加入FATFS文件管理系统
随着个人技术提升与精进相信可以更好的改良该项目。