硬件平台:STM32F4
库类型:标准库。
参考:【二代示波器教程】第12章 示波器设计—DAC信号发生器的实现

DAC框图如下:


通过TIM触发DAC转换,转换完成后通过DMA输出。

DMA通道框图

DAC输出阻抗的问题:

DAC集成了2个输出缓存,可以用来减少输出阻抗,无须外部运放即可直接驱动外部负载。每个DAC通道输出缓存可以通过设置DAC_CR寄存器的BOFFx位来使能或者关闭,如果带载能力还不行,后面就需要接一个电压跟随器,选择运放一定要选择电流大的型号。

DAC使能输出缓冲后,DAC的最小输出电压为0.2V。最大电压为Vref±0.2(会造成削顶问题)。而未使能输出缓冲则可达到0V。

输出缓冲和外接负载时的框图。

DAC驱动实现

我们这里使用了DAC1,驱动中还需要用到TIM6和DMA,方便我们配置不同的的频率,占空比和幅值。

1. 引脚配置和DAC配置

/*
*********************************************************************************************************
*   函 数 名: bsp_InitDAC1
*   功能说明: 配置PA4/DAC1
*   形    参: 无
*   返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitDAC1(void)
{   /* 配置GPIO */{GPIO_InitTypeDef GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);/* 配置DAC引脚为模拟模式  PA4 / DAC_OUT1 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;GPIO_Init(GPIOA, &GPIO_InitStructure);}    /* DAC通道1配置 */{DAC_InitTypeDef DAC_InitStructure;/* 使能DAC时钟 */RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);       DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;  /* 选择软件触发, 软件修改DAC数据寄存器 */DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;//DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;DAC_Init(DAC_Channel_1, &DAC_InitStructure);DAC_Cmd(DAC_Channel_1, ENABLE);}
}

特别注意。程序中关闭了DAC输出缓冲,即DAC参数成员DAC_InitStructure.DAC_OutputBuffer。

2. DAC的定时器触发和DMA配置:

/*
*********************************************************************************************************
*   函 数 名: dac1_InitForDMA
*   功能说明: 配置PA4 为DAC_OUT1, 启用DMA2
*   形    参: _BufAddr : DMA数据缓冲区地址
*             _Count   : 缓冲区样本个数
*            _DacFreq  : DAC样本更新频率
*   返 回 值: 无
*********************************************************************************************************
*/
void dac1_InitForDMA(uint32_t _BufAddr, uint32_t _Count, uint32_t _DacFreq)
{   uint16_t usPeriod;uint16_t usPrescaler;uint32_t uiTIMxCLK;TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;DMA_Cmd(DMA1_Stream5, DISABLE);DAC_DMACmd(DAC_Channel_1, DISABLE);TIM_Cmd(TIM6, DISABLE);/* TIM6配置 */{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);uiTIMxCLK = SystemCoreClock / 2;if (_DacFreq < 100){usPrescaler = 10000 - 1;                         /* 分频比 = 10000 */usPeriod =  (uiTIMxCLK / 10000) / _DacFreq  - 1; /* 自动重装的值 */}else if (_DacFreq < 3000){usPrescaler = 100 - 1;                          /* 分频比 = 100 */usPeriod =  (uiTIMxCLK / 100) / _DacFreq  - 1; /* 自动重装的值 */}else   /* 大于4K的频率,无需分频 */{usPrescaler = 0;                     /* 分频比 = 1 */usPeriod = uiTIMxCLK / _DacFreq - 1; /* 自动重装的值 */}TIM_TimeBaseStructure.TIM_Period = usPeriod;TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler;TIM_TimeBaseStructure.TIM_ClockDivision = 0;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseStructure.TIM_RepetitionCounter = 0x0000;        /* TIM1 和 TIM8 必须设置 */  TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);/* 选择TIM6做DAC的触发时钟 */TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);}/* DAC通道1配置 */{DAC_InitTypeDef DAC_InitStructure;/* 使能DAC时钟 */RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);     DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;//DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;DAC_Init(DAC_Channel_1, &DAC_InitStructure);DAC_Cmd(DAC_Channel_1, ENABLE);}/* DMA1_Stream5配置 */{DMA_InitTypeDef DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);/* 配置DMA1 Stream 5 channel 7用于DAC1 */DMA_InitStructure.DMA_Channel = DMA_Channel_7;DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1; DMA_InitStructure.DMA_Memory0BaseAddr = _BufAddr; DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;        DMA_InitStructure.DMA_BufferSize = _Count;DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                                //循环模式DMA_InitStructure.DMA_Priority = DMA_Priority_High;DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA1_Stream5, &DMA_InitStructure);DMA_Cmd(DMA1_Stream5, ENABLE);/* 使能DAC通道1的DMA */DAC_DMACmd(DAC_Channel_1, ENABLE);}/* 使能定时器 */TIM_Cmd(TIM6, ENABLE);
}

