首页 > 其他分享 >【STM32】RTT-Studio中HAL库开发教程二:RS485-DMA串行通信

【STM32】RTT-Studio中HAL库开发教程二:RS485-DMA串行通信

时间:2024-07-06 19:00:21浏览次数:14  
标签:DMA HAL UART RS485 Init GPIO

文章目录

一、前期准备

开发环境: 基于RT-Thread Studio软件的开发
辅助软件: STM32CubeMX初始化代码生成
调试软件: 串口助手
使用芯片: STM32F407VET6
硬件环境: 外接USB转RS485通信线,控制板

二、实验步骤

1.使用STM32CubeMX配置初始化代码

(1)配置时钟:
配置RCC,将RCC配置为外部时钟,外部低速时钟和外部高速时钟都可以配置,并且会自动配置IO引脚:
在这里插入图片描述

在SYS中配置程序烧录口,使用SWD进行程序烧录:
在这里插入图片描述

配置时钟树,外部时钟配置为8MHz,将HCLK时钟配置为168MHz,保证时钟性能最佳:
在这里插入图片描述

(2)配置串口USART3的参数

  • Mode:设置为异步通信(Asynchronous)
  • 基础参数:波特率为9600Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1 接收和发送都使能 (默认的就行)
    在这里插入图片描述

(3)DMA中断设置:DMA Settings ——> Add,选择USART3_RX,接着同样步骤再把USART3_TX,添加。选择Normal发送模式。

  • DMA Request(DMA请求): USART3_RX、USART3_TX
  • Dirction (DMA传输方向): Peripheral To Memory(外设到内存)、Memory To Peripheral(内存到外设 )
  • Priority(优先级): 低、低
  • Mode(模式): 正常、正常
    在这里插入图片描述

(4)打开串口中断以及设置优先级
在这里插入图片描述
在这里插入图片描述

(6)配置RS485控制引脚:设置成GPIO输出模式,默认为低电平接收模式,并且设置为上拉:
在这里插入图片描述

(7)生成代码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.常用函数解析

// 发送和接收函数
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);  // 串口发送数据,使用超时管理机制
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);   // 串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);		// 串口中断模式发送(只触发一次中断)
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);		// 串口中断模式接收(只触发一次中断)
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); 	// 串口DMA模式发送
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);	// 串口DMA模式接收
HAL_UART_GetState(UART_HandleTypeDef *huart);	// 判断接收与发送是否结束

// 回调函数,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);		// 接收中断回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); 	    // 发送中断回调函数
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); 	// 串口发送一半中断回调函数
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);	// 串口接收一半回调函数
  • LUART_HandleTypeDef *huart 串口的别名 如 : 我们使用串口USART1的别名就是huart1。
  • *pData 需要发送的数据
  • Size 发送的字节数
  • Timeout 最大发送时间
  • HAL_UART_STATE_BUSY_RX,接收完成标志
  • HAL_UART_STATE_BUSY_TX,发送完成标志

3.相关程序

(1)RS485.c相关程序

#include <rs485.h>

/*======================================================### 静态函数调用 ###==================================================*/
/**
 * @brief RS485GPIO控制引脚初始化
 */
static void RS485_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOB, DE_485_Pin | RE_485_Pin, GPIO_PIN_RESET);

    /*Configure GPIO pins : PBPin PBPin */
    GPIO_InitStruct.Pin = DE_485_Pin | RE_485_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

/**
 * @brief RS485的DMA中断使能和优先级配置
 */
static void RS485_DMA_Init(void)
{
    /* DMA controller clock enable */
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* DMA interrupt init */
    /* DMA1_Stream1_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 4, 0);
    HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);

    /* DMA1_Stream3_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn);
}

/**
 * @brief RS485串口3初始化
 */
static void RS485_USART3_Init(void)
{
    huart3.Instance = USART3;
    huart3.Init.BaudRate = 9600;
    huart3.Init.WordLength = UART_WORDLENGTH_8B;
    huart3.Init.StopBits = UART_STOPBITS_1;
    huart3.Init.Parity = UART_PARITY_NONE;
    huart3.Init.Mode = UART_MODE_TX_RX;
    huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart3.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huart3) != HAL_OK)
    {
        Error_Handler();
    }

    // 使能IDLE中断
    __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);

    // DMA接收函数
    HAL_UART_Receive_DMA(&huart3, g_rs485.RxBuff, BUFFER_SIZE);
}

