GD 32空闲中断DMA串口接收

前言


通过本次代码的学习,搞懂USART配置,DMA配置,数据校验,数据接收,进一步巩固串口发送和接收知识,了解指针和结构体的使用。


DMA 转运基础知识补充

串口接收中断的流程

配置串口接收使用DMA的方式


 初始化各个驱动

 声明结构体给结构体,创建结构体数字,初始化数据包格式

typedef struct
{
	uint32_t uartNo;
	rcu_periph_enum rcuUart;
	rcu_periph_enum rcuGpio;
	uint32_t gpio;
	uint32_t txPin;
	uint32_t rxPin;
	uint8_t irq;
	uint32_t dmaNo;
	rcu_periph_enum rcuDma;
	dma_channel_enum dmaCh;
} UartHwInfo_t;

static UartHwInfo_t g_uartHwInfo = 
{
	USART0,
	RCU_USART0, 
	RCU_GPIOA, 
	GPIOA,
	GPIO_PIN_9,
	GPIO_PIN_10,
	USART0_IRQn,
	DMA0,
	RCU_DMA0,
	DMA_CH4
};

#define USART0_DATA_ADDR      (USART0 + 0x04)   //串口0的数据寄存器十六进制地址值
						
/**
***********************************************************************
包格式:帧头0  帧头1  数据长度  功能字   LED编号  亮/灭  异或校验数据
        0x55   0xAA    0x03      0x06     0x00     0x01      0xFB
***********************************************************************
*/
#define FRAME_HEAD_0        0x55  
#define FRAME_HEAD_1        0xAA
#define CTRL_DATA_LEN       3     //数据域长度
#define PACKET_DATA_LEN     (CTRL_DATA_LEN + 4)  //包长度
#define FUNC_DATA_IDX       3     //功能字数组下标
#define LED_CTRL_CODE       0x06  //功能字

#define MAX_BUF_SIZE 20
static uint8_t g_rcvDataBuf[MAX_BUF_SIZE];
static bool g_pktRcvd = false;

typedef struct
{
	uint8_t ledNo;
	uint8_t ledState;
} LedCtrlInfo_t;				

gpio 初始化

 创建一个函数用于调用,gpio,usart,dma的初始化函数,第二个函数传递进去一个波特率为115200.

/**
***********************************************************
* @brief USB转串口硬件初始化
* @param
* @return 
***********************************************************
*/
void Usb2ComDrvInit(void)
{
	Usb2ComGpioInit();
	Usb2ComUartInit(115200);
	Usb2ComDmaInit();
}

gpio 初始化代码

 这是一个静态的函数只能在函数的内部进行调用对外部时隐藏的,

static void Usb2ComGpioInit(void)
{
    // 开启rcu时钟
	rcu_periph_clock_enable(g_uartHwInfo.rcuGpio);
    // 初始化串口发送引脚
	gpio_init
	(
		g_uartHwInfo.gpio, 
		GPIO_MODE_AF_PP,
		GPIO_OSPEED_10MHZ, 
		g_uartHwInfo.txPin
	);
    // 初始化串口接收引脚
	gpio_init
	(
		g_uartHwInfo.gpio, 
		GPIO_MODE_IPU, 
		GPIO_OSPEED_10MHZ,
		g_uartHwInfo.rxPin
	);
}

USART 初始化

 也是一个静态函数仅在本C语言文件中可以被调用

static void Usb2ComUartInit(uint32_t baudRate)
{
	/* 使能UART时钟;*/
	rcu_periph_clock_enable(g_uartHwInfo.rcuUart);
	/* 复位UART;*/
	usart_deinit (g_uartHwInfo.uartNo);
	/* 在USART_BAUD寄存器中设置波特率;*/ 
	usart_baudrate_set(g_uartHwInfo.uartNo, baudRate);
	/* 在USART_CTL0寄存器中设置TEN位,使能发送功能;*/
	usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_ENABLE);
	/* 在USART_CTL0寄存器中设置TEN位,使能接收功能;*/
	usart_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_ENABLE);
	/* 使能串口接收空闲中断;*/
	usart_interrupt_enable(g_uartHwInfo.uartNo, USART_INT_IDLE);
	/* 使能串口中断;*/
	nvic_irq_enable(g_uartHwInfo.irq, 0, 0);
	/* 在USART_CTL0寄存器中置位UEN位,使能UART;*/ 
	usart_enable(g_uartHwInfo.uartNo);
}

初始化 DMA

