一、概述
DMA(直接存储器访问)传输方式无需CPU直接控制传输,也没有中断处理方式需要保存现场和恢复现场,其通过硬件为RAM和I/O设备开辟一条类似队列的数据通道来直接传输数据,可以准确快速地传输数据,能大大提高CPU的效率。但是每一时刻只允许DMA的其中一条通道传输数据。下面是STMF1的DMA1的通道一览表。
UART(全双工通信),就是我们常说的串口,但准确来说UART是指通用异步收发器,还有一种叫做USART(通用同步和异步收发器)。一般来说UART由波特率发生器、UART接收器和UART发送器组成,有三条信号线(Rx、Tx和GND)。这里说一下同步和异步的区别,同步是指发送方发出数据后,等接收方回应下一个数据包的通信方式;异步是指发送方发出数据后,不等接收方的回应,接着发出下个数据包的通信方式。
UART的通信协议一般包含:
起始位:先发出一个逻辑“0”的信号,表示要开始传输数据了;
数据位:紧接着起始位后,可以组织8位数据;
奇偶校验位:数据位之后加这个位,使“1”的位数可以自己构建成奇数或者偶数,用来校验数据传输的准确性;
停止位:最后跟一个逻辑“1”的信号,表示这一帧数据结束了。他可以占1、1.5、2个位;
空闲位:逻辑“1”的状态,表示这条链路没有数据传输;
STM32F1拥有1~3个ADC(模拟数字转换器),这些ADC可以独立使用,也可以使用双重模式以提高采样率。STM32的ADC是12位逐次逼近型的模拟数字转换器。它有18个通道,可以测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐存储在16位数据寄存器中。STM32的ADC最大的转换速率为1MHz,也就是转换时间为1us(在ADCCLK=14M,采样周期为1.5个ADC时钟下得到),一般来说不要让ADC的时钟超出14M,否则会导致结果准确度下降。STM32将ADC的转换分为2个通道组:规则通道组和注入通道组。规则通道组相当于你正常运行的 程序,而注入通道组则相当于中断,所以注入通道的ADC转换是可以打断规则通道的转换的。所以灵活使用可以提高程序对时间处理的速度。下面是STM32F1的ADC通道与GPIO对应表:
二、代码实例
定长的串口DMA初始化代码如下:
void MyDMAConfig(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//使能DMA传输
//Rx
DMA_DeInit(DMA1_Channel5);//将DMA通道5重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;//DMA外设串口基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(&SendBuff);//DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//DMA传输方向,从外设读取到内存
DMA_InitStructure.DMA_BufferSize = sizeof(DMA_Typedef);//缓存区大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//内存数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//DMA通道拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//DMA通道没有设置为内存到内存模式
DMA_Init(DMA_Channel5, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel5,ENABLE);
//Tx
DMA_DeInit(DMA1_Channel4);//将DMA通道4重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;//DMA外设串口基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(&SendBuff);//DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//DMA传输方向,从内存读取到外设
DMA_InitStructure.DMA_BufferSize = sizeof(DMA_Typedef);//缓存区大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//内存数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//DMA通道拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//DMA通道没有设置为内存到内存模式
DMA_Init(DMA_Channel4, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel4,DISABLE);
}
void uart_init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//使能DMA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX GPIOA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = bound;
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(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//使能串口空闲中断
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);//使能串口DMA接收中断
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);//使能串口DMA发送中断
USART_Cmd(USART1, ENABLE);
MyDMAConfig();
}
void Usart1Send(u8 len)
{
DMA_Cmd(DMA1_Channel4,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel4,len);
DMA_Cmd(DMA1_Channel4,ENABLE);
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)
{
USART_DMACmd(USART1,USART_DMAReq_Rx,DISABLE);//一定要加这句,不然当输入长度大于缓冲区容量时,进入串口中断后DMA还会继续接收超出的那部分数据
USART_ReceiveData(USART1);
Usart1_Rec_Cnt = sizeof(DMA_Typedef) - DMA_GetCurrDataCounter(DMA1_Channel5);//算出本帧数据长度
Usart1_Send(Usart1_Rec_Cnt);
USART_ClearITPendingBit(USART1,USART_IT_IDLE);//清中断标志
DMA_Cmd(DMA1_Channel5,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel5,sizeof(DMA_Typedef));//重置接收数据长度
DMA_Cmd(DMA1_Channel5,ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
}
}
ADC扫描模式规则多通道连续转换代码如下:
#define ADC1_DR_Address ((u32)0x4001244C) //ADC数据寄存器地址
#define ADC1_SampleChannel 0x0002 //通道掩码
#define ADC1_SampleTime ADC_SampleTime_13Cycles5 //采样时间
#define ADC1_SampleTimes 1 //采样次数
#define ADC1_NbrOfChannel 1 //转换通道
#define ADC1_SampleBufferSize (ADC1_NbrOfChannel * ADC1_SampleTimes)//缓存大小
__IO uint16_t ADC1_SampleBuffer[10];//数据缓存区
void ADC_Init(void)
{
uint8_t i;
uint16_t temp = 0x0001;
uint8_t rank = 1;
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //开启扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //开启连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = ADC1_NbrOfChannel;
ADC_Init(ADC1, &ADC_InitStructure);
for (i = 0;i < 16;i++)
{
if (ADC1_SampleChannel & temp)
{
ADC_RegularChannelConfig(ADC1, i, rank++, ADC1_SampleTime);
}
temp <<= 1;
}
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC1_SampleBuffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ADC1_SampleBufferSize;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
ADC_RegularChannelConfig的参数Rank的含义:这个在你多通道采集和DMA应用时会有很大的作用,比如你需要多通道采集,你设置每个通道的采集顺序其实就是用这个变量来做的,假设你定义channel 1的rank=1,channel 2的rank=2,那么对应你在DMA缓存空间的变量数组ADCDMA[0]就是channel 1的转换结果,ADCDMA[1]就是通道2的转换结果。
三、总结
以上就是我整理的DMA的简单应用,可能有些地方讲的不好,请各位大佬指出。谢谢!