《DMG474电机控制专题教程V1.1 》第4章 电机控制相关的STM32外设(4.5)

第4章 电机控制相关的STM32外设(4.5)


       本小节我们将介绍STM32G474的DAC(Digital-to-analog converters,数模转换器)功能。我们通过三个实验来学习DAC,分别是DAC输出实验、DAC输出三角波实验和DAC输出正弦波实验。

       本小节分为如下几个部分:

       4.5.1 DAC简介

       4.5.2 DAC输出实验

       4.5.3 DAC输出三角波实验

       4.5.4 DAC输出正弦波实验


        4.5.1 DAC简介

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

       STM32的DAC模块主要特点有:

       ①  最多4个DAC转换器:每个转换器最多2个输出通道

       ② 8位或者12位单调输出

       ③ 12位模式下数据左对齐或者右对齐

       ④ 同步更新功能

       ⑤ 噪声波形生成

       ⑥ 三角波形生成

       ⑦ 双DAC双通道同时或者分别转换

       ⑧ 每个通道都有DMA功能

       DAC通道框图如图4.5.1.1所示:


图4.5.1.1 DAC通道框图


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


表4.5.1.1 DAC输入/输出引脚


       从图4.5.1.1可以看出,DAC输出是受DORx(x=1/2,下同)寄存器直接控制的,但是我们不能直接往DORx寄存器写入数据,而是通过DHRx间接的传给DORx寄存器,实现对DAC输出的控制。

       前面我们提到,STM32G474的DAC支持8/12位模式,8位模式的时候是固定的右对齐的,而12位模式又可以设置左对齐/右对齐。DAC单通道模式下的数据寄存器对齐方式,总共有3种情况,如下图所示:


图4.5.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 双通道(可用时),也有三种可能的方式,如下图所示:


图4.5.1.3 DAC双通道模式下的数据寄存器对齐方式


       ① 8位数据右对齐:用户将DAC通道1的数据写入DAC_DHR8RD[7:0]位(实际存入DAC_DHR8R1 [7:0]位),将DAC通道2的数据写入DAC_DHR8RD[15:8]位(实际存入DAC_DHR8R2 [7:0]位)。

       ② 12位数据左对齐:用户将DAC通道1的数据写入DAC_DHR12LD [15:4]位(实际存入DAC_DHR12L1寄存器的DACC1DHR[11:0]位),将DAC通道2的数据写入DAC_DHR12LD [31:20]位(实际存入DAC_DHR12L2寄存器的DACC2DHR[11:0]位)。

       ③ 12位数据右对齐:用户将DAC通道1的数据写入DAC_DHR12RD [11:0]位(实际存入DAC_DHR12R1寄存器的DACC1DHR[11:0]位),将DAC通道2的数据写入DAC_DHR12RD [27:16]位(实际存入DAC_DHR12R2寄存器的DACC2DHR[11:0]位)。

       DAC可以通过软件或者硬件触发转换,通过配置TENx控制位来决定。

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

       不使用硬件触发(TEN=0),其转换的时间框图如图4.5.1.4所示:


图4.5.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个触发事件请查看《STM32G4xx参考手册_V7(英文版).pdf》第725页表 183。

       每个DAC通道都有DMA功能,两个DMA通道分别用于处理两个DAC通道的DMA请求。如果DMAENx位置1时,如果发生外部触发(而不是软件触发),就会产生一个DMA请求,然后DAC_DHRx寄存器的数据被转移到DAC_DORx寄存器。


        4.5.2 DAC输出实验

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


       4.5.2.1 DAC寄存器

       下面,我们介绍要实现DAC的通道1输出,需要用到的一些DAC寄存器。

       l DACx控制寄存器(DACx_CR)

       DACx控制寄存器描述如图4.5.2.1.1所示:


