请选择 进入手机版 | 继续访问电脑版

硬汉嵌入式论坛

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

【安富莱二代示波器教程】第9章 示波器设计—自动触发和普通触发

[复制链接]

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
发表于 2018-7-3 15:18:07 | 显示全部楼层 |阅读模式


完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=45785


第9章        示波器设计—自动触发和普通触发

    自带触发和普通触发是示波器设计中比较重要的两个功能,本章节为大家讲解二代示波器中自动触发和普通触发的实现。
9.1    自动触发
9.2    普通触发
9.3     总结

9.1  自动触发
    由于示波器模拟前端模块稍有些问题,所以自动触发功能是用软件实现的。软件实现自动触发比较容易实现,具体的实现代码如下:
  1. /*  通过软件检测实现上升沿触发,并保留最后600的数据不做检测,用于直接显示 2048-600 = 1448; */

  2. j = 0;

  3. for(i = 0; i < 1448; i++)

  4. {

  5.      j++;

  6.      if((g_DSO1->usWaveBufTemp[i] > g_TrigVol->usTrigValue) &&

  7.         (g_DSO1->usWaveBufTemp[i+1] < g_TrigVol->usTrigValue))

  8.      {   

  9.          break;

  10.      }

  11. }
复制代码
    g_DSO1->usWaveBufTemp是2048个ADC数据的缓冲区,g_TrigVol->usTrigValue是上升沿触发值。for循环的作用就是从1448个数据中找到满足触发值的位置,判断方法也比较简单,大于前一个值小于后一个值即可。
    保留600个数据是因为这个大小是波形显示区一次可以显示的波形个数。如果从前2048-600 = 1448个数据中检索不到满足要求的数据将不再检索,直接显示末尾的600个数据,如果检测到将直接从for循环里面退出。
    这个方法在实际测试中比较好用,另外上升沿阀值的判断还不够严谨,大家有兴趣可以继续完善下。


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

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2018-7-3 15:21:26 | 显示全部楼层
9.2   普通触发
    普通触发的实现是基于ADC的模拟看门狗功能,通过设置不同看门狗阀值实现不同的触发电压。由于使能了看门狗中断,检测到外部触发电压后会进入ADC模拟看门狗中断。在中断里面判断是否是上升沿触发,如果是的话,会关闭模拟看门狗中断并开启一个定时器计数功能,目的是为了采集这个触发电压前后各1024个ADC数据,基本的实现思路就是这个样子的。
    下面把实际的实现为大家做个说明:
第1步:将ADC3配置使能模拟看门狗功能。
  1. /*ADC3的配置*****************************************************************/

  2. ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;

  3. ADC_InitStructure.ADC_ScanConvMode = DISABLE;

  4. ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;

  5. ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;

  6. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC3;

  7. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

  8. ADC_InitStructure.ADC_NbrOfConversion = 1;



  9. /* ADC3 规则通道配置 */

  10. ADC_Init(ADC3, &ADC_InitStructure);

  11. ADC_RegularChannelConfig(ADC3, ADC_Channel_10, 1, ADC_SampleTime_3Cycles);



  12. /* 使能 ADC3 DMA */

  13. ADC_DMACmd(ADC3, ENABLE);



  14. /* 配置模拟看门狗的阀值 注意别配置反了,要不一直进入中断 */

  15. ADC_AnalogWatchdogThresholdsConfig(ADC3, 4095, 0);



  16. /* 配置模拟看门狗监测ADC3的通道10 */

  17. ADC_AnalogWatchdogSingleChannelConfig(ADC3, ADC_Channel_10);



  18. /* 使能一个规则通道的看门狗 */

  19. ADC_AnalogWatchdogCmd(ADC3, ADC_AnalogWatchdog_SingleRegEnable);



  20. /* 使能模拟看门狗中断 */

  21. ADC_ITConfig(ADC3, ADC_IT_AWD, ENABLE);



  22. /* 使能DMA请求 -------------------------------------------------------------*/

  23. ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);



  24. /* Enable ADC3 --------------------------------------------------------------*/

  25. ADC_Cmd(ADC3, ENABLE);
复制代码
特别注意函数ADC_AnalogWatchdogThresholdsConfig的设置,因为是12位分辨率的ADC,最大值就是2^12 – 1 = 4095,这里设置为4095表示超过4095才会触发模拟看门狗中断,由于已经是最大值了,所以不会触发模拟看门狗中断。