通过这个函数可以方便的计算DAC的输出波形频率。 计算方法如下:

输出波形频率 = 配置的定时器触发频率 / DMA的缓冲个数 。

其中,DMA缓冲数据的个数就是输出波形一个周期的采样点数。程序中统一将其配置为128个点代表一个周期的波形。实际应用中,配置的点数不要太少, 否则波形不够漂亮。

比如,我们需要输出 10KHz的波形,这个函数的配置就是:

dac1_InitForDMA((uint32_t)&g_Wave1,128, 10000 * 128)

数组g_Wave1里面是128个波形采样点。通过修改触发频率来实现波形频率。

关于这个驱动代码,要注意TIM6的配置。F4的定时器从TIM1到TIM14的主频如下:

/*********************************************************************************system_stm32f4xx.c 文件中 voidSetSysClock(void) 函数对时钟的配置如下:HCLK = SYSCLK / 1     (AHB1Periph)PCLK2 = HCLK / 2      (APB2Periph)PCLK1 = HCLK / 4      (APB1Periph)因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = PCLK1 x 2 = SystemCoreClock/ 2;因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = PCLK2 x 2 =SystemCoreClock;APB1 定时器有 TIM2, TIM3,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14APB2 定时器有 TIM1, TIM8,TIM9, TIM10, TIM11TIM 更新周期是 = TIMCLK / (TIM_Period + 1)/(TIM_Prescaler+ 1)
********************************************************************************
*/

由此可知,TIM6的主频是SystemCoreClock / 2。当主频是168MHz时,TIM6的时钟就是84MHz,TIM6更新周期 = TIM6CLK / (TIM_Period + 1)/(TIM_Prescaler+ 1),其中

TIM_Period就是定时器结构体成员TIM_TimeBaseStructure.TIM_Period。

TIM_Prescaler就是定时器结构体成员TIM_TimeBaseStructure.TIM_Prescaler。

另外还有非常重要的一点,TIM6是16位定时器,这两个参范围是0-65535,切不要超过65535。正是因为这个原因,程序中对不同的输出频率做了范围区分。

3. 正弦波输出配置:

/*
*********************************************************************************************************
*   函 数 名: dac1_SetSinWave
*   功能说明: DAC1输出正弦波
*   形    参: _vpp : 幅度 0-4095;
*             _freq : 频率
*   返 回 值: 无
*********************************************************************************************************
*/
void dac1_SetSinWave(uint16_t _vpp, uint32_t _freq)
{   uint32_t i;uint32_t dac;TIM_Cmd(TIM6, DISABLE);/* 调整正弦波幅度 */        for (i = 0; i < 128; i++){dac = (g_SineWave128[i] * _vpp) / 4095;if (dac > 4095){dac = 4095; }g_Wave1[i] = dac;}dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);
}

正弦波输出128个采样点代表一个周期,同时程序里面增加了一个幅值设置功能,范围0到4095。实际DAC输出的波形频率由前面第2步函数 dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095正弦波,那么配置就是:dac1_SetSinWave(4095, 10000)

