【正点原子STM32连载】 第三十三章 DAC实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

news2024/10/6 10:43:05

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第三十三章 DAC实验

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

33.1 DAC简介

STM32F103的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功能
DAC通道框图如图33.1.1所示:
在这里插入图片描述

图33.1.1 DAC通道框图
图中VDDA和VSSA为DAC模块模拟部分的供电,而VREF+则是DAC模块的参考电压。DAC_OUTx就是DAC的两个输出通道了(对应PA4或者PA5引脚)。ADC的这些输入/输出引脚信息如下表所示:
引脚名称 信号类型 说明
VREF+ 正模拟参考电压输入 DAC高/正参考电压,VREF+≤VDDA(3.3V)
VDDA 模拟电源输入 模拟电源
VSSA 模拟电源地输入 模拟电源地
DAC_OUTx 模拟输出信号 DAC通道x模拟输出,x=1、2
表33.1.1 DAC输入/输出引脚
从图33.1.1可以看出,DAC输出是受DORx(x=1/2,下同)寄存器直接控制的,但是我们不能直接往DORx寄存器写入数据,而是通过DHRx间接的传给DORx寄存器,实现对DAC输出的控制。
前面我们提到,STM32F103的DAC支持8/12位模式,8位模式的时候是固定的右对齐的,而12位模式又可以设置左对齐/右对齐。DAC单通道模式下的数据寄存器对齐方式,总共有3种情况,如下图所示:
在这里插入图片描述

图33.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双通道(可用时),也有三种可能的方式,如下图所示:
在这里插入图片描述

图33.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_CR的TENx位置0),存入寄存器DAC_DHRx的数据会在1个APB1时钟周期后自动传至寄存器DAC_DORx。如果选中硬件触发(寄存器DAC_CR的TENx位置1),数据传输在触发发生以后3个APB1时钟周期后完成。一旦数据从DAC_DHRx寄存器装入DAC_DORx寄存器,在经过时间tSETTLING之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从《《STM32F103ZET6.pdf》数据手册查到tSETTLING的典型值为3us,最大是4us,所以DAC的转换速度最快是333K左右。
不使用硬件触发(TEN=0),其转换的时间框图如图33.1.4所示:
在这里插入图片描述

图33.1.4 TEN=0时DAC模块转换时间框图
当DAC的参考电压为Vref+的时候,DAC的输出电压是线性的从0~Vref+,12位模式下DAC输出电压与Vref+以及DORx的计算公式如下:
DACx输出电压= Vref *(DORx/4096)
如果使用硬件触发(TENx=1),可通过外部事件(定时计数器、外部中断线)触发DAC转换。由TSELx[2:0]控制位来决定选择8个触发事件中的一个来触发转换。触发事件如下表所示:
在这里插入图片描述

表33.1.3 DAC触发选择
原表见《STM32F10xxx参考手册_V10(中文版).pdf》第185页表71。
每个DAC通道都有DMA功能,两个DMA通道分别用于处理两个DAC通道的DMA请求。如果DMAENx位置1时,如果发生外部触发(而不是软件触发),就会产生一个DMA请求,然后DAC_DHRx寄存器的数据被转移到DAC_NORx寄存器。
33.2 DAC输出实验
本实验我们来学习DAC输出实验。
33.2.1 DAC寄存器
下面,我们介绍要实现DAC的通道1输出,需要用到的一些DAC寄存器。
 DAC控制寄存器(DAC_CR)
DAC控制寄存器描述如图33.2.1.1所示:
在这里插入图片描述

图33.2.1.1 DACx_CR寄存器
DAC_CR的低16位用于控制通道1,高16位用于控制通道2,下面介绍本实验需要设置的一些位:
EN1位用于DAC通道1的使能,我们需要用到DAC通道1的输出,该位必须设置为1。
BOFF1位用于DAC输出缓存控制,这里STM32的DAC输出缓存做的有些不好,如果使能的话,虽然输出能力强一点,但是输出没法到0,这是个很严重的问题。所以本章的三个实验我们都不使用输出缓存,即该位设置为1。
TEN1位用于DAC通道1的触发使能,我们设置该位为0,不使用触发。写入DHR1的值会在1个APB1周期后传送到DOR1,然后输出到PA4口上。
TSEL1[2:0]位用于选择DAC通道1的触发方式,这里我们没有用到外部触发,所以这几位设置为0即可。
WAVE1[1:0]位用于控制DAC通道1的噪声/波形输出功能,我们这里没用到波形发生器,所以默认设置为00,不使能噪声/波形输出。
MAMP[3:0]位是屏蔽/幅值选择器,用来在噪声生成模式下选择屏蔽位,在三角波生成模式下选择波形的幅值。本实验没有用到波形发生器,所以设置为0即可。
DMAEN1位用于DAC通道1的DMA使能,本实验没有用到DMA功能,所以设置为0。
 DAC通道1 12位右对齐数据保持寄存器(DAC_DHR12R1)
DAC通道1的12位右对齐数据保持寄存器描述如图33.2.1.3所示:
在这里插入图片描述

图33.2.1.3 DAC0_ DHR12R1寄存器
该寄存器用来设置DAC输出,通过写入12位数据到该寄存器,就可以在DAC输出通道1(PA4)得到我们所要的结果。
33.2.2 硬件设计

  1. 例程功能
    使用KEY1/KEY_UP两个按键,控制STM32内部DAC的通道1输出电压大小,然后通过ADC2的通道14采集DAC输出的电压,在LCD模块上面显示ADC采集到的电压值以及DAC的设定输出电压值等信息。也可以通过usmart调用dac_set_voltage函数,来直接设置DAC输出电压。LED0闪烁,提示程序运行。
  2. 硬件资源
    1)LED灯 LED0 – PB5
    2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
    3)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
    4)独立按键 :KEY1 - PE3、WK_UP - PA0
    5)ADC3 :通道1 – PA1
    6)DAC1 :通道1 – PA4
  3. 原理图
    我们来看看原理图上ADC3通道1(PA1)和DAC1通道1(PA4)引出来的引脚,如下图所示:
    在这里插入图片描述

