STM32-跑马灯

1  硬件连接

1.1 mini

1.2 战舰/精英

1.3 探索者

 2  F1库函数介绍

        头文件:stm32f10x_gpio.h
        源文件:stm32f10x_gpio.c

/*  命令功能设置GPIO配置为默认复位状态 ****/
void GPIO_DeInit(GPIO_TypeDef* GPIOx);

/* 初始化和配置功能 *********************************/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/* GPIO读写功能 **********************************************/
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/* GPIO备用功能配置功能 ****************************/
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);

2.1 1个初始化函数:

        void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

                作用:初始化一个或者多个IO口(同一组)的工作方式和速度。
                           该函数主要是操作GPIO_CRL(CRH)寄存器,在上拉或者下拉的时候有设置BSRR或者BRR寄存器
                GPIOx: GPIOA~GPIOG

typedef struct
{
  uint16_t GPIO_Pin;              //指定要初始化的IO口
  GPIOSpeed_TypeDef GPIO_Speed;   //设置IO口输出速度
  GPIOMode_TypeDef GPIO_Mode;     //设置工作模式:8种中的一个
}GPIO_InitTypeDef;

        注意:外设(包括GPIO)在使用之前,几乎都要先使能对应的时钟。

/**
  * @摘要   根据指定的GPIO_InitStruct参数初始化GPIOx外围设备。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @参数   GPIO_InitStruct: 指向GPIO_InitTypeDef结构的指针包含指定GPIO外设的配置信息。
  * @返回值 无
  */
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;
  /* 检查参数 */
  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模式配置 -----------------------*/
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
  { 
    /* 检查参数 */
    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
    /* 输出模式 */
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
/*---------------------------- 对CRL的配置 ------------------------*/
  /* 配置8个低端口引脚 */
  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;
      /* 取得端口脚的位置 */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* 清除相应的低控制寄存器位 */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* 将模式配置写入相应的位 */
        tmpreg |= (currentmode << pos);
        /* 重新设置对应的ODR位 */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          /* 设置对应的ODR位 */
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;
  }
/*---------------------------- GPIO CRH配置 ------------------------*/
  /* 配置8个高端口引脚 */
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
  {
    tmpreg = GPIOx->CRH;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = (((uint32_t)0x01) << (pinpos + 0x08));
      /* 取得端口脚的位置 */
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* 清除相应的高控制寄存器位 */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* 将模式配置写入相应的位 */
        tmpreg |= (currentmode << pos);
        /* 重新设置对应的ODR位 */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
        /* 设置对应的ODR位 */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
      }
    }
    GPIOx->CRH = tmpreg;
  }
}

        GPIO_Init函数初始化样例:

 GPIO_InitTypeDef  GPIO_InitStructure;
	
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);	 //根据设定参数初始化GPIOB.5

        可以一次初始化一个IO组下的多个IO,前提是这些IO口的配置方式一样。

2.2 2个读取输入电平函数:

        uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
                作用:读取某个GPIO的输入电平。
                           实际操作的是GPIOx_IDR寄存器。      
                例如: GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输入电平 入电平

/**
  * @摘要   读取指定的输入端口引脚。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @参数   GPIO_Pin:  指定要读的端口位。该参数可以是GPIO_Pin_x,其中x可以是(0..15)。
  * @返回值 输入端口引脚值。
  */
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  uint8_t bitstatus = 0x00;
  
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GET_GPIO_PIN(GPIO_Pin)); 
  
  if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
  {
    bitstatus = (uint8_t)Bit_SET;
  }
  else
  {
    bitstatus = (uint8_t)Bit_RESET;
  }
  return bitstatus;
}

        uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
                作用:读取某组GPIO的输入电平。
                           实际操作的是GPIOx_IDR寄存器。
                例如:GPIO_ReadInputData(GPIOA);//读取GPIOA组中所有io口输入电平

/**
  * @摘要   读取指定的GPIO输入数据端口。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @返回值 GPIO输入数据端口值。
  */
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  
  return ((uint16_t)GPIOx->IDR);
}

2.3 2个读取输出电平函数:

        uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
                作用:读取某个GPIO的输入电平。
                           实际操作的是GPIOx_IDR寄存器。
                例如:GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输入电平

