赞
踩
对于高职教师来说,必不可少的一个任务就是参加企业实践。这个暑假,本人也没闲着,报名参加了上海市电子信息类教师企业实践。7月8日到13日,有幸来到美丽的泉城济南,远离了上海的酷暑,走进了百科荣创科技发展有限公司。在这短短的一周时间里,我结合自己的教学经验和企业的需求,对一个简易函数信号发生器的嵌入式项目做了教学内容的转换。接下来,就用博客的形式,把这次转换后的成果分享出来,如果您觉得有用,还望多多点赞和转发!
针对本项目,笔者根据自己的理解,力求做到循序渐进和逐步深入,计划用6篇文章来展开,本文是第四篇,我们使用了DMA来搬运DAC的数据,让波形的产生更“丝滑”,频率范围更大,波形更稳。此外,除了三角波,还加入了正弦波、锯齿波和方波。
下面,我们让开发板输出四种常见的波形:正弦波、三角波、锯齿波和方波,仍然使用 TIM4_TRGO 作为触发源,同时引入DMA来搬运DAC数据,改善之前较高频率下波形不稳的缺陷。同时,可以通过按键S2来切换波形类型,S3和S4来调整频率。实验效果见文末的演示视频。
有关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所示。
如图10所示,我们在上一篇“DAC输出三角波”的工程上,修改了 dac.c 和 dac.h 里的部分代码,也修改了 main.c 文件里的控制逻辑。
(1)dac.h源码补充
代码清单11是补充后的 dac.h,可以看到增加了两个宏:Dot_X 表示一个周期内DAC数值点的个数,即DMA缓冲区的大小;DAC_DHR12R1_ADDRESS 表示存放DAC数值的寄存器地址,即DMA的目标地址。同时,还补充了一个 DAC_Data[] 全局数组,存放每一个DAC值,也是DMA的源地址。至于数组里每个元素的值,由 Generate_Wave() 函数根据波形类型和最大值来产生。
- //-------------------------------------------------------------
- // 代码清单11:补充后的dac.h
- //-------------------------------------------------------------
-
- #ifndef _DAC_H_
- #define _DAC_H_
-
- #include "sys.h"
-
- //-------------------------------------------------------------
- // 必要的宏定义
- //-------------------------------------------------------------
- //x代表三角波的不同振幅,x=0~11
- #define TriangleAmplitude(x) {DAC->CR&=0xfffff0ff; DAC->CR|=(x<<8);}
-
- //一个周期内有多少数值点
- #define Dot_X 100
-
- //DAC1的12位右对齐数据寄存器地址
- #define DAC_DHR12R1_ADDRESS DAC_BASE+0x08
-
- //-------------------------------------------------------------
- // 全局变量
- //-------------------------------------------------------------
- extern uint16_t DAC_Data[Dot_X]; //存放每个数值点的数组
-
- //-------------------------------------------------------------
- // 函数声明
- //-------------------------------------------------------------
- void DAC1_Init(void);
- void DAC1_Set_Vol(uint16_t vol);
- void Generate_Wave(uint8_t wave_mode, uint16_t vol_max, uint16_t wave[]);
-
- #endif
(2)dac.c源码补充
再来看 dac.c,如代码清单12所示。针对 DAC1_Init() 函数,补充了与DMA相关的初始化,同时把DAC的波形产生功能关了。另一方面,在 Generate_Wave() 函数中,根据各种波形的特点,编写了生成每个数值点的过程,注释中有详细描述。
- /**
- ************************************************************************
- * 代码清单12:补充后的dac.c
- * 描 述:DAC与DMA的初始化,波形产生
- * 平 台:百科荣创STM32F407核心板
- * 作 者:老耿
- * 日 期:2024-7-10
- * 固 件 库:ST3.5.0
- * 版 本:V1.0
- * 说 明:
- * 修改记录:无
- ************************************************************************
- **/
-
- //必要的头文件
- #include <math.h>
- #include "dac.h"
-
- //DAC数组初始化全0
- uint16_t DAC_Data[Dot_X] = {0};
-
- /**
- ************************************************************************
- * 函 数 名:DAC1_Init
- * 功 能:DAC通道1的配置
- * 入口参数:无
- * 出口参数:无
- * 说 明:DAC通道1对应PA4引脚,注意要配置成模拟输入
- * DAC1对应的是DMA1的通道7,数据流5
- ************************************************************************
- **/
- void DAC1_Init(void)
- {
- //定义必要的初始化结构体
- GPIO_InitTypeDef gpio_initstruct;
- DAC_InitTypeDef dac_initstruct;
- DMA_InitTypeDef dma_initstruct;
-
- //打开GPIOA、DAC和DMA1的外设时钟
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
-
- //PA4初始化为模拟输入
- gpio_initstruct.GPIO_Pin = GPIO_Pin_4;
- gpio_initstruct.GPIO_Mode = GPIO_Mode_AN;
- gpio_initstruct.GPIO_PuPd = GPIO_PuPd_DOWN;
- GPIO_Init(GPIOA, &gpio_initstruct);
-
- //TIM4的TRGO来触发DAC
- dac_initstruct.DAC_Trigger = DAC_Trigger_T4_TRGO;
- //不使用波形生成
- dac_initstruct.DAC_WaveGeneration = DAC_WaveGeneration_None;
- //DAC1输出缓存关闭
- dac_initstruct.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
- //初始化DAC通道1
- DAC_Init(DAC_Channel_1,&dac_initstruct);
-
- //复位DMA1_Stream5到默认值
- DMA_DeInit(DMA1_Stream5);
- //设置DMA通道(通道7)
- dma_initstruct.DMA_Channel = DMA_Channel_7;
- //设置DAC寄存器地址作为DMA的外设基地址
- dma_initstruct.DMA_PeripheralBaseAddr = DAC_DHR12R1_ADDRESS;
- //设置数据源的内存地址
- dma_initstruct.DMA_Memory0BaseAddr = (uint32_t)&DAC_Data[0];
- //设置DMA缓冲区大小
- dma_initstruct.DMA_BufferSize = Dot_X;
- //设置外设数据宽度为半字
- dma_initstruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
- //设置内存数据宽度为半字
- dma_initstruct.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;
- //设置DMA传输方向从内存到外设
- dma_initstruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
- //外设地址不递增
- dma_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
- //内存地址递增
- dma_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
- //设置DMA为循环模式
- dma_initstruct.DMA_Mode = DMA_Mode_Circular;
- //设置DMA的优先级为高
- dma_initstruct.DMA_Priority = DMA_Priority_High;
- //禁用DMA的FIFO模式
- dma_initstruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
- //设置FIFO阈值为半满
- dma_initstruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
- //设置内存突发单次传输
- dma_initstruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
- //设置外设突发单次传输
- dma_initstruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
- //应用以上设置初始化DMA
- DMA_Init(DMA1_Stream5, &dma_initstruct);
-
- //启动DMA传输
- DMA_Cmd(DMA1_Stream5, ENABLE);
- //使能DAC通道1
- DAC_Cmd(DAC_Channel_1, ENABLE);
- //启动DAC的DMA功能
- DAC_DMACmd(DAC_Channel_1, ENABLE);
- }
-
- /**
- ************************************************************************
- * 函 数 名:Generate_Wave
- * 功 能:产生波形
- * 入口参数:wave_mode --- 0代表正弦波,1代表三角波,2代表锯齿波,3代表方波
- * vol_max --- 0~3300,代表0~3.3V
- * wave[] --- 存储生成波形数据的数组
- * 出口参数:无
- * 说 明:无
- ************************************************************************
- **/
- void Generate_Wave(uint8_t wave_mode, uint16_t vol_max, uint16_t wave[])
- {
- uint16_t i; //循环下标
- uint16_t temp; //波形数据处理的中间变量
- float w; //正弦波的角速度
-
- switch(wave_mode)
- {
- case 0: //正弦波
- w = 2*3.14159/Dot_X; //计算每个点的角度增量,以生成正弦波
- for(i = 0; i < Dot_X; i++)
- wave[i] = 0.5*vol_max*(sin(w*i)+1); //根据正弦函数生成波形,映射到[0, vol_max]
- break;
-
- case 1: //三角波
- temp = (Dot_X%2) ? (Dot_X/2 + 1) : (Dot_X/2); //计算三角波上升和下降的转折点
- for(i = 0; i < temp; i++)
- wave[i] = i * vol_max / temp; //三角波上升部分
- for(i = temp; i < Dot_X; i++)
- wave[i] = vol_max - (vol_max*(i-temp)/temp); //三角波下降部分
- break;
-
- case 2: //锯齿波
- for(i=0; i<Dot_X; i++)
- wave[i] = vol_max*i/Dot_X; //线性增加,模拟锯齿波形状
- break;
-
- case 3: //方波
- for(i=0; i<Dot_X; i++)
- wave[i] = (i < Dot_X/2) ? 0 : vol_max; //前半周期为0,后半周期为vol_max
- break;
-
- default: break;
- }
- }
最后来看 main.c,如代码清单13所示。由于涉及到波形和频率的变化,因此用全局变量进行了定义。此外,频率的改变是通过修改TIM4的预分频系数来实现的,TIM4_Init(2, TIM_APB1/(2*Freq*Dot_X)); 这条初始化配置实现了 2us 的更新时间,一个周期即为 2us*Dot_X = 200us,即5kHz。
- /******************************************************
- * 代码清单13:main.c
- * 项 目:DAC搭配DMA,输出正弦波、三角波、锯齿波、方波
- * 任务描述:初始为正弦波,频率最大5kHz,幅值最大3.3V
- * S2切换波形,S3减小100Hz,S4增加100Hz
- * 实验平台:百科荣创STM32F407开发板
- * 作 者:老耿
- * 日 期:2024/07/10
- ******************************************************/
-
- //------------------------------------------------------
- // 必要的头文件
- //------------------------------------------------------
- #include "sys.h"
- #include "delay.h"
- #include "dac.h"
- #include "key.h"
- #include "timer4.h"
-
- //------------------------------------------------------
- // 必要的全局变量
- //------------------------------------------------------
- uint8_t Wave_Mode = 0; //当前波形,初始为正弦波
- uint8_t Wave_Mode_Bak = 4; //上次波形,初始为无关数
- uint16_t Vol_Max = 3300; //电压幅值(33为3.3V)
- uint16_t Freq = 5000; //当前频率,初始最大5kHz
- uint16_t Freq_Bak = 0; //上次频率,初始为0
-
- //------------------------------------------------------
- // 主函数
- //------------------------------------------------------
- int main(void)
- {
- uint8_t key; //存放键值
-
- //设置系统中断优先级分组2
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
-
- //先按正弦波/频率5kHz/幅值3.3V,准备好DAC数据
- Generate_Wave(Wave_Mode,Vol_Max*4096/3300-1,DAC_Data);
-
- Delay_Init(); //延时初始化
- Key_Init(); //按键初始化
- TIM4_Init(2, TIM_APB1/(2*Freq*Dot_X)); //TIM4初始化
- DAC1_Init(); //DAC通道1初始化
-
- while(1)
- {
- key = Key_Scan(0); //获取键值
-
- switch(key)
- {
- case 2: //S2切换波形
- Wave_Mode++;
- if(Wave_Mode == 4)
- Wave_Mode = 0;
- break;
-
- case 3: //S3减小频率
- if(Freq > 100)
- Freq -= 100;
- break;
-
- case 4: //S4增加频率
- if(Freq < 5000)
- Freq += 100;
- break;
-
- default: break;
- }
-
- //如果当前波形和上一次波形不一样,就更新数组
- if(Wave_Mode != Wave_Mode_Bak)
- {
- Generate_Wave(Wave_Mode,Vol_Max*4096/3300-1,DAC_Data);
- Wave_Mode_Bak = Wave_Mode;
- }
-
- //如果当前频率和上一次频率不一样,就更新频率和数组
- if(Freq != Freq_Bak)
- {
- TIM4->PSC = TIM_APB1/(2*Freq*Dot_X)-1;
- Generate_Wave(Wave_Mode,Vol_Max*4096/3300-1,DAC_Data);
- Freq_Bak = Freq;
- }
- }
- }
使用示波器来测量输出波形,如下面的视频所示。初始为正弦波,频率5kHz,幅值3.3V。每单击一次S2按键,波形切换一次;每单击一次S3按键,频率减小100Hz;每单击一次S4按键,频率增加100Hz。可以看到,搭配了DMA的DAC波形,明显稳定了许多,频率范围也更宽了。至于稳定的频率最大能达到多少,朋友们可以自行再尝试一下。
简易信号发生器的进阶,DAC与DMA的搭配,波形输出更丝滑
(第四部分完,共六部分)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。