目录

1.  DMA

1.1  简介

1.2  DMA总线架构

1.3  基本结构

1.4  优先级设置

2.  UART实现数据发送

3.  DMA实现发送数据转运

3.1  DMA初始化

3.2  DMA数据发送

3.3  主函数代码


1.  DMA

1.1  简介

         DMA,全称Direct Memory Access,即直接存储器访问。

        DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。

        如果没有不通过DMA,CPU传输数据还要以内核作为中转站,例如将ADC采集的数据转移到SRAM中。

        而如果通过DMA的话,DMA控制器将获取到的外设数据存储到DMA通道中,然后通过DMA总线与DMA总线矩阵协调,将数据传输到SRAM中,期间不需内核参与。

主要特征:

  • 同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推);
  • 独立数据源和目标数据区的传输宽度(字节、半字、全字);
  • 支持循环模式

1.2  DMA总线架构

        在看之前我们先需要搞懂一个概念什么是寄存器,寄存器是一种特殊的存储器,寄存器的每一位后面连接着一根导线,可以操作外设电平的状态,完成如操作引脚电平,开关的打开或者关闭,切换数据选择器,当做计数器等的操作:

所以寄存器可以说是连接软件和硬件的桥梁,软件读写寄存器就相当于在控制硬件的执行。

        中部分设备不需要被 DMA 访问,仅仅挂载于与 CPU 相连的总线上。
ADC/DAC/SPI/I2C/MCPWM/UART/Timer/GPIO/Hall/SRAM 等设备可以被CPU和DMA共享访问。 

        DMA有4个通道,共享一个搬运引擎。当DMA空闲且多个通道同时发生请求时,按优先级进行仲裁,但在搬运过程中不会发生抢占,即一定是某个通道完成搬运,DMA 空闲后,才会发起仲裁,判断下一个获得请求的通道是哪一个。 

1.3  基本结构

        主要参数:

/**
 *@brief DMA初始化结构体句柄,初始化时请定义该句柄,并用其它地址来传参
 */
typedef struct
{
    u8 IRQ_EN;
    u8 SBTW;          // 源地址访问位宽 0: Byte 1: Halfword 2: Word
    u8 DBTW;          // 目的地址访问位宽  0: Byte  1: Halfword  2: Word
    u8 SINC;          // 源地址递增使能
    u8 DINC;          // 目的地址递增使能
    u8 CIRC;          // 循环模式使能
    u8 RMODE;         // 多轮传输使能,多个DMA 请求完成传输
    u8 EN;            // 通道使能
    u8 TMS;           // DMA 通道 x 数据搬运次数。
    u32 SADR;         // DMA 通道 x 源地址
    u32 DADR;         // DMA 通道 x 目的地址
    stru_DMA_REN REN; // DMA 请求使能
} DMA_InitTypeDef;

        我们拆分一下,先看这部分:

        可以看出DMA的转运是后方向的,可以外设到内存,也可以内存到外设,我们可以通过函数进行控制:

        然后再来看看二者所需的数据:

        首先是基地址,也就是两者的起始地址,这两个参数决定数据从哪里来到哪里去,所需函数:

    u32 SADR;         // DMA 通道 x 源地址
    u32 DADR;         // DMA 通道 x 目的地址

        然后是数据宽度,其作用计时指定一次转运要按多大的数据宽度来进行,其可以选择字节(uint8_t),半字(uint16_t),字(uint32_t):

#define DMA_BYTE_TRANS     0 /* 访问位宽, 0:byte */
#define DMA_HALFWORD_TRANS 1 /* 访问位宽, 1:half-word */
#define DMA_WORD_TRANS     2 /* 访问位宽, 2:word */

        地址是否自增,作用是决定下次转运是不是要把地址移到下一个位置去,其参数可以选择使能或者失能:

    u8 SINC;          // 源地址递增使能
    u8 DINC;          // 目的地址递增使能

         然后我们看看另一个参数:传输计数器,这个值表示DMA需要转运几次,你可以将其理解为他是一个自减计数器,假如你初始化的值为5,那么每次转运一次计数减1,当减到0的时候,DMA就不会在进行转运了,并且当其减到0,之前自增的地址又会回到起始地址,方便新一轮的转换:

    u8 TMS;           // DMA 通道 x 数据搬运次数。

        那么他是怎么进行新一轮的转换呢?这就要靠自动重装器,其作用就是当转运次数归零后,询问是否将转运次数回到最初值,这样如果我们配置为循环模式,DMA计数归零回到起始地址,而自动重装器又将DMA的数据恢复,这样就可以循环:

    u8 CIRC;          // 循环模式使能