/**
  * @摘要   读取指定的输出数据端口位。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @参数   GPIO_Pin:  指定要读的端口位。该参数可以是GPIO_Pin_x,其中x可以是(0..15)。
  * @返回值 输出端口引脚值。
  */
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  uint8_t bitstatus = 0x00;
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GET_GPIO_PIN(GPIO_Pin)); 
  
  if ((GPIOx->ODR & GPIO_Pin) != (uint32_t)Bit_RESET)
  {
    bitstatus = (uint8_t)Bit_SET;
  }
  else
  {
    bitstatus = (uint8_t)Bit_RESET;
  }
  return bitstatus;
}

        uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
               
 作用:读取某组GPIO的输入电平。
                           实际操作的是GPIOx_IDR寄存器。
                
例如:GPIO_ReadInputData(GPIOA);//读取GPIOA组中所有io口输入电平

/**
  * @摘要   读取指定的GPIO输出数据端口。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @返回值 GPIO输出数据端口值。
  */
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
    
  return ((uint16_t)GPIOx->ODR);
}

2.4 4个设置输出电平函数:

        void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
                
作用:设置某个IO口输出为高电平(1)。
                           实际操作BSRR寄存器
                
例如:GPIO_SetBits(GPIOB,GPIO_Pin_5);//设置PB.5 输出高

/**
  * @摘要   设置选定的数据端口位。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @参数   GPIO_Pin:  指定要读的端口位。该参数可以是GPIO_Pin_x,其中x可以是(0..15)。
  * @返回值 无
  */
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  
  GPIOx->BSRR = GPIO_Pin;
}

        void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
         
       作用:设置某个IO口输出为低电平(0)。
                           实际操作的BRR寄存器。
                
例如:GPIO_ResetBits(GPIOB,GPIO_Pin_5);//设置PB.5 输出高

/**
  * @摘要   清除选定的数据端口位。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @参数   GPIO_Pin:  指定要读的端口位。该参数可以是GPIO_Pin_x,其中x可以是(0..15)。
  * @返回值 无
  */
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  
  GPIOx->BRR = GPIO_Pin;
}

        void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
                
作用:设置某个IO口输出为低电平(0)或者高电平(1)。
                           实际操作的BRR或BSRR寄存器。
                例如:GPIO_WriteBit(GPIOB,GPIO_Pin_5,Bit_RESET);//设置PB.5 输出低

/**
  * @摘要   设置或清除选定的数据端口位。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @参数   GPIO_Pin: 指定要写入的端口位。该参数可以是GPIO_Pin_x中的一个,其中x可以是(0..15)。
  * @参数   BitVal: 指定要写入所选位的值。该参数可以是BitAction枚举值之一:
  *         @arg Bit_RESET: 清除端口引脚
  *         @arg Bit_SET: 设置端口引脚
  * @返回值 无
  */
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_BIT_ACTION(BitVal)); 
  
  if (BitVal != Bit_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BRR = GPIO_Pin;
  }
}

        void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
                
作用:设置某组IO口输出为低电平(0)或者高电平(1)。
                           实际操作的ODR寄存器。
                例如:GPIO_Write(GPIOB,Bit_RESET);//设置PB 输出低

/**
  * @摘要   将数据写入指定的GPIO数据端口。
  * @参数   GPIOx: 其中x可以是(A..G)来选择GPIO外围设备。
  * @参数   PortVal: 指定要写入端口输出数据寄存器的值。
  * @返回值 无
  */
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  
  GPIOx->ODR = PortVal;
}

2.5 手把手写跑马灯实验-库函数

        (1)使能IO口时钟。调用函数RCC_APB2PeriphColckCmd();
                 不同的IO组,调用的时钟使能函数不一样。
        (2)初始化IO口模式。调用函数GPIO_Init();
        (3)操作IO口,输出高低电平。

                     GPIO_SetBits();
                     GPIO_ResetBits();

/************mian.c************/
#include "sys.h"
#include "delay.h"
#include "led.h"

