阅读 nRF51_Series_Reference_manual 手册可知 nRF51822有两种时钟源:
其时钟源来自以下两种之一,具体取决于 HFCLKSRC 的配置。
HFCLK crystal oscillator : 16 or 32 MHz crystal oscillator
HFCLK RC oscillator : 16 MHz RC oscillator
HFCLK时钟控制器从HFCL派生出以下时钟给系统,这些时钟仅在系统处于ON模式时可用。
(当系统处于ON模式下时,HFCLK RC振荡器将自动启动,为系统提供所需的时钟)
HCLK: 16 MHz高频时钟,用于CPU和整个系统。
PCLK1M: 1 MHz外设时钟。
PCLK16M: 16 MHz外设时钟。
其时钟源来自以下三种之一
LFCLK crystal oscillator: 32.768 kHz crystal oscillator
LFCLK RC oscillator: 32.768 kHz RC oscillator
LFCLK synthesizer: 32.768 kHz synthesized from HFCLK
LFCLK时钟控制器为从LFCLK派生的系统提供以下时钟:
PCLK32KI: 32.768 kHz low frequency clock for peripherals
当系统从OFF模式转换到ON模式时,LFCLK时钟控制器和所有LFCLK时钟源默认关闭。
关于时钟部分的基本知识,我觉得这篇博客写得很不错 nRF51822 - 时钟设置。
如果是要自己设计nRF51822 的外围电路时,就像我最开始只用了一个16M的晶振,没有接外部的32.768K晶振,所以在这个时候使用官方样例的时候,就需要注意对于系统时钟初始化的操作了,往往就需要一个内部RC和外部晶振的转换设置。关于这方面的设置有很多博客说明,我参考的博客是----nRF51822 LRC 内部RC和外部晶振的设置。这里我就不搬运了,就说以下我在具体操作时代码的更改情况。
我使用的样例是ble_app_hrs,在进行将使用外部晶振转换为使用内部的RC振荡器时,我只是对 main.c 中的 ble_stack_init 下对时钟的设置,因为其他博客中提到的修改CLOCK_LFCLKSRC_SRC_Xtal 我并没有在这个样例代码中找到引用它的地方。
static void ble_stack_init(void)
{
uint32_t err_code;
//初始化SoftDevice处理程序模块。
//SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, false);//使用外部32.768K晶振
SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_RC_250_PPM_250MS_CALIBRATION, false);//使用内部的RC振荡器
......
}
在要开始使用定时器的时,我们必须要先开启对应的时钟源,所以在学习定时器之前需要先了解整个时钟系统。
TIMER 可以在两种模式下工作,定时器模式和计数器模式。在两种模式下,TIMER通过触发 START 任务启动,并通过触发 STOP 任务停止。定时器停止后,定时器可以通过再次触发START任务来恢复定时/计数。当恢复计时/计数时,计时器将从停止之前的值继续。
如果计时器在 STOP 之后不需要被再次启动,则可以使用 SHUTDOWN 任务代替或执行STOP任务。
我用的样例文件是ble_app_hrs,样例文件可以直接通过keil MDK 下载下来用。具体方法如下:
首先在keil的菜单栏处选择 加载出了Pack Installer页面之后先在左面选择框选择你的芯片对应的型号,再在右面选择框选择Examples,然后选择你需要的样例,再Copy到本地文件夹下就可以了。
该模块使应用程序能够基于 RTC1 外设创建多个定时器实例。检查用户超时处理程序的超时和调用是在 RTC1中断处理程序 中执行的。列表处理使用软件中断(SWI0)完成。两个中断处理程序都以APP_LOW优先级运行。调用app_timer_start() 或app_timer_stop() 时,定时器操作仅排队,并触发软件中断。实际的定时器启动/停止操作由SWI0中断处理程序执行。由于SWI0中断在APP_LOW中运行,如果调用定时器函数的应用程序代码在APP_LOW或APP_HIGH中运行,则在应用程序处理程序返回之前不会执行定时器操作。例如,在不使用调度程序时从超时处理程序停止计时器时就是这种情况。使用APP_TIMER_INIT()宏的USE_SCHEDULER参数选择是否应使用调度程序。即使未使用调度程序,app_timer.h也将包含app_scheduler.h,因此在编译时,app_scheduler.h必须在其中一个编译器包含路径中可用。
使用应用定时器主要的函数就是如上图所示的函数,我们需要配置的参数也就是前三个函数的入口参数。下面将定时每2S 返回一次电量通知的主要代码和定义列举了出来。
其中定时器的模式有两种:一种是定时一次就结束的定时器,第二种是计时器每次时间到期都会重新启动。
static app_timer_id_t m_battery_timer_id; /**< 电池定时器。 */
define APP_TIMER_PRESCALER 0 /**< RTC1 PRESCALER寄存器的值。 */
#define APP_TIMER_MAX_TIMERS 4 /**< 最大同时创建的计时器数。 */
#define APP_TIMER_OP_QUEUE_SIZE 5 /**< 定时器操作队列的大小。 */
#define BATTERY_LEVEL_MEAS_INTERVAL APP_TIMER_TICKS(2000, APP_TIMER_PRESCALER)/**< 电池电量测量间隔(滴答)。每2S收到一次电池电量通知 */
APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);
// Create timers
err_code = app_timer_create(&m_battery_timer_id,APP_TIMER_MODE_REPEATED,battery_level_meas_timeout_handler);
// 启动应用程序计时器
err_code = app_timer_start(m_battery_timer_id, BATTERY_LEVEL_MEAS_INTERVAL, NULL);
APP_ERROR_CHECK(err_code);
APP_TIMER_PRESCALER :为时钟分频值,决定了当前时钟采用的时钟频率,当时钟分频数为0时,表示采用的计算定时时间的时钟频率为32.768kHZ。
0 32768
1 16384
3 8192
7 4096
15 2048
31 1024
APP_TIMER_TICKS 为具体定时的毫秒值的宏定义,展开如下
APP_TIMER_TICKS (MS, PRESCALER)
== ((uint32_t)ROUNDED_DIV((MS) * (uint64_t)APP_TIMER_CLOCK_FREQ, ((PRESCALER) + 1) * 1000))
#define ROUNDED_DIV(A, B) (A + B / 2) / B
其中频率和分频值关系为
f RTC [kHz] = 32.768 / (PRESCALER + 1 )
注意:APP_TIMER_TICKS 返回的是 时钟滴答数
所以时钟分频数 = 0 时
定时1S 即APP_TIMER_TICKS(1000,APP_TIMER_PRESCALER)
计算 返回的时钟滴答数 = ((1000*32768)+1000/2)/1000 = 32768.5
又因为目前时钟分频数 = 0 即目前时钟采用频率为32.768kHz,即一秒钟产生32768次滴答,因此返回的32768滴答数刚好表示定时了1S。
以上都是我自己学习过程中收集的资料以及理解,若有任何不正确的地方,欢迎各位博友留言或者私信指出,我好及时修正错误,共同进步。
nRF51822定时器设置
nRF51822 LRC 内部RC和外部晶振的设置
nRF51822 - 时钟设置