时钟问题

open这个系统调用会建立一条到文件或者设备的访问路径,如果open调用成功的话,那么它将返回一个可以被read系统调用,write系统调用等方法使用的文件描述符。文件描述符是唯一的,他不会与任何其它运行中的进程共享。如果两个进程同时打开一个文件的话,那么它们将会得到两个不同的文件描述符,如果这两个进程都对文件进行写操作的话,那么它们会各写各的,这就造成了一个问题,谁也搞不清哪部分数据将会被覆盖。对此,我们要利用文件锁来防止这种问题的出现。


STM32时钟树

当芯片开始复位的时候,代码的执行顺序是先调用SystemInit函数,接着在进入_main函数(注意这个函数与main函数的区别,_main是一个c标准库的初始化函数),执行完_main函数后,最终才会执行用户文件里面所定义的main函数。

至于为什么会是这样,我们可以看看芯片的启动文件。对于我们的STM32芯片来说,ST已经提供了启动文件startup_stm32f10x_hd.s启动文件,我们截取相关的部分代码如下:

Reset_Handler PROC
  EXPORT Reset_Handler
  IMPORT __main ;从外部导入__main方法
  IMPORT SystemInit ;同理
  LDR R0, = SystemInit ;方法地址存储在了r0寄存器
  BLX R0;跳转执行SystemInit
  LDR R0, =__main
  BX R0
 ENDP

从启动代码我们可以看出,先是SystemInit,接着是__main,最后才是main。

回到时钟问题上。如果我们想要使用某个外设的话,那么必须先配置好系统时钟SYSCLK,接着在开启外设时钟。为了配置好系统时钟SYSCLK,我们需要设置好一系列的时钟来源,倍频,分频等参数,这些工作都是由system_stm32f10x.c文件定义的SystemInit所完成。对于SystemInit函数来说,它的工作流程是这样的:首先将与配置时钟相关的寄存器都复位为默认值,复位寄存器后,接着调用SetSysClock函数,而对于SetSysClock来说,它又是根据条件编译宏来调用最底层的设置系统时钟的函数,这里指的这些函数都已经是相当底层了,都是直接更寄存器打交道。下面我们可以看看SetSysClock函数。

static void SetSysClock(void){
  #ifdef SYSCLK_FREQ_HSE
    SetSysClockToHSE();
  #elif defined SYSCLK_FREQ_24MHz
    SetSysClockTo24();
  #elif defined SYSCLK_FREQ_36MHz
    SetSysClockTo36();
  #elif defined SYSCLK_FREQ_48MHz
    SetSysClockTo48();
  #elif defined SYSCLK_FREQ_56MHz
    SetSysClockTo56();  
  #elif defined SYSCLK_FREQ_72MHz
    SetSysClockTo72();
  #endif
}

接着看看ST库定义的条件编译宏,根据这些宏我们可以看出对于我们的stm32f103ze开发板来说,它的系统时钟SYSCLK默认是72mhz的。

#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
 #define SYSCLK_FREQ_24MHz  24000000
#else
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz  24000000 */ 
/* #define SYSCLK_FREQ_36MHz  36000000 */
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
#define SYSCLK_FREQ_72MHz  72000000
#endif

重要的事情多说一遍,对于我们的STM32F10XZE开发板来说,它的系统时钟SYSCLK默认是72mhz。

言归正传,既然配置系统时钟的问题已经让system_stm32f10x.c文件定义的SystemInit函数给完成了,那么我们接下来要做的工作就是开启外设时钟,对于外设时钟来说,我们知道外设挂载在两条总线上,分别为APB1低速外设总线以及APB2高速外设总线。歪个题,在开发板中有AHB总线,APB1,APB2。其中高速外设总线APB2使用的时钟是PCLK2时钟,它的默认值就是SYSCLK也就是说挂载在高速外设总线的外设使用的时钟频率都是72mhz。对于低速外设总线APB1来说,它所使用的时钟就是PCLK1时钟。对于APB1外设以及APB2外设来说,开启外设时钟所使用的库函数是不同的,分别为RCC_APB1PeriphClockCmd()以及RCC_APB2PeriphClockCmd()。

一个问题为什么在配置好了系统时钟SYSCLK之后还得费力开启外设时钟?答案就是为了减少功耗。

关于开启时钟需要注意的问题,如果我们使用了IO引脚的复用功能的话,那么我们还得开启复用功能对应外设的时钟。比如我们如果想利用GPIOX的某个引脚为ADC采集引脚的话,那么就得像下面这样做:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOX, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADCX, ENABLE);

关于控制I/O引脚输出高低电平的问题,很简单就是通过控制GPIOX_BSRR寄存器上相应的值,前16位是set,后16位是reset。对于ST库来说,它提供了GPIO_SetBits(GPIOX,GPIO_Pin_x)来set以及GPIO_ResetBits(GPIOX,GPIO_Pin_x)来reset。


c语言编写头文件小技巧,形如下面这样:

#ifndef __LED_H
#define __LED_H
...
#endif

为什么需要这样做呢?答案是为了防止头文件重复包含。为什么还要两条下划线?也没什么,因为很少有这样命名变量的习惯,因此这样做可以避免重名。


END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容