STM32-17-DAC

news2025/3/1 21:21:20

STM32-01-认识单片机
STM32-02-基础知识
STM32-03-HAL库
STM32-04-时钟树
STM32-05-SYSTEM文件夹
STM32-06-GPIO
STM32-07-外部中断
STM32-08-串口
STM32-09-IWDG和WWDG
STM32-10-定时器
STM32-11-电容触摸按键
STM32-12-OLED模块
STM32-13-MPU
STM32-14-FSMC_LCD
STM32-15-DMA
STM32-16-ADC

文章目录

  • STM32-17-DAC
    • 1. DAC简介
    • 2. DAC工作原理
    • 3. DAC输出实验
    • 4. DAC输出三角波实验
    • 5. DAC输出正弦波实验
    • 6. PWM DAC实验

STM32-17-DAC

1. DAC简介

  • 什么是DAC?

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

  • 主要特点:

    • 2个DAC转换器,每个转换器对应1个输出通道
    • 8位或12位单调输出
    • 12位模式下数据左对齐或右对齐
    • 同步更新功能
    • 噪声/三角波形生成
    • 双DAC双通道同时或分别转换
    • 每个通道都有DMA功能
  • ADC与DAC的关系
    在这里插入图片描述

  • DAC的特性参数

    • 分辨率:表示模拟电压的最小增量,常用二进制位数表示,比如8/12位等

    • 建立时间:表示一个数字量转换为稳定模拟信号所需的时间

    • 精度:转换器实际特性曲线与理想特性曲线之间的最大偏差

      误差源:比例系统误差、失调误差、非线性误差

      原因:元件参数误差、基准电压不稳定、运算放大器零漂等

2. DAC工作原理

  • DAC框图
    在这里插入图片描述

引脚信息:
在这里插入图片描述

参考电压:
在这里插入图片描述

DAC数据格式:
在这里插入图片描述

触发源:
在这里插入图片描述

关闭触发时(TEN=0)的转换时序图:
在这里插入图片描述

DMA请求:
在这里插入图片描述

DAC输出电压:
在这里插入图片描述

