当前位置:   article > 正文

我在百科荣创企业实践——简易函数信号发生器(4)

我在百科荣创企业实践——简易函数信号发生器(4)

        对于高职教师来说,必不可少的一个任务就是参加企业实践。这个暑假,本人也没闲着,报名参加了上海市电子信息类教师企业实践。7月8日到13日,有幸来到美丽的泉城济南,远离了上海的酷暑,走进了百科荣创科技发展有限公司。在这短短的一周时间里,我结合自己的教学经验和企业的需求,对一个简易函数信号发生器的嵌入式项目做了教学内容的转换。接下来,就用博客的形式,把这次转换后的成果分享出来,如果您觉得有用,还望多多点赞和转发!


        针对本项目,笔者根据自己的理解,力求做到循序渐进和逐步深入,计划用6篇文章来展开,本文是第四篇,我们使用了DMA来搬运DAC的数据,让波形的产生更“丝滑”,频率范围更大,波形更稳。此外,除了三角波,还加入了正弦波、锯齿波和方波。

五、DAC输出多种波形

        下面,我们让开发板输出四种常见的波形:正弦波、三角波、锯齿波和方波,仍然使用 TIM4_TRGO 作为触发源,同时引入DMA来搬运DAC数据,改善之前较高频率下波形不稳的缺陷。同时,可以通过按键S2来切换波形类型,S3和S4来调整频率。实验效果见文末的演示视频。

5.1 DAC与DMA的结合

        有关STM32F4系列的DMA基础知识,本文不做展开介绍,不了解这部分内容的朋友请先查阅相关资料。之所引入DMA,就是因为连续转换和输出DAC数据,是一个单调却很耗CPU资源的工作。这也是上一个版本的不足,频率高一点,波形的稳定性就被“拖累”了。而DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与外设开辟一条直接传送数据的通路, 能使CPU的效率大为提高。

        STM32F4最多有2个DMA控制器(DMA1和DMA2),共16个数据流(每个控制器8个),每个DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8个通道(或称请求),查阅STM32F4手册的9.3.3小节可知,DAC1数据走的是DMA1的通道7和数据流5,如表1所示。

表1 DMA1的映射请求

5.2 工程文件清单

        如图10所示,我们在上一篇“DAC输出三角波”的工程上,修改了 dac.c 和 dac.h 里的部分代码,也修改了 main.c 文件里的控制逻辑。 

图10 DAC输出多波形的工程文件清单

5.3 工程代码剖析

(1)dac.h源码补充

        代码清单11是补充后的 dac.h,可以看到增加了两个宏:Dot_X 表示一个周期内DAC数值点的个数,即DMA缓冲区的大小;DAC_DHR12R1_ADDRESS 表示存放DAC数值的寄存器地址,即DMA的目标地址。同时,还补充了一个 DAC_Data[] 全局数组,存放每一个DAC值,也是DMA的源地址。至于数组里每个元素的值,由 Generate_Wave() 函数根据波形类型和最大值来产生。

  1. //-------------------------------------------------------------
  2. // 代码清单11:补充后的dac.h
  3. //-------------------------------------------------------------
  4. #ifndef _DAC_H_
  5. #define _DAC_H_
  6. #include "sys.h"
  7. //-------------------------------------------------------------
  8. // 必要的宏定义
  9. //-------------------------------------------------------------
  10. //x代表三角波的不同振幅,x=0~11
  11. #define TriangleAmplitude(x) {DAC->CR&=0xfffff0ff; DAC->CR|=(x<<8);}
  12. //一个周期内有多少数值点
  13. #define Dot_X 100
  14. //DAC1的12位右对齐数据寄存器地址
  15. #define DAC_DHR12R1_ADDRESS DAC_BASE+0x08
  16. //-------------------------------------------------------------
  17. // 全局变量
  18. //-------------------------------------------------------------
  19. extern uint16_t DAC_Data[Dot_X]; //存放每个数值点的数组
  20. //-------------------------------------------------------------
  21. // 函数声明
  22. //-------------------------------------------------------------
  23. void DAC1_Init(void);
  24. void DAC1_Set_Vol(uint16_t vol);
  25. void Generate_Wave(uint8_t wave_mode, uint16_t vol_max, uint16_t wave[]);
  26. #endif