int main(void)
{ 
 
	delay_init();		  //初始化延时函数
	LED_Init();		        //初始化LED端口
	while(1)
	{
			GPIO_ResetBits(GPIOB,GPIO_Pin_5);  //LED0对应引脚GPIOB.5拉低,亮  等同LED0=0;
			GPIO_SetBits(GPIOE,GPIO_Pin_5);   //LED1对应引脚GPIOE.5拉高,灭 等同LED1=1;
			delay_ms(300);  		   //延时300ms
			GPIO_SetBits(GPIOB,GPIO_Pin_5);	   //LED0对应引脚GPIOB.5拉高,灭  等同LED0=1;
			GPIO_ResetBits(GPIOE,GPIO_Pin_5); //LED1对应引脚GPIOE.5拉低,亮 等同LED1=0;
			delay_ms(300);                     //延时300ms
	}
} 

/************led.h************/
#ifndef __LED_H
#define __LED_H	 
#include "sys.h"

#define LED0 PBout(5)// PB5
#define LED1 PEout(5)// PE5	

void LED_Init(void);//初始化
		 				    
#endif

/************led.c************/
#include "led.h"
   
//初始化PB5和PE5为输出口.并使能这两个口的时钟		    
//LED IO初始化
void LED_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);	 //使能PB,PE端口时钟
	
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;				 //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5
 GPIO_SetBits(GPIOB,GPIO_Pin_5);						 //PB.5 输出高

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	    		 //LED1-->PE.5 端口配置, 推挽输出
 GPIO_Init(GPIOE, &GPIO_InitStructure);	  				 //推挽输出 ,IO口速度为50MHz
 GPIO_SetBits(GPIOE,GPIO_Pin_5); 						 //PE.5 输出高 
}

2.6 手把手写跑马灯实验-寄存器

        (1)使能IO口时钟。配置寄存器RCC_APB2ENR。
        (2)初始化IO口模式。配置寄存器GPIOx_CRH/CRL
        (3)操作IO口,输出高低电平。配置寄存器GPIOX_ODR或者BSRR/BRR。

/************mian.c************/
#include "sys.h"
#include "delay.h"
#include "led.h"

int main(void)
{				  
	Stm32_Clock_Init(9);//系统时钟设置
	delay_init(72);	  	//延时初始化
	LED_Init();		  	//初始化与LED连接的硬件接口
	while(1)
	{
		LED0=0;
		LED1=1;
		delay_ms(300);
		LED0=1;
		LED1=0;
		delay_ms(300);
	}	 
}

/************led.h************/
#ifndef __LED_H
#define __LED_H	 
#include "sys.h"

//LED端口定义
#define LED0 PBout(5)	// DS0
#define LED1 PEout(5)	// DS1	

void LED_Init(void);	//初始化		 				    
#endif

/************led.c************/
#include "led.h"

//初始化PB5和PE5为输出口.并使能这两个口的时钟		    
//LED IO初始化
void LED_Init(void)
{
	RCC->APB2ENR|=1<<3;    //使能PORTB时钟	   	 
	RCC->APB2ENR|=1<<6;    //使能PORTE时钟	
	   	 
	GPIOB->CRL&=0XFF0FFFFF; 
	GPIOB->CRL|=0X00300000;//PB.5 推挽输出   	 
    GPIOB->ODR|=1<<5;      //PB.5 输出高
											  
	GPIOE->CRL&=0XFF0FFFFF;
	GPIOE->CRL|=0X00300000;//PE.5推挽输出
	GPIOE->ODR|=1<<5;      //PE.5输出高 
}

2.7 手把手写跑马灯实验-位操作