static void Usb2ComDmaInit(void)
{
	/* 使能DMA时钟;*/
	rcu_periph_clock_enable(g_uartHwInfo.rcuDma);
	/* 复位DMA通道;*/
	dma_deinit(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
	dma_parameter_struct dmaStruct;
	/* 配置传输方向;*/ 
	dmaStruct.direction = DMA_PERIPHERAL_TO_MEMORY;
	/* 配置数据源地址;*/ 
	dmaStruct.periph_addr = USART0_DATA_ADDR;
	/* 配置源地址是固定的还是增长的;*/ 
	dmaStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	/* 配置源数据传输位宽;*/ 
	dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
	
	/* 配置数据目的地址;*/
	dmaStruct.memory_addr = (uint32_t)g_rcvDataBuf;
	/* 配置目的地址是固定的还是增长的;*/ 
	dmaStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	/* 配置目的数据传输位宽;*/ 
	dmaStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;
	/* 配置数据传输最大次数;*/ 
	dmaStruct.number = MAX_BUF_SIZE;
	/* 配置DMA通道优先级;*/ 
	dmaStruct.priority = DMA_PRIORITY_HIGH;
	dma_init(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh, &dmaStruct);
	
	/* 使能UART接收数据使用DMA;*/ 
	usart_dma_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_DMA_ENABLE);
	/* 使能DMA通道;*/ 
	dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
}

数据异或校验函数

/**
***********************************************************
* @brief 对数据进行异或运算
* @param data, 存储数组的首地址
* @param len, 要计算的元素的个数
* @return 异或运算结果
***********************************************************
*/
static uint8_t CalXorSum(const uint8_t *data, uint32_t len)
{
	uint8_t xorSum = 0;
	for (uint32_t i = 0; i < len; i++)
	{
		xorSum ^= data[i];
	}
	return xorSum;
}

LED灯控制函数

/**
***********************************************************
* @brief LED控制处理函数
* @param ctrlData,结构体指针,传入LED的编号和状态
* @return 
***********************************************************
*/
static void CtrlLed(LedCtrlInfo_t *ctrlData)
{
	ctrlData->ledState != 0 ? TurnOnLed(ctrlData->ledNo) : TurnOffLed(ctrlData->ledNo);
}

串口任务处理函数

/**
***********************************************************
* @brief USB转串口任务处理函数
* @param
* @return 
***********************************************************
*/
void Usb2ComTask(void)
{
    // 如果全局标志位为false,直接返回
	if (!g_pktRcvd)
	{
		return;
	}
    // 全局标志位为true,设置为false方便下一次进入进行判断
	g_pktRcvd = false;
	// 判断函数头是否相等不相等直接返回
	if (g_rcvDataBuf[0] != FRAME_HEAD_0 || g_rcvDataBuf[1] != FRAME_HEAD_1)
	{
		return;
	}
    // 对接收到的数据进行crc校验
	if (CalXorSum(g_rcvDataBuf, PACKET_DATA_LEN - 1) != g_rcvDataBuf[PACKET_DATA_LEN - 1])
	{
		return;
	}
    // 判断功能字是否相等,相等给led控制函数赋值,数据包发送过去
	if (g_rcvDataBuf[FUNC_DATA_IDX] == LED_CTRL_CODE)
	{
		CtrlLed((LedCtrlInfo_t *)(&g_rcvDataBuf[FUNC_DATA_IDX + 1]));
	}
}

串口中断服务函数

 具体的操作就是判断空闲中断标志位,如果标志位为1表示进入中断,获取中断标志位后需要马上清除中断标志位,不然会一直卡在这一直进入中断,清除中断标志位后,调用函数接收中断数据,判断接收到的数据包程度是否是自定义数据包长度,是通过DMA进行转运。

/**
***********************************************************
* @brief 串口0中断服务函数
* @param
* @return 
***********************************************************
*/
void USART0_IRQHandler(void)
{
	if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_IDLE) != RESET)
	{
		usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_IDLE);  //第一步,读取stat0寄存器,清除IDLE标志位
		usart_data_receive(g_uartHwInfo.uartNo);                               //第二步,读取数据寄存器,清除IDLE标志位
		
		if (PACKET_DATA_LEN == (MAX_BUF_SIZE - dma_transfer_number_get(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh)))
		{
			g_pktRcvd = true;
		}
		dma_channel_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
		dma_transfer_number_config(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh, MAX_BUF_SIZE);
		dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
	}
}

Printf 打印函数

/**
***********************************************************
* @brief printf函数默认打印输出到显示器,如果要输出到串口,
		 必须重新实现fputc函数,将输出指向串口,称为重定向
* @param
* @return 
***********************************************************
*/
int fputc(int ch, FILE *f)
{
	usart_data_transmit(g_uartHwInfo.uartNo, (uint8_t)ch);
	while (RESET == usart_flag_get(g_uartHwInfo.uartNo, USART_FLAG_TBE));
	return ch;
}