第2步:模拟看门狗中断。
    达到设置的模拟看门狗触发值会进入到这个中断里面。
  1. /*

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

  3. *    函 数 名: ADC_IRQHandler

  4. *    功能说明: 模拟看门狗中断服务程序。

  5. *    形    参: 无

  6. *    返 回 值: 无

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

  8. */

  9. void ADC_IRQHandler()

  10. {

  11.      /* 读取DMA剩余要传输的数目 */

  12.      g_usTrigCount = DMA2_Stream1->NDTR;



  13.      /* 确认是否是ADC3的看门狗中断 */

  14.      if((ADC3->SR)&0x01)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 

  15.     {

  16.          /* 取触发值的前一个点,查看是否是在阀值范围内,从而判断是上升沿还是下降沿 */

  17.          if(g_usTrigCount == 10240)

  18.          {

  19.               /* 剩余10240表示触发值是ADC3ConvertedValue[10239]是触发值,那么上一个点就是10238 */

  20.               g_usTrigTempFlag = ADC3ConvertedValue[10238];

  21.          }

  22.          else if(g_usTrigCount == 10239)

  23.          {

  24.               /* 剩余10239表示触发值是ADC3ConvertedValue[0]是触发值,那么上一个点就是10239 */

  25.               g_usTrigTempFlag = ADC3ConvertedValue[10239];

  26.          }

  27.          else

  28.          {

  29.               /* 剩余10239表示触发值是ADC3ConvertedValue[0]是触发值,那么上一个点就是10239 */

  30.               g_usTrigTempFlag = ADC3ConvertedValue[10238 - g_usTrigCount];

  31.          }

  32.         

  33.          /* 判断是否是上升沿,是的话开启定时器记录ADC数据 */

  34.          if(g_usTrigTempFlag <= g_TrigVol->usTrigValue)

  35.          {

  36.               /* 关闭ADC3的看门狗中断 */

  37.               ADC3->CR1 &= 0xffffffbf;

  38.               TriggerFlag = 1;

  39.             

  40.               /* 启动定时器计数 */

  41.               TIM8->CR1 |= 0x01;

  42.          }

  43.         /* 清除挂起标志 */

  44.          ADC3->SR &= 0xfe;

  45.     }

  46. }
复制代码
进入到这个中断后,主要做了一件事,判断是否是上升沿,如果是上升沿的话,将关闭模拟看门狗并开启定时器测量功能。也就是下面第3步要讲解的。

第3步:初始化一个定时器做时间测量,表示检测到触发值后记录一段时间的波形。
  1. /*

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

  3. *    函 数 名: TIM8_MeasureTrigConfig

  4. *    功能说明: 使用TIM8为普通触发模式下数据采集计时,定时采集触发值前后的1024个ADC数据

  5. *    形    参:无

  6. *    返 回 值: 无              

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

  8. */

  9. /*

  10.      每次捕获采集触发值前后的1024个ADC数据(单通道)。

  11. */

  12. const uint32_t g_TrigFreqTable[][2] =

  13. {

  14.      {60,   1024},    //2.8Msps  168000000/2800000 = 60  => 60 * 1024

  15.      {84,   1024},    //2Msps    168000000/2000000 = 84  => 84 * 1024

  16.      {168,  1024},    //1Msps    168000000/1000000 = 168 => 168 * 1024

  17.      {336,  1024},    //500Ksps  168000000/500000  = 336 => 336 * 1024

  18.      {840,  1024},    //200Ksps  168000000/200000  = 840 => 840 * 1024

  19.    

  20.      {1680,  1024},   //100Ksps 168000000/100000  = 1680  => 1680 * 1024

  21.      {3360,  1024},   //50Ksps  168000000/50000   = 3360  => 3360 * 1024

  22.      {8400,  1024},   //20Ksps  168000000/20000   = 8400  => 8400 * 1024

  23.      {16800, 1024},   //10Ksps  168000000/10000   = 16800 => 16800 * 1024

  24.      {33600, 1024},   //5Ksps   168000000/5000    = 33600 => 33600 * 1024



  25.      /* 下面5种采样频率下刷新较慢,因为采集前后1024个ADC的时间较长 */

  26.      {42000,    2048},    //2Ksps 168000000/2000  = 84000 => 84000 * 1024

  27.      {42000,    4096},    //1Ksps 168000000/1000  = 168000 => 168000 * 1024

  28.      {42000,    8192},    //500sps 168000000/500  = 336000 => 336000 * 1024

  29.      {42000,    20480},   //200sps 168000000/200  = 840000 => 840000 * 1024

  30.      {42000,    40960},   //100sps 168000000/100  = 1680000 => 1680000 * 1024

  31.    

  32.      /* 下面这几种采样率不做触发支持 */

  33.      {42000,    40960},   //50sps

  34.      {42000,    40960},   //20sps

  35.      {42000,    40960},   //10sps

  36.      {42000,    40960},   //5sps

  37.      {42000,    40960},   //2sps

  38.      {42000,    40960},   //1sps     

  39. };



  40. void TIM8_MeasureTrigConfig(void)

  41. {

  42.      TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;

  43.      NVIC_InitTypeDef NVIC_InitStructure;

  44.    

  45.      /* 开启时钟 */

  46.      RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);

  47.    

  48.      /* 加上第一次进入中断的标志,进入中断后将其置1 */

  49.      g_usFirstTimeIRQFlag = 0;



  50.      /* 使能定时器8中断  */

  51.      NVIC_InitStructure.NVIC_IRQChannel = TIM8_UP_TIM13_IRQn;

  52.      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

  53.      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

  54.      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  55.      NVIC_Init(&NVIC_InitStructure);



  56.      /* 定时器配置 */

  57.      TimeBaseId = 1;        /* 开机后按照ADC单通道1Msps进行配置 */

  58.      TIM_DeInit(TIM8);

  59.      TIM_BaseInitStructure.TIM_Period = g_TrigFreqTable[TimeBaseId][0] - 1;

  60.      TIM_BaseInitStructure.TIM_Prescaler = g_TrigFreqTable[TimeBaseId][1] - 1;

  61.      TIM_BaseInitStructure.TIM_ClockDivision = 0;

  62.      TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;

  63.      TIM_TimeBaseInit(TIM8, &TIM_BaseInitStructure);

  64.      TIM_ITConfig(TIM8, TIM_IT_Update, ENABLE);

  65.      TIM_Cmd(TIM8, DISABLE);

  66. }



  67. /*

  68. *********************************************************************************************************

  69. *    函 数 名: Time8Recorder

  70. *    功能说明: 使用TIM8为普通触发模式下数据采集计时,定时采集触发值前后的1024个ADC数据

  71. *    形    参:无

  72. *    返 回 值: 无              

  73. *********************************************************************************************************

  74. */

  75. static void Time8Recorder(void)

  76. {

  77.      TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;

  78.    

  79.      /* 加上第一次进入中断的标志 */

  80.      g_usFirstTimeIRQFlag = 0;

  81.    

  82.      TIM_DeInit(TIM8);

  83.      TIM_BaseInitStructure.TIM_Period = g_TrigFreqTable[TimeBaseId][0] - 1;

  84.      TIM_BaseInitStructure.TIM_Prescaler = g_TrigFreqTable[TimeBaseId][1] - 1;

  85.      TIM_BaseInitStructure.TIM_ClockDivision = 0;

  86.      TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;

  87.      TIM_TimeBaseInit(TIM8, &TIM_BaseInitStructure);

  88.      TIM_ITConfig(TIM8, TIM_IT_Update, ENABLE);

  89.      TIM_Cmd(TIM8, DISABLE);

  90. }
