在这次的博客中我们将使用STM32串口的DMA功能,DMA是在使用串口进行通讯时常用的一个功能,使用该功能能够完成串口和内存之间直接的数据传送,而不需要CPU进行控制,从而节约CPU的处理时间。
通过实验的方式学习如何通过DMA功能读取遥控器的数据,接着将学习如何在STM32上实现用DMA进行串口输出的printf函数,并使用其将遥控器的数据传输到串口工具。
DMA全称为Direct Memory Access(直接存储器访问),当需要将外部设备发来的数据存储在存储器中时,如果不使用DMA方式则首先需要将外部设备数据先读入CPU中,再由CPU将数据存储到存储器中,如果数据量很大的话,那么将会占用大量的CPU时间,而通过使用DMA控制器直接将外部设备数据送入存储器,不需要占用CPU。
STM32中的许多通讯如USART,SPI,IIC都支持DMA方式进行数据的收发。
这里我们所使用的遥控器和STM32之间采用DBUS协议进行通讯。DBUS通讯协议和串口类似,DBUS的传输速率为100k bit/s,数据长度为8位,奇偶校验位为偶校验,结束位1位。需要注意的是DBUS使用的电平标准和串口是相反的,在DBUS协议中高电平表示0,低电平表示1,如果使用串口进行接收需要在接收电路上添加一个反相器。
使用DBUS接收遥控器的数据,一帧数据的长度为18字节,一共144位,根据遥控器的说明书可以查出各段数据的含义,从而进行数据拼接,完成遥控器的解码
首先开启USART1和USART3并进行配置,其中USART1开启串口的DMA发送,用于数据发送PC的串口工具,USART3开启串口的DMA接收,用于遥控器数据的接收;配置如下:
在Connectivity标签页下将USART1打开,将其Mode设置为Asynchronous异步通讯方式。异步通讯即发送方和接收方间不依靠同步时钟信号的通讯方式。
在Connectivity标签页下将USART3打开,将其Mode设置为Asynchronous异步通讯方式。
接着分别开启USART1和USART3的DMA功能。点开USART1的设置页面,打开DMA Settings的标签页,点击Add。
在弹出的新条目中,将DMA Request选为USART1_TX,数据从存储器流向外设,Priority选为Very High。
同样,在USART3下找到DMA Settings标签呀,在USART3中将DMA Request选为USART3_RX,数据从外设流向存储器,Priority选为Very High。
通过以上的设置,完成了cubeMX中对两个串口的DMA的设置。
点击Generate Code生成工程。
这里我们就有了一个底层代码比较完善的工程,现在我们要实现我们需要的复杂功能。
首先我们要在工程中新建一个Boards目录,并在Boards目录之下添加我们自己定义的板载设备文件:bsp_rc.c和bsp_usart.c,并将两个文件保存到我们新建工程的Src目录之下(这里注意在Keil中的文件目录结构和实际文件的结构是不一样的)
bsp_rc.c
#include "bsp_rc.h"
#include "main.h"
extern UART_HandleTypeDef huart3;
extern DMA_HandleTypeDef hdma_usart3_rx;
void RC_init(uint8_t *rx1_buf, uint8_t *rx2_buf, uint16_t dma_buf_num)
{
//enable the DMA transfer for the receiver request
//使能DMA串口接收
SET_BIT(huart3.Instance->CR3, USART_CR3_DMAR);
//enalbe idle interrupt
//使能空闲中断
__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
while(hdma_usart3_rx.Instance->CR & DMA_SxCR_EN)
{
__HAL_DMA_DISABLE(&hdma_usart3_rx);
}
hdma_usart3_rx.Instance->PAR = (uint32_t) & (USART3->DR);
//memory buffer 1
//内存缓冲区1
hdma_usart3_rx.Instance->M0AR = (uint32_t)(rx1_buf);
//memory buffer 2
//内存缓冲区2
hdma_usart3_rx.Instance->M1AR = (uint32_t)(rx2_buf);
//data length
//数据长度
hdma_usart3_rx.Instance->NDTR = dma_buf_num;
//enable double memory buffer
//使能双缓冲区
SET_BIT(hdma_usart3_rx.Instance->CR, DMA_SxCR_DBM);
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
}
bsp_usart.c
#include "bsp_usart.h"
#include "main.h"
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_tx;
void usart1_tx_dma_init(void)
{
//enable the DMA transfer for the receiver request
//使能DMA串口接收
SET_BIT(huart1.Instance->CR3, USART_CR3_DMAT);
}
void usart1_tx_dma_enable(uint8_t *data, uint16_t len)
{
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart1_tx);
while(hdma_usart1_tx.Instance->CR & DMA_SxCR_EN)
{
__HAL_DMA_DISABLE(&hdma_usart1_tx);
}
//clear flag
//清除标志位
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_HISR_TCIF7);
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_HISR_HTIF7);
//set data address
//设置数据地址
hdma_usart1_tx.Instance->M0AR = (uint32_t)(data);
//set data length
//设置数据长度
hdma_usart1_tx.Instance->NDTR = len;
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart1_tx);
}
#ifndef BSP_RC_H
#define BSP_RC_H
#include "struct_typedef.h"
extern void RC_init(uint8_t *rx1_buf, uint8_t *rx2_buf, uint16_t dma_buf_num);
extern void RC_unable(void);
extern void RC_restart(uint16_t dma_buf_num);
#endif
bsp_usart.h
#ifndef BSP_USART_H
#define BSP_USART_H
#include "struct_typedef.h"
extern void usart1_tx_dma_init(void);
extern void usart1_tx_dma_enable(uint8_t *data, uint16_t len);
#endif
struct_typedef.h
#ifndef STRUCT_TYPEDEF_H
#define STRUCT_TYPEDEF_H
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed long long int64_t;
/* exact-width unsigned integer types */
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef unsigned char bool_t;
typedef float fp32;
typedef double fp64;
#endif
/**
****************************(C) COPYRIGHT 2019 DJI****************************
* @file remote_control.c/h
* @brief 遥控器处理,遥控器是通过类似SBUS的协议传输,利用DMA传输方式节约CPU
* 资源,利用串口空闲中断来拉起处理函数,同时提供一些掉线重启DMA,串口
* 的方式保证热插拔的稳定性。
* @note 该任务是通过串口中断启动,不是freeRTOS任务
* @history
* Version Date Author Modification
* V1.0.0 Dec-01-2019 RM 1. 完成
* V1.0.1 Jan-26-2021 yzy 1. f407的一些修改
*
@verbatim
==============================================================================
==============================================================================
@endverbatim
****************************(C) COPYRIGHT 2019 DJI****************************
*/
#include "remote_control.h"
#include "main.h"
extern UART_HandleTypeDef huart3;
extern DMA_HandleTypeDef hdma_usart3_rx;
/**
* @brief remote control protocol resolution
* @param[in] sbus_buf: raw data point
* @param[out] rc_ctrl: remote control data struct point
* @retval none
*/
/**
* @brief 遥控器协议解析
* @param[in] sbus_buf: 原生数据指针
* @param[out] rc_ctrl: 遥控器数据指
* @retval none
*/
static void sbus_to_rc(volatile const uint8_t *sbus_buf, RC_ctrl_t *rc_ctrl);
//remote control data
//遥控器控制变量
RC_ctrl_t rc_ctrl;
//receive data, 18 bytes one frame, but set 36 bytes
//接收原始数据,为18个字节,给了36个字节长度,防止DMA传输越界
static uint8_t sbus_rx_buf[2][SBUS_RX_BUF_NUM];
/**
* @brief remote control init
* @param[in] none
* @retval none
*/
/**
* @brief 遥控器初始化
* @param[in] none
* @retval none
*/
void remote_control_init(void)
{
RC_init(sbus_rx_buf[0], sbus_rx_buf[1], SBUS_RX_BUF_NUM);
}
/**
* @brief get remote control data point
* @param[in] none
* @retval remote control data point
*/
/**
* @brief 获取遥控器数据指针
* @param[in] none
* @retval 遥控器数据指针
*/
const RC_ctrl_t *get_remote_control_point(void)
{
return &rc_ctrl;
}
//串口中断
void USART3_IRQHandler(void)
{
if(huart3.Instance->SR & UART_FLAG_RXNE)//接收到数据
{
__HAL_UART_CLEAR_PEFLAG(&huart3);
}
else if(USART3->SR & UART_FLAG_IDLE)
{
static uint16_t this_time_rx_len = 0;
__HAL_UART_CLEAR_PEFLAG(&huart3);
if ((hdma_usart3_rx.Instance->CR & DMA_SxCR_CT) == RESET)
{
/* Current memory buffer used is Memory 0 */
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
//get receive data length, length = set_data_length - remain_length
//获取接收数据长度,长度 = 设定长度 - 剩余长度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
//reset set_data_lenght
//重新设定数据长度
hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//set memory buffer 1
//设定缓冲区1
hdma_usart3_rx.Instance->CR |= DMA_SxCR_CT;
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
sbus_to_rc(sbus_rx_buf[0], &rc_ctrl);
}
}
else
{
/* Current memory buffer used is Memory 1 */
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
//get receive data length, length = set_data_length - remain_length
//获取接收数据长度,长度 = 设定长度 - 剩余长度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
//reset set_data_lenght
//重新设定数据长度
hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//set memory buffer 0
//设定缓冲区0
DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
//处理遥控器数据
sbus_to_rc(sbus_rx_buf[1], &rc_ctrl);
}
}
}
}
/**
* @brief remote control protocol resolution
* @param[in] sbus_buf: raw data point
* @param[out] rc_ctrl: remote control data struct point
* @retval none
*/
/**
* @brief 遥控器协议解析
* @param[in] sbus_buf: 原生数据指针
* @param[out] rc_ctrl: 遥控器数据指
* @retval none
*/
static void sbus_to_rc(volatile const uint8_t *sbus_buf, RC_ctrl_t *rc_ctrl)
{
if (sbus_buf == NULL || rc_ctrl == NULL)
{
return;
}
rc_ctrl->rc.ch[0] = (sbus_buf[0] | (sbus_buf[1] << 8)) & 0x07ff; //!< Channel 0
rc_ctrl->rc.ch[1] = ((sbus_buf[1] >> 3) | (sbus_buf[2] << 5)) & 0x07ff; //!< Channel 1
rc_ctrl->rc.ch[2] = ((sbus_buf[2] >> 6) | (sbus_buf[3] << 2) | //!< Channel 2
(sbus_buf[4] << 10)) &0x07ff;
rc_ctrl->rc.ch[3] = ((sbus_buf[4] >> 1) | (sbus_buf[5] << 7)) & 0x07ff; //!< Channel 3
rc_ctrl->rc.s[0] = ((sbus_buf[5] >> 4) & 0x0003); //!< Switch left
rc_ctrl->rc.s[1] = ((sbus_buf[5] >> 4) & 0x000C) >> 2; //!< Switch right
rc_ctrl->mouse.x = sbus_buf[6] | (sbus_buf[7] << 8); //!< Mouse X axis
rc_ctrl->mouse.y = sbus_buf[8] | (sbus_buf[9] << 8); //!< Mouse Y axis
rc_ctrl->mouse.z = sbus_buf[10] | (sbus_buf[11] << 8); //!< Mouse Z axis
rc_ctrl->mouse.press_l = sbus_buf[12]; //!< Mouse Left Is Press ?
rc_ctrl->mouse.press_r = sbus_buf[13]; //!< Mouse Right Is Press ?
rc_ctrl->key.v = sbus_buf[14] | (sbus_buf[15] << 8); //!< KeyBoard value
rc_ctrl->rc.ch[4] = sbus_buf[16] | (sbus_buf[17] << 8); //NULL
rc_ctrl->rc.ch[0] -= RC_CH_VALUE_OFFSET;
rc_ctrl->rc.ch[1] -= RC_CH_VALUE_OFFSET;
rc_ctrl->rc.ch[2] -= RC_CH_VALUE_OFFSET;
rc_ctrl->rc.ch[3] -= RC_CH_VALUE_OFFSET;
rc_ctrl->rc.ch[4] -= RC_CH_VALUE_OFFSET;
}
/**
****************************(C) COPYRIGHT 2016 DJI****************************
* @file remote_control.c/h
* @brief 遥控器处理,遥控器是通过类似SBUS的协议传输,利用DMA传输方式节约CPU
* 资源,利用串口空闲中断来拉起处理函数,同时提供一些掉线重启DMA,串口
* 的方式保证热插拔的稳定性。
* @note
* @history
* Version Date Author Modification
* V1.0.0 Dec-26-2018 RM 1. 完成
*
@verbatim
==============================================================================
==============================================================================
@endverbatim
****************************(C) COPYRIGHT 2016 DJI****************************
*/
#ifndef REMOTE_CONTROL_H
#define REMOTE_CONTROL_H
#include "struct_typedef.h"
#include "bsp_rc.h"
#define SBUS_RX_BUF_NUM 36u
#define RC_FRAME_LENGTH 18u
#define RC_CH_VALUE_MIN ((uint16_t)364)
#define RC_CH_VALUE_OFFSET ((uint16_t)1024)
#define RC_CH_VALUE_MAX ((uint16_t)1684)
/* ----------------------- RC Switch Definition----------------------------- */
#define RC_SW_UP ((uint16_t)1)
#define RC_SW_MID ((uint16_t)3)
#define RC_SW_DOWN ((uint16_t)2)
#define switch_is_down(s) (s == RC_SW_DOWN)
#define switch_is_mid(s) (s == RC_SW_MID)
#define switch_is_up(s) (s == RC_SW_UP)
/* ----------------------- PC Key Definition-------------------------------- */
#define KEY_PRESSED_OFFSET_W ((uint16_t)1 << 0)
#define KEY_PRESSED_OFFSET_S ((uint16_t)1 << 1)
#define KEY_PRESSED_OFFSET_A ((uint16_t)1 << 2)
#define KEY_PRESSED_OFFSET_D ((uint16_t)1 << 3)
#define KEY_PRESSED_OFFSET_SHIFT ((uint16_t)1 << 4)
#define KEY_PRESSED_OFFSET_CTRL ((uint16_t)1 << 5)
#define KEY_PRESSED_OFFSET_Q ((uint16_t)1 << 6)
#define KEY_PRESSED_OFFSET_E ((uint16_t)1 << 7)
#define KEY_PRESSED_OFFSET_R ((uint16_t)1 << 8)
#define KEY_PRESSED_OFFSET_F ((uint16_t)1 << 9)
#define KEY_PRESSED_OFFSET_G ((uint16_t)1 << 10)
#define KEY_PRESSED_OFFSET_Z ((uint16_t)1 << 11)
#define KEY_PRESSED_OFFSET_X ((uint16_t)1 << 12)
#define KEY_PRESSED_OFFSET_C ((uint16_t)1 << 13)
#define KEY_PRESSED_OFFSET_V ((uint16_t)1 << 14)
#define KEY_PRESSED_OFFSET_B ((uint16_t)1 << 15)
/* ----------------------- Data Struct ------------------------------------- */
typedef __packed struct
{
__packed struct
{
int16_t ch[5];
char s[2];
} rc;
__packed struct
{
int16_t x;
int16_t y;
int16_t z;
uint8_t press_l;
uint8_t press_r;
} mouse;
__packed struct
{
uint16_t v;
} key;
} RC_ctrl_t;
/* ----------------------- Internal Data ----------------------------------- */
/**
* @brief remote control init
* @param[in] none
* @retval none
*/
/**
* @brief 遥控器初始化
* @param[in] none
* @retval none
*/
extern void remote_control_init(void);
/**
* @brief get remote control data point
* @param[in] none
* @retval remote control data point
*/
/**
* @brief 获取遥控器数据指针
* @param[in] none
* @retval 遥控器数据指针
*/
extern const RC_ctrl_t *get_remote_control_point(void);
#endif
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* © Copyright (c) 2021 STMicroelectronics.
* All rights reserved.
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "remote_control.h"
#include "bsp_usart.h"
#include
#include
#include "string.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 PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
const RC_ctrl_t *local_rc_ctrl;
void usart_printf(const char *fmt,...)
{
static uint8_t tx_buf[256] = {0};
static va_list ap;
static uint16_t len;
va_start(ap, fmt);
//return length of string
//返回字符串长度
len = vsprintf((char *)tx_buf, fmt, ap);
va_end(ap);
usart1_tx_dma_enable(tx_buf, len);
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
remote_control_init();
usart1_tx_dma_init();
local_rc_ctrl = get_remote_control_point();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
usart_printf(
"**********\r\n\
ch0:%d\r\n\
ch1:%d\r\n\
ch2:%d\r\n\
ch3:%d\r\n\
ch4:%d\r\n\
s1:%d\r\n\
s2:%d\r\n\
mouse_x:%d\r\n\
mouse_y:%d\r\n\
press_l:%d\r\n\
press_r:%d\r\n\
key:%d\r\n\
**********\r\n",
local_rc_ctrl->rc.ch[0], local_rc_ctrl->rc.ch[1], local_rc_ctrl->rc.ch[2], local_rc_ctrl->rc.ch[3], local_rc_ctrl->rc.ch[4],
local_rc_ctrl->rc.s[0], local_rc_ctrl->rc.s[1],
local_rc_ctrl->mouse.x, local_rc_ctrl->mouse.y,local_rc_ctrl->mouse.z, local_rc_ctrl->mouse.press_l, local_rc_ctrl->mouse.press_r,
local_rc_ctrl->key.v);
HAL_Delay(1000);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 6;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
现在,我们的完整工程已经OK,大家可以稍微歇一歇
利用stdarg.h下的va_start函数和vsprintf函数再配合串口的DMA发送功能来实现C语言中的printf。通过以上函数的操作,将要发送的数据内容存储在tx_buf中,将要发送的数据长度存储在len变量中,接着将tx_buf的首地址和数据长度len传递给DMA发送函数,完成本次的DMA数据发送。
void usart_printf(const char *fmt,...)
{
static uint8_t tx_buf[256] = {0};
static va_list ap;
static uint16_t len;
va_start(ap, fmt);
//return length of string
//返回字符串长度
len = vsprintf((char *)tx_buf, fmt, ap);
va_end(ap);
usart1_tx_dma_enable(tx_buf, len);
}
这里我们使用USART3的DMA接收功能来接收遥控器数据。
我们通过函数remote_control_init进行USART3的DMA接收的初始化。在初始化时,使能DMA串口接收和空闲中断,配置当外设数据到达之后的存储的缓冲区,在这里开启了双缓冲区功能,每一帧sbus数据为18字节,而开启的双缓冲区总大小为36字节,这样可以避免DMA传输越界。
void RC_init(uint8_t *rx1_buf, uint8_t *rx2_buf, uint16_t dma_buf_num)
{
//enable the DMA transfer for the receiver request
//使能DMA串口接收
SET_BIT(huart3.Instance->CR3, USART_CR3_DMAR);
//enalbe idle interrupt
//使能空闲中断
__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
while(hdma_usart3_rx.Instance->CR & DMA_SxCR_EN)
{
__HAL_DMA_DISABLE(&hdma_usart3_rx);
}
hdma_usart3_rx.Instance->PAR = (uint32_t) & (USART3->DR);
//memory buffer 1
//内存缓冲区1
hdma_usart3_rx.Instance->M0AR = (uint32_t)(rx1_buf);
//memory buffer 2
//内存缓冲区2
hdma_usart3_rx.Instance->M1AR = (uint32_t)(rx2_buf);
//data length
//数据长度
hdma_usart3_rx.Instance->NDTR = dma_buf_num;
//enable double memory buffer
//使能双缓冲区
SET_BIT(hdma_usart3_rx.Instance->CR, DMA_SxCR_DBM);
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
}
在完成初始化之后,每当USART3产生空闲中断时就会进入USART3_IRQHandler进行处理,在USART3_IRQHandler中,进行寄存器中断标志位的处理,然后判断进行接收的缓冲区是1号缓冲区还是2号缓冲区,使用设定长度减去剩余长度,获取本次DMA得到的数据的长度,判断是否与一帧数据(18字节)长度相等,如果相等则调用函数sbus_to_rc进行遥控器数据的解码。
void USART3_IRQHandler(void)
{
if(huart3.Instance->SR & UART_FLAG_RXNE)
//接收到数据
{
__HAL_UART_CLEAR_PEFLAG(&huart3);
}
else if(USART3->SR & UART_FLAG_IDLE)
{
static uint16_t this_time_rx_len = 0;
__HAL_UART_CLEAR_PEFLAG(&huart3);
if ((hdma_usart3_rx.Instance->CR & DMA_SxCR_CT) == RESET)
{
/* Current memory buffer used is Memory 0 */
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
//get receive data length, length = set_data_length - remain_length
//获取接收数据长度,长度 = 设定长度 - 剩余长度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
//reset set_data_lenght
//重新设定数据长度
hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//set memory buffer 1
//设定缓冲区1
hdma_usart3_rx.Instance->CR |= DMA_SxCR_CT;
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
sbus_to_rc(sbus_rx_buf[0], &rc_ctrl);
}
}
else
{
/* Current memory buffer used is Memory 1 */
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
//get receive data length, length = set_data_length - remain_length
//获取接收数据长度,长度 = 设定长度 - 剩余长度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
//reset set_data_lenght
//重新设定数据长度
hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//set memory buffer 0
//设定缓冲区0
DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx); if(this_time_rx_len == RC_FRAME_LENGTH)
{
//处理遥控器数据
sbus_to_rc(sbus_rx_buf[1], &rc_ctrl);
}
}
}
遥控器数据处理函数sbus_to_rc的功能是将通过DMA获取到的原始数据,按照遥控器的数据协议拼接成完整的遥控器数据,以通道0的数据为例,从遥控器的用户手册中查到通道0的长度为11bit,偏移为0。
这说明如果想要获取通道0的数据就需要将第一帧的8bit数据和第二帧数据的后三bit数据拼接,如果想要获取通道1的数据就将第二帧数据的前5bit和第三帧数据的后6bit数据进行拼接,不断通过拼接就可以获得所有数据帧,拼接过程的示意图如下:
解码函数sbus_to_rc通过位运算的方式完成上述的数据拼接工作,十六进制数0x07ff的二进制是0b0000 0111 1111 1111,也就是11位的1,和0x07ff进行与运算相当于截取出11位的数据。
通道0的数据获取:首先将数据帧1和左移8位的数据帧2进行或运算,拼接出16位的数据,前8位为数据帧2,后8位为数据帧1,再将其和0x07ff相与,截取11位,就获得了由数据帧2后3位和数据帧1拼接成的通道0数据。其过程示意图如下:
通过上述方式就可以获取遥控器各个通道和开关,以及键鼠的数据值。
static void sbus_to_rc(volatile const uint8_t *sbus_buf, RC_ctrl_t *rc_ctrl)
{
if (sbus_buf == NULL || rc_ctrl == NULL)
{
return;
}
rc_ctrl->rc.ch[0] = (sbus_buf[0] | (sbus_buf[1] << 8)) & 0x07ff; //!< Channel 0
rc_ctrl->rc.ch[1] = ((sbus_buf[1] >> 3) | (sbus_buf[2] << 5)) & 0x07ff; //!< Channel 1
rc_ctrl->rc.ch[2] = ((sbus_buf[2] >> 6) | (sbus_buf[3] << 2) | //!< Channel 2
(sbus_buf[4] << 10)) &0x07ff;
rc_ctrl->rc.ch[3] = ((sbus_buf[4] >> 1) | (sbus_buf[5] << 7)) & 0x07ff; //!< Channel 3
rc_ctrl->rc.s[0] = ((sbus_buf[5] >> 4) & 0x0003); //!< Switch left
rc_ctrl->rc.s[1] = ((sbus_buf[5] >> 4) & 0x000C) >> 2; //!< Switch right
rc_ctrl->mouse.x = sbus_buf[6] | (sbus_buf[7] << 8); //!< Mouse X axis
rc_ctrl->mouse.y = sbus_buf[8] | (sbus_buf[9] << 8); //!< Mouse Y axis
rc_ctrl->mouse.z = sbus_buf[10] | (sbus_buf[11] << 8); //!< Mouse Z axis
rc_ctrl->mouse.press_l = sbus_buf[12]; //!< Mouse Left Is Press ?
rc_ctrl->mouse.press_r = sbus_buf[13]; //!< Mouse Right Is Press ?
rc_ctrl->key.v = sbus_buf[14] | (sbus_buf[15] << 8); //!< KeyBoard value
rc_ctrl->rc.ch[4] = sbus_buf[16] | (sbus_buf[17] << 8); //NULL
rc_ctrl->rc.ch[0] -= RC_CH_VALUE_OFFSET;
rc_ctrl->rc.ch[1] -= RC_CH_VALUE_OFFSET;
rc_ctrl->rc.ch[2] -= RC_CH_VALUE_OFFSET;
rc_ctrl->rc.ch[3] -= RC_CH_VALUE_OFFSET;
rc_ctrl->rc.ch[4] -= RC_CH_VALUE_OFFSET;
}
接着使用USART1用DMA方式进行发送,将接收到的遥控器数据发送出来。首先通过usart1_tx_dma_init函数进行dma发送的初始化,在主循环中,调用usart_print函数,将解码完成的遥控器数据从USART1使用DMA方式发送出来。
本程序的流程为在初始化时进行USART1的DMA发送初始化和USART3的DMA接收初始化,接着在USART3的串口接收中断中使用DMA接收遥控器的数据,并使用解码函数将数据进行解码。
接着在主循环中调用串口实现的usart_printf函数,将解码完成的遥控器函数通过USART1的DMA发送功能发送出来。
代码我已经放到了我的GitHub仓库,如有需要可以下载使用:
CubeMX学习