/**
 * @brief 串口初始化
 * @param uartHandle
 */
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if (uartHandle->Instance == USART3)
    {
        /* USART3 clock enable */
        __HAL_RCC_USART3_CLK_ENABLE();
        __HAL_RCC_GPIOD_CLK_ENABLE();

        /**USART3 GPIO Configuration
        PD8     ------> USART3_TX
        PD9     ------> USART3_RX
         */
        GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
        HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

        /* USART3 DMA Init */
        /* USART3_RX Init */
        hdma_usart3_rx.Instance = DMA1_Stream1;
        hdma_usart3_rx.Init.Channel = DMA_CHANNEL_4;
        hdma_usart3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
        hdma_usart3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_usart3_rx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_usart3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_usart3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_usart3_rx.Init.Mode = DMA_NORMAL;
        hdma_usart3_rx.Init.Priority = DMA_PRIORITY_LOW;
        hdma_usart3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        if (HAL_DMA_Init(&hdma_usart3_rx) != HAL_OK)
        {
            Error_Handler();
        }
        __HAL_LINKDMA(uartHandle, hdmarx, hdma_usart3_rx);

        /* USART3_TX Init */
        hdma_usart3_tx.Instance = DMA1_Stream3;
        hdma_usart3_tx.Init.Channel = DMA_CHANNEL_4;
        hdma_usart3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
        hdma_usart3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_usart3_tx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_usart3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_usart3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_usart3_tx.Init.Mode = DMA_NORMAL;
        hdma_usart3_tx.Init.Priority = DMA_PRIORITY_LOW;
        hdma_usart3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        if (HAL_DMA_Init(&hdma_usart3_tx) != HAL_OK)
        {
            Error_Handler();
        }
        __HAL_LINKDMA(uartHandle, hdmatx, hdma_usart3_tx);

        /* USART3 interrupt Init */
        HAL_NVIC_SetPriority(USART3_IRQn, 5, 0);
        HAL_NVIC_EnableIRQ(USART3_IRQn);
    }
}
/*=====================================================#######  END  #######=================================================*/

/*======================================================##### 外部调用 #####==================================================*/
/**
 * @brief RS485初始化
 */
void RS485_Init(void)
{
    RS485_GPIO_Init();
    RS485_DMA_Init();
    RS485_USART3_Init();
}

/**
 * @brief RS485串口发送函数
 * @param buf:发送数据
 * @param len:发送数据长度
 */
void RS485_UART3_DMA_Send(uint8_t *buf, uint8_t len)
{
    // 发送模式
    RS485_RE(1);
    HAL_UART_Transmit_DMA(&huart3, buf, len);
}
/*=====================================================#######  END  #######=================================================*/

/*======================================================##### 中断函数 #####==================================================*/
/**
 * @brief 串口3中断函数
 */
void USART3_IRQHandler(void)
{
    uint32_t temp;

    if ((__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE) != RESET))
    {
        /* 清除状态寄存器和串口数据寄存器 */
        __HAL_UART_CLEAR_IDLEFLAG(&huart3);

        /* 失能DMA接收 */
        HAL_UART_DMAStop(&huart3);

        /* 读取接收长度,总大小-剩余大小 */
        temp = __HAL_DMA_GET_COUNTER(&hdma_usart3_rx);
        g_rs485.RxLen = BUFFER_SIZE - temp;

        /* 接收标志位置1 */
        g_rs485.RxEndFlag = 1;

        /* 使能接收DMA接收 */
        HAL_UART_Receive_DMA(&huart3, g_rs485.RxBuff, BUFFER_SIZE);

        /* 开启空闲中断,当时没有数据的时候中断*/
        __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
    }

    HAL_UART_IRQHandler(&huart3);
}

/**
 * @brief 接收中断
 */
void DMA1_Stream1_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_usart3_rx);
}

/**
 * @brief 发送中断
 */
void DMA1_Stream3_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_usart3_tx);
}

