OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 68|回复: 0

《STM32H562开发指南 V1.0 》第三十一章 DAC实验

[复制链接]

1404

主题

1420

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5988
金钱
5988
注册时间
2019-5-8
在线时间
1689 小时
发表于 昨天 09:45 | 显示全部楼层 |阅读模式
第三十一章 DAC实验

1)实验平台:正点原子Mini STM32H562开发板

2)章节摘自【正点原子】STM32H562开发指南 V1.0

3)购买链接: https://detail.tmall.com/item.htm?id=753606659612

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h562_mini.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:414052738


2.jpg

3.png

本章,我们将介绍STM32H562的DAC(Digital -to- analog converters,数模转换器)功能。我们通过三个实验来学习DAC,分别是DAC输出实验、DAC输出三角波实验和DAC输出正弦波实验。
本章分为如下几个小节:
31.1 DAC简介
31.2 DAC输出实验
31.3 DAC输出三角波实验
31.4 DAC输出正弦波实验


31.1 DAC简介
STM32H562的DAC模块(数字/模拟转换模块)是12位数字输入,电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,每个通道都有单独的转换器。在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压Vref+(通ADC共用)以获得更精确的转换结果。
STM32H562的DAC模块主要特点有:
①一个DAC有2个转换器:每个转换器对应1个输出通道
②DAC输出通道缓冲/非缓冲模式
③ 12位模式下数据左对齐或者右对齐
④ 同步更新功能
⑤ 噪声波形生成
⑥ 三角波形生成
⑦ 双DAC双通道同时或者分别转换
⑧ 每个通道都有DMA功能
DAC通道框图如图31.1.1所示:


第三十一章 DAC实验590.png
图31.1.1 DAC通道框图

图中VDDA和VSSA为DAC模块模拟部分的供电,而VREF+则是DAC模块的参考电压。DAC_OUT1/2就是DAC的两个输出通道了(对应PA4或者PA5引脚)。ADC的这些输入/输出引脚信息如下表所示:

1.png
表31.1.1 DAC输入/输出引脚

除了上表列出的输入/输出引脚,DAC通道框图还有一些内部输入/输出信号,具体如下表所示:

2.png
表31.1.2 DAC内部输入/输出信号

注意:表中的dac_ker_ck即DAC的内核时钟,DA转换和寄存器访问都是靠这个时钟,该时钟来自AHB2,通过sys_stm32_clock_init函数配置之后,为250MHz。
从图31.1.1可以看出,DAC输出是受DORx(x=1或2,下同)寄存器直接控制的,但是我们不能直接往DORx寄存器写入数据,而是通过DHRx间接的传给DORx寄存器,从而实现对DAC输出的控制。
前面我们提到,STM32H562的DAC支持8/12位模式,8位模式的时候是固定的右对齐的,而12位模式又可以设置左对齐/右对齐。DAC单通道模式下的数据寄存器对齐方式,总共有3种情况,如下图所示:


第三十一章 DAC实验1481.png
图31.1.2 DAC单通道模式下的数据寄存器对齐方式

①8位数据右对齐:用户将数据写入DAC_DHR8Rx[7:0]位(实际存入DHRx[11:4]位)。
②12位数据左对齐:用户将数据写入DAC_DHR12Lx[15:4]位(实际存入DHRx[11:0]位)。
③12位数据右对齐:用户将数据写入DAC_DHR12Rx[11:0]位(实际存入DHRx[11:0]位)。
我们本章实验中使用的都是单通道模式下的DAC通道1,采用12位右对齐格式,所以采用第③种情况。另外DAC还具有双通道转换功能。
对于 DAC 双通道(可用时),也有三种可能的方式,如下图所示:


第三十一章 DAC实验1764.png
图31.1.3 DAC双通道模式下的数据寄存器对齐方式

①8位数据右对齐:用户将DAC通道1的数据写入DAC_DHR8RD[7:0]位(实际存入DHR1 [11:4]位),将DAC通道2的数据写入DAC_DHR8RD[15:8]位(实际存入DHR2 [11:4]位)。
②12位数据左对齐:用户将DAC通道1的数据写入DAC_DHR12LD [15:4]位(实际存入DHR1[11:0]位),将DAC通道2的数据写入DAC_DHR12LD [31:20]位(实际存入DHR2[11:0]位)。
③12位数据右对齐:用户将DAC通道1的数据写入DAC_DHR12RD [11:0]位(实际存入DHR1[11:0]位),将DAC通道2的数据写入DAC_DHR12RD [27:16]位(实际存入DHR2[11:0]位)。
DAC可以通过软件或者硬件触发转换,通过配置TENx 控制位来决定。
如果没有选中硬件触发(寄存器DAC_CR1的TENx位置0),存入寄存器DAC_DHRx的数据会在1个APB1时钟周期后自动传至寄存器DAC_DORx。如果选中硬件触发(寄存器DAC_CR1的TENx位置1),数据传输在触发发生以后3个AHB2时钟周期后完成。 一旦数据从DAC_DHRx寄存器装入DAC_DORx寄存器,在经过时间tSETTLING之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从《STM32H562VIT6.pdf》数据手册查到tSETTLING的典型值为1.7us,最大是2us,所以DAC的转换速度最快是588K左右。
不使用硬件触发(TEN=0),其转换的时间框图如图31.1.4所示:


第三十一章 DAC实验2482.png
图31.1.4 TEN=0时DAC模块转换时间框图

当DAC的参考电压为VREF+的时候,DAC的输出电压是线性的从0~VREF+,12位模式下DAC输出电压与VREF +以及DORx的计算公式如下:
DACx输出电压 = VREF *(DORx/4096)
如果使用硬件触发(TEN=1),可通过外部事件(定时计数器、外部中断线)触发DAC转换。由TSELx[3:0]控制位来决定选择16个触发事件中的一个来触发转换。这16个触发事件(触发事件14和15保留,所以没有列出来)如下表所示:


第三十一章 DAC实验2730.png
表31.1.3 DAC触发选择

原表见《STM32H5xx参考手册_V1(英文版).pdf》第1156页表 266。
每个DAC通道都有DMA功能,两个DMA通道分别用于处理两个DAC通道的DMA 请求。如果DMAENx 位置1时,如果发生外部触发(而不是软件触发),就会产生一个DMA 请求,然后DAC_DHRx寄存器的数据被转移到DAC_NORx寄存器。


31.2 DAC输出实验
本实验我们来学习DAC输出实验。

31.2.1 DAC寄存器
下面,我们介绍要实现DAC的通道1输出,需要用到的一些DAC寄存器。
DAC控制寄存器(DAC_CR)
DAC控制寄存器描述如图31.2.1.1所示:


第三十一章 DAC实验3033.png
图31.2.1.1 DAC_CR寄存器

DAC_CR寄存器的低16位用于控制通道1,高16位用于控制通道2,下面介绍本实验需要设置的一些位:
EN1位用于使能/禁止DAC通道1,本实验用到ADC1通道1,所以该位EN1置1。
TEN1位用于DAC通道1的触发使能,本实验不使用硬件触发,所以该位置0。写入DHR1的值会在1个APB1周期后传送到DOR1,然后输出到PA4口上。
TSEL[3:0]位用于选择DAC通道1的触发方式,本实验使用软件触发,所以该位域置0。具体的设置关系详见表31.1.3 DAC触发选择。
WAVE[1:0]位用于控制DAC通道1的噪声/波形输出功能,默认设置为00,不使能噪声/波形输出。
DMAEN1位用于控制DAC通道1的DMA使能,本实验不使能,设置该位为0即可。
CEN1位用于控制DAC通道1的输出缓冲校准使能,本实验不使用校准功能(默认有一个出厂校准值,我们使用默认的校准值即可),设置该位为0即可。
DAC模式控制寄存器(DAC_ MCR)
DAC模式控制寄存器描述如图31.2.1.2所示:


第三十一章 DAC实验3510.png
图31.2.1.2 DAC_ MCR寄存器

该寄存器我们只关心MODE1[2:0],这三个位用于设置DAC通道1的工作模式,本实验使用普通模式,且使用输出缓冲,设置MODE1[2:0]=0即可。MODE2[2:0] 设置通道2的工作模式,本实验没用到。
DAC通道1 12位右对齐数据保持寄存器(DAC_ DHR12R1)
DACx通道1 12位右对齐数据保持寄存器描述如图31.2.1.3所示:


第三十一章 DAC实验3711.png
图31.2.1.3 DAC_ DHR12R1寄存器

该寄存器用来设置DAC输出,通过写入12位数据到该寄存器,就可以在DAC输出通道1(PA4)得到我们所要的结果。DACC1DHRB[11:0]只在双数据模式才用到。

31.2.2 硬件设计
1. 例程功能
1、本实验需要跳线帽短连接(P5)的ADC和DAC排针,再使用KEY1/KEY_UP两个按键,控制STM32内部DAC的通道1输出电压,通过ADC1的通道19采集DAC输出的电压,在LCD模块上面显示ADC采集到的电压值以及DAC的设定输出电压值等信息。也可以通过usmart调用dac_set_voltage函数,来直接设置DAC输出电压。
2、LED0闪烁指示系统正在运行。

2. 硬件资源
1)LED灯
       LED0 - PB4
2)串口1(PA9/PA10连接在板载USB转串口芯片CH310上面)
3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)独立按键 :KEY1 - PA15、WK_UP - PA0
5)ADC1 :通道19 - PA5
6)DAC1 :通道1 - PA4

3. 原理图
我们来看看原理图上ADC1通道19(PA5)和DAC1通道1(PA4)引出来的引脚,如下图所示:


第三十一章 DAC实验4274.png
图31.2.2.1 ADC和DAC在开发板上的连接关系原理图

P5是多功能端口,我们只需要通过跳线帽连接P5的ADC和DAC,就可以使得ADC1通道19(PA5)和DAC1通道1(PA4)连接起来。对应的硬件连接如图31.2.2.2所示:

第三十一章 DAC实验4398.png
图31.2.2.2 硬件连接示意图

31.2.3 程序设计

31.2.3.1 DAC的HAL库驱动
DAC在HAL库中的驱动代码在stm32h5xx_hal_dac.c和stm32h5xx_hal_dac_ex.c文件(及其头文件)中。

1. HAL_DAC_Init函数
DAC的初始化函数,其声明如下:

  1. HAL_StatusTypeDef HAL_DAC_Init(DAC_HandleTypeDef *hdac);
复制代码
函数描述:
用于初始化DAC。
函数形参:
形参1是DAC_HandleTypeDef结构体类型指针变量,其定义如下:

  1. typedef struct
  2. {
  3.   DAC_TypeDef                    *Instance;              /* DAC寄存器基地址 */
  4.   __IO HAL_DAC_StateTypeDef   State;                   /* DAC 工作状态 */
  5.   HAL_LockTypeDef               Lock;                    /* DAC锁定对象 */
  6.   DMA_HandleTypeDef             *DMA_Handle1;          /* 通道1的DMA处理句柄指针 */
  7.   DMA_HandleTypeDef             *DMA_Handle2;          /* 通道2的DMA处理句柄指针 */
  8.   __IO uint32_t                  ErrorCode;             /* DAC错误代码 */
  9. } DAC_HandleTypeDef;
复制代码
从该结构体看到该函数并没有设置任何DAC相关寄存器,即没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC,为后面HAL库操作DAC做好准备。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
注意事项:
DAC的MSP初始化函数HAL_DAC_MspInit,该函数声明如下:

  1. void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac);
复制代码

2. HAL_DAC_ConfigChannel函数
DAC 的通道参数初始化函数,其声明如下:

  1. HAL_StatusTypeDef HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac,
  2. DAC_ChannelConfTypeDef *sConfig, uint32_t Channel);
复制代码
函数描述:
该函数用来配置DAC通道的触发类型以及输出缓冲。
函数形参:
形参1是DAC_HandleTypeDef结构体类型指针变量。
形参2是DAC_ChannelConfTypeDef结构体类型指针变量,其定义如下:

  1. typedef struct
  2. {
  3.   uint32_t DAC_HighFrequency;                    /* 频率接口模式 */
  4.   FunctionalState DAC_DMADoubleDataMode;        /* 指定所选通道是否启用DMA双数据模式 */
  5.   FunctionalState DAC_SignedFormat;           /* 指定所选通道是否应使用符号格式 */
  6.   uint32_t DAC_SampleAndHold;                  /* 设置是否使能低功耗模式,即采样和保持模式 */
  7.   uint32_t DAC_Trigger;                         /* 指定所选DAC通道的外部触发器 */
  8.   uint32_t DAC_OutputBuffer;                     /* 指定DAC通道输出缓冲区是启用还是禁用 */
  9.   uint32_t DAC_ConnectOnChipPeripheral ;         /* 指定DAC输出是否连接到芯片外设 */
  10.   uint32_t DAC_UserTrimming;         /* 指定DAC的校准方式,采用出厂模式还是用户模式 */
  11.   uint32_t DAC_TrimmingValue;               /* 设置用户校准模式的偏移值 */
  12.   DAC_SampleAndHoldConfTypeDef  DAC_SampleAndHoldConfig; /* 设置采样保持具体参数 */
  13. } DAC_ChannelConfTypeDef;
复制代码
形参3用于选择要配置的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
函数返回值:
HAL_StatusTypeDef枚举类型的值。

3. HAL_DAC_Start函数
使能启动DAC转换通道函数,其声明如下:

  1. HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel);
复制代码
函数描述:
使能启动DAC转换通道。
函数形参:
形参1是DAC_HandleTypeDef结构体类型指针变量。
形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
函数返回值:
HAL_StatusTypeDef枚举类型的值。

4. HAL_DAC_SetValue函数
DAC的通道输出值函数,其声明如下:

  1. HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel,
  2. uint32_t Alignment, uint32_t Data);
复制代码
函数描述:
配置DAC的通道输出值。
函数形参:
形参1是DAC_HandleTypeDef结构体类型指针变量。
形参2用于选择要输出的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
形参3用于指定数据对齐方式。
形参4设置要加载到选定数据保存寄存器中的数据。
函数返回值:
HAL_StatusTypeDef枚举类型的值。

5. HAL_DAC_GetValue函数
DAC读取通道输出值函数,其声明如下:

  1. uint32_t HAL_DAC_GetValue(DAC_HandleTypeDef *hdac, uint32_t Channel);
复制代码
函数描述:
获取所选DAC通道的最后一个数据输出值。
函数形参:
形参1是DAC_HandleTypeDef结构体类型指针变量。
形参2用于选择要读取的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
函数返回值:
获取到的输出值。
DAC输出配置步骤
1)开启DACx和DAC通道对应的IO时钟,并配置该IO为模拟功能
首先开启DACx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下:

  1. __HAL_RCC_DAC1_CLK_ENABLE ();            /* 使能DAC1时钟 */
  2. __HAL_RCC_GPIOA_CLK_ENABLE();                   /* 开启GPIOA时钟 */