图33.2.2.1 ADC和DAC在开发板上的连接关系原理图
我们只需要通过跳线帽连接ADC和DAC,就可以使得ADC3通道1(PA1)和DAC1通道1(PA4)连接起来。对应的硬件连接如图33.2.2.2所示:
在这里插入图片描述

图33.2.2.2 硬件连接示意图
33.2.3 程序设计
33.2.3.1 DAC的HAL库驱动
DAC在HAL库中的驱动代码在stm32f1xx_hal_dac.c和stm32f1xx_hal_dac_ex.c文件(及其头文件)中。

  1. HAL_DAC_Init函数
    DAC的初始化函数,其声明如下:
    HAL_StatusTypeDef HAL_DAC_Init(DAC_HandleTypeDef *hdac);
    函数描述:
    用于初始化DAC。
    函数形参:
    形参1是DAC_HandleTypeDef结构体类型指针变量,其定义如下:
typedef struct
{
  DAC_TypeDef                    *Instance;    		/* DAC寄存器基地址 */
  __IO HAL_DAC_StateTypeDef   State;           	/* DAC 工作状态 */
  HAL_LockTypeDef               Lock;            	/* DAC锁定对象 */
  DMA_HandleTypeDef             *DMA_Handle1;  	/* 通道1的DMA处理句柄指针 */
  DMA_HandleTypeDef             *DMA_Handle2;  	/* 通道2的DMA处理句柄指针 */
  __IO uint32_t                  ErrorCode;     	/* DAC错误代码 */
} DAC_HandleTypeDef;

从该结构体看到该函数并没有设置任何DAC相关寄存器,即没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC,为后面HAL库操作DAC做好准备。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
注意事项:
DAC的MSP初始化函数HAL_DAC_MspInit,该函数声明如下:
void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac);
2. HAL_DAC_ConfigChannel函数
DAC 的通道参数初始化函数,其声明如下:

HAL_StatusTypeDef HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac, 
DAC_ChannelConfTypeDef *sConfig, uint32_t Channel);

函数描述:
该函数用来配置DAC通道的触发类型以及输出缓冲。
函数形参:
形参1是DAC_HandleTypeDef结构体类型指针变量。
形参2是DAC_ChannelConfTypeDef结构体类型指针变量,其定义如下:

typedef struct
{
  uint32_t DAC_Trigger;                 /* DAC触发源的选择 */
  uint32_t DAC_OutputBuffer;           /* 启用或者禁用DAC通道输出缓冲区 */
} DAC_ChannelConfTypeDef;

形参2用于选择要配置的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
3. HAL_DAC_Start函数
使能启动DAC转换通道函数,其声明如下:
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的通道输出值函数,其声明如下:
HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel,
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读取通道输出值函数,其声明如下:
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,它们的时钟开启方法如下:

__HAL_RCC_DAC_CLK_ENABLE ();           /* 使能DAC1时钟 */
__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的输出值。
33.2.3.2 程序流程图
在这里插入图片描述

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

/**
 * @brief       DAC初始化函数
 * @note        本函数支持DAC1_OUT1/2通道初始化
 *       DAC的输入时钟来自APB1, 时钟频率=36Mhz=27.8ns
 *       DAC在输出buffer关闭的时候, 输出建立时间: tSETTLING = 4us 
 *       因此DAC输出的最高速度约为:250Khz, 以10个点为一个周期, 最大能输出25Khz左右的波形
 * @param     outx: 要初始化的通道. 1,通道1; 2,通道2
* @retval    无
*/
void dac_init(uint8_t outx)
{
    GPIO_InitTypeDef gpio_init_struct;
    DAC_ChannelConfTypeDef dac_ch_conf;
    
    __HAL_RCC_DAC_CLK_ENABLE();  /* 使能DAC1的时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();/* 使能DAC OUT1/2的IO口时钟(都在PA口,PA4/PA5) */
    
/* STM32单片机, 总是PA4=DAC1_OUT1, PA5=DAC1_OUT2 */
    gpio_init_struct.Pin = (outx==1)? GPIO_PIN_4 : GPIO_PIN_5;  
    gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);

    g_dac_handle.Instance = DAC;
    HAL_DAC_Init(&g_dac_handle);                                  /* 初始化DAC */

    dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;                /* 不使用触发功能 */
    dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;/* DAC1输出缓冲关闭 */
    
    switch(outx)
    {
        case 1:
/* DAC通道1配置 */
            HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1); 
            HAL_DAC_Start(&g_dac_handle,DAC_CHANNEL_1);    /* 开启DAC通道1 */
            break;
        case 2:
/* DAC通道2配置 */
            HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_2);  
            HAL_DAC_Start(&g_dac_handle,DAC_CHANNEL_2);    /* 开启DAC通道1 */
            break;
        default:break;
    }
}

该函数主要调用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输出电压函数,其定义如下:

/**
 * @brief       设置通道1/2输出电压
 * @param       outx: 1,通道1; 2,通道2
 * @param       vol : 0~3300,代表0~3.3V
 * @retval      无
 */
