标题写的高大上,其实内容简单清晰。今天就来看看,你写的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)是怎么将灯点亮的,通过这个例子是可以看出标准库的基本工作原理的,这些东西看起来很简单,其实每一次回头看都是会有新的发现的。。。

posted on 2018-08-05 16:27  lzd626  阅读(830)  评论(0编辑  收藏  举报