当前位置: 首页 > news >正文

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

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…...

一杯咖啡的艺术 | 如何利用数字孪生技术做出完美的意式浓缩咖啡?

若您对数据分析以及人工智能感兴趣&#xff0c;欢迎与我们一起站在全球视野关注人工智能的发展&#xff0c;与Forrester 、德勤、麦肯锡等全球知名企业共探AI如何加速制造进程&#xff0c; 共同参与6月20日由Altair主办的面向工程师的全球线上人工智能会议“AI for Engineers”…...

使用QT制作QQ登录界面

mywidget.cpp #include "mywidget.h"Mywidget::Mywidget(QWidget *parent): QWidget(parent) {/********制作一个QQ登录界面*********************/this->resize(535,415);//设置登录窗口大小this->setFixedSize(535,415);//固定窗口大小this->setWindowTi…...

代码随想录训练营第七天 344反转字符串 541反转字符串II 替换数字

第一题&#xff1a; 原题链接&#xff1a;344. 反转字符串 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 双指针&#xff0c;一根指向字符串的头部&#xff0c;一根指向字符串的尾部。两个指针向中间移动&#xff0c;交换两根指针指向的值。 代码如下&#xf…...

【Python】数据处理:SQLite操作

使用 Python 与 SQLite 进行交互非常方便。SQLite 是一个轻量级的关系数据库&#xff0c;Python 标准库中包含一个名为 sqlite3 的模块&#xff0c;可以直接使用。 import sqlite3数据库连接和管理 连接到 SQLite 数据库。如果数据库文件不存在&#xff0c;则创建一个新数据库…...

NXP RT1060学习总结 - fsl_flexcan 基础CAN函数说明 -3

概要 CAN测试源码&#xff1a; https://download.csdn.net/download/qq_35671135/89425377 根据fsl_flexcan.h文件从文件末尾往前面梳理&#xff0c;总共30个基础CAN函数&#xff1b; 该文章只梳理常规CAN&#xff0c;增强型CAN后面再单独梳理。 使用的是RT1064开发板进行测试…...

2024年第三届数据统计与分析竞赛(B题)数学建模完整思路+完整代码全解全析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 详细请查 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024年第三届数据统计与分析竞赛&#xff08;B题&#xff09;的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有…...

高通Android 12 右边导航栏改成底部显示

最近同事说需要修改右边导航栏到底部&#xff0c;问怎么搞&#xff1f;然后看下源码尝试下。 1、Android 12修改代码路径 frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java a/frameworks/base/services/core/java/com/android/server/wm/Display…...

2.6数据报与虚电路

数据报 当作为通信子网用户的端系统要发送一个报文时&#xff0c;在端系统中实现的高层协议先把报文拆成若干个带有序号的数据单元&#xff0c;并在网络层加上地址等控制信息后形成数据报分组(即网络层PDU)中间结点存储分组一段很短的时间&#xff0c;找到最佳的路由后&#x…...

小主机折腾记26

双独立显卡调用问题 前两天将tesla p4从x99大板上拆了下来&#xff0c;将880G5twr上的rx480 4g安装到了x99大板上&#xff0c;预计是dg1输出&#xff0c;rx480做3d运算。安装完驱动后&#xff0c;还想着按照之前tesla p4的设置方法去设置rx480&#xff0c;结果果然&#xff0c…...

ArrayList浅析

目录 一、ArrayList源码1.1 迭代器1.1.1 Itr源码浅析1.1.2 ListItr源码浅析 1.2 常用方法1.3 System.arraycopy1.4 ArrayList 的创建方式 二、引申问题2.1 ArrayList的大小是如何增加的&#xff1f;2.2 什么情况下你会使用ArrayList2.3 在索引中ArrayList的增加或者删除某个对象…...

Spring Boot整合hibernate-validator实现数据校验

文章目录 概念基本概念常用校验注解 前置内容整合Hibernate Validator快速入门优雅处理参数校验异常其余注解校验自定义校验注解 参考来源 概念 基本概念 Hibernate Validator 是一个参数校验框架&#xff0c;可以非常方便地帮助我们校验应用程序的入参&#xff0c;实现了参数…...

Ubuntu系统中网易云音乐编译安装

项目地址&#xff1a; netease-cloud-music-gtk: Linux 平台下基于 Rust GTK 开发的网易云音乐播放器 目录 1.README.md中按照步骤来 2.安装git 3.报错 sudo apt install cmake sudo apt-get install libdbus-1-dev sudo apt install dnf sudo dnf install gettext 继…...

MPLS标签号

标签被压入在2层与3层之间 称为 2.5层 标签的格式----32 位4 个字节 前 20 位为标签号&#xff0c;2~20 个标签号&#xff1b;其中1-15号保留&#xff0c;作为特殊编号&#xff1b; 第 21-23位 exp&#xff0c;3位8个数&#xff0c;为优先级&#xff0c;用于Q0S 策略使用&a…...

OpenHarmony napi 编译 .so 并打包成 .har

一、前言 最近在搞公司标准产品适配OpenHarmony 平台&#xff0c; 按照行业上的常用方法&#xff0c;在Android 是将底层代码用c 封装成 xxx.so &#xff0c;然后将其他一部分打包成 xxx.jar。 因此&#xff0c;在OpenHarmony 平台也是打算按照这个模式。正所谓&#xff0c;好…...

python 循环导入(circular imports)解决方法

在 Python 中&#xff0c;大部分人都应该都遇到过循环导入的问题。 循环导入是指两个文件各自尝试导入另一个文件&#xff08;模块&#xff09;&#xff0c;当一个模块没有完全初始化时会导致失败。解决这种情况的最好方法是将代码分层组织&#xff0c;这样导入的关系就会自然…...

01、Linux网络设置

目录 1.1 查看及测试网络 1.1.1 查看网络配置 1、查看网络接口地址 2、查看主机状态 3、查看路由表条目 4、查看网络连接qing 1.1.2 测试网络连接 1.测试网络连接 2.跟踪数据包的路由路径 3.测试DNS域名解析 1.2 设置网络地址参数 1.2.1 使用网络配置命令 1.修改网卡…...

ssm160基于Java技术的会员制度管理的商品营销系统的设计与实现+vue

商品营销系统计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本商品营销系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理…...

边缘计算网关在智慧厕所远程监测与管理的应用

随着智慧城市建设的不断深入&#xff0c;城市公共设施的智慧化管理成为了提升城市品质和居民生活质量的关键建设。公厕作为城市基础设施的重要组成部分&#xff0c;其管理效率和卫生状况直接影响着市民的日常生活体验。在公厕设施建设背景下&#xff0c;边缘计算网关技术的应用…...

嵌入式linux中设备树使用of函数操作基本方法

各位开发者大家好,今天主要给大家分享一下,如何使用of操作函数,获取对应设备树节点先关的属性信息。 第一:of_find_property函数 of_find_property 函数用于在设备树中查找节点下具有指定名称的属性。如果找到了该属性,可以通过返回的属性结构体指针进行进一步的操作,比…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...

ZYNQ学习记录FPGA(二)Verilog语言

一、Verilog简介 1.1 HDL&#xff08;Hardware Description language&#xff09; 在解释HDL之前&#xff0c;先来了解一下数字系统设计的流程&#xff1a;逻辑设计 -> 电路实现 -> 系统验证。 逻辑设计又称前端&#xff0c;在这个过程中就需要用到HDL&#xff0c;正文…...