【二代示波器教程】第12章 示波器设计—DAC信号发生器的实现
第12章 示波器设计—DAC信号发生器的实现
本章节为大家讲解二代示波器中信号发生器的实现。这个功能还是比较实用的,方便为二代示波器提供测试信号。实现了正弦波,方波和三角波的频率,幅度以及占空比设置。
12.1 DAC的输出阻抗和使能缓冲问题
12.2 DAC驱动实现
12.3 信号发生器配置界面设计
12.4 信号发生器波形显示效果
12.5 总结
12.1 DAC的输出阻抗和使能缓冲问题
我们这里把F429的输出阻抗和使能缓冲问题放在最前面说。
使能了多缓冲后发现有失真问题,即满幅输出的时候有削顶和削底,而禁止了输出缓冲会导致输出阻抗仅有10KΩ左右,外接负载很容易造成分压(可以根据实际情况,外接运放输出)。
F429的手册中对于DAC的几个关键特性说明如下:
1、开启缓冲的时候,外接的负载阻抗最小得是5KΩ。
2、禁止缓冲的时候,DAC输出阻抗最大可达15KΩ,比如要实现1%精度的输出,外接负载阻抗至少得是1.5MΩ。
3、开启缓冲的时候,最小输出电压0.2V,最大Vdda - 0.2V,这个应该是造成削顶问题的根本原因。
4、禁止缓冲的时候,最小输出电压的典型值是0.5mV,最大输出是Vref - 1LSB。基本正好满幅输出,所以效果比较好。
F429数据手册中几个关键参数的截图:
缓冲和外接负载时的框图:
禁止缓冲时,满幅输出效果比较漂亮:
使能缓冲时,满幅输出效果,出现削顶问题:
有了上面的感性认识后,下面为大家讲解DAC的驱动实现和相应的GUI界面实现。
12.2 DAC驱动实现
F429带有两个DAC,分别是DAC1和DAC2,我们这里使用了DAC1,驱动中还需要用到TIM6和DMA,方便我们配置不同的的频率,占空比和幅值。
12.2.1 第1步:引脚配置和DAC配置
配置代码如下,使用的PA4引脚做输出:
/* ********************************************************************************************************* * 函 数 名: 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。关于DAC的缓冲问题,看前面12.1小节说明即可。
12.2.2 第2步:DAC的定时器触发和DMA配置
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 / ; if (_DacFreq < ) { usPrescaler = - ; /* 分频比 = 10000 */ usPeriod = (uiTIMxCLK / ) / _DacFreq - ; /* 自动重装的值 */ } else if (_DacFreq < ) { usPrescaler = - ; /* 分频比 = 100 */ usPeriod = (uiTIMxCLK / ) / _DacFreq - ; /* 自动重装的值 */ } else /* 大于4K的频率,无需分频 */ { usPrescaler = ; /* 分频比 = 1 */ usPeriod = uiTIMxCLK / _DacFreq - ; /* 自动重装的值 */ } TIM_TimeBaseStructure.TIM_Period = usPeriod; TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler; TIM_TimeBaseStructure.TIM_ClockDivision = ; 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的配置。F429的定时器从TIM1到TIM14的主频如下:
/* ******************************************************************************** system_stm32f4xx.c 文件中 void SetSysClock(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, TIM14 APB2 定时器有 TIM1, TIM8 ,TIM9, TIM10, TIM11 TIM 更新周期是 = 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。正是因为这个原因,程序中对不同的输出频率做了范围区分。
12.2.3 第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 = ; i < ; i++) { dac = (g_SineWave128[i] * _vpp) / ; if (dac > ) { dac = ; } g_Wave1[i] = dac; } dac1_InitForDMA((uint32_t)&g_Wave1, , _freq * ); }
正弦波输出128个采样点代表一个周期,同时程序里面增加了一个幅值设置功能,范围0到4095。实际DAC输出的波形频率由前面第2步函数dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095正弦波,那么配置就是:dac1_SetSinWave(4095, 10000)。
12.2.4 第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 = ; i < (_duty * ) / ; i++) { g_Wave1[i] = _high; } for (; i < ; i++) { g_Wave1[i] = _low; } dac1_InitForDMA((uint32_t)&g_Wave1, , _freq * ); }
方波也是输出128个采样点代表一个周期,同时支持幅值和占空比的配置,其中占空比可以配置2%到98%,直接填数值2到98就可以了。实际DAC输出的波形频率由前面第2步函数dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095,占空比50%的方波,那么配置就是:dac1_SetRectWave (0, 4095, 10000, 50)。
12.2.5 第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 * ) / ; if (m == ) { m = ; } if (m > ) { m = ; } for (i = ; i < m; i++) { dac = _low + ((_high - _low) * i) / m; g_Wave1[i] = dac; } for (; i < ; i++) { dac = _high - ((_high - _low) * (i - m)) / ( - m); g_Wave1[i] = dac; } dac1_InitForDMA((uint32_t)&g_Wave1, , _freq * ); }
三角波也是输出128个采样点代表一个周期,同时支持幅值和占空比的配置,其中占空比可以配置0%到100%,不过程序中对0%和100%做了一个特殊处理。实际DAC输出的波形频率由前面第2步函数dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095,占空比50%的三角波,那么配置就是:dac1_SetTriWave (0, 4095, 10000, 50)。
12.3 信号发生器配置界面设计
信号发生器的界面设计如下:
这个操作界面简单易用,支持正弦波,方波和三角波的切换,支持占空比设置,支持幅值设置,同时也支持频率设置,限制频率范围1Hz到50KHz。超过50KHz的话,波形效果会变的越来越差。
关于这个对话框的代码实现就不在教程里面做讲解了,我们这里主要讲解下对话框上的小键盘实现。这里小键盘是一个独立的窗口,父窗口是信号发生器主窗口,通过函数WM_SendMessageNoPara发自定义消息给父窗口,在父窗口里面更新Graph控件的波形和波形信息,同时DAC的波形输出也得到更新。了解了这知识点后,再看代码就比较容易了。
知识点拓展:
新版emWin教程第51章:实用的官方小键盘实例讲解:
http://forum.armfly.com/forum.php?mod=viewthread&tid=19834 。
另外还有emWin提高篇例子的第一期ATM机里面也有用到小键盘。
http://forum.armfly.com/forum.php?mod=viewthread&tid=23687 。
12.4 信号发生器波形显示效果
下面为大家展示信号发生器输出波形效果:
方波:
正弦波:
三角波:
12.5 总结
本章节为大家讲解的信号发生器还是比较实用的,建议实际动手操作下,有兴趣的话,还可以进一步优化升级。
【二代示波器教程】第12章 示波器设计—DAC信号发生器的实现的更多相关文章
- mySQL教程 第1章 数据库设计
E-R设计 很多同学在学SQL语句时,觉得非常困难,那是因为你在学一个你根本不了解的数据库,数据库中的表不是你设计的,表与表之间的关系你不明白.因此在学SQL语句之前,先介绍一下数据库设计. 下面举例 ...
- 【安富莱二代示波器教程】第6章 示波器设计—双通道ADC驱动
第6章 示波器设计—双通道ADC驱动 本章节为大家讲解示波器的ADC驱动,采用STM32自带ADC实现.关于STM32F429的ADC,可以说处处有地雷,不小心就踩上了,如果简单的使用, ...
- 【二代示波器教程】第15章 FreeRTOS操作系统版本二代示波器实现
第15章 FreeRTOS操作系统版本二代示波器实现 本章教程为大家讲解FreeRTOS操作系统版本的二代示波器实现.主要讲解RTOS设计框架,即各个任务实现的功能,任务间的通信方案选择,任务 ...
- 【二代示波器教程】第14章 uCOS-III操作系统版本二代示波器实现
第14章 uCOS-III操作系统版本二代示波器实现 本章教程为大家讲解uCOS-III操作系统版本的二代示波器实现.主要讲解RTOS设计框架,即各个任务实现的功能,任务间的通信方案选择,任 ...
- 【二代示波器教程】第13章 RTX操作系统版本二代示波器实现
第13章 RTX操作系统版本二代示波器实现 本章教程为大家讲解RTX操作系统版本的二代示波器实现.主要讲解RTOS设计框架,即各个任务实现的功能,任务间的通信方案选择,任务栈,系统栈以及全局 ...
- 【小梅哥FPGA进阶教程】第九章 基于串口猎人软件的串口示波器
九.基于串口猎人软件的串口示波器 1.实验介绍 本实验,为芯航线开发板的综合实验,该实验利用芯航线开发板上的ADC.独立按键.UART等外设,搭建了一个具备丰富功能的数据采集卡,芯航线开发板负责进行数 ...
- 【STM32H7教程】第12章 STM32H7的HAL库框架设计学习
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第12章 STM32H7的HAL库框架设计学 ...
- JS读书心得:《JavaScript框架设计》——第12章 异步处理
一.何为异步 执行任务的过程可以被分为发起和执行两个部分. 同步执行模式:任务发起后必须等待直到任务执行完成并返回结果后,才会执行下一个任务. 异步执行模式:任务发起后不等待任务执行完成,而是马上 ...
- 【RL-TCPnet网络教程】第12章 TCP传输控制协议基础知识
第12章 TCP传输控制协议基础知识 本章节为大家讲解TCP(Transmission Control Protocol,传输控制协议),通过本章节的学习,需要大家对TCP有个基本的认识,方 ...
随机推荐
- 一个Tomcat下部署两个,甚至多个项目
是的這是我粘過來的 Tomcat目录下的结构如图: 第一步:Tomcat默认空间webapps,中已经存在一个项目了,此时要增加一个项目运行可以将原本webapps目录copa一份, 改名为webap ...
- python3+Robot Framework+PyCharm自动化测试框架设计
关于自动化测试框架的设计,笔者在前面的随笔里面有介绍和总结,这里结合实际的项目例子做个demo,环境部署参考笔者的的随笔<python3+Robot Framework+PyCharm环境部署及 ...
- mysql中null与“空值”的坑
https://blog.csdn.net/u014743697/article/details/54136092
- buaaoo_first_improvement
优化,还是不优化,这是个问题 本讨论仅基于程序基本上正确的情况下. (一)第一次作业 众所周知,本次作业没有优化到100分的都进入了B组或者C组,所以事实上本次作业的优化是十分简单的,在这里提几句. ...
- git clone pytorch或caffe2速度慢的解决办法
caffe2官方代码,现在已经放在pytorch项目中了. 因此,源码编译pytorch或caffe2,都需要 https://github.com/pytorch/pytorch 下载代码. 由于p ...
- Postman测试上传文件
postman测试上传文件 输入url:http://127.0.0.1:8081/uploadfile 选择post方式 选择body 选择form-data,text改为file 输入key: ...
- CocosCreator检测动作执行完毕的方法~之一吧,应该= =
解决方案是利用动作序列,在动作后面跟一个回调函数,然后再利用之前设置好的动作执行完毕标志,即可完成动作结束的判断!Bingo!
- [原创]大数据:布隆过滤器C#版简单实现。
public class BloomFilter { public BitArray _BloomArray; public Int64 BloomArryLength { get; } public ...
- SSH免密登录实现
现在先想要把项目部署到linux系统中 通过使用maven添加tomcat插件可以做到,右击项目 配置这里的url,是部署到哪里的意思(比如我们现在将这个项目部署到以下系统的tomcat中) 此处只有 ...
- TCP/IP详解 卷一学习笔记(转载)
https://blog.csdn.net/cpcpcp123/article/details/51259498