《DMG474开发指南_V1.1》第二十三章 硬件随机数实验

第二十三章 硬件随机数实验


       本章,我们将介绍STM32G474的硬件随机数发生器。我们使用KEY0按键来获取硬件随机数,并且将获取到的随机数值显示在LCD上面。同时,使用LED0指示程序运行状态。

       本章分为如下几个小节:

       23.1 随机数发生器简介

       23.2 硬件设计

       23.3 程序设计

       23.4 下载验证


        23.1 随机数发生器简介

       STM32G474自带了硬件随机数发生器(RNG),RNG处理器是一个以连续模拟噪声为基础的随机数发生器,在主机读数时提供一个32位的随机数。


       23.1.1 RNG框图

       下面先来学习RNG框图,通过学习RNG框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。STM32G474的随机数发生器框图如图23.1.1.1所示:


图23.1.1.1 随机数发生器(RNG)框图


       随机数发生器有2个时钟域:AHB时钟域和RNG时钟域。从RNG框图整体上知道,RNG有两个输入和一个输出。具体如下表:


表28.1.1.1 RNG内部输入/输出信号


       STM32G474的随机数发生器(RNG)采用模拟电路实现,由内部两个模拟噪声源产生种子,经过采样和归一化处理,再经过线性移位寄存器和判断逻辑,最终输出到RNG_DR,生成32 位随机数。

       每个模拟噪声源由3个环形振荡器组成,振荡器产生的输出经过异或运算产生种子,经过采样归一化处理后,输出到RNG内部的线性移位寄存器。采样频率由rng_clk时钟提供,因此,随机数质量与 HCLK 频率无关。当将大量种子引入线性移位寄存器后,经过判断逻辑,最终输出到数据寄存器 (RNG_DR)。

       同时,系统会监视模拟种子和专用时钟rng_clk,当种子上出现异常序列,或rng_clk时钟频率过低时,可以由RNG_SR寄存器的对应位读取到,如果设置了中断,则在检测到错误时,还可以产生中断。


       23.1.2 RNG寄存器

       l RNG控制寄存器(RNG_CR)

       RNG控制寄存器描述如图23.1.2.1所示:


图23.1.2.1 RNG_CR寄存器


       该寄存器我们只关心RNGEN位,该位用于使能随机数发生器,所以必须设置为1。

       l RNG状态寄存器(RNG_SR)

       RNG状态寄存器描述如图23.1.2.2所示:


图23.1.2.2 RNG_SR寄存器


       该寄存器我们仅关心最低位(DRDY位),该位用于表示RNG_DR寄存器包含的随机数数据是否有效,如果该位为1,则说明RNG_DR的数据是有效的,可以读取出来了。读RNG_DR后,该位自动清零。

       l RNG数据寄存器(RNG_DR)

       RNG数据寄存器描述如图23.1.2.3所示:


图23.1.2.3 RNG_DR寄存器


       RNG_DR寄存器是只读寄存器,我们可以读取该寄存器获得32位随机数值。此寄存器在最多216个AHB时钟周期后,又可以提供新的随机数值。


        23.2 硬件设计


       1. 例程功能

       本实验使用STM32G474自带的硬件随机数生成器(RNG),获取随机数,并显示在LCD屏幕上。按KEY0可以获取一次随机数。同时程序自动获取0~9范围内的随机数,显示在屏幕上。LED0闪烁用于提示程序正在运行。


       2. 硬件资源


       1)LED灯

               LED0 – PE0

       2)独立按键

               KEY0 – PE12

       3)串口1(PB6/PB7连接在板载USB转串口芯片CH340上面)

       4)正点原子1.3寸 TFTLCD模块(SPI接口)

       5)RNG(硬件随机数生成器)


       3. 原理图

       RNG属于STM32G474内部资源,通过软件设置好就可以了。本实验通过配合按键获取随机数和通过LCD显示。


        23.3 程序设计


       23.3.1 RNG的HAL库驱动

       RNG在HAL库中的驱动代码在stm32g4xx_hal_rng.c文件(及其头文件)中。

       1. HAL_RNG_Init函数

       RNG的初始化函数,其声明如下:

HAL_StatusTypeDef HAL_RNG_Init(RNG_HandleTypeDef *hrng);

       l 函数描述:

       用于初始化RNG。

       l 函数形参:

       形参1是RNG_HandleTypeDef结构体类型指针变量,其定义如下:

typedef struct
{
  RNG_TypeDef                   *Instance;     /* RNG基地址 */
  RNG_InitTypeDef               Init;          /* RNG初始化配置结构体 */
  HAL_LockTypeDef               Lock;          /* RNG锁设置 */
  __IO HAL_RNG_StateTypeDef   State;         /* RNG设备访问状态 */
  __IO  uint32_t                ErrorCode;     /* RNG错误代码 */
  uint32_t                       RandomNumber;  /* RNG最后生成的随机数 */
} RNG_HandleTypeDef;

       1)Instance:指向RNG寄存器基地址。

       2)Init:是的RNG初始化结构体,其结构体类型RTC_InitTypeDef定义如下:

typedef struct
{
  uint32_t            ClockErrorDetection;   /* CED时钟错误检测 */
} RNG_InitTypeDef;

       3)Lock:用于配置锁状态。

       4)State:RNG设备访问状态。

       5)ErrorCode :RNG错误代码

       6)RandomNumber :该变量存储RNG最后生成的随机数

       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。


       2. HAL_RNG_GenerateRandomNumber函数

       HAL_RNG_GenerateRandomNumber是RNG生成随机数函数。其声明如下:

HAL_StatusTypeDef HAL_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个步骤的设置,我们就可以使用STM32G474的随机数发生器(RNG)了。


       23.3.2 程序流程图


图23.3.2.1 硬件随机数实验程序流程图


       23.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;
    rng_handle.Init.ClockErrorDetection = RNG_CED_ENABLE;
    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      无
 */
void HAL_RNG_MspInit(RNG_HandleTypeDef *hrng)
{
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
    
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RNG;
    PeriphClkInit.RngClockSelection = RCC_RNGCLKSOURCE_PLL;
    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
    __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
 */
int rng_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.c 里面编写如下代码:

int main(void)
{
    uint32_t random;
    uint8_t t = 0, key;
    HAL_Init();                            /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8);  /* 设置时钟, 170Mhz */
    delay_init(170);                       /* 延时初始化 */
    usart_init(115200);                   /* 串口初始化为115200 */
    usmart_dev.init(170);                 /* 初始化USMART */
    led_init();                               /* 初始化LED */
    lcd_init();                           /* 初始化LCD */
    key_init();                           /* 初始化按键 */
 
    lcd_show_string(10, 10, 200, 32, 32, "STM32", RED);
    lcd_show_string(10, 45, 200, 24, 24, "RNG TEST", RED);
    lcd_show_string(10, 75, 200, 24, 24, "ATOM@ALIENTEK", RED);
 
    while (rng_init())  /* 初始化随机数发生器 */
    {
        lcd_show_string(10, 110, 200, 16, 16, "  RNG Error! ", RED);
        delay_ms(200);
        lcd_show_string(10, 110, 200, 16, 16, "RNG Trying...", RED);
    }
 
    lcd_show_string(10, 110, 200, 16, 16, "RNG Ready!   ", RED);
    lcd_show_string(10, 130, 200, 16, 16, "KEY0:Get Random Num", RED);
    lcd_show_string(10, 150, 200, 16, 16, "Random Num:", RED);
    lcd_show_string(10, 170, 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(10 + 8 * 11, 150, random, 10, 16, BLUE);
        }
        if ((t % 20) == 0)
        {
            LED0_TOGGLE();                            /* 每200ms,翻转一次LED0 */
            random = rng_get_random_range(0, 9); /* 取[0,9]区间的随机数 */
            lcd_show_num(10 + 8 * 16, 170, 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_t read_addr(uint32_t addr)",
    (void *)write_addr, "void write_addr(uint32_t addr,uint32_t val)",
#endif
    (void *)delay_ms, "void delay_ms(uint16_t nms)",
    (void *)delay_us, "void delay_us(uint32_t nus)",
        
    (void *)rng_get_random_num, "uint32_t rng_get_random_num(void)",
    (void *)rng_get_random_range, "int rng_get_random_range(int min, int max)",
};

       这样,我们便可以在串口输入我们想要调用的函数(rng_get_random_range或rng_get_random_num)进行测试。


        23 .4 下载验证

       将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。然后我们按下KEY0,就可以在屏幕上看到获取到的随机数。同时,就算不按KEY0,程序也会自动的获取0~9区间的随机数显示在LCD上面。实验结果如图23.4.1所示:


图23.4.1 获取随机数成功


请使用浏览器的分享功能分享到微信等