图4.5.2.1.1 DACx_CR寄存器


       DACx_CR寄存器的低16位用于控制通道1,高16位用于控制通道2,下面介绍本实验需要设置的一些位:

       EN1位用于使能/禁止DAC通道1,本实验用到ADC1通道1,所以该位EN1置1。

       TEN1位用于DAC通道1的触发使能,本实验不使用硬件触发,所以该位置0。写入DHR1的值会在1个APB1周期后传送到DOR1,然后输出到PA4口上。

       TSEL1[3:0]位,用于选择DAC通道1的触发方式,本实验使用软件触发,所以该位域置0即可。

       WAVE1[1:0]位,用于控制DAC通道1的噪声/波形输出功能,我们这里没用到波形发生器,所以默认设置为00,不使能噪声/波形输出。

       MAMP[3:0]位,是屏蔽/幅值选择器,用来在噪声生成模式下选择屏蔽位,在三角波生成模式下选择波形的幅值。本实验没有用到波形发生器,所以设置为0即可。

       DMAEN1位,用于DAC通道1的DMA使能,本实验没有用到DMA功能,所以设置为0。

       l DACx模式控制寄存器(DACx_ MCR)

       DACx模式控制寄存器描述如图4.5.2.1.2所示:


图4.5.2.1.2 DACx_ MCR寄存器


       该寄存器我们只关心MODE1[2:0],这三个位用于设置DAC通道1的工作模式,本实验使用普通模式,且使用输出缓冲,设置MODE1[2:0]=0即可。MODE2[2:0] 设置通道2的工作模式,本实验没用到

       l DACx通道1 12位右对齐数据保持寄存器(DACx_ DHR12R1)

       DACx通道1 12位右对齐数据保持寄存器描述如图4.5.2.1.3所示:


图4.5.2.1.3 DACx_ DHR12R1寄存器


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


       4.5.2.2 硬件设计


       1. 例程功能

       使用KEY0/KEY1两个按键,控制STM32内部DAC的通道1输出电压大小,然后通过ADC1的通道3采集DAC输出的电压,在LCD模块上面显示ADC采集到的电压值以及DAC的设定输出电压值等信息。LED0闪烁,提示程序运行。


       2. 硬件资源


       1)LED灯

              LED0 – PE0

       2)串口1(PB6/PB7连接在板载USB转串口芯片CH340上面)

       3)正点原子 1.3寸TFTLCD模块

       4)独立按键 :

              KEY0 – PE12

              KEY1 – PE13

       5)ADC1 :

              通道8 – PC2

       6)DAC1 :

              通道1 - PA4


       3. 原理图

       我们只需要通过杜邦线连接PC2和PA4,就可以使得ADC1通道8和DAC1通道1连接起来。


       4.5.2.3 程序设计


       4.5.2.3.1 DAC的HAL库驱动

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

       1. HAL_DAC_Init函数

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

HAL_StatusTypeDef HAL_DAC_Init(DAC_HandleTypeDef *hdac);

       l 函数描述:

       用于初始化DAC。

       l 函数形参:

       形参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做好准备。

       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。

       l 注意事项:

       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);

       l 函数描述:

       该函数用来配置DAC通道的触发类型以及输出缓冲。

       l 函数形参:

       形参1是DAC_HandleTypeDef结构体类型指针变量。

       形参2是DAC_ChannelConfTypeDef结构体类型指针变量,其定义如下:

typedef struct
{
  … …         /* 省略部分结构体 */
  uint32_t DAC_SampleAndHold;          /* 设置是否使能低功耗模式,即采样和保持模式 */
  uint32_t DAC_Trigger;               /* DAC触发源的选择 */
  uint32_t DAC_OutputBuffer;           /* 启用或者禁用DAC通道输出缓冲区 */
  uint32_t DAC_ConnectOnChipPeripheral;/* 指定DAC输出是否连接到片上外设 */
  uint32_t DAC_UserTrimming;        /* 设置DAC的校准方式*/
  uint32_t DAC_TrimmingValue;       /* 设置用户校准模式的偏移值 */
  DAC_SampleAndHoldConfTypeDef  DAC_SampleAndHoldConfig;/* 设置采样保持具体参数*/
} DAC_ChannelConfTypeDef;

       形参3用于选择要配置的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。

       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。

       3. HAL_DAC_Start函数

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

HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel);

       l 函数描述:

       使能启动DAC转换通道。

       l 函数形参:

       形参1是DAC_HandleTypeDef结构体类型指针变量。

       形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。

       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。

       4. HAL_DAC_SetValue函数

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

HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel,
uint32_t Alignment, uint32_t Data);

       l 函数描述:

       配置DAC的通道输出值。

       l 函数形参:

       形参1是DAC_HandleTypeDef结构体类型指针变量。

       形参2用于选择要输出的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。

       形参3用于指定数据对齐方式。

       形参4设置要加载到选定数据保存寄存器中的数据。

       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。

       5. HAL_DAC_GetValue函数

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

uint32_t HAL_DAC_GetValue(DAC_HandleTypeDef *hdac, uint32_t Channel);

       l 函数描述:

       获取所选DAC通道的最后一个数据输出值。

       l 函数形参:

       形参1是DAC_HandleTypeDef结构体类型指针变量。

       形参2用于选择要读取的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。

       l 函数返回值:

       获取到的输出值。


       DAC输出配置步骤

       1)开启DAC和输出通道的GPIO时钟,配置该IO口的模拟输出功能

       首先开启DAC和GPIO的时钟,然后配置GPIO为模拟模式。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下:

__HAL_RCC_DAC_CLK_ENABLE ();         /* 使能DAC1时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();            /* 开启GPIOA时钟 */

       IO口模拟输出功能是通过函数HAL_GPIO_Init来配置的。

       2)初始化DAC

       通过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的输出值。


       4.5.2.3.2 程序流程图


图4.5.2.3.2.1 DAC输出实验程序流程图


       4.5.2.3.3 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。另外我们使用ADC1来测量DAC输出的电压是否准确,所以还需要用到单通道ADC采集实验里面的adc.c和adc.h文件,关于ADC的程序这里就不再讲解。

       dac.h文件只有一些函数声明,下面直接开始介绍dac.c的程序,首先是DAC初始化函数。

/**
 * @brief       DAC初始化函数
 * @param       outx: 要初始化的通道. 1,通道1; 2,通道2
 * @retval      无
 */
