硬汉嵌入式论坛

 找回密码
 立即注册
查看: 2177|回复: 6
收起左侧

【安富莱二代示波器教程】第12章 示波器设计—DAC信号发生器的实现

[复制链接]

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
发表于 2018-7-9 16:50:37 | 显示全部楼层 |阅读模式
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=45785


第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数据手册中几个关键参数的截图:
12.1.png
                              
缓冲和外接负载时的框图:
12.2.png
禁止缓冲时,满幅输出效果比较漂亮:
12.3.png
使能缓冲时,满幅输出效果,出现削顶问题:
12.4.png
有了上面的感性认识后,下面为大家讲解DAC的驱动实现和相应的GUI界面实现。


努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2018-7-9 16:54:27 | 显示全部楼层
12.2  DAC驱动实现
    F429带有两个DAC,分别是DAC1和DAC2,我们这里使用了DAC1,驱动中还需要用到TIM6和DMA,方便我们配置不同的的频率,占空比和幅值。

12.2.1  第1步:引脚配置和DAC配置
    配置代码如下,使用的PA4引脚做输出:
  1. /*

  2. *********************************************************************************************************

  3. *    函 数 名: bsp_InitDAC1

  4. *    功能说明: 配置PA4/DAC1

  5. *    形    参: 无

  6. *    返 回 值: 无

  7. *********************************************************************************************************

  8. */

  9. void bsp_InitDAC1(void)

  10. {   

  11.      /* 配置GPIO */

  12.      {

  13.          GPIO_InitTypeDef GPIO_InitStructure;

  14.         

  15.          RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

  16.         

  17.          /* 配置DAC引脚为模拟模式  PA4 / DAC_OUT1 */

  18.          GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;

  19.          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;

  20.          GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

  21.          GPIO_Init(GPIOA, &GPIO_InitStructure);

  22.      }   



  23.      /* DAC通道1配置 */

  24.      {

  25.          DAC_InitTypeDef DAC_InitStructure;

  26.         

  27.          /* 使能DAC时钟 */

  28.          RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);   



  29.          DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;  /* 选择软件触发, 软件修改DAC数据寄存器 */

  30.          DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;

  31.          DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;

  32.          //DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;

  33.          DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;

  34.          DAC_Init(DAC_Channel_1, &DAC_InitStructure);

  35.          DAC_Cmd(DAC_Channel_1, ENABLE);

  36.      }

  37. }
复制代码
特别注意。程序中关闭了DAC输出缓冲,即DAC参数成员DAC_InitStructure.DAC_OutputBuffer。关于DAC的缓冲问题,看前面12.1小节说明即可。