2.7.1 位操作原理

        把每个比特膨胀为一个32位的字,当访问这些字的时候就达到了访问比特的目的,比如说BSRR寄存器有32个位,那么可以映射到32个地址上,我们去访问(读-改-写)这32个地址就达到访问32个比特的目的。我们可以更方便更快捷更安全的设置或读取地址中的内容。

         地址空间的有一个512MB范围由片上外设(的寄存器)使用。这个区通过系统总线来访问。在这个区的下部,有一个1MB的区间,被称为“位带区”。该位带区还有一个对应的、 32MB 的“位带别名(alias)区”,容纳了 8M 个“位变量”(对比 8051 的只有 128 个位变量)。要注意的是,外设区内不允许执行指令SRAM区同样有位带区。

        位带区: 支持位带操作的地址区
        位带别名: 对别名地址的访问最终会变换成对位带区的访问(注意:这中途有一个地址映射过程)
        CM3的预定义的存储器映射如下:

         对应于 STM32F103ZET6内存映射如下:

         可知外设区位带区地址 0x4000 0000 - 0x400F FFFF 基本涵盖了所有的外设寄存器,从而使得读改外设的效率提高。

                SRAM 位带区的最低 1MB 范围:  0x20000000 ‐ 0x200FFFFF(SRAM区中的最低 1MB
                片内外设位带区的最低 1MB范围:0x40000000 ‐ 0x400FFFFF(片上外设区中最低 1MB

        下图为位带区和位带别名区的对应关系:

        可以发现,位带区首地址:0x200F FFFF - 0x2000 0000 = 0xF FFFF (1Mb个地址) ,而位带别名区: 0x23FF FFFC - 0x2200 0000 = 0x1FF FFFF (32Mb个地址),的确是 1:32。又有 0x2200 0000 - 0x2000 0000 = 0x200 0000 (32Mb),0x2400 0000 - 0x2200 0000 = 0x200 0000 (32Mb),即位带别名区和位带区并非相邻的,而是相隔 31Mb 的内存空间(用作其他用途)。且位带区相互之间对应的位带别名区也不是连续的。

 2.7.2 如何位带操作

        在位带区中,每个比特都映射到别名地址区的一个字——这是个只有 LSB 才有效的字。当一个别名地址被访问时,会先把该地址变换成位带地址。对于操作,读取位带地址中的一个字,再把需要的位右移到 LSB,并把 LSB 返回。对于操作,把需要写的位左移至对应的位序号处,然后执行一个原子的 “读-改-写” 过程。
        支持位带操作的两个内存区的范围是:
                0x2000_0000-0x200F_FFFF(SRAM 区中的最低 1MB)
                0x4000_0000-0x400F_FFFF(片上外设区中的最低 1MB)
        对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
                AliasAddr = 0x42000000 + ((A-0x40000000)*8+n)*4 =0x42000000 + (A-0x40000000)*32 + n*4
        上式中,“*4” 表示一个字为 4 个字节,“*8” 表示一个字节中有 8 个比特。
        在STM32中,上式可理解为:addr(GPIOx.y) = (addr(GPIO_BASE) + 0x200 0000) + (GPIOx-GPIO_BASE)*32 + y*4
                                                                                                 位带别名区的起始地址           外设的偏移地址 1:32  别名区以4字节为步进 
                                                                                    (                                   外设的别名区起始地址                             )   
        上式中,x = (A, B, C, …),y 为 bit 位。GPIO_BASE 为 IO 口的起始地址。

        举例:欲设置地址 0x2000_0000 中的比特 2,则使用位带操作的设置过程如下图所示:

         对应的汇编代码如下图:

在这里插入图片描述

         位带读操作相对简单些:

         对应的汇编代码如下图:

在这里插入图片描述

        例子
                1)在地址 0x40000000 处写入 0x3355AACC。
                2)读取地址 0x42000008。本次读访问将读取 0x20000000,并提取比特 2,值为 1。
                3)往地址 0x42000008 处写 0。本次操作将被映射成对地址 0x40000000 的 “读-改-写” 操作(原子的说法),把比特 2 清 0。
                4)现在再读取 0x40000000,将返回 0x3355AAC8(bit[2]已清零)。
        位带别名区的字只有 LSB 有意义。另外,在访问位带别名区时,不管使用哪一种长度的数据传送指令(字/半字/字节),都把地址对齐到字的边界上,否则会产生不可预料的结果。

 2.7.3 在C编译器中使用位带操作

        在 C 编译器中并没有直接支持位带操作。比如, C 编译器并不知道同一块内存能够使用不同的地址来访问,也不知道对位带别名区的访问只对 LSB 有效。欲在 C 中使用位带操作,最简单的做法就是 #define 一个位带别名区的地址。例如: 

#define DEVICE_REG0 ((volatile unsigned long *) (0x40000000))
#define DEVICE_REG0_BIT0 ((volatile unsigned long *) (0x42000000))
#define DEVICE_REG0_BIT1 ((volatile unsigned long *) (0x42000004))
...
*DEVICE_REG0 = 0xAB; //使用正常地址访问寄存器
...
*DEVICE_REG0 = *DEVICE_REG0 | 0x2; //使用传统方法设置 bit1
...
*DEVICE_REG0_BIT1 = 0x1; // 通过位带别名地址设置 bit1

      为简化位带操作,也可以定义一些。比如,我们可以建立一个把 “位带地址+位序号” 转换成别名地址的宏,再建立一个把别名地址转换成指针类型的宏:

// 把 “位带地址+位序号” 转换成 别名地址 的宏    
//                                 外设的基地址                  外设地址区域<=1Mb
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr & 0xFFFFF)<<5) +(bitnum<<2))
// 把该地址转换成一个指针
#define MEM_ADDR(addr) *((volatile unsigned long *) (adr))
// 在此基础上,我们就可以如下改写代码:
MEM_ADDR(DEVICE_REG0) = 0xAB; //使用正常地址访问寄存器
MEM_ADDR(DEVICE_REG0)= MEM_ADDR(DEVICE_REG0) | 0x2; //传统做法
MEM_ADDR(BITBAND(DEVICE_REG0,1)) = 0x1; //使用位带别名地址

        注意:当使用位带功能时,要访问的变量必须用 volatile 来定义。因为 C 编译器并不知道同一个比特可以有两个地址。所以就要通过 volatile,使得编译器每次都如实地把新数值写入存储器(内存),而不再会出于优化的考虑,在中途使用寄存器来操作数据的复本,直到最后才把复本写回——这会导致按不同的方式访问同一个位会得到不一致的结果。

        以GPIO的位带操作为例,sys.h里面对GPIO输入输出部分功能实现了位带操作:

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    
#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)     BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 
…
#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

         GPIOx_ODR 寄存器如下:

         每个寄存器32位,占4个地址(4 x 8),在访问或修改某个寄存器时,是从首地址开始的,逻辑运算则是直接可涵盖到32bit,offset 为0x0C。GPIOA 的地址在 stm32 的 datasheet 中给出为 0x40010800

在这里插入图片描述

2.8 手把手写跑马灯实验-位带操作

         (1)使能IO口时钟。调用函数RCC_APB2PeriphColckCmd();
         (2)初始化IO口模式。调用函数GPIO_Init();
         (3)操作IO口,输出高低电平。
使用位带操作

/************mian.c************/
#include "sys.h"
#include "delay.h"
#include "led.h"

 int main(void)
 {	
	delay_init();	    //延时函数初始化	  
	LED_Init();		  	//初始化与LED连接的硬件接口
	while(1)
	{
		LED0=0;
		LED1=1;
		delay_ms(300);	 //延时300ms
		LED0=1;
		LED1=0;
		delay_ms(300);	//延时300ms
	}
 }

/************led.h************/
#ifndef __LED_H
#define __LED_H	 
#include "sys.h"

#define LED0 PBout(5)// PB5
#define LED1 PEout(5)// PE5	

void LED_Init(void);//初始化
		 				    
#endif

/************led.c************/
#include "led.h"
   
//初始化PB5和PE5为输出口.并使能这两个口的时钟		    
//LED IO初始化
void LED_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);	 //使能PB,PE端口时钟
	
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;				 //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5
 GPIO_SetBits(GPIOB,GPIO_Pin_5);						 //PB.5 输出高

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	    		 //LED1-->PE.5 端口配置, 推挽输出
 GPIO_Init(GPIOE, &GPIO_InitStructure);	  				 //推挽输出 ,IO口速度为50MHz
 GPIO_SetBits(GPIOE,GPIO_Pin_5); 						 //PE.5 输出高 
}

 3  F4库函数介绍

        头文件:stm32f4xx_gpio.h
        源文件:stm32f4xx_gpio.c

/*  命令功能设置GPIO配置为默认复位状态 ****/
void GPIO_DeInit(GPIO_TypeDef* GPIOx);

/* 初始化和配置功能 *********************************/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/* GPIO读写功能 **********************************************/
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/* GPIO备用功能配置功能 ****************************/
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);

3.1 1个初始化函数:

        void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

        作用:初始化一个或者多个IO口(同一组)的工作模式,输出类型,速度以及上下拉方式。也就是一组IO口的4个配置寄存器。
                (GPIOx->MODER, GPIOx->OSPEEDR,GPIOx->OTYPER,GPIOx->PUPDR)
        GPIOx: GPIOA~GPIOK(最多11组,也就是16X11=176个IO)