void dac_init(uint8_t outx)
{
    GPIO_InitTypeDef gpio_init_struct;
    DAC_ChannelConfTypeDef dac_ch_conf = {0};
    
    __HAL_RCC_DAC1_CLK_ENABLE();                /* 使能DAC1的时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /* 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; 
    gpio_init_struct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);
 
    g_dac_handle.Instance = DAC1;
    HAL_DAC_Init(&g_dac_handle);            /* 初始化DAC */
 
    dac_ch_conf.DAC_HighFrequency =  DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC;
    dac_ch_conf.DAC_DMADoubleDataMode = DISABLE;
    dac_ch_conf.DAC_SignedFormat = DISABLE;
    dac_ch_conf.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
    dac_ch_conf.DAC_Trigger2 = DAC_TRIGGER_NONE;
dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE; /* 不使用触发功能 */
/* DAC1输出缓冲关闭 */
    dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;        
    dac_ch_conf.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_EXTERNAL;
    dac_ch_conf.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
    
    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通道2 */
            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设置要输出的电压值,设置的范围:0~3300,代表0~3.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(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    led_init();                                 /* 初始化LED */
    lcd_init();                                 /* 初始化LCD */
    key_init();                                 /* 初始化按键 */
    adc_init();                                 /* 初始化ADC */
    dac_init(1);                                /* 初始化DAC1_OUT1通道 */
    
    lcd_show_string(10, 10, 140, 32, 32, "STM32", RED);
    lcd_show_string(10, 42, 140, 24, 24, "DAC TEST", RED);
    lcd_show_string(10, 66, 140, 24, 24, "ATOM@ALIENTEK", RED);
    lcd_show_string(10, 90, 200, 16, 16, "KEY1:+  KEY0:-", RED);
    
    lcd_show_string(10, 110, 200, 16, 16, "DAC VAL:", BLUE);
    lcd_show_string(10, 130, 200, 16, 16, "DAC VOL:0.000V", BLUE);
    lcd_show_string(10, 150, 200, 16, 16, "ADC VOL:0.000V", BLUE);
    
    while (1)
    {
        t++;
        key = key_scan(0);               /* 按键扫描 */
 
        if (key == KEY1_PRES)
        {
            if (dacval < 4000)
            {
                dacval += 200;
            }
/* 输出增大200 */
          HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 
dacval);    
        }
        else if (key == KEY0_PRES)
        {
            if (dacval > 200)
            {
                dacval -= 200;
            }
            else 
            {
                dacval = 0;
            }
            HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 
dacval);             /* 输出减少200 */
        }
/* KEY0/KEY1按下了,或者定时时间到了 */
        if (t == 10 || key == KEY0_PRES || key == KEY1_PRES)        
        {
        /* 读取前面设置DAC1_OUT1的值 */
            adcx = HAL_DAC_GetValue(&g_dac_handle, DAC_CHANNEL_1);  
            /* 显示DAC寄存器值 */
            lcd_show_xnum(74, 110, adcx, 4, 16, 0, BLUE);           
            
            temp = (float)adcx * (3.3 / 4096);      /* 得到DAC电压值 */
            adcx = temp;
            lcd_show_xnum(74, 130, temp, 1, 16, 0, BLUE);  /* 显示电压值整数部分*/
            
            temp -= adcx;
            temp *= 1000;
            /* 显示电压值的小数部分 */
            lcd_show_xnum(90, 130, temp, 3, 16, 0X80, BLUE);        
            /* 得到ADC1通道3的转换结果 */
            adcx = adc_get_result_average(ADC_ADCX_CHY, 20);        
            temp = (float)adcx * (3.3 / 4096);          /* 得到ADC电压值 */
            adcx = temp;
            lcd_show_xnum(74, 150, temp, 1, 16, 0, BLUE); /* 显示电压值整数部分*/
            
            temp -= adcx;
            temp *= 1000;
/* 显示电压值的小数部分 */
            lcd_show_xnum(90, 150, temp, 3, 16, 0X80, BLUE);        
            
            LED0_TOGGLE();                                /* LED0闪烁 */
            t = 0;
        }
 
        delay_ms(10);
    }
}

       此部分代码,我们通过KEY0和KEY1来实现对DAC输出的幅值控制。按下KEY1增加,按KEY0减小。同时在LCD上面显示DHR12R1寄存器的值、DAC设置输出电压以及ADC采集到的DAC输出电压。


       4.5.2.4 下载验证

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


图4.5.2.4.1 DAC输出实验测试图


       验证试验前记得先通过杜邦线连接PC2和PA4,然后我们可以通过按KEY1按键,增加DAC输出的电压,这时ADC采集到的电压也会增大,通过按KEY0减小DAC输出的电压,这时ADC采集到的电压也会减小。


        4.5.3 DAC输出三角波实验

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


       4.5.3.1 DAC寄存器

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


       4.5.3.2 硬件设计


       1. 例程功能

       使用DAC输出三角波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种三角波,需要通过示波器接PA4进行观察。LED0闪烁,提示程序运行。


       2. 硬件资源

       1)LED灯

              LED0 – PE0

       2)串口1(PB6/PB7连接在板载USB转串口芯片CH340上面)

       3)正点原子 1.3寸TFTLCD模块

       4)独立按键 :

              KEY0 – PE12

              KEY1 – PE13

       5)DAC1 :

              通道1 - PA4


       3. 原理图

       我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。


       4.5.3.3 程序设计

       本实验用到的DAC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍DAC输出三角波的配置步骤。


       DAC输出三角波配置步骤

       1)开启DACx和输出通道的GPIO时钟,配置该IO口的模拟输出功能

       首先开启DACx和GPIO的时钟,然后配置GPIO为模拟模式。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下:

