FreeRTOS+CAN通信+光电传感器=例程开发,步骤详解

目录

STM32CubeMX创建FreeRTOS工程详细指南:

项目具体实现功能:

步骤 1:创建新工程

步骤 2:配置系统时钟(HSE)

步骤 3:配置输入引脚(PC2、PD13和PG14)

步骤 4:配置时钟树(HCLK = 168MHz)

步骤5:配置CAN控制器

步骤 6:启用FreeRTOS

***补充:

步骤 6:生成代码

步骤 7:编写业务代码

项目补充说明:

项目扩展:

思考:

思考问题解答:


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:配置输入引脚(PC2PD13和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)管理数据。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值