这里需要注意一点软件触发不能和循环一起使用。

因为软件触发就是想将传输计数器清零,但是循环模式我们上面也说了清零后会进行自动重装,因此不能一起使用。

1.4  优先级设置

        DMA 的优先级采用固定优先级。为避免出现来不及响应某些外设请求的情况,在设计应用软件时应考虑任务实时性,每个通道不配置搬运过于大量的数据导致其他通道得不到及时响应。 

        优先级由上至下降低。4个DMA通道中,优先级关系为:通道0>通道1>通道2>通道3 (>号表示优先级高于)。在DMA各通道内部,通常有多个硬件请求事件和一个软件请求事件,硬件请求优先级高于软件请求。多个硬件请求事件优先级相同。通常,一个DMA 通道配置一个硬件请求事件使能,多个硬件请求不应在一个通道内同时发生。

2.  UART实现数据发送

        对于串口的原理这里不进行过多表述,详细可以查看上一篇文章:

【凌鸥LKS32】基础篇(四)·UART串口收发运用-CSDN博客

        这里在文章的基础上增加了一些工程,增加一些串口发送数据函数:

#define Sum_Duff_Size 5000

uint8_t Uart_TxPacket[Sum_Duff_Size];	//定义发送数据包数组
uint8_t Uart_RxPacket[4];				//定义接收数据包数组

/**
 * 发送单个字符(轮询等待方式)
 */
void uart_putchar(uint8_t ch)
{
    // 等待发送缓冲区空闲
    while (!(UART0->STT & BIT0));
    UART_SendData(UART0, ch);
}

/**
 * 发送数组
 */
void uart_send_array(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)		//遍历数组
	{
		uart_putchar(Array[i]);		//依次调用Serial_SendByte发送每个字节数据
	}	
}

/**
 * 发送字符串
 */
void uart_send_string(char *str)
{
    while (*str)
    {
        uart_putchar(*str++);
    }
}

/**
 * fputc 重定向
 * 标准库的 printf/puts 等函数内部都会调用 fputc
 */
int fputc(int ch, FILE *f)
{
    uart_putchar((uint8_t)ch);
    return ch;
}

        程序设计:通过UART0发送5000个A的数据,主循环继续进行流水灯的操作

        来到主函数,我们创建一个宏定义,循环在串口发送缓冲区写入5000个A,然后进行发送,为了方便观察实验效果,我将该函数写在while循环之外,这样发送完成后LED灯就能正常工作,前后可以做个对比:

#include "lks32mc07x_lib.h"
#include "hardware_init.h"
#include "gpio_led.h"
#include "gpio_key.h"
#include "uart.h"
#include "OLED.h"
#include "delay.h"

#include <string.h>

u8 flow_direction = 0;       /* 全局变量: 流水灯方向, 0=正向, 1=反向 */
u8 Uart0_Buff = 0;

int main(void)
{
	Hardware_init();

	for(int i = 0;i<Sum_Duff_Size;i++)
	{
		Uart_TxPacket[i] = 'A';
	}

	uart_send_array(Uart_TxPacket, Sum_Duff_Size);	//普通数据发送	
	
	while(1)
	{
			led_water_flow(flow_direction);      /* 执行流水灯 */
			OLED_ShowString(1, 1, "KEY:");
			OLED_ShowNum(1, 5, flow_direction, 1);
			OLED_ShowString(2, 1, "RXD:");
			OLED_ShowNum(2, 5, Uart0_Buff, 2);
	}
}

        这里还移植了一些江协的OLED代码,并进行了一些简单的修改,修改如下:

#include "OLED.h"
#include "OLED_Font.h"
#include "delay.h"

/**
  * @brief  OLED I2C初始化
  * @param  
  * @retval 
  */
