如何测量脉冲宽度

前言

对于脉冲宽度的测量,小编其实也很少有接触到,但是有一次项目量产时需要写一套自动化测试软件,刚好需要测量某个IO口输出的脉冲宽度。所以对一些需要测量的系统,这个操作还是挺常用。因此,小编接下来将介绍如何使用nrf52840实现这个功能。

开发环境

原理

其实脉冲宽度的测量原理还是比较简单的,当发生边缘变化时就开始计时等到下一个边缘到来时,则用当前的时间减去上一次的时间,即可知道这个宽度的时间。但是,务必要使用位数长一点的计时器来保证精度和深度。为什么这么说呢?因为如果位数偏小,那么计时器就很容易溢出;如果精度不够,则计算出来的宽度时间则偏差较大。以下是小编的TIMER配置:

  • timer频率(1MHz)
  • timer位宽度(32Bit)

从以上的配置我们可以知道,1us一个tick且拥有32bit的宽度,那么这就意味着这个timer溢出需要2^32us即1.2小时。这样的精度用来捕捉宽度已经足够了,因为刚好在1.2小时前后采集数据的概率会低很多。以下的diagram就演示整个采集的过程。

[外链图片转存失败(img-1MLzh39D-1565883383672)(https://raw.githubusercontent.com/xiaolongba/HX_DK_FOR_NORDIC_52840_BLE/master/%E8%BD%AF%E4%BB%B6/%E7%BA%A2%E6%97%AD%E6%97%A0%E7%BA%BF%E5%BC%80%E5%8F%91%E6%9D%BF%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B/nRF52840/the%20releated%20pics%20about%20tutorials/measure%20pulse%20width.svg?sanitize=true)]

如上图所示,只要获取得到a,c,e三点的时间就可以知道整个脉冲的宽度了。

如何使用

就如上一篇什么是 PPI,它能干什么?所说的,脉冲宽度测量对实时性的要求比较高,因此我们这里就不得不又要用到PPI了。

GPIOTE初始化

/**
 * gpiote初始化函数
 * @param[in]   NULL
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/07/28, 初始化版本\n
 */
void user_gpiote_init(nrf_drv_gpiote_evt_handler_t evt_handler)
{
  nrfx_err_t error_code;
  nrf_drv_gpiote_in_config_t m_nrf_drv_gpiote_in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
  /* 初始化gpiote */
  error_code = nrf_drv_gpiote_init();
  NRF_LOG_INFO("nrf_drv_gpiote_init is %d\n", error_code);

  /* gpiote输入初始化 */
  error_code = nrf_drv_gpiote_in_init(GPIOTE_IN_PIN,&m_nrf_drv_gpiote_in_config,evt_handler);
  NRF_LOG_INFO("nrf_drv_gpiote_in_init is %d\n", error_code);

  /* 使能gpiote输入事件 */
  nrf_drv_gpiote_in_event_enable(GPIOTE_IN_PIN,true);
}

从以上源码我们可以看出,设置一个GPIO口当有电平变化时产生一个Event并在相应的回调处理函数中进行下一步的处理,这里我们回调函数指的就是user_app_gpiote_in_evt_handler,我们的宽度测量就放在这个回调中处理了。

PPI初始化

/**
 * ppi初始化函数
 * @param[in]   eep:event的地址
 * @param[in]   tep:task的地址
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/10/27, 初始化版本\n
 */
void user_ppi_init(uint32_t eep, uint32_t tep)
{
  ret_code_t error_code;
  /* 存放ppi通道 */
  nrf_ppi_channel_t m_nrf_ppi_channel;
  /* 初始化ppi */
  error_code = nrf_drv_ppi_init();
  NRF_LOG_INFO("nrf_drv_ppi_init is %d\n",error_code);
  /* 从未使用的ppi通道中分配一个通道 */
  error_code = nrf_drv_ppi_channel_alloc(&m_nrf_ppi_channel);
  NRF_LOG_INFO("nrf_drv_ppi_channel_alloc is %d\n",error_code);

  /* 将指定的event地址和task地址安排至分配到的ppi channel */
  error_code = nrf_drv_ppi_channel_assign(m_nrf_ppi_channel,eep,tep);
  NRF_LOG_INFO("nrf_drv_ppi_channel_assign is %d\n",error_code);

  /* 使能ppi channel */
  error_code = nrf_drv_ppi_channel_enable(m_nrf_ppi_channel);
  NRF_LOG_INFO("nrf_drv_ppi_channel_enable is %d\n",error_code);
}

以上的源码跟上几篇所说的初始化没有什么区别,基本上就是先初始化PPI模块->分配ppi通道->将event与task通过ppi通道相连接,最后使能这个ppi通道。套路都是一样的。

Timer初始化

/**
 * timer1初始化函数
 * @param[in]   p_instance:表示定时器的ID
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/10/27, 初始化版本\n
 */
void user_timer_init(nrfx_timer_t const * const  p_instance)
{
  nrfx_err_t error_code;
  /* 默认的配置通过sdk_config.h中设置 */
  nrf_drv_timer_config_t m_nrf_drv_timer_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
  error_code = nrf_drv_timer_init(p_instance,&m_nrf_drv_timer_config,user_timer_event_dummy_handler);
  NRF_LOG_INFO("nrf_drv_timer_init is %d\n",error_code);

  /* 此时Timer1定时器就开始工作,每个时钟tick就加1 */
  nrf_drv_timer_enable(p_instance);
}

以上的源码同样跟上几篇所说的基本一致,无非就是设置相关的参数最后填充进初始化函数,然后再使能。

PWM初始化

这里主要是模拟一个脉冲,然后与上面的GPIOTE中所设置的GPIO相连接,PWM一启动那么与其相连的GPIO就会生成event,从而与event相连的task就会被自动鉵发。

/**
 * pwm初始化函数
 * @param[in]   NULL
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/07/28, 初始化版本\n
 */

void user_pwm_init(void)
{
  ret_code_t err_code;
  /* 使用默认的配置,配置PWM的周期为1K,PWM输出脚为BROAD_LED,其他3路不使用 */
  nrf_drv_pwm_config_t m_nrf_drv_pwm_config = NRF_DRV_PWM_DEFAULT_CONFIG;
//  m_nrf_drv_pwm_config.output_pins[0] = NRF_DRV_PWM_PIN_INVERTED;
  m_nrf_drv_pwm_config.output_pins[1] = NRF_DRV_PWM_PIN_NOT_USED;
  m_nrf_drv_pwm_config.output_pins[2] = NRF_DRV_PWM_PIN_NOT_USED;
  m_nrf_drv_pwm_config.output_pins[3] = NRF_DRV_PWM_PIN_NOT_USED;

  err_code = nrf_drv_pwm_init(&gs_m_nrf_drv_pwm, &m_nrf_drv_pwm_config, NULL);
  NRF_LOG_INFO("nrf_drv_pwm_init is %d\n", err_code);

  nrf_pwm_sequence_t const c_m_seq =
      {
          .values.p_common = duty_values,
          .length = NRF_PWM_VALUES_LENGTH(duty_values),
          .repeats = 0,
          .end_delay = 0};
  /* 重复3次duty_values中指定的占空比,最后保持最后一个占空比的值即100% */
  err_code = nrf_drv_pwm_simple_playback(&gs_m_nrf_drv_pwm, &c_m_seq, 3, 0);
  NRF_LOG_INFO("nrf_drv_pwm_simple_playback is %d\n", err_code);
}

总结

脉冲宽度的测量主要由PPI,GPIOTE,PWM,TIMER四个部分组成,利用PPI实时性以及高精度的定时器来实现这个功能,从测试的效果看精度可达us级,这样的精度基本上能满足大部分的场景了。总得来说就是,GPIOTE产生event然后触发定时器1的task,此时定时器1的task就是记录当前跑了多少个tick.然后,我们可以利用前后的tick计算出高电平以及低电平的宽度,从而间接得到整个脉冲的宽度。

最后

小编于2019年大年三十含泪写下了这篇文章,祝各位同行2019年个个梦想成真,登上人生巅峰。?

更多

红旭无线Github
红旭无线技术交流论坛
微信公众号
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值