(2)dac.c源码补充

        再来看 dac.c,如代码清单12所示。针对 DAC1_Init() 函数,补充了与DMA相关的初始化,同时把DAC的波形产生功能关了。另一方面,在 Generate_Wave() 函数中,根据各种波形的特点,编写了生成每个数值点的过程,注释中有详细描述。

  1. /**
  2. ************************************************************************
  3. * 代码清单12:补充后的dac.c
  4. * 描 述:DAC与DMA的初始化,波形产生
  5. * 平 台:百科荣创STM32F407核心板
  6. * 作 者:老耿
  7. * 日 期:2024-7-10
  8. * 固 件 库:ST3.5.0
  9. * 版 本:V1.0
  10. * 说 明:
  11. * 修改记录:无
  12. ************************************************************************
  13. **/
  14. //必要的头文件
  15. #include <math.h>
  16. #include "dac.h"
  17. //DAC数组初始化全0
  18. uint16_t DAC_Data[Dot_X] = {0};
  19. /**
  20. ************************************************************************
  21. * 函 数 名:DAC1_Init
  22. * 功 能:DAC通道1的配置
  23. * 入口参数:无
  24. * 出口参数:无
  25. * 说 明:DAC通道1对应PA4引脚,注意要配置成模拟输入
  26. * DAC1对应的是DMA1的通道7,数据流5
  27. ************************************************************************
  28. **/
  29. void DAC1_Init(void)
  30. {
  31. //定义必要的初始化结构体
  32. GPIO_InitTypeDef gpio_initstruct;
  33. DAC_InitTypeDef dac_initstruct;
  34. DMA_InitTypeDef dma_initstruct;
  35. //打开GPIOA、DAC和DMA1的外设时钟
  36. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  37. RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  38. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
  39. //PA4初始化为模拟输入
  40. gpio_initstruct.GPIO_Pin = GPIO_Pin_4;
  41. gpio_initstruct.GPIO_Mode = GPIO_Mode_AN;
  42. gpio_initstruct.GPIO_PuPd = GPIO_PuPd_DOWN;
  43. GPIO_Init(GPIOA, &gpio_initstruct);
  44. //TIM4的TRGO来触发DAC
  45. dac_initstruct.DAC_Trigger = DAC_Trigger_T4_TRGO;
  46. //不使用波形生成
  47. dac_initstruct.DAC_WaveGeneration = DAC_WaveGeneration_None;
  48. //DAC1输出缓存关闭
  49. dac_initstruct.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
  50. //初始化DAC通道1
  51. DAC_Init(DAC_Channel_1,&dac_initstruct);
  52. //复位DMA1_Stream5到默认值
  53. DMA_DeInit(DMA1_Stream5);
  54. //设置DMA通道(通道7)
  55. dma_initstruct.DMA_Channel = DMA_Channel_7;
  56. //设置DAC寄存器地址作为DMA的外设基地址
  57. dma_initstruct.DMA_PeripheralBaseAddr = DAC_DHR12R1_ADDRESS;
  58. //设置数据源的内存地址
  59. dma_initstruct.DMA_Memory0BaseAddr = (uint32_t)&DAC_Data[0];
  60. //设置DMA缓冲区大小
  61. dma_initstruct.DMA_BufferSize = Dot_X;
  62. //设置外设数据宽度为半字
  63. dma_initstruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  64. //设置内存数据宽度为半字
  65. dma_initstruct.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;
  66. //设置DMA传输方向从内存到外设
  67. dma_initstruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  68. //外设地址不递增
  69. dma_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  70. //内存地址递增
  71. dma_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
  72. //设置DMA为循环模式
  73. dma_initstruct.DMA_Mode = DMA_Mode_Circular;
  74. //设置DMA的优先级为高
  75. dma_initstruct.DMA_Priority = DMA_Priority_High;
  76. //禁用DMA的FIFO模式
  77. dma_initstruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
  78. //设置FIFO阈值为半满
  79. dma_initstruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  80. //设置内存突发单次传输
  81. dma_initstruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  82. //设置外设突发单次传输
  83. dma_initstruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  84. //应用以上设置初始化DMA
  85. DMA_Init(DMA1_Stream5, &dma_initstruct);
  86. //启动DMA传输
  87. DMA_Cmd(DMA1_Stream5, ENABLE);
  88. //使能DAC通道1
  89. DAC_Cmd(DAC_Channel_1, ENABLE);
  90. //启动DAC的DMA功能
  91. DAC_DMACmd(DAC_Channel_1, ENABLE);
  92. }
  93. /**
  94. ************************************************************************
  95. * 函 数 名:Generate_Wave
  96. * 功 能:产生波形
  97. * 入口参数:wave_mode --- 0代表正弦波,1代表三角波,2代表锯齿波,3代表方波
  98. * vol_max --- 0~3300,代表0~3.3V
  99. * wave[] --- 存储生成波形数据的数组
  100. * 出口参数:无
  101. * 说 明:无
  102. ************************************************************************
  103. **/
  104. void Generate_Wave(uint8_t wave_mode, uint16_t vol_max, uint16_t wave[])
  105. {
  106. uint16_t i; //循环下标
  107. uint16_t temp; //波形数据处理的中间变量
  108. float w; //正弦波的角速度
  109. switch(wave_mode)
  110. {
  111. case 0: //正弦波
  112. w = 2*3.14159/Dot_X; //计算每个点的角度增量,以生成正弦波
  113. for(i = 0; i < Dot_X; i++)
  114. wave[i] = 0.5*vol_max*(sin(w*i)+1); //根据正弦函数生成波形,映射到[0, vol_max]
  115. break;
  116. case 1: //三角波
  117. temp = (Dot_X%2) ? (Dot_X/2 + 1) : (Dot_X/2); //计算三角波上升和下降的转折点
  118. for(i = 0; i < temp; i++)
  119. wave[i] = i * vol_max / temp; //三角波上升部分
  120. for(i = temp; i < Dot_X; i++)
  121. wave[i] = vol_max - (vol_max*(i-temp)/temp); //三角波下降部分
  122. break;
  123. case 2: //锯齿波
  124. for(i=0; i<Dot_X; i++)
  125. wave[i] = vol_max*i/Dot_X; //线性增加,模拟锯齿波形状
  126. break;
  127. case 3: //方波
  128. for(i=0; i<Dot_X; i++)
  129. wave[i] = (i < Dot_X/2) ? 0 : vol_max; //前半周期为0,后半周期为vol_max
  130. break;
  131. default: break;
  132. }
  133. }