void OLED_I2C_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_StructInit(&GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Mode   = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Pin    = IIC_SCL_PIN;
	GPIO_Init(IIC_SCL_PORT, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Mode   = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Pin    = IIC_SDA_PIN;
	GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure);

	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void OLED_I2C_Start(void)
{
	OLED_W_SDA(1);
	OLED_W_SCL(1);
	OLED_W_SDA(0);
	OLED_W_SCL(0);
}

/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void OLED_I2C_Stop(void)
{
	OLED_W_SDA(0);
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval 无
  */
void OLED_I2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		OLED_W_SDA(Byte & (0x80 >> i));
		OLED_W_SCL(1);
		OLED_W_SCL(0);
	}
	OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
	OLED_W_SCL(0);
}

/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x00);		//写命令
	OLED_I2C_SendByte(Command); 
	OLED_I2C_Stop();
}

/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void OLED_WriteData(uint8_t Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x40);		//写数据
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
	}
}

/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval 无
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//端口初始化
	
	OLED_WriteCommand(0xAE);	//关闭显示
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//设置显示开始行
	
	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//设置对比度控制
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/倒转显示

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
		
	OLED_Clear();				//OLED清屏
}

        这里只修改了.c和.h的文件,对于字库文件未进行修改,可以直接去复制,这里也放出来了:

#ifndef __OLED_H__
#define __OLED_H__

#include "lks32mc07x_sys.h"
#include "lks32mc07x_gpio.h"

/* I2C引脚配置 (软件模拟I2C,使用GPIO0.3/GPIO0.4) */
#define IIC_SCL_PIN     GPIO_Pin_3
#define IIC_SCL_PORT    GPIO0
#define IIC_SCL_SOURCE  GPIO_PinSource_3
#define IIC_SCL_HIGH    GPIO_SetBits(IIC_SCL_PORT, IIC_SCL_PIN)
#define IIC_SCL_LOW     GPIO_ResetBits(IIC_SCL_PORT, IIC_SCL_PIN)

#define IIC_SDA_PIN     GPIO_Pin_4
#define IIC_SDA_PORT    GPIO0
#define IIC_SDA_SOURCE  GPIO_PinSource_4
#define IIC_SDA_HIGH    GPIO_SetBits(IIC_SDA_PORT, IIC_SDA_PIN)
#define IIC_SDA_LOW     GPIO_ResetBits(IIC_SDA_PORT, IIC_SDA_PIN)

/* 位操作宏 */
#define OLED_W_SCL(x)   GPIO_WriteBit(IIC_SCL_PORT, IIC_SCL_PIN, (BitAction)(x))
#define OLED_W_SDA(x)   GPIO_WriteBit(IIC_SDA_PORT, IIC_SDA_PIN, (BitAction)(x))

/* OLED驱动函数声明 */
void OLED_I2C_Init(void);
void OLED_I2C_Start(void);
void OLED_I2C_Stop(void);
void OLED_I2C_SendByte(uint8_t Byte);
void OLED_WriteCommand(uint8_t Command);
void OLED_WriteData(uint8_t Data);
void OLED_SetCursor(uint8_t Y, uint8_t X);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
uint32_t OLED_Pow(uint32_t X, uint32_t Y);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_Init(void);

#endif
#ifndef __OLED_FONT_H
#define __OLED_FONT_H

