目录
STM32CubeMX创建FreeRTOS工程详细指南:
项目具体实现功能:
任务1,当PC2为高电平时,由PD13控制的红色LED灯常亮;当PC2为低电平时,由PD13控制的红色LED灯常灭。
任务2,当PC2为高电平时,由PG14控制的白色LED灯以0.3秒的间隔亮灭;当PC2为低电平时,由PG14控制的白色LED灯以2秒的间隔亮灭;
任务3,当PC2为高电平时,通过CAN通信发送指令到总线上“0X2B 0X01 0X20 0X00 0XF4 0X01 0X00 0X00”
步骤 1:创建新工程
1. 打开STM32CubeMX,点击左上角File → New Project。
2. 在芯片选择界面输入 `STM32F407ZGT6`,双击选中该芯片,进入配置界面。
步骤 2:配置系统时钟(HSE)
1. 左侧导航栏点击 System Core → RCC。
2. 在右侧的High Speed Clock (HSE)选择Crystal/Ceramic Resonator启用外部8MHz晶振)。
3.Low Speed Clock (LSE)保持默认(无需配置,除非需要RTC)。
步骤 3:配置输入引脚(PC2、PD13和PG14)
1. 左侧导航栏点击 System Core → GPIO。
2. 在右侧芯片引脚图中找到PC2:
- 单击 PC2,选择GPIO_Input
- 单击 PD13,选择GPIO_Output
- 单击 PG14,选择GPIO_Output
3. 在左侧GPIO配置中,可设置上拉/下拉电阻(根据实际电路选择,默认浮空输入)。
步骤 4:配置时钟树(HCLK = 168MHz)
1. 点击顶部 Clock Configuration 标签页。
2. 按以下步骤配置:
- HSE输入频率:输入 `8 MHz`。
- PLL Source Mux:选择 HSE。
- 配置分频/倍频参数:
- PLLM = `8`(HSE分频后为1MHz)。
- PLLN = `336`(倍频到336MHz)。
- PLLP = `2`(输出168MHz作为系统时钟)。
- SYSCLK 应显示为 `168 MHz`。
3.确认其他时钟(如APB1=42MHz,APB2=84MHz)。
步骤5:配置CAN控制器
左侧导航栏选择Connectivity > CAN1
配置模式:
Mode: Activated
Prescaler: 根据波特率计算(见下方公式)
Time Quantum: 1个时间单位 = 1/(CAN时钟频率 / Prescaler)
Time Segments:
Time Segment 1 (tq1): 4
Time Segment 2 (tq2): 2
ReSync Jump Width: 1
波特率计算示例(以1M bps为例):
APB1时钟 = 42 MHz
波特率 = APB1时钟 / (Prescaler × (tq1 + tq2 + 1))
代入:1000000 = 42000000 / (Prescaler × (4 + 2 + 1))
解得:Prescaler = 42000000 / (1000000 × 7) = 6
5. 配置GPIO
自动分配CAN引脚(如CAN1默认使用PA11和PA12)
PA11: CAN_RX(输入)
PA12: CAN_TX(输出)
确认引脚无冲突(黄色警告需解决)
6. 配置中断
导航至NVIC Settings
勾选CAN1 RX0 interrupts和CAN1 TX interrupts
步骤 6:启用FreeRTOS
1. 左侧导航栏点击 Middleware → FREERTOS。
2. 在右侧 Mode 下选择 Interface → CMSIS_V1 或 CMSIS_V2(推荐V2,兼容性更好)。
3. 在 Configuration 选项卡中:
- 检查调度器配置(默认参数可暂时不改)。
- 切换到 Tasks and Queues 标签页,点击 Add 创建任务:
- 输入任务名称(如 `StartCanTask`)。
- 设置优先级(如 `osPriorityNormal`)。
- 堆栈大小(如 `128` words,即512字节)。
- 入口函数名(如 `StartCanTask`,后续在代码中实现)。
***补充:
1.采用ST-LINK烧录程序,需要勾选Serial Wire;
2.FreeRTOS默认使用SysTick定时器来管理任务调度(如osDelay),如果HAL库(如HAL_Delay())也使用SysTick作为时基源,两者会争夺SysTick中断,导致计时不准确甚至系统崩溃。(HAL库的依赖:HAL库需要一个独立的时基(Timebase)来实现延时和超时检测。
默认情况下,STM32CubeMX可能将SysTick设为HAL的时基源,这在FreeRTOS工程中需要修改。)
解决方法:在CubeMX中修改HAL时基源
返回CubeMX主界面,点击左侧 System Core → SYS。
在右侧的 Timebase Source 选项中:
将默认的 SysTick 改为其他硬件定时器(如TIM6等)。
推荐选择未被FreeRTOS占用的基本定时器(例如TIM6)。
重要:确保选择的定时器在后续代码中不会被其他功能占用。
步骤 6:生成代码
1. 点击顶部 Project Manager 标签页:
- 设置工程名称和存储路径。
- 在 Toolchain/IDE 中选择你的开发环境(如MDK-ARM等)。
2. 在 Code Generator 中勾选:
- `Generate peripheral initialization as a pair of .c/.h files`。
- 可选:勾选 `Copy all used libraries into the project folder`。
3.点击 Generate Code 生成工程。
步骤 7:编写业务代码
使用STM32CubeMX初始化配置好所有外设之后,只需要修改freertos.c文件内的代码,并进行业务代码的编写即可
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : freertos.c
* Description : Code for freertos applications
******************************************************************************
* @attention
*
* Copyright (c) 2025 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 */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define CAN_TX_ID 0x123 // CAN发送ID
#define CAN_RX_ID 0x456 // CAN接收ID
#define LED_FAST_BLINK 300 // 快速闪烁间隔(ms)
#define LED_SLOW_BLINK 2000 // 慢速闪烁间隔(ms)
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
extern CAN_HandleTypeDef hcan1; // 添加外部声明
CAN_TxHeaderTypeDef TxHeader; // CAN发送帧头
CAN_RxHeaderTypeDef RxHeader; // CAN接收帧头
CAN_FilterTypeDef sFilterConfig; // CAN过滤器
uint8_t TxData[8]; // 发送数据缓冲区
uint8_t RxData[8]; // 接收数据缓冲区
uint32_t TxMailbox; // 发送邮箱
/* 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,
};
/* Definitions for redLedTask */
osThreadId_t redLedTaskHandle;
const osThreadAttr_t redLedTask_attributes = {
.name = "redLedTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for whiteLedTask */
osThreadId_t whiteLedTaskHandle;
const osThreadAttr_t whiteLedTask_attributes = {
.name = "whiteLedTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for canTask */
osThreadId_t canTaskHandle;
const osThreadAttr_t canTask_attributes = {
.name = "canTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
void StartRedLedTask(void *argument);
void StartWhiteLedTask(void *argument);
void StartCanTask(void *argument);
void CAN_Config(void);
/* 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 */
CAN_Config(); // 初始化CAN配置
/* 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);
redLedTaskHandle = osThreadNew(StartRedLedTask, NULL, &redLedTask_attributes);
whiteLedTaskHandle = osThreadNew(StartWhiteLedTask, NULL, &whiteLedTask_attributes);
canTaskHandle = osThreadNew(StartCanTask, NULL, &canTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* 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 StartRedLedTask 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 */
void CAN_Config(void)
{
// 配置CAN过滤器
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
// 配置发送参数
TxHeader.StdId = CAN_TX_ID;
TxHeader.ExtId = 0x00;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.IDE = CAN_ID_STD;
TxHeader.DLC = 8;
TxHeader.TransmitGlobalTime = DISABLE;
// 启动CAN
HAL_CAN_Start(&hcan1);
}
// 红色LED任务
void StartRedLedTask(void *argument)
{
for(;;)
{
// 读取PC2状态并控制PD13 LED
if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_2) == GPIO_PIN_SET)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET); // LED常灭
}
else
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET); // LED常亮
}
osDelay(100); // 适当的延时
}
}
// 白色LED任务
void StartWhiteLedTask(void *argument)
{
for(;;)
{
// 根据PC2状态决定闪烁间隔
uint32_t blinkInterval = (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_2) == GPIO_PIN_SET) ?
LED_FAST_BLINK : LED_SLOW_BLINK;
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_14); // LED翻转
osDelay(blinkInterval);
}
}
// CAN通信任务
void StartCanTask(void *argument)
{
uint8_t canData[8] = {0x2B, 0x01, 0x20, 0x00, 0xF4, 0x01, 0x00, 0x00};
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_2) == GPIO_PIN_SET)
{
// 发送CAN数据
if(HAL_CAN_AddTxMessage(&hcan1, &TxHeader, canData, &TxMailbox) == HAL_OK)
{
// 发送成功
}
}
osDelay(100); // 适当的延时
}
}
/* USER CODE END Application */
项目补充说明:
1.采用的传感器是微型漫反射光电开关传感器EE-SPY301-1;
2.LED灯电路原理图:
3.传感器信号输入电路原理图:
4.实物验证视频:
FreeRTOS+CAN通信+光电传感器
项目扩展:
思考:
我使用的是FreeRTOS实时操作系统架构,其下运行有CAN通信任务,以及8个传感器的不同信号处理任务,CAN通信任务是否依然可以采用中断接收总线上报文的形式呢?或者有什么信号接收处理方式?
解释:之所以有8个传感器的不同信号处理任务,是因为我项目真正应用到的对象是输电线路巡检机器人,传感器实时感知机器人各关节是否已到达可运行的极限位置。
思考问题解答:
在使用FreeRTOS的情况下,仍然可以采用中断接收CAN总线报文,但需要结合FreeRTOS的特性进行优化设计。理由:中断能立即响应CAN总线的数据到达,适合对实时性要求高的场景;中断服务程序(ISR)仅负责快速接收数据,实际处理逻辑交由FreeRTOS任务完成。
需注意的问题:FreeRTOS要求ISR尽量简短(通常仅标记事件或发送信号量/队列),避免阻塞其他任务或中断;若CAN中断优先级过高,可能影响其他传感器任务;若过低,可能导致中断响应延迟。
后续改进的两个方向:
1.DMA + FreeRTOS同步
适用场景:若CAN总线数据量大(如多传感器高频通信),DMA可减少中断频率,降低CPU负载;当8个传感器任务已占用较多CPU资源时,DMA可提升系统整体效率。
2.中断 + 队列/信号量:
适用于大多数场景,平衡实时性与资源占用。确保:CAN接收任务优先级高于传感器处理任务(避免数据积压);使用高效的队列或流缓冲区(FreeRTOS Stream Buffer)管理数据。