生成正弦波数据表

要输出正弦波,实质是要控制 DAC 以 v=sin(t)的正弦函数关系输出电压,其中 v 为电压输出,t 为时间。 而由于模拟信号连续而数字信号是离散的,所以使用 DAC 产生正弦波时,只能按一定时间间隔输出正弦曲线上的点,在该时间段内输出相同的电压值,若缩短时间间隔,提高单个周期内的输出点数,可以得到逼近连续正弦波的图形,见下图 37-3,若在外部电路加上适当的电容滤波,可得到更完美的图形。

由于正弦曲线是周期函数,所以只需要得到单个周期内的数据后按周期重复即可,而单个周期内取样输出的点数又是有限的,所以为了得到呈 v=sin(t)函数关系电压值的数据通常不会实时计算获取,而是预先计算好函数单个周期内的电压数据表,并且转化成以 DAC 寄存器表示的值。 如 sin 函数值的范围为[-1: +1],而 STM32 的 DAC 输出电压范围为[0~3.3]V,按 12 位 DAC 分辨率表示的方法,可写入寄存器的最大值为 212 = 4096,即范围为[0:4096]。所以,实际输出时,会进行如下处理:

  1. 抬升 sin 函数的输出为正值:v = sin(t)+1 ,此时,v 的输出范围为[0:2];
  2. 扩展输出至 DAC 的全电压范围: v = 3.3*(sin(t)+1)/2 ,此时,v 的输出范围为[0:3.3], 正是 DAC 的电压输出范围,扩展至全电压范围可以充分利用 DAC 的分辨率;
  3. 把电压值以 DAC 寄存器的形式表示:Reg_val = 212/3.3 * v = 211*(sin(t)+1),此时,存储到 DAC 寄存器的值范围为[0:4096];
  4. 实践证明,在 sin(t)的单个周期内,取 32 个点进行电压输出已经能较好地还原正弦波形,所以在 t∈[0:2π]区间内等间距根据上述 Reg_val 公式运算得到 32 个寄存器值,即可得到正弦波表;
  5. 控制 DAC 输出时,每隔一段相同的时间从上述正弦波表中取出一个新数据进行输出,即可输出正弦波。改变间隔时间的单位长度,可以改变正弦波曲线的周期。

生成的正弦波数据表:

/*  正弦波数据,12bit,1个周期128个点, 0-4095之间变化 */
const uint16_t g_SineWave128[] = {2047 ,  2147 ,   2248 ,  2347 ,  2446 ,  2544 ,  2641 ,  2737 ,2830 ,  2922 ,    3012 ,  3099 ,  3184 ,  3266 ,  3346 ,  3422 ,3494 ,  3564 ,    3629 ,  3691 ,  3749 ,  3803 ,  3852 ,  3897 ,3938 ,    3974 ,  4006 ,  4033 ,  4055 ,  4072 ,  4084 ,  4092 ,4094 ,    4092 ,  4084 ,  4072 ,  4055 ,  4033 ,  4006 ,  3974 ,3938 ,    3897 ,  3852 ,  3803 ,  3749 ,  3691 ,  3629 ,  3564 ,3494 ,    3422 ,  3346 ,  3266 ,  3184 ,  3099 ,  3012 ,  2922 ,2830 ,    2737 ,  2641 ,  2544 ,  2446 ,  2347 ,  2248 ,  2147 ,2047 ,    1947 ,  1846 ,  1747 ,  1648 ,  1550 ,  1453 ,  1357 ,1264 ,    1172 ,  1082 ,  995  ,  910  ,  828  ,  748  ,  672  ,600  ,    530  ,  465  ,  403  ,  345  ,  291  ,  242  ,  197  ,156  ,    120  ,  88   ,  61   ,  39   ,  22   ,  10   ,  2    ,0    ,    2    ,  10   ,  22   ,  39   ,  61   ,  88   ,  120  ,156  ,    197  ,  242  ,  291  ,  345  ,  403  ,  465  ,  530  ,600  ,    672  ,  748  ,  828  ,  910  ,  995  ,  1082 ,  1172 ,1264 ,    1357 ,  1453 ,  1550 ,  1648 ,  1747 ,  1846 ,  1947
};

