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[RX2_BUFFER_SIZE];
static uint8_t tx_buffer[TX2_BUFFER_SIZE];
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[ORDER_LIST_SIZE];
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[3]; // 相机固件号,可以用来判断相机初始化结束了没有,大端模式
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[7]){
case 0x01: // 获取云台固件
{
// 判断固件号有没有,有就是初始化成功了
if(!((rx2_buffer[8] == 0x00) && (rx2_buffer[9] == 0x00) && (rx2_buffer[10] == 0x00)))
{
camera_init = 1;
memcpy(camera_firmware_version, rx2_buffer + 8, 3);
}
xSemaphoreGive(gimbal_init_flag);
break;
}
case 0x0A: // 云台配置信息
{
if(rx2_buffer[14] == 0x00) video_output_mode = 0x01;
else if(rx2_buffer[14] == 0x01) video_output_mode = 0x02;
break;
}
case 0x21: // 设置画面的清晰度
{
uint8_t now_code = circle_read();
if(rx2_buffer[9] != 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[8] == 0x03) // 表示当前画面为模式3
{
if(current_frame_mode == 0x01)
{
Gimbal_DMA_Send_record(set_frame_mode7, sizeof(set_frame_mode7));
}
}else if(rx2_buffer[8] == 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[8] == 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[7]);
// 底层发送
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[next_addr] = 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[read_pointer];
__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[size] = crc16_code & 0xFF;
data[size + 1] = (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[size - 1] == ((calculate_crc16_code >> 8) & 0xFF)) && (data[size - 2] == (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[10] = (pitch_angle >> 8) & 0xFF; // 大端模式
set_gimbal_pitch[11] = 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));
}


stm32cubemx+DMA+freertos实现usart不定长数据接收和发送的更多相关文章
- STM32使用串口1配合DMA接收不定长数据,减轻CPU载荷
STM32使用串口1配合DMA接收不定长数据,减轻CPU载荷 http://www.openedv.com/thread-63849-1-1.html 实现思路:采 用STM32F103的串口1,并配 ...
- STM32之串口DMA接收不定长数据
STM32之串口DMA接收不定长数据 引言 在使用stm32或者其他单片机的时候,会经常使用到串口通讯,那么如何有效地接收数据呢?假如这段数据是不定长的有如何高效接收呢? 同学A:数据来了就会进入串口 ...
- STM32 HAL库使用中断实现串口接收不定长数据
以前用DMA实现接收不定长数据,DMA的方法接收串口助手的数据,全部没问题,不过如果接收模块返回的数据,而这些数据如果包含回车换行的话就会停止接收,例如接收:AT\r\nOK\r\n,就只能接收到AT ...
- 关于socket客户端接收不定长数据的解决方案
#!/usr/bin/env python3.5 # -*-coding:utf8-*- """ 本实例客户端用于不断接收不定长数据,存储到变量res "&qu ...
- STM32CubeMX HAL库串口+DMA数据发送不定长度数据接收
参考资料:1.ST HAL库官网资料 2.https://blog.csdn.net/u014470361/article/details/79206352#comments 一.STM32CubeM ...
- STM32串口接收不定长数据原理与源程序(转)
今天说一下STM32单片机的接收不定长度字节数据的方法.由于STM32单片机带IDLE中断,所以利用这个中断,可以接收不定长字节的数据,由于STM32属于ARM单片机,所以这篇文章的方法也适合其他的A ...
- Python3的tcp socket接收不定长数据包接收到的数据不全。
Python Socket API参考出处:http://blog.csdn.net/xiangpingli/article/details/47706707 使用socket.recv(pack_l ...
- udp协议的数据接收与发送的代码
我想基于lwIP协议中的UDP协议,用单片机做一个服务器,接受电脑的指令然后返回数据.以下是我的代码 /************************************************ ...
- 串口配合DMA接收不定长数据(空闲中断+DMA接收)-(转载)
1.空闲中断和别的接收完成(一个字节)中断,发送完成(发送寄存器控)中断的一样是串口中断: 2.空闲中断是接收到一个数据以后,接收停顿超过一字节时间 认为桢收完,总线空闲中断是在检测到在接收数据后, ...
- 串口1配合DMA接收不定长数据(空闲中断+DMA接收)
1.空闲中断和别的接收完成(一个字节)中断,发送完成(发送寄存器控)中断的一样是串口中断: 2.空闲中断是接收到一个数据以后,接收停顿超过一字节时间 认为桢收完,总线空闲中断是在检测到在接收数据后, ...
随机推荐
- The selected directory is not a valid home for Go SDK
前言 The selected directory is not a valid home for Go SDK 出现这个错误的原因是 idea 的 Go-plugin 插件,和 Go 的sdk版本不 ...
- HTTP/1.1、HTTP/2、HTTP/3
HTTP/1.1 相比 HTTP/1.0 性能上的改进: 使用长连接的方式改善了 HTTP/1.0 短连接造成的性能开销. 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来, ...
- 冒泡排序(LOW)
博客地址:https://www.cnblogs.com/zylyehuo/ # _*_coding:utf-8_*_ import random def bubble_sort(li): for i ...
- PLSQL中查询数据的时候查询结果显示中文乱码
要需要很努力才能看起来毫不费力.....1.在PLSQL中查询数据的时候查询结果显示中文乱码这里写图片描述2.需要在环境变量中新建两个环境变量:第一个:设置 NLS_LANG=SIMPLIFIED C ...
- UNIQUE VISION Programming Contest 2025 Spring (AtCoder Beginner Contest 398) (A~F) 补题+题解
A - Doors in the Center 签到题,直接构造即可. 点击查看代码 #include<bits/stdc++.h> using namespace std; #defin ...
- ANSYS实体单元施加扭矩方法分析
ANSYS 结构分析单元与应用-王新敏等(P199) 此处以等截面椭圆柱为例. 对实体单元施加扭矩,主要方法如下: 引入质量单元 MASS21 并新建顶面的中心节点,随后将顶面所有节点通过 cerig ...
- 渗透技巧——CDN绕过
渗透技巧--CDN绕过 一.前言: 在渗透站点的时候常常会遇见站点有CDN加速情况,就无法准确的找到目标IP.首先是检测如何发现有无CDN,然后才能说绕过的问题. 二.检测有无CDN: 首先有以下几种 ...
- 第10章面向对象编程(高级部分)-cnblog
类变量与类方法 static修饰的成员变量(类变量,静态变量)的特性? 同一个类所有对象共享 类变量是随着类的加载而创建, 所以即使没有创建对象实例也可以访问 ,但是类变量的访问, 必须遵守 相关的访 ...
- C# 多项目打包时如何将项目引用转为包依赖
项目背景 最近开发一组类库,大约会有五六个项目.一个Core,加上若干面向不同产品的实现库,A/B/C/D...它们都依赖Core. 首先,我想统一版本号,这个容易,通过Directory.Build ...
- chrony时间同步软件介绍
本文分享自天翼云开发者社区<chrony时间同步软件介绍>,作者:刘****苏 chrony是网络时间协议NTP的通用实现,它可以将系统时钟和`NTP服务器同步.它支持在各种条件下包括间歇 ...