const uint8_t OLED_F8x16[][16]=
{
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//  0
	
	0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
	
	0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
	
	0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
	0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3
	
	0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
	0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4
	
	0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
	0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5
	
	0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
	0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6
	
	0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7
	
	0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
	0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8
	
	0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
	0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9
	
	0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
	0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10
	
	0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
	0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14
	
	0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
	0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15
	
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16
	
	0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17
	
	0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
	0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18
	
	0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
	0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19
	
	0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
	0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20
	
	0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
	0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21
	
	0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
	0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22
	
	0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23
	
	0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
	0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24
	
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25
	
	0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
	0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26
	
	0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,
	0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27
	
	0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
	0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28
	
	0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
	0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29
	
	0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
	0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30
	
	0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
	0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31
	
	0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
	0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32
	
	0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
	0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33
	
	0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34
	
	0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
	0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35
	
	0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36
	
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37
	
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38
	
	0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
	0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39
	
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40
	
	0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41
	
	0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
	0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42
	
	0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
	0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43
	
	0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44
	
	0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
	0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45
	
	0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46
	
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47
	
	0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
	0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48
	
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49
	
	0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50
	
	0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
	0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51
	
	0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52
	
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53
	
	0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
	0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54
	
	0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
	0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55
	
	0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
	0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56
	
	0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57
	
	0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58
	
	0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
	0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59
	
	0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60
	
	0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
	0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61
	
	0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62
	
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63
	
	0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65
	
	0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
	0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66
	
	0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67
	
	0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69
	
	0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71
	
	0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
	0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72
	
	0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73
	
	0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
	0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74
	
	0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75
	
	0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76
	
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77
	
	0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,
	0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79
	
	0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
	0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80
	
	0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
	0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81
	
	0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82
	
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83
	
	0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
	0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84
	
	0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85
	
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
	
	0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
	0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
	
	0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
	
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
	
	0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
	
	0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
	0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
	
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
	
	0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
	0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
	
	0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};

#endif

        此时我们下载程序,会发现串口持续打印数据,但是此时LED灯被卡着无法进行闪烁,需要等待数据发送完成才能进行下一步的操作:

        由于每发送或接收一个字节,串口都会产生一个中断。CPU需要停下当前的工作,保存现场,跳转到中断服务程序,处理这一个字节的数据(例如,将数据从接收缓冲区复制到用户数组,或从用户数组加载下一个要发送的字节),然后恢复现场,继续之前的工作。这么做就会导致CPU大量时间浪费在数据的传输上,阻塞后续功能的实现。

        为了减轻CPU的负担,我们引入DMA进行数据转运。DMA模式下CPU只需要在传输开始前,对DMA控制器进行配置,告诉它数据的源地址、目标地址和传输长度。之后,整个数据块的传输(比如1024个字节)就完全由DMA控制器接管。在整个块传输完成或达到半传输、传输完成时,DMA才会产生一个中断通知CPU。CPU在此期间可以完全不受打扰地执行其他任务,或者进入低功耗模式。

3.  DMA实现发送数据转运

3.1  DMA初始化

        首先常规的初始化配置,开启DMA的时钟,定义结构体变量:

    DMA_InitTypeDef DMA_InitStruct;
    DMA_StructInit(&DMA_InitStruct);

      对于结构体变量的参数:

/**
 *@brief DMA初始化结构体句柄,初始化时请定义该句柄,并用其它地址来传参
 */
typedef struct
{
    u8 IRQ_EN;
    u8 SBTW;          // 源地址访问位宽 0: Byte 1: Halfword 2: Word
    u8 DBTW;          // 目的地址访问位宽  0: Byte  1: Halfword  2: Word
    u8 SINC;          // 源地址递增使能
    u8 DINC;          // 目的地址地政使能
    u8 CIRC;          // 循环模式使能
    u8 RMODE;         // 多轮传输使能,多个DMA 请求完成传输
    u8 EN;            // 通道使能
    u8 TMS;           // DMA 通道 x 数据搬运次数。
    u32 SADR;         // DMA 通道 x 源地址
    u32 DADR;         // DMA 通道 x 目的地址
    stru_DMA_REN REN; // DMA 请求使能
} DMA_InitTypeDef;

        此时DMA结构体初始化如下:

void uart0_tx_dma(void)
{
    DMA_InitTypeDef DMA_InitStruct;
    DMA_StructInit(&DMA_InitStruct);

    DMA_InitStruct.IRQ_EN = ENABLE;             // 使能DMA中断
    DMA_InitStruct.SBTW = DMA_BYTE_TRANS;       // 源地址访问位宽为字节
    DMA_InitStruct.DBTW = DMA_BYTE_TRANS;       // 目的地址访问位宽为字节
    DMA_InitStruct.SINC = ENABLE;               // 源地址递增使能
    DMA_InitStruct.DINC = DISABLE;              // 目的地址递增禁止
    DMA_InitStruct.CIRC = DISABLE;              // 循环模式禁止
    DMA_InitStruct.RMODE = ENABLE;              // 多轮传输使能,多个DMA请求完成传输
    DMA_InitStruct.EN = DISABLE;                // 先禁止DMA通道
    DMA_InitStruct.TMS = 0;                     // DMA传输次数为发送数据包的长度
    DMA_InitStruct.SADR = (u32)Uart_TxPacket;   // DMA源地址为发送数据包数组的地址
    DMA_InitStruct.DADR = (u32)&UART0->BUFF;    // DMA目的地址为UART0的发送缓冲区地址
    DMA_InitStruct.REN.B.UART0_TX_EN = ENABLE;  // 使能UART0 TX的DMA请求
    DMA_Init(DMA_CHN3, &DMA_InitStruct);        // 初始化DMA通道3用于UART0 TX

    DMA_Enable();                               // 使能DMA控制器
}

        此时初始化部分参数:

#include "basic.h"
#include "lks32mc07x_sys.h"
#include "hardware_init.h"
#include "gpio_led.h"
#include "gpio_key.h"
#include "uart.h"
#include "OLED.h"
#include "delay.h"

#include <string.h>

/*******************************************************************************
 函数名称:    void Hardware_init(void)
 功能描述:    硬件部分初始化
 输入参数:    无
 输出参数:    无
 返 回 值:    无
 其它说明:
 *******************************************************************************/
void Hardware_init(void)
{
    __disable_irq();                  /* 关闭中断 中断总开关 */
    SYS_WR_PROTECT = 0x7a83;          /* 解除系统寄存器写保护 */
    FLASH_CFG |= 0x00080000;          /* enable prefetch ,FLASH预取加速使能*/
    IWDG_DISABLE();                   /* 关闭看门狗*/
    GPIO_init();                      /* GPIO初始化 */
    GPIO_init_key();                  /* KEY初始化 */
    uart0_init();                     /* UART0初始化 */
    uart0_tx_dma();                   /* UART0 TX DMA初始化 */
    OLED_Init();                      /* OLED初始化 */
    SoftDelay(100);                   /* 延时等待硬件初始化稳定 */
    NVIC_SetPriority(GPIO_IRQn, 0);   /* 设置GPIO中断优先级为最高 */
    NVIC_EnableIRQ(GPIO_IRQn);        /* 使能GPIO中断 */
    NVIC_SetPriority(DMA0_IRQn, 2);   /* 设置DMA0中断优先级为2 */
    NVIC_EnableIRQ(DMA0_IRQn);        /* 使能DMA0中断 */
    NVIC_SetPriority(UART0_IRQn, 3);  /* 设置UART0中断优先级为3 */
    NVIC_EnableIRQ(UART0_IRQn);       /* 使能UART0中断 */
    __enable_irq();                   /* 开启总中断 */
}

/*******************************************************************************
 函数名称:    void Clock_Init(void)
 功能描述:    时钟配置
 输入参数:    无
 输出参数:    无
 返 回 值:    无
 其它说明:
 *******************************************************************************/
void Clock_Init(void)
{
    SYS_WR_PROTECT = 0x7a83;   /* 解除系统寄存器写保护 */
    SYS_AFE_REG5 = BIT15;      /* BIT15:PLLPDN 开PLL */
    SoftDelay(100);            /* 延时100us, 等待PLL稳定 21.4.17*/
    SYS_CLK_CFG = 0x000001ff;  /* BIT8:0: CLK_HS,1:PLL  | BIT[7:0]CLK_DIV  | 1ff对应96M时钟 */
    SYS_WR_PROTECT = 0;        /* 关闭系统寄存器写操作*/
}

/*******************************************************************************
 函数名称:    void SystemInit(void)
 功能描述:    硬件系统初始化,调用时钟初始化函数
 输入参数:    无
 输出参数:    无
 返 回 值:    无
 其它说明:
 *******************************************************************************/
void SystemInit(void)
{
    Clock_Init();  /* 时钟初始化 */
}

/*******************************************************************************
 函数名称:    void GPIO_init(void)
 功能描述:    GPIO硬件初始化
 输入参数:    无
 输出参数:    无
 返 回 值:    无
 其它说明:
 *******************************************************************************/