/**
 * @brief DMA 发送完成回调函数
 * @param huart
 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart3)
    {
        // 切换为 RS485 接收模式
        RS485_RE(0);

        // 清除接收到的数据
        memset(g_rs485.RxBuff, 0, g_rs485.RxLen);

        // 清除计数
        g_rs485.RxLen = 0;
    }
}
/*=====================================================#######  END  #######=================================================*/

(2)RS485.h相关程序

#ifndef APPLICATIONS_RS485_H_
#define APPLICATIONS_RS485_H_

#include <rtthread.h>
#include <drv_common.h>
#include <string.h>

/**=====================================================###### 宏定义 ######==================================================*/
#define DE_485_Pin          GPIO_PIN_14         // DE发送使能,高电平有效
#define DE_485_GPIO_Port    GPIOB
#define RE_485_Pin          GPIO_PIN_15         // RE接收使能,低电平有效
#define RE_485_GPIO_Port    GPIOB

#define BUFFER_SIZE         255                 // 接收数据大小
/**====================================================#######  END  #######=================================================*/

/**=====================================================### 全局变量定义 ####=================================================*/
UART_HandleTypeDef huart3;
DMA_HandleTypeDef hdma_usart3_rx;
DMA_HandleTypeDef hdma_usart3_tx;

/* 控制RS485_RE脚, 控制RS485发送/接收状态
 * RS485_RE = 0, 进入接收模式
 * RS485_RE = 1, 进入发送模式
 */
#define RS485_RE(x)   do{ x ? \
                          HAL_GPIO_WritePin(GPIOB, DE_485_Pin | RE_485_Pin, GPIO_PIN_SET) :  \
                          HAL_GPIO_WritePin(GPIOB, DE_485_Pin | RE_485_Pin, GPIO_PIN_RESET); \
                      }while(0)

typedef struct
{
    uint8_t RxBuff[BUFFER_SIZE];    // 接收缓冲器
    uint8_t TxBuff[BUFFER_SIZE];    // 发送缓冲器
    uint8_t TxEndFlag;              // 发送完成标志
    uint8_t RxEndFlag;              // 接收完成标志
    uint8_t RxLen;                  // 接收数据长度

} rs485_t;

rs485_t g_rs485;
/**====================================================#######  END  #######=================================================*/

/**==================================================##### 函数及变量声明 #####===============================================*/
extern void RS485_Init(void);                                   // RS485初始化
extern void RS485_UART3_DMA_Send(uint8_t *buf, uint8_t len);    // RS485串口发送函数
/**====================================================#######  END  #######=================================================*/

#endif /* APPLICATIONS_RS485_H_ */

(3)main.c相关程序

#include <rtthread.h>
#include <drv_common.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "rs485.h"

int main(void)
{
    int count = 1;

    // RS485初始化
    RS485_Init();

    // 测试发送函数
    for (int i = 0; i < 5; ++i)
    {
        g_rs485.TxBuff[i] = 0x0A + i;
    }
    // 发送数据
    RS485_UART3_DMA_Send(g_rs485.TxBuff, 5);

    while (count)
    {
        // 接收完成标志
        if (g_rs485.RxEndFlag == 1)
        {
            // 发送接收到的数据
            RS485_UART3_DMA_Send(g_rs485.RxBuff, g_rs485.RxLen);

            // 清除接收结束标志位
            g_rs485.RxEndFlag = 0;
        }

        rt_thread_mdelay(1);
    }

    return RT_EOK;
}

4.实验效果

通过发送ModBus的指令,来测试程序是否可以使用,并且串口可以自动加CRC16校验:
在这里插入图片描述

三、参考文章

===》》》RS485使用中断进行数据收发

标签:DMA,HAL,UART,RS485,Init,GPIO
From: https://blog.csdn.net/Hei_se_meng_yan/article/details/140178025

