STM32-17-DAC

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【cocos creator 3.x】 修改builtin-unlit 加了一个类似流光显示的mask参数

效果见图&#xff1a; shader 代码修改如下&#xff0c; 主要看 USE_MASK_UVY 关键字部分修改&#xff1a; // Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd. CCEffect %{techniques:- name: opaquepasses:- vert: unlit-vs:vertfrag: unlit-fs:fragproperties: &a…

Python 踩坑记 -- 调优

前言 继续解决问题 慢 一个服务运行有点慢&#xff0c;当然 Python 本身不快&#xff0c;如果再编码不当那这个可能就是量级上的劣化。 整个 Code 主线逻辑 1700&#xff0c;各依赖封装 3000&#xff0c;主线逻辑也是很久远的痕迹&#xff0c;长函数都很难看清楚一个 if els…

【因果推断python】32_合成控制2

目录 合成控制作为线性回归的一种实现​编辑 合成控制作为线性回归的一种实现 为了估计综合控制的治疗效果&#xff0c;我们将尝试构建一个类似于干预期之前的治疗单元的“假单元”。然后&#xff0c;我们将看到这个“假单位”在干预后的表现。合成控制和它所模仿的单位之间的…

16个不为人知的资源网站,强烈建议收藏!

整理了16个不为人知的资源网站&#xff0c;涵盖了课程学习、办公技能、娱乐休闲、小说音乐等多种资源&#xff0c;强烈建议收藏&#xff01; #学习网站 1、中国大学MOOC icourse163.org/ 这是一个汇集了国内顶尖大学免费课程资源的平台&#xff0c;众多985工程院校如北京大…

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…

WINUI——CommunityToolkit.Mvvm Messenger接收消息时报错:Cannot access a disposed object.

背景 WINUI开发时使用CommunityToolkit.Mvvm的Messemger让UI展示一些信息时出现错误&#xff1a; System.ObjectDisposedException:“Cannot access a disposed object. ObjectDisposed_ObjectName_Name” 详细见下述截图&#xff1a; 开发环境 WIN11 WINUI&#xff13; …

【源码】html+JS实现:24小时折线进度图

<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>24小时折线进度图</title> <st…

代码生成-CodeGeeX2本地部署体验

一 CodeGeeX2介绍&#xff1a; CodeGeeX2 是多语言代码生成模型 CodeGeeX (KDD’23) 的第二代模型。不同于一代 CodeGeeX&#xff08;完全在国产华为昇腾芯片平台训练&#xff09; &#xff0c;CodeGeeX2 是基于 ChatGLM2 架构加入代码预训练实现&#xff0c;得益于 ChatGLM2 的…

是否可以购买外链?

答案是可以&#xff0c;但要看你买什么外链&#xff0c;有价值的自然外链价格肯定也高&#xff0c;随便到某些平台发的外链&#xff0c;哪怕是相关的高权重平台&#xff0c;作用也有限&#xff0c;当然&#xff0c;你要大批量购买&#xff0c;说不定也能出一点效果&#xff0c;…

天诚公租房、人才公寓NB-IOT人脸物联网智能门锁解决方案

近期&#xff0c;全国已有超70城推出商品房“以旧换新”。各地商品房“以旧换新”主要采取国企收购、市场联动、税费补贴三种模式&#xff0c;二手房和新房市场交易活跃度均有提升。 一、人才公寓掀起建设浪潮 事实上&#xff0c;旧房被收购后将被纳入保障性租赁住房&#xf…

opencv 通过滑动条调整阈值处理、边缘检测、轮廓检测、模糊、色调调整和对比度增强参数 并实时预览效果

使用PySimpleGUI库创建了一个图形用户界面(GUI),用于实时处理来自OpenCV摄像头的图像。它允许用户应用不同的图像处理效果,如阈值处理、边缘检测、轮廓检测、模糊、色调调整和对比度增强。用户可以通过滑动条调整相关参数。 完整代码在文章最后,可以运行已经测试; 代码的…

代码随想录Day58

392.判断子序列 题目&#xff1a;392. 判断子序列 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;定义重合数记录s与t的比对情况&#xff0c;挨个取出t的字符&#xff0c;与s的字符进行比较&#xff0c;如果相同&#xff0c;重合数就加1&#xff0c;跳到s的下一个字…

QStyledItemDelegate的使用方法

QStyledItemDelegate 是 Qt 框架中用于为模型/视图框架提供数据项显示和编辑的一个类。 1. 创建 QStyledItemDelegate 实例 通常&#xff0c;你不需要直接实例化 QStyledItemDelegate&#xff0c;因为它是默认的委托。但如果你需要自定义显示和编辑行为&#xff0c;你可以继承…

韩顺平0基础学java——第22天

p441-459 异常exception 选中代码块&#xff0c;快捷键ctraltt6&#xff0c;即trt-catch 如果进行了异常处理&#xff0c;那么即使出现了异常&#xff0c;但是会继续执行 程序过程中发生的异常事件分为两大类&#xff1a; 异常体系图※ 常见的运行异常&#xff1a;类型转换…

继承深度剖析

前言 从继承开始就开始C进阶了&#xff0c; 这一块需要好好学习&#xff0c;这块知识很重要&#xff0c; 坑有点多&#xff0c;所以是面试笔试的常客。 基本概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c; 它允许程序员在保持原有…

使用MNIST数据集训练手写数字识别模型

一、MNIST数据集介绍 MNIST 数据集&#xff08;手写数字数据集&#xff09;是一个公开的公共数据集&#xff0c;任何人都可以免费获取它。目前&#xff0c;它已经是一个作为机器学习入门的通用性特别强的数据集之一&#xff0c;所以对于想要学习机器学习分类的、深度神经网络分…

抓包工具 Wireshark 的下载、安装、使用、快捷键

目录 一、什么是Wireshark&#xff1f;二、Wireshark下载三、Wireshark安装四、Wireshark使用4.1 基本使用4.2 过滤设置1&#xff09;捕获过滤器2&#xff09;显示过滤器 4.3 过滤规则1&#xff09;捕获过滤器-规则语法2&#xff09;显示过滤器-规则语法 4.4 常用的显示过滤器规…

js实现一个数据结构——栈

栈的概念就不再赘述&#xff0c;无可厚非的先进后出&#xff0c;而JS又是高级语言&#xff0c;数组中的方法十分丰富&#xff0c;已经自带了push pop方法进行入栈出栈的操作。 1.基本实现 class Stack {constructor() {this.items [];}// 入栈push(item) {this.items.push(i…

【C++入门(1)】命名空间

一、C出世 我们先简单认识下C的来历&#xff0c;C是在C语言的基础上发展来的。 当年C的设计者Bjarne Stroustrup&#xff0c;本贾尼斯特劳斯特卢普先生设计C语言之初&#xff0c;是为了对C语言做出一些更改&#xff0c;弥补C语言在一些方面的不足&#xff0c;或者做出其他的设…

二阶段提交(2pc)协议

二阶段提交&#xff08;2pc&#xff09;协议 1、 简介 二阶段提交算法是一个分布式一致性算法&#xff0c;强一致、中心化的原子提交协议&#xff0c;主要用来解决分布式事务问题。在单体spring应用中我们往往通过一个Transactional注解就可以保证方法的事务性&#xff0c;但…