typedef struct
{
  uint32_t GPIO_Pin;             //指定要初始化的端口
  GPIOMode_TypeDef GPIO_Mode;    //指定要初始化端口模式
  GPIOSpeed_TypeDef GPIO_Speed;  //指定要初始化端口速度
  GPIOOType_TypeDef GPIO_OType;  //指定要初始化端口输出类型
  GPIOPuPd_TypeDef GPIO_PuPd;    //指定要初始化端口上拉或者下拉
}GPIO_InitTypeDef;

        注意:外设(包括GPIO)在使用之前,几乎都要先使能对应的时钟。

/**
  * @摘要   根据GPIO_InitStruct中的指定参数初始化GPIOx外围设备。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @参数   GPIO_InitStruct: 指向GPIO_InitTypeDef结构的指针,该结构包含指定GPIO外设的配置信息。
  * @返回值 无
  */
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
  uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;

  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PUPD(GPIO_InitStruct->GPIO_PuPd));

  /* ------------------------- 配置的端口引脚 ---------------- */
  /*-- GPIO模式配置 --*/
  for (pinpos = 0x00; pinpos < 0x10; pinpos++)
  {
    pos = ((uint32_t)0x01) << pinpos;
    /* Get the port pins position */
    currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

    if (currentpin == pos)
    {
      GPIOx->MODER  &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
      GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));

      if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
      {
        /* 检查速度模式参数 */
        assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));

        /* 速度模式配置 */
        GPIOx->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0 << (pinpos * 2));
        GPIOx->OSPEEDR |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (pinpos * 2));

        /* 检查输出模式参数 */
        assert_param(IS_GPIO_OTYPE(GPIO_InitStruct->GPIO_OType));

        /* 输出模式配置 */
        GPIOx->OTYPER  &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)pinpos)) ;
        GPIOx->OTYPER |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << ((uint16_t)pinpos));
      }

      /* 上拉下拉电阻配置 */
      GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
      GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
    }
  }
}

        GPIO_Init函数初始化样例:

  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟

  //GPIOF9,F10初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//LED0和LED1对应IO口
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIOF9,F10

        可以一次初始化一个IO组下的多个IO,前提是这些IO口的配置方式一样。

3.2 2个读取输入电平函数:

        uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
        作用:读取某个GPIO的输入电平。
                   实际操作的是GPIOx_IDR寄存器。
        例如:GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输入电平

/**
  * @摘要   读取指定的输入端口引脚。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @参数   GPIO_Pin: 指定要读的端口位。该参数可以是GPIO_Pin_x,其中x可以是(0..15)。
  * @返回值 输入端口引脚值。
  */
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  uint8_t bitstatus = 0x00;

  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GET_GPIO_PIN(GPIO_Pin));

  if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
  {
    bitstatus = (uint8_t)Bit_SET;
  }
  else
  {
    bitstatus = (uint8_t)Bit_RESET;
  }
  return bitstatus;
}

        uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
        作用:读取某组GPIO的输入电平。
                   实际操作的是GPIOx_IDR寄存器。
        例如:GPIO_ReadInputData(GPIOA);//读取GPIOA组中所有io口输入电平

/**
  * @摘要   读取指定的GPIO输入数据端口。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @返回值 GPIO输入数据端口值。
  */
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

  return ((uint16_t)GPIOx->IDR);

3.3  2个读取输出电平函数

        uint8_t GPIO_ReadOutputDataBit (GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
        作用:读取某个GPIO的输出电平。
                   实际操作的是GPIO_ODR寄存器。
        例如:GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输出电平

/**
  * @摘要   读取指定的输出数据端口位。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @参数   GPIO_Pin: 指定要读的端口位。该参数可以是GPIO_Pin_x,其中x可以是(0..15)。
  * @返回值 输出端口引脚值。
  */
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  uint8_t bitstatus = 0x00;

  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GET_GPIO_PIN(GPIO_Pin));

  if (((GPIOx->ODR) & GPIO_Pin) != (uint32_t)Bit_RESET)
  {
    bitstatus = (uint8_t)Bit_SET;
  }
  else
  {
    bitstatus = (uint8_t)Bit_RESET;
  }
  return bitstatus;
}

        uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
        作用:读取某组GPIO的输出电平。
                   实际操作的是GPIO_ODR寄存器。
        例如:GPIO_ReadOutputData(GPIOA);//读取GPIOA组中所有io口输出电平

