【凌鸥LKS32】基础篇(五)·UART实现DMA数据转运

目录
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实现数据发送
对于串口的原理这里不进行过多表述,详细可以查看上一篇文章:
这里在文章的基础上增加了一些工程,增加一些串口发送数据函数:
#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灯也在正常的闪烁(这里没法放视频,就不进行演示了,可以自行看一下效果)。


更多推荐
所有评论(0)