3. DAC输出实验

  • 功能:

    通过DAC1通道1(PA4)输出预设电压,然后由ADC1通道1 (PA1) 采集,最后显示ADC转换的数字量及换算后的电压值。

  • 相关寄存器

    DAC控制寄存器(DAC_CR)
    在这里插入图片描述

    ​ DAC_CR寄存器的低16位用于控制通道1,高16位用于控制通道2。

    DAC通道1 12位右对齐数据保持寄存器(DAC_DHR12R1)
    在这里插入图片描述

  • DAC初始化函数

    void dac_init(void)
    {
        DAC_ChannelConfTypeDef dac_ch_conf;
        
        g_dac_handle.Instance = DAC;
        HAL_DAC_Init(&g_dac_handle);
        
        dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;
        dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
        HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);
        
        HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_1);
    }
    

    初始化DAC句柄

    g_dac_handle.Instance = DAC;
    HAL_DAC_Init(&g_dac_handle);
    
    • g_dac_handle.Instance = DAC;:将DAC外设的基地址赋给全局DAC句柄的实例成员g_dac_handle.Instance
    • HAL_DAC_Init(&g_dac_handle);:调用HAL库的初始化函数HAL_DAC_Init,使用DAC句柄g_dac_handle进行初始化。这一步设置了DAC的时钟和一些基本的硬件配置。

    配置DAC通道

    dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;
    dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
    HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);
    
    • dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;:设置DAC的触发方式为无触发,即DAC输出不依赖于外部事件或定时器。
    • dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;:禁用DAC输出缓冲,这通常用于降低功耗或满足特定应用要求。
    • HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);:调用HAL_DAC_ConfigChannel函数,将配置应用到DAC通道1。这个函数根据提供的配置结构体dac_ch_conf和DAC句柄g_dac_handle,对指定通道(这里是通道1)进行配置。

    启动DAC通道

    HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_1);
    

    最后,调用HAL_DAC_Start函数,启动DAC通道1,使其开始输出模拟信号。

  • DAC MSP初始化函数

    void HAL_DAC_MspInit(DAC_HandleTypeDef *hadc)
    {
        if(hadc->Instance == DAC)
        {
            GPIO_InitTypeDef gpio_init_struct;
            
            //使能时钟
            __HAL_RCC_GPIOA_CLK_ENABLE();
            __HAL_RCC_DAC_CLK_ENABLE();
            
            //配置工作模式
            gpio_init_struct.Pin = GPIO_PIN_4;
            gpio_init_struct.Mode = GPIO_MODE_ANALOG;  //模拟输入
            HAL_GPIO_Init(GPIOA, &gpio_init_struct);
        }
    }
    
  • 设置通道输出电压

    void dac_set_voltage(uint16_t vol)
    {
        double temp = vol;
        temp /= 1000; 
        temp = temp * 4096 / 3.3;
        
        if(temp >= 4096) temp = 4095;
        
        HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp);
    }
    

    电压转换为DAC值

    double temp = vol;
    temp /= 1000;
    temp = temp * 4096 / 3.3;
    
    • double temp = vol;:将输入参数 vol 转换为 double 类型并赋值给临时变量 temp。这样做是为了后续的浮点运算。
    • temp /= 1000;:将电压值从毫伏转换为伏特。
    • temp = temp * 4096 / 3.3;:将电压值转换为DAC寄存器值。这里的 4096 是12位DAC的最大值(2^12),3.3 是参考电压。公式计算方法如下:
      • 先将电压值转换为0到1之间的比值: temp / 3.3
      • 然后乘以DAC的最大值4096,得到对应的DAC寄存器值。

    限制最大值

    if(temp >= 4096) temp = 4095;
    

    因为12位DAC的最大值是4095,所以需要确保计算出的DAC寄存器值不超过4095。如果 temp 大于等于4096,则将其限制为4095,以防止超出DAC的范围。

    设置DAC值

    HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp);
    

    调用 HAL_DAC_SetValue 函数,设置DAC通道1的输出值。参数说明如下:

    • &g_dac_handle:DAC句柄,指向全局的DAC配置结构体。
    • DAC_CHANNEL_1:指定DAC的通道1。
    • DAC_ALIGN_12B_R:使用右对齐方式设置12位的DAC值。
    • temp:计算出的DAC寄存器值。
  • 主函数

    /*确保按键按下*/
    void key_led(void)
    {
        LED1(0);
        delay_ms(20);
        LED1(1);
    }    
      
    /*LCD显示函数*/
    void lcd_display(void)
    {
        lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
        lcd_show_string(30, 70, 200, 16, 16, "ADC TEST", RED);
        lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
        lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);
        lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    }
    
    /*循环内部显示函数*/
    void lcd_display_value(void)
    {
        static uint16_t adcx;
        static float temp;
        
        adcx = adc_get_result();
        lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE);   /* 显示ADCC采样后的原始值 */
    
        temp = (float)adcx * (3.3 / 4096);               /* 获取计算后的带小数的实际电压值,比如3.1111 */
        adcx = temp;                                     /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
        lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE);   /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
    
        temp -= adcx;                                    /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
        temp *= 1000;                                    /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
        lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
    }
    
    /*按键值确认函数*/
    void key_value(void)
    {
        static int swap = 0;
        
        switch(key_scan(0))
        {
            case 2: swap += 100;
                if(swap >= 3300)
                    swap = 0;
                key_led();
                break;
            case 4: swap -= 100;
                if(swap < 0)
                    swap = 3300;
                key_led();
                break;
            default: 
                break;
        }
            
        dac_set_voltage(swap);
    }
    
    /*主函数*/
    int main(void)
    {
        HAL_Init();                                 /* 初始化HAL库 */
        sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
        delay_init(72);                             /* 延时初始化 */
        usart_init(115200);                         /* 串口初始化为115200 */
        led_init();                                 /* 初始化LED */
        lcd_init();                                 /* 初始化LCD */
        adc_init();                                /* 初始化ADC3 */
        dac_init();
        key_init();
    
        lcd_display();
    
        while (1)
        {
            key_value();
            
            lcd_display_value();
    
            LED0_TOGGLE();
            delay_ms(250);
        }
    }
    

    执行逻辑:

    • 初始化硬件和外设。
    • 显示初始LCD内容。
    • 主循环中,持续检测按键输入,更新DAC电压,并刷新LCD显示值。
    • 定时切换LED状态。

    总结

    实现一个简单的ADC采集和DAC输出的系统,通过按键控制DAC输出电压,并在LCD上实时显示ADC采样值和对应的电压值。按键按下时,通过视觉反馈(LED闪烁)确认按键操作,并根据按键输入调整DAC输出。

