本帖最后由 正点原子运营 于 2023-7-26 14:32 编辑
第二十八章 硬件随机数实验
1)实验平台:正点原子探索者STM32F407开发板
2) 章节摘自【正点原子】STM32F407开发指南 V1.1
6)STM32技术交流QQ群:151941872
本章,我们将介绍STM32F407的硬件随机数发生器。我们使用KEY0按键来获取硬件随机数,并且将获取到的随机数值显示在LCD上面。同时,使用LED0指示程序运行状态。 本章分为如下几个小节: 28.1 随机数发生器简介 28.2 硬件设计 28.3 程序设计 28.4 下载验证
28.1 随机数发生器简介STM32F407自带了硬件随机数发生器(RNG),RNG处理器是一个以连续模拟噪声为基础的随机数发生器,在主机读数时提供一个32位的随机数。
28.1.1 RNG框图下面先来学习RNG框图,通过学习RNG框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。STM32F407的随机数发生器框图如图28.1.1.1所示: STM32F407的随机数发生器(RNG)采用模拟电路实现。此电路产生馈入线性反馈移位寄存器(RNG_LFSR)的种子,用于生成32位随机数。
该模拟电路由几个环形振荡器组成,振荡器的输出进行异或运算以产生种子。RNG_LFSR由专用时钟(PLL48CLK)按恒定频率提供时钟信息,因此随机数质量与HCLK频率无关。当将大量种子引入RNG_LFSR后,RNG_LFSR的内容会传入数据寄存器(RNG_DR)。
同时,系统会监视模拟种子和专用时钟rng_clk,当种子上出现异常序列,或rng_clk时钟频率过低时,可以由RNG_SR寄存器的对应位读取到,如果设置了中断,则在检测到错误时,还可以产生中断。
28.1.2 RNG寄存器l RNG控制寄存器(RNG_CR) RNG控制寄存器描述如图28.1.2.1所示: 该寄存器我们只关心RNGEN位,该位用于使能随机数发生器,所以必须设置为1。
l RNG状态寄存器(RNG_SR) RNG状态寄存器描述如图28.1.2.2所示: 该寄存器我们仅关心最低位(DRDY位),该位用于表示RNG_DR寄存器包含的随机数数据是否有效,如果该位为1,则说明RNG_DR的数据是有效的,可以读取出来了。读RNG_DR后,该位自动清零。
l RNG数据寄存器(RNG_DR) RNG数据寄存器描述如图28.1.2.3所示: RNG_DR寄存器是只读寄存器,我们可以读取该寄存器获得32位随机数值。此寄存器在最多40个PLL48CLK时钟周期后,又可以提供新的随机数值。
28.2 硬件设计
1. 例程功能本实验使用STM32F407自带的硬件随机数生成器(RNG),获取随机数,并显示在LCD屏幕上。按KEY0可以获取一次随机数。同时程序自动获取0~9范围内的随机数,显示在屏幕上。LED0闪烁用于提示程序正在运行。
2. 硬件资源1)LED灯 LED0 – PF9 2)独立按键 KEY0 – PE4 3)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 4)正点原子2.8/3.5/4.3/7寸 TFTLCD模块(仅限MCU屏,16位8080并口驱动) 5)RNG(硬件随机数生成器)
3. 原理图RNG属于STM32F407内部资源,通过软件设置好就可以了。本实验通过配合按键获取随机数和通过LCD显示。
28.3 程序设计
28.3.1 RNG的HAL库驱动RNG在HAL库中的驱动代码在stm32f4xx_hal_rng.c文件(及其头文件)中。
1. HAL_RNG_Init函数 RNG的初始化函数,其声明如下: - HAL_StatusTypeDefHAL_RNG_Init(RNG_HandleTypeDef *hrng);
复制代码l 函数描述: 用于初始化RNG。
l 函数形参: 形参1是RNG_HandleTypeDef结构体类型指针变量,其定义如下: - typedef struct
- {
- RNG_TypeDef *Instance; /* RNG基地址 */
- HAL_LockTypeDef Lock; /* RNG锁设置 */
- __IOHAL_RNG_StateTypeDef State; /* RNG设备访问状态 */
- __IO uint32_t ErrorCode; /* RNG错误代码 */
- uint32_t RandomNumber; /* RNG最后生成的随机数 */
- }RNG_HandleTypeDef;
复制代码1)Instance:指向RNG寄存器基地址。 2)Lock:用于配置锁状态。 3)State:RNG设备访问状态。 4)ErrorCode:RNG错误代码。 5)RandomNumber:该变量存储RNG最后生成的随机数。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
2. HAL_RNG_GenerateRandomNumber函数 HAL_RNG_GenerateRandomNumber是RNG生成随机数函数。其声明如下: - HAL_StatusTypeDefHAL_RNG_GenerateRandomNumber(RNG_HandleTypeDef *hrng,
- uint32_t *random32bit);
复制代码l 函数描述: 该函数用于RNG生成随机数。
l 函数形参: 形参1是RNG_HandleTypeDef结构体类型指针变量,即RNG的句柄。 形参2是uint32_t类型指针变量,随机32位指针,生成随机变量。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
RNG配置步骤 1)使能随机数发生器时钟 调用__HAL_RCC_RNG_CLK_ENABLE函数使能随机数发生器时钟,实际是通过设置AHB2ENR寄存器的相关位。
2)初始化(使能)随机数发生器 通过调用HAL_RNG_Init函数初始化随机数发生器,然后自行调用RNG的MSP回调函数,并使能随机数发生器。 当我们使用HAL_RNG_Init之后,在该函数内部,会调用RNG的MSP回调函数。回调函数中一般编写与MCU相关的外设时钟初始化以及NVIC配置。
3)判断DRDY位,读取随机数值 经过前面两个步骤,我们就可以读取随机数值了,不过每次读取之前,必须先判断RNG_SR寄存器的DRDY位,如果该位为1,则可以读取RNG_DR得到随机数值,如果不为1,则需要等待。在HAL库中,通过调用HAL_RNG_GenerateRandomNumber函数判断DRDY位并读取随机数值。 通过以上3个步骤的设置,我们就可以使用STM32F407的随机数发生器(RNG)了。
28.3.2 程序流程图 28.3.3 程序解析
1. RNG驱动代码这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。RNG驱动源码包括两个文件:rng.c和rng.h。 rng.h头文件只有函数的声明,下面我们直接介绍rng.c的程序,先看RNG的初始化函数,其定义如下: - /**
- *@brief 初始化RNG
- *@param 无
- *@retval 0,成功;1,失败
- */
- uint8_t rng_init(void)
- {
- uint16_t retry = 0;
- rng_handle.Instance = RNG;
- HAL_RNG_DeInit(&rng_handle);
- HAL_RNG_Init(&rng_handle); /* 初始化RNG */
- while (__HAL_RNG_GET_FLAG(&rng_handle, RNG_FLAG_DRDY) == RESET
- && retry < 10000) /* 等待RNG准备就绪 */
- {
- retry++;
- delay_us(10);
- }
- if (retry >= 10000)
- {
- return 1; /* 随机数产生器工作不正常 */
- }
- return 0;
- }
复制代码rng_init函数中调用HAL_RNG_Init函数初始化RNG,然后等待RNG初始化完成,同时判断初始化过程是否超时。
我们用HAL_RTC_MspInit回调函数来编写RNG时钟配置等代码,其定义如下: - /**
- *@brief RNG底层驱动,时钟源设置和使能
- *@note 此函数会被HAL_RNG_Init()调用
- *@param hrng:RNG句柄
- *@retval 无
- */
- voidHAL_RNG_MspInit(RNG_HandleTypeDef*hrng)
- {
- __HAL_RCC_RNG_CLK_ENABLE(); /* 使能RNG时钟 */
- }
- 最后两个是我们获取随机数函数,它们的定义如下:
- /**
- *@brief 得到随机数
- *@param 无
- *@retval 获取到的随机数(32bit)
- */
- uint32_t rng_get_random_num(void)
- {
- uint32_t randomnum;
- HAL_RNG_GenerateRandomNumber(&rng_handle, &randomnum);
- return randomnum;
- }
- /**
- *@brief 得到某个范围内的随机数
- *@param min,max: 最小,最大值.
- *@retval 得到的随机数(rval),满足:min<=rval<=max
- */
- intrng_get_random_range(int min, int max)
- {
- uint32_t randomnum;
- HAL_RNG_GenerateRandomNumber(&rng_handle, &randomnum);
- return randomnum%(max-min+1) + min;
- }
复制代码其中rng_get_random_num用于读取随机数值,而rng_get_random_range用于读取一个特定范围内的随机数,实际上它们都是通过调用的函数HAL_RNG_GenerateRandomNumber来实现的。
2. main.c代码main函数代码如下: - int main(void)
- {
- uint32_t random;
- uint8_t t = 0, key;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- usmart_dev.init(84); /* 初始化USMART */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- key_init(); /* 初始化按键 */
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "RNGTEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- while (rng_init()) /* 初始化随机数发生器 */
- {
- lcd_show_string(30, 130, 200, 16, 16, " RNG Error! ", RED);
- delay_ms(200);
- lcd_show_string(30, 130, 200, 16, 16, "RNGTrying...", RED);
- }
- lcd_show_string(30, 110, 200, 16, 16, "RNGReady! ", RED);
- lcd_show_string(30, 130, 200, 16, 16, "KEY0:GetRandom Num", RED);
- lcd_show_string(30, 150, 200, 16, 16, "RandomNum:", RED);
- lcd_show_string(30, 180, 200, 16, 16, "Random Num[0-9]:", RED);
- while (1)
- {
- key = key_scan(0);
- if (key == KEY0_PRES)
- {
- random =rng_get_random_num();
- lcd_show_num(30 + 8 * 11, 180, random, 10, 16, BLUE);
- }
- if ((t % 20) == 0)
- {
- LED0_TOGGLE(); /* 每200ms,翻转一次LED0 */
- random =rng_get_random_range(0, 9); /* 取[0,9]区间的随机数 */
- lcd_show_num(30 + 8 * 16, 210, random, 1, 16, BLUE);/* 显示随机数 */
- }
- delay_ms(10);
- t++;
- }
- }
复制代码该部分代码也比较简单,在所有外设初始化成功后,进入死循环,等待按键按下,如果KEY0按下,则调用rng_get_random_num函数,读取随机数值,并将读到的随机数显示在LCD上面。每隔200ms获取一次区间[0,9]的随机数,并实时显示在液晶上。同时LED0,周期性闪烁,400ms闪烁一次。
最后,为了方便测试,我们将rng_get_random_num和rng_get_random_range加入USMART,修改usmart_nametab如下: - struct _m_usmart_nametab usmart_nametab[] =
- {
- #if USMART_USE_WRFUNS == 1 /* 如果使能了读写操作 */
- (void *)read_addr, "uint32_tread_addr(uint32_t addr)",
- (void *)write_addr, "void write_addr(uint32_taddr, uint32_t val)",
- #endif
- (void *)delay_ms, "voiddelay_ms(uint16_t nms)",
- (void *)delay_us, "voiddelay_us(uint32_t nus)",
- (void *)rng_get_random_num, "uint32_trng_get_random_num(void)",
- (void *)rng_get_random_range, "intrng_get_random_range(int min, int max)",
- };
复制代码这样,我们便可以在串口输入我们想要调用的函数(rng_get_random_range或rng_get_random_num)进行测试。
28.4 下载验证将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。然后我们按下KEY0,就可以在屏幕上看到获取到的随机数。同时,就算不按KEY0,程序也会自动的获取0~9区间的随机数显示在LCD上面。实验结果如图28.4.1所示: 然后,我们还可以在串口调试助手里面,调用rng_get_random_num和rng_get_random_range函数,测试这两个函数的功能,大家可以自行测试。 |