STM32入门学习 第八天

提示:今天学习DAC,PWM DAC,DAM

目录

第一讲 DAC数模转换实验

1.1数模转换DAC原理 

 1.2寄存器库函数配置

1.2.1寄存器的配置

1.2.2 库函数的配置

1.3硬件设计

 1.4软件设计

第二讲 PWM DAC实验

1.PWM DAC 简介

2.硬件设计

3.软件设计

第三讲 DMA 实验

 1.STM32 DMA 简介

1.1 DMA特性

1.2寄存器介绍 

1.3库函数介绍

2.硬件设计

3.软件设计


 

第一讲 DAC数模转换实验

  • 1.1数模转换DAC原理
  • 1.2寄存器库函数配置
  • 1.3硬件设计
  • 1.4实验程序讲解

1.1数模转换DAC原理 

STM32 的 DAC 模块(数字/模拟转换模块)是 12 位数字输入,电压输出型的 DAC。DAC 可 以配置为 8 位或 12 位模式,也可以与 DMA 控制器配合使用。DAC 工作在 12 位模式时,数据可以设置成左对齐或右对齐。DAC 模块有 2 个输出通道,每个通道都有单独的转换器。在双 DAC 模式下,2 个通道可以独立地进行转换,也可以同时进行转换并同步地更新 2 个通道的输 出。DAC 可以通过引脚输入参考电压 VREF+以获得更精确的转换结果。

STM32 的 DAC 模块主要特点有: ① 2 个 DAC 转换器:每个转换器对应 1 个输出通道 ② 8 位或者 12 位单调输出 ③ 12 位模式下数据左对齐或者右对齐 ④ 同步更新功能 ⑤ 噪声波形生成 ⑥ 三角波形生成 ⑦ 双 DAC 通道同时或者分别转换 ⑧ 每个通道都有 DMA 功能

VDDA 和 VSSA 为 DAC 模块模拟部分的供电,而 Vref+则是 DAC 模块的参考电压。 DAC_OUTx 就是 DAC 的输出通道了(对应 PA4 或者 PA5 引脚)。

DAC 输出是受 DORx 寄存器直接控制的,但是我们不能直接往 DORx 寄存器写入数据,而是通过 DHRx 间接的传给 DORx 寄存器,实现对 DAC 输出的控制。前面我们提到,STM32 的 DAC 支持 8/12 位模式,8 位模式的时候是固定的右对齐的,而 12 位模式 又可以设置左对齐/右对齐。单 DAC 通道 x,总共有 3 种情况:

  1. 8 位数据右对齐:用户将数据写入 DAC_DHR8Rx[7:0]位(实际是存入 DHRx[11:4]位)。
  2. 12 位数据左对齐:用户将数据写入 DAC_DHR12Lx[15:4]位(实际是存入 DHRx[11:0]位)。
  3. 12 位数据右对齐:用户将数据写入 DAC_DHR12Rx[11:0]位(实际是存入 DHRx[11:0位)。

如果没有选中硬件触发(寄存器 DAC_CR1 的 TENx 位置’0’),存入寄存器 DAC_DHRx 的数据会在一个 APB1 时钟周期后自动传至寄存器 DAC_DORx。如果选中硬件触发(寄存器 DAC_CR1 的 TENx 位置’1’),数据传输在触发发生以后 3 个 APB1 时钟周期后完成。 一旦 数据从 DAC_DHRx 寄存器装入 DAC_DORx 寄存器,在经过时间 之后,输出即有效, 这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从 STM32F103ZET6 的数据手册查到的典型值为 3us,最大是 4us。所以 DAC 的转换速度最快是 250K 左 右。

 当 DAC 的参考电压为 Vref+的时候,DAC 的输出电压是线性的从 0~Vref+,12 位模式下 DAC 输出电压与 Vref+以及 DORx 的计算公式如下: DACx 输出电压=Vref*(DORx/4095)

 1.2寄存器库函数配置

1.2.1寄存器的配置

下要实现 DAC 的通道 1 输出,需要用到的一些寄存器。首先是 DAC 控制寄存器 DAC_CR,DAC_CR 的低 16 位用于控制通道 1,而高 16 位用于控制通道 2。

在 DAC_CR 设置好之后,DAC 就 可以正常工作了,我们仅需要再设置 DAC 的数据保持寄存器的值,就可以在 DAC 输出通道得 到你想要的电压了(对应 IO 口设置为模拟输入)。我们用的是 DAC 通道 1 的 12 位右对 齐数据保持寄存器:DAC_DHR12R1:

 这里的位数已经在上面写过,就不再过多描述了。

 1.2.2 库函数的配置

1)开启 PA 口时钟,设置 PA4 为模拟输入。

