0. 实验目标
使用 DMA 控制 DAC1 和 DAC2 同时输出不一样的波形,实验平台STM32F407ZG
1. 原理部分
1.1 DMA
DMA 全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
STM32F407 最多有 2 个 DMA 控制器( DMA1 和 DMA2 ),两个 DMA 控制器总共有 16 个数据流。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个 DMA 请求的优先权。
使用DMA主要有以下几个配置项需要注意
- DMA 通道选择:外设或者存储器是挂载在不同的DMA通道的不同流中的,查看手册找到要控制的外设挂载在哪里
- DMA 外设地址:DMA所连接的外设的地址,下面会详细写如何查找
- DMA 存储器 0 地址:若配置数据流方向为存储器到外设,则是要传给外设的数据的地址
- DMA 传输方向:支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
- 传输数据量:一次传输多少数据
- 外设是否为增量模式:根据
DMA_SxCR
寄存器中PINC
和MINC
位的状态,外设和存储器指针在每次传输后可以
自动向后递增或保持常量。如果使能了递增模式,则根据在DMA_ SxCR
寄存器PSIZE
或MSIZE
位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增 1 (对于字节)、2 (对于半字)或 4 (对于字)。为了优化封装操作,可以不管AHB
外设端口上传输的数据的大小,将外设地址的增量偏移大小固定下来。DMA_ SxCR
寄存器中的PINCOS
位用于将增量偏移大小与外设AHB
端口或 32 位地址(此时地址递增 4 )上的数据大小对齐。PINCOS
位仅对AHB
外设端口有影响。 - 存储器是否为增量模式:同上
- 外设数据长度:用多少位的方式去解析或者发送数据
- 存储器数据长度:若是从存储器给外设发送数据,则存储器和外设的数据长度应该一致
- DMA 模式:普通模式或者循环模式,选择普通模式
- 优先级:有低、中等、高、非常高的优先级可以选择
- 是否使用FIFO:FIFO 用于在源数据传输到目标之前临时存储这些数据,这里不需要
- 存储器突发传输模式:在直接模式下,数据流只能生成单次传输,而 MBURST[1:0] 和 PBURST[1:0] 位由硬件强制 配置。
- 外设突发传输模式:同上
配置完成后初始化 DMA 然后使能 DMA 就可以完成把数据传输给DAC的寄存器了。
1.2 DAC
我们通过DMA往DAC的寄存器中写入值依次让DAC来产生波形,主要用到下面两个寄存器 DHR12R1
和 DHR12R2
,分别对应 DAC channel1
和 DAC channel2
的 12 位右对齐数据保持寄存器(更详细的 DAC 解析可以查看讲解DAC的博客)
2. STM32代码
2.1 dma.c
#include "dma.h"
//DMAx的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
//mar:存储器地址
//ndtr:数据传输量
//DACch:DAC通道选择,@ref DAC_Channel_1,DAC_Channel_2
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr,u16 DACch)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); // DMA1时钟使能
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} // 等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = chx; // 通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par; // DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar; // DMA存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; // 存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr; // 数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;// 外设数据长度:16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; // 存储器数据长度:16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; // 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 非常高优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 外设突发单次传输
DMA_Init(DMA_Streamx, &DMA_InitStructure); // 初始化DMA Stream
DMA_Cmd(DMA_Streamx, ENABLE);
DAC_DMACmd(DACch,ENABLE);
}
2.2 dac.c
#include "dac.h"
#include "sys.h"
void Dac1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitType;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 模拟输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); // 使能DAC时钟
DAC_InitType.DAC_Trigger=DAC_Trigger_T2_TRGO; // 不使用触发功能 TEN1=0
DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None; // 不使用波形发生
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;// 屏蔽、幅值设置
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; // DAC1输出缓存关闭 BOFF1=1
DAC_Init(DAC_Channel_1,&DAC_InitType); // 初始化DAC通道1
DAC_Cmd(DAC_Channel_1, ENABLE); // 使能DAC通道1
DAC_SetChannel1Data(DAC_Align_12b_R, 0); // 12位右对齐数据格式设置DAC值
}
void Dac2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitType;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 模拟输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); // 使能DAC时钟
DAC_InitType.DAC_Trigger=DAC_Trigger_T2_TRGO; // 不使用触发功能 TEN1=0
DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None; // 不使用波形发生
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;// 屏蔽、幅值设置
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; // DAC2输出缓存关闭 BOFF1=1
DAC_Init(DAC_Channel_2,&DAC_InitType); // 初始化DAC通道2
DAC_Cmd(DAC_Channel_2, ENABLE); // 使能DAC通道2
DAC_SetChannel2Data(DAC_Align_12b_R, 0); // 12位右对齐数据格式设置DAC值
}
2.3 main.c
#include "sys.h"
#include "delay.h"
#include "dma.h"
#include "dac.h"
#include "tim.h"
#define SEND_BUF_SIZE 100//发送数据长度
int main(void)
{
u32 DATA1[SEND_BUF_SIZE]={
0., 84., 167., 251., 334., 418., 501., 585., 669.,
752., 836., 919., 1003., 1086., 1170., 1254., 1337., 1421.,
1504., 1588., 1671., 1755., 1839., 1922., 2006., 2089., 2173.,
2256., 2340., 2424., 2507., 2591., 2674., 2758., 2841., 2925.,
3009., 3092., 3176., 3259., 3343., 3426., 3510., 3594., 3677.,
3761., 3844., 3928., 4011., 4095., 4095., 4011., 3928., 3844.,
3761., 3677., 3594., 3510., 3426., 3343., 3259., 3176., 3092.,
3009., 2925., 2841., 2758., 2674., 2591., 2507., 2424., 2340.,
2256., 2173., 2089., 2006., 1922., 1839., 1755., 1671., 1588.,
1504., 1421., 1337., 1254., 1170., 1086., 1003., 919., 836.,
752., 669., 585., 501., 418., 334., 251., 167., 84.,
0.
};
u32 DATA2[SEND_BUF_SIZE]={
0., 84., 167., 251., 334., 418., 501., 585., 669.,
752., 836., 919., 1003., 1086., 1170., 1254., 1337., 1421.,
1504., 1588., 1671., 1755., 1839., 1922., 2006., 2089., 2173.,
2256., 2340., 2424., 2507., 2591., 2674., 2758., 2841., 2925.,
3009., 3092., 3176., 3259., 3343., 3426., 3510., 3594., 3677.,
3761., 3844., 3928., 4011., 4095., 2048., 2006., 1964., 1923.,
1881., 1839., 1797., 1755., 1714., 1672., 1630., 1588., 1546.,
1505., 1463., 1421., 1379., 1337., 1296., 1254., 1212., 1170.,
1128., 1087., 1045., 1003., 961., 920., 878., 836., 794.,
752., 711., 669., 627., 585., 543., 502., 460., 418.,
376., 334., 293., 251., 209., 167., 125., 84., 42.,
0.
};
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置系统中断优先级分组2
delay_init(168); // 初始化延时函数
Dac1_Init();
Dac2_Init();
// TIM2_Int_Init(100-1,7200-1);//100*7200/84Mhz
TIM2_Int_Init(21-1,2-1); // 21*2/84Mhz=0.5us 采样率为2M
MYDMA_Config(DMA1_Stream5,DMA_Channel_7,(u32)&DAC->DHR12R1,(u32)DATA1,SEND_BUF_SIZE,DAC_Channel_1);// DMA1,Stream5,CH7,外设dac1,长度为:SEND_BUF_SIZE,DAC1----PA4
MYDMA_Config(DMA1_Stream6,DMA_Channel_7,(u32)&DAC->DHR12R2,(u32)DATA2,SEND_BUF_SIZE,DAC_Channel_2);// DMA1,Stream6,CH7,外设dac2,长度为:SEND_BUF_SIZE,DAC2----PA5
while(1)
{
}
}
这里需要注意几点:
SEND_BUF_SIZE
可以自行设定大小,不适合设置太大,DAC 会循环的生成数组里面的数据- 我们配置的 DMA 传输数据长度为16位,但是我们传输的数据是 32 位的数据,此时会发生高位丢失,只出传输了低位过去,由于 DAC 可接收的最大的数据是 4096 ,所以高位丢失不会对结果产生任何的影响
- 数组里面的每一个数字与输出电压的对应关系为
0~4095------0~3.3V
- 定时器的时间决定了DAC发出的每一个点的间隔时间,所以可以通过定时器的配置来修改采样率。上面程序的 DAC 采样率为2M,周期为 50us
3. 电脑生成数据
由于数字和电压存在一一对应的关系,所以可以通过Python来进行批量化数据的生成,下面以三角波为例,由于设置的SEND_BUF_SIZE=100
所以上升和下降都分别为 50 个点,从 0V 到 3.3V 再到 0V,然后复制 x 的值到 stm32 程序中,由于要验证两路DAC的同时输入,可以修改一下数组的数据,然后给第二个数组,让两路DAC输出不一样的波形
#%%
import matplotlib.pyplot as plt
import numpy as np
#%%
x = np.linspace(0,4095,50)
y = np.linspace(4095,0,50)
x = np.append(x,y)
x = np.round(x)
plt.figure()
plt.plot(x)
plt.show()
# %%
下面是我设定的两个波形
4. 验证
连接示波器两个探头分别连接 PA4 和 PA5 ,可以看到如下的图,周期和波形和预期一致,实验完成