Skip to content

Latest commit

 

History

History
275 lines (232 loc) · 13 KB

如何使用硬件定时器及计数器.md

File metadata and controls

275 lines (232 loc) · 13 KB

前言

我相信在嵌入式领域的各位大佬,不管在什么样的项目或多或少都是要用到定时器吧,基本上大部分项目均会涉及到定时。然而,计数或许用得会比较少。那么今天小编就给大伙讲讲如何配置使用Nordic 52840的定时器以及计数器。

开发环境

原理

小编这个人有个习惯,面对新的物种或者技术我均会先去了解下原理。最终才会去写代码。我个人认为写代码是最不费时间的,最费时间的是写代码前期的了解以及思路构思。如果前期准备的不好,那务必会导致你把大部分时间花在了写bug或者在改bug的路上。既然要说原理,那么最靠谱的资料当然是规格书了,先让我们看看Nordic 52840的硬件定时器的diagram.

从上图中可以看出,硬件TIMER的时钟来自于HCLK。因此,如果想要做低功耗的话就直接放弃吧,因为在低功耗模式下,HCLK是要被关闭的。从这个框图看,硬件定时器的结构还是比较简单清晰的。

  • 时钟

    • PCLK1M

      fTIMER小于等于1Mz时,就选择这个时钟,应用层不用去处理它,这个操作是自动进行的

    • PLCK16M

      fTIMER大于1Mhz时,就会选择这个时钟,同样也是自动选择的,应用层只需要关注fTIMER即可。

    从这里我们可以知道,硬件定时器的时钟最大就是16Mhz。

  • Task&&Event

    这里一共有5种任务以及1种事件,具体如下所示:

    • COUNT
    • STOP
    • START
    • CLEAR
    • CAPTURE
    • COMPARE(事件)

    从命名我们就很清楚地知道它们的含义分别为计数、停止、开始、清零、捕捉、比较,而且我们从箭头的方向也知道,输入的是任务输出的是事件。

从以上我们分析的结果可知,要想Nordic 52840的硬件定时器工作起来,就必须要使能HCLK->选择工作模式->输入COUT、START、STOP、CLEAR、CAPTURE、COMPARE中的任意一个信号

如何使用

Nordic 52840的定时器有两个功能,一个是定时器另外一个是计数器。总共有5个TIMER,分别为TIMER0-TIMER4。 有一点还是要强调一下的,不管是定时器还是计数器它们是共用这5个TIMER的。 例如TIMER0已经做为定时器了,那么此时计数器就只能从TIMER1~TIMER4中选择一个了。

定时器

通过上面的大体原理讲解,我想大家应该有个相对的了解吧,接下来我们看看如何初始化定时器。废话不多说,直接上源码:joy:。

/**
 * 初始化timer0,用于定时
 * @param[in]   NULL
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/10/23, 初始化版本\n
 */
static void user_app_timer0_init(void)
{
  nrfx_err_t err_code;
  uint32_t timer_ms_tick;
  /* 使用NRFX_TIMER_DEFAULT_CONFIG配置hardware timer0为默认的配置参数 */
  nrf_drv_timer_config_t m_hardware_timer0_config = NRF_DRV_TIMER_DEFAULT_CONFIG;

  /* 初始化hardware timer0 */
  err_code = nrf_drv_timer_init(&hardware_timer0,
                                &m_hardware_timer0_config,
                                user_nrf_timer_evt_handler);
  NRF_LOG_INFO("nrf_drv_timer0_init is %d\n", err_code);
  /* 1.设置hardware timer0在NRF_TIMER_CC_CHANNEL0通道每一秒产生event,并触发clear task 
     2.每一秒就回调一次用户自定义的回调处理函数user_nrf_timer_evt_handler,当前false不调用
  */
  timer_ms_tick = nrf_drv_timer_ms_to_ticks(&hardware_timer0, 1000);
  NRF_LOG_INFO("nrf_drv_timer_ms_to_ticks is %d\n", timer_ms_tick);
  nrf_drv_timer_extended_compare(&hardware_timer0,
                                 NRF_TIMER_CC_CHANNEL0,
                                 timer_ms_tick,
                                 NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                 false);
  /* 使能hardware timer0 */                                 
  nrf_drv_timer_enable(&hardware_timer0);
}