STM32F103ZET6 的 DAC 通道 1 在 PA4 上,所以,我们先要使能 PORTA 的时钟,然后设置 PA4 为模拟输入。DAC 本身是输出,端口要设置为模拟输入模式,一但使能 DACx 通道之后,相应的 GPIO 引脚(PA4 或者 PA5)会自动与 DAC 的模拟输出相连,设置为输入,是为了避免额外的干扰。

使能 GPIOA 时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE ); //使能PORTA时钟
设置 PA1 为模拟输入只需要设置初始化参数即可:
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输

 2)使能 DAC1 时钟。

同其他外设一样,要想使用,必须先开启相应的时钟。STM32 的 DAC 模块时钟是由 APB1 提供的,所以我们调用函数 RCC_APB1PeriphClockCmd()设置 DAC 模块的时钟使能。

void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct)
结构体类型 DAC_InitTypeDef 的定义:
typedef struct
{
 uint32_t DAC_Trigger; 
 uint32_t DAC_WaveGeneration; 
 uint32_t DAC_LFSRUnmask_TriangleAmplitude; 
 uint32_t DAC_OutputBuffer; 
}DAC_InitTypeDef;

这个结构体的定义只有四个成员变量,

第一个参数 DAC_Trigger 用来设置是否使用触发功能,前面已经讲解过这个的含义,这里我们 不是用触发功能,所以值为 DAC_Trigger_None。 第二个参数 DAC_WaveGeneratio 用来设置是否使用波形发生,这里我们前面同样讲解过不使 用。所以值为 DAC_WaveGeneration_None。 第三个参数 DAC_LFSRUnmask_TriangleAmplitude 用来设置屏蔽/幅值选择器,这个变量只在使 用波形发生器的时候才有用,这里我们设置为 0 即可,值为 DAC_LFSRUnmask_Bit0。 第四个参数 DAC_OutputBuffer 是用来设置输出缓存控制位,我们不使用输出缓存, 所以值为 DAC_OutputBuffer_Disable。

4)使能 DAC 转换通道

初始化 DAC 之后,理所当然要使能 DAC 转换通道,库函数方法是: DAC_Cmd(DAC_Channel_1, ENABLE); //使能 DAC1

 5)设置 DAC 的输出值。

通过前面 4 个步骤的设置,DAC 就可以开始工作了,我们使用 12 位右对齐数据格式,所以我们通过设置 DHR12R1,就可以在 DAC 输出引脚(PA4)得到不同的电压值了。库函数的 函数是: DAC_SetChannel1Data(DAC_Align_12b_R, 0);

第一个参数设置对齐方式,可以为 12 位右对齐 DAC_Align_12b_R,12 位左对齐 DAC_Align_12b_L 以及 8 位右对齐 DAC_Align_8b_R 方式。

第二个参数就是 DAC 的输入值了,这个很好理解,初始化设置为 0。 这里,还可以读出 DAC 的数值,函数是: DAC_GetDataOutputValue(DAC_Channel_1);

1.3硬件设计

硬件资源有:1)指示灯 DS0 2) WK_UP 和 KEY1 按键 3) 串口 4) TFTLCD 模块 5) ADC 6) DAC

我们使用 DAC 通道 1 输出模拟电压,然后通过 ADC1 的通道 1 对该输出电压进行 读取,并显示在 LCD 模块上面,DAC 的输出电压,我们通过按键(或 USMART)进行设置。ADC 采集 DAC 的输出电压,所以需要在硬件上把他们短接起来。

 1.4软件设计

dac 相 关的函数分布在固件库文件 stm32f10x_dac.c 文件和 stm32f10x_dac.h 头文件中。