4. 方波输出配置:

/*
*********************************************************************************************************
*   函 数 名: dac1_SetRectWave
*   功能说明: DAC1输出方波
*   形    参: _low  : 低电平时DAC,
*             _high : 高电平时DAC
*             _freq : 频率 Hz
*             _duty : 占空比 2% - 98%, 调节步数 1%
*   返 回 值: 无
*********************************************************************************************************
*/
void dac1_SetRectWave(uint16_t _low, uint16_t _high, uint32_t _freq, uint16_t _duty)
{   uint16_t i;TIM_Cmd(TIM6, DISABLE);for (i = 0; i < (_duty * 128) / 100; i++){g_Wave1[i] = _high;}for (; i < 128; i++){g_Wave1[i] = _low;}dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);
}

方波也是输出128个采样点代表一个周期,同时支持幅值和占空比的配置,其中占空比可以配置2%到98%,直接填数值2到98就可以了。实际DAC输出的波形频率由前面第2步函数dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095,占空比50%的方波,那么配置就是:dac1_SetRectWave(0, 4095, 10000, 50)。

5. 三角波输出配置:

/*
*********************************************************************************************************
*   函 数 名: dac1_SetTriWave
*   功能说明: DAC1输出三角波
*   形    参: _low : 低电平时DAC,
*             _high : 高电平时DAC
*             _freq : 频率 Hz
*             _duty : 占空比
*   返 回 值: 无
*********************************************************************************************************
*/
void dac1_SetTriWave(uint16_t _low, uint16_t _high, uint32_t _freq, uint16_t _duty)
{   uint32_t i;uint16_t dac;uint16_t m;TIM_Cmd(TIM6, DISABLE);/* 构造三角波数组,128个样本,从 _low 到 _high */     m = (_duty * 128) / 100;if (m == 0){m = 1;}if (m > 127){m = 127;}for (i = 0; i < m; i++){dac = _low + ((_high - _low) * i) / m;g_Wave1[i] = dac;}for (; i < 128; i++){dac = _high - ((_high - _low) * (i - m)) / (128 - m);g_Wave1[i] = dac;}   dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);
}

三角波也是输出128个采样点代表一个周期,同时支持幅值和占空比的配置,其中占空比可以配置0%到100%,不过程序中对0%和100%做了一个特殊处理。实际DAC输出的波形频率由前面第2步函数dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095,占空比50%的三角波,那么配置就是:dac1_SetTriWave (0, 4095, 10000, 50)

