梭净挟 发表于 2025-6-6 23:46:34

stm32cubemx+DMA+freertos实现usart不定长数据接收和发送

本博客讲解如何在stm32cubemx+freertos+dma的情况下实现usart不定长接收和发送数据
cubemx配置



波特率随意
freertos没啥特别的配置,打开即可,我用的是CMSIS_V1,如下:

代码

先初始化,如下:
void GimbalInitStart(void)
{
    // 启动DMA接收
    HAL_UART_Receive_DMA(&huart2, rx2_buffer, RX2_BUFFER_SIZE);
    // 启用空闲中断
    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
    // 使能DMA传输完成中断
    __HAL_DMA_ENABLE_IT(&hdma_usart2_tx, DMA_IT_TC);
}后重写下面几个函数,完成接收:
// 接收中断
void Gimbal_USART2_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET) {
      __HAL_UART_CLEAR_IDLEFLAG(&huart2);
      
      // 停止 DMA 接收
      HAL_UART_DMAStop(&huart2);
      
      // 计算接收长度
      uint16_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
      rx2_length = RX2_BUFFER_SIZE - temp;
      
      // 发送信号量,原来在freertos下提醒另一个任务可以处理数据了
      BaseType_t xHigherPriorityTaskWoken = pdFALSE;
      xSemaphoreGiveFromISR(uart_rx_sem, &xHigherPriorityTaskWoken);
      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

// 自己写的处理具体数据的任务,可在freertos的某个任务下循环调用
void Gimbal_Process_Rx_Data(void)
{
    // 对应上面的发送信号量,检测到信号量后就执行数据处理任务
    if(xSemaphoreTake(uart_rx_sem, 0) == pdTRUE) {
      
      // 重新启动DMA接收
      HAL_UART_Receive_DMA(&huart2, rx2_buffer, RX2_BUFFER_SIZE);
    }
}完整代码如下,可参考下:
// gimbal.h
#ifndef GIMBAL_H
#define GIMBAL_H

#include "stm32f1xx_hal.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#include "cmsis_os.h"
#include "string.h"
#include "crc16.h"

// 底层能调用的函数和接口
#define RX2_BUFFER_SIZE 256
#define TX2_BUFFER_SIZE 128
#define ORDER_LIST_SIZE 64
// 角度限制
#define MAX_PITCH_ANGLE 25 * 10// 最大25° 即向上
#define MIN_PITCH_ANGLE -90 * 10// 最小-90° 即向下

void GimbalInitStart(void);   
void Gimbal_USART2_IRQHandler(void);// 自定义的回调处理函数,计算接收的数据长度,在“stm32f1xx_it.c”中被调用
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart); // 错误处理
void Gimbal_Process_Rx_Data(void);// 处理接收到的数据
void Gimbal_DMA_Send_record(const uint8_t *data, uint16_t size);// 会自动在环形数组中记录当前发送的是啥东西
void UART2_DMA_Send(const uint8_t *data, uint16_t size);// 底层发送函数
void circle_write(uint8_t data);// 环形数组写入
uint8_t circle_read(void);// 环形数组读取
void Gimbal_Init_Func(void);// 云台初始化发送指令
void Add_crc16_code(uint8_t *data, uint16_t size);// 按照给定数组和长度,在最后面加上两字节的校验位
uint8_t check_crc16(uint8_t *data, uint16_t size);// 检查收到的数据是否正确
void Gimbal_total_Init(void);// 其他初始化
void Set_Gimbal_Pitch(int16_t pitch_angle);

#endif // GIMBAL_H#include "gimbal.h"