更多的就不再细述了,基本上每一行代码我都有有注释了。其中m_hardware_timer0_config可以在sdk_config.h头文件中配置,至于配置的方法此次不再详述。感兴趣的可以查看如何搭建Nordic 52840开发环境->如何使能GUI配置界面

计数器

其实计数器,我觉得用的人会比较少。因为多数应该会定义一个变量来存放计数,但是在一些特殊的场合还是需要用到计数器的,例如需要一些计量的应用场景。

/**
 * 初始化timer1,用于计数
 * @param[in]   NULL
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/10/23, 初始化版本\n
 */
static void user_app_timer1_init(void)
{
  nrfx_err_t err_code;

  /* 设置hardware timer1的长度为32bit的计数器 */
  nrf_drv_timer_config_t m_hardware_timer1_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
  m_hardware_timer1_config.mode = NRF_TIMER_MODE_COUNTER;
  m_hardware_timer1_config.bit_width = NRF_TIMER_BIT_WIDTH_32;

  /* 初始化hardware timer1 */
  err_code = nrf_drv_timer_init(&hardware_timer1,
                                &m_hardware_timer1_config,
                                user_nrf_timer_evt_handler);
  NRF_LOG_INFO("nrf_drv_timer1_init is %d\n", err_code);
  
  /* 使能hardware timer1*/
  nrf_drv_timer_enable(&hardware_timer1);
}

PPI串联

上面讲到的是定时器以及计数器的初始化过程,现在为了让定时器和计数器真正应用起来,小编使用了TIMER0用于定时开关LED灯,TIMER1用于按揵的按下的计数。但是,在实现这个功能之前,我们需要用到PPI,将这些串联起来方可完成这个功能。

/**
 * 初始化gpiote
 * @param[in]   NULL
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/10/23, 初始化版本\n
 */

static void user_app_gpiote_init(void)
{
  nrfx_err_t err_code;
  nrf_drv_gpiote_in_config_t m_gpiote_in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
  nrf_drv_gpiote_out_config_t m_gpiote_out_config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
  /* 初始化gpiote */
  err_code = nrf_drv_gpiote_init();
  NRF_LOG_INFO("nrf_drv_gpiote_init is %d\n", err_code);

  /* 设置按键对应的GPIO口由高电平变低电平时,产生ENVENT */
  err_code = nrf_drv_gpiote_in_init(KEY_NUMBER,
                                    &m_gpiote_in_config,
                                    user_nrf_drv_gpiote_evt_handler);
  NRF_LOG_INFO("nrf_drv_gpiote_in_init is %d\n", err_code);

  /* 设置LED灯对应的GPIO口被hardware timer0 compare event控制,且每次有EVENT触发GPIO OUT TASK时,取反当前的LED灯 */
  err_code = nrf_drv_gpiote_out_init(LED_NUMBER,&m_gpiote_out_config);
  NRF_LOG_INFO("nrfx_gpiote_out_init is %d\n",err_code);

  /* 使能gpiote的in event,并触发user_nrf_drv_gpiote_evt_handler回调函数 */
  nrf_drv_gpiote_in_event_enable(KEY_NUMBER, true);
  /* 使能gpiote的out task */
  nrf_drv_gpiote_out_task_enable(LED_NUMBER);
}

以上的代码,就是将与LED连接的GPIO设置成GPIO的Task,将按键相连的GPIO设置为GPIO的Event。

/**
 * 初始化ppi
 * @param[in]   NULL
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/10/23, 初始化版本\n
 */