__HAL_RCC_DAC1_CLK_ENABLE ();        /* 使能DAC1时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();            /* 开启GPIOA时钟 */

       IO口模拟输出功能是通过函数HAL_GPIO_Init来配置的。

       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函数用于控制输出三角波。


       4.5.3.3.1 程序流程图


图4.5.3.3.1.1 DAC输出三角波实验程序流程图


       4.5.3.3.2 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。本实验代码在DAC输出实验代码后进行追加。

       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: 采样点的个数, 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(samples > ((maxval + 1) * 2))return ; /* 数据不合法 */
        
    incval = (maxval + 1) / (samples / 2);  /* 计算递增量 */
    
    for(j = 0; j < n; j++)
    { 
        Curval = 0;        /* 先输出0 */
        HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,Curval);
        for(i = 0; i < (samples / 2); i++)  /* 输出上升沿 */
        {
            Curval  +=  incval;     /* 新的输出值 */
            HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,
Curval);
            delay_us(dt);
        } 
        for(i = 0; i < (samples / 2); i++)  /* 输出下降沿 */
        {
            Curval  -=  incval;     /* 新的输出值 */
            HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,
Curval);
            delay_us(dt);
        }
    }
}

       该函数用于设置DAC通道1输出三角波,输出频率 ≈ 1000 / (dt * samples) Khz,形参意义在源码已经有详细注释。该函数中,我们使用HAL_DAC_SetValue函数来设置DAC的输出值,这样得到的三角波在示波器上可以看到。如果有跳动现象(不平稳),是正常的,因为调用函数,计算等都需要时间,这样就会导致输出的波形是不太稳定的。越高性能的MCU,得到的波形会越稳定。除此之外,用HAL库函数操作的效率没有直接操作寄存器高,如果对波形质量要求较高,可以直接操作DHR12R1寄存器,这样得到的波形会相对稳定些。

       最后在main.c里面编写如下代码:

int main(void)
{
    uint8_t t = 0;
    uint8_t key;
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    led_init();                                 /* 初始化LED */
    lcd_init();                                 /* 初始化LCD */
    key_init();                                 /* 初始化按键 */
    dac_init(1);                                /* 初始化DAC1_OUT1通道 */
    
    lcd_show_string(10, 10, 140, 32, 32, "STM32", RED);
    lcd_show_string(10, 42, 140, 24, 24, "DAC Triangular WAVE TEST", RED);
    lcd_show_string(10, 66, 140, 24, 24, "ATOM@ALIENTEK", RED);
    lcd_show_string(10, 100, 200, 16, 16, "KEY0:Wave1  KEY1:Wave2", RED);
    lcd_show_string(10, 120, 200, 16, 16, "DAC None", BLUE);       
    
    while (1)
    {
        t++;
        key = key_scan(0);                    /* 按键扫描 */
 
        if (key == KEY0_PRES)          /* 高采样率 , 约88.5Hz波形 */
        {
            lcd_show_string(10, 120, 200, 16, 16, "DAC Wave1 ", BLUE);
/* 幅值4095, 采样点间隔5us, 2000个采样点, 100个波形 */
            dac_triangular_wave(4095, 5, 2000, 100);                        
            lcd_show_string(10, 120, 200, 16, 16, "DAC None  ", BLUE);
        }
        else if (key == KEY1_PRES)            /* 低采样率 , 约100Hz波形 */
        {
            lcd_show_string(10, 120, 200, 16, 16, "DAC Wave2 ", BLUE);
/* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */
            dac_triangular_wave(4095, 500, 20, 100);                        
            lcd_show_string(10, 120, 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,由于三角波1采样点之间时延比较小,频率会偏小。


       4.5.3.4 下载验证

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


图4.5.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在示波器的显示情况如下图所示:


图4.5.3.4.2 DAC输出的三角波1


图4.5.3.4.3 DAC输出的三角波2


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


        4.5.4 DAC输出正弦波实验

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


       4.5.4.1 DAC寄存器

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


       4.5.4.2 硬件设计


       1. 例程功能

       使用DAC1的通道1输出正弦波,需要通过示波器接PA4进行观察。TFTLCD显示实验名称。LED0闪烁,提示程序运行。


       2. 硬件资源


       1)LED灯

              LED0 – PE0

       2)串口1(PB6/PB7连接在板载USB转串口芯片CH340上面)

       3)正点原子 1.3寸TFTLCD模块

       4)定时器2

       5)DAC1 :

              通道1 - PA4

       6)DMA(DMA1_Channel2)


       3. 原理图

       我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。


       4.5.4.3 程序设计


       4.5.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);

       l 函数描述:

       用于启动DAC使用DMA的方式。

       l 函数形参:

       形参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位右对齐)三种方式。

       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。

       2. HAL_TIMEx_MasterConfigSynchronization函数

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