static uint8_t rx2_buffer;
static uint8_t tx_buffer;
static volatile uint16_t rx2_length = 0;
SemaphoreHandle_t uart_rx_sem;
SemaphoreHandle_t gimbal_init_flag;
extern UART_HandleTypeDef huart2;
extern DMA_HandleTypeDef hdma_usart2_tx;
extern DMA_HandleTypeDef hdma_usart2_rx;
extern volatile uint8_t beer_ring_mode;
// 环形数组,按顺序记录和相机通信的指令,若发送失败则可快速找到上一条指令重新发送
static uint8_t order_list;
static volatile uint8_t write_pointer;// 写指针(新数据位置)
static volatile uint8_t read_pointer;   // 读指针(若要读数据一般从该位置的下一个开始读)
// 各种特定的指令代号
static uint8_t set_video_argument = 0x21;// 设置云台图像的各种参数
// 一些固定的指令,直接封装成一条了
static uint8_t get_gimbal_config[] = {0x55, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0F, 0x75};// 获取云台配置
static uint8_t set_follow_mode[] = {0x55, 0x66, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x04, 0xB0, 0x8E};// 设置云台跟随模式,即只有pitch轴能控制
static uint8_t set_CVBS_output[] = {0x55, 0x66, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x07, 0xD3, 0xBE}; // 设置视频CVBS输出
static uint8_t get_gimbal_firmware_version[] = {0x55, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0xC4};// 获取云台固件版本号
static uint8_t set_frame_super_definition[] = {0x55, 0x66, 0x01, 0x09, 0x00, 0x00, 0x00, 0x21, 0x01, 0x02, 0x80, 0x07, 0x38, 0x04, 0xD0, 0x07, 0x00, 0x5A, 0x68};// 设置画面超清
static uint8_t set_frame_high_definition[] = {0x55, 0x66, 0x01, 0x09, 0x00, 0x00, 0x00, 0x21, 0x01, 0x02, 0x00, 0x05, 0xD0, 0x02, 0xDC, 0x05, 0x00, 0x58, 0x45};// 设置画面高清
static uint8_t set_frame_mode3[] = {0x55, 0x66, 0x01, 0x01, 0x00, 0x00, 0x00, 0x11, 0x03, 0x78, 0x8B};// 设置模式3,主码流为变焦相机
static uint8_t set_frame_mode7[] = {0x55, 0x66, 0x01, 0x01, 0x00, 0x00, 0x00, 0x11, 0x07, 0xFC, 0xCB};// 设置模式7,主码流为热成像相机
static uint8_t one_click_back[] = {0x55, 0x66, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, 0x01, 0xD1, 0x12};// 云台一键回中
static uint8_t set_gimbal_pitch[] = {0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};// 设置云台pitch角度
// 其他相关的一些云台的参数
static uint8_t camera_firmware_version;// 相机固件号,可以用来判断相机初始化结束了没有,大端模式
extern volatile uint8_t camera_init = 0;// 相机是否初始化
static volatile uint8_t video_output_mode = 0;// 相机输出模式 0x00表示还未读取到信息 0x01 表示读取到了但并不是预期的CVBS模式 0x02表示当前模式为预期的模式CVBS
static volatile uint8_t video_super_or_high_definition = 0;// 0表示超清,1表示高清
static volatile uint8_t current_frame_mode = 0;// 0表示模式3, 1表示模式7
void GimbalInitStart(void)
{
    // 启动DMA接收
    HAL_UART_Receive_DMA(&huart2, rx2_buffer, RX2_BUFFER_SIZE);
    // 启用空闲中断
    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
    // 创建二值信号量
    uart_rx_sem = xSemaphoreCreateBinary();
    // 使能DMA传输完成中断
    __HAL_DMA_ENABLE_IT(&hdma_usart2_tx, DMA_IT_TC);
}

void Gimbal_Process_Rx_Data(void)
{
    if(xSemaphoreTake(uart_rx_sem, 0) == pdTRUE) {
      // 处理接收到的数据
      // 校验crc16
      if(check_crc16(rx2_buffer, rx2_length) == 0x01)
      {
            switch(rx2_buffer){
                case 0x01:// 获取云台固件
                {
                  // 判断固件号有没有,有就是初始化成功了
                  if(!((rx2_buffer == 0x00) && (rx2_buffer == 0x00) && (rx2_buffer == 0x00)))
                  {
                        camera_init = 1;
                        memcpy(camera_firmware_version, rx2_buffer + 8, 3);
                  }
                  xSemaphoreGive(gimbal_init_flag);
                  break;
                }
                case 0x0A:// 云台配置信息
                {
                  if(rx2_buffer == 0x00) video_output_mode = 0x01;
                  else if(rx2_buffer == 0x01) video_output_mode = 0x02;
                  
                  break;
                }
                case 0x21:// 设置画面的清晰度
                {
                  uint8_t now_code = circle_read();
                  if(rx2_buffer != 0x01)
                  {
                        if(video_super_or_high_definition == 0x00)
                        {
                            Gimbal_DMA_Send_record(set_frame_super_definition, sizeof(set_frame_super_definition));
                        }else
                        {
                            Gimbal_DMA_Send_record(set_frame_high_definition, sizeof(set_frame_high_definition));
                        }
                  }
                  if(now_code == 0x21)
                  {
                  
                  }
                  else
                  {
                        // 若不是当前的恢复,好像也没啥办法哈哈
                  }
                  
                  break;
                }
                case 0x11:
                {
                  uint8_t now_code = circle_read();
                  if(rx2_buffer == 0x03)// 表示当前画面为模式3
                  {
                        if(current_frame_mode == 0x01)
                        {
                            Gimbal_DMA_Send_record(set_frame_mode7, sizeof(set_frame_mode7));
                        }
                  }else if(rx2_buffer == 0x07) // 表示模式7
                  {
                        if(current_frame_mode == 0x00)
                        {
                            Gimbal_DMA_Send_record(set_frame_mode3, sizeof(set_frame_mode3));
                        }
                  }else// 其他的一些不想要的模式
                  {
                        // 就默认设置为模式3
                        Gimbal_DMA_Send_record(set_frame_mode3, sizeof(set_frame_mode3));
                  }
                  if(now_code == 0x21)
                  {
                  
                  }
                  else
                  {
                        // 若不是当前的恢复,好像也没啥办法哈哈
                  }
                  
                  break;
                }
                case 0x08:
                {
                  uint8_t now_code = circle_read();
                  if(rx2_buffer == 0x00)
                  {
                        Gimbal_DMA_Send_record(one_click_back, sizeof(one_click_back));
                  }
                  
                  break;
                }
                default:
                {
                  // 若收到一些预期之外的信息,则直接掠过
                  break;
                }
            }
      }
      
      // 重新启动DMA接收
      HAL_UART_Receive_DMA(&huart2, rx2_buffer, RX2_BUFFER_SIZE);
    }
}