5.4 main.c源码剖析

        最后来看 main.c,如代码清单13所示。由于涉及到波形和频率的变化,因此用全局变量进行了定义。此外,频率的改变是通过修改TIM4的预分频系数来实现的,TIM4_Init(2, TIM_APB1/(2*Freq*Dot_X)); 这条初始化配置实现了 2us 的更新时间,一个周期即为 2us*Dot_X = 200us,即5kHz。

  1. /******************************************************
  2. * 代码清单13:main.c
  3. * 项 目:DAC搭配DMA,输出正弦波、三角波、锯齿波、方波
  4. * 任务描述:初始为正弦波,频率最大5kHz,幅值最大3.3V
  5. * S2切换波形,S3减小100Hz,S4增加100Hz
  6. * 实验平台:百科荣创STM32F407开发板
  7. * 作 者:老耿
  8. * 日 期:2024/07/10
  9. ******************************************************/
  10. //------------------------------------------------------
  11. // 必要的头文件
  12. //------------------------------------------------------
  13. #include "sys.h"
  14. #include "delay.h"
  15. #include "dac.h"
  16. #include "key.h"
  17. #include "timer4.h"
  18. //------------------------------------------------------
  19. // 必要的全局变量
  20. //------------------------------------------------------
  21. uint8_t Wave_Mode = 0; //当前波形,初始为正弦波
  22. uint8_t Wave_Mode_Bak = 4; //上次波形,初始为无关数
  23. uint16_t Vol_Max = 3300; //电压幅值(33为3.3V)
  24. uint16_t Freq = 5000; //当前频率,初始最大5kHz
  25. uint16_t Freq_Bak = 0; //上次频率,初始为0
  26. //------------------------------------------------------
  27. // 主函数
  28. //------------------------------------------------------
  29. int main(void)
  30. {
  31. uint8_t key; //存放键值
  32. //设置系统中断优先级分组2
  33. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  34. //先按正弦波/频率5kHz/幅值3.3V,准备好DAC数据
  35. Generate_Wave(Wave_Mode,Vol_Max*4096/3300-1,DAC_Data);
  36. Delay_Init(); //延时初始化
  37. Key_Init(); //按键初始化
  38. TIM4_Init(2, TIM_APB1/(2*Freq*Dot_X)); //TIM4初始化
  39. DAC1_Init(); //DAC通道1初始化
  40. while(1)
  41. {
  42. key = Key_Scan(0); //获取键值
  43. switch(key)
  44. {
  45. case 2: //S2切换波形
  46. Wave_Mode++;
  47. if(Wave_Mode == 4)
  48. Wave_Mode = 0;
  49. break;
  50. case 3: //S3减小频率
  51. if(Freq > 100)
  52. Freq -= 100;
  53. break;
  54. case 4: //S4增加频率
  55. if(Freq < 5000)
  56. Freq += 100;
  57. break;
  58. default: break;
  59. }
  60. //如果当前波形和上一次波形不一样,就更新数组
  61. if(Wave_Mode != Wave_Mode_Bak)
  62. {
  63. Generate_Wave(Wave_Mode,Vol_Max*4096/3300-1,DAC_Data);
  64. Wave_Mode_Bak = Wave_Mode;
  65. }
  66. //如果当前频率和上一次频率不一样,就更新频率和数组
  67. if(Freq != Freq_Bak)
  68. {
  69. TIM4->PSC = TIM_APB1/(2*Freq*Dot_X)-1;
  70. Generate_Wave(Wave_Mode,Vol_Max*4096/3300-1,DAC_Data);
  71. Freq_Bak = Freq;
  72. }
  73. }
  74. }

5.5 验证与测试

        使用示波器来测量输出波形,如下面的视频所示。初始为正弦波,频率5kHz,幅值3.3V。每单击一次S2按键,波形切换一次;每单击一次S3按键,频率减小100Hz;每单击一次S4按键,频率增加100Hz。可以看到,搭配了DMA的DAC波形,明显稳定了许多,频率范围也更宽了。至于稳定的频率最大能达到多少,朋友们可以自行再尝试一下。

简易信号发生器的进阶,DAC与DMA的搭配,波形输出更丝滑

(第四部分完,共六部分)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/847582?site
推荐阅读
相关标签
  

闽ICP备14008679号