#include "dac.h"
//DAC 通道 1 输出初始化
void Dac1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitType;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE ); //①使能 PA 时钟
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE ); //②使能 DAC时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //①初始化 GPIOA
GPIO_SetBits(GPIOA,GPIO_Pin_4) ; //PA.4 输出高
DAC_InitType.DAC_Trigger=DAC_Trigger_None; //不使用触发功能
DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;//不使用波形发生
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; //DAC1 输出缓存关
 DAC_Init(DAC_Channel_1,&DAC_InitType); //③初始化 DAC 通道 1
DAC_Cmd(DAC_Channel_1, ENABLE); //④使能 DAC1
 DAC_SetChannel1Data(DAC_Align_12b_R, 0); //⑤12 位右对齐,设置 DAC 初始值
}
//设置通道 1 输出电压
//vol:0~3300,代表 0~3.3V
void Dac1_Set_Vol(u16 vol)
{
float temp=vol;
temp/=1000;
temp=temp*4096/3.3;
DAC_SetChannel1Data(DAC_Align_12b_R,temp);// 12 位右对齐设置 DAC 值
}

第二讲 PWM DAC实验

  • 1.PWM DAC 简介
  • 2.硬件设计
  • 3.软件设计

1.PWM DAC 简介

PWM:脉冲宽度调制,本质上其实就是是一种周期一定,而高低电平占空比可调的方波。

虽然大容量的 STM32F103 具有内部 DAC,但是更多的型号是没有 DAC 的,不过 STM32 所有的芯片都有 PWM 输出,因此,我们可以用 PWM+简单的 RC 滤波来实现 DAC 输出, 从而节省成本。

实际电路的典 型 PWM 波形:

 T 是单片机中计数脉冲的基本周期,也就是 STM32 定时器的计数频率的倒数。 N 是 PWM 波一个周期的计数脉冲个数,也就是 STM32 的 ARR-1 的值。n 是 PWM 波一个 周期中高电平的计数脉冲个数,也就是 STM32 的 CCRx 的值。VH 和 VL 分别是 PWM 波的 高低电平电压值,k 为谐波次数,t 为时间。

将①式展开成傅里叶级数,得到公式②

 从②式可以看出,式中第 1 个方括弧为直流分量,第 2 项为 1 次谐波分量,第 3 项为大 于 1 次的高次谐波分量。式②中的直流分量与 n 成线性关系,并随着 n 从 0 到 N,直流分量 从 VL 到 VL+VH 之间变化。这正是电压输出的 DAC 所需要的。因此,如果能把式②中除 直流分量外的谐波过滤掉,则可以得到从 PWM 波到电压输出 DAC 的转换,即:PWM 波 可以通过一个低通滤波器进行解调。式②中的第 2 项的幅度和相角与 n 有关,频率为 1/(NT), 其实就是 PWM 的输出频率。该频率是设计低通滤波器的依据。如果能把 1 次谐波很好过滤 掉,则高次谐波就应该基本不存在了。

PWM DAC 的分辨率计算公式:分辨率=log2(N)

这里假设 n 的最小变化为 1,当 N=256 的时候,分辨率就是 8 位。而 STM32 的定时器 都是 16 位的,可以很容易得到更高的分辨率,分辨率越高,速度就越慢。不过我们在本章 要设计的 DAC 分辨率为 8 位。 在 8 位分辨条件下,我们一般要求 1 次谐波对输出电压的影响不要超过 1 个位的精度,也就是 3.3/256=0.01289V。假设 VH 为 3.3V,VL 为 0V,那么一次谐波的最大值是 2*3.3/ π=2.1V,这就要求我们的 RC 滤波电路提供至少-20lg(2.1/0.01289)=-44dB 的衰减。 STM32 的定时器最快的计数频率是 72Mhz,8 为分辨率的时候,PWM 频率为 72M/256=281.25Khz。如果是 1 阶 RC 滤波,则要求截止频率为 1.77Khz,如果为 2 阶 RC 滤 波,则要求截止频率为 22.34Khz