/**
  * @摘要   读取指定的GPIO输出数据端口。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @返回值 GPIO输出数据端口值。
  */
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

  return ((uint16_t)GPIOx->ODR);
}

3.4 4个设置输出电平函数:

        void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
        作用:设置某个IO口输出为高电平(1)。
                   实际操作BSRRL寄存器
        例如:GPIO_SetBits(GPIOB,GPIO_Pin_5);//设置PB.5输出高

/**
  * @摘要   设置选定的数据端口位。
  * @注意   这个函数使用GPIOx_BSRR寄存器来允许原子读/修改访问。这样,就不会有读取和修改访问之间发生IRQ的风险。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @参数   GPIO_Pin:指定要写入的端口位。这个参数可以是GPIO_Pin_x的任意组合,其中x可以是(0..15)。
  * @返回值 无
  */
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  GPIOx->BSRRL = GPIO_Pin;
}

        void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
        作用:设置某个IO口输出为低电平(0)。
                   实际操作的BSRRH寄存器。
        例如:GPIO_ResetBits(GPIOB,GPIO_Pin_5);//设置PB.5输出低

/**
  * @摘要   清除选定的数据端口位。
  * @注意   这个函数使用GPIOx_BSRR寄存器来允许原子读/修改访问。这样,就不会有读取和修改访问之间发生IRQ的风险。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @参数   GPIO_Pin: 指定要写入的端口位。这个参数可以是GPIO_Pin_x的任意组合,其中x可以是(0..15)。
  * @返回值 无
  */
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  GPIOx->BSRRH = GPIO_Pin;
}

        void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
        作用:设置某个IO口输出为低电平(0)或者高电平(1)。
                   实际操作的BSRRH或BSRRL寄存器。
        例如:GPIO_WriteBit(GPIOB,GPIO_Pin_5,Bit_RESET);//设置PB.5输出低

/**
  * @摘要   设置或清除选定的数据端口位。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @参数   GPIO_Pin: 指定要写入的端口位。这个参数可以是GPIO_Pin_x的任意组合,其中x可以是(0..15)。
  * @参数   BitVal: 指定要写入所选位的值。该参数可以是BitAction枚举值之一:
  *            @arg Bit_RESET: 清除端口引脚
  *            @arg Bit_SET: 设置端口引脚
  * @返回值 无
  */
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_BIT_ACTION(BitVal));

  if (BitVal != Bit_RESET)
  {
    GPIOx->BSRRL = GPIO_Pin;
  }
  else
  {
    GPIOx->BSRRH = GPIO_Pin ;
  }
}

        void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
        作用:设置某组IO口输出为低电平(0)或者高电平(1)。
                   实际操作的ODR寄存器。
        例如:GPIO_Write(GPIOB,Bit_RESET);//设置PB输出低

/**
  * @摘要   将数据写入指定的GPIO数据端口。
  * @参数   GPIOx: 其中x可以(A..K)为STM32F405xx/407xx和STM32F415xx/417xx设备选择GPIO外设
                       x可以(A..I)为STM32F42xxx/43xxx设备选择GPIO外设。
                       x可以为(A, B, C, D和H)选择STM32F401xx设备的GPIO外设。
  * @参数   GPIO_Pin: 指定要写入的端口位。这个参数可以是GPIO_Pin_x的任意组合,其中x可以是(0..15)。
  * @参数   BitVal: 指定要写入所选位的值。该参数可以是BitAction枚举值之一:
  *            @arg Bit_RESET: 清除端口引脚
  *            @arg Bit_SET: 设置端口引脚
  * @返回值 无
  */
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)
{
  /* 检查参数 */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

  GPIOx->ODR = PortVal;
}

3.5  手把手写跑马灯实验-库函数

        (1)使能IO口时钟。调用函数RCC_AHB1PeriphClockCmd();
                 不同的外设调用的时钟使能函数可能不一样。
        (2)初始化IO口模式。调用函数GPIO_Init();
        (3)操作IO口,输出高低电平。

                     GPIO_SetBits();
                     GPIO_ResetBits();

/************mian.c************/
#include "sys.h"
#include "delay.h"
#include "led.h"

