第18章 硬件随机数发生器介绍
第十八章 硬件随机数发生器介绍
1. STM32F4 随机数发生器简介
STM32F4 自带了硬件随机数发生器(RNG), RNG 处理器是一个以连续模拟噪声为基础的随机数发生器,在主机读数时提供一个 32 位的随机数。 STM32F4 的随机数发生器框图如图:
STM32F4 的随机数发生器(RNG)采用模拟电路实现。此电路产生馈入线性反馈移位寄存器 (RNG_LFSR) 的种子,用于生成 32 位随机数。
该模拟电路由几个环形振荡器组成,振荡器的输出进行异或运算以产生种子。 RNG_LFSR由专用时钟 (PLL48CLK) 按恒定频率提供时钟信息,因此随机数质量与 HCLK 频率无关。当将大量种子引入 RNG_LFSR 后, RNG_LFSR 的内容会传入数据寄存器 (RNG_DR)。
同时,系统会监视模拟种子和专用时钟 PLL48CLK,当种子上出现异常序列,或 PLL48CLK时钟频率过低时,可以由 RNG_SR 寄存器的对应位读取到,如果设置了中断,则在检测到错误时,还可以产生中断。
接下来,我们介绍下 STM32F4 随机数发生器(RNG)的几个寄存器。
2. 相关寄存器介绍
2.1 控制寄存器(RNG_CR)
该寄存器只有 bit2 和 bit3 有效,用于使能随机数发生器和中断。我们一般不用中断,所以只需要设置 bit2 为 1,使能随机数发生器即可。
-
使能 RNG:
要启动 RNG 模块,需要将 EN 位设置为 1。 -
中断配置:
如果需要在生成随机数时使用中断,可以设置 IE 位为 1。
#include "stm32f4xx.h"
void RNG_Init(void) {
RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // 使能 RNG 时钟
RNG->CR |= RNG_CR_EN; // 使能 RNG
}
void EnableRNGInterrupt(void) {
RNG->CR |= RNG_CR_IE; // 使能 RNG 中断
}
// 中断服务程序示例
void RNG_IRQHandler(void) {
if (RNG->SR & RNG_SR_DRDY) {
uint32_t random_value = RNG->DR; // 获取随机数
// 处理随机数
}
}
int main(void) {
RNG_Init(); // 初始化 RNG
EnableRNGInterrupt(); // 启用 RNG 中断
while (1) {
// 主循环
}
}
2.2 状态寄存器(RNG_SR)
该寄存器我们仅关心最低位(DRDY 位),该位用于表示 RNG_DR 寄存器包含的随机数数据是否有效,如果该位为 1,则说明 RNG_DR 的数据是有效的,可以读取出来了。读 RNG_DR后,该位自动清零。
2.3 数据寄存器(RNG_DR)
在 RNG_SR 的 DRDY 位置位后,我们就可以读取该寄存器获得 32 位随机数值。此寄存器在最多 40 个 PLL48CK 时钟周期后,又可以提供新的随机数值。
3. 随机数发生器操作步骤
3.1 使能随机数发生器时钟
要使用随机数发生器,必须先使能其时钟。随机数发生器时钟来自 PLL48CK,通过AHB2ENR 寄存器使能。 所以我们调用使能 AHB2 总线外设时钟的函数使能 RNG 时钟即可:
__HAL_RCC_RNG_CLK_ENABLE(); // 使能 RNG 时钟
3.2 初始化(使能)随机数发生器
HAL 库提供了 HAL_RNG_Init 函数,该函数非常简单,主要作用是引导调用 RNG 的 MSP回调函数,然后使能随机数发生器。该函数声明如下:
HAL_StatusTypeDef HAL_RNG_Init(RNG_HandleTypeDef *hrng);
该函数非常简单,这里我们就不做过多讲解。使用方法如下:
RNG_HandleTypeDef RNG_Handler; // RNG 句柄
RNG_Handler.Instance = RNG;
HAL_RNG_Init(&RNG_Handler); // 初始化 RNG
当我们使用 HAL_RNG_Init 之后,在该函数内部,会调用 RNG 的 MSP 回调函数,回调函数声明如下:
void HAL_RNG_MspInit(RNG_HandleTypeDef *hrng);
回调函数中一般编写与 MCU 相关的外设时钟初始化以及 NVIC 配置。同时, HAL 库也提供了单独使能随机数发生器的方法为:
__HAL_RNG_ENABLE(hrng); // 使能 RNG
3.3 判断DRDY位,读取随机数值
经过前面两个步骤,我们就可以读取随机数值了,不过每次读取之前,必须先判断 RNG_SR寄存器的 DRDY 位,如果该位为 1,则可以读取 RNG_DR 得到随机数值,如果不为 1,则需要等待。
在 HAL 库中,判断 DRDY 位并读取随机数值的函数为:
uint32_t HAL_RNG_GetRandomNumber(RNG_HandleTypeDef *hrng);
4. 简单运用
4.1. 硬件连接
- 按键连接:将按键连接到某个 GPIO 引脚(例如
PA0
),并确保使用内部上拉电阻。 - 串口连接:将 STM32 的串口 TX 引脚连接到 USB 转串口模块,便于调试和输出随机数。
4.2. 软件配置
使用 STM32 HAL 库进行开发。以下是主要步骤:
- 配置 GPIO 引脚以读取按键输入。
- 配置 UART 以打印输出。
- 配置 RNG 以生成随机数。
4.3. 示例代码
下面是完整的代码示例:
#include "stm32f4xx_hal.h"
// 函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_RNG_Init(void);
UART_HandleTypeDef huart2;
RNG_HandleTypeDef hrng;
// 主函数
int main(void) {
HAL_Init(); // 初始化 HAL 库
SystemClock_Config(); // 配置系统时钟
MX_GPIO_Init(); // 初始化 GPIO
MX_USART2_UART_Init(); // 初始化 USART
MX_RNG_Init(); // 初始化 RNG
while (1) {
// 检查按键状态
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { // 按键被按下
uint32_t random_number;
// 生成随机数
HAL_RNG_GenerateRandomNumber(&hrng, &random_number);
// 打印随机数
char buffer[50];
int len = snprintf(buffer, sizeof(buffer), "Random Number: %lu\r\n", random_number);
HAL_UART_Transmit(&huart2, (uint8_t*)buffer, len, HAL_MAX_DELAY);
// 等待按键释放
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
HAL_Delay(100); // 消除抖动
}
}
}
// 系统时钟配置
void SystemClock_Config(void) {
// 根据具体的 STM32F4 板子进行时钟配置
}
// GPIO 初始化
static void MX_GPIO_Init(void) {
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0; // 按键引脚
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// USART 初始化
static void MX_USART2_UART_Init(void) {
__HAL_RCC_USART2_CLK_ENABLE(); // 使能 USART2 时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3; // TX 和 RX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // 高速
GPIO_InitStruct.Alternate = GPIO_AF7_USART2; // 复用功能
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200; // 波特率
huart2.Init.WordLength = UART_WORDLENGTH_8B; // 数据位
huart2.Init.StopBits = UART_STOPBITS_1; // 停止位
huart2.Init.Parity = UART_PARITY_NONE; // 无奇偶校验
huart2.Init.Mode = UART_MODE_TX_RX; // 收发模式
HAL_UART_Init(&huart2);
}
// RNG 初始化
static void MX_RNG_Init(void) {
__HAL_RCC_RNG_CLK_ENABLE(); // 使能 RNG 时钟
hrng.Instance = RNG;
HAL_RNG_Init(&hrng);
}