我在百科荣创企业实践——简易函数信号发生器(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() 函数根据波形类型和最大值来产生。

//-------------------------------------------------------------
// 代码清单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;
	}
}

5.4 main.c源码剖析

        最后来看 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;
		}
	}		
}

5.5 验证与测试

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

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

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

  • 20
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南耿先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值