HAL_StatusTypeDef HAL_TIMEx_MasterConfigSynchronization(
TIM_HandleTypeDef *htim, TIM_MasterConfigTypeDef *sMasterConfig);

       l 函数描述:

       用于配置主模式下的定时器触发输出选择。

       l 函数形参:

       形参1是TIM_HandleTypeDef结构体类型指针变量。 

       形参2是TIM_MasterConfigTypeDef结构体类型指针变量,用于配置定时器工作在主/从模式,以及触发输出(TRGO和TRGO2)的选择。

       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。


       DAC输出正弦波配置步骤

       1)开启DACx、DMA和输出通道的GPIO时钟,配置该IO口的模拟输出功能

       首先开启DACx、DMA和GPIO的时钟,然后配置GPIO为模拟模式。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下:

__HAL_RCC_DAC1_CLK_ENABLE ();        /* 使能DAC时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();            /* 开启GPIOA时钟 */
__HAL_RCC_DMAMUX1_CLK_ENABLE()    /* 开启DMAMUX1时钟 */
__HAL_RCC_DMA1_CLK_ENABLE ();            /* 开启DMA1时钟 */

       IO口模拟输出功能是通过函数HAL_GPIO_Init来配置的。

       2)配置DMA并关联DAC

       通过HAL_DMA_Init函数初始化DMA,包括配置通道,外设地址,存储器地址,传输数据量等。

       HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。这个宏定义为__HAL_LINKDMA。

       3)初始化DACx

       通过HAL_DAC_Init函数来设置需要初始化的DAC。该函数并没有设置任何DAC相关寄存器,也就是说没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC。

       注意:该函数会调用HAL_DAC_MspInit函数来存放DAC和对应通道的IO时钟使能和初始化IO等代码。

       4)配置定时器控制触发DAC

       通过HAL_TIM_Base_Init函数设置定时器溢出频率。 

       通过HAL_TIMEx_MasterConfigSynchronization函数配置定时器溢出事件用做触发器。

       通过HAL_TIM_Base_Start函数启动计数。

       5)产生正弦波序列

       通过dac_creat_sin_buf函数产生一组正弦波序列,其本质上就是正弦波曲线上的点。

       6)配置DAC通道并开启DMA传输

       通过HAL_DAC_ConfigChannel函数来设置配置DAC的通道,根据需求设置触发类型以及输出缓冲等。

       再通过HAL_DAC_Start_DMA函数启动DMA传输以及DAC输出。


       4.5.4.3.2 程序流程图


