• DAC实验(DAC 输出三角波实验)(DAC 输出正弦波实验)


    DAC 输出三角波实验

    本实验我们来学习使用如何让 DAC 输出三角波,DAC 初始化部分还是用 DAC 输出实验 的,所以做本实验的前提是先学习 DAC 输出实验。

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

    我们只需要把示波器的探头接到 DAC1 通道 1(PA4)引脚,就可以在示波器上显示 DAC 输出的波形。 

    1. #include "./BSP/DAC/dac.h"
    2. #include "./SYSTEM/delay/delay.h"
    3. DAC_HandleTypeDef g_dac_handle;
    4. /* DAC初始化函数 */
    5. void dac_init(void)
    6. {
    7. DAC_ChannelConfTypeDef dac_ch_conf = {0};
    8. g_dac_handle.Instance = DAC;
    9. HAL_DAC_Init(&g_dac_handle);/* 初始化 DAC */
    10. dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;/* 不使用触发功能,(自动模式) */
    11. dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;/* DAC1 输出缓冲关闭 */
    12. HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);
    13. HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_1);/* 开启 DAC 通道 1 */
    14. }
    15. /* DAC MSP底层初始化函数 */
    16. void HAL_DAC_MspInit(DAC_HandleTypeDef *hdac)
    17. {
    18. if(hdac->Instance == DAC)
    19. {
    20. GPIO_InitTypeDef gpio_init_struct = {0};
    21. __HAL_RCC_DAC_CLK_ENABLE();/* 使能 DAC1 的时钟 */
    22. __HAL_RCC_GPIOA_CLK_ENABLE();;/* 使能 DAC OUT1/2 的 IO 口时钟(都在 PA 口,PA4/PA5) */
    23. gpio_init_struct.Pin = GPIO_PIN_4;
    24. gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    25. HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    26. }
    27. }
    28. /**
    29. * @brief 设置DAC_OUT1输出三角波
    30. * @note 输出频率 ≈ 1000 / (dt * samples) Khz, 不过在dt较小的时候,比如小于5us时, 由于delay_us
    31. * 本身就不准了(调用函数,计算等都需要时间,延时很小的时候,这些时间会影响到延时), 频率会偏小.
    32. *
    33. * @param maxval : 最大值(0 < maxval < 4096), (maxval + 1)必须大于等于samples/2
    34. * @param dt : 每个采样点的延时时间(单位: us)
    35. * @param samples: 采样点的个数, samples必须小于等于(maxval + 1) * 2 , 且maxval不能等于0
    36. * @param n : 输出波形个数,0~65535
    37. *
    38. * @retval 无
    39. */
    40. void dac_triangular_wave(uint16_t maxval, uint16_t dt, uint16_t samples, uint16_t n)
    41. {
    42. uint16_t i, j;
    43. float incval; /* 递增量 */
    44. float Curval; /* 当前值 */
    45. if((maxval + 1) <= samples)return ; /* 数据不合法 */
    46. incval = (maxval + 1) / (samples / 2); /* 计算递增量 */
    47. for(j = 0; j < n; j++)
    48. {
    49. Curval = 0;
    50. HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval); /* 先输出0 */
    51. for(i = 0; i < (samples / 2); i++) /* 输出上升沿 */
    52. {
    53. Curval += incval; /* 新的输出值 */
    54. HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);
    55. delay_us(dt);
    56. }
    57. for(i = 0; i < (samples / 2); i++) /* 输出下降沿 */
    58. {
    59. Curval -= incval; /* 新的输出值 */
    60. HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);
    61. delay_us(dt);
    62. }
    63. }
    64. }

    该函数用于设置 DAC 通道 1 输出三角波,输出频率 ≈ 1000 / (dt * samples) Khz,形参含 义在源码已经有详细注释。该函数中,我们使用 HAL_DAC_SetValue 函数来设置 DAC 的输出 值,这样得到的三角波在示波器上可以看到。如果有跳动现象(不平稳),是正常的,因为调用 函数,计算等都需要时间,这样就会导致输出的波形是不太稳定的。越高性能的 MCU,得到的 波形会越稳定。而且用 HAL 库函数操作效率没有直接操作寄存器高,所以可以像寄存器版本实 验一样,直接操作 DHR12R1 寄存器,得到的波形会相对稳定些。

    由于使用 HAL 库的函数,CPU 花费的时间会更长(因为指令变多了),在时间精度要求比 较高的应用,就不适合用 HAL 库函数来操作了,这一点希望大家明白。所以学 STM32 不是说 只要会 HAL 库就可以了,对寄存器也是需要有一定的理解,最好是熟悉。这里用 HAL 库操作 只是为了演示怎么使用 HAL 库的相关函数。

    1. #include "./SYSTEM/sys/sys.h"
    2. #include "./SYSTEM/usart/usart.h"
    3. #include "./SYSTEM/delay/delay.h"
    4. #include "./BSP/LED/led.h"
    5. #include "./BSP/LCD/lcd.h"
    6. #include "./BSP/ADC/adc.h"
    7. #include "./BSP/DAC/dac.h"
    8. #include "./BSP/KEY/key.h"
    9. int main(void)
    10. {
    11. uint8_t t = 0;
    12. uint8_t key;
    13. HAL_Init(); /* 初始化 HAL 库 */
    14. sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    15. delay_init(72); /* 延时初始化 */
    16. usart_init(115200); /* 传口初始化 */
    17. led_init(); /* LED初始化 */
    18. lcd_init(); /* LCD初始化 */
    19. adc_init(); /* ADC初始化 */
    20. dac_init(); /* DAC初始化 */
    21. key_init(); /* KEY初始化 */
    22. lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    23. lcd_show_string(30, 70, 200, 16, 16, "DAC Triangular WAVE TEST", RED);
    24. lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    25. lcd_show_string(30, 110, 200, 16, 16, "KEY0:Wave1 KEY1:Wave2", RED);
    26. lcd_show_string(30, 130, 200, 16, 16, "DAC None", BLUE); /* 提示无输出 */
    27. while(1)
    28. {
    29. t++;
    30. key = key_scan(0); /* 按键扫描 */
    31. if (key == KEY0_PRES) /* 高采样率 , 约1Khz波形 */
    32. {
    33. lcd_show_string(30, 130, 200, 16, 16, "DAC Wave1 ", BLUE);
    34. dac_triangular_wave(4095, 5, 2000, 100); /* 幅值4095, 采样点间隔5us, 2000个采样点, 100个波形 */
    35. lcd_show_string(30, 130, 200, 16, 16, "DAC None ", BLUE);
    36. }
    37. else if (key == KEY1_PRES) /* 低采样率 , 约1Khz波形 */
    38. {
    39. lcd_show_string(30, 130, 200, 16, 16, "DAC Wave2 ", BLUE);
    40. dac_triangular_wave(4095, 500, 20, 100); /* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */
    41. lcd_show_string(30, 130, 200, 16, 16, "DAC None ", BLUE);
    42. }
    43. if (t == 10) /* 定时时间到了 */
    44. {
    45. LED0_TOGGLE(); /* LED0闪烁 */
    46. t = 0;
    47. }
    48. delay_ms(10);
    49. }
    50. }

    该部分代码功能是,按下 KEY0 后,DAC 输出三角波 1,按下 KEY1 后,DAC 输出三角波 2,将 dac_triangular_wave 的形参代入公式:输出频率 ≈ 1000 / (dt * samples) KHz,得到三角 波 1 和三角波 2 的频率都是 0.1KHz。

    下载代码后,可以看到 LED0 不停的闪烁,提示程序已经在运行了。LCD 显示如图 33.3.4.1 所示:

    没有按下任何按键之前,LCD 屏显示 DAC None,当按下 KEY0 后,DAC 输出三角波 1, LCD 屏显示 DAC Wave1 ,三角波 1 输出完成后 LCD 屏继续显示 DAC None,当按下 KEY1后,DAC 输出三角波 2,LCD 屏显示 DAC Wave2,三角波 2 输出完成后 LCD 屏继续显示 DAC None。

    其中三角波 1 和三角波 2 在示波器的显示情况如下图所示:

    由上面两副测试图可以知道,三角波 1 的频率是 64.5Hz,三角波 2 的频率是 99.5Hz。三角 波 2 基本接近我们算出来的结果 0.1KHz,三角波 1 有较大误差,在介绍 dac_triangular_wave 函 数时也说了原因,加上三角波 1 的采样率比较高,所以误差就会比较大。 

    DAC 输出正弦波实验

    本实验我们来学习使用如何让 DAC 输出正弦波。实验将用定时器 7 来触发 DAC 进行转换 输出正弦波,以 DMA 传输数据的方式。

    使用 DAC 输出正弦波,通过 KEY0/KEY1 两个按键,控制 DAC1 的通道 1 输出两种正弦 波,需要通过示波器接 PA4 进行观察。LED0 闪烁,提示程序运行。

    我们只需要把示波器的探头接到 DAC1 通道 1(PA4)引脚,就可以在示波器上显示 DAC 输出的波形。PA4 在 P7 已经引出,硬件连接如图 33.4.2.1 所示:

    HAL库函数 

     代码

    1. #include "./BSP/DAC/dac.h"
    2. DMA_HandleTypeDef g_dma_dac_handle;
    3. DAC_HandleTypeDef g_dac_dma_handle;
    4. extern uint16_t g_dac_sin_buf[4096]; /* 发送数据缓冲区 */
    5. /* DAC DMA输出波形初始化函数 */
    6. void dac_dma_wave_init(void)
    7. {
    8. DAC_ChannelConfTypeDef dac_ch_conf = {0};
    9. __HAL_RCC_DMA2_CLK_ENABLE();/* DMA2 时钟使能 */
    10. g_dma_dac_handle.Instance = DMA2_Channel3;/* 设置 DMA 通道 */
    11. g_dma_dac_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;/* 从存储器到外设模式 */
    12. g_dma_dac_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;/* 存储器数据长度:16*/
    13. g_dma_dac_handle.Init.MemInc = DMA_MINC_ENABLE;/* 存储器增量模式 */
    14. g_dma_dac_handle.Init.Mode = DMA_CIRCULAR;/* 循环模式 */
    15. g_dma_dac_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;/* 外设数据长度:16*/
    16. g_dma_dac_handle.Init.PeriphInc = DMA_PINC_DISABLE;/* 外设非增量模式 */
    17. g_dma_dac_handle.Init.Priority = DMA_PRIORITY_HIGH;/* 优先级 */
    18. HAL_DMA_Init(&g_dma_dac_handle);/* 初始化 DMA */
    19. __HAL_LINKDMA(&g_dac_dma_handle, DMA_Handle1, g_dma_dac_handle);/* DMA 句柄与 DAC 句柄关联 */
    20. g_dac_dma_handle.Instance = DAC;
    21. HAL_DAC_Init(&g_dac_dma_handle);
    22. dac_ch_conf.DAC_Trigger = DAC_TRIGGER_T7_TRGO;/* 使用 TIM7 TRGO 事件触发 */
    23. dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;/* DAC1 输出缓冲关闭 */
    24. HAL_DAC_ConfigChannel(&g_dac_dma_handle, &dac_ch_conf, DAC_CHANNEL_1);/* DAC 通道 1 配置 */
    25. /* 配置 DMA 传输参数 */
    26. HAL_DMA_Start(&g_dma_dac_handle, (uint32_t)g_dac_sin_buf, (uint32_t)&DAC->DHR12R1, 0);
    27. }
    28. /* DAC MSP底层初始化函数 */
    29. void HAL_DAC_MspInit(DAC_HandleTypeDef *hdac)
    30. {
    31. if(hdac->Instance == DAC)
    32. {
    33. GPIO_InitTypeDef gpio_init_struct = {0};
    34. __HAL_RCC_DAC_CLK_ENABLE();/* 使能 DAC1 的时钟 */
    35. __HAL_RCC_GPIOA_CLK_ENABLE();/* DAC 通道引脚端口时钟使能 */
    36. /* STM32 单片机, 总是 PA4=DAC1_OUT1, PA5=DAC1_OUT2 */
    37. gpio_init_struct.Pin = GPIO_PIN_4;
    38. gpio_init_struct.Mode = GPIO_MODE_ANALOG;/* 模拟 */
    39. HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    40. }
    41. }
    42. void dac_dma_wave_enable(uint16_t cndtr, uint16_t arr, uint16_t psc)
    43. {
    44. TIM_HandleTypeDef tim7_handle = {0};
    45. TIM_MasterConfigTypeDef tim7_master_config = {0};
    46. __HAL_RCC_TIM7_CLK_ENABLE();/* TIM7 时钟使能 */
    47. tim7_handle.Instance = TIM7;/* 选择定时器 7 */
    48. tim7_handle.Init.Prescaler = psc;/* 预分频 */
    49. tim7_handle.Init.Period = arr;/* 自动装载值 */
    50. tim7_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数器 */
    51. HAL_TIM_Base_Init(&tim7_handle);
    52. tim7_master_config.MasterOutputTrigger = TIM_TRGO_UPDATE;/* 定时器更新事件用于触发 */
    53. tim7_master_config.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    54. HAL_TIMEx_MasterConfigSynchronization(&tim7_handle, &tim7_master_config);/* 配置定时器 7 的更新事件触发 DAC 转换 */
    55. HAL_TIM_Base_Start(&tim7_handle);/* 启动定时器 7 */
    56. HAL_DAC_Stop_DMA(&g_dac_dma_handle, DAC_CHANNEL_1);/* 先停止之前的传输 */
    57. HAL_DAC_Start_DMA(&g_dac_dma_handle, DAC_CHANNEL_1, (uint32_t *)g_dac_sin_buf, cndtr, DAC_ALIGN_12B_R);
    58. }

    该函数用于初始化 DAC 用 DMA 的方式输出正弦波。本函数用到的 API 函数起前面都介 绍过,请结合前面介绍过的相关内容来理解源码。这里值得注意的是我们是采用定时器 7 触发 DAC 进行转换输出的。

    该函数用于使能波形输出,利用定时器 7 的更新事件来触发 DAC 转换输出。使能定时器 7 的时钟后,调用 HAL_TIMEx_MasterConfigSynchronization 函数配置 TIM7 选择更新事件作为触 发输出 (TRGO),然后调用 HAL_DAC_Stop_DMA 函数停止 DAC 转换以及 DMA 传输,最后 再调用 HAL_DAC_Start_DMA 函数重新配置并启动 DAC 和 DMA。

    1. #include "./SYSTEM/sys/sys.h"
    2. #include "./SYSTEM/usart/usart.h"
    3. #include "./SYSTEM/delay/delay.h"
    4. #include "./BSP/LED/led.h"
    5. #include "./BSP/LCD/lcd.h"
    6. #include "./BSP/ADC/adc.h"
    7. #include "./BSP/DAC/dac.h"
    8. #include "./BSP/KEY/key.h"
    9. #include "math.h"
    10. uint16_t g_dac_sin_buf[4096]; /* 发送数据缓冲区 */
    11. /**
    12. * @brief 产生正弦波函序列
    13. * @note 需保证: maxval > samples/2
    14. *
    15. * @param maxval : 最大值(0 < maxval < 2048)
    16. * @param samples: 采样点的个数
    17. *
    18. * @retval 无
    19. */
    20. void dac_creat_sin_buf(uint16_t maxval, uint16_t samples)
    21. {
    22. uint8_t i;
    23. float inc = (2 * 3.1415962) / samples; /* 计算增量(一个周期DAC_SIN_BUF个点)*/
    24. float outdata = 0;
    25. for (i = 0; i < samples; i++)
    26. {
    27. outdata = maxval * (1 + sin(inc * i)); /* 计算以dots个点为周期的每个点的值,放大maxval倍,并偏移到正数区域 */
    28. if (outdata > 4095)
    29. outdata = 4095; /* 上限限定 */
    30. //printf("%f\r\n",outdata);
    31. g_dac_sin_buf[i] = outdata;
    32. }
    33. }
    34. int main(void)
    35. {
    36. uint8_t t = 0;
    37. uint8_t key;
    38. HAL_Init(); /* 初始化 HAL 库 */
    39. sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    40. delay_init(72); /* 延时初始化 */
    41. usart_init(115200); /* 传口初始化 */
    42. led_init(); /* LED初始化 */
    43. lcd_init(); /* LCD初始化 */
    44. key_init(); /* KEY初始化 */
    45. /* 初始化DAC通道1 DMA波形输出 */
    46. dac_dma_wave_init();
    47. lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    48. lcd_show_string(30, 70, 200, 16, 16, "DAC DMA Sine WAVE TEST", RED);
    49. lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    50. lcd_show_string(30, 110, 200, 16, 16, "KEY0:3Khz KEY1:30Khz", RED);
    51. dac_creat_sin_buf(2048, 100);
    52. dac_dma_wave_enable(100, 10 - 1, 72 - 1);/* 100Khz触发频率, 100个点, 得到1Khz的正弦波 */
    53. while(1)
    54. {
    55. t++;
    56. key = key_scan(0); /* 按键扫描 */
    57. if (key == KEY0_PRES) /* 高采样率 , 约1Khz波形 */
    58. {
    59. dac_creat_sin_buf(2048, 100);
    60. dac_dma_wave_enable(100, 10 - 1, 24 - 1); /* 300Khz触发频率, 100个点, 得到最高3KHz的正弦波. */
    61. }
    62. else if (key == KEY1_PRES) /* 低采样率 , 约1Khz波形 */
    63. {
    64. dac_creat_sin_buf(2048, 10);
    65. dac_dma_wave_enable(10, 10 - 1, 24 - 1); /* 300Khz触发频率, 10个点, 可以得到最高30KHz的正弦波. */
    66. }
    67. if (t == 40) /* 定时时间到了 */
    68. {
    69. LED0_TOGGLE(); /* LED0闪烁 */
    70. t = 0;
    71. }
    72. delay_ms(5);
    73. }
    74. }

    dac_dma_wave_init 函数初始化 DAC 通道 1,并指定 DMA 搬运的数据的开始地址和目标 地址。dac_creat_sin_buf 函数用于产生正弦波序列,并保存在 g_dac_sin_buf 数组中,供给 DAC 转换。在进入wilhe(1)循环之前,dac_dma_wave_enable函数默认配置DAC的采样点个数时100, 并配置定时器 7 的溢出频率为 100KHz。这样就可以输出 1KHz 的正弦波。下面给大家解释一下 为什么是输出 1KHz 的正弦波?

    定时器 7 的溢出频率为 100KHz,不记得怎么计算的朋友,请回顾基本定时器的相关内容, 这里直接把公式列出:Tout= ((arr+1)*(psc+1))/Tclk

    看到 dac_dma_wave_enable(100, 10 - 1, 72 - 1);这个语句,第二个形参是自动重装载值,第 三个形参是分频系数,那么代入公式,可得:Tout= ((arr+1)*(psc+1))/Tclk= ((9+1)*(71+1))/ 72000000= 0.00001s得到定时器的更新事件周期是 0.00001 秒,即更新事件频率为 100KHz,也就得到 DAC 输 出触发频率为 100KHz。再结合总一个正弦波共有 100 个采样点,就可以得到正弦波的频率为 100KHz/100 = 1KHz。

    知道了正弦波的频率怎么来的,下面代码中,按下按键 KEY0,得到 3KHz 的正弦波,按下 按键 KEY1,得到 30KHz 的正弦波,计算方法都一样的。

    实验现象

    没有按下任何按键之前,默认输出 1KHz(100 个采样点)的正弦波,如下图所示:

    当按下 KEY0 后,DAC 输出 3KHz(100 个采样点)的正弦波,如下图所示: 当按下 KEY1 后,DAC 输出 30KHz(10 个采样点)的正弦波,如下图所示:

  • 相关阅读:
    健身房管理系统
    轻松了解JVM
    利用大模型MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7实现零样本分类
    AcWing 835. Trie字符串统计
    ubuntu安装docker
    C++之const浅谈(2)
    Java多线程-ThreadPool线程池-2(四)
    实战之AngularJS 的Scope和Service的深入应用心得
    算法之冒泡排序
    PMI认证考试成绩查询步骤指南,建议收藏!
  • 原文地址:https://blog.csdn.net/ff_juju/article/details/134430766