nRF52832——内部温度传感器与随机数产生
内部温度传感器
在 nrf52xx 系列芯片内部,包含一个内部温度传感器。在一些恶劣的应用环境下面,可以通过检测芯片内部温度而感知设备的工作环境温度,如果温度过高或者过低了则马上睡眠或者停止运转。 可以保证您的设备工作的可靠性。
内部温度传感器的主要功能是测量芯片温度,但是如果外部应用需要测量温度也可以使用。因为芯片外接负载一定的情况下,那么芯片的发热也基本稳定,相对于外界的温度而言,这个偏差值也是基本稳定的,这时可以通过线性补偿来实现应用。
这里列出的是温度传感器的主要功能:
- 温度范围大于或等于设备的工作温度
- 分辨率为 0.25 度
通过触发 START 任务启动 TEMP 温度传感器。温度测量完成后,将生成 DATARDY 事件,并可从 TEMP 寄存器读取测量结果。要达到电气规范中规定的测量精度,必须选择外部晶体振荡器作为HFCLK 源。 温度测量完成后,TEMP 这部分模拟电子设备掉电以节省电力。TEMP 仅支持一次性操作,这意味着必须使用 START 任务显式启动每个 TEMP 测量。
温度传感寄存器
内部温度传感器作为外部设备,基础地址为 0x4000C000,其寄存器描述如表
对应上面的寄存器,具体介绍以下几个寄存器,说明如下:
INTENSET 寄存器,1 - 使能 DATARDY 事件中断
INTENCLR 寄存器,1- 禁止 DATARDY 事件中断
TEMP 寄存器,温度单位:摄氏度,0.25 一个进位
A[n[ n = 0~5 寄存器,线性函数的斜率
B[n] n= 0~5 寄存器,段线性函数的 y 轴截距
T[n] n = 0~4 寄存器,分段线性函数的端点
温度传感器电气特征
任何的温度传感器都有其电气特性,也就是说有采集温度的范围和转换时间、精度等参数。不是说任何温度都可以进行采集的。nRF52 处理器的内部温度传感器的电气特性如下表所示,表中标注了温度采集的转换时间、温度测量范围、传感器精度、传感器分辨率、稳定性、偏移量等参数,这些参数在测量温度以及参数效正的时候需要参考的。
温度传感器库函数编程
对应温读采集的工程搭建,我们可以采用前面的串口组件库的工程例子进行修改。由于组件 库中提供了一个 nrf_temp.h 驱动文件,因此我们需要关注在工程配置 Options for Target 中的 C/C++ 选项卡下:Include Paths中添加\\modules\nrfx\hal路径,因为nrf_temp.h驱动文件位于modules\nrfx\hal 文件夹内,具体如下图
主函数中,需要在 main.c
文件最开头的文件包含中添加 nrf_temp.h
头文件。
- 初始化温度采集,有初始化函数
- 启动温度采集,等待采集过程完成。温湿度采集提供了寄存器 TASKS_START 来启动采集,当置位该寄存器,就可以启动内部温度采集模块进行温度采集了。
NRF_TEMP->TASKS_START = 1
采集需要时间,这个过程并不能立即打断,因此需要等待 EVENTS_DATARDY 事件是否被置位,如果置位了,表面临时缓冲区中温度数据已经采集存储就绪。或者在 INTENSET 寄存器中使能 DATARDY 事件的中断,采用中断方式进行判断。
- 读出采集温度的数值,并且停止温度采集。当临时缓冲中温度数据已经准备就绪后,就可以读出数据。读出数据的函数如下:
该函数从 TEMP 寄存器中读取 10 位 2 的补码值并将其转换为 32 位 2 的补码值,注意 TEMP 寄存器中存储的值是以 0.25°C 一个进位级,如果要转换为°C 为单位,则实际的温度值应该是*0.25 或者除以 4。 当温度读取成功后,对寄存器 TASKS_STOP 置位以停止温度采集。
采集步骤如下图所示:
具体程序代码如下:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "app_uart.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "nrf.h"
#include "bsp.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif
#include "nrf_temp.h"//添加内部温度库头文件
#define MAX_TEST_DATA_BYTES (15U) /**< max number of test bytes to be used for tx and rx. */
#define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */
void uart_error_handle(app_uart_evt_t * p_event)
{
if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_communication);
}
else if (p_event->evt_type == APP_UART_FIFO_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_code);
}
}
//串口初始化函数
void uart_init()
{
uint32_t err_code;
const app_uart_comm_params_t comm_params =
{
RX_PIN_NUMBER,
TX_PIN_NUMBER,
RTS_PIN_NUMBER,
CTS_PIN_NUMBER,
APP_UART_FLOW_CONTROL_DISABLED,
false,
UART_BAUDRATE_BAUDRATE_Baud115200
};
APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_error_handle,
APP_IRQ_PRIORITY_LOW,
err_code);
APP_ERROR_CHECK(err_code);
}
//主函数,主要实现了测试内部温度,通过串口输出
int main(void)
{
int32_t volatile temp;
//初始化LED灯
LEDS_CONFIGURE(LEDS_MASK);
LEDS_OFF(LEDS_MASK);
//初始化串口
uart_init();
//初始化内部温度传感器
nrf_temp_init();
printf("Temperature example started.");
//循环测试内部温度
while (true)
{
//开始温度测量
NRF_TEMP->TASKS_START = 1;
/*当温度测量尚未完成时,如果为DATARDY事件启用中断并在中断中读取结果,则可以跳过等待。 */
while (NRF_TEMP->EVENTS_DATARDY == 0)
{
//不做任何事
}
NRF_TEMP->EVENTS_DATARDY = 0;
//停止任务清除临时寄存器。
temp = (nrf_temp_read() / 4);
/*TEMP:当DATARDY事件发生时,Temp模块模拟前端不断电。*/
NRF_TEMP->TASKS_STOP = 1; /** 停止温度测量。 */
printf("温度采样: %u\n\r", (uint8_t)(temp));
nrf_gpio_pin_toggle(LED_1);
nrf_delay_ms(800);
}
}
随机数产生器
这里的随机数产生器并不是通常 C 语言中常用的 random()
随机数生成,而是基于内部热噪声产生的真实非确定性的随机数。通常可以用于通讯加密等场合使用。
该随机数发生器 RNG 通过触发 START 任务进行启动;通过触发 STOP 任务停止运行。在启动时,新的随机数连续产生,并在准备好时写入 VALUE 寄存器中。随着每次新的随机数写入到 VALUE 寄存器,都会触发一个 VALRDY 事件。这意味着生成 VALRDY 事件后,直到下一个 VALRDY 事件之前这段时间,CPU 有足够的时间在新的随机数覆盖旧的随机数之前,从 VALUE 寄存器中读出的旧随机数的值。
随机数发生器的应用场合
随机数发生器常用于处理器和设备之间的通讯中进行信息加密和身份认证,其应用场合如下:
- 某设备发送操作请求给 MCU,MCU 利用随机数发生器产生一段随机序列(明文),MCU 采用加密方式对明文进行加密产生一段密文(明文 + 用户密码 = 密文)。然后 MCU 发送密文给 某设备。
- 某设备利用用户密码,同时采用相同的解密方式对密文进行解密,还原出明文,并发送给 MCU。MCU 在 100ms 内等待设备端解密出来的明文,并与随机数发生器随机产生的明文做比较。 若比较正确则认为设备端输入了正确的密码,如果错误则终止通讯。 整个通信过程都中,明文都是随机序列。如果设备端无正确的密码,通讯过程是无法正确进行 的。因此实现了安全的密码验证授权!这种方式比较常见的如应用于 AES 加密算法。
偏差矫正
在内部比特流上采用偏差校正算法以消除对“1”或“0”的任何偏差。然后将这些位排队到一 个 8 位寄存器,以便从 VALUE 寄存器进行并行读出。可以在 CONFIG 寄存器中启用偏差校正。这将导致较慢的值生成,但将确保随机值的统计均匀分布。 生成速度:生成一个随机字节数据所需的时间是不可预测的,并且可能在一个字节到下一个字节之间变化。启用偏差校正时尤其如此。
随机数发生器寄存器
内部温度传感器作为外部设备,基础地址为 0x4000D000 ,其寄存器描述如下:
以上寄存器详细介绍几个经常用到的:
- SHORTS 快捷寄存器:用于快速通过 AVLRDY 事件触发 STOP 任务
- INTENSET 启用中断寄存器:使能随机数中断
位数 | 复位值 | Field | Value ID | Value | 描述:可读可写寄存器 |
---|---|---|---|---|---|
第 0 位 | 0x00000000 | VALRDY | Set | – | 写入 1 为启用 VALRDY 事件中断,使能中断 |
- INTENCLR 关闭中断寄存器:禁止随机数中断
- CONFIG 配置寄存器:配置是否开启偏差校正
- VALUE 输出随机数寄存器:随机数的存储寄存器
随机数发生器库函数编程
库函数使用流程
随机数发生器采用组件库编程会大大降低编程难度,优化数据处理过程。下面详细讲解组件库 编写随机数发生器的过程,并且进入到库函数内部,探讨整个随机数产生以及读取的过程。
- 使用 RNG 模块之前,首先初始化 RNG 发生器模块,在 SDK 的组件库中,提供了
nrf_drv_rng.c
这样一个库文件,该文件中提供了一个nrf_drv_rng_init()
函数,用于初始化 RNG 模块功能。nrf_drv_rng_init
函数介绍如下:
这里参数nrf_drv_mg_config_t const *p_config
也是提供了一个结构体对初始化参数进行定义,这个结构体如下:
/** * @brief Struct for RNG configuration. */
typedef struct
{
bool error_correction : 1; /**< Error correction flag. 误差修正的帧*/
uint8_t interrupt_priority; /**< interrupt priority 中断优先级*/
} nrfx_rng_config_t;
这个结构体配置了两个参数,一个帧标注是否有数据修正,一个设置了优先级。我们可以进入 nrf_drv_mg_init()
函数内部看一下具体的操作。
/*默认配置 */
#define NRFX_RNG_DEFAULT_CONFIG \ {
\ .error_correction = NRFX_RNG_CONFIG_ERROR_CORRECTION, \ .interrupt_priority = NRFX_RNG_CONFIG_IRQ_PRIORITY, \ }
#define NRF_DRV_RNG_DEFAULT_CONFIG NRFX_RNG_DEFAULT_CONFIG
static const nrf_drv_rng_config_t m_default_config = NRF_DRV_RNG_DEFAULT_CONFIG;
ret_code_t nrf_drv_rng_init(nrf_drv_rng_config_t const * p_config)
{
ret_code_t err_code = NRF_SUCCESS;
if (m_rng_cb.state != NRFX_DRV_STATE_UNINITIALIZED)//如果没有初始化
{
return NRF_ERROR_MODULE_ALREADY_INITIALIZED;//返回已经初始化的状态
}
if (p_config == NULL)//如果配置为NULL,
{
p_config = &m_default_config;//设置为默认配置
}
m_rng_cb.config = *p_config;
NRF_DRV_RNG_LOCK();//RNG锁定
if (!NRF_DRV_RNG_SD_IS_ENABLED())
{
err_code = nrfx_rng_init(&m_rng_cb.config, nrfx_rng_handler);//触发RNG,并且触发RNG事件
if (err_code != NRF_SUCCESS)
{
return err_code;
}
nrfx_rng_start();//开始RNG
}
m_rng_cb.state = NRFX_DRV_STATE_INITIALIZED;
NRF_DRV_RNG_RELEASE();//释放
return err_code;
}
默认配置参数在 sdk_config.h
文件中,具体配置如下:
// <e> NRFX_RNG_ENABLED - nrfx_rng - RNG peripheral driver
//==========================================================
#ifndef NRFX_RNG_ENABLED
#define NRFX_RNG_ENABLED 0
#endif
// <q> NRFX_RNG_CONFIG_ERROR_CORRECTION - Error correction
#ifndef NRFX_RNG_CONFIG_ERROR_CORRECTION
#define NRFX_RNG_CONFIG_ERROR_CORRECTION 1
#endif
// <o> NRFX_RNG_CONFIG_IRQ_PRIORITY - Interrupt priority
// <0=> 0 (highest)
// <1=> 1
// <2=> 2
// <3=> 3
// <4=> 4
// <5=> 5
// <6=> 6
// <7=> 7
#ifndef NRFX_RNG_CONFIG_IRQ_PRIORITY
#define NRFX_RNG_CONFIG_IRQ_PRIORITY 7
#endif
nrfx_rng_init()
是初始化 RNC 配置,首先开始错误修正功能,然后配置 RNC 中断优先级,开启 RNC 中断。中断执行后会触发 nrfx_rng_evt_handler
的回调事件。
nrfx_err_t nrfx_rng_init(nrfx_rng_config_t const * p_config, nrfx_rng_evt_handler_t handler)
{
NRFX_ASSERT(p_config);
NRFX_ASSERT(handler);
if (m_rng_state != NRFX_DRV_STATE_UNINITIALIZED)
{
return NRFX_ERROR_ALREADY_INITIALIZED;
}
m_rng_hndl = handler;
/* 开启错误修正 */
if (p_config->error_correction)
{
nrf_rng_error_correction_enable();
}
/* 设置中断优先级,并且开启中断 */
nrf_rng_shorts_disable(NRF_RNG_SHORT_VALRDY_STOP_MASK);
NRFX_IRQ_PRIORITY_SET(RNG_IRQn, p_config->interrupt_priority);
NRFX_IRQ_ENABLE(RNG_IRQn);
m_rng_state = NRFX_DRV_STATE_INITIALIZED;
return NRFX_SUCCESS;
}
之后调用 nrfx_rng_start()
开始 RNC 随机数发生器运行。一旦开始运行,新的随机数便会产生,并在准备好时写入 VALUE 寄存器中。随着新的随机数写入会触发一个 VALRDY 中断事件。
void nrfx_rng_start(void)
{
NRFX_ASSERT(m_rng_state == NRFX_DRV_STATE_INITIALIZED);
nrf_rng_event_clear(NRF_RNG_EVENT_VALRDY);
nrf_rng_int_enable(NRF_RNG_INT_VALRDY_MASK);
nrf_rng_task_trigger(NRF_RNG_TASK_START);
}
中断函数在 nrfx_rng.c
进行了定义,前面已经使能了中断,当中断事件发生后,我们进入中断。中断中首先清除 VALRDY 事件标志,然后通过 nrf_rng_random_value_get
函数读取出 VALUE 寄存器的值,最后去执行 nrfx_rng_evt_handler
的回调事件。
void nrfx_rng_irq_handler(void)
{
nrf_rng_event_clear(NRF_RNG_EVENT_VALRDY);//清除中断标志
uint8_t rng_value = nrf_rng_random_value_get();//读取VALUE寄存器值
m_rng_hndl(rng_value);//执行回调事件
NRFX_LOG_DEBUG("Event: NRF_RNG_EVENT_VALRDY.");
}
nrfx_rng_evt_handler
回调事件主要是需要把 VALUE 寄存器中读出的值放入到队列文件中声明的一个专用放置随机数的 m_rand_pool
池中,如果池子满了,避免溢出会停止 RNC 模块的运行。
static void nrfx_rng_handler(uint8_t rng_val)
{
NRF_DRV_RNG_LOCK();
if (!NRF_DRV_RNG_SD_IS_ENABLED())
{
UNUSED_RETURN_VALUE(nrf_queue_push(&m_rand_pool, &rng_val));//把VALUE读取的随机数放入pool池子中
if (nrf_queue_is_full(&m_rand_pool))//如果队列满了
{
nrfx_rng_stop();//停止RNG
}
NRF_LOG_DEBUG("Event: NRF_RNG_EVENT_VALRDY.");
}
NRF_DRV_RNG_RELEASE();
}
执行完回调后,m_rand_pool
中就会有 RNC 模块参数的随机数了。
关于这个 m_rand_pool
池的说明: 在 SDK 中提供了一个 nrf_queue.c
文件,专门来提供类似堆栈的缓冲区,相当一个存储池。随 机数这个工程中需要调用这个函数,并且在 nrf_drv_rng.c
库文件中,调用了这个队列的定义,声明 了一个 m_rand_pool
池,代码如下所示:
#define RNG_CONFIG_POOL_SIZE 64
/**@brief Supported queue modes. */
typedef enum
{
NRF_QUEUE_MODE_OVERFLOW, //覆盖模式:!< If the queue is full, new element will overwrite the oldest.
NRF_QUEUE_MODE_NO_OVERFLOW, //溢出模式:!< If the queue is full, new element will not be accepted.
} nrf_queue_mode_t;
// nrf_drv_rng.c 中声明
NRF_QUEUE_DEF(uint8_t, m_rand_pool, RNG_CONFIG_POOL_SIZE, NRF_QUEUE_MODE_OVERFLOW);
默认配置为覆盖模式,新的数据过来就会覆盖老的数据。因此前面我们会判断 pool 池子是否满 了,如果满了就停止 RNG 产生新的随机数,避免数据被覆盖。
- 第二步从
pool
池中读出对应字节的随机数,并且把读出的随机数放入一个缓冲向量中,然后 通过串口在 PC 机上打印进行观察。这时候我们可以调用两个库函数nrf_drv_rng_bytes_available
和nrf_drv_rng_rand
,这两个函数介绍如下:
使用如下代码获取pool
池中随机数的大小,比较设置的 BUFF 大小,选择最小值作为存入 BUFF 的长度,避免数据丢失。
/** 函数功能是用于获取随机数,把随机数放入缓冲向量。 * * 参数 p_buff 指向unit8_t缓冲区的指针,用于存储随机数。 * 参数 length 从pool池中取出并放入p_buff中的字节数。 * * 返回值 实际放置在p_buff中的字节数。 */
static uint8_t random_vector_generate(uint8_t * p_buff, uint8_t size)
{
uint32_t err_code;
uint8_t available;
nrf_drv_rng_bytes_available(&available);//获取可用的随机数
uint8_t length = MIN(size, available);
err_code = nrf_drv_rng_rand(p_buff, length);//随机数放入缓冲
APP_ERROR_CHECK(err_code);
return length;
}
那么整个随机数的获取,以及随机数读出过程。可以总结如下图所示的流程。主程序和 中断程序相互独立。如果使能了 RNG 中断,当然 RNG 启动后,CPU 就会把新产生的随机数放入到 VALUE 寄存器中就会触发对应中断,在中断中完成把 VALUE 寄存器中的随机数放入到 pool 队列 池的过程。当 pool 队列池满了后,会停止 RNG 模块,退出中断。主函数则是以循环扫描的方式读 取 pool 队列池中的随机数,放入到缓冲数组中,通过串口打印输出进行观察。
RNG 工程搭建使用
搭建 RNC 工程目录,随机数发生器的演示工程可以在串口例程上进行修改,如下图所示。需要添加是三个驱动库函数,分别为:nrfx_rng.c
、nrf_drv_rng.c
和 nrf_queue.c
函数。
添加驱动文件库后,这些文件库的路径也需要进行链接,路径如下图所示。在工程配置 Options for Target 中的 C/C++选项卡下:Include Paths 中添加:
由于 nrf_queue.h
的头文件是在 nrf_drv_rng.c
文件中被引用、nrfx_rng.h
的头文件在 nrf_drv_rng.h
文件中被引用。所以主函数中只需要引用一个头文件 nrf_drv_rng.h
就可以了对这三个函数进行调用。
同时需要在 sdk_config.h
文件中,添加配置 RNG 相关的配置,注意串口的 sdk_config.h
文件中 是没有 RNG 相关的配置的,需要自己手动添加的,具体添加内容请例程参考代码。如果添加成功, 切换到配置导航选项卡 configuarton wizard
上,会出现对应配置被勾选,如下图所示的。
完成后编写 main.c
文件,详细内容如下:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "app_uart.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "nrf.h"
#include "bsp.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif
#include "nrf_drv_rng.h"
#define MAX_TEST_DATA_BYTES (15U) /*发送和接收的最大测试字节. */
#define UART_TX_BUF_SIZE 256 /*发送缓冲的大小 */
#define UART_RX_BUF_SIZE 256 /*接收缓冲的大小 */
#define RANDOM_BUFF_SIZE 16 /**< Random numbers buffer size. */
/** 函数功能是用于获取随机数,把随机数放入缓冲向量。 * * 参数 p_buff 指向unit8_t缓冲区的指针,用于存储随机数。 * 参数 length 从pool池中取出并放入p_buff中的字节数。 * * 返回值 实际放置在p_buff中的字节数。 */
static uint8_t random_vector_generate(uint8_t * p_buff, uint8_t size)
{
uint32_t err_code;
uint8_t available;
nrf_drv_rng_bytes_available(&available);//获取可用的随机数
uint8_t length = MIN(size, available);
err_code = nrf_drv_rng_rand(p_buff, length);//随机数放入缓冲
APP_ERROR_CHECK(err_code);
return length;
}
/*串口中断回调*/
void uart_Interrupt_handle(app_uart_evt_t * p_event)
{
if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_communication);
}
}
/** *主函数:循环输出随机数 */
int main(void)
{
LEDS_CONFIGURE(LEDS_MASK);
LEDS_OFF(LEDS_MASK);
uint32_t err_code;
const app_uart_comm_params_t comm_params =
{
RX_PIN_NUMBER,
TX_PIN_NUMBER,
RTS_PIN_NUMBER,
CTS_PIN_NUMBER,
APP_UART_FLOW_CONTROL_DISABLED,
false,
UART_BAUDRATE_BAUDRATE_Baud115200
};
APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_Interrupt_handle,
APP_IRQ_PRIORITY_LOW,
err_code);
APP_ERROR_CHECK(err_code);
//内部随机数模块初始化
err_code = nrf_drv_rng_init(NULL);
APP_ERROR_CHECK(err_code);
printf("RNG example started.");
while (true)
{
uint8_t p_buff[RANDOM_BUFF_SIZE];
//计算随机数长度
uint8_t length = random_vector_generate(p_buff,RANDOM_BUFF_SIZE);
printf("Random Vector:");
//输出随机数
for(uint8_t i = 0; i < length; i++)
{
printf(" %2x",(int)p_buff[i]);
}
printf("\n\r");
nrf_delay_ms(1000);
}
}
文章评论