4. DAC输出三角波实验

  • 功能:

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

  • 输出三角波:
    在这里插入图片描述

  • 输出三角波函数:

    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;
            
            HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);    // 先输出0 
            
            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);
            }
        }
    }
    

    参数说明:

    • maxval: 三角波的最大值(峰值)。
    • dt: 每次输出之间的延时,以微秒为单位。
    • samples: 完整波形的样本数。
    • n: 波形重复的次数。

    代码分析:

    if(samples > ((maxval + 1) * 2)) return;  // 数据不合法
    
    • 检查 samples 是否超过合法范围,如果是,直接返回。
    incval = (maxval + 1) / (samples / 2);    // 计算递增量
    
    • 计算每次递增或递减的量 incval
    for(j = 0; j < n; j++)
    {
        Curval = 0;
        HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);  // 先输出0
    
        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);
        }
    }
    
    • 外层循环 for(j = 0; j < n; j++) 用于生成 n 次三角波。
    • 每次循环开始时,将 Curval 设置为0,并通过 HAL_DAC_SetValue 输出0电压。
    • 内层两个 for 循环分别生成上升沿和下降沿:
      • 第一个 for 循环从0递增到最大值 maxval,每次增加 incval,通过 HAL_DAC_SetValue 更新DAC输出,并延时 dt 微秒。
      • 第二个 for 循环从最大值 maxval 递减到0,每次减少 incval,通过 HAL_DAC_SetValue 更新DAC输出,并延时 dt 微秒。

    功能总结:

    该函数生成并输出一个三角波形。通过调整 maxval 控制波形的峰值,调整 dt 控制波形的频率,调整 samples 控制波形的采样点数,调整 n 控制波形的重复次数。每个周期的波形由 samples/2 个上升点和 samples/2 个下降点构成,每个点之间延时 dt 微秒。

  • 主函数:

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

    采样频率的计算和波形质量之间的关系

    采样频率计算

    采样频率是指在一秒钟内采集的采样点数,单位为赫兹(Hz)。采样频率的计算公式是:
    [ 采样频率 = 1 采样间隔时间 ] [ \text{采样频率} = \frac{1}{\text{采样间隔时间}} ] [采样频率=采样间隔时间1]
    其中,采样间隔时间是每个采样点之间的时间间隔。

    代码中的采样频率计算

    高采样率:

    dac_triangular_wave(4095, 5, 2000, 100); /* 幅值4095, 采样点间隔5us, 2000个采样点, 100个波形 */
    
    • 采样间隔时间:5微秒(us)
    • 每秒采样次数 = 1秒 / 5微秒 = 1,000,000微秒 / 5微秒 = 200,000次/秒

    所以,采样频率为200 kHz(千赫兹)。

    低采样率:

    dac_triangular_wave(4095, 500, 20, 100); /* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */
    
    • 采样间隔时间:500微秒(us)
    • 每秒采样次数 = 1秒 / 500微秒 = 1,000,000微秒 / 500微秒 = 2000次/秒

    所以,采样频率为2 kHz(千赫兹)。

    波形频率计算

    高采样率波形频率:

    • 一个完整的波形周期有2000个采样点。

    • 200 kHz的采样频率 = 200,000个采样点/秒

    • 波形周期时间 = 2000采样点 / 200,000采样点/秒 = 0.01秒

      代码里面使用的是采样间隔时间*采样点

      • 波形周期时间 = 采样点数 采样频率 = 2000 采样点 200 , 000 采样点/秒 = 0.01 秒 = 10 毫秒 \text{波形周期时间} = \frac{\text{采样点数}}{\text{采样频率}} = \frac{2000 \text{采样点}}{200,000 \text{采样点/秒}} = 0.01 \text{秒}= 10 \text{毫秒}\\ 波形周期时间=采样频率采样点数=200,000采样点/2000采样点=0.01=10毫秒
    • 波形频率 = 1 / 波形周期时间 = 1 / 0.01秒 = 100Hz

    低采样率波形频率:

    • 一个完整的波形周期有20个采样点。
    • 2 kHz的采样频率 = 2000个采样点/秒
    • 波形周期时间 = 20采样点 / 2000采样点/秒 = 0.01秒
    • 波形频率 = 1 / 波形周期时间 = 1 / 0.01秒 = 100 Hz

    由于采样频率较低,波形频率接近100 Hz,但更高的非理想因素影响使其实际波形频率为99.5 Hz。

    高采样率与低采样率的区别

    • 高采样率:更高的采样率使得DAC输出能够更加精确地重现波形的细节,减少失真,波形看起来更平滑和接近于理想形态。
    • 低采样率:较低的采样率会导致波形重现不够精确,采样点之间的间隔大,波形失真较大,看起来会比较粗糙。
  • 实验结果:

    高采样率的波形图:
    在这里插入图片描述

    低采样率的波形图:
    在这里插入图片描述