void dac_set_voltage(uint8_t outx, uint16_t vol)
{
    double temp = vol;
    temp /= 1000;
    temp = temp * 4096 / 3.3;
    if (temp >= 4096)temp = 4095;   /* 如果值大于等于4096, 则取4095 */

    if (outx == 1)		/* 通道1 */
{   /* 12位右对齐数据格式设置DAC值 */
        HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp); 
    }
    else            		/* 通道2 */
{   /* 12位右对齐数据格式设置DAC值 */
        HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_2, DAC_ALIGN_12B_R, temp); 
    }
}

该函数实际就是将电压值转换为DAC输入值,形参1用于设置通道,形参2设置要输出的电压值,设置的范围:03300,代表03.3V。
最后在main函数里面编写如下代码:

int main(void)
{
    uint16_t adcx;
    float temp;
    uint8_t t = 0;
    uint16_t dacval = 0;
    uint8_t key;

    HAL_Init();                            		/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);		/* 设置时钟, 72Mhz */
    delay_init(72);                           	/* 延时初始化 */
    usart_init(115200);                      	/* 串口初始化为115200 */
    usmart_dev.init(72);                     	/* 初始化USMART */
    usart_init(115200);                      	/* 串口初始化为115200 */
    led_init();                               	/* 初始化LED */
    lcd_init();                                 	/* 初始化LCD */
    key_init();                                 	/* 初始化按键 */
    adc2_init();                                	/* 初始化ADC2 */
    dac_init(1);                                	/* 初始化DAC1_OUT1通道 */

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

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

    while (1)
    {
        t++;
        key = key_scan(0);          /* 按键扫描 */

        if (key == WKUP_PRES)
        {
          if (dacval < 4000)dacval += 200;
/* 输出增大200 */
          HAL_DAC_SetValue(&g_dac1_handler,DAC_CHANNEL_1,DAC_ALIGN_12B_R,dacval);
        }
        else if (key == KEY1_PRES)
        {
           if (dacval > 200)dacval -= 200;
           else dacval = 0;
/* 输出减少200 */
          HAL_DAC_SetValue(&g_dac1_handler,DAC_CHANNEL_1,DAC_ALIGN_12B_R,dacval); 
        }
/* WKUP/KEY1按下了,或者定时时间到了 */
        if (t == 10 || key == KEY1_PRES || key == WKUP_PRES)    
        {
/* 读取前面设置DAC1_OUT1的值 */
adcx = HAL_DAC_GetValue(&g_dac1_handler, DAC_CHANNEL_1);
            lcd_show_xnum(94, 150, adcx, 4, 16, 0, BLUE);	/* 显示DAC寄存器值 */
            
            temp = (float)adcx * (3.3 / 4096);               	/* 得到DAC电压值 */
            adcx = temp;
            lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE); 	/* 显示电压值整数部分 */
            
            temp -= adcx;
            temp *= 1000;
            lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE);/*显示电压值的小数部分*/
            
            adcx = adc2_get_result_average(ADC2_CHY,20);/*得到ADC3通道1的转换结果*/
            temp = (float)adcx * (3.3 / 4096);      /*得到ADC电压值(adc是16bit的)*/
            adcx = temp;
            lcd_show_xnum(94, 190, temp, 1, 16, 0, BLUE);    /* 显示电压值整数部分 */
            
            temp -= adcx;
            temp *= 1000;
            lcd_show_xnum(110, 190, temp, 3, 16, 0X80, BLUE);/*显示电压值的小数部分*/
            
            LED0_TOGGLE();  /* LED0闪烁 */
            t = 0;
        }
        delay_ms(10);
    }
}

此部分代码,我们通过KEY_UP(WKUP按键)和KEY1(也就是上下键)来实现对DAC输出的幅值控制。按下KEY_UP增加,按KEY1减小。同时在LCD上面显示DHR12R1寄存器的值、DAC设置输出电压以及ADC采集到的DAC输出电压。
33.2.4 下载验证
下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如图33.2.4.1所示:
在这里插入图片描述

图33.2.4.1 DAC输出实验测试图
验证试验前,记得先通过跳线帽连接ADC和DAC排针,然后我们可以通过按WK_UP按键,增加DAC输出的电压,这时ADC采集到的电压也会增大,通过按KEY1减小DAC输出的电压,这时ADC采集到的电压也会减小。
除此之外,我们还可以通过usmart调用dac_set_voltage函数,来直接设置DAC输出电压,如下图33.2.4.2所示:
在这里插入图片描述

图33.2.4.2 usmart测试图

33.3 DAC输出三角波实验
本实验我们来学习使用如何让DAC输出三角波,DAC初始化部分还是用DAC输出实验的,所以做本实验的前提是先学习DAC输出实验。
33.3.1 DAC寄存器
本实验用到的寄存器在DAC输出实验都有介绍。
33.3.2 硬件设计

  1. 例程功能
    使用DAC输出三角波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种三角波,需要通过示波器接PA4进行观察。也可以通过usmart调用dac_triangular_wave函数,来控制输出哪种三角波。LED0闪烁,提示程序运行。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
    3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
    4)独立按键:KEY0 – PE4、KEY1 – PE3
    5)DAC1 :通道1 - PA4
  3. 原理图
    我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4对应P10的DAC排针,如图33.3.2.1所示:
    在这里插入图片描述

