zoukankan      html  css  js  c++  java
  • STM32(2):点亮LED(下)

    本文摘自:
    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的配置已经生效。

  • 相关阅读:
    js正则表达式中的问号使用技巧总结
    380. Insert Delete GetRandom O(1)
    34. Find First and Last Position of Element in Sorted Array
    162. Find Peak Element
    220. Contains Duplicate III
    269. Alien Dictionary
    18. 4Sum
    15. 3Sum
    224. Basic Calculator
    227. Basic Calculator II
  • 原文地址:https://www.cnblogs.com/xiashiwendao/p/15760663.html
Copyright © 2011-2022 走看看