5. DAC输出正弦波实验

  • 配置步骤:

    1. 初始化DMA

      HAL_DMA_Init()
      
    2. 将DMA和ADC句柄联系起来

      __HAL_LINKDMA()
      
    3. 初始化DAC

      HAL_DAC_Init()
      
    4. DAC MSP初始化

      HAL_DAC_MspInit()     
      
    5. 配置DAC相应通道相关参数

      HAL_DAC_ConfigChannel()
      
    6. 启动DAM传输

      HAL_DMA_Start()
      
    7. 配置定时器溢出频率并启动

      HAL_TIM_Base_Init()
      HAL_TIM_Base_Start()
      
    8. 配置定时器触发DAC转换

      HAL_TIMEx_MasterConfigSynchronization()
      
    9. 停止/启动DAC转换、DMA传输

      HAL_DAC_Stop_DMA()
      HAL_DAC_Start_DMA()
      
  • 产生正弦波的函数
    在这里插入图片描述

  • DAC初始化函数

    void dac_dma_wave_init(void)
    {
        DAC_ChannelConfTypeDef dac_ch_conf;
        
        __HAL_RCC_DMA1_CLK_ENABLE();   //使能DMA1时钟
          
        g_dma_dac_handle.Instance = DMA2_Channel3;                          //初始化DMA2的通道3
        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;                     //内存地址递增
        g_dma_dac_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//数据对齐使用半字
        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);
        
        __HAL_LINKDMA(&g_dac_dma_handle, DMA_Handle1, g_dma_dac_handle);
        
        g_dac_dma_handle.Instance = DAC;
        HAL_DAC_Init(&g_dac_dma_handle);
        
        dac_ch_conf.DAC_Trigger = DAC_TRIGGER_T7_TRGO;  //触发源设置为T7触发输出(TRGO)
        dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;  //关闭输出缓冲
        HAL_DAC_ConfigChannel(&g_dac_dma_handle, &dac_ch_conf, DAC_CHANNEL_1);
        
        //启动DMA传输,将数据从内存(g_dac_sin_buf)传输到DAC的通道1数据寄存器(DAC1->DHR12R1)
        HAL_DMA_Start(&g_dma_dac_handle, (uint32_t)g_dac_sin_buf, (uint32_t)&DAC1->DHR12R1, 0);
    }
    
  • DAC MSP初始化函数

    void HAL_DAC_MsoInit(DAC_HandleTypeDef *hdac)
    {
        if(hdac->Instance == DAC)
        {
            GPIO_InitTypeDef gpio_init_struct;
            
            //使能时钟
            __HAL_RCC_GPIOA_CLK_ENABLE();
            __HAL_RCC_DAC_CLK_ENABLE();
            
            //配置工作模式
            gpio_init_struct.Pin = GPIO_PIN_4;
            gpio_init_struct.Mode = GPIO_MODE_ANALOG;  //模拟输入
            HAL_GPIO_Init(GPIOA, &gpio_init_struct);
        }
    }
    
  • DAC DMA使能波形输出

    void dac_dma_wave_enable(uint16_t cndtr, uint16_t arr, uint16_t psc)
    {
        TIM_HandleTypeDef tim7_handle = {0};
        TIM_MasterConfigTypeDef tim_mater_config;
        
        __HAL_RCC_TIM7_CLK_ENABLE();
        
        tim7_handle.Instance = TIM7;
        tim7_handle.Init.Prescaler = psc;
        tim7_handle.Init.Period = arr;  
        HAL_TIM_Base_Init(&tim7_handle);
        
        //设置为更新事件触发(TIM_TRGO_UPDATE)
        tim_mater_config.MasterOutputTrigger = TIM_TRGO_UPDATE;
        //设置为禁用主从模式(TIM_MASTERSLAVEMODE_DISABLE)
        tim_mater_config.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
        HAL_TIMEx_MasterConfigSynchronization(&tim7_handle ,&tim_mater_config);
        
        //启动定时器, 开始计时
        HAL_TIM_Base_Start(&tim7_handle);
        
        //
        HAL_DAC_Stop(&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);
    }
    

    DAC通过DMA从内存缓冲区中读取波形数据并输出模拟信号。定时器7的预分频器周期决定了DAC的更新频率,从而控制输出波形的频率和形状。

  • DAC正弦波输出函数

    void dac_create_sin_buf(uint16_t maxval, uint16_t samples)
    {
        uint8_t i;
        float outdata = 0;
        float inc = (2 * 3.1415962) / samples;
        
        if(maxval <= (samples / 2)) return;
        
        for(i = 0;i < samples; i++)
        {
            outdata = maxval * sin(inc * i) + maxval;
            if(outdata > 4095)
                outdata = 4095;
            g_dac_sin_buf[i] = outdata;
        }
    }
    
    1. 变量初始化:

      • i:循环计数器。
      • outdata:临时变量,用于存储当前计算的输出数据。
      • inc:每次递增的角度值,计算方式为 ( \frac{2\pi}{\text{samples}} ),用于生成正弦波。
    2. 合法性检查:

      • 判断 maxval 是否小于等于 samples / 2,如果是则直接返回,不进行后续操作。这是为了确保生成的波形幅值合理。
    3. 生成正弦波缓冲区:

      • 使用 for 循环遍历每个采样点,计算对应的正弦波值。
      • 计算公式为 outdata = maxval * sin(inc * i) + maxval,即将生成的正弦波调整为非负值,并确保最大幅值为 maxval
      • 如果计算出的 outdata 超过了4095(DAC的最大值),则将其限制为4095。
      • 将计算出的 outdata 存入全局缓冲区 g_dac_sin_buf 对应的位置。

    该函数通过计算并填充正弦波数据的方式,生成了一组用于DAC输出的波形数据。生成的数据缓冲区可以在后续通过DMA传输到DAC,从而生成平滑的正弦波输出。

  • 主函数

    while (1)
        {
            t++;
            key = key_scan(0);                                  // 按键扫描 
    
            if (key == 4)                               // 高采样率 
            {
                dac_create_sin_buf(2048, 100);
                dac_dma_wave_enable(100, 10 - 1, 24 - 1);       // 300Khz触发频率, 100个点, 得到最高3KHz的正弦波. 
            }
            else if (key == 2)                          // 低采样率 
            {
                dac_create_sin_buf(2048, 10);
                dac_dma_wave_enable(10, 10 - 1, 24 - 1);        // 300Khz触发频率, 10个点, 可以得到最高30KHz的正弦波. 
            }
    
            if (t == 40)       
            {
                LED0_TOGGLE(); 
                t = 0;
            }
    
            delay_ms(5);
        }
    

    触发频率计算

    定时器的触发频率预分频器自动重装载寄存器决定。假设系统时钟频率为72MHz:

    触发频率:

    • 触发频率 = 系统时钟频率 ( 预分频器 + 1 ) × ( 自动重装载寄存器值 + 1 ) 触发频率 = 72 MHz 24 × 10 = 72 MHz 240 = 300 kHz \text{触发频率} = \frac{\text{系统时钟频率}}{(\text{预分频器} + 1) \times (\text{自动重装载寄存器值} + 1)}\\ \text{触发频率} = \frac{72\text{MHz}}{24 \times 10} = \frac{72\text{MHz}}{240} = 300\text{kHz} 触发频率=(预分频器+1)×(自动重装载寄存器值+1)系统时钟频率触发频率=24×1072MHz=24072MHz=300kHz
      波形频率:

    • 波形频率 = 触发频率 每周期采样点数 \text{波形频率} = \frac{\text{触发频率}}{\text{每周期采样点数}} 波形频率=每周期采样点数触发频率

    高采样率:
    波形频率 = 300 kHz 100 = 3 kHz \text{波形频率} = \frac{300\text{kHz}}{100} = 3\text{kHz} 波形频率=100300kHz=3kHz
    低采样率:
    波形频率 = 300 kHz 10 = 30 kHz \text{波形频率} = \frac{300\text{kHz}}{10} = 30\text{kHz} 波形频率=10300kHz=30kHz
    通过调节采样点数和触发频率,可以生成不同频率和精度的波形。较高的采样点数会使波形更平滑,但波形频率较低;较低的采样点数则会使波形频率较高,但波形较为粗糙。

  • 实验结果

    高采样率波形图:
    在这里插入图片描述

    低采样率波形图:
    在这里插入图片描述

