/*本专栏所有源码、文字转述、说明框图都来自百问网,版权归属于百问科技。
【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=36&share_source=copy_web&vd_source=bab35cd72a6b7a3ffd3c77e664d802f1
这里是36讲,前面的笔记我会写一大篇复习笔记。
另外给初学者几点建议:
①自己尝试用CubeMX重新写一个完整工程出来,驱动可以移植老师写的,熟悉一下流程。
②挑一个教学裁剪FreeRTOS模板的课学习一下这个过程,一方面可以提高对内核源码的熟悉程度,另一方面如果以后遇到了使用其他MCU+RTOS组合(例如将RT-Thread移植到GD32上)的时候不至于因为不能用CubeMX而束手无措。
*/
第36讲 队列集实验_增加姿态控制
先解决两个BUG:
①想要再工程文件中使用队列集相关的API,必须要在FreeRTOSConfig.h头文件中修改这个宏定义(切记这个不是在 CubeMX里面点出来的):
/* USER CODE BEGIN Includes */
/* Section where include file can be added */
#define configUSE_QUEUE_SETS 1
/* USER CODE END Includes */
②keil mdk报错Error: L6406E: No space in execution regions with .ANY selector matching driver_ir_receiver.o(.data).
笔者当时遇到的情况是编译输出窗口(Build Output)中一大串驱动接口函数都在报错,问AI然后答复是这样的:这个错误提示是在链接阶段出现的,表示在执行区域中没有足够的空间来满足某些条件。具体来说,错误信息指出在链接过程中,有一个或多个目标文件或库试图将数据(通常是全局变量或静态变量)放在执行区域中,但这些区域没有足够的空间来容纳这些数据。
CubeMX的选项卡中有这个存在这个设定:
这个TOTAL_HAEP_SIZE是为了FreeRTOS预留的,具体根据单片机执行任务的复杂程度来进行估算,面多加水,水多加面即可。
这节课我们的目的是在上一节课的基础上实现MPU6050控制挡球板的功能,分为三步走,第一步创建一个在死循环中读数据写队列的任务,第二部把队列写入队列集,第三步写一个处理MPU6050的接口函数。
给大家贴一下我零错误零警告的实现代码:
1.freertos.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : freertos.c
* Description : Code for freertos applications
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "driver_led.h"
#include "driver_lcd.h"
#include "driver_mpu6050.h"
#include "driver_timer.h"
#include "driver_ds18b20.h"
#include "driver_dht11.h"
#include "driver_active_buzzer.h"
#include "driver_passive_buzzer.h"
#include "driver_color_led.h"
#include "driver_ir_receiver.h"
#include "driver_ir_sender.h"
#include "driver_light_sensor.h"
#include "driver_ir_obstacle.h"
#include "driver_ultrasonic_sr04.h"
#include "driver_spiflash_w25q64.h"
#include "driver_rotary_encoder.h"
#include "driver_motor.h"
#include "driver_key.h"
#include "driver_uart.h"
#include "Music.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
void game1_task(void *params);
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
LCD_Init();
LCD_Clear();
MPU6050_Init();
IRReceiver_Init();
RotaryEncoder_Init();
LCD_PrintString(0, 0, "Starting");
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
// defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* 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(;;)
{
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* USER CODE END Application */
2.game1.c
/*
* Project: N|Watch
* Author: Zak Kemble, contact@zakkemble.co.uk
* Copyright: (C) 2013 by Zak Kemble
* License: GNU GPL v3 (see License.txt)
* Web: http://blog.zakkemble.co.uk/diy-digital-wristwatch/
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cmsis_os.h"
#include "FreeRTOS.h" // ARM.FreeRTOS::RTOS:Core
#include "task.h" // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h" // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h" // ARM.FreeRTOS::RTOS:Core
#include "draw.h"
#include "resources.h"
#include "driver_lcd.h"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.h"
#define NOINVERT false
#define INVERT true
#define sprintf_P sprintf
#define PSTR(a) a
#define PLATFORM_WIDTH 12
#define PLATFORM_HEIGHT 4
#define UPT_MOVE_NONE 0
#define UPT_MOVE_RIGHT 1
#define UPT_MOVE_LEFT 2
#define BLOCK_COLS 32
#define BLOCK_ROWS 5
#define BLOCK_COUNT (BLOCK_COLS * BLOCK_ROWS)
typedef struct{
float x;
float y;
float velX;
float velY;
}s_ball;
static const byte block[] ={
0x07,0x07,0x07,
};
static const byte platform[] ={
0x60,0x70,0x50,0x10,0x30,0xF0,0xF0,0x30,0x10,0x50,0x70,0x60,
};
static const byte ballImg[] ={
0x03,0x03,
};
static const byte clearImg[] ={
0,0,0,0,0,0,0,0,0,0,0,0,
};
void game1_draw(void);
static byte uptMove;
static s_ball ball;
static bool* blocks;
static byte lives, lives_origin;
static uint score;
static byte platformX;
uint8_t *g_framebuffer;
static uint32_t g_xres, g_yres, g_bpp;
static QueueSetHandle_t g_xQueueSetInput; /* 输入设备的队列集 */
static QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
static QueueHandle_t g_xQueueIR;
static QueueHandle_t g_xQueueRotary;
static QueueHandle_t g_xQueueMPU6050; /* MPU6050队列 */
/* 挡球板任务 */
static void platform_task(void *params)
{
byte platformXtmp = platformX;
struct input_data idata;
// Draw platform
draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
while (1)
{
//if (0 == IRReceiver_Read(&dev, &data))
xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY);
uptMove = idata.val;
// Hide platform
draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
// Move platform
if(uptMove == UPT_MOVE_RIGHT)
platformXtmp += 3;
else if(uptMove == UPT_MOVE_LEFT)
platformXtmp -= 3;
uptMove = UPT_MOVE_NONE;
// Make sure platform stays on screen
if(platformXtmp > 250)
platformXtmp = 0;
else if(platformXtmp > g_xres - PLATFORM_WIDTH)
platformXtmp = g_xres - PLATFORM_WIDTH;
// Draw platform
draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
platformX = platformXtmp;
}
}
/**********************************************************************
* 函数名称: ProcessIRData
* 功能描述: 读取红外遥控器键值并转换为游戏控制键,写入挡球板队列
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/09/02 V1.0 韦东山 创建
***********************************************************************/
static void ProcessIRData(void)
{
struct ir_data idata;
static struct input_data input;
xQueueReceive(g_xQueueIR, &idata, 0);
if (idata.val == IR_KEY_LEFT)
{
input.dev = idata.dev;
input.val = UPT_MOVE_LEFT;
}
else if (idata.val == IR_KEY_RIGHT)
{
input.dev = idata.dev;
input.val = UPT_MOVE_RIGHT;
}
else if (idata.val == IR_KEY_REPEAT)
{
/* 保持不变 */;
}
else
{
input.dev = idata.dev;
input.val = UPT_MOVE_NONE;
}
/* 写挡球板队列 */
xQueueSend(g_xQueuePlatform, &input, 0);
}
/**********************************************************************
* 函数名称: ProcessRotaryData
* 功能描述: 读取旋转编码器数据并转换为游戏控制键,写入挡球板队列
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/09/02 V1.0 韦东山 创建
***********************************************************************/
static void ProcessRotaryData(void)
{
struct rotary_data rdata;
struct input_data idata;
int left;
int i, cnt;
/* 读旋转编码器队列 */
xQueueReceive(g_xQueueRotary, &rdata, 0);
/* 处理数据 */
/* 判断速度: 负数表示向左转动, 正数表示向右转动 */
if (rdata.speed < 0)
{
left = 1;
rdata.speed = 0 - rdata.speed;
}
else
{
left = 0;
}
//cnt = rdata.speed / 10;
//if (!cnt)
// cnt = 1;
if (rdata.speed > 100)
cnt = 4;
else if (rdata.speed > 50)
cnt = 2;
else
cnt = 1;
/* 写挡球板队列 */
idata.dev = 1;
idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
for (i = 0; i < cnt; i++)
{
xQueueSend(g_xQueuePlatform, &idata, 0);
}
}
/**********************************************************************
* 函数名称: ProcessMPU6050Data
* 功能描述: 读取MPU6050D的角度值并转换为游戏控制键,写入挡球板队列
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/09/05 V1.0 韦东山 创建
***********************************************************************/
static void ProcessMPU6050Data(void)
{
struct mpu6050_data mdata;
struct input_data idata;
/* 读旋转编码器队列 */
xQueueReceive(g_xQueueMPU6050, &mdata, 0);
/* 处理数据 */
/* 判断角度, 大于90度表示往左移动挡球板, 小于90度表示往右移动挡球板 */
if (mdata.angle_x > 90)
{
idata.val = UPT_MOVE_LEFT;
}
else if(mdata.angle_x < 90)
{
idata.val = UPT_MOVE_RIGHT;
}
else
{
idata.val = UPT_MOVE_NONE;
}
/* 写挡球板队列 */
idata.dev = 2;
xQueueSend(g_xQueuePlatform, &idata, 0);
}
/**********************************************************************
* 函数名称: InputTask
* 功能描述: 输入任务,检测多个输入设备并调用对应处理函数
* 输入参数: params - 未使用
* 输出参数: 无
* 返 回 值: 无
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/09/02 V1.0 韦东山 创建
***********************************************************************/
static void InputTask(void *params)
{
QueueSetMemberHandle_t xQueueHandle;
while (1)
{
/* 读队列集, 得到有数据的队列句柄 */
xQueueHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY);
if (xQueueHandle)
{
/* 读队列句柄得到数据,处理数据 */
if (xQueueHandle == g_xQueueIR)
{
ProcessIRData();
}
else if (xQueueHandle == g_xQueueRotary)
{
ProcessRotaryData();
}
else if (xQueueHandle == g_xQueueMPU6050)
{
ProcessMPU6050Data();
}
}
}
}
/*游戏任务的任务函数*/
void game1_task(void *params)
{
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 创建队列,队列集,创建输入任务InputTask */
g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));
g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN);
g_xQueueIR = GetQueueIR();
g_xQueueRotary = GetQueueRotary();
g_xQueueMPU6050 = GetQueueMPU6050();
xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);
xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);
xQueueAddToSet(g_xQueueMPU6050, g_xQueueSetInput);
xTaskCreate(MPU6050_Task, "MPU6050Task", 128, NULL, osPriorityNormal, NULL);
xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);
uptMove = UPT_MOVE_NONE;
ball.x = g_xres / 2;
ball.y = g_yres - 10;
ball.velX = -0.5;
ball.velY = -0.6;
// ball.velX = -1;
// ball.velY = -1.1;
blocks = pvPortMalloc(BLOCK_COUNT);
memset(blocks, 0, BLOCK_COUNT);
lives = lives_origin = 3;
score = 0;
platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);
xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);
while (1)
{
game1_draw();
//draw_end();
vTaskDelay(50);
}
}
bool btnExit()
{
vPortFree(blocks);
if(lives == 255)
{
//game1_start();
}
else
{
//pwrmgr_setState(PWR_ACTIVE_DISPLAY, PWR_STATE_NONE);
//animation_start(display_load, ANIM_MOVE_OFF);
vTaskDelete(NULL);
}
return true;
}
bool btnRight()
{
uptMove = UPT_MOVE_RIGHT;
return false;
}
bool btnLeft()
{
uptMove = UPT_MOVE_LEFT;
return false;
}
void game1_draw()
{
bool gameEnded = ((score >= BLOCK_COUNT) || (lives == 255));
// byte platformXtmp = platformX;
static bool first = 1;
// Move ball
// hide ball
draw_bitmap(ball.x, ball.y, clearImg, 2, 2, NOINVERT, 0);
draw_flushArea(ball.x, ball.y, 2, 8);
// Draw platform
//draw_bitmap(platformX, g_yres - 8, platform, 12, 8, NOINVERT, 0);
//draw_flushArea(platformX, g_yres - 8, 12, 8);
if(!gameEnded)
{
ball.x += ball.velX;
ball.y += ball.velY;
}
bool blockCollide = false;
const float ballX = ball.x;
const byte ballY = ball.y;
// Block collision
byte idx = 0;
LOOP(BLOCK_COLS, x)
{
LOOP(BLOCK_ROWS, y)
{
if(!blocks[idx] && ballX >= x * 4 && ballX < (x * 4) + 4 && ballY >= (y * 4) + 8 && ballY < (y * 4) + 8 + 4)
{
// buzzer_buzz(100, TONE_2KHZ, VOL_UI, PRIO_UI, NULL);
// led_flash(LED_GREEN, 50, 255); // 100ask todo
blocks[idx] = true;
// hide block
draw_bitmap(x * 4, (y * 4) + 8, clearImg, 3, 8, NOINVERT, 0);
draw_flushArea(x * 4, (y * 4) + 8, 3, 8);
blockCollide = true;
score++;
}
idx++;
}
}
// Side wall collision
if(ballX > g_xres - 2)
{
if(ballX > 240)
ball.x = 0;
else
ball.x = g_xres - 2;
ball.velX = -ball.velX;
}
if(ballX < 0)
{
ball.x = 0;
ball.velX = -ball.velX;
}
// Platform collision
bool platformCollision = false;
if(!gameEnded && ballY >= g_yres - PLATFORM_HEIGHT - 2 && ballY < 240 && ballX >= platformX && ballX <= platformX + PLATFORM_WIDTH)
{
platformCollision = true;
// buzzer_buzz(200, TONE_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
ball.y = g_yres - PLATFORM_HEIGHT - 2;
if(ball.velY > 0)
ball.velY = -ball.velY;
ball.velX = ((float)rand() / (RAND_MAX / 2)) - 1; // -1.0 to 1.0
}
// Top/bottom wall collision
if(!gameEnded && !platformCollision && (ballY > g_yres - 2 || blockCollide))
{
if(ballY > 240)
{
// buzzer_buzz(200, TONE_2_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
ball.y = 0;
}
else if(!blockCollide)
{
// buzzer_buzz(200, TONE_2KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
ball.y = g_yres - 1;
lives--;
}
ball.velY *= -1;
}
// Draw ball
draw_bitmap(ball.x, ball.y, ballImg, 2, 2, NOINVERT, 0);
draw_flushArea(ball.x, ball.y, 2, 8);
// Draw platform
//draw_bitmap(platformX, g_yres - 8, platform, 12, 8, NOINVERT, 0);
//draw_flushArea(platformX, g_yres - 8, 12, 8);
if (first)
{
first = 0;
// Draw blocks
idx = 0;
LOOP(BLOCK_COLS, x)
{
LOOP(BLOCK_ROWS, y)
{
if(!blocks[idx])
{
draw_bitmap(x * 4, (y * 4) + 8, block, 3, 8, NOINVERT, 0);
draw_flushArea(x * 4, (y * 4) + 8, 3, 8);
}
idx++;
}
}
}
// Draw score
char buff[6];
sprintf_P(buff, PSTR("%u"), score);
draw_string(buff, false, 0, 0);
// Draw lives
if(lives != 255)
{
LOOP(lives_origin, i)
{
if (i < lives)
draw_bitmap((g_xres - (3*8)) + (8*i), 1, livesImg, 7, 8, NOINVERT, 0);
else
draw_bitmap((g_xres - (3*8)) + (8*i), 1, clearImg, 7, 8, NOINVERT, 0);
draw_flushArea((g_xres - (3*8)) + (8*i), 1, 7, 8);
}
}
// Got all blocks
if(score >= BLOCK_COUNT)
draw_string_P(PSTR(STR_WIN), false, 50, 32);
// No lives left (255 because overflow)
if(lives == 255)
draw_string_P(PSTR(STR_GAMEOVER), false, 34, 32);
}
3.driver_mpu6050.c
/* Copyright (s) 2023 深圳百问网科技有限公司
* All rights reserved
*
* 文件名称:driver_mpu6050.c
* 摘要:
*
* 修改历史 版本号 Author 修改内容
*--------------------------------------------------
* 2023.08.03 v01 百问科技 创建文件
*--------------------------------------------------
*/
#include "stm32f1xx_hal.h"
#include "driver_mpu6050.h"
#include "driver_lcd.h"
#include "driver_timer.h"
#include <math.h>
static QueueHandle_t g_xQueueMPU6050; /*MPU6050队列*/
//****************************************
// 定义MPU6050内部地址
//****************************************
#define MPU6050_SMPLRT_DIV 0x19 // 陀螺仪采样率,典型值:0x07(125Hz)
#define MPU6050_CONFIG 0x1A // 低通滤波频率,典型值:0x06(5Hz)
#define MPU6050_GYRO_CONFIG 0x1B // 陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define MPU6050_ACCEL_CONFIG 0x1C // 加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
#define MPU6050_I2C_ADDR 0xD0
#define MPU6050_TIMEOUT 500
/* 传感器数据修正值(消除芯片固定误差,根据硬件进行调整) */
#define MPU6050_X_ACCEL_OFFSET (-64)
#define MPU6050_Y_ACCEL_OFFSET (-30)
#define MPU6050_Z_ACCEL_OFFSET (14400)
#define MPU6050_X_GYRO_OFFSET (40)
#define MPU6050_Y_GYRO_OFFSET (-7)
#define MPU6050_Z_GYRO_OFFSET (-14)
extern I2C_HandleTypeDef hi2c1;
static I2C_HandleTypeDef *g_pHI2C_MPU6050 = &hi2c1;
/*接口函数 返回MPU6050的队列句柄*/
QueueHandle_t GetQueueMPU6050(void)
{
return g_xQueueMPU6050;
}
/**********************************************************************
* 函数名称: MPU6050_WriteRegister
* 功能描述: 写MPU6050寄存器
* 输入参数: reg-寄存器地址, data-要写入的数据
* 输出参数: 无
* 返 回 值: 0 - 成功, 其他值 - 失败
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/03 V1.0 韦东山 创建
***********************************************************************/
static int MPU6050_WriteRegister(uint8_t reg, uint8_t data)
{
uint8_t tmpbuf[2];
tmpbuf[0] = reg;
tmpbuf[1] = data;
return HAL_I2C_Master_Transmit(g_pHI2C_MPU6050, MPU6050_I2C_ADDR, tmpbuf, 2, MPU6050_TIMEOUT);
}
/**********************************************************************
* 函数名称: MPU6050_ReadRegister
* 功能描述: 读MPU6050寄存器
* 输入参数: reg-寄存器地址
* 输出参数: pdata-用来保存读出的数据
* 返 回 值: 0 - 成功, 其他值 - 失败
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/03 V1.0 韦东山 创建
***********************************************************************/
int MPU6050_ReadRegister(uint8_t reg, uint8_t *pdata)
{
return HAL_I2C_Mem_Read(g_pHI2C_MPU6050, MPU6050_I2C_ADDR, reg, 1, pdata, 1, MPU6050_TIMEOUT);
}
/**********************************************************************
* 函数名称: MPU6050_Init
* 功能描述: MPU6050初始化函数,
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0 - 成功, 其他值 - 失败
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/03 V1.0 韦东山 创建
***********************************************************************/
int MPU6050_Init(void)
{
MPU6050_WriteRegister(MPU6050_PWR_MGMT_1, 0x00); //解除休眠状态
MPU6050_WriteRegister(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteRegister(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteRegister(MPU6050_CONFIG, 0x06);
MPU6050_WriteRegister(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteRegister(MPU6050_ACCEL_CONFIG, 0x18);
g_xQueueMPU6050=xQueueCreate(MPU6050_QUEUE_LEN,sizeof(struct mpu6050_data));
return 0;
}
/**********************************************************************
* 函数名称: MPU6050_GetID
* 功能描述: 读取MPU6050 ID
* 输入参数: 无
* 输出参数: 无
* 返 回 值: -1 - 失败, 其他值 - ID
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/03 V1.0 韦东山 创建
***********************************************************************/
int MPU6050_GetID(void)
{
uint8_t id;
if(0 == MPU6050_ReadRegister(MPU6050_WHO_AM_I, &id))
return id;
else
return -1;
}
/**********************************************************************
* 函数名称: MPU6050_ReadData
* 功能描述: 读取MPU6050数据
* 输入参数: 无
* 输出参数: pAccX/pAccY/pAccZ - 用来保存X/Y/Z轴的加速度
* pGyroX/pGyroY/pGyroZ - 用来保存X/Y/Z轴的角速度
* 返 回 值: 0 - 成功, 其他值 - 失败
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/03 V1.0 韦东山 创建
***********************************************************************/
int MPU6050_ReadData(int16_t *pAccX, int16_t *pAccY, int16_t *pAccZ, int16_t *pGyroX, int16_t *pGyroY, int16_t *pGyroZ)
{
uint8_t datal, datah;
int err = 0;
err |= MPU6050_ReadRegister(MPU6050_ACCEL_XOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_ACCEL_XOUT_L, &datal);
if(pAccX)
*pAccX = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_ACCEL_YOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_ACCEL_YOUT_L, &datal);
if(pAccY)
*pAccY = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_ACCEL_ZOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_ACCEL_ZOUT_L, &datal);
if(pAccZ)
*pAccZ = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_GYRO_XOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_GYRO_XOUT_L, &datal);
if(pGyroX)
*pGyroX = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_GYRO_YOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_GYRO_YOUT_L, &datal);
if(pGyroY)
*pGyroY = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_GYRO_ZOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_GYRO_ZOUT_L, &datal);
if(pGyroZ)
*pGyroZ = (datah << 8) | datal;
return err;
}
/**********************************************************************
* 函数名称: MPU6050_ParseData
* 功能描述: 解析MPU6050数据
* 输入参数: AccX/AccY/AccZ/GyroX/GyroY/GyroZ
* X/Y/Z轴的加速度,X/Y/Z轴的角速度
* 输出参数: result - 用来保存计算出的结果,目前仅支持X方向的角度
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/09/05 V1.0 韦东山 创建
***********************************************************************/
void MPU6050_ParseData(int16_t AccX, int16_t AccY, int16_t AccZ, int16_t GyroX, int16_t GyroY, int16_t GyroZ, struct mpu6050_data *result)
{
if (result)
{
result->angle_x = (int32_t)(acos((double)((double)(AccX + MPU6050_X_ACCEL_OFFSET) / 16384.0)) * 57.29577);
}
}
/**********************************************************************
* 函数名称: MPU6050_Test
* 功能描述: MPU6050测试程序
* 输入参数: 无
* 输出参数: 无
* 无
* 返 回 值: 0 - 成功, 其他值 - 失败
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/03 V1.0 韦东山 创建
***********************************************************************/
void MPU6050_Test(void)
{
int id,len;
int16_t AccX, AccY, AccZ, GyroX, GyroY, GyroZ;
struct mpu6050_data result;
MPU6050_Init();
id = MPU6050_GetID();
LCD_PrintString(0, 0, "MPU6050 ID:");
LCD_PrintHex(0, 2, id, 1);
mdelay(1000);
LCD_Clear();
while (1)
{
MPU6050_ReadData(&AccX, &AccY, &AccZ, &GyroX, &GyroY, &GyroZ);
MPU6050_ParseData(AccX,AccY,AccZ,GyroX,GyroY,GyroZ,&result);
LCD_PrintString(0, 0, "X: ");
LCD_PrintSignedVal(3, 0, AccX);
LCD_PrintSignedVal(12, 0, GyroX);
LCD_PrintString(0, 2, "Y: ");
LCD_PrintSignedVal(3, 2, AccY);
LCD_PrintSignedVal(12, 2, GyroY);
LCD_PrintString(0, 4, "Z: ");
LCD_PrintSignedVal(3, 4, AccZ);
LCD_PrintSignedVal(12, 4, GyroZ);
len=LCD_PrintString(0,6,"Anglex:");
LCD_PrintSignedVal(len,6,result.angle_x);
}
}
/*MPU6050的任务函数*/
void MPU6050_Task(void *params)
{
int16_t AccX;
struct mpu6050_data result;
int ret;
extern volatile int bInUsed;//来自draw.c的全局变量,用来保护OLED屏幕这个临界资源
for(;;)
{
/*读数据*/
while(bInUsed);
bInUsed=1;
ret=MPU6050_ReadData(&AccX,NULL,NULL,NULL,NULL,NULL);
bInUsed=0;
if(0==ret)
{
/*解析数据*/
MPU6050_ParseData(AccX,NULL,NULL,NULL,NULL,NULL,&result);
/*写队列*/
xQueueSend(g_xQueueMPU6050,&result,0);
}
/*延迟*/
vTaskDelay(50);
}
}
由于还是没有介绍互斥锁,所以还是使用全局变量来保护OLED这个临界资源,防止小球和挡球板出现残影。代码我是自己复现的,和老师的可能不太一样。
第37讲 队列实验_分发数据给多个任务(赛车游戏)
这一讲我们的任务是在16_queueset_game_mpu6050的基础上改出17_queueset_car_dispatch,
红外遥控器的中断服务函数解析出按键值后,写入三个队列,3个赛车任务读取其中一个队列得到按键数据。
直接在nwatch路径下新建game2.c文件,如下所示:
/*
* Project: N|Watch
* Author: Zak Kemble, contact@zakkemble.co.uk
* Copyright: (C) 2013 by Zak Kemble
* License: GNU GPL v3 (see License.txt)
* Web: http://blog.zakkemble.co.uk/diy-digital-wristwatch/
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cmsis_os.h"
#include "FreeRTOS.h" // ARM.FreeRTOS::RTOS:Core
#include "task.h" // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h" // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h" // ARM.FreeRTOS::RTOS:Core
#include "draw.h"
#include "resources.h"
#include "driver_lcd.h"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.h"
#define NOINVERT false
#define INVERT true
#define CAR_COUNT 3
#define CAR_WIDTH 12
#define CAR_LENGTH 15
#define ROAD_SPEED 6
static uint32_t g_xres, g_yres, g_bpp;
uint8_t *g_framebuffer1;
struct car {
int x;
int y;
int control_key;
};
struct car g_cars[3] = {
{0, 0, IR_KEY_1},
{0, 17, IR_KEY_2},
{0, 34, IR_KEY_3},
};
static const byte carImg[] ={
0x40,0xF8,0xEC,0x2C,0x2C,0x38,0xF0,0x10,0xD0,0x30,0xE8,0x4C,0x4C,0x9C,0xF0,
0x02,0x1F,0x37,0x34,0x34,0x1C,0x0F,0x08,0x0B,0x0C,0x17,0x32,0x32,0x39,0x0F,
};
static const byte clearImg[30] ={0};
static const byte roadMarking[] ={
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
};
#if 0
void car_test(void)
{
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
draw_bitmap(0, 0, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(0, 0, 15, 16);
draw_bitmap(0, 16, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(0, 16, 8, 1);
while (1);
}
#endif
static void ShowCar(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x, pcar->y, 15, 16);
}
static void HideCar(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, clearImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x, pcar->y, 15, 16);
}
static void CarTask(void *params)
{
struct car *pcar = params;
struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
while (1)
{
/* 读取按键值:读队列 */
xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
/* 控制汽车往右移动 */
if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x += 20;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
}
}
}
}
void car_game(void)
{
int i, j;
g_framebuffer1 = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 画出路标 */
for (i = 0; i < 3; i++)
{
for (j = 0; j < 8; j++)
{
draw_bitmap(16*j, 16+17*i, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(16*j, 16+17*i, 8, 1);
}
}
/* 创建3个汽车任务 */
#if 0
for (i = 0; i < 3; i++)
{
draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);
}
#endif
xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);
}
第38讲 信号量的本质
先下个定义:
信号量是一个非负整数(计数器),用于表示可用资源的数量。每个任务在需要访问共享资源时,都会尝试获取信号量。如果信号量的值大于零,任务将成功获取信号量并可以继续执行;如果信号量的值为零,任务将被阻塞,直到有其他任务释放信号量。
FreeRTOS提供了两种类型的信号量:二进制信号量和计数信号量。二进制信号量只有两个状态:可用(1)和不可用(0),通常用于排他性访问共享资源。计数信号量则可以跟踪多个资源的数量,通常用于限制对某些资源的访问,并可以实现生产者-消费者模式。
信号量的主要作用是实现两个功能:任务之间或中断函数与任务之间的同步,以及临界资源的互斥访问。通过信号量,可以确保在某一时刻只有一个任务访问共享资源,从而避免资源冲突和数据不一致的问题。
总之信号量的本质也是一个队列,但是只不过这个队列不涉及数据的真正传输,只涉及里面数据个数的统计。
队列的数据结构框图:
Len队列长度 |
R读位置 |
W写位置 |
cnt计数值 |
发送者链表 |
接收者链表 |
信号量的数据结构框图:
最大计数值 |
计数值 |
接收者链表 |
第39讲 信号量实验_控制车辆运行
使用信号量需要引用头文件,"semphr.h"。
下面介绍一下所需的KPI。
二进制信号量
|
计数型信号量
| |
动态
创建
|
xSemaphoreCreateBinary
计数值初始值为 0
|
xSemaphoreCreateCounting
|
静态
创建
|
xSemaphoreCreateBinaryStatic
|
xSemaphoreCreateCountingStatic
|
/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
/* 创建一个二进制信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的
指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuf
fer );
/* 创建一个计数型信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t
uxInitialCount);
/* 创建一个计数型信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的
指针
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* pxSemaphoreBuffer: StaticSemaphore_t 结构体指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t *pxSemaphore
Buffer );
第40集 信号量实验_优先级反转
优先级反转实验
/*
* Project: N|Watch
* Author: Zak Kemble, contact@zakkemble.co.uk
* Copyright: (C) 2013 by Zak Kemble
* License: GNU GPL v3 (see License.txt)
* Web: http://blog.zakkemble.co.uk/diy-digital-wristwatch/
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cmsis_os.h"
#include "FreeRTOS.h" // ARM.FreeRTOS::RTOS:Core
#include "task.h" // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h" // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h" // ARM.FreeRTOS::RTOS:Core
#include "draw.h"
#include "resources.h"
#include "driver_lcd.h"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.h"
#include "driver_timer.h"
#define NOINVERT false
#define INVERT true
#define CAR_COUNT 3
#define CAR_WIDTH 12
#define CAR_LENGTH 15
#define ROAD_SPEED 6
static SemaphoreHandle_t g_xSemTicks;
static uint32_t g_xres, g_yres, g_bpp;
uint8_t *g_framebuffer1;
struct car {
int x;
int y;
int control_key;
};
struct car g_cars[3] = {
{0, 0, IR_KEY_1},
{0, 17, IR_KEY_2},
{0, 34, IR_KEY_3},
};
static const byte carImg[] ={
0x40,0xF8,0xEC,0x2C,0x2C,0x38,0xF0,0x10,0xD0,0x30,0xE8,0x4C,0x4C,0x9C,0xF0,
0x02,0x1F,0x37,0x34,0x34,0x1C,0x0F,0x08,0x0B,0x0C,0x17,0x32,0x32,0x39,0x0F,
};
static const byte clearImg[30] ={0};
static const byte roadMarking[] ={
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
};
#if 0
void car_test(void)
{
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
draw_bitmap(0, 0, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(0, 0, 15, 16);
draw_bitmap(0, 16, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(0, 16, 8, 1);
while (1);
}
#endif
static void ShowCar(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x, pcar->y, 15, 16);
}
static void HideCar(struct car *pcar)
{
draw_bitmap(pcar->x, pcar->y, clearImg, 15, 16, NOINVERT, 0);
draw_flushArea(pcar->x, pcar->y, 15, 16);
}
static void Car1Task(void *params)
{
struct car *pcar = params;
// struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
/*获得信号量*/
xSemaphoreTake(g_xSemTicks,portMAX_DELAY);
while (1)
{
// /* 读取按键值:读队列 */
// xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
//
// /* 控制汽车往右移动 */
// if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x +=2;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
vTaskDelay(50);
if(pcar->x ==g_xres - CAR_LENGTH)
{
/*释放信号量*/
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
}
}
static void Car2Task(void *params)
{
struct car *pcar = params;
// struct ir_data idata;
vTaskDelay(1000);
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
/*获得信号量*/
// xSemaphoreTake(g_xSemTicks,portMAX_DELAY);
while (1)
{
// /* 读取按键值:读队列 */
// xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
//
// /* 控制汽车往右移动 */
// if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x +=2;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
// vTaskDelay(50);
mdelay(50);
if(pcar->x ==g_xres - CAR_LENGTH)
{
/*释放信号量*/
//xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
}
}
static void Car3Task(void *params)
{
struct car *pcar = params;
// struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
vTaskDelay(2000);
/*获得信号量*/
xSemaphoreTake(g_xSemTicks,portMAX_DELAY);
while (1)
{
// /* 读取按键值:读队列 */
// xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
//
// /* 控制汽车往右移动 */
// if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x +=2;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
vTaskDelay(50);
if(pcar->x ==g_xres - CAR_LENGTH)
{
/*释放信号量*/
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
}
}
void car_game(void)
{
int i, j;
g_framebuffer1 = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
// g_xSemTicks=xSemaphoreCreateCounting(3,1);
g_xSemTicks=xSemaphoreCreateBinary();
xSemaphoreGive(g_xSemTicks);/*二进制信号量只能Give一次*/
/* 画出路标 */
for (i = 0; i < 3; i++)
{
for (j = 0; j < 8; j++)
{
draw_bitmap(16*j, 16+17*i, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(16*j, 16+17*i, 8, 1);
}
}
/* 创建3个汽车任务 */
#if 0
for (i = 0; i < 3; i++)
{
draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);
draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);
}
#endif
xTaskCreate(Car1Task, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(Car2Task, "car2", 128, &g_cars[1], osPriorityNormal+1, NULL);
xTaskCreate(Car3Task, "car3", 128, &g_cars[2], osPriorityNormal+2, NULL);
}
第41讲 互斥量_解决优先级反转
1.动态创建:
/* 创建一个互斥量,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
2.静态创建:
/* 创建一个互斥量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的
指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );
在car_game任务里面创建互斥量
void car_game(void)
{
int i, j;
g_framebuffer1 = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
g_xSemTicks=xSemaphoreCreateMutex();
/* 画出路标 */
for (i = 0; i < 3; i++)
{
for (j = 0; j < 8; j++)
{
draw_bitmap(16*j, 16+17*i, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(16*j, 16+17*i, 8, 1);
}
}
/* 创建3个汽车任务 */
xTaskCreate(Car1Task, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(Car2Task, "car2", 128, &g_cars[1], osPriorityNormal+1, NULL);
xTaskCreate(Car3Task, "car3", 128, &g_cars[2], osPriorityNormal+2, NULL);
}
烧录之后发现实验现象是car2消失之后,car3只是出现了但一动不动,问题出在哪里呢?通过Debug之后发现显存刷新函数里面我们还在使用全局变量作为临界资源的保护。在这个方式下,如果出现了car2在刷新过程中优先级更高的car3开始运行的情况,那么car3就会一直在等待全局变量被car2清零,但是car3优先级更高抢占了CPU资源,导致car2迟迟无法更改全局变量,程序就会卡死了。
volatile int bInUsed = 0;
void draw_flushArea(byte x, byte y, byte w, byte h)
{
while (bInUsed);
//taskENTER_CRITICAL();
bInUsed = 1;
LCD_FlushRegion(x, y, w, h);
bInUsed = 0;
//taskEXIT_CRITICAL();
}
索性一起改为互斥量吧,
void draw_flushArea(byte x, byte y, byte w, byte h)
{
GetI2C();
LCD_FlushRegion(x, y, w, h);
PutI2C();
}
然后注意我为了代码方便维护和移植,这里架构和老师弄得有点不一样,新手建议和老师一样弄。
我在FreeRTOS里面定义了I2C互斥量和等待/释放I2C的函数,
static SemaphoreHandle_t g_xI2CMutex;
void GetI2C(void)
{
/*等待一个互斥量*/
xSemaphoreTake(g_xI2CMutex,portMAX_DELAY);
}
void PutI2C(void)
{
/*释放互斥量*/
xSemaphoreGive(g_xI2CMutex);
}
然后在draw.h里面声明了这两个I2C互斥量操作函数,这样方便定义的跳转。
MPU6050的任务函数也要改:
/*MPU6050的任务函数*/
void MPU6050_Task(void *params)
{
int16_t AccX;
struct mpu6050_data result;
int ret;
// extern volatile int bInUsed;//来自draw.c的全局变量,用来保护OLED屏幕这个临界资源
for(;;)
{
/*读数据*/
// while(bInUsed);
// bInUsed=1;
GetI2C();
ret=MPU6050_ReadData(&AccX,NULL,NULL,NULL,NULL,NULL);
PutI2C();
// bInUsed=0;
if(0==ret)
{
/*解析数据*/
MPU6050_ParseData(AccX,NULL,NULL,NULL,NULL,NULL,&result);
/*写队列*/
xQueueSend(g_xQueueMPU6050,&result,0);
}
/*延迟*/
vTaskDelay(50);
}
}
最后我跟着老师一步步改代码发现和老师的实验现象不太一样,我的car3需要等待car2自我删除之后才开始运动,原因是car3里面使用了vTaskDelay阻塞式等待函数,应该改为mdelay。
static void Car3Task(void *params)
{
struct car *pcar = params;
// struct ir_data idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
vTaskDelay(2000);
/*获得信号量*/
xSemaphoreTake(g_xSemTicks,portMAX_DELAY);
while (1)
{
// /* 读取按键值:读队列 */
// xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
//
// /* 控制汽车往右移动 */
// if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x +=2;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
// vTaskDelay(50);
mdelay(50);
if(pcar->x ==g_xres - CAR_LENGTH)
{
/*释放信号量*/
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
}
}
第42讲 事件组的本质
时间组是一种广播机制,可以通过一个任务写入数据唤醒多个任务。
在FreeRTOS中,事件组是一个非常重要的概念,它实现了对多个任务的广播机制,主要用于多任务的同步。事件组是由一组事件标志结合而成的,这些事件标志是布尔值(0或1),用来标记一个事件是否发生。每一个事件标志在事件组中占据一个位,这些标志共同构成了事件组的状态。
在FreeRTOS中,事件组通过一个名为EventBits_t的数据类型来表示,该类型变量的每一位都表示一个事件标志。事件组的位数可以通过配置configUSE_16_BIT_TICKS宏的值来设置。例如,当configUSE_16_BIT_TICKS设置为1时,事件组将包含8位可以使用的事件位;当configUSE_16_BIT_TICKS设置为0时,事件组将包含24位可以使用的事件位。