12.2.2 第2步:DAC的定时器触发和DMA配置
    DAC的定时器触发和DMA配置如下:
  1. /*

  2. *********************************************************************************************************

  3. *    函 数 名: dac1_InitForDMA

  4. *    功能说明: 配置PA4 为DAC_OUT1, 启用DMA2

  5. *    形    参: _BufAddr : DMA数据缓冲区地址

  6. *               _Count   : 缓冲区样本个数

  7. *             _DacFreq  : DAC样本更新频率

  8. *    返 回 值: 无

  9. *********************************************************************************************************

  10. */

  11. void dac1_InitForDMA(uint32_t _BufAddr, uint32_t _Count, uint32_t _DacFreq)

  12. {   

  13.      uint16_t usPeriod;

  14.      uint16_t usPrescaler;

  15.      uint32_t uiTIMxCLK;

  16.      TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

  17.    

  18.      DMA_Cmd(DMA1_Stream5, DISABLE);

  19.      DAC_DMACmd(DAC_Channel_1, DISABLE);

  20.      TIM_Cmd(TIM6, DISABLE);

  21.    



  22.      /* TIM6配置 */

  23.      {

  24.          RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);



  25.          uiTIMxCLK = SystemCoreClock / 2;

  26.         

  27.          if (_DacFreq < 100)

  28.          {

  29.               usPrescaler = 10000 - 1;                         /* 分频比 = 10000 */

  30.               usPeriod =  (uiTIMxCLK / 10000) / _DacFreq  - 1; /* 自动重装的值 */

  31.          }

  32.          else if (_DacFreq < 3000)

  33.          {

  34.               usPrescaler = 100 - 1;                         /* 分频比 = 100 */

  35.               usPeriod =  (uiTIMxCLK / 100) / _DacFreq  - 1; /* 自动重装的值 */

  36.          }

  37.          else /* 大于4K的频率,无需分频 */

  38.          {

  39.               usPrescaler = 0;                     /* 分频比 = 1 */

  40.               usPeriod = uiTIMxCLK / _DacFreq - 1; /* 自动重装的值 */

  41.          }



  42.          TIM_TimeBaseStructure.TIM_Period = usPeriod;

  43.          TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler;

  44.          TIM_TimeBaseStructure.TIM_ClockDivision = 0;

  45.          TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  46.          TIM_TimeBaseStructure.TIM_RepetitionCounter = 0x0000;        /* TIM1 和 TIM8 必须设置 */



  47.          TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);



  48.           /* 选择TIM6做DAC的触发时钟 */

  49.          TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);

  50.      }



  51.      /* DAC通道1配置 */

  52.      {

  53.          DAC_InitTypeDef DAC_InitStructure;

  54.         

  55.          /* 使能DAC时钟 */

  56.          RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);   



  57.          DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;

  58.          DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;

  59.          DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;

  60.          //DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;

  61.          DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;

  62.          DAC_Init(DAC_Channel_1, &DAC_InitStructure);

  63.          DAC_Cmd(DAC_Channel_1, ENABLE);

  64.      }



  65.      /* DMA1_Stream5配置 */

  66.      {

  67.          DMA_InitTypeDef DMA_InitStructure;



  68.          RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);



  69.          /* 配置DMA1 Stream 5 channel 7用于DAC1 */

  70.          DMA_InitStructure.DMA_Channel = DMA_Channel_7;

  71.          DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1;

  72.          DMA_InitStructure.DMA_Memory0BaseAddr = _BufAddr;

  73.          DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;     

  74.          DMA_InitStructure.DMA_BufferSize = _Count;

  75.          DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

  76.          DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

  77.          DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

  78.          DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;

  79.          DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

  80.          DMA_InitStructure.DMA_Priority = DMA_Priority_High;

  81.          DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;

  82.          DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;

  83.          DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

  84.          DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

  85.          DMA_Init(DMA1_Stream5, &DMA_InitStructure);

  86.          DMA_Cmd(DMA1_Stream5, ENABLE);



  87.          /* 使能DAC通道1的DMA */

  88.          DAC_DMACmd(DAC_Channel_1, ENABLE);

  89.      }



  90.      /* 使能定时器 */

  91.      TIM_Cmd(TIM6, ENABLE);

  92. }
复制代码
通过这个函数可以方便的设置DAC的输出波形频率。计算方法是:
配置的定时器触发频率 / DMA的缓冲个数 = 输出波形频率
其中DMA缓冲数据的个数就是输出波形一个周期的采样点数。程序中统一将其配置为128个点代表一个周期的波形,大家实际应用中配置的点数不要太少,否则波形不够漂亮。比如我们要出10KHz的波形,这个函数的配置就是:dac1_InitForDMA((uint32_t)&g_Wave1,128, 10000 * 128); 数组g_Wave1里面是128个波形采样点。
   关于这个驱动代码,要注意TIM6的配置。F429的定时器从TIM1到TIM14的主频如下:
  1. /*

  2. ********************************************************************************

  3. system_stm32f4xx.c 文件中 void SetSysClock(void) 函数对时钟的配置如下:



  4. HCLK = SYSCLK / 1     (AHB1Periph)

  5. PCLK2 = HCLK / 2      (APB2Periph)

  6. PCLK1 = HCLK / 4      (APB1Periph)



  7. 因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = PCLK1 x 2 = SystemCoreClock / 2;

  8. 因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = PCLK2 x 2 = SystemCoreClock;



  9. APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14

  10. APB2 定时器有 TIM1, TIM8 ,TIM9, TIM10, TIM11



  11. TIM 更新周期是 = TIMCLK / (TIM_Period + 1)/(TIM_Prescaler + 1)

  12. ********************************************************************************

  13. */
复制代码
由此可知,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步:正弦波输出配置
    正弦波的输出配置如下:
  1. /*

  2. *********************************************************************************************************

  3. *    函 数 名: dac1_SetSinWave

  4. *    功能说明: DAC1输出正弦波

  5. *    形    参: _vpp : 幅度 0-4095;

  6. *               _freq : 频率

  7. *    返 回 值: 无

  8. *********************************************************************************************************

  9. */

  10. void dac1_SetSinWave(uint16_t _vpp, uint32_t _freq)

  11. {   

  12.      uint32_t i;

  13.      uint32_t dac;

  14.    

  15.      TIM_Cmd(TIM6, DISABLE);

  16.         

  17.      /* 调整正弦波幅度 */      

  18.      for (i = 0; i < 128; i++)

  19.      {

  20.          dac = (g_SineWave128[i] * _vpp) / 4095;

  21.          if (dac > 4095)

  22.          {

  23.               dac = 4095;  

  24.          }

  25.          g_Wave1[i] = dac;

  26.      }

  27.    

  28.      dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);

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