图33.3.2.1 硬件连接示意图
33.3.3 程序设计
本实验用到的DAC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍DAC输出三角波的配置步骤。
DAC输出三角波配置步骤
1)开启DACx和DAC通道对应的IO时钟,并配置该IO为模拟功能
首先开启DACx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下:
__HAL_RCC_DAC_CLK_ENABLE (); /* 使能DAC1时钟 /
__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函数用于控制输出三角波。
33.3.3.1 程序流程图
在这里插入图片描述

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

/**
 * @brief       设置DAC_OUT1输出三角波
 * @note        输出频率 ≈ 1000 / (dt * samples) Khz, 不过在dt较小的时候,比如小于5us
 *               时, 由于delay_us本身就不准了(调用函数,计算等都需要时间,延时很小的时候,这些*               时间会影响到延时), 频率会偏小.
 * 
 * @param       maxval: 最大值(0< maxval<4096), (maxval + 1)必须大于等于samples/2
 * @param       dt     : 每个采样点的延时时间(单位: us)
 * @param       samples: 采样点的个数,必须小于等于(maxval+1)*2,且maxval不能等于0
 * @param       n      : 输出波形个数,0~65535
 *
 * @retval      无
 */
void dac_triangular_wave(uint16_t maxval, uint16_t dt, uint16_t samples,
 uint16_t n)
{
    uint16_t i, j;
    float incval;           /* 递增量 */
    float Curval;           /* 当前值 */
    if((maxval + 1) <= samples)return ; 	/* 数据不合法 */
    incval = (maxval + 1) / (samples / 2);  /* 计算递增量 */
    for(j = 0; j < n; j++)
    { 
Curval = 0;   /* 先输出0 */
        HAL_DAC_SetValue(&dac1_handler,DAC_CHANNEL_1,DAC_ALIGN_12B_R,Curval);
        for(i = 0; i < (samples / 2); i++)  	/* 输出上升沿 */
        {
            Curval  +=  incval;         		/* 新的输出值 */
            HAL_DAC_SetValue(&dac1_handler,DAC_CHANNEL_1,DAC_ALIGN_12B_R,Curval);
            delay_us(dt);
        } 
        for(i = 0; i < (samples / 2); i++)  /* 输出下降沿 */
        {
            Curval  -=  incval;         		/* 新的输出值 */
            HAL_DAC_SetValue(&dac1_handler,DAC_CHANNEL_1,DAC_ALIGN_12B_R,Curval);
            delay_us(dt);
        }
    }
}

该函数用于设置DAC通道1输出三角波,输出频率 ≈ 1000 / (dt * samples) Khz,形参含义在源码已经有详细注释。该函数中,我们使用HAL_DAC_SetValue函数来设置DAC的输出值,这样得到的三角波在示波器上可以看到。如果有跳动现象(不平稳),是正常的,因为调用函数,计算等都需要时间,这样就会导致输出的波形是不太稳定的。越高性能的MCU,得到的波形会越稳定。而且用HAL库函数操作效率没有直接操作寄存器高,所以可以像寄存器版本实验一样,直接操作DHR12R1寄存器,得到的波形会相对稳定些。
由于使用HAL库的函数,CPU花费的时间会更长(因为指令变多了),在时间精度要求比较高的应用,就不适合用HAL库函数来操作了,这一点希望大家明白。所以学STM32不是说只要会HAL库就可以了,对寄存器也是需要有一定的理解,最好是熟悉。这里用HAL库操作只是为了演示怎么使用HAL库的相关函数。
最后在main.c里面编写如下代码:

int main(void)
{
    uint8_t t = 0; 
uint8_t key;

    HAL_Init();                             	/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);	/* 设置时钟, 72Mhz */
    delay_init(72);                        	/* 延时初始化 */
    usart_init(115200);                   	/* 串口初始化为115200 */
    usmart_dev.init(72);                  	/* 初始化USMART */
    led_init();                             	/* 初始化LED */
    lcd_init();                             	/* 初始化LCD */
    key_init();                             	/* 初始化按键 */
dac_init(1);                           	/* 初始化DAC1_OUT1通道 */

    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "DAC Triangular WAVE TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Wave1  KEY1:Wave2", RED);
lcd_show_string(30, 130, 200, 16, 16, "DAC None", BLUE); /* 提示无输出 */

    while (1)
    {
        t++;
        key = key_scan(0); 		/* 按键扫描 */
        if (key == KEY0_PRES) 	/* 高采样率 , 约1Khz波形 */
        {
            lcd_show_string(30, 130, 200, 16, 16, "DAC Wave1 ", BLUE);
/* 幅值4095, 采样点间隔5us, 200个采样点, 100个波形 */
            dac_triangular_wave(4095, 5, 2000, 100); 
            lcd_show_string(30, 130, 200, 16, 16, "DAC None  ", BLUE);
        }
        else if (key == KEY1_PRES) /* 低采样率 , 约1Khz波形 */
        {
            lcd_show_string(30, 130, 200, 16, 16, "DAC Wave2 ", BLUE);
/* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */
            dac_triangular_wave(4095, 500, 20, 100);
            lcd_show_string(30, 130, 200, 16, 16, "DAC None  ", BLUE);
        }
        if (t == 10)			/* 定时时间到了 */
        {
            LED0_TOGGLE(); 	/* LED0闪烁 */
            t = 0;
        }
        delay_ms(10);
    }
}

该部分代码功能是,按下KEY0后,DAC输出三角波1,按下KEY1后,DAC输出三角波2,将dac_triangular_wave的形参代入公式:输出频率 ≈ 1000 / (dt * samples) KHz,得到三角波1和三角波2的频率都是0.1KHz。
33.3.4 下载验证
下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如图33.3.4.1所示:
在这里插入图片描述

图33.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在示波器的显示情况如下图所示:
在这里插入图片描述

图33.3.4.2 DAC输出的三角波1
在这里插入图片描述

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