复制代码
2)初始化DACx
通过HAL_DAC_Init函数来设置需要初始化的DAC。该函数并没有设置任何DAC相关寄存器,也就是说没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC。
注意:该函数会调用HAL_DAC_MspInit函数来存放DAC和对应通道的IO时钟使能和初始化IO等代码。
3)配置DAC通道并启动DA转换器
在HAL库中,通过HAL_DAC_ConfigChannel函数来设置配置DAC的通道,根据需求设置触发类型以及输出缓冲。
配置好DAC通道之后,通过HAL_DAC_Start函数启动DA转换器。
4)设置DAC的输出值
通过HAL_DAC_SetValue函数设置DAC的输出值。


31.2.3.2 程序流程图

第三十一章 DAC实验7751.png
图31.2.3.2.1 DAC输出实验程序流程图

31.2.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。
dac.h文件只有一些声明,下面直接开始介绍dac.c的程序,首先是DAC初始化函数。

  1. /**
  2. * @brief   DAC初始化函数
  3. *   [url=home.php?mod=space&uid=60778]@note[/url]  本函数支持DAC1_OUT1/2通道初始化
  4. *           DAC的输入时钟来自AHB2, 时钟频率=250Mhz=4ns
  5. *       DAC在输出buffer关闭的时候, 输出建立时间: tSETTLING = 2us(H563数据手册有写)
  6. *       因此DAC输出的最高速度约为:500Khz, 以10个点为一个周期, 最大能输出50Khz左右的波形
  7. *
  8. * [url=home.php?mod=space&uid=271674]@param[/url]   outx: 要初始化的通道. 1,通道1; 2,通道2
  9. * @retval  无
  10. */
  11. void dac_init(uint8_t outx)
  12. {
  13.     DAC_ChannelConfTypeDef dac_ch_conf = {0};
  14.     GPIO_InitTypeDef gpio_init_struct = {0};
  15.     RCC_PeriphCLKInitTypeDef dac_clk_init = {0};

  16.     __HAL_RCC_DAC1_CLK_ENABLE(); /* 使能DAC12时钟,本芯片只有DAC1 */
  17.     __HAL_RCC_GPIOA_CLK_ENABLE();/* 使能DAC OUT1/2的IO口时钟(都在PA口,PA4/PA5) */

  18.     dac_clk_init.PeriphClockSelection = RCC_PERIPHCLK_DAC;
  19.     dac_clk_init.AdcDacClockSelection = RCC_ADCDACCLKSOURCE_HCLK;
  20. HAL_RCCEx_PeriphCLKConfig(&dac_clk_init);

  21. /* STM32单片机, 总是PA4=DAC1_OUT1, PA5=DAC1_OUT2 */
  22.     gpio_init_struct.Pin = (outx == 1)? GPIO_PIN_4 : GPIO_PIN_5;               
  23.     gpio_init_struct.Mode = GPIO_MODE_ANALOG;                   /* 模拟 */
  24.     HAL_GPIO_Init(GPIOA, &gpio_init_struct);

  25.     g_dac_handle.Instance = DAC1;                                /* DAC1寄存器基地址 */
  26.     HAL_DAC_Init(&g_dac_handle);                                 /* 初始化DAC */

  27.     dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;
  28.     dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
  29.     dac_ch_conf.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_EXTERNAL;

  30.     switch(outx)
  31.     {
  32.         case 1 :
  33.             /* 配置DAC通道1 */
  34.             HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);
  35.             HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_1);   /* 开启DAC通道1 */
  36.             break;
  37.         case 2 :
  38.             /* 配置DAC通道2 */
  39.             HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_2);
  40.             HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_2);    /* 开启DAC通道2 */
  41.             break;
  42.         default : break;
  43.     }
  44. }
复制代码
该函数主要调用HAL_DAC_Init和HAL_DAC_ConfigChannel函数初始化DAC,并调用HAL_DAC_Start函数使能DAC通道。HAL_DAC_Init函数会调用HAL_DAC_MspInit回调函数,该函数用于存放DAC和对应通道的IO时钟使能和初始化IO等代码。本实验为了让dac_init函数支持DAC的OUT1/2两个通道的初始化,就没有用到该函数。
下面是设置DAC通道1/2输出电压函数,其定义如下:

  1. /**
  2. * @brief       设置通道1/2输出电压
  3. * @param       outx: 1,通道1; 2,通道2
  4. * @param       vol : 0~3300,代表0~3.3V
  5. * @retval      无
  6. */
  7. void dac_set_voltage(uint8_t outx, uint16_t vol)
  8. {
  9.     double temp = vol;
  10.     temp /= 1000;
  11.     temp = temp * 4096 / 3.3;
  12.     if (temp >= 4096)temp = 4095;   /* 如果值大于等于4096, 则取4095 */
  13.     if (outx == 1)   /* 通道1 */
  14. {
  15. /* 12位右对齐数据格式设置DAC值 */
  16.         HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp);
  17.     }
  18.     else             /* 通道2 */
  19. {
  20. /* 12位右对齐数据格式设置DAC值 */
  21.         HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_2, DAC_ALIGN_12B_R, temp);
  22.     }
  23. }
复制代码
该函数实际就是将电压值转换为DAC输入值,形参1用于设置通道,形参2设置要输出的电压值,设置的范围:0~3300,代表0~3.3V。
最后在main函数里面编写如下代码:

  1. int main(void)
  2. {
  3.     uint32_t adcx;
  4.     float temp;
  5.     uint8_t t = 0;
  6.     uint16_t dacval = 0;
  7.     uint8_t key;

  8.     HAL_Init();                                       /* HAL库初始化 */
  9.     HAL_ICACHE_Enable();                             /* 使能指令缓存 */
  10.     sys_stm32_clock_init(250, 4, 2, 2);            /* 系统时钟初始化, 250Mhz */
  11.     delay_init(250);                                   /* 延时初始化 */
  12.     usart_init(115200);                               /* 串口初始化 */
  13.     usmart_dev.init(250);                              /* USMART初始化 */
  14.     led_init();                                        /* LED初始化 */
  15.     key_init();                                        /* 按键初始化 */
  16.     lcd_init();                                         /* LCD初始化 */
  17.     adc_init();                                       /* ADC初始化 */
  18.     dac_init(1);                                       /* 初始化DAC1_OUT1通道 */

  19.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  20.     lcd_show_string(30, 70, 200, 16, 16, "DAC TEST", RED);
  21.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  22.     lcd_show_string(30, 110, 200, 16, 16, "WK_UP:+  KEY1:-", RED);

  23.     lcd_show_string(30, 150, 200, 16, 16, "DAC VAL:", BLUE);
  24.     lcd_show_string(30, 170, 200, 16, 16, "DAC VOL:0.000V", BLUE);
  25.     lcd_show_string(30, 190, 200, 16, 16, "ADC VOL:0.000V", BLUE);
  26. /* 初始值为0 */
  27.     HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 0);
  28.     while (1)
  29.     {
  30.         t++;
  31.         key = key_scan(0);          /* 按键扫描 */
  32.         if (key == WKUP_PRES)
  33.         {
  34.             if (dacval < 4000)dacval += 200;
  35.             HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R,
  36. dacval);/* 输出增大200 */
  37.         }
  38.         else if (key == KEY1_PRES)
  39.         {
  40.             if (dacval > 200)dacval -= 200;
  41.             else dacval = 0;
  42.             HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R,
  43. dacval);/* 输出减少200 */
  44.         }
  45. /* WKUP/KEY1按下了,或者定时时间到了 */
  46.         if (t == 10 || key == KEY1_PRES || key == WKUP_PRES)   
  47.         {
  48. /* 读取前面设置DAC1_OUT1的值 */
  49.             adcx = HAL_DAC_GetValue(&g_dac_handle, DAC_CHANNEL_1);
  50.             lcd_show_xnum(94, 150, adcx, 4, 16, 0, BLUE);           /* 显示DAC寄存器值 */
  51.             temp = (float)adcx * (3.3 / 4096);                        /* 得到DAC电压值 */
  52.             adcx = temp;
  53.             lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE);          /* 显示电压值整数部分 */
  54.             temp -= adcx;
  55.             temp *= 1000;
  56.             lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE);/*显示电压值的小数部分*/
  57.             /* 得到ADC通道19的转换结果 */
  58.             adcx=adc_get_result_average(ADC_ADCX_CHY, 10);
  59.             temp = (float)adcx * (3.3 / 65536); /* 得到ADC电压值(adc是16bit的) */
  60.             adcx = temp;
  61.             lcd_show_xnum(94, 190, temp, 1, 16, 0, BLUE); /* 显示电压值整数部分 */
  62.             temp -= adcx;
  63.             temp *= 1000;
  64.             lcd_show_xnum(110, 190, temp, 3, 16, 0X80, BLUE);/*显示电压值的小数部分*/
  65.             LED0_TOGGLE();  /* LED0闪烁 */
  66.             t = 0;
  67.         }
  68.         delay_ms(10);
  69.     }
  70. }