图4.5.4.3.2.1 DAC输出正弦波实验程序流程图


       4.5.4.3.3 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。

       dac.h文件只有一些声明,下面直接开始介绍dac.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 outdata = 0;
    
    if( maxval <= (samples/2) )return ;        /* 数据不合法 */
    /*
     * 正弦波最小正周期为2π,约等于2 * 3.1415926
     * 曲线上相邻的两个点在x轴上的间隔 = 2 * 3.1415926 / 采样点数量
     * DAC无法输出负电压,所以需要将曲线向上偏移一个峰值的量,让整个曲线都落在正数区域
     */
    float inc = (2 * 3.1415926) / samples;  /* 计算相邻两个点的x轴间隔 */
 
    for (i = 0; i < samples; i++)    /* 连续打samples个点 */
    {
        /*
         * 正弦波函数解析式:y = Asin(wx + φ)+ b
         * 计算每个点的y值,将峰值放大maxval倍,并将曲线向上偏移maxval到正数区域
         */
        outdata = maxval * sin(inc * i) + maxval;
        
        if (outdata > 4095)
        {
            outdata = 4095;      /* y值上限限定 */
        }
        g_dac_sin_buf[i] = outdata;
    }
}

       该函数用于产生正弦波序列,即正弦波曲线上的各个采样点,各个点最终控制的是DAC_DHR12R1寄存器的值。在DAC输出频率固定、波形周期一定的情况下,采样点越多,实际的曲线越接近于正弦波,但是波形的频率也会越小。

       产生正弦波序列函数的实现思路请看注释,值得注意的是,DAC是无法输出负电压的,所以我们需要将正弦波的曲线向上偏移一个峰值maxval的量,让整个曲线都落在正数区域。

       下面介绍DAC DMA初始化函数,其定义如下:

/**
 * @brief       DAC初始化函数
 * @param       outx: 要初始化的通道. 1,通道1; 2,通道2
 * @retval      无
 */
void dac_init(uint8_t outx,uint16_t cndtr)
{
    GPIO_InitTypeDef gpio_init_struct;
    DAC_ChannelConfTypeDef dac_ch_conf = {0};
    
__HAL_RCC_DAC1_CLK_ENABLE();                  /* 使能DAC1的时钟 */
/* 使能DAC OUT1/2的IO口时钟(都在PA口,PA4/PA5) */
    __HAL_RCC_GPIOA_CLK_ENABLE();                
    __HAL_RCC_DMAMUX1_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();
    /* 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; 
    gpio_init_struct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);
 
    /* DMA配置 */
    g_dma_dac_handle.Instance = DAC_ADCX_DMASx;      /* 设置DMA通道 */
g_dma_dac_handle.Init.Request= DAC_ADCX_DMASx_REQUEST; /* 设置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;
/* 外设数据长度:32位 */
g_dma_dac_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;        
/* 存储器数据长度: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_LOW;   /* 低优先级 */
    HAL_DMA_Init(&g_dma_dac_handle);                      /* 初始化DMA */
     /* 把DAC和DMA关联 */
    __HAL_LINKDMA(&g_dac_dma_handle,DMA_Handle1,g_dma_dac_handle);         
    
    g_dac_dma_handle.Instance = DAC1;
    HAL_DAC_Init(&g_dac_dma_handle);                     /* 初始化DAC */
    
dac_ch_conf.DAC_HighFrequency = 
DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC;
    dac_ch_conf.DAC_DMADoubleDataMode = DISABLE;
    dac_ch_conf.DAC_SignedFormat = DISABLE;
    dac_ch_conf.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
dac_ch_conf.DAC_Trigger2 = DAC_TRIGGER_T6_TRGO;
/* 使用定时器2的TRGO事件触发DAC转换 */
dac_ch_conf.DAC_Trigger = DAC_TRIGGER_T2_TRGO;                  
/* DAC1输出缓冲使能 */        
    dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
    dac_ch_conf.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_EXTERNAL;
    dac_ch_conf.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
 
    dac_creat_sin_buf(2048, cndtr);        /* 产生正弦波序列,即画点 */
    
    switch(outx)
    {
        case 1:
        /* DAC通道1配置 */    HAL_DAC_ConfigChannel(&g_dac_dma_handle,&dac_ch_conf,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); /* 开启DAC通道1 */
            break;
        case 2:
        /* DAC通道2配置 */    HAL_DAC_ConfigChannel(&g_dac_dma_handle,&dac_ch_conf,DAC_CHANNEL_2);    
HAL_DAC_Start_DMA(&g_dac_dma_handle,DAC_CHANNEL_2,
(uint32_t*)g_dac_sin_buf,cndtr,DAC_ALIGN_12B_R); /* 开启DAC通道2 */
            break;
        default:break;
    }
}

       该函数用于初始化DAC用DMA的方式输出正弦波,我们采用定时器2触发DAC进行转换输出。本函数用到的API函数起前面都介绍过,请结合前面介绍过的相关内容来理解源码。值得注意的是,这里调用了dac_creat_sin_buf函数来产生正弦波序列,采样点的个数通过入口参数cndtr传进来。

       接着看定时器相关的gtim.c文件,我们只关注通用定时器的初始化函数,其定义如下:

/**
 * @brief      通用定时器TIMX初始化函数
 * @param      arr: 自动重装值。
 * @param      psc: 时钟预分频数
 * @retval     无
 */
void gtim_timx_int_init(uint16_t arr, uint16_t psc)
{
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    GTIM_TIMX_INT_CLK_ENABLE();        /* 使能TIMx时钟 */
 
    g_timx_handle.Instance = GTIM_TIMX_INT;    /* 通用定时器x */
    g_timx_handle.Init.Prescaler = psc;     /* 预分频系数 */
    g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
    g_timx_handle.Init.Period = arr;      /* 自动装载值 */
    HAL_TIM_Base_Init(&g_timx_handle);
 
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; /* 更新事件用于触发 */
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&g_timx_handle, &sMasterConfig);
    HAL_TIM_Base_Start(&g_timx_handle);     /* 使能定时器x */
}

       该函数调用了HAL_TIM_Base_Init函数对定时器2的基本参数进行了初始化,并通过HAL_TIMEx_MasterConfigSynchronization函数配置TIM2,将其更新事件作为DAC输出的触发源,然后开启定时器计数。

       最后在main.c里面编写如下代码: 

int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    led_init();                                 /* 初始化LED */
    lcd_init();                                 /* 初始化LCD */
    gtim_timx_int_init(10 - 1, 170 - 1);        /* 初始化定时器,定时触发DAC */
    dac_init(1, 100);   /* 初始化DAC1_OUT1通道,100个点,正弦波频率1k Hz */
    lcd_show_string(10, 10, 140, 24, 24, "STM32", RED);
    lcd_show_string(10, 42, 239, 16, 16, "DAC DMA Sine WAVE TEST", RED);
    lcd_show_string(10, 66, 140, 16, 16, "ATOM@ALIENTEK", RED);     
    
    while (1)
    {
        LED0_TOGGLE();                          /* 每1000ms,翻转一次LED0 */
        delay_ms(1000);
    }
}

       gtim_timx_int_init函数初始化定时器2,其计数频率为1MHz,计数溢出(发生一次更新事件)的频率为100K Hz,不记得怎么计算的朋友,请回顾基本定时器的相关内容,这里直接把公式列出: 

Tout= ((arr+1)*(psc+1))/Tclk

       看到dac_init(1,100);这个语句,第一个形参是DAC的通道,本实验使用通道1;第二个形参是采集点的个数,本实验默认的采集点个数为100。可以得到正弦波的频率为100KHz/100 = 1KHz。


       4.5 . 4.4   下载验证

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


图4.5.4.4.1 DAC输出三角波实验测试图


       接下来我们借助示波器来观察波形,注意要将探头接到PA4的排针上(注意:开发板需要和示波器共地)。实际输出的1KHz(100个采样点)正弦波如下图所示:


图4.5.4.4.2 DAC输出的1K Hz正弦波


请使用浏览器的分享功能分享到微信等