全部代码

串口接收DMA转运部分代码

#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include "gd32f30x.h"
#include "led_drv.h"

typedef struct
{
	uint32_t uartNo;
	rcu_periph_enum rcuUart;
	rcu_periph_enum rcuGpio;
	uint32_t gpio;
	uint32_t txPin;
	uint32_t rxPin;
	uint8_t irq;
	uint32_t dmaNo;
	rcu_periph_enum rcuDma;
	dma_channel_enum dmaCh;
} UartHwInfo_t;

static UartHwInfo_t g_uartHwInfo = 
{
	USART0,
	RCU_USART0, 
	RCU_GPIOA, 
	GPIOA,
	GPIO_PIN_9,
	GPIO_PIN_10,
	USART0_IRQn,
	DMA0,
	RCU_DMA0,
	DMA_CH4
};

#define USART0_DATA_ADDR      (USART0 + 0x04)   //串口0的数据寄存器十六进制地址值
						
/**
***********************************************************************
包格式:帧头0  帧头1  数据长度  功能字   LED编号  亮/灭  异或校验数据
        0x55   0xAA    0x03      0x06     0x00     0x01      0xFB
***********************************************************************
*/
#define FRAME_HEAD_0        0x55  
#define FRAME_HEAD_1        0xAA
#define CTRL_DATA_LEN       3     //数据域长度
#define PACKET_DATA_LEN     (CTRL_DATA_LEN + 4)  //包长度
#define FUNC_DATA_IDX       3     //功能字数组下标
#define LED_CTRL_CODE       0x06  //功能字

#define MAX_BUF_SIZE 20
static uint8_t g_rcvDataBuf[MAX_BUF_SIZE];
static bool g_pktRcvd = false;

typedef struct
{
	uint8_t ledNo;
	uint8_t ledState;
} LedCtrlInfo_t;				

static void Usb2ComDmaInit(void)
{
	/* 使能DMA时钟;*/
	rcu_periph_clock_enable(g_uartHwInfo.rcuDma);
	/* 复位DMA通道;*/
	dma_deinit(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
	dma_parameter_struct dmaStruct;
	/* 配置传输方向;*/ 
	dmaStruct.direction = DMA_PERIPHERAL_TO_MEMORY;
	/* 配置数据源地址;*/ 
	dmaStruct.periph_addr = USART0_DATA_ADDR;
	/* 配置源地址是固定的还是增长的;*/ 
	dmaStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	/* 配置源数据传输位宽;*/ 
	dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
	
	/* 配置数据目的地址;*/
	dmaStruct.memory_addr = (uint32_t)g_rcvDataBuf;
	/* 配置目的地址是固定的还是增长的;*/ 
	dmaStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	/* 配置目的数据传输位宽;*/ 
	dmaStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;
	/* 配置数据传输最大次数;*/ 
	dmaStruct.number = MAX_BUF_SIZE;
	/* 配置DMA通道优先级;*/ 
	dmaStruct.priority = DMA_PRIORITY_HIGH;
	dma_init(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh, &dmaStruct);
	
	/* 使能UART接收数据使用DMA;*/ 
	usart_dma_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_DMA_ENABLE);
	/* 使能DMA通道;*/ 
	dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
}

static void Usb2ComGpioInit(void)
{
	rcu_periph_clock_enable(g_uartHwInfo.rcuGpio);
	gpio_init
	(
		g_uartHwInfo.gpio, 
		GPIO_MODE_AF_PP,
		GPIO_OSPEED_10MHZ, 
		g_uartHwInfo.txPin
	);
	gpio_init
	(
		g_uartHwInfo.gpio, 
		GPIO_MODE_IPU, 
		GPIO_OSPEED_10MHZ,
		g_uartHwInfo.rxPin
	);
}

static void Usb2ComUartInit(uint32_t baudRate)
{
	/* 使能UART时钟;*/
	rcu_periph_clock_enable(g_uartHwInfo.rcuUart);
	/* 复位UART;*/
	usart_deinit (g_uartHwInfo.uartNo);
	/* 在USART_BAUD寄存器中设置波特率;*/ 
	usart_baudrate_set(g_uartHwInfo.uartNo, baudRate);
	/* 在USART_CTL0寄存器中设置TEN位,使能发送功能;*/
	usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_ENABLE);
	/* 在USART_CTL0寄存器中设置TEN位,使能接收功能;*/
	usart_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_ENABLE);
	/* 使能串口接收空闲中断;*/
	usart_interrupt_enable(g_uartHwInfo.uartNo, USART_INT_IDLE);
	/* 使能串口中断;*/
	nvic_irq_enable(g_uartHwInfo.irq, 0, 0);
	/* 在USART_CTL0寄存器中置位UEN位,使能UART;*/ 
	usart_enable(g_uartHwInfo.uartNo);
}