void Gimbal_USART2_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET) {
      __HAL_UART_CLEAR_IDLEFLAG(&huart2);
      
      // 停止 DMA 接收
      HAL_UART_DMAStop(&huart2);
      
      // 计算接收长度
      uint16_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
      rx2_length = RX2_BUFFER_SIZE - temp;
      
      // 发送信号量
      BaseType_t xHigherPriorityTaskWoken = pdFALSE;
      xSemaphoreGiveFromISR(uart_rx_sem, &xHigherPriorityTaskWoken);
      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

void Gimbal_DMA_Send_record(const uint8_t *data, uint16_t size)
{
    // 记录当前发送的指令代号
    circle_write(data);
    // 底层发送
    UART2_DMA_Send(data, size);
}

void UART2_DMA_Send(const uint8_t *data, uint16_t size)
{
   
    HAL_UART_Transmit_DMA(&huart2, data, size);
}

void circle_write(uint8_t data)
{
    __disable_irq();// 关中断(ARM Cortex-M)
    uint8_t next_addr = (write_pointer + 1) & (ORDER_LIST_SIZE - 1);
    if(write_pointer == read_pointer) read_pointer = next_addr;
    order_list = data;
    write_pointer = next_addr;
    __enable_irq();   // 开中断
}

uint8_t circle_read(void)
{
    __disable_irq();// 关中断(ARM Cortex-M)
    if(write_pointer == read_pointer) return 0xFF;// 代表没有数据能读
    read_pointer = (read_pointer + 1) & (ORDER_LIST_SIZE - 1);
    return order_list;
    __enable_irq();   // 开中断
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
    if(huart->Instance == USART2) {
      if(huart->ErrorCode & HAL_UART_ERROR_DMA) {
            // 重新初始化DMA
            HAL_UART_DMAStop(huart);
            // 接收重启
            HAL_UART_Receive_DMA(huart, rx2_buffer, RX2_BUFFER_SIZE);
      }
      
    }
}

void Gimbal_Init_Func(void)
{
    UART2_DMA_Send(get_gimbal_firmware_version, sizeof(get_gimbal_firmware_version));
}

// 此处size为要发送的数据。不包括两字节的校验码
void Add_crc16_code(uint8_t *data, uint16_t size)
{
    uint16_t crc16_code = do_crc16_table(data, size);
    // 小端模式赋到数组的末尾
    data = crc16_code & 0xFF;
    data = (crc16_code >> 8) & 0xFF;
}

// 此处size为整个接收到的数据的长度,包括两位的校验码
uint8_t check_crc16(uint8_t *data, uint16_t size)
{
    uint16_t calculate_crc16_code = do_crc16_table(data, size - 2);
    if((data == ((calculate_crc16_code >> 8) & 0xFF)) && (data == (calculate_crc16_code & 0xFF)))
    {
      return 0x01;// 校验成功
    }else{
      return 0x00;// 校验失败
    }
}

void Gimbal_total_Init(void)
{
    while(video_output_mode != 0x02)
    {
      if(video_output_mode == 0x00)
      {
            beer_ring_mode = 4;
            UART2_DMA_Send(get_gimbal_config, sizeof(get_gimbal_config));
            osDelay(100);
      }else if(video_output_mode == 0x01)
      {
            UART2_DMA_Send(set_CVBS_output, sizeof(set_CVBS_output));
            beer_ring_mode = 3;
            while(1);// 会卡死在这,代表需要重新断电重启才行
      }
    }
    // 设置画面超清
    video_super_or_high_definition = 0;
    Gimbal_DMA_Send_record(set_frame_super_definition, sizeof(set_frame_super_definition));
    osDelay(100);// 延迟一段时间,一个一个初始化
    // 设置推流方式3
    current_frame_mode = 0;
    Gimbal_DMA_Send_record(set_frame_mode3, sizeof(set_frame_mode3));
    osDelay(100);// 延迟一段时间,一个一个初始化
    // 设置跟随模式
    UART2_DMA_Send(set_follow_mode, sizeof(set_follow_mode));// 无ack,不加入环形数组
    osDelay(100);// 延迟一段时间,一个一个初始化
}

void Set_Gimbal_Pitch(int16_t pitch_angle)
{
    if(pitch_angle > MAX_PITCH_ANGLE) pitch_angle = MAX_PITCH_ANGLE;
    if(pitch_angle < MIN_PITCH_ANGLE) pitch_angle = MIN_PITCH_ANGLE;
    set_gimbal_pitch = (pitch_angle >> 8) & 0xFF;// 大端模式
    set_gimbal_pitch = pitch_angle & 0xFF;
    Add_crc16_code(set_gimbal_pitch, sizeof(set_gimbal_pitch) - 2);
    Gimbal_DMA_Send_record(set_gimbal_pitch, sizeof(set_gimbal_pitch));
}


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: stm32cubemx+DMA+freertos实现usart不定长数据接收和发送