复制代码
    首先讲解数组g_TrigFreqTable的作用,因为不同采样率下,采样1024个数据时间是不同的,所以我们就要将定时器设置为不同的参数配置,以此实现这段时间内采集1024个数据(也许有读者会问,我们不是要采集2048个数据吗,为什么这里只采集了1024个。原因在于另外1024个数据直接从这个触发值所在位置的前面获取即可,也就是已经采集的,最近的1024个数据)。比如第一组是60和1024,他的作用就是将定时器结构体成员配置为:
        TIM_BaseInitStructure.TIM_Period = 60 – 1
        TIM_BaseInitStructure.TIM_Prescaler = 1024 – 1
那么定时器的更新周期= TIMCLK / (TIM_Period + 1)/(TIM_Prescaler + 1)=TIMCLK/60/1024。到了这个时间后,就会进入到定时器中断,也就是在这个采样率下,已经完成了1024个数据的采集。其它采样率的参数配置同理。具体取值不限制,只要大家的配置满足采集了1024个数据即可。
    这个理解了,其它的就是TIM8的配置,这个就相对容易些,我们这里就不做介绍了。另外特别注意变量标志g_usFirstTimeIRQFlag,下一步里面会说明为什么要做这个。

第4步:也是最后一步,测量时间到后,进入到定时器中断。
    进入到定时器中断后,要关闭定时器,并将触发值前后各1024个采样数据从缓冲区中提取出来。具体实现代码如下:
  1. /*

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

  3. *    函 数 名: TIM8_UP_TIM13_IRQHandler

  4. *    功能说明: 定时器中断函数

  5. *    形    参: 无

  6. *    返 回 值: 无              

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

  8. */

  9. void TIM8_UP_TIM13_IRQHandler(void)

  10. {

  11.      uint16_t i, k, usCurPos;

  12.    

  13.      /* 第一次进入TIM8中断执行退出操作,因为定时器更新中断有个小bug,

  14.         一旦初始化了定时器并且使能中断就会有立即进入一次中断的情况,为了防止这种情况的

  15.         出现,作如下操作:

  16.      */

  17.      if(g_usFirstTimeIRQFlag == 0)

  18.      {

  19.          g_usFirstTimeIRQFlag = 1;

  20.         

  21.          TIM_ClearITPendingBit(TIM8, TIM_IT_Update);

  22.         

  23.          /* 如果是第一次进入就退出 */

  24.          return;

  25.      }

  26.    

  27.      /* 定时器时间中断 */

  28.      if (TIM_GetITStatus(TIM8, TIM_IT_Update) != RESET)

  29.      {

  30.          /* 先关闭定时器 */

  31.          TIM_Cmd(TIM8, DISABLE);

  32.    

  33.          /* 确保开启的定时器计时采集ADC数据 */

  34.          if(TriggerFlag == 1)

  35.          {

  36.               /* 定时器采集ADC数据结束 */

  37.               TriggerFlag = 2;

  38.             

  39.               /* 开启定时器计数前DMA传输数据的位置 */

  40.               usCurPos = 10240 - g_usTrigCount;

  41.             

  42.               /*

  43.                    下面分为三种情况获取2K的数据(三通道,单通道就是前后1024个ADC数据):

  44.                    1. 当前的位置 < 1024.

  45.                    2. 1024 <= 当前的位置 <= 10240 - 1024(9216)

  46.                     3. 当前位置 > 10240 - 1024(9216)

  47.               */

  48.               /* 第一种情况:当前的位置 < 1024. */

  49.               if(usCurPos < 1024)

  50.               {

  51.                    k = 1024 - usCurPos;

  52.                    usCurPos = 10240 - k;

  53.                   

  54.                    /* 前部分数据 */

  55.                    for(i = 0; i < k; i++)

  56.                    {

  57.                        g_DSO1->usWaveBufTemp[i] = ADC3ConvertedValue[i+usCurPos];

  58.                    }

  59.                   

  60.                    usCurPos = i;

  61.                    k = 10240 - g_usTrigCount + 1024;

  62.                   

  63.                    /* 后部分数据 */

  64.                    for(i = 0; i < k; i++)

  65.                    {

  66.                        g_DSO1->usWaveBufTemp[i + usCurPos] = ADC3ConvertedValue[i];

  67.                    }

  68.               }

  69.               /* 第三种情况:当前位置 > 10240 - 1024(9216) */

  70.               else if(usCurPos > 9216)

  71.               {

  72.                    usCurPos = usCurPos - 1024;

  73.                   

  74.                    /* 采集前1024+g_usTrigCount的数据 */

  75.                    for(i = 0; i < (g_usTrigCount + 1024); i++)

  76.                    {

  77.                        g_DSO1->usWaveBufTemp[i] = ADC3ConvertedValue[i+usCurPos];

  78.                    }

  79.                   

  80.                    k = i;

  81.                    usCurPos = (1024 - g_usTrigCount);

  82.                   

  83.                    /* 剩余数据的采集 */

  84.                    for(i = 0; i < usCurPos; i++)

  85.                    {

  86.                        g_DSO1->usWaveBufTemp[i + k] = ADC3ConvertedValue[i];

  87.                    }

  88.               }

  89.               /* 第二种情况:1024 <= 当前的位置 <= 10240 - 1024(9216)  */

  90.               else

  91.               {

  92.                    usCurPos = usCurPos - 1024;

  93.                    for(i = 0; i < 2048; i++)

  94.                    {

  95.                        g_DSO1->usWaveBufTemp[i] = ADC3ConvertedValue[i+usCurPos];

  96.                    }

  97.               }

  98.             

  99.          }

  100.    

  101.          TIM_ClearITPendingBit(TIM8, TIM_IT_Update);

  102.      }

  103. }