12.2.4 第4步:方波输出配置
    方波的输出配置如下:
  1. /*

  2. *********************************************************************************************************

  3. *    函 数 名: dac1_SetRectWave

  4. *    功能说明: DAC1输出方波

  5. *    形    参: _low  : 低电平时DAC,

  6. *               _high : 高电平时DAC

  7. *               _freq : 频率 Hz

  8. *               _duty : 占空比 2% - 98%, 调节步数 1%

  9. *    返 回 值: 无

  10. *********************************************************************************************************

  11. */

  12. void dac1_SetRectWave(uint16_t _low, uint16_t _high, uint32_t _freq, uint16_t _duty)

  13. {   

  14.      uint16_t i;

  15.      TIM_Cmd(TIM6, DISABLE);

  16.    

  17.      for (i = 0; i < (_duty * 128) / 100; i++)

  18.      {

  19.          g_Wave1[i] = _high;

  20.      }

  21.      for (; i < 128; i++)

  22.      {

  23.          g_Wave1[i] = _low;

  24.      }

  25.    

  26.      dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);

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

12.2.5 第5步:三角波输出配置
    三角波的输出配置如下:
  1. /*

  2. *********************************************************************************************************

  3. *    函 数 名: dac1_SetTriWave

  4. *    功能说明: DAC1输出三角波

  5. *    形    参: _low : 低电平时DAC,

  6. *               _high : 高电平时DAC

  7. *               _freq : 频率 Hz

  8. *               _duty : 占空比

  9. *    返 回 值: 无

  10. *********************************************************************************************************

  11. */

  12. void dac1_SetTriWave(uint16_t _low, uint16_t _high, uint32_t _freq, uint16_t _duty)

  13. {   

  14.      uint32_t i;

  15.      uint16_t dac;

  16.      uint16_t m;

  17.    

  18.      TIM_Cmd(TIM6, DISABLE);

  19.          

  20.      /* 构造三角波数组,128个样本,从 _low 到 _high */      

  21.      m = (_duty * 128) / 100;

  22.    

  23.      if (m == 0)

  24.      {

  25.          m = 1;

  26.      }

  27.    

  28.      if (m > 127)

  29.      {

  30.          m = 127;

  31.      }

  32.      for (i = 0; i < m; i++)

  33.      {

  34.          dac = _low + ((_high - _low) * i) / m;

  35.          g_Wave1[i] = dac;

  36.      }

  37.      for (; i < 128; i++)

  38.      {

  39.          dac = _high - ((_high - _low) * (i - m)) / (128 - m);

  40.          g_Wave1[i] = dac;

  41.      }   

  42.    

  43.      dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);

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

努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2018-7-9 16:55:41 | 显示全部楼层
12.3 信号发生器配置界面设计
    信号发生器的界面设计如下:
12.5.png
                              
这个操作界面简单易用,支持正弦波,方波和三角波的切换,支持占空比设置,支持幅值设置,同时也支持频率设置,限制频率范围1Hz到50KHz。超过50KHz的话,波形效果会变的越来越差。
    关于这个对话框的代码实现就不在教程里面做讲解了,我们这里主要讲解下对话框上的小键盘实现。这里小键盘是一个独立的窗口,父窗口是信号发生器主窗口,通过函数WM_SendMessageNoPara发自定义消息给父窗口,在父窗口里面更新Graph控件的波形和波形信息,同时DAC的波形输出也得到更新。了解了这知识点后,再看代码就比较容易了。

知识点拓展:
    新版emWin教程第51章:实用的官方小键盘实例讲解:
    另外还有emWin提高篇例子的第一期ATM机里面也有用到小键盘。

努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2018-7-9 16:57:03 | 显示全部楼层
12.4 信号发生器波形显示效果
    下面为大家展示信号发生器输出波形效果:
方波:
12.6.png
                              
正弦波:
12.7.png
三角波:
12.8.png

努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2018-7-9 16:57:31 | 显示全部楼层
12.5   总结
    本章节为大家讲解的信号发生器还是比较实用的,建议实际动手操作下,有兴趣的话,还可以进一步优化升级。

努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

1

主题

5

回帖

8

积分

新手上路

积分
8
发表于 2022-3-22 16:51:29 | 显示全部楼层
h750最大可以输出多少hz的正弦波
回复

使用道具 举报

1

主题

5

回帖

8

积分

新手上路

积分
8
发表于 2022-3-22 16:53:21 | 显示全部楼层
h750最大可以输出多少HZ的正弦波?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-2 15:12 , Processed in 0.184932 second(s), 28 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表