美文网首页
STM32(2):点亮LED(下)

STM32(2):点亮LED(下)

作者: 张叫兽的技术研究院 | 来源:发表于2022-01-03 20:49 被阅读0次

    本文摘自:
    https://blog.csdn.net/xiashiwendao/article/details/122292404

    概述

    点亮LED表面看起来貌似很简单,但是如何想要搞清楚其背后牵涉的每一行代码的具体含义,还是需要花费一些功夫的,而且,只有把LED的背后只是搞清楚了,才算嵌入式开发的基础入门。
    今天我们就来研究一下LED的重头戏,RCC_Init;什么是RCC?上手册:


    file

    RCC

    RCC,Reset and Clock Control,重置以及时钟控制;STM32手册使用了两个章节来对其进行描述,可见它的重要性;对于RCC的初始化也是比较复杂,里面包含了STM32对于时钟的相关机制,

    代码总览

    void RCC_init(uint16_t PLL)
    {
        uint32_t temp=0;  
    
        *((uint32_t *)RCC_CR) |= 0x00010000; 
        while(!( *((uint32_t *)RCC_CR) >>17));
    
        *((uint32_t *)RCC_CFGR) = 0X00000400;
    
        PLL -= 2;
        *((uint32_t *)RCC_CFGR) |= PLL<<18;   
    
        *((uint32_t *)RCC_CFGR) |= 1<<16;
    
        *((uint32_t *)FLASH_ACR)|=0x2;
        *((uint32_t *)RCC_CR) |= 0x01000000;
        while(!(*((uint32_t *)RCC_CR) >> 25));
    
        *((uint32_t *)RCC_CFGR) |= 0x00000002;
        while(temp != 0x02)
        {  
            temp = *((uint32_t *)RCC_CFGR) >> 2;
            temp &= 0x03;
        }   
    }
    

    使能HSE

    第一行有效代码,是熟悉的味道,前一节我们说过,或运算一般用于设定指定位(而且还不影响其他位)

    *((uint32_t *)RCC_CR) |= 0x00010000; 
    

    看到了RCC_CR,首先就是翻手册,RCC的章节的RCC_CR章节:


    file

    然后查看寄存器内部32位的定义:


    file

    0x00010000转换为32bit的二进制:0000 0000 0000 0001 0000 0000 0000 0000;这里有一个小技巧,就是学会分割来看,手册里面的寄存器的定义一般定义是分为上下两行的;从16进制来看,从左往右,前面4为是负责上面RCC_CFGR的高位16bit,后面4位是对应(二进制)低位16bit,即下面的16bit;对此次而言,低4位都是0,可以不需要care,直接关注高4位即可,其中高4为只有最后一位是有效设置,1对应的四位二进制是0001,即16位设置为1,查看寄存器定义的16位对应的是HSEON位,即使能HSEON。

    HSE和时钟

    什么是HSEON?需要拆开来看:HSE ON,HSE是High Speed External,外部高速时钟,所以HSEON就是使能外部高速时钟;
    为什么要配置HSE呢?因为在硬件系统中,各个部件的工作、协调,都是基于系统时钟的,比如我们通常讲的CPU的速度快慢,就是指CPU的时钟频率,即每秒钟能够工作多少个时钟;


    file

    小贴士:

    在win10系统中可以看到有两个频率,如下图,分别是1.9GHz以及2.11GHz;其中第一个是Intel提供的标准频率,其实是CPU名字的一部分,第二个频率是win10系统自己计算出来的,很多时候和Intel提供的频率值并不相等;不过两者相差也不会太大。

    系统时钟有三个来源,分别是HSE(High Spped External),HSI(High Speed Internal)以及PLLCLK,引入了PLL就是因为很多时候,需要基于系统时钟进行分频倍频,比如有的设备(RAM,DMA)需要比系统时钟快,于是需要通过PLL来进行加倍频率,还有的低俗的设备需要低于系统频率工作,也是通过PLL来进行减速,这样只需要一个系统时钟就可以同时满足高速和低俗设备。

    所以PLL的时钟源还是HSE,HSI,不过经过PLL倍频调节输出的时钟称之为PLLCLK;
    再回到我们的代码里面,这里配置时钟源就是HSEON(如果需要使用PLL则还需要将PLLx位配置为1);

    确认HSE使能生效

    完成了时钟源的配置,下面一步是等待HSEON的配置的生效:

    while(!( *((uint32_t *)RCC_CR) >>17));
    

    即第等待第18位(编号17)HSERDY的值变成1,当且仅当配置HSEON生效之后,该位置才会由硬件设置为1,注意在寄存器定义里面HSERDY配置为“r”,这个代表软件层面是无法改变这个bit的值,只能够读取:

    file
    然后再查看寄存器定义里面对于这一位的解释,0就是HSEON位设置并未生效,1就是设置已经生效:
    file
    等待的过程是使用位运算里面右移“>>"实现的,右移的运算规则里面是低位直接丢弃,例如11111右移4bit,最后结果就是1;这行代码的逻辑意义就是RCC_CR右边17位直接丢弃,即016为抛弃,只是保留了3118位(注意数字方向,是从左向右逐次减小的,和寄存器定义一致),共计15bit;
    又因为上一步骤中通过和0x00010000进行与运算将除了HSEON位之外的位置全部置为“0”了,所以之类RCC_CR右移17位之后,包括HSERDY在内的位全部都是0,只有当HSEON生效之后,HSERDY才是1,即*((uint32_t *)RCC_CR) >>17 = 1,于是此时退出while循环。

    配置其他时钟

    CR位配置解决了,下面我们看一下CFGR的配置:

    *((uint32_t *)RCC_CFGR) = 0X00000400;
    

    看到这里,我觉得我们可以总结一下写寄存器的基本套路:

    1. 查看手册相应章节,并了解缩写意义,比如CFGR,全称Clock Configuration Register,时钟配置寄存器:


      file
    2. 查看寄存器定义:


      file
    3. 查看具体的某一位的定义,比如我们这里设置为0X00000400,4是在后四位,所以重点关注低16bit,即从15 ~ 0bit:0000 0100 0000 0000,发现正好配置的PPRE1,值为100,手册介绍如下:


      file

    用来配置APB1的从HCLK中获得时钟的分频系数,二进制100对应的分频系数是2;HCLK是AHB总线的时钟;然后AHB经过AHB-APB桥接将时钟分配到APB1和APB2总线,APB1的总线对应PCLK1,APB2总线对应的PCLK2;这里配置的就是APB1时钟频率的分频值,为HCLK/2;

    这里有一个坑,虽然显式的为PCLK1赋值了,但是其实隐式的同时将PCLK2(对应APB2),AHB都赋值了;只不过配置的是0;

    比如PPRE2配置为000,对应的就是不分频,或者说分频数为1:


    file

    还有HPRE位,配置也是000,对应的不分频,或者说频数为1:


    file

    所以CFGR的配置重要的指定了AHB总线以及APB1和APB2总线的分频/倍频数;

    设置PLL倍频数

    继续看后面的代码,PLL是参数,即倍频数,PLL上面已经介绍了,专门用于接入时钟源然后后对其进行分频倍频再分配给各个总线(下面挂载的设备):

    PLL -= 2;
    *((uint32_t *)RCC_CFGR) |= PLL<<18;  
    

    那么这里为什么要-2呢?我们先存疑,理解了下一行代码谜底就自然打开了;

    PLL值左移18位,左移是位运算,左移+或运算 = 赋值操作,即将PLL值赋给18位起始的后面四位,通过查看寄存器定义可以看到CFGR的18位起始到后面四位是PLLMUL,再查看手册对于PLLMUL的解释:PLL的倍频;不过看一下赋值情况0010(十进制2)对应是4倍频,0011(十进制3)对应的是5倍频,以此类推,就是寄存器的只是和真实的倍频差2,看到这里你就明白了为什么在PLL -=2了:


    file

    紧随其后的,可以知道是要给CFGR的第16bit赋值为1:

    *((uint32_t *)RCC_CFGR) |= 1<<16;
    

    查看手册,是描述PLL时钟源,1对应是PLL的时钟原始PREDIV1:

    时钟树

    关于PLLSRC以及PREDIV1他们之间关系在时钟树(Clock Tree)上面有比较明确的关系说明,从下图可以看到作为时钟源最开始是HSE,然后通过时钟被分频后成为了PREDIV1,然后再除以2,获得了PLLSRC:


    file

    所以如果PLLMUL的值设置为1,即PLL的时钟源采用内部高速时钟(HSI),最终输出时钟信号在HSI/2即可;
    如果值设置为1即采用外部高速时钟(HSE),在经历了分频后输出为PREDIV1(时钟)信号,还需要再分频一下,分频系数为2;

    FLASH ARC配置

    再看下面的代码就没有那么怕了,直接明白,目的就是要给FLASH的ACR寄存器赋值:

    *((uint32_t *)FLASH_ACR)|=0x2; 
    

    不过这次要找FLash的ACR似乎并不那么顺利,很难直接从目录中查找到,需要全局搜一下ACR,不过还好,没有让我们搜索太多时间,在3.3.3章节中找到了:


    file

    寄存器的定义如下:


    file

    关于16进制转2进制补0

    这里其实有一个补0问题,就是对于32bit,0x2究竟是0x0000 0002,还是0x2000 0000呢?回归本源,这两种表达形式哪一个是2呢?毫无疑问,是第一个;
    所以这里的0x2,切换到32bit二进制表示就是:0000 0000 0000 0000 0000 0000 0000 0010;可以看到其实有效赋值是LATENCY,位的值是010,查看寄存器定义:


    file

    代表了比例,系统时钟比FLASH存取周期的值,010对应的2,即系统时间是Flash的两倍,所以同步的时候,需要做两个周期的延迟用以同步CPU和Flash之间时钟;
    为什么需要做此配置呢?首先程序都是存放在FLASH里面,CPU需要从FLASH里面取出执行指令,所以通信之前需要首先同步时钟;那么就需要知道Flash的时钟和系统时钟(CPU时钟)之间的差别;又因为我们配置时钟是72MHz,这里010的时钟范围包含了72MHz;

    使能PLL

    下面是对于RCC_CR寄存器最后的配置:

    *((uint32_t *)RCC_CR) |= 0x01000000;
    while(!(*((uint32_t *)RCC_CR) >> 25));
    

    还是套路:拆解0x0100 0000,只有高16位有有效值,低16位全零;高16位转换为2进制之后值为:0000 0001 0000 0000,所以我们看到对应操作的是PLLON位,PLLON位说明如下:


    file

    写入1则代表打开PLL,上面我们做了关于PLL的倍频的配置,PLL时钟源的配置,都是需要在PLL使能之后才会生效,在嵌入式开发中,所有的配置都是基于设备/ 组件使能的前提下才会配置生效;
    至于while语句和上面配置HSE使能的语句的等待READY的功效是一致的;可以通过查看寄存器定义,25位是PLLRDY,while循环就是等待这一位置为1,是否需要担心其他位有1从而影响循环判断?大可不必,因为31~26都是Reserved,必然为0,唯一的有效位就是PLLRDY位。
    可以看到在RCC初始化的代码中,每次配置一个使能都是需要通过while循环来确认配置成功了;

    配置PLLCLK为系统时钟

    最后一部分代码,让RCC_CFGR与0x00000002做或运算:

    *((uint32_t *)RCC_CFGR) |= 0x00000002;
    while(temp != 0x02)
    {  
            temp = *((uint32_t *)RCC_CFGR) >> 2;
            temp &= 0x03;
    }   
    

    有效为发生在低16位,0000 0000 0000 0010,有效配置位是最后两位,即SW位(Switch,切换系统时钟之意),解释如下,可以看到10代表使用PLL的输出(PLLCLK)作为系统时钟:


    file

    之后的while语句则是轮训查看使能配置是否生效;注意这里的使能生效并没有采用上面单句while循环的方式,而是采用赋值方式来进行的,就是因为会有其他位配置会影响判断;
    首先看一下SWS位说明:


    file

    赋值判断的方式也是非常巧妙,首先是RCC_CFGR右移(>>)2位,挤掉了SW位,现在SWS(Switch Status)位在最后面,然后将其和0x03进行与运算,与运算的目的就是:0位清零,1位保留原值(或运算目的:0位维持原值不变,1位用于置1)。

    所以,和temp进行与运算0x03的其实是30bit数(最后两位已经通过右移2位挤掉了):0000 0000 0000 0000 0000 0000 0000 11,所以就是要取用SWS的位的值,当返回值为“10”的时候,即0x02,代表PLL已经被设置为系统时钟,说明RCC_CFGR的配置已经生效。

    本文由博客一文多发平台 OpenWrite 发布!

    相关文章

      网友评论

          本文标题:STM32(2):点亮LED(下)

          本文链接:https://www.haomeiwen.com/subject/ybhfcrtx.html