STM32 CubeMX学习:9. 串口打印遥控器数据

STM32 CubeMX学习:9. 串口打印遥控器数据

系列文章目录
  1. 前期的准备
  2. 点亮 LED
  3. 闪烁 LED
  4. 定时器闪烁LED
  5. PWM控制LED亮度
  6. 常见的PWM设备
  7. 按键的外部中断
  8. ADC模数转换
  9. 串口收发
  10. 串口打印遥控器数据
  11. 未完待续…


0 前言

在这次的博客中我们将使用STM32串口的DMA功能,DMA是在使用串口进行通讯时常用的一个功能,使用该功能能够完成串口和内存之间直接的数据传送,而不需要CPU进行控制,从而节约CPU的处理时间。
通过实验的方式学习如何通过DMA功能读取遥控器的数据,接着将学习如何在STM32上实现用DMA进行串口输出的printf函数,并使用其将遥控器的数据传输到串口工具。

1. 基础学习

1.1 DMA功能介绍

DMA全称为Direct Memory Access(直接存储器访问),当需要将外部设备发来的数据存储在存储器中时,如果不使用DMA方式则首先需要将外部设备数据先读入CPU中,再由CPU将数据存储到存储器中,如果数据量很大的话,那么将会占用大量的CPU时间,而通过使用DMA控制器直接将外部设备数据送入存储器,不需要占用CPU。

STM32中的许多通讯如USART,SPI,IIC都支持DMA方式进行数据的收发。

1.2 DBUS协议介绍

这里我们所使用的遥控器和STM32之间采用DBUS协议进行通讯。DBUS通讯协议和串口类似,DBUS的传输速率为100k bit/s,数据长度为8位,奇偶校验位为偶校验,结束位1位。需要注意的是DBUS使用的电平标准和串口是相反的,在DBUS协议中高电平表示0,低电平表示1,如果使用串口进行接收需要在接收电路上添加一个反相器。

使用DBUS接收遥控器的数据,一帧数据的长度为18字节,一共144位,根据遥控器的说明书可以查出各段数据的含义,从而进行数据拼接,完成遥控器的解码
遥控器

2. 程序学习

2.1 串口发送的DMA配置

首先开启USART1和USART3并进行配置,其中USART1开启串口的DMA发送,用于数据发送PC的串口工具,USART3开启串口的DMA接收,用于遥控器数据的接收;配置如下:

  1. 在Connectivity标签页下将USART1打开,将其Mode设置为Asynchronous异步通讯方式。异步通讯即发送方和接收方间不依靠同步时钟信号的通讯方式。

  2. 将其波特率设置为115200,数据帧设置为8位数据位,无校验位,1位停止位。
    USART1

  3. 在Connectivity标签页下将USART3打开,将其Mode设置为Asynchronous异步通讯方式。

  4. 将其波特率设置为100000,数据帧设置为8位数据位,无校验位,1位停止位。
    USART3

  5. 接着分别开启USART1和USART3的DMA功能。点开USART1的设置页面,打开DMA Settings的标签页,点击Add。
    打开DMA

  6. 在弹出的新条目中,将DMA Request选为USART1_TX,数据从存储器流向外设,Priority选为Very High。
    DMA设置

  7. 同样,在USART3下找到DMA Settings标签呀,在USART3中将DMA Request选为USART3_RX,数据从外设流向存储器,Priority选为Very High。
    DMA开启和设置
    通过以上的设置,完成了cubeMX中对两个串口的DMA的设置。

  8. 点击Generate Code生成工程。
    这里我们就有了一个底层代码比较完善的工程,现在我们要实现我们需要的复杂功能。

  9. 首先我们要在工程中新建一个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);
}



  1. 然后我们新建对应的h文件保存到文件工程的Inc目录中以及我们常用的结构体定义头文件
    bsp_rc.h
#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




  1. 接着我们在Keil工程里新建application目录,并在该目录中添加remote_control.c文件
/**
  ****************************(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;
}

  1. 然后我们新建对应的h文件保存到文件工程的Inc目录中
    remote_control.h
/**
  ****************************(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

  1. 然后我们修改main.c文件
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * 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 <stdio.h>
#include <stdarg.h>
#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,大家可以稍微歇一歇

2.2 printf函数实现过程

利用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); 
}

2.3 串口的DMA接收与发送配置

这里我们使用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方式发送出来。

2.4 程序流程

本程序的流程为在初始化时进行USART1的DMA发送初始化和USART3的DMA接收初始化,接着在USART3的串口接收中断中使用DMA接收遥控器的数据,并使用解码函数将数据进行解码。
接着在主循环中调用串口实现的usart_printf函数,将解码完成的遥控器函数通过USART1的DMA发送功能发送出来。

代码我已经放到了我的GitHub仓库,如有需要可以下载使用:
CubeMX学习


总结

这次的博客,我们学习了串口的DMA发送接收功能。DMA发送接收功能使得CPU能够高效的完成数据接收发送,此外我们还学习了遥控器的接收和解码,以及使用串口实现printf功能。遥控器是RM机器人最重要的人机交互装置,用于机器人的控制输入。Printf函数是标准化输出的函数,主要用于调试的功能。
  • 8
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值