STM32 DAC + DMA + TIM 输出正弦波,三角波,方波信号相关推荐

  1. STM32单片机可变频率幅度DDS信号发生器正弦波三角波方波AD9833

    实践制作DIY- GC0094-DDS信号发生器 一.功能说明: 基于STM32单片机设计-DDS信号发生器 功能介绍: 硬件组成:STM32F103C系列最小系统板 +LCD1602显示器+AD98 ...

  2. dac0832三角波c语言程序,单片机控制DAC0832输出正弦波三角波汇编程序

    org 0000h LJMP MAIN ORG 0003H LJMP L0 MAIN:MOV R2,#0aH                    ;调幅倍数 MOV R4,#01H          ...

  3. 基于STM32+DAC+DMA和AD9850的波形发生器

    基于STM32+DAC+DMA和AD9850的波形发生器 试验目的 一.通过STM32单片机DAC+DMA产生频率可调正弦波.三角波.锯齿波.方波. 二.使用STM32驱动AD9850波形发生模块产生 ...

  4. STM32 DAC DMA 使用

    目的:STM32 DAC DMA 环形发送音频数据:(ffmpeg.exe 可以将一些常见的音频文件转为原始数据,很强大): 用到的外设:DAC ,TIM,DMA DCA配置 TIM配置 加入代码 H ...

  5. ICL8038信号发生器 正弦波 方波 三角波 低频信号发生 波形发生 原理图和PCB

    ICL8038信号发生器 正弦波 方波 三角波 低频信号发生 波形发生 原理图和PCB 目录 ICL8038信号发生器 正弦波 方波 三角波 低频信号发生 波形发生 原理图和PCB 基本原理 芯片选型 ...

  6. matlab电路仿真三角波,Matlab仿真: 1Mhz正弦载波与2kHz三角波调制信号进行调制仿真...

    %Matlab仿真: 1Mhz正弦载波与2kHz三角波调制信号进行调制仿真 %======================= %1,2,5,2kHz三角波的产生: clear;clc; f_tri=1 ...

  7. DAC+DMA+TIM实现音频播放问题记录

    目录 1. 概述 2. 音频采样率 2.1 定时器触发周期 2.2 音频文件的格式 3. DAC的左对齐和右对齐 3.1 为什么要使用左对齐 3.2 左对齐数据的读写 3.3 音频数据的使用 3.3. ...

  8. 单片机c语言1ms 2ms 4ms方波,定时器使用:利用单片机内部定时器0通过P1.0端口输出一定周期的方波信号。 - 试题答案网问答...

    相关题目与解析 使用定时/计数器0以工作方式2实现定时,在P1.0输出周期为200s的连续方波.已知晶振频率fosc=6MHz. 已知单片机的晶振频率为6MHz,下面程序用单片机内部定时器T0工作在方 ...

  9. dac0832产生梯形波程序C语言,在8086系统中用DAC0832输出一个三角波,一个梯形波,和一个正弦波。...

    满意答案 晓玫1022 2014.09.28 采纳率:57%    等级:13 已帮助:8429人 MOV DX,PORTD MOV AL,0FFH Repeat:INC AL OUT DX,AL J ...

最新文章

  1. Android 开发者必知的开发资源
  2. Codeforces Round #632 (Div. 2) C和D和F
  3. pandas改变dataframe的列的顺序、改变数据列的排列次序
  4. 第二届中国云计算应用论坛圆满落幕
  5. ue4 改变枢轴位置_【UE4地形】轻松实现UE4自动地貌和自动植被分布
  6. JQuery3 的新变化
  7. jQuery ajax发送POST、JS url跳转、console用法
  8. no amd graphics driver怎么解决_《英雄联盟手游》卡顿怎么解决 游戏设置优化教程...
  9. 很少使用“ ControlFlowException”
  10. [jQuery] ajax跨域处理方式
  11. Silverlight中使用MVVM
  12. 【牛腩新闻发布系统】开始
  13. LaTex 符号大全
  14. a7100换电池_如何评价三星galaxy A7100(2016版)?
  15. FFmpeg 视频裁剪
  16. 实验三 大数据可视化工具—ECharts
  17. 20210107WEB渗透学习之信息收集
  18. 重装系统,超详细教程
  19. Python智能机械助理
  20. php微信调用天气api,微信公众号接口开发--snoweek测试

热门文章

  1. java如何通过client客戶端http实现get/ post请求传递json参数到restful 服务接口
  2. win10+vs2017配置MPI和OpenMP
  3. linux查询历史登入系统的主机名称或IP地址
  4. HDU6194 后缀数组
  5. 支持向量机原理_支持向量机
  6. 2017.7.27 计算机编程培训第二天
  7. 数字签名与数字证书技术简介(二)
  8. 无法访问您试图使用的功能所在的网络位置 无法找到vcredist.msi的解决办法
  9. 操作系统-进程调度实验报告
  10. 计蒜客 2017 ACM-ICPC 亚洲区(西安赛区)网络赛 B coin(求乘法逆元)