对于STM32的ADC模数转换器的介绍以及配置在文章《STM32单片机(六). 传感器的使用》中已经详细介绍,在本章节中主要介绍DAC数模转换器以及DMA的使用。
DAC(Digital to analog converter),数字模拟转换器,可以将数字信号转换为模拟信号。DAC可以输出电压模拟信号,用来去驱动其它器件。STM32F1中的DAC模块是由12位电压输出数模转换器,可以配置为8位或12位模式,也可以与DMA控制器配合使用。在12位模式下,数据可使用左对齐或右对齐方式;8位模式下,数据只有右对齐方式。DAC含有两个输出通道,每个通道各对应一个转换器。在双通道模式下,每个通道可进行单独的转换;当两个通道组合在一起同步执行更新操作时,也可同时进行转换。DAC可通过输入参考电压引脚VREF+来提高转换后的数据精度,对于DAC通道的模块框图如下所示:
使用库函数对DAC进行配置需要使用到库文件stm32f10x_dac.h和stm32f10x_dac.c,详细的步骤如下:
1、 使能端口及DAC时钟,设置引脚为模拟输入模式;
在STM32F103中DAC两个通道对应的是PA4和PA5引脚,因此需要使能GPIOA以及DAC时钟,DAC是挂载在APB1总线上的设备,GPIOA是挂载在APB2总线上的设备。调用函数:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//使DA时钟
2、 初始化DAC,设置DAC工作模式;
调用函数:void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct);
第一个参数选取DAC通道(DAC_Channelx),第二个参数结构体DAC_InitTypeDef的成员变量如下:
typedef struct
{
uint32_t DAC_Trigger; //DAC触发选择
uint32_t DAC_WaveGeneration; //DAC波形发生
uint32_t DAC_LFSRUnmask_TriangleAmplitude; //屏蔽/幅值选择器
uint32_t DAC_OutputBuffer; //DAC输出缓存
}DAC_InitTypeDef;
3、 使能DAC输出通道;
调用函数:void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
4、 设置DAC输出值;
在使用12位数据右对齐的工作模式下,通过设置DHR12R1就可已在DAC输出引脚得到不同的电压值,调用函数:DAC_SetChannel1Data(DAC_Align_12b_R, 0);
读取DAC对应通道最后一次转换值调用函数:uint16_t DAC_GetDataOutputValue(uint32_t DAC_Channel);
STM32使用按键控制DAC输出电压的大小,并将该数据通过串口输出,详细的代码模块如下:
dac.h
#ifndef _dac_H
#define _dac_H
#include "system.h"
void DAC1_Init(void);
#endif
dac.c
#include "dac.h"
void DAC1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
DAC_InitStruct.DAC_Trigger=DAC_Trigger_None; //不使用触发
DAC_InitStruct.DAC_WaveGeneration=DAC_WaveGeneration_None; //不使用波形输出
DAC_InitStruct.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
DAC_InitStruct.DAC_OutputBuffer=DAC_OutputBuffer_Disable; //关闭输出缓存
DAC_Init(DAC_Channel_1,&DAC_InitStruct);
DAC_Cmd(DAC_Channel_1, ENABLE);
DAC_SetChannel1Data(DAC_Align_12b_R,0); //初始电压值设置为0
}
main.c
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "key.h"
#include "dac.h"
int main()
{
u8 i=0;
u8 key;
u16 dacVal;
u16 dac_value;
float U;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
USART1_Init(9600);
DAC1_Init();
KEY_Init();
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP)
{
dacVal+=400;
if(dacVal>=4095)
{
dacVal=4095;
}
DAC_SetChannel1Data(DAC_Align_12b_R,dacVal); //更新电压
}
else if(key==KEY_DOWN)
{
dacVal-=400;
if(dacVal<=0)
{
dacVal=0;
}
DAC_SetChannel1Data(DAC_Align_12b_R,dacVal);
}
i++;
if(i%20==0)
{
led1=!led1;
}
if(i%50==0)
{
dac_value=DAC_GetDataOutputValue(DAC_Channel_1);
U=(float)dac_value*(3.3/4096);
printf("输出电压为:%.2f\n",U);
}
delay_ms(10);
}
}
DMA,Direct Memory Access,直接存储器访问。STM32中DMA可以实现外设与寄存器之间,存储器与存储器之间高效的数据传输,DMA传输数据的过程不需要CPU直接操作可以节省对CPU的占用。DMA是RAM和IO设备之间数据传输的通路,外设和存储器之间,存储器和存储器之间可以直接在通道上进行数据传输。在STM32F1中最多有2个DMA控制器(DMA2仅存在于大容量产品系列),DMA1有7个通道,DMA2有5个通道,每个通道可管理来自于一个或者多个外设对存储器访问的请求,面对这些请求通过一个仲裁器来协调其优先权。对于DMA的结构框图如下所示:
DMA和Cortex-M3内核共享系统数据总线,当CPU和DMA同时访问相同的目标时,DMA会请求暂停CPU访问系统总线若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线带宽。
DMA的处理过程: 当发生一个事件后,外设向DMA控制器发送一个请求信号,DMA控制器根据通道优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即给其发送一个应答信号。外设从DMA得到应答信号后,外设立即释放请求,请求一旦被释放,DMA控制器同时撤销硬打信号。当有更多的请求时,外设可以启动下一个周期。
DMA的数据配置: 外设到存储器,使用外设到存储器传输时,DMA外设寄存器的地址为外设的数据寄存器地址,DMA存储器的地址为用户自定义变量的地址;存储器到外设,使用存储器到外设的传输时,DMA外设寄存器地址对应的是其数据寄存器地址,DMA存储器地址为用户自定义变量(缓冲区,存储通过外设的数据)的地址;存储器到存储器,DMA外设寄存器地址为内部存储器(如Flash,可将其看作是外设)的地址,DMA存储器地址为用户自定义的变量(缓冲区,存储来自内部Flash的数据)的地址。DMA的传输完成分为两种模式,一次传输和循环传输。如果使用一次传输后DMA停止,若要在此传输,必须失能DMA后在重新配置方可继续传输;当使用循环传输时,一次传输完成后又再次恢复第一次传输时的配置进行数据传输,不断重复。
对于DMA的配置,需要使用到库函数文件stm32f10x_dma.h和stm32f10x_dma.c,详细的步骤如下所示:
1、 使能DMA控制器时钟;
使能DMA时钟,需要通过AHB1ENR寄存器控制,调用函数:void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
2、 初始化DMA通道;
需要配置DMA的通道、外设和内存地址、通道优先级以及传输数据量等,调用函数:void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
其中第一个参数用于选择哪个DMA的通道x,第二个参数是一个结构体变量包含的成员变量如下:
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_Mode; // 模式选择
uint32_t DMA_M2M; // 存储器到存储器模式
} DMA_InitTypeDef;
3、 使能外设DMA功能;
配置好DMA后,需要使能外设的DMA功能,根据使用外设的不同调用相应的函数,如使能串口DMA的发送功能需要调用:USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
4、 开启DMA通道传输;
调用函数:void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Chammelx, FunctionalState NewState);
5、 查询DMA传输状态。
查询状态调用函数:FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
获取当前传输剩余数据量大小调用函数:uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
设置传输数据量大小调用函数:void DMA_SeyCurrDatatCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
STM32使用按键控制DMA串口USART1的数据传送,详细的代码模块如下:
dma.h
#ifndef _dma_H
#define _dma_H
#include "system.h"
void DMAx_Init(u32 paddr,u32 maddr,u16 buffsize,DMA_Channel_TypeDef* DMAy_Channelx);
void DMAx_Enable(DMA_Channel_TypeDef* DMAy_Channelx,u16 ndtr);
#endif
dma.c
#include "dma.h"
void DMAx_Init(u32 paddr,u32 maddr,u16 buffsize,DMA_Channel_TypeDef* DMAy_Channelx)
{
DMA_InitTypeDef DMA_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_InitStruct.DMA_PeripheralBaseAddr=paddr; //外设地址
DMA_InitStruct.DMA_MemoryBaseAddr=maddr; //内存地址
DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralDST; //存储器到外设
DMA_InitStruct.DMA_BufferSize=buffsize; //传输数据量大小
DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable; //外设地址不增
DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable; //内存地址自增
DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte; //外设数据单位
DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte; //内存数据单位
DMA_InitStruct.DMA_Mode=DMA_Mode_Normal; //一次传输
DMA_InitStruct.DMA_Priority=DMA_Priority_Medium; //通道优先级中
DMA_InitStruct.DMA_M2M=DMA_M2M_Disable; //存储器到存储器失能
DMA_Init(DMAy_Channelx,&DMA_InitStruct);
}
void DMAx_Enable(DMA_Channel_TypeDef* DMAy_Channelx,u16 ndtr)
{
DMA_Cmd(DMAy_Channelx, DISABLE);
DMA_SetCurrDataCounter(DMAy_Channelx,ndtr);
DMA_Cmd(DMAy_Channelx, ENABLE);
}
main.c
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "key.h"
#include "dma.h"
#define buf_len 5000
u8 sendBuffer[buf_len];
void send_Data(u8 *p)
{
u16 i;
for(i=0;i<buf_len;i++)
{
*p='5';
p++;
}
}
int main()
{
u8 i=0;
u8 key;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
USART1_Init(9600);
KEY_Init();
DMAx_Init((u32)&USART1->DR,(u32)sendBuffer,buf_len,DMA1_Channel4);
send_Data(sendBuffer);
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP)
{
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //开启外设DMA功能
DMAx_Enable(DMA1_Channel4,buf_len);
while(1)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=0) //判断DMA数据是否发送完成
{
DMA_ClearFlag(DMA1_FLAG_TC4);
break;
}
led2=!led2;
delay_ms(300);
}
}
i++;
if(i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}