标题写的高大上,其实内容简单清晰。今天就来看看,你写的C程序是怎么调用库函数来完成对寄存器的操作,如果你一清二楚,扫一眼下面的内容即可,如若不然,还是老老实实看下去吧,相信对你会有一点点帮助的。单就GPIOB这个外设来谈谈,你可以找任一一个外设来按照下面的顺序看。
一.程序目标:使红灯亮
二.程序设计:看LED的原理图,通过使能相应的外设,和操作外设完成目标。(看了原理图我们知道,只要让相应的端口位输出低电平即可点亮小灯)
三.程序代码分析:
1.stm32f10x.h文件部分代码分析
typedef struct { __IO uint32_t CRL; __IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR; } GPIO_TypeDef;
以上代码是F1系列的标准库里的stm32f10x.h文件里的GPIO的结构体定义。不同系列的标准库包可在ST官网免费下载,很清楚的可以看到就是7个寄存器来构成这个外设的。
#define FLASH_BASE ((uint32_t)0x08000000) #define SRAM_BASE ((uint32_t)0x20000000) #define PERIPH_BASE ((uint32_t)0x40000000) #define SRAM_BB_BASE ((uint32_t)0x22000000) #define PERIPH_BB_BASE ((uint32_t)0x42000000) #define FSMC_R_BASE ((uint32_t)0xA0000000) /*!< Peripheral memory map */ #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
那么我们要找到GPIOB,就得知道GPIOB的地址,因为他是外设,我们就得先找外设的基地址,但从上面可以看出,外设的基地址有三种类型,也就是所谓的三种总线类型,而GPIOB是挂载在APB2总线上的,所以他的地址如下:
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
那我们就知道了GPIOB的基地址了,可是光知道地址怎么操作这个外设的七个寄存器?那你肯定知道需要将他转换为GPIO结构体的指针,才能使用结构体的成员,也即寄存器,转换过程如下:
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
那为什么对寄存器的某些位写1写0,就能得到我们想要的配置呢?这个其实就是由硬件规定的,下面我们看看GPIO的ODR输出数据寄存器的位定义,从下面的代码可以得出,ODR寄存器的每一个功能位,都对应一个端口(16个)。这个定义主要得目的就是对寄存器的各个功能位位进行显式区别(以命名的方式,让程序变得易读,而且逻辑清晰)。
#define GPIO_ODR_ODR0 ((uint16_t)0x0001) #define GPIO_ODR_ODR1 ((uint16_t)0x0002) #define GPIO_ODR_ODR2 ((uint16_t)0x0004) #define GPIO_ODR_ODR3 ((uint16_t)0x0008) #define GPIO_ODR_ODR4 ((uint16_t)0x0010) #define GPIO_ODR_ODR5 ((uint16_t)0x0020) #define GPIO_ODR_ODR6 ((uint16_t)0x0040) #define GPIO_ODR_ODR7 ((uint16_t)0x0080) #define GPIO_ODR_ODR8 ((uint16_t)0x0100) #define GPIO_ODR_ODR9 ((uint16_t)0x0200) #define GPIO_ODR_ODR10 ((uint16_t)0x0400) #define GPIO_ODR_ODR11 ((uint16_t)0x0800) #define GPIO_ODR_ODR12 ((uint16_t)0x1000) #define GPIO_ODR_ODR13 ((uint16_t)0x2000) #define GPIO_ODR_ODR14 ((uint16_t)0x4000) #define GPIO_ODR_ODR15 ((uint16_t)0x8000)
2.stm32f10x_gpio.h文件部分代码分析:
首要的是看这个外设的初始化结构体,
typedef struct { uint16_t GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode; }GPIO_InitTypeDef;
这个初始化结构体,是每个外设都会有的,它的主要作用就是来配置用户自己想要的外设工作模式,多样的工作模式说白了就是功能多,应该是现在MCU的一大亮点,会发现,这个初始化结构体里的结构体和其他成员,都是在这个文件里定义的,具体的就不贴出来的,相信你手里肯定有源码,一般里面的结构体类型成员和普通成员,都是作为配置参数(定义成不同的数字编码),以被函数调用,最终写入对应寄存器(对对应位进行读写),完成操作目标的。下面的来自F1官方数据手册的图片截取和代码对应起来你就一清二楚了。以前我说的都很抽象,现在尽量会贴代码。
typedef enum { GPIO_Speed_10MHz = 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz }GPIOSpeed_TypeDef;
3.stm32f10x.c文件部分代码分析:
说了这么多,还是没见着库函数,下面我们就来看看,当然,这些库函数在上面的.h文件里声明了对应的接口,这是C程序模块化编程的重要思想,下面我们就来看一个简单的初始化函数,一般参数配置完成后,都会调用这个函数来完成初始化操作,根据我们上面说的,其实知道这里面就是操作寄存器来完成工作方式的配置,然后.h提供接口,供其他函数调用,当你打开这个文件,你会发现还有很多的函数,你甚至可以添加自己的函数进入这个文件(如果你觉得自己的函数很常用,很有意思),当然标准库提供的函数一般来讲是足够使用的了。
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) { uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00; uint32_t tmpreg = 0x00, pinmask = 0x00; /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode)); assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)); /*---------------------------- GPIO Mode Configuration -----------------------*/ currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F); if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) { /* Check the parameters */ assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); /* Output mode */ currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed; } /*---------------------------- GPIO CRL Configuration ------------------------*/ /* Configure the eight low port pins */ if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) { tmpreg = GPIOx->CRL; for (pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = ((uint32_t)0x01) << pinpos; /* Get the port pins position */ currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; if (currentpin == pos) { pos = pinpos << 2; /* Clear the corresponding low control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* Write the mode configuration in the corresponding bits */ tmpreg |= (currentmode << pos); /* Reset the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((uint32_t)0x01) << pinpos); } else { /* Set the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((uint32_t)0x01) << pinpos); } } } } GPIOx->CRL = tmpreg; } /*---------------------------- GPIO CRH Configuration ------------------------*/ /* Configure the eight high port pins */ if (GPIO_InitStruct->GPIO_Pin > 0x00FF) { tmpreg = GPIOx->CRH; for (pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = (((uint32_t)0x01) << (pinpos + 0x08)); /* Get the port pins position */ currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos); if (currentpin == pos) { pos = pinpos << 2; /* Clear the corresponding high control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* Write the mode configuration in the corresponding bits */ tmpreg |= (currentmode << pos); /* Reset the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08)); } /* Set the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08)); } } } GPIOx->CRH = tmpreg; } }
4.bsp_led.h部分文件代码分析:
以下就不是库函数的范畴了,不过还是看一下,这可是你自己写的代码了,你想怎么写就怎么写,这是你驱动的头文件,这样的方式,看似多次一举,其实会增加你程序的可读性和可移植性,这两点很重要,还会提供接口以供其他文件使用(声明函数)。
#ifndef __LED_H #define __LED_H #include"stm32f10x.h" #define red_PORT GPIOB #define red_PIN GPIO_Pin_5 #define red_LCK RCC_APB2Periph_GPIOB
#define ON 0
#define OFF 1
#define red(a) if(a) GPIO_SetBits(red_PORT,red_PIN);\ else GPIO_ResetBits(red_PORT,red_PIN) void LED_GPIO_Config(void);
5.bsp_led.c文件分析:
从GPIO_Init函数的参数可以看出,都是指针类型的,外设GPIOB本身就是一个指针,而初始化结构体,在声明一个对象的时候,是声明一个普通结构体变量,所以在调用的时候会加&取地址符(这可不是引用,C没有引用),为什么有些库函数在设计的时候要用指针作为参数?我觉得应该效率会更高。
#include "bsp_led.h" void LED_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(red_LCK|green_LCK|blue_LCK,ENABLE); GPIO_InitStructure.GPIO_Pin=red_PIN; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(red_PORT,&GPIO_InitStructure); }
6.main.c文件:
到这就没什么好说的了,全凭自己发挥了。
#include"stm32f10x.h" #include"bsp_led.h" int main(void) { LED_GPIO_Config(); while(1) { red(ON); } }
额,以上就是通过一个最简单的例子来分析了一下,stm32的main函数里一句简简单单的red(ON)是怎么将灯点亮的,通过这个例子是可以看出标准库的基本工作原理的,这些东西看起来很简单,其实每一次回头看都是会有新的发现的。。。