int main(void)
{ 
 
	delay_init(168);		  //初始化延时函数
	LED_Init();		        //初始化LED端口
	
	while(1)
	{
	GPIO_ResetBits(GPIOF,GPIO_Pin_9);  //LED0对应引脚GPIOF.9拉低,亮  等同LED0=0;
	GPIO_SetBits(GPIOF,GPIO_Pin_10);   //LED1对应引脚GPIOF.10拉高,灭 等同LED1=1;
	delay_ms(500);  		   //延时300ms
	GPIO_SetBits(GPIOF,GPIO_Pin_9);	   //LED0对应引脚GPIOF.0拉高,灭  等同LED0=1;
	GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉低,亮 等同LED1=0;
	delay_ms(500);                     //延时300ms
	}
}

/************led.h************/
#ifndef __LED_H
#define __LED_H
#include "sys.h"	

//LED端口定义
#define LED0 PFout(9)	// DS0
#define LED1 PFout(10)	// DS1	 

void LED_Init(void);//初始化		 				    
#endif

/************led.c************/
#include "led.h" 	 

//初始化PF9和PF10为输出口.并使能这两个口的时钟		    
//LED IO初始化
void LED_Init(void)
{    	 
  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟

  //GPIOF9,F10初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//LED0和LED1对应IO口
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO
	
  GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10设置高,灯灭

}

3.6  手把手写跑马灯实验-寄存器

        (1)使能IO口时钟。配置相关寄存器寄存器RCC->AHB1ENR 
        (2)初始化IO口模式。配置四个配置寄存器

                 GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR、GPIOx_PUPDR
        (3)操作IO口,输出高低电平。配置寄存器GPIOX_ODR或者BSRRL/BSRRH。

/************mian.c************/
#include "sys.h"
#include "delay.h"
#include "led.h"

int main(void)
{ 
 
	delay_init(168);		  //初始化延时函数
	LED_Init();		          //初始化LED端口
	while(1)
	{
       GPIOF->BSRRH=GPIO_Pin_9;  //LED0亮
	   GPIOF->BSRRL=GPIO_Pin_10; //LED1灭
		 delay_ms(500);
       GPIOF->BSRRL=GPIO_Pin_9;  //LED0灭
	   GPIOF->BSRRH=GPIO_Pin_10; //LED1亮
		 delay_ms(500);

	 }
 }

/************led.h************/
#ifndef __LED_H
#define __LED_H	 
#include "sys.h" 
	
//LED端口定义
#define LED0 PFout(9)	// DS0
#define LED1 PFout(10)	// DS1	 

void LED_Init(void);//初始化		 				    
#endif

/************led.c************/
#include "led.h" 	 

//初始化PF9和PF10为输出口.并使能这两个口的时钟		    
//LED IO初始化
void LED_Init(void)
{    	 
	RCC->AHB1ENR|=1<<5;//使能PORTF时钟 
	GPIO_Set(GPIOF,PIN9|PIN10,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU); 
    //PF9,PF10设置
	LED0=1;//LED0关闭
	LED1=1;//LED1关闭
}

3.7  手把手写跑马灯实验-位操作

         (1)使能IO口时钟。调用函数RCC_AHB1PeriphColckCmd();
         (2)初始化IO口模式。调用函数GPIO_Init();
         (3)操作IO口,输出高低电平。
使用位带操作

/************mian.c************/
#include "sys.h"
#include "delay.h"
#include "led.h"

int main(void)
{ 
 
	delay_init(168);		  //初始化延时函数
	LED_Init();		        //初始化LED端口
    while(1)
	{
         LED0=0;			    //LED0亮
	     LED1=1;				//LED1灭
		 delay_ms(500);
		 LED0=1;				//LED0灭
		 LED1=0;				//LED1亮
		 delay_ms(500);
	 }
}

/************led.h************/
#ifndef __LED_H
#define __LED_H
#include "sys.h"	

//LED端口定义
#define LED0 PFout(9)	// DS0
#define LED1 PFout(10)	// DS1	 

void LED_Init(void);//初始化		 				    
#endif

/************led.c************/
#include "led.h" 	 

//初始化PF9和PF10为输出口.并使能这两个口的时钟		    
//LED IO初始化
void LED_Init(void)
{    	 
  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟

  //GPIOF9,F10初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//LED0和LED1对应IO口
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO
	
  GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10设置高,灯灭

}

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值