相关文章

  • STM32F1+HAL库+FreeTOTS学习6——临界段代码保护函数&任务调度器的挂起和恢复函数
    STM32F1+HAL库+FreeTOTS学习6——临界段代码保护函数临界段临界段代码保护函数任务调度器的挂起和恢复函数上一期我们学习了FreeRTOS的内核中断管理以及中断屏蔽控制函数,下面我们来学习临界端代码保护函数的使用临界段临界段也叫临界区,指的是必须完整运行完,不能被......
  • STM32F1+HAL库+FreeTOTS学习3——任务创建(动态和静态两种)
    STM32F1+HAL库+FreeTOTS学习3——任务创建(动态和静态两种)任务创建API函数任务创建流程代码实现1.动态任务创建和删除2.静态任务创建和删除上期我们学习了STM32移植FreeRTOS搭建基准工程,现在我们来学习任务创建任务创建API函数前面我们了解到,FreeRTOS相对于裸机......
  • WS2812b彩灯(DMA+PWM)
    一.WS2812B简介        WS2812B是一种数字可编程的LED灯条,可以使用单个数据线进行通讯控制LED灯的颜色和亮度。每个WS2812B都有一个唯一的地址,可以通过单个数据线进行级联。二.WS2812B参数简介 三.WS2812B数据通讯简介 1.级联电路2.数据传输    ......
  • IP核:XDMA学习
    知识:XDMA效率没有RIFFA高,最高似乎只支持1288位宽;输出的用户时钟是250MHZ;IP核配置:参考:https://docs.amd.com/r/zh-CN/pg195-pcie-dma/具体: 重要:PCIe:BARs标签页该标签页主要用于配置BAR,所谓的使能和配置各接口interface,其实质是配置不同的BAR,首先介绍下什么是BAR......
  • Halcon 学习笔记(1):常用套路和算子
    目录前言套路常用算子图形色相消息Region处理特征值逻辑前言这里总结常用算子和套路套路预处理:设置图片大小,RGB通道设置找到Region,特征值分割:形状:球,正方形,椭圆,长方形面积:亮度:滤波,过滤不需要的图像输出结果常用算子图形read_image:得到imagedev_open_windowde......
  • 学习笔记(0):重拾Halcon
    目录前言教学视频前言了解我的人可能知道,我其实很想回去全职做外贸,但是大环境不好,淘宝做了3个月,1688做了1个月。我只能说销量很惨淡。现在打算还是老老实实上班去了。教学视频我之前找一个B站UP主,买了一下他的教学视频。600块钱,总共有40集,大概10个小时。大概需要一个星期学完,......
  • STM32F1+HAL库+FreeTOTS学习5——内核中断管理及中断控制函数
    STM32F1+HAL库+FreeTOTS学习5——中断管理和临界段代码保护中断简介中断优先级寄存器拓展FreeRTOS中PendSV和Systick中断优先级配置三个中断屏蔽寄存器FreeRTOS中断管理函数代码验证上一期我们学习了FreeRTOS中任务挂起与恢复,在中断服务程序中恢复任务过程中,尤其强调......
  • BACON: Supercharge Your VLM with Bag-of-Concept Graph to Mitigate Hallucinations
    目录概BACON代码[YangZ.,FengR.,etal.BACON:Superchargeyourvlmwithbag-of-conceptgraphtomitigatehallucinations.2024.]概本文提出了一种新的数据格式:BACON(BAg-of-Conceptgraph).BACONBACON希望将一个图片转换为\(G=(D,O,R,B)\)的数据格式......
  • 1panel搭建halo+alist+兰空图床
    由于服务器更新过后,ssh连接不上,机商vnc延迟卡的很,输个命令都不行,刚好centos断更了,换ubuntu系统了。也顺便从MySQL转到PostgreSQL。这个算是个记录,给有基础的人看的,你连ssh都不会连,那你得自己一步一步慢慢百度来。其实还是比较无脑的,除了1panel的反代,感觉设计对小白来说不是那么友......
  • 【2024datawhale 分子AI预测赛笔记】数据挖掘速通Baseline -分类/回归
    赛题概述精准预测分子性质有助于高效筛选出具有优异性能的候选药物。以PROTACs为例,它是一种三元复合物由目标蛋白配体、linker、E3连接酶配体组成,靶向降解目标蛋白质。(研究PROTACs技术在靶向降解目标蛋白质方面的潜力。)提醒:需要python和机器学习基础。赛事任务根据提......