/**
***********************************************************
* @brief USB转串口硬件初始化
* @param
* @return 
***********************************************************
*/
void Usb2ComDrvInit(void)
{
	Usb2ComGpioInit();
	Usb2ComUartInit(115200);
	Usb2ComDmaInit();
}

/**
***********************************************************
* @brief 对数据进行异或运算
* @param data, 存储数组的首地址
* @param len, 要计算的元素的个数
* @return 异或运算结果
***********************************************************
*/
static uint8_t CalXorSum(const uint8_t *data, uint32_t len)
{
	uint8_t xorSum = 0;
	for (uint32_t i = 0; i < len; i++)
	{
		xorSum ^= data[i];
	}
	return xorSum;
}

/**
***********************************************************
* @brief LED控制处理函数
* @param ctrlData,结构体指针,传入LED的编号和状态
* @return 
***********************************************************
*/
static void CtrlLed(LedCtrlInfo_t *ctrlData)
{
	ctrlData->ledState != 0 ? TurnOnLed(ctrlData->ledNo) : TurnOffLed(ctrlData->ledNo);
}

/**
***********************************************************
* @brief USB转串口任务处理函数
* @param
* @return 
***********************************************************
*/
void Usb2ComTask(void)
{
	if (!g_pktRcvd)
	{
		return;
	}
	g_pktRcvd = false;
	
	if (g_rcvDataBuf[0] != FRAME_HEAD_0 || g_rcvDataBuf[1] != FRAME_HEAD_1)
	{
		return;
	}
	if (CalXorSum(g_rcvDataBuf, PACKET_DATA_LEN - 1) != g_rcvDataBuf[PACKET_DATA_LEN - 1])
	{
		return;
	}
	if (g_rcvDataBuf[FUNC_DATA_IDX] == LED_CTRL_CODE)
	{
		CtrlLed((LedCtrlInfo_t *)(&g_rcvDataBuf[FUNC_DATA_IDX + 1]));
	}
}

/**
***********************************************************
* @brief 串口0中断服务函数
* @param
* @return 
***********************************************************
*/
void USART0_IRQHandler(void)
{
	if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_IDLE) != RESET)
	{
		usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_IDLE);  //第一步,读取stat0寄存器,清除IDLE标志位
		usart_data_receive(g_uartHwInfo.uartNo);                               //第二步,读取数据寄存器,清除IDLE标志位
		
		if (PACKET_DATA_LEN == (MAX_BUF_SIZE - dma_transfer_number_get(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh)))
		{
			g_pktRcvd = true;
		}
		dma_channel_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
		dma_transfer_number_config(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh, MAX_BUF_SIZE);
		dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
	}
}

/**
***********************************************************
* @brief printf函数默认打印输出到显示器,如果要输出到串口,
		 必须重新实现fputc函数,将输出指向串口,称为重定向
* @param
* @return 
***********************************************************
*/
int fputc(int ch, FILE *f)
{
	usart_data_transmit(g_uartHwInfo.uartNo, (uint8_t)ch);
	while (RESET == usart_flag_get(g_uartHwInfo.uartNo, USART_FLAG_TBE));
	return ch;
}

头文件代码

#ifndef _USB2COM_DRV_H_
#define _USB2COM_DRV_H_

#include <stdint.h>

/**
***********************************************************
* @brief USB转串口硬件初始化
* @param
* @return 
***********************************************************
*/
void Usb2ComDrvInit(void);

/**
***********************************************************
* @brief USB转串口任务处理函数
* @param
* @return 
***********************************************************
*/
void Usb2ComTask(void);
#endif

主函数程序调用

#include <stdint.h>
#include <stdio.h>
#include "gd32f30x.h"
#include "led_drv.h"
#include "key_drv.h"
#include "systick.h"
#include "usb2com_drv.h"

int main(void)
{	
	SystickInit();
	LedDrvInit();
	KeyDrvInit();
	Usb2ComDrvInit();
	
	while (1)
	{
		Usb2ComTask();
	}
}

当然程序中包含LED部分的代码没有一一的列出,本程序仅对串口DMA部分进行参考。

结语

这是初步编写的学习总结,参考自郭天祥老师的ARM32教程,后续会进一步完善,仅用于学习参考。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值