33.4 DAC输出正弦波实验
本实验我们来学习使用如何让DAC输出正弦波。实验将用定时器7来触发DAC进行转换输出正弦波,以DMA传输数据的方式。
33.4.1 DAC寄存器
本实验用到的寄存器在前面的实验都有介绍。
33.4.2 硬件设计

  1. 例程功能
    使用DAC输出正弦波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种正弦波,需要通过示波器接PA4进行观察。TFTLCD显示DAC转换值、电压值和ADC的电压值。LED0闪烁,提示程序运行。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
    3)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
    4)独立按键 :KEY0 – PE4、KEY1 – PE3
    5)ADC1 :通道1 – PA1
    6)DAC1 :通道1 – PA4
    7)DMA(DMA2_Channel13)
    8)定时器7
  3. 原理图
    我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4对应P10的DAC排针,硬件连接如图33.4.2.1所示:
    在这里插入图片描述

图33.4.2.1 硬件连接示意图
33.4.3 程序设计
33.4.3.1 DAC的HAL库驱动
本实验用到的HAL库API函数前面大都介绍过,下面将介绍本实验用到且没有介绍过的。

  1. HAL_DAC_Start_DMA函数
    启动DAC使用DMA方式传输函数,其声明如下:
    HAL_StatusTypeDef HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel,
    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方式函数,其声明如下:
    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函数
    配置主模式下的定时器触发输出选择函数,其声明如下:
    HAL_StatusTypeDef HAL_TIMEx_MasterConfigSynchronization(
    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,它们的时钟开启方法如下:
    __HAL_RCC_DAC_CLK_ENABLE (); /
    使能DAC1时钟 /
    __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并关联DAC
    通过HAL_DMA_Init函数初始化DMA,包括配置通道,外设地址,存储器地址,传输数据量等。
    HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。这个宏定义为__HAL_LINKDMA。
    5)配置定时器控制触发DAC
    通过HAL_TIM_Base_Init函数设置定时器溢出频率。
    通过HAL_TIMEx_MasterConfigSynchronization函数配置定时器溢出事件用做触发器。
    通过HAL_TIM_Base_Start函数启动计数。
    6)启动DAC转换并以DMA方式传输数据
    通过HAL_DAC_Stop_DMA函数先停止之前的DMA传输以及DAC输出。
    再通过HAL_DAC_Start_DMA函数启动DMA传输以及DAC输出。
    33.4.3.2 程序流程图
    在这里插入图片描述

图33.4.3.2.1 DAC输出正弦波实验程序流程图
33.4.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。
dac.h文件只有一些声明,下面直接开始介绍dac.c的程序,本实验的DAC以及DMA的初始化,我们用到dac_dma_wave_init函数,其定义如下:

/**
 * @brief       DAC DMA输出波形初始化函数
 * @note        本函数支持DAC1_OUT1/2通道初始化
 *         DAC的输入时钟来自APB1, 时钟频率=36Mhz=27.7ns
 *         DAC在输出buffer关闭的时候, 输出建立时间:tSETTLING = 4us(F103数据手册有写)
 *         因此DAC输出的最高速度约为:300Khz,以10个点为一个周期,最大能输出30Khz左右的波形
 *
 * @param       outx: 要初始化的通道. 1,通道1; 2,通道2
 * @param       par         : 外设地址
 * @param       mar         : 存储器地址
 * @retval      无
 */
void dac_dma_wave_init(uint8_t outx, uint32_t par, uint32_t mar)
{
    GPIO_InitTypeDef gpio_init_struct;
    DAC_ChannelConfTypeDef dac_ch_conf={0};
    DMA_Channel_TypeDef *dmax_chy;

    if (outx == 1)
    {
        dmax_chy = DMA2_Channel3;       /* OUT1对应DMA2_Channel3 */
    }
    else
    {
        dmax_chy = DMA2_Channel4;       /* OUT2对应DMA2_Channel4 */
    }

    __HAL_RCC_GPIOA_CLK_ENABLE();       /* DAC通道引脚端口时钟使能 */
    __HAL_RCC_DAC_CLK_ENABLE();         /* 使能DAC1的时钟 */
    __HAL_RCC_DMA2_CLK_ENABLE();        /* DMA2时钟使能 */

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

    /* 初始化DMA */
    g_dma_dac_handle.Instance = dmax_chy;                      /* 设置DMA通道 */
    g_dma_dac_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;/* 从存储器到外设模式 */
    g_dma_dac_handle.Init.PeriphInc = DMA_PINC_DISABLE;     /* 外设非增量模式 */
g_dma_dac_handle.Init.MemInc = DMA_MINC_ENABLE;          /* 存储器增量模式 */
/* 外设数据长度:16位 */
g_dma_dac_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
/* 存储器数据长度:16位 */
    g_dma_dac_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;       
    g_dma_dac_handle.Init.Mode = DMA_CIRCULAR;               /* 循环模式 */
    g_dma_dac_handle.Init.Priority = DMA_PRIORITY_MEDIUM;  /* 中等优先级 */
    HAL_DMA_Init(&g_dma_dac_handle);                           /* 初始化DMA */
    HAL_DMA_Start(&g_dma_dac_handle, mar, par, 0);          /* 配置DMA传输参数 */

/* DMA句柄与DAC句柄关联 */
    __HAL_LINKDMA(&g_dac_dma_handle, DMA_Handle1, g_dma_dac_handle);        

    /* 初始化DAC */
    g_dac_dma_handle.Instance = DAC;
    HAL_DAC_Init(&g_dac_dma_handle);                     /* 初始化DAC */

    /* 配置DAC通道 */
    dac_ch_conf.DAC_Trigger = DAC_TRIGGER_T7_TRGO;    /* 使用TIM7 TRGO事件触发 */
    dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;/* DAC1输出缓冲关闭 */

    switch(outx)
    {
        case 1:
            HAL_DAC_ConfigChannel(&g_dac_dma_handle, &dac_ch_conf, 
DAC_CHANNEL_1);   /* DAC通道1配置 */
            break;
        case 2:
            HAL_DAC_ConfigChannel(&g_dac_dma_handle, &dac_ch_conf, 
DAC_CHANNEL_2);   /* DAC通道2配置 */
            break;
        default:break;
    }
}