复制代码
此部分代码,我们通过KEY_UP(WKUP按键)和KEY1(也就是上下键)来实现对DAC输出的幅值控制。按下KEY_UP增加,按KEY1减小。同时在LCD上面显示DHR12R1寄存器的值、DAC设置输出电压以及ADC采集到的DAC输出电压。

31.2.4 下载验证
下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如下图所示:

第三十一章 DAC实验13691.png
图31.2.4.1 DAC输出实验测试图

验证试验前记得先通过跳线帽连接P5的ADC和DAC,然后我们可以通过按WK_UP按键,增加DAC输出的电压,这时ADC采集到的电压也会增大,通过按KEY1减小DAC输出的电压,这时ADC采集到的电压也会减小。
除此之外,我们还可以通过usmart调用dac_set_voltage函数,来直接设置DAC输出电压。


31.3 DAC输出三角波实验
本实验我们来学习使用如何让DAC输出三角波,DAC初始化部分还是用DAC输出实验的,所以做本实验的前提是先学习DAC输出实验。

31.3.1 DAC寄存器
本实验用到的寄存器在DAC输出实验都有介绍。

31.3.2 硬件设计

1. 例程功能
使用DAC输出三角波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种三角波,需要通过示波器接PA4进行观察。也可以通过usmart调用dac_triangular_wave函数,来控制输出哪种三角波。LED0闪烁,提示程序运行。

2. 硬件资源
1)LED灯
       LED0 - PB4
2)串口1(PA9/PA10连接在板载USB转串口芯片CH310上面)
3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)独立按键 :KEY1 - PA15、WK_UP - PA0
5)DAC1 :通道1 - PA4

3. 原理图
我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4在P5多功能端口的DAC标志排针已经引出,硬件连接如图31.3.2.1所示:


第三十一章 DAC实验14398.png
图31.3.2.1 硬件连接示意图

31.3.3 程序设计
本实验用到的DAC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍DAC输出三角波的配置步骤。
DAC输出三角波配置步骤
1)开启DACx和DAC通道对应的IO时钟,并配置该IO为模拟功能
首先开启DACx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下:

  1. __HAL_RCC_DAC1_CLK_ENABLE ();            /* 使能DAC1时钟 */
  2. __HAL_RCC_GPIOA_CLK_ENABLE();                   /* 开启GPIOA时钟 */
复制代码
2)初始化DACx
通过HAL_DAC_Init函数来设置需要初始化的DAC。该函数并没有设置任何DAC相关寄存器,也就是说没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC。
注意:该函数会调用HAL_DAC_MspInit函数来存放DAC和对应通道的IO时钟使能和初始化IO等代码。
3)配置DAC通道并启动DA转换器
在HAL库中,通过HAL_DAC_ConfigChannel函数来设置配置DAC的通道,根据需求设置触发类型以及输出缓冲。
配置好DAC通道之后,通过HAL_DAC_Start函数启动DA转换器。
4)设置DAC的输出值
通过HAL_DAC_SetValue函数设置DAC的输出值。这里我们根据三角波的特性,创建了dac_triangular_wave函数用于控制输出三角波。


31.3.3.1 程序流程图

第三十一章 DAC实验15120.png
图31.3.3.1.1 DAC输出三角波实验程序流程图

31.3.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。
dac.h文件只有一些声明,下面直接开始介绍dac.c的程序,本实验的DAC初始化我们还是用到dac_init函数,就添加了一个设置DAC_OUT1输出三角波函数,其定义如下:

  1. /**
  2. * @brief       设置DAC_OUT1输出三角波
  3. *   @note      输出频率 ≈ 1000 / (dt * samples) Khz, 不过在dt较小的时候,
  4. *              比如小于5us时, 由于delay_us本身就不准了(调用函数,计算等都需要时间,
  5. *              延时很小的时候,这些时间会影响到延时), 频率会偏小
  6. *
  7. * @param     maxval : 最大值(0 < maxval < 4096), (maxval + 1)必须大于等于samples/2
  8. * @param     dt     : 每个采样点的延时时间(单位: us)
  9. * @param     samples:采样点的个数,samples必须小于等于(maxval+1)*2,且maxval不能等于0
  10. * @param     n      : 输出波形个数,0~65535
  11. *
  12. * @retval    无
  13. */
  14. void dac_triangular_wave(uint16_t maxval, uint16_t dt,
  15. uint16_t samples, uint16_t n)
  16. {
  17.     uint16_t i, j;
  18.     float incval;                                           /* 递增量 */
  19.     float Curval;                                           /* 当前值 */

  20.     if((maxval + 1) <= samples)return ;             /* 数据不合法 */

  21.     incval = (maxval + 1) / (samples / 2);         /* 计算递增量 */

  22.     for(j = 0; j < n; j++)
  23.     {
  24.         Curval = 0;
  25. /* 先输出0 */
  26.         HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);
  27.         for(i = 0; i < (samples / 2); i++)  /* 输出上升沿 */
  28.         {
  29.             Curval  +=  incval;                     /* 新的输出值 */
  30.             HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R,
  31. Curval);        /* 用寄存器操作波形会更稳定 */
  32.             delay_us(dt);
  33.         }
  34.         for(i = 0; i < (samples / 2); i++)   /* 输出下降沿 */
  35.         {
  36.             Curval  -=  incval;                  /* 新的输出值 */
  37.             HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R,
  38. Curval);        /* 用寄存器操作波形会更稳定 */
  39.             delay_us(dt);
  40.         }
  41.     }
  42. }