void GPIO_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_Pin =  LED1_PIN;
    GPIO_Init(LED1_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_Pin =  LED2_PIN;
    GPIO_Init(LED2_PORT, &GPIO_InitStruct);

//    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
//    GPIO_InitStruct.GPIO_Pin =  LED3_PIN;
//    GPIO_Init(LED3_PORT, &GPIO_InitStruct);
}

/*******************************************************************************
 函数名称:    void GPIO_init_key(void)
 功能描述:    KEY硬件初始化
 输入参数:    无
 输出参数:    无
 返 回 值:    无
 其它说明:
 *******************************************************************************/
void GPIO_init_key(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStruct.GPIO_Pin =  KEY_STOP_PIN;
    GPIO_Init(KEY_STOP_PORT, &GPIO_InitStruct);
    EXTI_Trigger_Config(KEY_STOP_PORT,KEY_STOP_Source,EXTI_Trigger_Negedge);

    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStruct.GPIO_Pin =  KEY_START_PIN;
    GPIO_Init(KEY_START_PORT, &GPIO_InitStruct);
}

void uart0_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_StructInit(&GPIO_InitStruct); //初始化结构体

    //配置UART0_RXD  P0.15
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStruct.GPIO_Pin = UART0_RX_PIN;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(UART0_RX_PORT, &GPIO_InitStruct);
	
    //配置UART0_TXD  P1.0
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_Pin = UART0_TX_PIN;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(UART0_TX_PORT, &GPIO_InitStruct);

    GPIO_PinAFConfig(UART0_RX_PORT, UART0_RX_Source, AF4_UART);
    GPIO_PinAFConfig(UART0_TX_PORT, UART0_TX_Source, AF4_UART);	
	
    UART_InitTypeDef UART_InitStruct;

    UART_StructInit(&UART_InitStruct);
    UART_InitStruct.BaudRate = 9600;                                    /* 设置波特率9600 */
    UART_InitStruct.WordLength = UART_WORDLENGTH_8b;                     /* 发送数据长度8位 */
    UART_InitStruct.StopBits = UART_STOPBITS_1b;                         /* 停止位1位 */
    UART_InitStruct.FirstSend = UART_FIRSTSEND_LSB;                      /* 先发送LSB */
    UART_InitStruct.ParityMode = UART_Parity_NO;                         /* 无奇偶校验 */
    UART_InitStruct.IRQEna = UART_IRQEna_SendOver | UART_IRQEna_RcvOver; /* 串口中断使能 */
    UART_InitStruct.DMARE = TX_DMA_RE | TX_BUF_DMA_RE;  // 使能UART TX的DMA请求
    UART_Init(UART0, &UART_InitStruct);
}

void uart0_tx_dma(void)
{
    DMA_InitTypeDef DMA_InitStruct;
    DMA_StructInit(&DMA_InitStruct);

    DMA_InitStruct.IRQ_EN = ENABLE;             // 使能DMA中断
    DMA_InitStruct.SBTW = DMA_BYTE_TRANS;       // 源地址访问位宽为字节
    DMA_InitStruct.DBTW = DMA_BYTE_TRANS;       // 目的地址访问位宽为字节
    DMA_InitStruct.SINC = ENABLE;               // 源地址递增使能
    DMA_InitStruct.DINC = DISABLE;              // 目的地址递增禁止
    DMA_InitStruct.CIRC = DISABLE;              // 循环模式禁止
    DMA_InitStruct.RMODE = ENABLE;              // 多轮传输使能,多个DMA请求完成传输
    DMA_InitStruct.EN = DISABLE;                // 先禁止DMA通道
    DMA_InitStruct.TMS = 0;                     // DMA传输次数为发送数据包的长度
    DMA_InitStruct.SADR = (u32)Uart_TxPacket;   // DMA源地址为发送数据包数组的地址
    DMA_InitStruct.DADR = (u32)&UART0->BUFF;    // DMA目的地址为UART0的发送缓冲区地址
    DMA_InitStruct.REN.B.UART0_TX_EN = ENABLE;  // 使能UART0 TX的DMA请求
    DMA_Init(DMA_CHN3, &DMA_InitStruct);        // 初始化DMA通道3用于UART0 TX

    DMA_Enable();                               // 使能DMA控制器
}