该函数用于初始化DAC用DMA的方式输出正弦波。本函数用到的API函数起前面都介绍过,请结合前面介绍过的相关内容来理解源码。这里值得注意的是我们是采用定时器7触发DAC进行转换输出的。
下面介绍DAC DMA使能波形输出函数,其定义如下:

/**
 * @brief       DAC DMA使能波形输出
 * @note        TIM7的输入时钟频率(f)来自APB1, f = 36M * 2 = 72Mhz.
 *               DAC触发频率 ftrgo = f / ((psc + 1) * (arr + 1))
 *               波形频率 = ftrgo / ndtr; 
 *
 * @param       outx        : 要初始化的通道. 1,通道1; 2,通道2
 * @param       ndtr        : DMA通道单次传输数据量
 * @param       arr         : TIM7的自动重装载值
 * @param       psc         : TIM7的分频系数
 * @retval      无
 */
void dac_dma_wave_enable(uint16_t cndtr, uint16_t arr, uint16_t psc)
{
    TIM_HandleTypeDef tim7_handle= {0};
    TIM_MasterConfigTypeDef tim_master_config= {0};
    
    __HAL_RCC_TIM7_CLK_ENABLE();                            /* TIM7时钟使能 */ 

    tim7_handle.Instance = TIM7;                            /* 选择定时器7 */
    tim7_handle.Init.Prescaler = psc;                      /* 预分频 */
    tim7_handle.Init.CounterMode = TIM_COUNTERMODE_UP;	/* 递增计数器 */
    tim7_handle.Init.Period = arr;                        	/* 自动装载值 */
    HAL_TIM_Base_Init(&tim7_handle);                      	/* 初始化定时器7 */

/* 定时器更新事件用于触发 */
    tim_master_config.MasterOutputTrigger = TIM_TRGO_UPDATE; 
tim_master_config.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
/* 配置定时器7的更新事件触发DAC转换 */
    HAL_TIMEx_MasterConfigSynchronization(&tim7_handle, &tim_master_config);  
    HAL_TIM_Base_Start(&tim7_handle);                         /* 启动定时器7 */

    HAL_DAC_Stop_DMA(&g_dac_dma_handle, DAC_CHANNEL_1)    /* 先停止之前的传输 */
HAL_DAC_Start_DMA(&g_dac_dma_handle, DAC_CHANNEL_1, 
(uint32_t *)g_dac_sin_buf, cndtr, DAC_ALIGN_12B_R);
}
该函数用于使能波形输出,利用定时器7的更新事件来触发DAC转换输出。使能定时器7的时钟后,调用HAL_TIMEx_MasterConfigSynchronization函数配置TIM7选择更新事件作为触发输出 (TRGO),然后调用HAL_DAC_Stop_DMA函数停止DAC转换以及DMA传输,最后再调用HAL_DAC_Start_DMA函数重新配置并启动DAC和DMA。
最后在main.c里面编写如下代码: 
uint16_t g_dac_sin_buf[4096];    /* 发送数据缓冲区 */

/**
 * @brief     产生正弦波序列
 * @note      需保证: maxval > samples/2
 *
 * @param       maxval : 最大值(0 < maxval < 2048)
 * @param       samples: 采样点的个数
 * @retval      无
 */
void dac_creat_sin_buf(uint16_t maxval, uint16_t samples)
{
    uint8_t i;
    float inc = (2 * 3.1415962) / samples; /* 计算增量(一个周期DAC_SIN_BUF个点)*/
    float outdata = 0;

    for (i = 0; i < samples; i++)
{
/* 计算以dots个点为周期的每个点的值,放大maxval倍,并偏移到正数区域 */
        outdata = maxval * (1 + sin(inc * i));  
        if (outdata > 4095) outdata = 4095;     /* 上限限定 */ 
        //printf("%f\r\n",outdata);
        g_dac_sin_buf[i] = outdata;
    }
}

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

int main(void)
{
    uint16_t adcx;
    float temp;
    uint8_t t = 0;
    uint8_t key;

    HAL_Init();                         	/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);	/* 设置时钟, 72Mhz */
    delay_init(72);                     	/* 延时初始化 */
    usart_init(115200);                 	/* 串口初始化为115200 */
    usmart_dev.init(72);                	/* 初始化USMART */
    led_init();                         	/* 初始化LED */
    lcd_init();                         	/* 初始化LCD */
    key_init();                         	/* 初始化按键 */
    adc3_init();                        	/* 初始化ADC */

adc3_channel_set(&g_adc3_handle, ADC3_CHY, ADC_CHANNEL_0, 
ADC_SAMPLETIME_1CYCLE_5);

    /* 初始化DAC通道1 DMA波形输出 */
    dac_dma_wave_init(1, (uint32_t)&DAC1->DHR12R1, (uint32_t)g_dac_sin_buf);

    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "DAC DMA Sine WAVE TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:3Khz  KEY1:30Khz", RED);

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

