C8T6超绝模块--DMA
C8T6超绝模块–DMA
对于DMA我们有三种模式:
外设到寄存器
寄存器到寄存器
寄存器到外设
大纲
- 结构体讲解
- 存储器到存储器实验
- 存储器到外设实验
- DMA收发数据
具体案例
结构体讲解
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; // 外设地址
uint32_t DMA_MemoryBaseAddr; // 存储器地址
uint32_t DMA_DIR; // 传输方向
uint32_t DMA_BufferSize; // 传输数目
uint32_t DMA_PeripheralInc; // 外设地址增量模式
uint32_t DMA_MemoryInc; // 存储器地址增量模式
uint32_t DMA_PeripheralDataSize; // 外设数据宽度
uint32_t DMA_MemoryDataSize; // 存储器数据宽度
uint32_t DMA_Mode; // 模式选择
uint32_t DMA_Priority; // 通道优先级
uint32_t DMA_M2M; // 存储器到存储器模式
} DMA_InitTypeDef;
-
DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据
寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址 -
DMA_Memory0BaseAddr:存储器地址,设定 DMA_CMAR 寄存器值;一般设置为我们自定义
存储区的首地址 -
DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定 DMA_CCR 寄存器的
DIR[1:0] 位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把
其中一个存储器当作外设使用即可(即把一个存储器当作外设) -
DMA_BufferSize:设定待传输数据数目,初始化设定 DMA_CNDTR 寄存器的值
-
DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它
设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能
该位 -
DMA_MemoryInc:如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它
设定 DMA_CCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以
要使能存储器地址自动递增功能 -
DMA_PeripheralDataSize:外设数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位),它设定
DMA_CCR 寄存器的 PSIZE[1:0] 位的值 -
DMA_MemoryDataSize:存储器数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位),它设定
DMA_CCR 寄存器的 MSIZE[1:0] 位的值。当外设和存储器之间传数据时,两边的数据宽度应该
设置为一致大小 -
DMA_Mode:DMA 传输模式选择,可选一次传输或者循环传输,它设定 DMA_CCR 寄存器的
CIRC 位的值。例程我们的 ADC 采集是持续循环进行的,所以使用循环传输模式 -
DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设
定 DMA_CCR 寄存器的 PL[1:0] 位的值。DMA 通道优先级只有在多个 DMA 通道同时使用时才
有意义,如果是单个通道,优先级可以随便设置 -
DMA_M2M:存储器到存储器模式,使用存储器到存储器时用到,设定 DMA_CCR 的位 14
MEN2MEN 即可启动存储器到存储器模式
存储器到存储器实验
我们先定义一个静态的源数据,存放在内部 FLASH,然后使用 DMA 传输把源数据拷贝到目标地址上(内部 SRAM),最后对比源数据和目标地址的数据,看看是否传输准确
DMA.H
这里是一些头文件的定义
#ifndef __BSP_DMA_MTM_H
#define __BSP_DMA_MTM_H
#include "stm32f10x.h"
#define MTM_DMA_CLK RCC_AHBPeriph_DMA1
#define MTM_DMA_CHANNEL DMA1_Channel6
#define MTM_DMA_FLAG_TC DMA1_FLAG_TC6
// 要发送的数据大小
#define BUFFER_SIZE 32
void MtM_DMA_Config(void);
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength);
#endif
DMA.C
我们先创建数据分别存放在FLASH和SRAM中。如下
/* 定义aSRC_Const_Buffer数组作为DMA传输数据源
* const关键字将aSRC_Const_Buffer数组变量定义为常量类型
* 表示数据存储在内部的FLASH中
*/
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
/* 定义DMA传输目标存储器
* 存储在内部的SRAM中
*/
uint32_t aDST_Buffer[BUFFER_SIZE];
根据STM32芯片内核,FLASH来存储常量,所以加const关键字的数据存储在里面,而不加该关键字的变量存储在SRAM
DMA配置(自行选择使用的DMA通道)
void MtM_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(MTM_DMA_CLK,ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;// 配置外设的地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;// 配置存储器地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;// 配置传输方向
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;// 配置传输数目
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;// 配置外设地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;// 配置外设数据宽度
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 配置存储器地址自增
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;// 配置存储器数据宽度
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 配置模式是发一次还是循环
DMA_InitStructure.DMA_Priority = DMA_Priority_High;// 配置优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;// 存储器到存储器模式打开
DMA_Init(MTM_DMA_CHANNEL,&DMA_InitStructure);// 写入到寄存器
DMA_ClearFlag(MTM_DMA_FLAG_TC);// 清除标志位
DMA_Cmd(MTM_DMA_CHANNEL,ENABLE);// 使能dma
}
无非是声明结构体,打开时钟,按序配置结构体的成员(代码当中有注释了),再初始化写入,清除标志位后使能DMA
下面是当DMA传送完成后,进行两个地址的数据的比较
/**
* 判断指定长度的两个数据源是否完全相等,
* 如果完全相等返回1,只要其中一对数据不相等返回0
*/
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength)
{/* 数据长度递减 */
while(BufferLength--)
{
/* 判断两个数据源是否对应相等 */
if(*pBuffer != *pBuffer1)
{
/* 对应数据源不相等马上退出函数,并返回0 */
return 0;
}
/* 递增两个数据源的地址指针 */
pBuffer++;
pBuffer1++;
}
/* 完成判断并且对应数据相对 */
return 1;
}
main
# include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_dma_mtm.h"
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];;
void prepare(uint32_t time);
void Delay( uint32_t count)
{
for(;count != 0;count--);
}
int main(void)
{
uint8_t status = 0;
LED_GPIO_Config();
MtM_DMA_Config();
prepare(0x1F);
while(DMA_GetFlagStatus(MTM_DMA_FLAG_TC) == RESET);
status = Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE);
if(status == 1)
{
LED_G(ON);
}else
{
LED_G(OFF);
}
while(1)
{
}
}
void prepare(uint32_t time){
for(;time;time--){
LED_G(ON);
Delay(0xFFFFF);
LED_G(OFF);
Delay(0xFFFFF);
}
}
主函数没什么技巧,prepare函数主要是实现小灯闪烁,因为DMA不占用CPU,在小灯闪烁的时候就进行DMA的传送,然后判断TC标志位,判断传输完成没有,当传输完成时,就调用判断传输数据对比函数,并相应的根据情况来执行相应的功能
存储器到外设实验
DMA.H
这里是主要的宏定义的信息
#ifndef __BSP_DMA_MTM_H
#define __BSP_DMA_MTM_H
#include "stm32f10x.h"
#include <stdio.h>
/**
* 串口宏定义,不同的串口挂载的总线和IO不一样,移植时需要修改这几个宏
* 1-修改总线时钟的宏,uart1挂载到apb2总线,其他uart挂载到apb1总线
* 2-修改GPIO的宏
*/
#define DEBUG_USART1 1
#define DEBUG_USART2 0
#define DEBUG_USART3 0
#define DEBUG_USART4 0
#define DEBUG_USART5 0
#if DEBUG_USART1
// 串口1-USART1
#define DEBUG_USARTx USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler
#elif DEBUG_USART2
// 串口2-USART2
#define DEBUG_USARTx USART2
#define DEBUG_USART_CLK RCC_APB1Periph_USART2
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_2
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_3
#define DEBUG_USART_IRQ USART2_IRQn
#define DEBUG_USART_IRQHandler USART2_IRQHandler
#elif DEBUG_USART3
// 串口3-USART3
#define DEBUG_USARTx USART3
#define DEBUG_USART_CLK RCC_APB1Periph_USART3
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOB)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOB
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_RX_GPIO_PORT GPIOB
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_11
#define DEBUG_USART_IRQ USART3_IRQn
#define DEBUG_USART_IRQHandler USART3_IRQHandler
#elif DEBUG_USART4
// 串口4-UART4
#define DEBUG_USARTx UART4
#define DEBUG_USART_CLK RCC_APB1Periph_UART4
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOC)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOC
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_RX_GPIO_PORT GPIOC
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_11
#define DEBUG_USART_IRQ UART4_IRQn
#define DEBUG_USART_IRQHandler UART4_IRQHandler
#elif DEBUG_USART5
// 串口5-UART5
#define DEBUG_USARTx UART5
#define DEBUG_USART_CLK RCC_APB1Periph_UART5
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOC
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_12
#define DEBUG_USART_RX_GPIO_PORT GPIOD
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_2
#define DEBUG_USART_IRQ UART5_IRQn
#define DEBUG_USART_IRQHandler UART5_IRQHandler
#endif
#define USART_TX_DMA_CLK RCC_AHBPeriph_DMA1
#define USART_TX_DMA_CHANNEL DMA1_Channel4
#define USART_TX_DMA_FLAG_TC DMA1_FLAG_TC4
// 外设寄存器地址
#define USART_DR_ADDRESS (USART1_BASE + 0x04)
#define SENDBUFF_SIZE 5000
void USARTx_DMA_Config(void);
void USART_Config(void);
#endif
上面是对一些基本的属性进行宏定义,增加代码的可移植性,下面是DMA和串口的初始化,这里主要是把串口的数据存储位置作为DMA发送的一个外设的存储地址,实现直接通过DMA发送数据到串口。串口再把接收到的数据发送到我们的电脑上
DMA.C
这里分别介绍在DMA.C中的每一个部分的函数
下面是串口的初始化(我们在USART介绍中有过详细的讲解,在[C8T6-USART模块(http://t.csdnimg.cn/Xgnwe)一文中有代码小实验)
// 初始化串口
void USART_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK,ENABLE);
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(DEBUG_USARTx,&USART_InitStructure);
USART_Cmd(DEBUG_USARTx,ENABLE);
}
下面是DMA的初始化,主要就是数据接收到的地址发生了改变,这里是串口的数据接收区而不再是芯片里的内存
// Memory -> P (USART -> DR)
void USARTx_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(USART_TX_DMA_CLK,ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(USART_TX_DMA_CHANNEL,& DMA_InitStructure);
DMA_ClearFlag(USART_TX_DMA_FLAG_TC);
DMA_Cmd(USART_TX_DMA_CHANNEL,ENABLE);
}
这里还是相当于主要是对DMA的结构体进行配置
main
# include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_dma_mtp.h"
extern uint8_t SendBuff[SENDBUFF_SIZE];
void Delay( uint32_t count)
{
for(;count != 0;count--);
}
int main(void)
{
for(uint16_t i = 0;i < SENDBUFF_SIZE;i++)
{
SendBuff[i] = 'P';
}
LED_GPIO_Config();
USART_Config();
USARTx_DMA_Config();
USART_DMACmd(DEBUG_USARTx,USART_DMAReq_Tx,ENABLE);
while(1)
{
}
}
这里是先把我们要发生的数据进行初始化,这里是5000个P,而接下来就是初始化每一个部分,这里重点讲解的是最后这个USART_DMACmd()函数
USART_DMACmd 函数用于控制 USART 的 DMA 请求的启动和关闭。它接收三个参数,第一个参数用于设置串口外设,可以是 USART1/2/3 和 UART4/5 这 5 个参数可选,第二个参数设置串口的具体 DMA 请求,有串口发送请求 USART_DMAReq_Tx 和接收请求 USART_DMAReq_Rx 可选,第三个参数用于设置启动请求 ENABLE 或者关闭请求 DISABLE。运行该函数后 USART 的DMA 发送传输就开始了,根据配置存储器的数据会发送到串口
DMA收发数据
在上面的实验主要是通过DMA来进行发送数据,而我们接下来这个程序是通过DMA通道来实现收发数据,而比USART串口通讯更加快速,且不占用CPU内存,且在这里使用的是双缓冲区,这样可以避免因为发送数据过快而导致上一次的数据还没有发送出去就接收新的数据而把未发送出去的数据覆盖了
DMA.H
这里进行头文件的定义,相关信息的声明,还有宏定义,如下:
#ifndef __BSP_DMA_H
#define __BSP_DMA_H
#include "stm32f10x.h"
#include <stdio.h>
#include <stdarg.h>
#include "string.h"
#define USART2_MAX_TX_LEN 1000 // 最大发送缓存字节数
#define USART2_MAX_RX_LEN 1000 // 最大接收缓存字节数
extern uint8_t USART2_TX_BUF[USART2_MAX_TX_LEN]; //发送缓冲区
extern uint8_t u2rxbuf1[USART2_MAX_RX_LEN]; //接收数据缓冲区1
extern uint8_t u2rxbuf2[USART2_MAX_RX_LEN]; //接收数据缓冲区2
extern uint8_t chooseBuf ; //标记当前使用的是哪个缓冲区,0,使用u1rxbuf;1,使用u2rxbuf;
extern uint8_t USART2_TX_FLAG;
extern uint8_t USART2_RX_FLAG;
void DMA1_USART2_Init(void);
void DMA_USART2_Tx_Data(u8 *buffer, u32 size);
void USART2_printf(char *format, ...);
void DMA1_Channel6_IRQHandler(void);
void DMA1_Channel7_IRQHandler(void);
#endif
这里主要解释一下这几个量:
USART2_MAX_TX_LEN 和 USART2_MAX_RX_LEN 是我们一次接收发送缓冲的最大的字节数
USART2_TX_BUF 和 u2rxbuf2 和 u2rxbuf2 是缓冲区,我们稍后介绍怎么工作原理
chooseBuf 是用来确定判断现在该使用的是哪个缓冲区
USART2_TX_FLAG = 0;
USART2_RX_FLAG = 0;
因为我们只能同时进行一次数据发送,所以当我们进行发送时需要判断此时发送的状态,若此时该变量为1说明在进行发送,为0就代表此时并没有进行发送,可以进行发送,在发送时,把该标志位置为1,防止下次发送进入造成干扰,看不懂的话,下面还有解释
双缓冲区的流程原理
当我们发送数据时,第一时间发送到接收缓冲区1,而此时接收缓冲区接收到数据之后就会把接收到的数据发送到电脑,而当我们发送过快时,可能接收缓冲区的数据还没有完全发送到我们的电脑上,此时再接收到新的数据到接收缓冲区1,就会造成数据的覆盖,而发送到我们电脑的数据就会造成影响,所以为了在传输速度高的情况下尽量保证传输数据的准确性,我们在第一次发送数据时,数据传输到接收缓冲区1,此时把我们发送的数据的接收地址切换到接收缓冲区2,,这样接收缓冲区1用来发送刚刚得到的数据,而接收缓冲区2用来接收下次发送到串口的数据,然后在每次中断进行缓冲区的交换
DMA.C
初始化DMA1
// 在这里完成对DMA1的初始化
void DMA1_USART2_Init(void)
{
DMA_InitTypeDef DMA1_Init; //DMA初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //NVIC初始化结构体
// 打开DMA1时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
// 配置从外设到内存接收数据通道
// DMA_USART2_RX USART2->RAM的数据传输
DMA_DeInit(DMA1_Channel6); // 将DMA的通道6寄存器重设为缺省值
DMA1_Init.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR); // 配置外设地址
DMA1_Init.DMA_MemoryBaseAddr = (uint32_t)u2rxbuf1; // 这里内存地址设置为第一个缓冲区
DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC; // 配置方向,从外设到内存
DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN; // DMA通道的DMA缓存的大小
DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址进行自增
DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不进行内存
DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 配置外设数据宽度
DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 配置内存数据宽度
DMA1_Init.DMA_Mode = DMA_Mode_Normal; // 配置模式为一次
DMA1_Init.DMA_Priority = DMA_Priority_High; // 配置优先级
DMA1_Init.DMA_M2M = DMA_M2M_Disable; // 关闭内存到内存
DMA_Init(DMA1_Channel6,&DMA1_Init); // 对DMA1通道6进行初始化
// 配置从内存到外设发送数据通道
// DMA_USART2_TX RAM->USART2的数据传输
DMA_DeInit(DMA1_Channel7); // 将DMA的通道7寄存器重设为缺省值
DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR); // 启动传输前装入实际RAM地址
DMA1_Init.DMA_MemoryBaseAddr = (u32)USART2_TX_BUF; // 设置发送缓冲区首地址
DMA1_Init.DMA_DIR = DMA_DIR_PeripheralDST; // 数据传输方向,从内存发送到外设
DMA1_Init.DMA_BufferSize = USART2_MAX_TX_LEN; // DMA通道的DMA缓存的大小
DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址寄存器不变
DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址寄存器递增
DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 数据宽度为8位
DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 数据宽度为8位
DMA1_Init.DMA_Mode = DMA_Mode_Normal; // 工作在正常模式
DMA1_Init.DMA_Priority = DMA_Priority_High; // DMA通道高优先级
DMA1_Init.DMA_M2M = DMA_M2M_Disable; // DMA通道没有设置为内存到内存传输
DMA_Init(DMA1_Channel7,&DMA1_Init); // 对DMA通道7进行初始化
// DMA1通道6 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; // NVIC通道设置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化NVIC寄存器
//DMA1通道7 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn; // NVIC通道设置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化NVIC寄存器
DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE); // 开USART2 Rx DMA中断
DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE); // 开USART2 Tx DMA中断
DMA_Cmd(DMA1_Channel6,ENABLE); // DMA通道6开始工作
DMA_Cmd(DMA1_Channel7,DISABLE); // 使DMA通道7停止工作,当接收数据时,才会开启
USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE); // 开启串口DMA发送
USART_DMACmd(USART2,USART_DMAReq_Rx,ENABLE); // 开启串口DMA接收
}
注意这里启用的是空闲中断,而不是接收中断,具体差异可以自行搜索,这里简单的提一下,空闲中断是当接收到数据而在下一个数据帧没有接收到数据时就产生中断,去执行中断服务函数
DMA发送部分
//DMA 发送应用源码
void DMA_USART2_Tx_Data(uint8_t *buffer, u32 size)
{
while(USART2_TX_FLAG); //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据)
USART2_TX_FLAG = 1; //USART2发送标志(启动发送)
DMA1_Channel7->CMAR = (uint32_t)buffer; //设置要发送的数据地址
DMA1_Channel7->CNDTR = size; //设置要发送的字节数目
DMA_Cmd(DMA1_Channel7, ENABLE); //开始DMA发送
}
根据 USART2_TX_FLAG 来判断是否进入发送,当为1时代表此时有在进行的发送,进行循环等待,而当该标志为0时,代表没有其它正在进行的发送,此时进入数据的发送,并把标志位置为1,防止其它发送进入干扰
接收不定长数据
void USART2_printf(char *format, ...)
{
//VA_LIST 是在C语言中解决变参问题的一组宏,所在头文件:#include <stdarg.h>,用于获取不确定个数的参数。
va_list arg_ptr; //实例化可变长参数列表
while(USART2_TX_FLAG); //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据)
va_start(arg_ptr, format); //初始化可变参数列表,设置format为可变长列表的起始点(第一个元素)
// USART2_MAX_TX_LEN+1可接受的最大字符数(非字节数,UNICODE一个字符两个字节), 防止产生数组越界
vsnprintf((char*)USART2_TX_BUF, USART2_MAX_TX_LEN+1, format, arg_ptr); //从USART2_TX_BUF的首地址开始拼合,拼合format内容;USART2_MAX_TX_LEN+1限制长度,防止产生数组越界
va_end(arg_ptr); //注意必须关闭
DMA_USART2_Tx_Data(USART2_TX_BUF,strlen((const char*)USART2_TX_BUF)); //发送USART2_TX_BUF内容
}// 其实把我们要发送的数据整合为字符数组的形式,再通过上面的定长数据发送函数进行发送
具体就是把收到的不定长数据整理一个已知长度的数据,再调用上面的发送函数进行发送
通道6接收完成中断
void DMA1_Channel6_IRQHandler(void)
{
uint8_t *p;
if(DMA_GetITStatus(DMA1_IT_TC6)!= RESET) //DMA接收完成标志
{
DMA_ClearITPendingBit(DMA1_IT_TC6); //清除中断标志
USART_ClearFlag(USART2,USART_FLAG_TC); //清除USART2标志位
DMA_Cmd(DMA1_Channel6, DISABLE ); //关闭USART2 TX DMA1 所指示的通道
if(chooseBuf) //之前用的u2rxbuf,切换为u1rxbuf
{
p = u2rxbuf2; //先保存前一次数据地址再切换缓冲区
DMA1_Channel6->CMAR=(uint32_t)u2rxbuf1; //切换为u2rxbuf1缓冲区地址
chooseBuf = 0; //下一次切换为u2rxbuf2
}else //之前用的u2rxbuf1,切换为u2rxbuf2
{
p = u2rxbuf1; //先保存前一次数据地址再切换缓冲区
DMA1_Channel6->CMAR=(uint32_t)u2rxbuf2; //切换为u2rxbuf2缓冲区地址
chooseBuf = 1; //下一次切换为u2rxbuf1
}
DMA1_Channel6->CNDTR = USART2_MAX_RX_LEN; //DMA通道的DMA缓存的大小
DMA_Cmd(DMA1_Channel6, ENABLE); //使能USART2 TX DMA1 所指示的通道
//******************↓↓↓↓↓这里作数据处理↓↓↓↓↓******************//
DMA_USART2_Tx_Data(p,USART2_MAX_RX_LEN); // 接收完成,发送数据
}
}
这里在接收到数据时,根据缓冲区来进行交换缓冲区,再把接收到的数据发送出去
通道7中断
//DMA1通道7中断
void DMA1_Channel7_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC7)!= RESET) //DMA接收完成标志
{
DMA_ClearITPendingBit(DMA1_IT_TC7); //清除中断标志
USART_ClearFlag(USART2,USART_FLAG_TC); //清除串口2的标志位
DMA_Cmd(DMA1_Channel7, DISABLE ); //关闭USART2 TX DMA1 所指示的通道
USART2_TX_FLAG=0; //USART2发送标志(关闭)
}
}
全部代码
#include "bsp_dma.h"
// 参数定义
//USART2_MAX_TX_LEN和USART2_MAX_RX_LEN在头文件进行了宏定义,分别指USART2最大发送长度和最大接收长度
uint8_t USART2_TX_BUF[USART2_MAX_TX_LEN]; // 发送缓冲区的最大为USART2_MAX_TX_LEN字节
uint8_t u2rxbuf1[USART2_MAX_RX_LEN]; // 接收第一个缓冲区,大小为USART2_MAX_RX_LEN字节
uint8_t u2rxbuf2[USART2_MAX_RX_LEN]; // 接收第一个缓冲区,大小为USART2_MAX_RX_LEN字节
uint8_t chooseBuf = 0; // 用来进行选择使用哪一个缓冲区来接收数据
uint8_t USART2_TX_FLAG = 0; // USART2发送标志,启动发送时置1
uint8_t USART2_RX_FLAG = 0; // USART2接收标志,启动发送时置1
// 在这里完成对DMA1的初始化
void DMA1_USART2_Init(void)
{
DMA_InitTypeDef DMA1_Init; //DMA初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //NVIC初始化结构体
// 打开DMA1时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
// 配置从外设到内存接收数据通道
// DMA_USART2_RX USART2->RAM的数据传输
DMA_DeInit(DMA1_Channel6); // 将DMA的通道6寄存器重设为缺省值
DMA1_Init.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR); // 配置外设地址
DMA1_Init.DMA_MemoryBaseAddr = (uint32_t)u2rxbuf1; // 这里内存地址设置为第一个缓冲区
DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC; // 配置方向,从外设到内存
DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN; // DMA通道的DMA缓存的大小
DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址进行自增
DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不进行内存
DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 配置外设数据宽度
DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 配置内存数据宽度
DMA1_Init.DMA_Mode = DMA_Mode_Normal; // 配置模式为一次
DMA1_Init.DMA_Priority = DMA_Priority_High; // 配置优先级
DMA1_Init.DMA_M2M = DMA_M2M_Disable; // 关闭内存到内存
DMA_Init(DMA1_Channel6,&DMA1_Init); // 对DMA1通道6进行初始化
// 配置从内存到外设发送数据通道
// DMA_USART2_TX RAM->USART2的数据传输
DMA_DeInit(DMA1_Channel7); // 将DMA的通道7寄存器重设为缺省值
DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR); // 启动传输前装入实际RAM地址
DMA1_Init.DMA_MemoryBaseAddr = (u32)USART2_TX_BUF; // 设置发送缓冲区首地址
DMA1_Init.DMA_DIR = DMA_DIR_PeripheralDST; // 数据传输方向,从内存发送到外设
DMA1_Init.DMA_BufferSize = USART2_MAX_TX_LEN; // DMA通道的DMA缓存的大小
DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址寄存器不变
DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址寄存器递增
DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 数据宽度为8位
DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 数据宽度为8位
DMA1_Init.DMA_Mode = DMA_Mode_Normal; // 工作在正常模式
DMA1_Init.DMA_Priority = DMA_Priority_High; // DMA通道高优先级
DMA1_Init.DMA_M2M = DMA_M2M_Disable; // DMA通道没有设置为内存到内存传输
DMA_Init(DMA1_Channel7,&DMA1_Init); // 对DMA通道7进行初始化
// DMA1通道6 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; // NVIC通道设置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化NVIC寄存器
//DMA1通道7 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn; // NVIC通道设置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化NVIC寄存器
DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE); // 开USART2 Rx DMA中断
DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE); // 开USART2 Tx DMA中断
DMA_Cmd(DMA1_Channel6,ENABLE); // DMA通道6开始工作
DMA_Cmd(DMA1_Channel7,DISABLE); // 使DMA通道7停止工作,当接收数据时,才会开启
USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE); // 开启串口DMA发送
USART_DMACmd(USART2,USART_DMAReq_Rx,ENABLE); // 开启串口DMA接收
}
//DMA 发送应用源码
void DMA_USART2_Tx_Data(uint8_t *buffer, u32 size)
{
while(USART2_TX_FLAG); //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据)
USART2_TX_FLAG = 1; //USART2发送标志(启动发送)
DMA1_Channel7->CMAR = (uint32_t)buffer; //设置要发送的数据地址
DMA1_Channel7->CNDTR = size; //设置要发送的字节数目
DMA_Cmd(DMA1_Channel7, ENABLE); //开始DMA发送
}
void USART2_printf(char *format, ...)
{
//VA_LIST 是在C语言中解决变参问题的一组宏,所在头文件:#include <stdarg.h>,用于获取不确定个数的参数。
va_list arg_ptr; //实例化可变长参数列表
while(USART2_TX_FLAG); //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据)
va_start(arg_ptr, format); //初始化可变参数列表,设置format为可变长列表的起始点(第一个元素)
// USART2_MAX_TX_LEN+1可接受的最大字符数(非字节数,UNICODE一个字符两个字节), 防止产生数组越界
vsnprintf((char*)USART2_TX_BUF, USART2_MAX_TX_LEN+1, format, arg_ptr); //从USART2_TX_BUF的首地址开始拼合,拼合format内容;USART2_MAX_TX_LEN+1限制长度,防止产生数组越界
va_end(arg_ptr); //注意必须关闭
DMA_USART2_Tx_Data(USART2_TX_BUF,strlen((const char*)USART2_TX_BUF)); //发送USART2_TX_BUF内容
}// 其实把我们要发送的数据整合为字符数组的形式,再通过上面的定长数据发送函数进行发送
//处理DMA1 通道6的接收完成中断
void DMA1_Channel6_IRQHandler(void)
{
uint8_t *p;
if(DMA_GetITStatus(DMA1_IT_TC6)!= RESET) //DMA接收完成标志
{
DMA_ClearITPendingBit(DMA1_IT_TC6); //清除中断标志
USART_ClearFlag(USART2,USART_FLAG_TC); //清除USART2标志位
DMA_Cmd(DMA1_Channel6, DISABLE ); //关闭USART2 TX DMA1 所指示的通道
if(chooseBuf) //之前用的u2rxbuf,切换为u1rxbuf
{
p = u2rxbuf2; //先保存前一次数据地址再切换缓冲区
DMA1_Channel6->CMAR=(uint32_t)u2rxbuf1; //切换为u2rxbuf1缓冲区地址
chooseBuf = 0; //下一次切换为u2rxbuf2
}else //之前用的u2rxbuf1,切换为u2rxbuf2
{
p = u2rxbuf1; //先保存前一次数据地址再切换缓冲区
DMA1_Channel6->CMAR=(uint32_t)u2rxbuf2; //切换为u2rxbuf2缓冲区地址
chooseBuf = 1; //下一次切换为u2rxbuf1
}
DMA1_Channel6->CNDTR = USART2_MAX_RX_LEN; //DMA通道的DMA缓存的大小
DMA_Cmd(DMA1_Channel6, ENABLE); //使能USART2 TX DMA1 所指示的通道
//******************↓↓↓↓↓这里作数据处理↓↓↓↓↓******************//
DMA_USART2_Tx_Data(p,USART2_MAX_RX_LEN); // 接收完成,发送数据
}
}
//DMA1通道7中断
void DMA1_Channel7_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC7)!= RESET) //DMA接收完成标志
{
DMA_ClearITPendingBit(DMA1_IT_TC7); //清除中断标志
USART_ClearFlag(USART2,USART_FLAG_TC); //清除串口2的标志位
DMA_Cmd(DMA1_Channel7, DISABLE ); //关闭USART2 TX DMA1 所指示的通道
USART2_TX_FLAG=0; //USART2发送标志(关闭)
}
}
USART.H
这里要进行串口的初始化,因为DMA收发数据是基于串口的基础上,然后采用DMA通道来进行传输
#ifndef __BSP_USART_H
#define __BSP_USART_H
#include "stm32f10x.h"
#include "bsp_dma.h"
#include <stdio.h>
#include <stdarg.h>
#include "string.h"
// 注意这里使用的是串口二
void Initial_USART2();
void USART2_IRQHandler(void);
#endif
这是串口头文件进行简单的头文件引入和宏定义,注意本实验使用的是串口2
USART.C
这里主要是初始化串口,
初始化
#include "bsp_usart.h"
void Initial_USART2()
{
// 结构体声明
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 打开时钟
// RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能USART2时钟
// 配置GPIO端口设置
// USART2_TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO速率50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化
// USART_RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA.3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART 初始化设置
USART_InitStructure.USART_BaudRate = 115200; // 串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No ; // 无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
USART_Init(USART2, &USART_InitStructure); // 初始化串口2
// 中断开启设置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; // NVIC通道设置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8; // 抢占优先级8
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化NVIC寄存器
USART_ITConfig(USART2,USART_IT_IDLE,ENABLE); // NVIC通道设置开启空闲中断
USART_ClearFlag(USART2,USART_FLAG_TC); // 清除USART2标志位
USART_Cmd(USART2, ENABLE);
DMA1_USART2_Init(); //DMA1_USART2初始化
}
先来解释串口的初始化函数,与往常一样,先打开时钟,然后需要配置TX和RX两个GPIO,接着配置串口结构体的各个成员,最后进行初始化,这里还配置了串口的中断,配置中断源和优先级后,开启我们需要的空闲中断,清除其它标志位防止干扰,最后的DMA1_USART2_Init()是我们自己写的,目的是为了初始化DMA,并开启DMA的接收发送,在DMA的CPP里面,我们讲解了
当串口初始化完成之后,我们还需要编写串口的中断
中断
//串口2中断函数
void USART2_IRQHandler(void)
{
uint8_t *p;
uint8_t USART2_RX_LEN = 0; //接收数据长度
if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET) //串口2空闲中断
{
USART_ReceiveData(USART2); //清除串口2空闲中断IDLE标志位
USART_ClearFlag(USART2,USART_FLAG_TC); //清除USART2标志位
DMA_Cmd(DMA1_Channel6, DISABLE ); //关闭USART2 TX DMA1 所指示的通道
USART2_RX_LEN = USART2_MAX_RX_LEN - DMA1_Channel6->CNDTR; //获得接收到的字节数
if(chooseBuf) //之前用的u2rxbuf,切换为u1rxbuf
{
p = u2rxbuf2; //先保存前一次数据地址再切换缓冲区
DMA1_Channel6->CMAR=(u32)u2rxbuf1; //切换为u1rxbuf缓冲区地址
chooseBuf = 0; //下一次切换为u2rxbuf
}else //之前用的u1rxbuf,切换为u2rxbuf
{
p = u2rxbuf1; //先保存前一次数据地址再切换缓冲区
DMA1_Channel6->CMAR=(u32)u2rxbuf2; //切换为u2rxbuf缓冲区地址
chooseBuf = 1; //下一次切换为u1rxbuf
}
DMA1_Channel6->CNDTR = USART2_MAX_RX_LEN; //DMA通道的DMA缓存的大小
DMA_Cmd(DMA1_Channel6, ENABLE); //使能USART2 TX DMA1 所指示的通道
//******************↓↓↓↓↓这里作数据处理↓↓↓↓↓******************//
DMA_USART2_Tx_Data(p,USART2_RX_LEN);
//******************↑↑↑↑↑这里作数据处理↑↑↑↑↑******************//
}
USART_ClearITPendingBit(USART2,USART_IT_ORE); //清除USART2_ORE标志位
}
这串口中断的流程和前面通道6接收中断是一样的,也是确认标志位,然后就行双缓冲区处理,就是切换缓冲区,根据缓冲区的标志,之后再调用发送的函数