复制代码
该函数用于设置DAC通道1输出三角波,输出频率 ≈ 1000 / (dt * samples) KHz,形参意义在源码已经有详细注释。该函数中,我们使用HAL_DAC_SetValue函数来设置DAC的输出值,这样得到的三角波在示波器上可以看到。如果有跳动现象(不平稳),是正常的,因为调用函数,计算等都需要时间,这样就会导致输出的波形是不太稳定的。越高性能的MCU,得到的波形会越稳定。而且用HAL库函数操作效率没有直接操作寄存器高,所以可以像寄存器版本实验一样,直接操作DHR12R1寄存器,得到的波形会相对稳定些。
由于使用HAL库的函数,CPU花费的时间会更长(因为指令变多了),在时间精度要求比较高的应用,就不适合用HAL库函数来操作了,这一点希望大家明白。所以学STM32不是说只要会HAL库就可以了,对寄存器也是需要有一定的理解,最好是熟悉。这里用HAL库操作只是为了演示怎么使用HAL库的相关函数。
最后在main.c里面编写如下代码:

  1. int main(void)
  2. {
  3.     uint8_t t = 0;
  4.     uint8_t key;

  5.     HAL_Init();                                           /* HAL库初始化 */
  6.     HAL_ICACHE_Enable();                                   /* 使能指令缓存 */
  7.     sys_stm32_clock_init(250, 4, 2, 2);                 /* 系统时钟初始化, 250Mhz */
  8.     delay_init(250);                                      /* 延时初始化 */
  9.     usart_init(115200);                                   /* 串口初始化 */
  10.     usmart_dev.init(250);                                  /* USMART初始化 */
  11.     led_init();                                             /* LED初始化 */
  12.     key_init();                                              /* 按键初始化 */
  13.     lcd_init();                                                 /* LCD初始化 */
  14.     dac_init(1);                                           /* 初始化DAC1_OUT1通道 */

  15.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  16.     lcd_show_string(30, 70, 200, 16, 16, "DAC Triangular WAVE TEST", RED);
  17.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  18.     lcd_show_string(30, 110, 200, 16, 16, "KEY0:Wave1  KEY1:Wave2", RED);

  19.     lcd_show_string(30, 130, 200, 16, 16, "DAC None", BLUE);  /* 提示无输出 */

  20.     while (1)
  21.     {
  22.         t++;
  23.         key = key_scan(0);           /* 按键扫描 */
  24.         if (key == KEY0_PRES)       /* 高采样率 , 约0.1Khz波形 */
  25.         {
  26.             lcd_show_string(30, 130, 200, 16, 16, "DAC Wave1 ", BLUE);
  27. /* 幅值4095, 采样点间隔5us, 2000个采样点, 100个波形 */
  28.             dac_triangular_wave(4095, 5, 2000, 100);   
  29.             lcd_show_string(30, 130, 200, 16, 16, "DAC None  ", BLUE);
  30.         }
  31.         else if (key == KEY1_PRES)  /* 低采样率 , 约0.1Khz波形 */
  32.         {
  33.             lcd_show_string(30, 130, 200, 16, 16, "DAC Wave2 ", BLUE);
  34. /* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */
  35.             dac_triangular_wave(4095, 500, 20, 100);   
  36.             lcd_show_string(30, 130, 200, 16, 16, "DAC None  ", BLUE);
  37.         }
  38.         if (t == 10 )       /* 定时时间到了 */
  39.         {
  40.             LED0_TOGGLE();  /* LED0闪烁 */
  41.             t = 0;
  42.         }
  43.         delay_ms(10);
  44.     }
  45. }
复制代码
该部分代码功能是,按下KEY0后,DAC输出三角波1,按下KEY1后,DAC输出三角波2,将dac_triangular_wave的形参代入公式:输出频率 ≈ 1000 / (dt * samples) KHz,得到三角波1和三角波2的频率都是0.1KHz。

31.3.4 下载验证
下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如图31.3.4.1所示:

第三十一章 DAC实验19356.png
图31.3.4.1 DAC输出三角波实验测试图

没有按下任何按键之前,LCD屏显示DAC None,当按下KEY0后,DAC输出三角波1,LCD屏显示DAC Wave1 ,三角波1输出完成后LCD屏继续显示DAC None,当按下KEY1后,DAC输出三角波2,LCD屏显示DAC Wave2,三角波2输出完成后LCD屏继续显示DAC None。
其中三角波1和三角波2在示波器的显示情况如下图所示:


第三十一章 DAC实验19559.png
图31.3.4.2 DAC输出的三角波1

第三十一章 DAC实验19582.png
图31.3.4.3 DAC输出的三角波2

由上面两副测试图可以知道,三角波1的频率是95.1Hz,三角波2的频率是100Hz,基本都接近我们算出来的结果0.1KHz。三角波1频率的误差较大,在介绍dac_triangular_wave函数时也说了原因,加上三角波1的采样率比较高,所以误差就会比较大。

31.4 DAC输出正弦波实验
本实验我们来学习使用如何让DAC输出正弦波。实验将用定时器7来触发DAC进行转换输出正弦波,以DMA传输数据的方式。

31.4.1 DAC寄存器  
本实验用到的寄存器在前面的实验都有介绍。

31.4.2 硬件设计

1. 例程功能
使用DAC输出正弦波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种正弦波,需要通过示波器接PA4进行观察。TFTLCD显示DAC转换值、电压值和ADC的电压值。LED0闪烁,提示程序运行。

2. 硬件资源
1)LED灯  LED0 - PB4
2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)独立按键 :KEY0 - PA1、KEY1 - PA15
5)ADC1 :通道19 - PA5
6)DAC1 :通道1 - PA4
7)DMA(GPDMA1通道1)
8)定时器7

3. 原理图
我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4在P5多功能端口的DAC标志排针已经引出,硬件连接如图31.4.2.1所示:


第三十一章 DAC实验20276.png
图31.4.2.1 硬件连接示意图

31.4.3 程序设计

31.4.3.1 DAC的HAL库驱动
本实验用到的HAL库API函数前面大都介绍过,下面将介绍本实验用到且没有介绍过的。

1. HAL_DAC_Start_DMA函数
启动DAC使用DMA方式传输函数,其声明如下:

  1. HAL_StatusTypeDef HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel,
  2. uint32_t *pData, uint32_t Length, uint32_t Alignment);
复制代码
函数描述:
用于启动DAC使用DMA的方式。
函数形参:
形参1是DAC_HandleTypeDef结构体类型指针变量。
形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
形参3是使用DAC输出数据缓冲区的指针。
形参4是DAC输出数据的长度。
形参5是指定DAC通道的数据对齐方式,有:DAC_ALIGN_8B_R(8位右对齐)、DAC_ALIGN_12B_L(12位左对齐)和DAC_ALIGN_12B_R(12位右对齐)三种方式。
函数返回值:
HAL_StatusTypeDef枚举类型的值。

2. HAL_DAC_Stop_DMA函数
停止DAC的DMA方式函数,其声明如下:

  1. HAL_StatusTypeDef HAL_DAC_Stop_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel);
复制代码
函数描述:
用于停止DAC的DMA方式。
函数形参:
形参1是DAC_HandleTypeDef结构体类型指针变量。
形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
函数返回值:
HAL_StatusTypeDef枚举类型的值。

3. HAL_TIMEx_MasterConfigSynchronization函数
配置主模式下的定时器触发输出选择函数,其声明如下:

  1. HAL_StatusTypeDef HAL_TIMEx_MasterConfigSynchronization(
  2. TIM_HandleTypeDef *htim, TIM_MasterConfigTypeDef *sMasterConfig);
