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

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

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

相关文章

k8s学习--OpenKruise详细解释以及原地升级及全链路灰度发布方案

文章目录 OpenKruise简介OpenKruise来源OpenKruise是什么&#xff1f;核心组件有什么&#xff1f;有什么特性和优势&#xff1f;适用于什么场景&#xff1f; 什么是OpenKruise的原地升级原地升级的关键特性使用原地升级的组件原地升级的工作原理 应用环境一、OpenKruise部署1.安…

ssm宠物网站系统-计算机毕业设计源码07183

摘 要 在信息飞速发展的今天&#xff0c;网络已成为人们重要的信息交流平台。宠物网站每天都有大量的信息需要通过网络发布&#xff0c;为此&#xff0c;本人开发了一个基于B/S&#xff08;浏览器/服务器&#xff09;模式的宠物网站系统。 该系统以JJava编程语言、MySQL和SSM框…

基于 VITA57.1 标准的 2 收 2 发射频子卡(国产 ADRV9009子卡)

FMC159 是一款基于 VITA57.1 标准规范&#xff0c;实现 2 收 2 发的射频子模块&#xff0c;该板卡基于国产化 ADRV9009 射频收发器&#xff0c;涵盖了50MHz~6GHz 频段&#xff0c;并集成了双通道收发链路&#xff0c;发送最大实时带宽可到 450MHz&#xff0c;接收最大带宽可以到…

十进制、二进制、十六进制之间的相互转换

实验目的 实现int 、float 转换为字符串并显示 实现数字字符以二进制、十进制、十六进制显示 实现十进制、二进制、十六进制之间的相互转换 #include "numconvert.h" #include "ui_numconvert.h"NumConvert::NumConvert(QWidget *parent): QWidget(parent)…

C#特性-CallerMemberName、CallerFilePath和CallerLineNumber的介绍和应用

介绍 在csharp中&#xff0c;CallerMemberName, CallerFilePath, 和 CallerLineNumber 是编译时常量&#xff0c;它们是csharp 5.0引入的特性&#xff0c;用于提供有关调用堆栈的信息&#xff0c;通常用于日志记录和调试。这些特性可以自动填充方法的参数&#xff0c;无需显式…

软考初级网络管理员__网络单选题

1.观察交换机状态指示灯初步判断交换机故障&#xff0c;交换机运行中指示灯显示红色表示()。 警告 正常 待机 繁忙 2.通常测试网络连通性采用的命令是()。 Netstat Ping Msconfig Cmd 3.一台16端口的交换机可以产生&#xff08;&#xff09;个冲突域? 1 4 15 16…

安装 Nuxt.js 的步骤和注意事项

title: 安装 Nuxt.js 的步骤和注意事项 date: 2024/6/17 updated: 2024/6/17 author: cmdragon excerpt: Nuxt.js在Vue.js基础上提供的服务器端渲染框架优势&#xff0c;包括提高开发效率、代码维护性和应用性能。指南详细说明了从环境准备、Nuxt.js安装配置到进阶部署技巧&…

3D元宇宙虚拟主播引领文旅产业迈向更加美好的未来!

随着文旅产业的蓬勃发展&#xff0c;3D数字代言人的应用正逐渐改变着传统的文旅传播方式。文旅3D数字代言人搭建编辑器的出现&#xff0c;正是满足大部分文旅产业借助数字人浪潮&#xff0c;将传统文化与现代科技完美融合的创新之举。 文旅3D数字代言人搭建编辑器不仅满足了Z世…

SelfGNN: Self-Supervised Graph Neural Networks for Sequential Recommendation

SelfGNN: Self-Supervised Graph Neural Networks for Sequential Recommendation&#xff08;Sigir2024&#xff09; 摘要 顺序推荐通过对用户的时间和顺序交互模式进行建模&#xff0c;有效地解决信息过载问题。 为了克服监督信号的局限性&#xff0c;最近的方法在推荐系统中…

奇安信齐向东:AI驱动安全,应对数字经济时代挑战