dac_creat_sin_buf(2048, 100);
/* 100Khz触发频率, 100个点, 得到1Khz的正弦波 */
    dac_dma_wave_enable(100, 10 - 1, 72 - 1); 

    while (1)
    {
        t++;
        key = key_scan(0);                                  /* 按键扫描 */

        if (key == KEY0_PRES)                              /* 高采样率 , 约1Khz波形 */
        {
            dac_creat_sin_buf(2048, 100);
/* 300Khz触发频率, 100个点, 得到最高3KHz的正弦波. */
            dac_dma_wave_enable(100, 10 - 1, 24 - 1);       
        }
        else if (key == KEY1_PRES)                        /* 低采样率 , 约1Khz波形 */
        {
            dac_creat_sin_buf(2048, 10);
/* 300Khz触发频率, 10个点, 可以得到最高30KHz的正弦波. */
            dac_dma_wave_enable(10, 10 - 1, 24 - 1);       
        }

        adcx = DAC1->DHR12R1;                             /* 获取DAC1_OUT1的输出状态 */
        lcd_show_xnum(94, 130, adcx, 4, 16, 0, BLUE);/* 显示DAC寄存器值 */

        temp = (float)adcx * (3.3 / 4096);             /* 得到DAC电压值 */
        adcx = temp;
        lcd_show_xnum(94, 150, temp, 1, 16, 0, BLUE);/* 显示电压值整数部分 */

        temp -= adcx;
        temp *= 1000;
        lcd_show_xnum(110, 150, temp, 3, 16, 0X80, BLUE);/* 显示电压值的小数部分 */

        adcx = adc3_get_result_average(ADC3_CHY, 10);/* 得到ADC3通道1的转换结果 */
        temp = (float)adcx * (3.3 / 4096);       /* 得到ADC电压值(adc是12bit的) */
        adcx = temp;
        lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE);     /* 显示电压值整数部分 */

        temp -= adcx;
        temp *= 1000;
        lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE);/* 显示电压值的小数部分 */

        if (t == 40)        /* 定时时间到了 */
        {
            LED0_TOGGLE();  /* LED0闪烁 */
            t = 0;
        }

        delay_ms(5);
    }
}

adc3_init函数初始化ADC3,用于测量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, 10 - 1, 72 - 1);这个语句,第二个形参是自动重装载值,第三个形参是分频系数,那么代入公式,可得:
Tout= ((arr+1)
(psc+1))/Tclk= ((9+1)*(71+1))/ 72000000= 0.00001s
得到定时器的更新事件周期是0.00001秒,即更新事件频率为100KHz,也就得到DAC输出触发频率为100KHz。
再结合总一个正弦波共有100个采样点,就可以得到正弦波的频率为100KHz/100 = 1KHz。
知道了正弦波的频率怎么来的,下面代码中,按下按键KEY0,得到3KHz的正弦波,按下按键KEY1,得到30KHz的正弦波,计算方法都一样的。
dac_dma_sin_set函数可以通过USMART设置正弦波输出参数,方便修改输出频率。
33.4.4 下载验证
下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如图33.4.4.1所示:
在这里插入图片描述

图33.4.4.1 DAC输出正弦波实验测试图
用短路帽将ADC和DAC排针连接后,可以看到ADC VOL的值随着DAC的输出变化而变化,即ADC采集到的值是不停变化的。由于变化太快了,这样看不出采集到值形成什么波形,下面我们借用示波器来进行观察,首先将探头接到DAC的排针上。
没有按下任何按键之前,默认输出1KHz(100个采样点)的正弦波,如下图所示:
在这里插入图片描述

图33.3.4.2 默认DAC输出的的正弦波
当按下KEY0后,DAC输出3KHz(100个采样点)的正弦波,如下图所示:
在这里插入图片描述

图33.3.4.3 按下KEY0,DAC输出的的正弦波
当按下KEY1后,DAC输出30KHz(10个采样点)的正弦波,如下图所示:
在这里插入图片描述

图33.3.4.4 按下KEY1,DAC输出的的正弦波

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/661817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

亚马逊云服务器EC2开通Windows系统实例和远程RDP连接远程桌面

在这篇文章中&#xff0c;我们准备详细的亲测记录开通亚马逊云服务器EC2开通Windows系统。这里需要提醒的是&#xff0c;如果我们是初次免费体验亚马逊云服务器账户的话&#xff0c;是有支持单个云服务器750小时免费&#xff0c;如果我们超过部分是需要支付的&#xff0c;所以如…

探究贴纸滤镜和美颜SDK的技术实现原理

在移动应用高需求的背景下&#xff0c;贴纸滤镜和美颜SDK成为了移动应用中不可或缺的功能之一。那么&#xff0c;这些功能是如何实现的呢&#xff1f;本文将探究贴纸滤镜和美颜SDK的技术实现原理。 一、贴纸滤镜的技术实现原理 贴纸滤镜是一种可以在图片或视频上添加贴纸、滤镜…

1、DuiLib的编译和运行

文章目录 1、原生DuiLib的编译和运行1.1、下载并解压成这个样子1.2、打开.sln解决方案文件1.3、编译成功 2、网易DuiLib编译和运行2.1、下载并解压成这个样子2.2、打开.sln解决方案文件2.3、编译成功 3、腾讯DuiLib编译和运行vs 20173.1、下载并解压成这个样子3.2、打开.sln解决…

个人IP打造全过程:如何打造出属于自己的个人IP?

随着互联网的快速发展&#xff0c;个人IP已经逐渐成为一个热门话题。拥有自己的个人IP可以为你带来更多的机会和收益&#xff0c;让你在竞争激烈的市场中脱颖而出。但是&#xff0c;要想打造自己的个人IP并不是一件容易的事情。本文将为你介绍如何打造属于自己的个人IP。#IP# 一…

Burp Suite 功能详解(渗透测试工具)

一、抓包。 找到 代理--拦截--打开自带浏览器&#xff08;这里也可以自己找扩展设置代理&#xff0c;但还要添加安全证书等等&#xff0c;我觉得太麻烦了&#xff0c;还不如直接使用Burp Suite自带的浏览器抓包&#xff09;。 这是Burp Suite自带网页的页面。 点击开启拦截。 …