6. PWM DAC实验

  • PWM DAC初始化函数

    void pwmdac_init(uint16_t arr, uint16_t psc)
    {
        TIM_OC_InitTypeDef timx_oc_pwm_chy = {0};
        
        g_timx_pwm_chy_handle1.Instance = TIM1;                       //定时器选择
        g_timx_pwm_chy_handle1.Init.Prescaler = psc;                  //定时器分频
        g_timx_pwm_chy_handle1.Init.Period = arr;
        g_timx_pwm_chy_handle1.Init.CounterMode = TIM_COUNTERMODE_UP; //定时器计数模式
        g_timx_pwm_chy_handle1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
        HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle1);   //初始化PWM
        
        timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;   //模式选择PWM1
        timx_oc_pwm_chy.Pulse = 0;              //占空比为50%
        timx_oc_pwm_chy.OCNPolarity = TIM_OCNPOLARITY_LOW;  //输出比较极性为低
        
        HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle1, &timx_oc_pwm_chy, TIM_CHANNEL_1);   //配置定时器3通道2
        HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle1, TIM_CHANNEL_1);  //开启PWM通道  
    }
    

    定时器配置:

    • g_timx_pwm_chy_handle1.Instance = TIM1;:选择定时器1。
    • g_timx_pwm_chy_handle1.Init.Prescaler = psc;:设置预分频器值。
    • g_timx_pwm_chy_handle1.Init.Period = arr;:设置自动重装载寄存器值。
    • g_timx_pwm_chy_handle1.Init.CounterMode = TIM_COUNTERMODE_UP;:设置计数模式为向上计数。
    • g_timx_pwm_chy_handle1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;:使能自动重装载预装载。
    • HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle1);:初始化定时器以生成PWM信号。

    PWM通道配置:

    • timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;:设置输出比较模式为PWM模式1。
    • timx_oc_pwm_chy.Pulse = 0;:设置初始脉冲宽度(占空比)。
    • timx_oc_pwm_chy.OCNPolarity = TIM_OCNPOLARITY_LOW;:设置输出极性为低。
    • HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle1, &timx_oc_pwm_chy, TIM_CHANNEL_1);:配置定时器的第1通道。

    启动PWM输出:

    • HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle1, TIM_CHANNEL_1);:启动PWM输出。
  • 定时器输出PWM MSP初始化函数

    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
    {
        if(htim->Instance == TIM1)
        {
            GPIO_InitTypeDef gpio_init_struct;
            
            __HAL_RCC_GPIOA_CLK_ENABLE();
            __HAL_RCC_TIM1_CLK_ENABLE();
            
            gpio_init_struct.Pin = GPIO_PIN_8;
            gpio_init_struct.Mode = GPIO_MODE_AF_PP;
            gpio_init_struct.Pull = GPIO_PULLUP;
            gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
            HAL_GPIO_Init(GPIOA, &gpio_init_struct);
        }
    } 
    
  • 设置PWM DAC输出电压

    void pwmdac_set_voltage(uint16_t vol)
    {
        float temp = vol;
        temp /= 1000;
        //将电压值转换为相应的PWM占空比值
        temp = temp * 256 / 3.3;
        //设置PWM占空比
        __HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle1, TIM_CHANNEL_1, temp);
    }
    

    通过计算PWM占空比来模拟设定的输出电压,适用于需要将数字信号转换为模拟信号的场合。这个过程的关键是将输入电压值转换为适合定时器使用的PWM占空比值,并将其应用于定时器通道,以生成所需的模拟电压。

  • 实验结果
    在这里插入图片描述