战舰 STM32 开发板的 PWM DAC 输出采用二阶 RC 滤波:

 二阶 RC 滤波截止频率计算公式为: f=1/2πRC 以上公式要求 R1=R2=R,C2=C2=C。根据这个公式,我们计算出图 26.1.2 的截止频率 为:33.8Khz 超过了 22.34Khz,这个和我们前面提到的要求有点出入,原因是该电路我们还 需要用作 PWM DAC 音频输出,而音频信号带宽是 22.05Khz,为了让音频信号能够通过该 低通滤波,同时为了标准化参数选取,所以确定了这样的参数。实测精度在 0.5LSB 以内。

2.硬件设计

硬件资源有: 1) 指示灯 DS0 2) WK_UP 和 KEY1 按键 3) 串口 4) TFTLCD 模块 5) ADC 6) PWM DAC

我们使用 STM32 的定时器 1 的通道 1(PA8)输出 PWM,经过二阶 RC 滤波后, 转换为直流输出,实现 PWM DAC。同上一讲一样,我们通过 ADC1 的通道 1(PA1)读取 PWM DAC 的输出,并在 LCD 模块上显示相关数值,通过按键和 USMART 控制 PWM DAC 的输出值。我们需要用到 ADC 采集 DAC 的输出电压,所以需要在硬件上将 PWM DAC 和 ADC 短接起来

3.软件设计

添加了一个 新的函数:TIM1_PWM_Init,

//TIM1 CH1 PWM 输出设置
//PWM 输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM1_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //使能 TIMx 外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能 PA 时钟
 //设置该引脚为复用输出功能,输出 TIM1 CH1 的 PWM 脉冲波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM1_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用功能输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIO
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载周期值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //初始化 TIMx
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //CH1 PWM2 模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //OC1 低电平有效
TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据指定的参数初始化外设 TIMx
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1 预装载使能
TIM_ARRPreloadConfig(TIM1, ENABLE); //使能 TIMx 在 ARR 上的预装载寄存器
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能,高级定时器必须开启
TIM_Cmd(TIM1, ENABLE); //使能 TIMx
} 

该函数用来初始化 TIM1_CH1 的 PWM 输出(PA8),其原理同之前介绍的 PWM 输出 一模一样,只是换过一个定时器而已。不过这里 TIM1 是高级定时器,高级定时器的 PWM 输出,与普通定时器稍有区别,必须通过函数 TIM_CtrlPWMOutputs 来设置 BDTR 寄存器 的 MOE 位为 1,才可以正常输出 PWM。

第三讲 DMA 实验

将利用 STM32F1 的 DMA 来实现串口数据传送,并在 TFTLCD 模块上显示当前的传送进度

  • 1.STM32 DMA 简介
  • 2.硬件设计
  • 3.软件设计

 1.STM32 DMA 简介

DMA,全称为:Direct Memory Access,即直接存储器访问,DMA 传输将数据从一个地址空间复制到另外一个地址空间。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器 来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工 作。DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备 开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。

1.1 DMA特性

STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中),DMA1 有 7 个通道。DMA2 有 5 个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起 来协调各个 DMA 请求的优先权。