复制代码
函数描述:
用于配置主模式下的定时器触发输出选择。
函数形参:
形参1是TIM_HandleTypeDef结构体类型指针变量。
形参2是TIM_MasterConfigTypeDef结构体类型指针变量,用于配置定时器工作在主/从模式,以及触发输出(TRGO和TRGO2)的选择。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
DAC输出正弦波配置步骤
1)开启DACx和DAC通道对应的IO时钟,并配置该IO为模拟功能
首先开启DACx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下:

  1. __HAL_RCC_DAC1_CLK_ENABLE ();            /* 使能DAC1时钟 */
  2. __HAL_RCC_GPIOA_CLK_ENABLE();                   /* 开启GPIOA时钟 */
复制代码
2)初始化DACx
通过HAL_DAC_Init函数来设置需要初始化的DAC。该函数并没有设置任何DAC相关寄存器,也就是说没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC。
注意:该函数会调用HAL_DAC_MspInit函数来存放DAC和对应通道的IO时钟使能和初始化IO等代码。
3)配置DAC通道
在HAL库中,通过HAL_DAC_ConfigChannel函数来设置配置DAC的通道,根据需求设置触发类型以及输出缓冲等。
4)以链表的方式初始化DMA
通过调用29.3.3.1小节介绍的API函数初始化并使用DMA,具体请参考源码
5)配置定时器控制触发DAC
通过HAL_TIM_Base_Init函数设置定时器溢出频率。
通过HAL_TIMEx_MasterConfigSynchronization函数配置定时器溢出事件用于触发。
通过HAL_TIM_Base_Start函数启动计数。
6)启动DAC转换并以DMA方式传输数据
通过HAL_DAC_Start_DMA函数启动DMA传输以及DAC输出。

31.4.3.2 程序流程图


第三十一章 DAC实验22193.png
图31.4.3.2.1 DAC输出正弦波实验程序流程图

31.4.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。
dac.h文件只有一些声明,下面直接开始介绍dac.c的程序。首先是dac_dma_wave_init和HAL_DAC_MspInit函数,定义如下:

  1. /**
  2. * @brief     DAC DMA输出正弦波初始化函数
  3. *   @note    DAC的输入时钟来自AHB2, 时钟频率=250Mhz=4ns
  4. *             DAC在输出buffer关闭的时候, 输出建立时间:tSETTLING=2us(H563数据手册有写)
  5. *       因此DAC输出的最高速度约为:500Khz, 以10个点为一个周期, 最大能输出50Khz左右的波形
  6. *
  7. * @retval      无
  8. */
  9. void dac_dma_wave_init(void)
  10. {
  11.     DAC_ChannelConfTypeDef dac_ch_conf = {0};

  12.     g_dac_dma_handle.Instance = DAC1;     /* 选择哪个DAC */
  13.     HAL_DAC_Init(&g_dac_dma_handle);      /* DAC初始化 */
  14.    
  15. /* DAC 通道输出配置 */
  16. /* 禁用高频接口模式 */
  17.     dac_ch_conf.DAC_HighFrequency = DAC_HIGH_FREQUENCY_INTERFACE_MODE_DISABLE;  
  18.     dac_ch_conf.DAC_DMADoubleDataMode = DISABLE;  /* 禁用DMA双数据模式 */
  19. dac_ch_conf.DAC_SignedFormat = DISABLE;        /* 禁用签名格式 */
  20. /* 关闭采样保持模式,这个模式主要用于低功耗 */
  21.     dac_ch_conf.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;   
  22.     dac_ch_conf.DAC_Trigger = DAC_TRIGGER_T7_TRGO;            /* 采用定时器7触发 */
  23. dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; /* 使能输出缓冲 */
  24. /* 指定DAC输出是否连接到芯片外设 */
  25.     dac_ch_conf.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_EXTERNAL;
  26. dac_ch_conf.DAC_UserTrimming = DAC_TRIMMING_FACTORY;     /* 使用出厂校准 */
  27. /* DAC通道输出配置 */
  28.     HAL_DAC_ConfigChannel(&g_dac_dma_handle, &dac_ch_conf, DAC_CHANNEL_1);   
  29. }

  30. /**
  31. * @brief       DAC底层驱动,引脚配置,时钟使能
  32.                  此函数会被HAL_DAC_Init()调用
  33. * @param       hadc:DAC句柄
  34. * @retval      无
  35. */
  36. void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac)
  37. {
  38.     GPIO_InitTypeDef gpio_init_struct = {0}; /* 定义GPIO初始化结构体变量 */
  39.     DMA_NodeConfTypeDef node_conf = {0};      /* 定义GPDMA的链表节点配置结构体变量 */
  40.     RCC_PeriphCLKInitTypeDef adc_clk_init = {0}; /* 定义外设时钟配置结构体变量 */
  41.     DMA_NodeTypeDef   gpdma1_ch1_node = {0}; /* 定义DMA1通道1的链表节点结构体变量 */
  42.     DMA_QListTypeDef  gpdma1_ch1_qlist = {0};/* 定义DMA1通道1的链表队列结构体变量 */

  43.     if(hdac->Instance==DAC1)
  44.     {
  45.         /* 打开IO、DAC1和GPDMA相关时钟 */
  46.         __HAL_RCC_GPIOA_CLK_ENABLE();                /* DAC通道引脚时钟使能 */
  47.         __HAL_RCC_DAC1_CLK_ENABLE();                 /* DAC1外设时钟使能 */
  48.         __HAL_RCC_GPDMA1_CLK_ENABLE();              /* GPDMA1时钟使能 */

  49.         /* DAC1 时钟源初始化 */
  50.         adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_DAC;/* 配置DAC时钟源 */
  51. /* DAC时钟源选择HCLK */
  52.         adc_clk_init.AdcDacClockSelection = RCC_ADCDACCLKSOURCE_HCLK;  
  53.         HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);            /* 配置DAC时钟 */

  54.         /* DAC1 对应IO初始化 */
  55.         gpio_init_struct.Pin = GPIO_PIN_4;                    /* PA4 */
  56.         gpio_init_struct.Mode = GPIO_MODE_ANALOG;              /* 模拟 */
  57.         gpio_init_struct.Pull = GPIO_NOPULL;                    /* 无上下拉 */
  58.         HAL_GPIO_Init(GPIOA, &gpio_init_struct);                /* 初始化DAC引脚 */

  59.         /* DAC1 DMA 初始化 */
  60.         node_conf.NodeType = DMA_GPDMA_LINEAR_NODE;            /* GPDMA线性寻址节点类型 */
  61.         node_conf.Init.Request = GPDMA1_REQUEST_DAC1_CH1; /* DMA通道请求 */
  62. /* 单/突发级别的硬件请求协议 */
  63.         node_conf.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
  64.         /* DMA通道的传输方向:内存到外设 */
  65.         node_conf.Init.Direction = DMA_MEMORY_TO_PERIPH;   
  66.         node_conf.Init.SrcInc = DMA_SINC_INCREMENTED;/* 源增量模式:增量模式 */
  67.         node_conf.Init.DestInc = DMA_DINC_FIXED;      /* 目标增量模式:固定模式 */
  68. /* 源数据宽度:16位 */
  69.         node_conf.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
  70. /* 目标数据宽度:16位 */
  71.         node_conf.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
  72.         node_conf.Init.SrcBurstLength = 1;  /* 指定DMA的源突发长度,范围: 1 到 64 */
  73.         node_conf.Init.DestBurstLength = 1; /* 指定的目标突发长度,范围: 1 到 64 */
  74.         node_conf.Init.TransferAllocatedPort =
  75. DMA_SRC_ALLOCATED_PORT1|DMA_DEST_ALLOCATED_PORT0; /* 指定传输分配的端口 */
  76. /* 指定DMA通道的传输事件模式 */
  77.         node_conf.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;   
  78.         node_conf.Init.Mode = DMA_NORMAL;  /* DMA模式:正常模传输 */
  79.         /* 设置DMA通道触发极性 */
  80.         node_conf.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;  
  81.         /* DMA通道数据交换模式:无数据交换 */
  82.         node_conf.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;  
  83. /* 如果源数据宽度<目标数据宽度=>右对齐,填充0至目标数据宽度 */
  84.         node_conf.DataHandlingConfig.DataAlignment =
  85. DMA_DATA_RIGHTALIGN_ZEROPADDED;        
  86.         HAL_DMAEx_List_BuildNode(&node_conf, &gpdma1_ch1_node);/*构建DMA通道节点*/
  87.         /* 在链表队列中插入新节点 */
  88.         HAL_DMAEx_List_InsertNode(&gpdma1_ch1_qlist, NULL, &gpdma1_ch1_node);   
  89.         /* 设置链表队列的循环模式 */
  90.         HAL_DMAEx_List_SetCircularMode(&gpdma1_ch1_qlist);  

  91.         /* 以链表方式初始化DMA通道 */
  92. /* 选择GPDMA1_Channel1 */
  93.         g_gpdma1_ch1_dac_handle.Instance = GPDMA1_Channel1;
  94.         g_gpdma1_ch1_dac_handle.InitLinkedList.Priority =
  95. DMA_LOW_PRIORITY_LOW_WEIGHT; /* 通道优先级 */
  96.         g_gpdma1_ch1_dac_handle.InitLinkedList.LinkStepMode =
  97. DMA_LSM_FULL_EXECUTION;       /* 执行所有链表的通道 */
  98.         g_gpdma1_ch1_dac_handle.InitLinkedList.LinkAllocatedPort =
  99. DMA_LINK_ALLOCATED_PORT1;     /* 链路分配端口1  */
  100.         g_gpdma1_ch1_dac_handle.InitLinkedList.TransferEventMode =
  101. DMA_TCEM_BLOCK_TRANSFER; /* 指定DMA通道的传输事件模式 */
  102.         g_gpdma1_ch1_dac_handle.InitLinkedList.LinkedListMode =
  103. DMA_LINKEDLIST_CIRCULAR;      /* 循环链表DMA通道传输 */
  104.         /* 以链表方式初始化DMA通道 */
  105.         HAL_DMAEx_List_Init(&g_gpdma1_ch1_dac_handle);  
  106.         /* 链接到DMA通道的链表队列 */
  107.         HAL_DMAEx_List_LinkQ(&g_gpdma1_ch1_dac_handle, &gpdma1_ch1_qlist);  
  108.         /* 将DMA句柄与ADC句柄关联起来 */
  109.         __HAL_LINKDMA(hdac, DMA_Handle1, g_gpdma1_ch1_dac_handle);  

  110.         HAL_DMA_ConfigChannelAttributes(&g_gpdma1_ch1_dac_handle,
  111. DMA_CHANNEL_NPRIV);  /* 通道没有特权 */
  112.     }
  113. }
