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

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

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

文章目录

  • STM32 CubeMX学习:9. 串口打印遥控器数据
  • 0 前言
  • 1. 基础学习
    • 1.1 DMA功能介绍
    • 1.2 DBUS协议介绍
  • 2. 程序学习
    • 2.1 串口发送的DMA配置
    • 2.2 printf函数实现过程
    • 2.3 串口的DMA接收与发送配置
    • 2.4 程序流程
  • 总结


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位,根据遥控器的说明书可以查出各段数据的含义,从而进行数据拼接,完成遥控器的解码
STM32 CubeMX学习:9. 串口打印遥控器数据_第1张图片

2. 程序学习

2.1 串口发送的DMA配置

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

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

  2. 将其波特率设置为115200,数据帧设置为8位数据位,无校验位,1位停止位。
    STM32 CubeMX学习:9. 串口打印遥控器数据_第2张图片

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

  4. 将其波特率设置为100000,数据帧设置为8位数据位,无校验位,1位停止位。
    STM32 CubeMX学习:9. 串口打印遥控器数据_第3张图片

  5. 接着分别开启USART1和USART3的DMA功能。点开USART1的设置页面,打开DMA Settings的标签页,点击Add。
    STM32 CubeMX学习:9. 串口打印遥控器数据_第4张图片

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

  7. 同样,在USART3下找到DMA Settings标签呀,在USART3中将DMA Request选为USART3_RX,数据从外设流向存储器,Priority选为Very High。
    STM32 CubeMX学习:9. 串口打印遥控器数据_第5张图片
    通过以上的设置,完成了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
  *
  * 

© 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,大家可以稍微歇一歇

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。
STM32 CubeMX学习:9. 串口打印遥控器数据_第6张图片
这说明如果想要获取通道0的数据就需要将第一帧的8bit数据和第二帧数据的后三bit数据拼接,如果想要获取通道1的数据就将第二帧数据的前5bit和第三帧数据的后6bit数据进行拼接,不断通过拼接就可以获得所有数据帧,拼接过程的示意图如下:
STM32 CubeMX学习:9. 串口打印遥控器数据_第7张图片
解码函数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数据。其过程示意图如下:
STM32 CubeMX学习:9. 串口打印遥控器数据_第8张图片
通过上述方式就可以获取遥控器各个通道和开关,以及键鼠的数据值。

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函数是标准化输出的函数,主要用于调试的功能。

你可能感兴趣的:(cubemx,嵌入式,stm32,c语言,单片机)