open这个系统调用会建立一条到文件或者设备的访问路径,如果open调用成功的话,那么它将返回一个可以被read系统调用,write系统调用等方法使用的文件描述符。文件描述符是唯一的,他不会与任何其它运行中的进程共享。如果两个进程同时打开一个文件的话,那么它们将会得到两个不同的文件描述符,如果这两个进程都对文件进行写操作的话,那么它们会各写各的,这就造成了一个问题,谁也搞不清哪部分数据将会被覆盖。对此,我们要利用文件锁来防止这种问题的出现。
![](https://img.haomeiwen.com/i6038421/7236c69c9b767e43.jpg)
当芯片开始复位的时候,代码的执行顺序是先调用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
为什么需要这样做呢?答案是为了防止头文件重复包含。为什么还要两条下划线?也没什么,因为很少有这样命名变量的习惯,因此这样做可以避免重名。
网友评论