实战:DMA
简介:
DMA,全称为:Direct
Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
STM32 最多有2个DMA控制器(DMA2仅存在大容量产品中), DMA1有7个通道。DMA2有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个DMA请求的优先权。
DMA主要特性
● 12个独立的可配置的通道(请求): DMA1有7个通道, DMA2有5个通道
● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
● 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
● 支持循环的缓冲器管理
● 每个通道都有3个事件标志(DMA半传输、 DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
● 存储器和存储器间的传输
● 外设和存储器、存储器和外设之间的传输
● 闪存、 SRAM、外设的SRAM、 APB1、 APB2和AHB外设均可作为访问的源和目标。
● 可编程的数据传输数目:最大为65535
下面为功能框图:
本次实验用到的ADC1是通道1
自己将原子哥的例程实验后,将DMA和ADC、DAC进行了结合,原子哥的例程是通过DMA读取数据到串口发送到电脑显示,并且通过寄存器的递减来显示传递的进度然后在电脑显示,实现这个功能后,自己又对前面两章学习的内容进行了结合,设置4个ADC通道,2路DAC输出,DMA进行数据传递,并且给LCD增加了新的功能,下面介绍DMA需要配置的几个寄存器
DMA中断状态寄存器(DMA_ISR)
这次自己分别试了下使用中断和不使用中断,都没有问题,下面说下不使用中断的配置
DMA_GetFlagStatus(DMA1_FLAG_TC1)!=RESET
//等待通道1传输完成
DMA中断标志清除寄存器(DMA_IFCR)
DMA_ClearFlag(DMA1_FLAG_TC1);//清除通道1传输完成标志
DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)
DMA_InitStruct.DMA_BufferSize
= cndtr; //设置为4,有4个通道的ADC数据
DMA_InitStruct.DMA_DIR
= DMA_DIR_PeripheralSRC;//外设通过DMA向内存存入数据
DMA_InitStruct.DMA_M2M
= DMA_M2M_Disable; //DMA通道x不设置为内存到内存传输
DMA_InitStruct.DMA_MemoryBaseAddr
= cmar; //DMA内存基地址
DMA_InitStruct.DMA_MemoryInc
= DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStruct.DMA_Mode
= DMA_Mode_Circular; //循环DMA模式(适合ADC扫描类)
DMA_InitStruct.DMA_PeripheralBaseAddr
= cpar; //DMA外设ADC基地址
DMA_InitStruct.DMA_PeripheralInc
= DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStruct.DMA_Priority=DMA_Priority_High; //DMA通道优先级高
DMA_Init(DMA_CHx,
&DMA_InitStruct);
DMA_Cmd(DMA1_Channel1,ENABLE);
//使能DMA1通道1传输
DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)
DMA_InitStruct.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_HalfWord;//外设数据位宽16位(AD转换数据为16位)
DMA通道x存储器地址寄存器(DMA_CMARx)(x = 1…7)
DMA_InitStruct.DMA_MemoryDataSize =
DMA_MemoryDataSize_HalfWord;//内存数据位宽16位
设置扫描模式,不用每次都用单次转换和读取一个规则通道的值了,那样的话必须按顺序来读,而用ADC的扫描模式则可以设置ADC的采样顺序了.
ADC_InitStruct.ADC_ContinuousConvMode
= ENABLE;//开启连续转换
ADC_InitStruct.ADC_ScanConvMode
= ENABLE;//开启扫描模式
ADC_InitStruct.ADC_NbrOfChannel
= 4;//顺序进行规则转换的ADC通道的数目
//设置4通道的采样顺序和采样时间(可以更改)
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_239Cycles5);
最后设置一个数组来保存ADC_DR寄存器的4个通道值
ADC_ConvertedValue[4];
MY_DMA_Init(DMA1_Channel1, (u32)&ADC1->DR, (u32)ADC_ConvertedValue, 4);//传递进形参地址
关于LCD的新功能就是将printf函数重映像到屏幕上,之前试过没有x,y轴定位的方式,但是实际意义不大,这次修改了一下可以支持定位操作了,实现的方法来自于论坛的网友提供,对于原子哥之前要取出整数和小数部分显得比较麻烦,所以用printf的%f直接输出就可以了,以后直接可以用屏幕来打印信息了,免得开串口那么麻烦,哈哈,懒人神技!
printf重定向函数:
[mw_shl_code=c,true]//重定义fputc函数
int fputc(int ch, FILE *f)
{
static u16 x=0, y=0;
if(ch == '\n')
{
x = 0;
y += 12;
return ch;
}
if(x > 240-6)
{
x = 0;
y += 12;
}
if(y > 320-12)
{
y = 0;
LCD_Clear(WHITE);
}
LCD_Show_Char(x, y, ch, 12);
x+=6;
return ch;
}
#include <stdarg.h>//加入头文件[/mw_shl_code]
[mw_shl_code=c,true]void LCD_ShowString(u16 x,u16 y,const u8 *p)
{
u16 x1;
x1=x;
while(*p!='\0')
{
if(x>=lcddev.width){x=x1;}
if(y>=lcddev.height){y=x=0;LCD_Clear(WHITE);}
if(*p=='\n')
{
y+=16;
x=x1;
}
else
{
LCD_Show_Char(x,y,*p,16);
x+=8;
}
p++;
}
}
void LCD_printf(u16 x,u16 y,const char *format, ...)
{
char tmp[25];
__va_list arg;
va_start(arg, format);
vsprintf(tmp,format,arg);
va_end(arg);
LCD_ShowString(x,y,(const u8*)tmp);
}[/mw_shl_code]
DAC输出:
Dac1_Set_Vol(1500); //1.5V
Dac2_Set_Vol(2100); //2.1V
ADC获取:
adcx = ADC_ConvertedValue;
temp = (float)adcx*(3.3/4096);
LCD_printf(96, 40+i*16, "%1.3fV",
temp);
算出值后直接定位打印出来就好了,非常方便,下面是例程的效果图,4路ADC+2路DAC+DMA的效果(本章代码在附件中,有需要的可以参考下)
|