STM32 的 DMA 有以下一些特性:

  • 每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
  • 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。
  • 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  • 支持循环的缓冲器管理
  • 每个通道都有 3 个事件标志(DMA 半传输,DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。
  • 存储器和存储器间的传输
  • 外设和存储器,存储器和外设的传输
  • 闪存、SRAM、外设的 SRAM、APB1 APB2 和 AHB 外设均可作为访问的源和目标。
  • 可编程的数据传输数目:最大为 65536

 STM32F103ZET6 有两个 DMA 控制器,DMA1 和 DMA2,本章,我们仅针对 DMA1 进行研究。

从外设(TIMx、ADC、SPIx、I2Cx 和 USARTx)产生的 DMA 请求,通过逻辑或输入到 DMA 控制器,这就意味着同时只能有一个请求有效。外设的 DMA 请求,可以通过设置相应的外设寄存器中的控制位,被独立地开启或关闭。

这里解释一下上面说的逻辑或,例如通道 1 的几个 DMA1 请求(ADC1、TIM2_CH3、TIM4_CH1), 这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个。其他通道也是类似的。 这里我们要使用的是串口 1 的 DMA 传送,也就是要用到通道 4。

1.2寄存器介绍 

第一个是 DMA 中断状态寄存器(DMA_ISR)。

 DMA_ISR 中这些中断,在达到条件后就会跳到中断服务函数里面去,即使 没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx, 即通道 DMA 传输完成与否的标志。注意此寄存器为只读寄存器,所以在这些位被置位之后,只 能通过其他的操作来清除。

第二个是 DMA 中断标志清除寄存器(DMA_IFCR)。

 DMA_IFCR 的各位就是用来清除 DMA_ISR 的对应位的,通过写 0 清除。在 DMA_ISR 被置位后, 我们必须通过向该位寄存器对应的位写入 0 来清除。

第三个是 DMA 通道 x 配置寄存器(DMA_CCRx)(x=1~7,下同)。该寄存器控制着 DMA 的很多相关信息, 包括数据宽度、外设及存储器的宽度、通道优先级、增量模式、传输方向、中断允许、使能等 都是通过该寄存器来设置的。所以 DMA_CCRx 是 DMA 传输的核心控制寄存器。

第四个是 DMA 通道 x 传输数据量寄存器(DMA_CNDTRx)。这个寄存器控制 DMA 通道 x 的每次 传输所要传输的数据量。其设置范围为 0~65535。并且该寄存器的值会随着传输的进行而减少, 当该寄存器的值为 0 的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存 器的值来知道当前 DMA 传输的进度。

第五个是 DMA 通道 x 的外设地址寄存器(DMA_CPARx)。该寄存器用来存储 STM32 外设的地 址,比如我们使用串口 1,那么该寄存器必须写入 0x40013804(其实就是&USART1_DR)。如果使 用其他外设,就修改成相应外设的地址就行了。

最后一个是 DMA 通道 x 的存储器地址寄存器(DMA_CMARx),该寄存器和 DMA_CPARx 差不多, 但是是用来放存储器的地址的。比如我们使用 SendBuf[5200]数组来做存储器,那么我们在 DMA_CMARx 中写入&SendBuff 就可以了。

1.3库函数介绍

我们要用到串口 1 的发送,属于 DMA1 的通道 4。

1)使能 DMA 时钟 

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 时钟

2)初始化 DMA 通道 4 参数

DMA 通道配置参数种类比较繁多,包括内存地址,外设地址,传输数据长度, 数据宽度,通道优先级等等。这些参数的配置在库函数中都是在函数 DMA_Init 中完成,函数定义:

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)

函数的第一个参数是指定初始化的 DMA 通道号。第二个参数跟其他外设一样,同样是通过初始化结构体成员变量值来达到初始化的目的,我们来看看 DMA_InitTypeDef 结构体的定义:

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 传输的外设基地址,比如要进行串口 DMA 传输,那么外设基地址为串口接受发送数据存储器 USART1->DR 的地址,表示方法为 &USART1->DR。
  • 第二个参数DMA_MemoryBaseAddr为内存基地址,也就是我们存放DMA传输数据的内存地址。
  • 第三个参数 DMA_DIR 设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数 据发送到外设,也就是外设是源地还是目的地,这里我们设置为从内存读取数据发送到串口, 所以外设自然就是目的地了,所以选择值为 DMA_DIR_PeripheralDST。
  • 第四个参数 DMA_BufferSize 设置一次传输数据量的大小,这个很容易理解。
  • 第五个参数 DMA_PeripheralInc 设置传输数据的时候外设地址是不变还是递增。如果设置 为递增,那么下一次传输的时候地址加 1,这里因为我们是一直往固定外设地址&USART1->DR 发送数据,所以地址不递增,值为 DMA_PeripheralInc_Disable;
  • 第六个参 数 DMA_MemoryInc 设置传输数据时候内存地址是否递增。这个参数 和 DMA_PeripheralInc 意思接近,只不过针对的是内存。这里我们的场景是将内存中连续存储单 元的数据发送到串口,毫无疑问内存地址是需要递增的,所以值为 DMA_MemoryInc_Enable。
  • 第七个参数 DMA_PeripheralDataSize 用来设置外设的数据长度是为字节传输(8bits),半 字传输 (16bits) 还 是 字 传 输 (32bits) , 这 里 我 们 是 8 位 字 节 传 输 , 所 以 值 设 置 为 DMA_PeripheralDataSize_Byte。
  • 第八个参数 DMA_MemoryDataSize 是用来设置内存的数据长度,和第七个参数意思接近,这 里我们同样设置为字节传输 DMA_MemoryDataSize_Byte
  • 第九个参数 DMA_Mode 用来设置 DMA 模式是否循环采集,也就是说,比如我们要从内存中采 集 64 个字节发送到串口,如果设置为重复采集,那么它会在 64 个字节采集完成之后继续从内 存的第一个地址采集,如此循环。这里我们设置为一次连续采集完成之后不循环。所以设置值 为 DMA_Mode_Normal。在我们下面的实验中,如果设置此参数为循环采集,那么你会看到串口 不停的打印数据,不会中断,大家在实验中可以修改这个参数测试一下。
  • 第十个参数是设置 DMA 通道的优先级,有低,中,高,超高三种模式,这个在前面讲解过, 这里我们设置优先级别为中级,所以值为 DMA_Priority_Medium。如果要开启多个通道,那么 这个值就非常有意义。
  • 第 十 一 个 参 数 DMA_M2M 设 置 是 否 是 存 储 器 到 存 储 器 模 式 传 输 , 这 里 我 们 选 择 DMA_M2M_Disable。