复制代码
    变量标志g_usFirstTimeIRQFlag的作用就是代码前面的注释,第一次进入TIM8中断执行退出操作,因为定时器更新中断有个小bug,一旦初始化了定时器并且使能中断就会有立即进入一次中断的情况,为了防止这种情况而做的这个变量。
    定时器中断里面最重要的功能还是从10240大小的缓冲区里面提取2048个采样数据,代码里面分为三种情况进行实现:
1. 当前的位置 < 1024。
2. 1024 <= 当前的位置 <= 10240 - 1024(9216)。
3. 当前位置 > 10240 - 1024(9216)。
    具体实现需要大家拿笔实际计算下,捋一遍基本就都明白了,我们这里就不做说明了。

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

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2018-7-3 15:22:36 | 显示全部楼层
9.3   总结
     本章为大家讲解的普通触发更适合做波形故障记录,如果波形触发速度太快了,有点处理不过来,因为我们需要记录2048个数据,大家可以根据实际需要调整记录的大小。

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

使用道具 举报

34

主题

111

回帖

213

积分

高级会员

程序小白

积分
213
QQ
发表于 2020-10-27 08:41:28 | 显示全部楼层
哥,普通触发并不是单次触发吧,如果要实现单次触发,该如何修改?
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-3-29 15:31 , Processed in 0.284726 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

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