static void user_app_ppi_init(void)
{
  nrfx_err_t err_code;
  nrf_ppi_channel_t m_nrf_ppi_channel1,m_nrf_ppi_channel2;
  uint32_t gpiote_evt_addr,gpiote_task_addr;
  uint32_t hardware_timer1_capture_task_addr,hardware_timer0_compare_evt_addr;
  /* 初始化ppi */
  NRF_LOG_INFO("nrf_drv_ppi_init is %d\n", nrf_drv_ppi_init());

  /* 选择未使用的PPI通道 */
  err_code = nrf_drv_ppi_channel_alloc(&m_nrf_ppi_channel1);
  NRF_LOG_INFO("nrf_drv_ppi_channel1_alloc is %d\n", err_code);

  /* 选择未使用的PPI通道 */
  err_code = nrf_drv_ppi_channel_alloc(&m_nrf_ppi_channel2);
  NRF_LOG_INFO("nrf_drv_ppi_channel2_alloc is %d\n", err_code);

  /* 获取event和task的地址 */
  gpiote_task_addr = nrf_drv_gpiote_out_task_addr_get(LED_NUMBER);
  hardware_timer0_compare_evt_addr = nrf_drv_timer_compare_event_address_get(&hardware_timer0,
                                                                             NRF_TIMER_CC_CHANNEL0);
  gpiote_evt_addr = nrf_drv_gpiote_in_event_addr_get(KEY_NUMBER);
  hardware_timer1_capture_task_addr = nrf_drv_timer_task_address_get(&hardware_timer1,
                                                                     NRF_TIMER_TASK_COUNT);
  /* 将event和task通过选中的ppi_channel连接起来 */
  err_code = nrf_drv_ppi_channel_assign(m_nrf_ppi_channel1,
                                        hardware_timer0_compare_evt_addr,
                                        gpiote_task_addr);
  NRF_LOG_INFO("nrf_drv_ppi_channel1_assign is %d\n", err_code);

  err_code = nrf_drv_ppi_channel_assign(m_nrf_ppi_channel2,
                                        gpiote_evt_addr,
                                        hardware_timer1_capture_task_addr);
  NRF_LOG_INFO("nrf_drv_ppi_channel2_assign is %d\n", err_code);

  /* 使能选中的PPI的通道 */
  err_code = nrf_drv_ppi_channel_enable(m_nrf_ppi_channel1);
  NRF_LOG_INFO("nrf_drv_ppi_channel1_enable is %d\n", err_code);

  err_code = nrf_drv_ppi_channel_enable(m_nrf_ppi_channel2);
  NRF_LOG_INFO("nrf_drv_ppi_channel2_enable is %d\n", err_code);
}

以上代码就是把TIMER0的Event与GPIO的Task通过PPI通道连接在一起,把GPIO的Event与TIMER1的Task同样通过通道连接在一起。如初始化的过程中所讲,TIMER0每秒就会产生一个Event,所以与其相连接的Task就会随之执行一次。同理,与按键相接的GPIO每产生一次Event就会自动触发TIMER1的Task用于计数。

完整的工程实现

其中上面的几个章节,基本上把使用的功能都讲到了,下面只要将这些拼装起来即可。

/**
 * 初始化使用到的外设
 * @param[in]   NULL
 * @retval      NULL
 * @par         修改日志
 *              Ver0.0.1:
                  Helon_Chan, 2018/10/23, 初始化版本\n
 */
void user_app_init(void)
{  
  user_app_gpiote_init();
  user_app_timer0_init();
  user_app_timer1_init();
  user_app_ppi_init();
}

/**
* @file         main.c
* @brief        用户应用程序入口
* @details      用户应用程序的入口文件,用户所有要实现的功能逻辑均是从该文件开始或者处理
* @author       Helon_Chan
* @par Copyright (c):
*               红旭无线开发团队
* @par History:
*               Ver0.0.1:
                     Helon_Chan, 2018/06/09, 初始化版本\n
*/
int main(void)
{
  /* log模块初始化 */
  log_init();
  user_app_init();
  NRF_LOG_INFO("/******************************************************************************/\n");
  NRF_LOG_INFO("            Welcome to wireless-tech hardware timer demo project                \n");
  NRF_LOG_INFO("            website :bbs.wireless-tech.cn                                       \n");
  NRF_LOG_INFO("            QQ Group:671139854                                                  \n");
  NRF_LOG_INFO("/******************************************************************************/\n");
}

最后

至此,Nordic 52840的硬件定时器以及计数器就讲完了,基本上还是比较简单的,Nordic已经将大部分的库都完成了,我们唯一要做的就是在了解原理的情况下,如何使用现在的API。

本文原创,转载请注明出处