随着数字经济的蓬勃发展&#xff0c;网络安全问题日益凸显&#xff0c;AI技术的融入为网络安全带来了新的机遇和挑战。近日&#xff0c;备受瞩目的2024全球数字经济大会数字安全高层论坛暨北京网络安全大会战略峰会&#xff08;简称“BCS大会”&#xff09;在北京盛大开幕。本次…

GPT-4o的视觉识别能力,将绕过所有登陆的图形验证码

知识星球&#x1f517;除了包含技术干货&#xff1a;《Java代码审计》《Web安全》《应急响应》《护网资料库》《网安面试指南》还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 我们来看一下市面上常见的图形验证…

3秒生成!这个AI模型画风也太治愈了,新手也能轻松驾驭

还在为不会画画而苦恼吗&#xff1f;别担心&#xff0c;今天给大家介绍一个超好用的AI模型——Soft and Squishy Linework&#xff0c;即使是小白也能轻松生成可爱的动漫图像&#xff01; Soft and Squishy Linework&#xff1a;专门生成柔和的、低保真&#xff08;lofi&#…

为何选择Xinstall?告别邀请码,让App推广更便捷!

在互联网日益繁荣的今天&#xff0c;App的推广和运营成为了各大企业关注的重点。然而&#xff0c;传统的推广方式如邀请码限制&#xff0c;往往会给用户带来不便&#xff0c;同时也限制了App的快速增长。在这个背景下&#xff0c;Xinstall凭借其独特的功能和服务&#xff0c;成…

快慢指针技巧

快慢指针技巧 在说快慢指针之前&#xff0c;我们先说一下双指针。 双指针 双指针&#xff1a;使用两个指针来解决问题。 所谓的指针其实就是指数组的下标&#xff0c;或者链表的节点的地址。 我们以数组为例介绍一下。 有两个指针分别存储着数组的两个下标&#xff0c;这就…

成都某展厅2套2x2透明OLED拼接屏项目

成都某展厅的2套2x2透明OLED拼接屏展示设计具有独特的技术魅力和视觉效果。以下是关于这一展示设计的详细介绍&#xff1a; 1.产品规格 类型&#xff1a;透明OLED拼接屏 尺寸与配置&#xff1a;每套为2x2拼接&#xff0c;即每套由4块屏幕组成。 2.应用场景 成都某展厅&#…

HarmonyOS NEXT首个公测Beta版封包完成

华为将在6月21日至23日在深圳举办华为开发者大会2024。 根据华为消费者业务CEO余承东此前的预告&#xff0c;HarmonyOS NEXT将在大会上正式推出Beta版本&#xff0c;用户将有机会体验全新的鸿蒙系统。 HarmonyOS NEXT首个公测Beta版封包完成&#xff1a;Mate 60和Pura 70系列即…

从短期到长期,不同保存期限的红酒管理技巧

在葡萄酒的世界中&#xff0c;保存与管理的技巧对于确保葡萄酒的品质和口感至关重要。特别是对于云仓酒庄雷盛红酒&#xff0c;这种多类型红酒&#xff0c;更需要我们掌握一定的保存管理技巧。这篇文章将详细探讨从短期到长期&#xff0c;不同保存期限的云仓酒庄雷盛红酒的管理…

WPF/C#:如何将数据分组显示

WPF Samples中的示例 在WPF Samples中有一个关于Grouping的Demo。 该Demo结构如下&#xff1a; MainWindow.xaml如下&#xff1a; <Window x:Class"Grouping.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x&q…

Android-apk自动签名

一、创建apk签名 1、有得话忽略 Build->Generate Signed Bundle or APK&#xff0c;选择APK&#xff0c;然后Next&#xff0c;然后选择Create new 2、 2.在app/build.gradle中&#xff0c;在android{…}中添加以下内容 signingConfigs { release { storeFile file(androi…

Java实现俄罗斯方块-初始化游戏界面的JPanel

目录 技术实现&#xff1a; 2.初始化游戏界面&#xff1b; 2.1 什么是游戏界面 2.2 JPanel面板 2.3 流布局管理器【FlowLayout】 2.4 网络布局管理器 【GridLayout】 2.5 边界布局管理器 技术实现&#xff1a; 1.初始化游戏窗口&#xff1b; 2.初始化游戏界面&#xff…