VMware Cloud Director Availability 4.6 | 灾难恢复和迁移 | DRaaS

VMware Cloud Director Availability 4.6 | 灾难恢复和迁移 | DRaaS 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-cloud-director-availability-4/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org VMware Cloud …

二维码在隐患排查中的应用:扫码上报,实时通知

正值安全生产月&#xff0c;对企业而言隐患排查是安全管理的重要环节&#xff0c;众多企业设立了安全部门&#xff0c;部门内的相关人员会定期对生产设备、作业场所、作业人员等进行检查&#xff0c;以确保安全隐患的消除&#xff0c;从而预防事故的发生。 传统的隐患排查主要…

2018年全国硕士研究生入学统一考试管理类专业学位联考写作试题

2018年1月真题 四、写作&#xff1a;第56~57小题&#xff0c;共65分。其中论证有效性分析30 分&#xff0c;论说文35分。 56.论证有效性分析&#xff1a; 分析下述论证中存在的缺陷和漏洞&#xff0c;选择若干要点&#xff0c;写一篇600字左右的文章&#xff0c;对该论证的有…

掌握Spring Cloud:打造高效可靠的微服务生态系统

1、SpringCloud概述 Spring Cloud是一个用于构建分布式系统的开源框架&#xff0c;它提供了一系列的组件和工具&#xff0c;用于实现微服务架构中的各项核心功能。本文将重点介绍Spring Cloud中的关键组件&#xff0c;并详细探讨它们的功能和作用。 网关&#xff1a;Zuul/Gat…

自动化测试-selenium基础操作

元素操作 方法&#xff1a; 1、.send_keys() # 输入方法 2、.click() # 点击方法 3、.clear() # 清空方法# 导包 from time import sleep from selenium import webdriver# 实例化浏览器 driver webdriver.Chrome()# 打开网址 driver.get(https://www.baidu.com/)# 需求 e…

HTTP项目常见状态码笔记(200,302,400,403,404,405,500...),

目录 认识 "状态码" (status code) 出现200 403 Forbidden 出现404 404 Not Found 出现 405 Method Not Allowed 出现 500 Internal Server Error 504 Gateway Timeout 302 Move temporarily 301 Moved Permanently 出现 "空白页面" 出现 &qu…

制作内网离线百度地图

下载离线地图瓦片 通过百度ak&#xff08;没有的可以去百度官网自行申请下载&#xff09;&#xff0c;拉取下列git 项目&#xff0c;可以免费下载离线瓦片 https://gitee.com/jinqianwang/baidu-offline-map-download 目录结构&#xff1a; 使用步骤&#xff1a; 第一步&am…

Midjourney绘画常用聊天界面介绍

刚开始使用MJ (Midjourney) 的小伙伴可能对这个复杂的界面有点迷糊&#xff0c;网站也无法翻译。 实不相瞒刚开始的时候我也一脸懵逼&#xff0c;对于小白来说启动门槛很高&#xff0c;但是认识之后就一点也不复杂。 接下来我们分三步认识Midjourney的界面: 第一步: 打开Mid…

基于Java+Swing+Mysql实现酒店管理系统

基于JavaSwingMysql实现酒店管理系统 一、系统介绍二、功能展示1.用户登陆2.首页3.开房4.退房5.房间信息6、顾客信息 三、数据库四、其它1.其他系统实现2.获取源码 一、系统介绍 系统功能&#xff1a;用户登陆、验证码验证、开房、退房、房间信息、顾客信息 运行环境&#xf…

【im群发推送软件】(Apple Push Notification service):APNs 是苹果提供的推送服务

iMessage 推送需要您对苹果的推送服务和相关开发技术有一定的了解&#xff0c;并熟悉应用程序开发的相关知识。以下是一些关键的方面和步骤&#xff0c;供您参考&#xff1a; 开发者账号和证书&#xff1a;您需要注册苹果开发者账号&#xff0c;并获取开发者证书用于推送服务。…

PEFAT:通过伪损失估计和特征对抗训练增强半监督医学图像分类

文章目录 PEFAT: Boosting Semi-supervised Medical Image Classification via Pseudo-loss Estimation and Feature Adversarial Training摘要本文方法Feature Adversarial Training 实验结果 PEFAT: Boosting Semi-supervised Medical Image Classification via Pseudo-loss …

PIQN:Parallel Instance Query Network for Named Entity Recognition

原文链接&#xff1a;https://aclanthology.org/2022.acl-long.67.pdf ACL 2022 介绍 问题 将命名实体识别任务作为阅读理解任务&#xff08;MRC&#xff1a;machine reading comprehensive&#xff09;来做&#xff0c;存在3个问题&#xff1a; 1&#xff09;一类问题只能提取…

腾讯云服务器可用区什么意思?怎么选择合适?

腾讯云服务器可用区是什么意思&#xff1f;可用区是指在同一个地域内电力和网络相互独立的区域&#xff0c;可用区可以做到故障隔离&#xff0c;所以可用区存在的意义在于构建高可用、高容灾应用&#xff0c;将应用部署在不同可用区内&#xff0c;一个可用区发生故障&#xff0…

Cisco MPLS VPN Option C1

一、拓扑 AR1和AR8是一家公司两个站点&#xff0c;现在通过MPLS VPN Option C1实现跨域互联 使用思科设备模拟 AR1到AR8各有一个loopback0接口分别是1.1.1.1 2.2.2.2 二、配置思路 1、先配置AS内底层网络 AR2 AR3 AR4配置eigrp实现互联 AR5 AR6 AR7同样方法 2、AS 10…