3.2  DMA数据发送

        首先创建一个数据发送标志位,注意使用volatile声明,防止编译器优化:

volatile uint8_t dma_busy = 0; // DMA发送状态标志

        设置一个while循环,根据标志位,等待DMA数据发送完成:

void USART_WaitDMAFinish(void)
{
    while (dma_busy)
    {
        // 等待DMA发送完成
    }
}

        首先判断一下传输的数据是否为空,或者超出缓冲区范围,如果是则直接返回,不进行数据传输,若是满足,则通过wait函数等待上一次的数据完成,完成后复制本次数据到缓冲区,配置DMA传输数据长度,然后清除标志位,设置忙状态,然后使能DMA发送中断:

void uart_SendDataDMA(uint8_t *data, uint16_t len)
{
    if (len == 0 || len > Sum_Duff_Size)
        return;

    uart_WaitDMAFinish();                       // 等待上一次DMA传输完成
    memcpy(Uart_TxPacket, data, len);            // 拷贝数据到发送缓冲区

    dma_tx_ptr = Uart_TxPacket;                  // 初始化发送指针
    dma_tx_remaining = len;                      // 初始化剩余字节数

    uint16_t first_chunk = (len > 255) ? 255 : len;
    dma_tx_ptr      += first_chunk;
    dma_tx_remaining -= first_chunk;

    DMA_SetLength(DMA_CHN3, first_chunk);       // 设置本次传输长度
    DMA_ClearIRQFlag(DMA_IF_CH3);               // 清除中断标志
    dma_busy = 1;                               // 设置忙标志
}

        然后回进入到中断当中,如果数据传输完成标志位置0,进行下一次数据传输,这里我们只进行发送,不进行后续处理,因此没有后续代码,想要进行一些处理的可以自行添加:

void DMA0_IRQHandler(void)
{
    if (DMA_GetIRQFlag(DMA_IF_CH3))
    {
        DMA_ClearIRQFlag(DMA_IF_CH3);

        if (dma_tx_remaining > 0)
        {
            // 还有数据,启动下一块
            uint16_t chunk = (dma_tx_remaining > 255) ? 255 : dma_tx_remaining;
            DMA_CHN3->SADR = (u32)dma_tx_ptr;     // 更新源地址到新偏移位置
            dma_tx_ptr      += chunk;
            dma_tx_remaining -= chunk;
            DMA_SetLength(DMA_CHN3, chunk);        // 启动下一块传输
        }
        else
        {
            dma_busy = 0;                          // 全部发送完成
        }
    }
}

3.3  主函数代码

        主函数部分就是将刚刚的串口发送,更改为DMA的发送:

#include "lks32mc07x_lib.h"
#include "hardware_init.h"
#include "gpio_led.h"
#include "gpio_key.h"
#include "uart.h"
#include "OLED.h"
#include "delay.h"

#include <string.h>

u8 flow_direction = 0;       /* 全局变量: 流水灯方向, 0=正向, 1=反向 */
u8 Uart0_Buff = 0;

int main(void)
{
	Hardware_init();

	for(int i = 0;i<Sum_Duff_Size;i++)
	{
		Uart_TxPacket[i] = 'A';
	}

//	uart_send_array(Uart_TxPacket, Sum_Duff_Size);	//普通数据发送	
	uart_SendDataDMA(Uart_TxPacket, Sum_Duff_Size);		//DMA数据发送
	
	while(1)
	{
			led_water_flow(flow_direction);      /* 执行流水灯 */
			OLED_ShowString(1, 1, "KEY:");
			OLED_ShowNum(1, 5, flow_direction, 1);
			OLED_ShowString(2, 1, "RXD:");
			OLED_ShowNum(2, 5, Uart0_Buff, 2);
	}
}

      运行后可以发现,串口在正常打印数据的同时,LED灯也在正常的闪烁(这里没法放视频,就不进行演示了,可以自行看一下效果)。

完整工程:凌鸥LKS32MC071系列单片机·DMA实现UART数据发送资源-CSDN下载

凌鸥LKS32_时光の尘的博客-CSDN博客

更多推荐