STM32-17-DAC

2024-06-13 22:04
文章标签 stm32 17 dac

本文主要是介绍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);  // 先输出0for(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的通道3g_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);   //初始化PWMtimx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;   //模式选择PWM1timx_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通道2HAL_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

这篇关于STM32-17-DAC的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1058544

相关文章

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

STM32(十一):ADC数模转换器实验

AD单通道: 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO,把GPIO配置成模拟输入的模式。 3.配置多路开关,把左面通道接入到右面规则组列表里。 4.配置ADC转换器, 包括AD转换器和AD数据寄存器。单次转换,连续转换;扫描、非扫描;有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。 5.ADC_CMD 开启ADC。 void RCC_AD

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介  1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 (OTA) 3在线编程(ICP把整个程序都更新掉) 1 系统的Bootloader写死了,只能用串口下载到指定的位置,启动方式也不方便需要配置BOOT引脚触发启动  4 IAP(自己写的Bootloader,实现程序升级) 1 比如蓝牙转串口,

FreeRTOS-基本介绍和移植STM32

FreeRTOS-基本介绍和STM32移植 一、裸机开发和操作系统开发介绍二、任务调度和任务状态介绍2.1 任务调度2.1.1 抢占式调度2.1.2 时间片调度 2.2 任务状态 三、FreeRTOS源码和移植STM323.1 FreeRTOS源码3.2 FreeRTOS移植STM323.2.1 代码移植3.2.2 时钟中断配置 一、裸机开发和操作系统开发介绍 裸机:前后台系

寻迹模块TCRT5000的应用原理和功能实现(基于STM32)

目录 概述 1 认识TCRT5000 1.1 模块介绍 1.2 电气特性 2 系统应用 2.1 系统架构 2.2 STM32Cube创建工程 3 功能实现 3.1 代码实现 3.2 源代码文件 4 功能测试 4.1 检测黑线状态 4.2 未检测黑线状态 概述 本文主要介绍TCRT5000模块的使用原理,包括该模块的硬件实现方式,电路实现原理,还使用STM32类

STM32 ADC+DMA导致写FLASH失败

最近用STM32G070系列的ADC+DMA采样时,遇到了一些小坑记录一下; 一、ADC+DMA采样时进入死循环; 解决方法:ADC-dma死循环问题_stm32 adc dma死机-CSDN博客 将ADC的DMA中断调整为最高,且增大ADCHAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_Buffer_Size); 的ADC_Bu

17 通过ref代替DOM用来获取元素和组件的引用

重点 ref :官网给出的解释是: ref: 用于注册对元素或子组件的引用。引用将在父组件的$refs 对象下注册。如果在普通DOM元素上使用,则引用将是该元素;如果在子组件上使用,则引用将是组件实例: <!-- vm.$refs.p will be the DOM node --><p ref="p">hello</p><!-- vm.$refs.child will be the c

react笔记 8-17 属性绑定 class绑定 引入图片 循环遍历

1、绑定属性 constructor(){super()this.state={name:"张三",title:'我是一个title'}}render() {return (<div><div>aaaaaaa{this.state.name}<div title={this.state.title}>我是一个title</div></div></div>)} 绑定属性直接使用花括号{}   注

基于stm32的河流检测系统-单片机毕业设计

文章目录 前言资料获取设计介绍功能介绍具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机设计精品

STM32的使用方法一

注:我采用的是STM32F103RC芯片、相应的电路图和STM32CubeIDE软件这是在STM32CubeIDE软件定义芯片后,所给的必要的代码逻辑,加上了注释 #include "main.h"/* Private variables ---------------------------------------------------------*//* Private function