声明:资料来源(战舰STM32F103ZET6开发板资源包)

  1. Cortex-M3权威指南(中文).pdf
  2. STM32F10xxx参考手册_V10(中文版).pdf
  3. STM32F103 战舰开发指南V1.3.pdf
  4. STM32F103ZET6(中文版).pdf
  5. 战舰V4 硬件参考手册_V1.0.pdf

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

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

相关文章

uni-ui:基于uni-app的全端兼容高性能UI框架

一、引言 在移动应用开发领域&#xff0c;跨平台框架因其能够降低开发成本、提高开发效率而备受开发者青睐。其中&#xff0c;uni-app作为一个使用Vue.js开发所有前端应用的框架&#xff0c;不仅支持编译到iOS、Android、H5、以及各种小程序等多个平台&#xff0c;还因其丰富的…

实现开发板三盏灯点亮熄灭

实现开发板三盏灯点亮熄灭 typedef struct {volatile unsigned int MODER; // 0x00volatile unsigned int OTYPER; // 0x04volatile unsigned int OSPEEDR; // 0x08volatile unsigned int PUPDR; // 0x0Cvolatile unsigned int IDR; // 0x10volatile unsigned int OD…

无线网络与物联网技术[1]之近距离无线通信技术

无线网络与物联网技术 近距离无线通信技术WIFIWi-Fi的协议标准Wi-Fi的信道Wi-Fi技术的术语Wi-Fi的组网技术Ad-hoc模式无线接入点-APAP&#xff1a;FAT AP vs FIT AP Wi-Fi的特点与应用Wi-Fi的安全技术 Bluetooth蓝牙技术概论蓝牙的技术协议蓝牙的组网技术微微网piconet(了解)散…

C#聊天室①

聊天室服务器&#xff1a; 创建项目 桌面不需要使用控件 Program.cs internal class Program {static TcpListener server;[STAThread]static void Main(){Program p new Program(); p.start();}void start(){server new TcpListener(IPAddress.Parse(GetIP()), 33…

58.WEB渗透测试-信息收集- 端口、目录扫描、源码泄露(6)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;57.WEB渗透测试-信息收集- 端口、目录扫描、源码泄露&#xff08;5&#xff09;-CSDN博客…

模拟笔试 - 卡码网周赛第二十一期(23年美团笔试真题)

第一题&#xff1a;小美的排列询问 解题思路: 简单题&#xff0c;一次遍历数组&#xff0c;判断 是否有和x、y相等并且相连 即可。 可优化逻辑&#xff1a;因为x和y是后输入的&#xff0c;必须存储整个数组&#xff0c;但是上面说了 **排列是指一个长度为n的数组&#xff0…

学习笔记——网络管理与运维——概述(网络管理)

二、概述 1、什么是网络管理&#xff1f; 网络管理是通过对网络中设备的管理&#xff0c;保证设备工作正常&#xff0c;使通信网络正常地运行&#xff0c;以提供高效、可靠和安全的通信服务&#xff0c;是通信网络生命周期中的重要一环。 2、网络管理分类 网络管理(Network …

C++ 24 之 拷贝构造函数

c24拷贝构造函数.cpp #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std;class Person3 { private:int p_age; public:// 构造函数分类&#xff1a;// 按参数分类&#xff1a;1、有参 2、无参// 按类型分类:普通、拷贝&#xff08;复制&…

Charles代理https接口到本地

一、操作手册 1、安装工具 1.1、安装代理软件Charles 软件下载地址&#xff1a;Download a Free Trial of Charles • Charles Web Debugging Proxy 1.2、安装https代理插件&#xff1a;&#xff08;有问题自行百度解决&#xff09; 2、配置策略 以下以https接口为例&…

型号设计师和指挥调度体系: 中国航天系统工程的组织方式

在中国航天&#xff0c;系统工程的组织管理体系是设计师系统和指挥调度系统。 型号设计师系统是型号的技术体系&#xff0c;总设计师是研制任务的技术总负责人&#xff0c;是设计技术方面的组织者、指挥者&#xff0c;重大技术问题的决策者。 指挥调度系统是型号行政管理系统…

C++switch陈述

C 使用关键字 switch、case、default对一个常数执行不同的分流&#xff0c;这构成多重选择的结构&#xff0c;形式如下 简单来说&#xff0c;switch后头接一小括弧&#xff0c;小括弧内为一常数运算式&#xff0c;计算出常数值若与其后case的位标(label) 相符&#xff0c;就会执…

文件没有权限问题:cannot create /opt/apollo/neo/data/log/monitor.log: Permission denied

问题描述 执行 aem bootstrap start --plus 命令启动 Dreamview 提示错误&#xff1a; /bin/sh: 1: cannot create /opt/apollo/neo/data/log/monitor.log: Permission denied [ERROR] Failed to start Dreamview. Please check /opt/apollo/neo/data/log/dreamview.log or /op…

Windows环境RocketMQ启动broker时启动失败

在输入”start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnabletrue“命令启动失败后&#xff0c;会出现如下图所示的样子。 但我们重新启动电脑、重新启动RocketMQ或者杀死进程等方法都对此无效&#xff0c;还是没有启动成功。 启动失败的原因&#xff1a; 中途断网或…

Kaggle -- Multi-Class Prediction of Obesity Risk

使用简单的决策树进行分类&#xff0c;代码如下&#xff1a; import pandas as pd import numpy as np from sklearn.preprocessing import OneHotEncoder from sklearn.model_selection import train_test_splitdf pd.read_csv("train.csv")df[Gender] df[Gender…

React 中的 Lanes

React 中有一个 Lane 的概念&#xff0c;Lane 就像高速路上的不同车道&#xff0c;具有不同优先级&#xff0c;在 React Lane 通过一个 32 位的二进制数来表示。越小优先级别越高&#xff0c;SyncLane 级别最高。用二进制存储的方式&#xff0c;可以通过逻辑操作快速判断 Lane …

vite.config.js如何使用env的环境变量

了解下环境变量在vite中 官方文档走起 https://cn.vitejs.dev/guide/env-and-mode.html#env-variables-and-modes 你见到的.env,.env.production等就是放置环境变量的 官方文档说到.env.[mode] # 只在指定模式下加载,比如.env.development只在开发环境加载 至于为什么是deve…

算法课程笔记——线段树维护矩阵

算法课程笔记——线段树维护矩阵 2

【每日一练 】day2

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; &#x1f388;丠丠64-CSDN博客&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起…

代理IP常见问题解答,新手必看手册

代理IP在互联网数据收集和业务开展中发挥着重要作用&#xff0c;它充当用户客户端和网站服务器之间的“屏障”&#xff0c;可以保护用户的真实IP地址&#xff0c;并允许用户通过不同的IP地址进行操作。然而&#xff0c;在使用代理IP的过程中&#xff0c;用户经常会遇到一些问题…

2.spring cloud gateway 源码编译

spring cloud gateway编译 1.编译 命令 mvn clean compile -U2.报错 报错信息 核心信息 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin:3.1.2:check (checkstyle-validation) on project spring-cloud-gateway-mvc: Failed during …