复制代码
上述函数用于初始化DAC用DMA的方式输出正弦波。本函数用到的API函数起前面都介绍过,请结合前面介绍过的相关内容来理解源码。这里值得注意的是我们是采用定时器7触发DAC进行转换输出的。
下面介绍DAC DMA使能波形输出函数,其定义如下:

  1. /**
  2. * @brief       DAC DMA使能波形输出
  3. *   @note      TIM7的输入时钟频率(f)来自APB1, f = 250Mhz.
  4. *               DAC触发频率 ftrgo = f / ((psc + 1) * (arr + 1))
  5. *               波形频率 = ftrgo / ndtr;
  6. * @param       ndtr        : DMA通道单次传输数据量
  7. * @param       arr         : TIM7的自动重装载值
  8. * @param       psc         : TIM7的分频系数
  9. * @retval      无
  10. */
  11. void dac_dma_wave_enable(uint16_t ndtr, uint16_t arr, uint16_t psc)
  12. {
  13.     TIM_HandleTypeDef tim_handle = {0};
  14.     TIM_ClockConfigTypeDef  tim_clock_config = {0};
  15.     TIM_MasterConfigTypeDef master_config = {0};

  16.     __HAL_RCC_TIM7_CLK_ENABLE();                                        /* TIM7时钟使能 */

  17.     tim_handle.Instance = TIM7;                                          /* 选择定时器7 */
  18.     tim_handle.Init.Prescaler = psc;                                     /* 分频系数 */
  19.     tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP;         /* 递增计数 */
  20.     tim_handle.Init.Period = arr;                                        /* 重装载值 */
  21.     tim_handle.Init.AutoReloadPreload =TIM_AUTORELOAD_PRELOAD_ENABLE;/*自动重装*/
  22.     HAL_TIM_Base_Init(&tim_handle);                                       /* 初始化定时器7 */
  23.     tim_clock_config.ClockSource = TIM_CLOCKSOURCE_INTERNAL;/*TIM7时钟源来自内部*/
  24.     HAL_TIM_ConfigClockSource(&tim_handle, &tim_clock_config);/* 配置TIM7时钟源*/

  25.     master_config.MasterOutputTrigger = TIM_TRGO_UPDATE;/* 定时器更新事件用于触发 */
  26.     master_config.MasterOutputTrigger2 = TIM_TRGO2_RESET;  /* 不使用触发输出2 */
  27. master_config.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;/* 选择主从模式 */
  28. /* 配置TIM7 TRGO */
  29.     HAL_TIMEx_MasterConfigSynchronization(&tim_handle, &master_config);
  30.    
  31.     HAL_TIM_Base_Start(&tim_handle);   /* 使能定时器7开始计时 */

  32.     /* 启用DAC选择的通道和关联的DMA */
  33. HAL_DAC_Start_DMA(&g_dac_dma_handle, DAC_CHANNEL_1,
  34. (uint32_t *)g_dac_sin_buf, ndtr, DAC_ALIGN_12B_R);
  35. }