3)使能串口 DMA 发送

进行 DMA 配置之后,我们就要开启串口的 DMA 发送功能,使用的函数是

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); 

如果是要使能串口 DMA 接受,那么第二个参数修改为 USART_DMAReq_Rx 

4)使能 DMA1 通道 4,启动传输。

使能串口 DMA 发送之后,我们接着就要使能 DMA 传输通道:

DMA_Cmd(DMA_CHx, ENABLE);

通过以上 3 步设置,我们就可以启动一次 USART1 的 DMA 传输了。

 5)查询 DMA 传输状态

在 DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的函数是:

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)

比如我们要查询 DMA 通道 4 传输是否完成,方法是: DMA_GetFlagStatus(DMA2_FLAG_TC4);

获取当前剩余数据量大小的函数:

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)

比如我们要获取 DMA 通道 4 还有多少个数据没有传输,方法是: DMA_GetCurrDataCounter(DMA1_Channel4);

2.硬件设计

硬件资源有: 1) 指示灯 DS0 2) KEY0 按键 3) 串口 4) TFTLCD 模块 5) DMA

利用外部按键 KEY0 来控制 DMA 的传送,每按一次 KEY0,DMA 就传送一次数据到 USART1,然后在 TFTLCD 模块上显示进度等信息。DS0 还是用来做为程序运行的指示灯。

3.软件设计

dma 相关的库函数文件 stm32f10x_dma.c 和 stm32f10x_dma.h。

#include "dma.h"
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN;//保存 DMA 每次数据传送的长度 
//DMA1 的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8 位数据宽度/存储器增量模式
//DMA_CHx:DMA 通道 CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 时钟
DMA_DeInit(DMA_CHx); //将 DMA 的通道 1 寄存器重设为缺省值
DMA1_MEM_LEN=cndtr;
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA 外设 ADC 基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA 内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向内存到外设
DMA_InitStructure.DMA_BufferSize = cndtr; //DMA 通道的 DMA 缓存的大小
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; //DM 通道拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //初始化 DMA 的通道
} 
//开启一次 DMA 传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
DMA_Cmd(DMA_CHx, DISABLE ); //关闭 USART1 TX DMA1 所指示的通道 
DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN);//设置 DMA 缓存的大小
DMA_Cmd(DMA_CHx, ENABLE); //使能 USART1 TX DMA1 所指示的通道
} 

该部分代码仅仅 2 个函数,MYDMA_Config 函数,基本上就是按照步骤来初始化 DMA 的,该函数在外部只能修改通道、源地址、目标地址和传输数据量等几个参数,更多 的其他设置只能在该函数内部修改。MYDMA_Enable 函数就是设置 DMA 缓存大小并且使能 DMA 通道。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值