本文章主要记录cubemx生成串口代码,并且移植缓冲fifo和创建接口函数准备的步骤,保姆级别的创建,分为一下几个步骤。
1、cubemx配置uart+中断
第一步-新建工程
1.选择MCU-stm32f407vgt6
第二步-配置DMA
2.配置时钟等必配功能后,打开配置串口的DMA如下
DMA接收必须打开循环模式,如下图
第三步-配置中断
3.配置时钟等必配功能后,打开配置串口的中断如下(这里打开了6个串口,都是相同的配置)
第四步-生成
4.点击GENERATE CODE直接生成
2、移植缓冲fifo文件
第一步-添加BSP、APP文件夹
4.在生成的文件夹中添加文件夹BSP、APP,并在其子目录建立source include 两目录如下:
第二步-准备cfifo.c fifo.h
cfifo.c
#include "cfifo.h"
/*环形CFIFO初始化*/
void CfifoBuff_Init(CfifoBuff * Cfifo_pointer)
{
//初始化相关信息
Cfifo_pointer->Head = 0;
Cfifo_pointer->Tail = 0;
Cfifo_pointer->Lenght = 0;
memset(Cfifo_pointer->BUFF,'\0',CFIFO_SIZE); //环形CFIFO缓存区初始化
}
/*环形CFIFO数据清除*/
void CfifoBuff_Clear(CfifoBuff * Cfifo_pointer)
{
//清除相关信息
Cfifo_pointer->Head = 0;
Cfifo_pointer->Tail = 0;
Cfifo_pointer->Lenght = 0;
memset(Cfifo_pointer->BUFF,'\0',CFIFO_SIZE); //环形CFIFO缓存区清除
}
/*环形CFIFO数据写入*/
/*
*参数说明:
* Cfifo_pointer————环形CFIFO结构体
* User_buff————待写入数据
* num————写入数据长度
*
*返回值说明:正确写入到FIFO缓存区中的数据长度
*
*功能说明:将User_buff中的数据写入到环形CFIFO缓存区
*
*/
int16_t CfifoBuff_Write(CfifoBuff * Cfifo_pointer,char * User_buff,uint16_t num)
{
int16_t i , wrint_num;
if(Cfifo_pointer->Lenght >= CFIFO_SIZE) //判断缓存区是否已满
{
wrint_num=-1;
return wrint_num; //数据溢出
}
if(Cfifo_pointer->Lenght+num<CFIFO_SIZE) //判断写入的数据长度是否超出当前可写入的最大值
{
wrint_num=num;
}
else
{
wrint_num=CFIFO_SIZE-Cfifo_pointer->Lenght;
}
for(i=0;i<wrint_num;i++)
{
Cfifo_pointer->BUFF[Cfifo_pointer->Tail]=*(User_buff+i);
Cfifo_pointer->Tail = (Cfifo_pointer->Tail+1)%CFIFO_SIZE;//防止越界非法访问
}
Cfifo_pointer->Lenght+=wrint_num;
return wrint_num; //返回正确写入的数据长度
}
/*环形CFIFO读取*/
/*
*参数说明:
* Cfifo_pointer————环形CFIFO结构体
* User_buff————读取数据存放地
* num————读取数据长度
*
*返回值说明:正确读取到User_buff的数据长度
*
*功能说明:将环形CFIFO缓存区的数据读取到User_buff
*
*/
int16_t CfifoBuff_Read(CfifoBuff * Cfifo_pointer,char * User_buff,uint16_t num)
{
int16_t i , read_num;
if(Cfifo_pointer->Lenght == 0) //判断非空
{
read_num=-1;
return read_num; //没有数据
}
if(Cfifo_pointer->Lenght-num>=0) //判断读取的数据长度是否超出当前可读取的最大值
{
read_num=num;
}
else
{
read_num=Cfifo_pointer->Lenght;
}
for(i=0;i<read_num;i++)
{
*(User_buff+i)=Cfifo_pointer->BUFF[Cfifo_pointer->Head];
Cfifo_pointer->Head = (Cfifo_pointer->Head+1)%CFIFO_SIZE;//防止越界非法访问
}
Cfifo_pointer->Lenght-=read_num;
return read_num; //返回正确写入的数据长度
}
cfifo.h
#ifndef _CFIFO_H_
#define _CFIFO_H_
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN Private defines */
#define CFIFO_SIZE 1024 //环形队列CFIFO大小
/*环形CFIFO结构体*/
typedef struct
{
uint16_t Head; //环形CFIFO队列头
uint16_t Tail; //环形CFIFO队列尾
uint16_t Lenght; //环形CFIFO数据长度
uint8_t BUFF[CFIFO_SIZE]; //环形CFIFO缓存区
}CfifoBuff;
/* USER CODE END Private defines */
/* USER CODE BEGIN Prototypes */
void CfifoBuff_Init(CfifoBuff * Cfifo_pointer); //CFIFO初始化
void CfifoBuff_Clear(CfifoBuff * Cfifo_pointer); //CFIFO数据清除
int16_t CfifoBuff_Write(CfifoBuff * Cfifo_pointer,char * User_buff,uint16_t num); //CFIFO数据写人
int16_t CfifoBuff_Read(CfifoBuff * Cfifo_pointer,char * User_buff,uint16_t num); //CFIFO数据读出
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* _CFIFO_H_ */
第三步-准备cfifo_usart_dma.c cfifo_usart_dma.h
cfifo_usart_dma.c
#include "cfifo_usart_dma.h"
static uint8_t DbgTxDma[MW_UART_DMA_LEN] = {0}; //DMA发送缓存区
static uint8_t DbgRxDma[MW_UART_DMA_LEN] = {0}; //DMA接收缓存区
static uint8_t E2KTxDma[MW_UART_DMA_LEN] = {0}; //DMA发送缓存区
static uint8_t E2KRxDma[MW_UART_DMA_LEN] = {0}; //DMA接收缓存区
static uint8_t FPGATxDma[MW_UART_DMA_LEN] = {0}; //DMA发送缓存区
static uint8_t FPGARxDma[MW_UART_DMA_LEN] = {0}; //DMA接收缓存区
MW_UART_ATTR E2K_UartAttr;
MW_UART_ATTR FPGA_UartAttr;
MW_UART_ATTR Dbg_UartAttr;
/**
* @brief 初始化串口维护的循环队列
* @param sUartAttr 维护串口循环队列的结构体 包括发送接收缓冲fifo、dma的偏移量、发送状态、dma的缓冲大小
* @param huart 维护的串口号 huartx(x = 1.2.3) 在usart.c中定义
* @param RxDma DMA接收数据缓冲区
* @param TxDma DMA接收数据缓冲区
* @retval 初始化成功状态 成功:MW_FAIL 失败:MW_SUCCESS
*/
int8_t MW_UART_Init(MW_UART_ATTR *sUartAttr, UART_HandleTypeDef *huart, uint8_t *RxDma, uint8_t *TxDma)
{
MW_UART_ATTR *pUartAttr = NULL;
/*为属性的参数附初值 */
pUartAttr = sUartAttr;
pUartAttr->handle = huart;
pUartAttr->DamOffset = 0;
pUartAttr->TransFlag = MW_TRANS_IDLE;
pUartAttr->DmaSize = MW_UART_DMA_LEN;
pUartAttr->pReadDma = RxDma;
pUartAttr->pWriteDma = TxDma;
CfifoBuff_Init(&pUartAttr->AcceptCFifo); //用于数据接受
CfifoBuff_Init(&pUartAttr->SendCFifo); //用于数据发送
/*配置DMA参数并使能中断*/
if(HAL_OK != HAL_UART_Receive_DMA(pUartAttr->handle, pUartAttr->pReadDma, MW_UART_DMA_LEN))
{
return MW_FAIL;
}
/*使能串口空闲中断*/
__HAL_UART_ENABLE_IT(pUartAttr->handle, UART_IT_IDLE);
return MW_SUCCESS;
}
/**
* @brief 初始化所有串口维护的循环队列
* @param none
* @retval 初始化成功状态 成功:MW_FAIL 失败:MW_SUCCESS
*/
int8_t MW_UART_Init_All()
{
if(MW_UART_Init(&E2K_UartAttr, &huart3, E2KRxDma, E2KTxDma) && \
MW_UART_Init(&FPGA_UartAttr, &huart2, FPGARxDma, FPGATxDma) && \
MW_UART_Init(&Dbg_UartAttr, &huart1, DbgRxDma, DbgTxDma))
{
return MW_SUCCESS;
}
return MW_FAIL;
}
/**
* @brief 使用指令的循环队列发送数据。
* @param sUartAttr 维护串口循环队列的结构体 包括发送接收缓冲fifo、dma的偏移量、发送状态、dma的缓冲大小
* @param buffer 发送数据缓冲区地址
* @param len 发送长度大小
* @retval 发送成功长度大小
*/
int32_t MW_UART_Transmit(MW_UART_ATTR *sUartAttr, uint8_t* buffer,int32_t len)
{
int32_t TransNum = 0;
int32_t TransLen = 0;
MW_UART_ATTR *pUartAttr = sUartAttr;
/*将要发送的数据先写入循环缓冲区*/
TransNum = CfifoBuff_Write(&pUartAttr->SendCFifo, (char *) buffer, len);
/*如果发送DMA未在发送中,则使能发送*/
if(pUartAttr->TransFlag == MW_TRANS_IDLE)
{
TransLen = CfifoBuff_Read(&pUartAttr->SendCFifo,(char *)(pUartAttr->pWriteDma),pUartAttr->DmaSize);
if(TransLen > 0)
{
pUartAttr->TransFlag = MW_TRANS_BUSY;
if(HAL_OK != HAL_UART_Transmit_DMA(pUartAttr->handle,pUartAttr->pWriteDma,TransLen))
{
printf("Uart Trans_DMA failed\r\n");
}
}
}
return TransNum;
}
cfifo_usart_dma.h
#ifndef _CFIFO_USART_DMA_H_
#define _CFIFO_USART_DMA_H_
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdio.h>
#include "cfifo.h"
#include "usart.h"
/* USER CODE END Includes */
/* USER CODE BEGIN Private defines */
#define MW_FAIL 0
#define MW_SUCCESS 1
#define MW_TRANS_IDLE 0
#define MW_TRANS_BUSY 1
#define MW_UART_DMA_LEN 256
/* USER CODE END Private defines */
typedef struct
{
UART_HandleTypeDef *handle; /*HAL库提供的串口句柄*/
int16_t TransFlag; /*数据发送标志位*/
int32_t DmaSize; /*DMA缓冲区的大小*/
int32_t DamOffset; /*获取数据在DMA缓冲区的偏移量*/
uint8_t *pReadDma; /*指向接收DMA缓冲区的首地址*/
uint8_t *pWriteDma; /*指向发送DMA缓冲区的首地址*/
CfifoBuff AcceptCFifo; /*接受数据的循环缓冲区*/
CfifoBuff SendCFifo; /*发送数据的循环缓冲区*/
}MW_UART_ATTR;
extern MW_UART_ATTR E2K_UartAttr;
extern MW_UART_ATTR FPGA_UartAttr;
extern MW_UART_ATTR Dbg_UartAttr;
/* USER CODE BEGIN Prototypes */
/**
* @brief 初始化所有串口维护的循环队列
* @param none
* @retval 初始化成功状态 成功:MW_FAIL 失败:MW_SUCCESS
*/
int8_t MW_UART_Init_All(void);
/**
* @brief 初始化串口维护的循环队列
* @param sUartAttr 维护串口循环队列的结构体 包括发送接收缓冲fifo、dma的偏移量、发送状态、dma的缓冲大小
* @param huart 维护的串口号 huartx(x = 1.2.3) 在usart.c中定义
* @param RxDma DMA接收数据缓冲区
* @param TxDma DMA接收数据缓冲区
* @retval 初始化成功状态 成功:MW_FAIL 失败:MW_SUCCESS
*/
int8_t MW_UART_Init(MW_UART_ATTR *sUartAttr, UART_HandleTypeDef *huart, uint8_t *RxDma, uint8_t *TxDma);
/**
* @brief 使用指令的循环队列发送数据。
* @param sUartAttr 维护串口循环队列的结构体 包括发送接收缓冲fifo、dma的偏移量、发送状态、dma的缓冲大小
* @param buffer 发送数据缓冲区地址
* @param len 发送长度大小
* @retval 发送成功长度大小
*/
int32_t MW_UART_Transmit(MW_UART_ATTR *sUartAttr, uint8_t* buffer,int32_t len);
void MW_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断中调用
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* _CFIFO_USART_DMA_H_ */
3、配置缓冲fifo代码文件具体位置和步骤。
第一步-修改fifo.h
cfifo.h中,改变最多的就是去修改18行的缓冲大小,其他改动不大
第一步-修改cfifo_usart_dma.c
cfifo_usart_dma.中,需要根据你使用的串口多少进行初始化,改变地方如下:
1、根据串口数量,为每个串口的发送接收开启DMA的缓冲区,其实就是静态的全局数组,自行添加减少(6~13行),如下图
2、根据串口数量,为每个串口的定义一个维护的结构体,如MW_UART_ATTR Dbg_UartAttr; 自行添加减少(15~17行),如下图
3、根据串口数量,进行初始化绑定, 自行添加减少(60~63行),如下图
第三步-修改cfifo_usart_dma.h
1、根据cfifo_usart_dma.c定义的维护串口数量,进行extern的外扩到其他文件。 自行添加减少cfifo_usart_dma.h(42~44行),如下图
至此配置fifo的操作就到此结束了,接下来就是在整个工程的调用和使用了
4、工程配置绑定使用
第一步-修改rewrite_it.c
#include "rewrite_it.h"
#include "usart.h"
MW_UART_ATTR *CaseUsart(UART_HandleTypeDef *huart)
{
MW_UART_ATTR *ret = NULL;
if(huart->Instance == huart1.Instance)
{
ret = &UART0_UartAttr;
}
else if(huart->Instance == huart2.Instance)
{
ret = &UART2_UartAttr;
}
else if(huart->Instance == huart4.Instance)
{
ret = &FPGA_UartAttr;
}
else if(huart->Instance == huart6.Instance)
{
ret = &Dbg_UartAttr;
}
else
{
;
}
return ret;
}
/**
* @brief 串口发送完成回调函数
* @param huart: 串口句柄 .
* @retval 无
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
int32_t TransNum = 0;
MW_UART_ATTR *pUartAttr = NULL;
pUartAttr = CaseUsart(huart);
/*从发送循环缓冲区中获取数据*/
TransNum = CfifoBuff_Read(&pUartAttr->SendCFifo,(char *)(pUartAttr->pWriteDma),pUartAttr->DmaSize);
if(TransNum > 0)
{
if(HAL_OK != HAL_UART_Transmit_DMA(pUartAttr->handle,pUartAttr->pWriteDma,TransNum))
{
printf("Uart Trans_DMA failed\r\n");
}
}
else
{
pUartAttr->TransFlag = MW_TRANS_IDLE;
}
}
/**
* @brief 串口接收完成回调函数,进行DMA的偏移量复位,当DMA接收完成会调用此函数
* @param huart: 串口句柄 .
* @retval 无
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
int32_t DmaLen = 0;
int32_t WriteNum = 0;
MW_UART_ATTR *pUartAttr = NULL;
pUartAttr = CaseUsart(huart);
/*计算需要获取数据的长度*/
DmaLen = pUartAttr->DmaSize - pUartAttr->DamOffset;
/*将获取的数据存放到数据缓冲区中*/
WriteNum = CfifoBuff_Write(&pUartAttr->AcceptCFifo,(char *)(pUartAttr->pReadDma + pUartAttr->DamOffset),DmaLen);
if(WriteNum != DmaLen)
{
printf("Uart ReadFifo is not enough\r\n");
}
/*复位DMA偏移量*/
pUartAttr->DamOffset = 0;
}
/**
* @brief 串口中断调用函数,处理空闲中断数据,将数据写入到缓冲区中,并维护循环队列大小、头尾指针
* @param huart: 串口句柄 .
* @retval 无
*/
void MW_UART_IRQHandler(UART_HandleTypeDef *huart)
{
int32_t RecvNum = 0;
int32_t WriteNum = 0;
int32_t DmaIdleNum = 0;
MW_UART_ATTR *pUartAttr = NULL;
pUartAttr = CaseUsart(huart);
if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
{
/*清除空闲中断标识位,重新接受串口空闲中断*/
__HAL_UART_CLEAR_IDLEFLAG(huart);
/*计算在DMA缓冲区需要获取的数据长度*/
DmaIdleNum = __HAL_DMA_GET_COUNTER(huart->hdmarx);
RecvNum = pUartAttr->DmaSize - DmaIdleNum - pUartAttr->DamOffset;
/*将获取到的数据放到数据接收缓冲区中*/
WriteNum = CfifoBuff_Write(&pUartAttr->AcceptCFifo,(char *)(pUartAttr->pReadDma + pUartAttr->DamOffset),RecvNum);
if(WriteNum != RecvNum)
{
printf("Uart ReadFifo is not enough\r\n");
}
/*计算获取数据位置的偏移量*/
pUartAttr->DamOffset += RecvNum;
//
// printf("%s",pUartAttr.AcceptCFifo.BUFF);
//
// CfifoBuff_Clear(&pUartAttr.AcceptCFifo);
}
}
*对于rewrite_it 文件,主要需要修改的地方是MW_UART_ATTR *CaseUsart(UART_HandleTypeDef huart)这个函数,根据串口情况,对维护串口进行选择,根据需要自行修改函数,函数如下图:
第二步-修改rewrite_it.h
#ifndef _REWRITE_IT_
#define _REWRITE_IT_
/**
* @brief 串口发送完成回调函数
* @param huart: 串口句柄 .
* @retval 无
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
/**
* @brief 串口接收完成回调函数
* @param huart: 串口句柄 .
* @retval 无
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
/**
* @brief 串口中断调用函数,处理空闲中断数据,将数据写入到缓冲区中,并维护循环队列大小、头尾指针
* @param huart: 串口句柄 .
* @retval 无
*/
void MW_UART_IRQHandler(UART_HandleTypeDef *huart);
#endif
第三步-修改stm32f4xx_it.c文件
对中断函数UARTx_IRQHandler或USARTx_IRQHandler函数添加MW_UART_IRQHandler(&huartx);
到此,所有的添加配置都已经完成。
4、使用举例
void DbgPrintf(const char *format, ...)
{
uint16_t len;
char sbuf[128];
va_list args;
va_start(args, format);
len = vsnprintf((char *)sbuf, sizeof(sbuf) - 1, (char *)format, args);
va_end(args);
MW_UART_Transmit(&Dbg_UartAttr, (uint8_t *)sbuf, len);
}
5、如果使用的是H7系列,可能dma访问的ram端需要你在内存中指定。
通常使用STM32CubeMX生成项目时,程序默认执行地址是从RAM1: 0x20000000处开始执行,而0x20000000是DTCM段,DMA无法进行访问,这就导致使用DMA传输时无法读写数据,也无法进入回调。
5.1 下图为H7系列的部分系统架构图,中文:
5.2 下图为H7系列的部分系统架构图,英文:
内部sram:
从上图可以看出,ITCM和 DTCM 是不支持 DMA1、DMA2 和 BDMA访问的,而AXI SRAM支持,所以把数据读写地址指定到AXI SRAM(0x24000000)区域内即可。
代码如下:
static uint8_t uart1TxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000000"))) = {0}; // DMA发送缓存区
static uint8_t uart1RxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000100"))) = {0}; // DMA接收缓存区
static uint8_t uart2TxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000200"))) = {0}; // DMA发送缓存区
static uint8_t uart2RxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000300"))) = {0}; // DMA接收缓存区
static uint8_t uart3TxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000400"))) = {0}; // DMA发送缓存区
static uint8_t uart3RxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000500"))) = {0}; // DMA接收缓存区
static uint8_t uart4TxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000600"))) = {0}; // DMA发送缓存区
static uint8_t uart4RxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000700"))) = {0}; // DMA接收缓存区
static uint8_t uart5TxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000800"))) = {0}; // DMA发送缓存区
static uint8_t uart5RxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000900"))) = {0}; // DMA接收缓存区
static uint8_t DbgTxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000a00"))) = {0}; // DMA发送缓存区 uart6
static uint8_t DbgRxDma[MW_UART_DMA_LEN] __attribute__((section(".ARM.__at_0x24000b00"))) = {0}; // DMA接收缓存区