复制代码
该函数用于使能波形输出,利用定时器7的更新事件来触发DAC转换输出。使能定时器7的时钟后,调用HAL_TIMEx_MasterConfigSynchronization函数配置TIM7选择更新事件作为触发输出 (TRGO),然后调用HAL_DAC_Stop_DMA函数停止DAC转换以及DMA传输,最后再调用HAL_DAC_Start_DMA函数重新配置并启动DAC和DMA。
最后在main.c里面编写如下代码:

  1. uint16_t g_dac_sin_buf[4096];    /* 发送数据缓冲区 */

  2. /**
  3. * @brief       产生正弦波序列
  4. *   @note      需保证: maxval > samples/2
  5. *
  6. * @param       maxval : 最大值(0 < maxval < 2048)
  7. * @param       samples: 采样点的个数
  8. *
  9. * @retval      无
  10. */
  11. void dac_creat_sin_buf(uint16_t maxval, uint16_t samples)
  12. {
  13.     uint8_t i;
  14.     float inc = (2 * 3.1415962) / samples; /* 计算增量(一个周期DAC_SIN_BUF个点)*/
  15.     float outdata = 0;
  16.     for (i = 0; i < samples; i++)
  17. {
  18. /* 计算以dots个点为周期的每个点的值,放大maxval倍,并偏移到正数区域 */
  19.         outdata = maxval * (1 + sin(inc * i));  
  20.         if (outdata > 4095) outdata = 4095;     /* 上限限定 */
  21.         //printf("%f\r\n",outdata);
  22.         g_dac_sin_buf[i] = outdata;
  23.     }
  24. }

  25. /**
  26. * @brief       通过USMART设置正弦波输出参数,方便修改输出频率.
  27. * @param       arr : TIM7的自动重装载值
  28. * @param       psc : TIM7的分频系数
  29. * @retval      无
  30. */
  31. void dac_dma_sin_set(uint16_t arr, uint16_t psc)
  32. {
  33.     dac_dma_wave_enable(100, arr, psc);
  34. }

  35. int main(void)
  36. {
  37.     uint16_t adcx;
  38.     float temp;
  39.     uint8_t t = 0;
  40.     uint8_t key;

  41.     HAL_Init();                                         /* HAL库初始化 */
  42.     HAL_ICACHE_Enable();                              /* 使能指令缓存 */
  43.     sys_stm32_clock_init(250, 4, 2, 2);           /* 系统时钟初始化, 250Mhz */
  44.     delay_init(250);                                  /* 延时初始化 */
  45.     usart_init(115200);                             /* 串口初始化 */
  46.     usmart_dev.init(250);                            /* USMART初始化 */
  47.     led_init();                                        /* LED初始化 */
  48.     key_init();                                       /* 按键初始化 */
  49.     lcd_init();                                        /* LCD初始化 */
  50.     adc_init();                                         /* ADC初始化 */
  51.     dac_dma_wave_init();                            /* 初始化DAC通道1 DMA波形输出 */

  52.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  53.     lcd_show_string(30, 70, 200, 16, 16, "DAC DMA Sine WAVE TEST", RED);
  54.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  55.     lcd_show_string(30, 110, 200, 16, 16, "KEY0:5Khz  KEY1:50Khz", RED);

  56.     lcd_show_string(30, 130, 200, 16, 16, "DAC VAL:", BLUE);
  57.     lcd_show_string(30, 150, 200, 16, 16, "DAC VOL:0.000V", BLUE);
  58.     lcd_show_string(30, 170, 200, 16, 16, "ADC VOL:0.000V", BLUE);

  59.     dac_creat_sin_buf(2048, 100);
  60.     dac_dma_wave_enable(100, 100 - 1, 25 - 1);

  61.     while (1)
  62.     {
  63.         t++;
  64.         key = key_scan(0);  /* 按键扫描 */
  65.         if (key == KEY0_PRES)                             /* 高采样率 , 约5Khz波形 */
  66.         {
  67.             dac_creat_sin_buf(2048, 100);               /* 产生正弦波函序列 */
  68. /* 500Khz触发频率, 100个点, 得到最高5KHz的正弦波. */
  69.             dac_dma_wave_enable(100, 20 - 1, 24 - 1);           
  70.         }
  71.         else if (key == KEY1_PRES)                       /* 低采样率 , 约50Khz波形 */
  72.         {
  73.             dac_creat_sin_buf(2048, 10);                /* 产生正弦波函序列 */
  74. /* 500Khz触发频率, 10个点, 可以得到最高50KHz的正弦波. */
  75.             dac_dma_wave_enable(10, 20 - 1, 24 - 1);            
  76.         }
  77.         adcx = DAC1->DHR12R1;                             /* 获取DAC1_OUT1的输出状态 */
  78.         lcd_show_xnum(94, 130, adcx, 4, 16, 0, BLUE);/* 显示DAC寄存器值 */
  79.         temp = (float)adcx * (3.3 / 4096);             /* 得到DAC电压值 */
  80.         adcx = temp;
  81.         lcd_show_xnum(94, 150, temp, 1, 16, 0, BLUE);/* 显示电压值整数部分 */
  82.         temp -= adcx;
  83.         temp *= 1000;
  84.         lcd_show_xnum(110, 150, temp, 3, 16, 0X80, BLUE);  /* 显示电压值的小数部分 */
  85.         adcx = adc_get_result_average(ADC_ADCX_CHY,20);/*得到ADC通道19的转换结果*/
  86.         temp = (float)adcx * (3.3 / 65536);      /* 得到ADC电压值(adc是16bit的) */
  87.         adcx = temp;
  88.         lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE);   /* 显示电压值整数部分 */
  89.         temp -= adcx;
  90.         temp *= 1000;
  91.         lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE);/* 显示电压值的小数部分 */
  92.         if (t == 10)        /* 定时时间到了 */
  93.         {
  94.             LED0_TOGGLE();  /* LED0闪烁 */
  95.             t = 0;
  96.         }
  97.         delay_ms(5);
  98.     }
  99. }
复制代码
adc_init函数初始化ADC1,用于测量DAC通道1的电压值。
dac_dma_wave_init函数初始化DAC通道1,并指定DMA搬运的数据的开始地址和目标地址。dac_creat_sin_buf函数用于产生正弦波序列,并保存在g_dac_sin_buf数组中,供给DAC转换。在进入wilhe(1)循环之前,dac_dma_wave_enable函数默认配置DAC的采样点个数时100,并配置定时器7的溢出频率为100KHz。这样就可以输出1KHz的正弦波。下面给大家解释一下为什么是输出1KHz的正弦波?
定时器7的溢出频率为100KHz,不记得怎么计算的朋友,请回顾基本定时器的相关内容,这里直接把公式列出:
Tout= ((arr+1)*(psc+1))/Tclk
看到dac_dma_wave_enable(100, 100 - 1, 25 - 1);这个语句,第二个形参是自动重装载值,第三个形参是分频系数,那么代入公式,可得:
Tout= ((arr+1)*(psc+1))/Tclk= ((99+1)*(24+1))/ 250MHz= 0.00001s
得到定时器的更新事件周期是0.00001秒,即更新事件频率为100KHz,也就得到DAC输出触发频率为100KHz。
再结合总一个正弦波共有100个采样点,就可以得到正弦波的频率为100KHz/100 = 1KHz。
知道了正弦波的频率怎么来的,下面代码中,按下按键KEY0,得到5KHz的正弦波,按下按键KEY1,得到50KHz的正弦波,计算方法都一样的。
dac_dma_sin_set函数可以通过USMART设置正弦波输出参数,方便修改输出频率。


31.4.4 下载验证
下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如图31.4.4.1所示:

第三十一章 DAC实验34169.png
图31.4.4.1 DAC输出正弦波实验测试图

上图是将跳线帽连接多功能端口P5的ADC和DAC两个排针,可以看到ADC VOL的值随着DAC的输出变化而变化,即ADC采集到的值是不停变化的。由于变化太快了,这样看不出采集到值形成什么波形,下面我们借用示波器来进行观察,首先将探头接到DAC的排针上。
没有按下任何按键之前,默认是输出1KHz(100个采样点)的正弦波,如下图所示:


第三十一章 DAC实验34364.png
图31.4.4.2 默认DAC输出的的正弦波

当按下KEY0后,DAC输出5KHz(100个采样点)的正弦波,如下图所示:

第三十一章 DAC实验34428.png
图31.4.4.3 按下KEY0,DAC输出的的正弦波

当按下KEY1后,DAC输出50KHz(10个采样点)的正弦波,如下图所示:

第三十一章 DAC实验34497.png
图31.4.4.4 按下KEY1,DAC输出的的正弦波
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-9-9 03:34

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表