六、固件库方式LED灯控制

固件库方式LED灯控制
在工程APP目录下新建一个 led 文件夹用于存放 led 驱动程序。

在这里插入图片描述
对 STM32的GPIO外设操作,需在工程中添加stm32f10x_gpio.c 和 stm32f10x_rcc.c文件。对GPIO操作的函数都在 stm32f10x_gpio.c 中,stm32f10x_gpio.h是函数的申明及一些选项配置的宏定义。

在工程模板上新建一个 led.c 和 led.h 文件,将其存放在 led 文件夹内。
通常 xxx.c 文件用于存放编写的驱动程序,xxx.h 文件用于存放 xxx.c 内的 stm32头文件、管脚定义、全局变量声明、函数声明等内容。

如果要对同一端口的多个引脚输出高电平,可以使用"|“运算符,相应的在对结构体初始化配置时管脚设置那里也要使用”|"将管脚添加进去,前提条件是:要操作的多个引脚必须是配置同一种工作模式。

1、点亮一个led灯

[led.h]

#ifndef _led_H //防止头文件被重复包含,避免引起编译器错误
#define _led_H

#include "system.h" //已包含#include "stm32f10x.h"

#define LED_PORT_RCC RCC_APB2Periph_GPIOC 
#define LED_PIN  (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_PORT GPIOC
void LED_Init(void);

#endif

[led.c]

#include "led.h"

void LED_Init()
{
  GPIO_InitTypeDef GPIO_InitStructure;//定义GPIO引脚结构体变量
  //1、开启外设时钟
  RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);
  //2、GPIO引脚结构体变量赋值
  GPIO_InitStructure.GPIO_Pin=LED_PIN; //选择要设置的IO口(引脚)
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //设置推挽输出模式
  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
  //3、GPIO端口和相应引脚初始化
  GPIO_Init(LED_PORT, &GPIO_InitStructure);//初始化GPIO(端口)
  //4、GPIO引脚设置数据输出
  GPIO_SetBits(LED_PORT,LED_PIN);//输出高电平,灯熄灭
}

[main.c]

#include "stm32f10x.h"
#include "led.h"

//点亮第一个led灯
int main()
{
  LED_Init();
  while(1)
  {
    GPIO_ResetBits(LED_PORT,GPIO_Pin_0);//输出低电平,灯点亮
  }
}

2、设置led灯闪烁

[led.h]

#ifndef _led_H //防止头文件被重复包含,避免引起编译器错误
#define _led_H

#include "system.h" //已包含#include "stm32f10x.h"

#define LED_PORT_RCC RCC_APB2Periph_GPIOC 
#define LED_PIN  (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_PORT GPIOC
void LED_Init(void);

#endif

[led.c]

#include "led.h"

void LED_Init()
{
  GPIO_InitTypeDef GPIO_InitStructure;//定义GPIO引脚结构体变量
  //1、开启外设时钟
  RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);
  //2、GPIO引脚结构体变量赋值
  GPIO_InitStructure.GPIO_Pin=LED_PIN; //选择要设置的IO口(引脚)
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //设置推挽输出模式
  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
  //3、GPIO端口和相应引脚初始化
  GPIO_Init(LED_PORT, &GPIO_InitStructure);//初始化GPIO(端口)
  //4、GPIO引脚设置数据输出
  GPIO_SetBits(LED_PORT,LED_PIN);//输出高电平,灯熄灭
}

[main.c]

//led0闪烁
void delay(unsigned int i)//延时函数
{
  while(i--);
}

int main()
{
  LED_Init();//LED初始化
  while(1)
  {
    GPIO_ResetBits(LED_PORT,GPIO_Pin_0);//点亮D1灯
    delay(0xfffff);//延时
    GPIO_SetBits(LED_PORT,GPIO_Pin_0);//熄灭D1灯
    delay(0xfffff);//延时
  }
}

3、位带操作实现led灯控

3.1 位带操作

(1)位带概念
51单片机通过关键字sbit使用位操作,对单片机IO口进行位定义。
STM32中通过访问位带别名区来实现位操作,即将每个比特位膨胀成一个32位字,当访问这些字的时候就达到了访问比特的目的。

STM32F1 中有两个区域支持位带操作,一个是 SRAM 区的最低 1MB 范围(0x20000000~0x20100000),一个是片内外设区的最低 1MB 范围(APB1、APB2、AHB 外设(0x40000000~0x40100000))。
0x4000 0000-0x4000 77FF:APB1总线外设
0x4000 7800-0x4000 FFFF:预留
0x4001 0000-0x4001 3FFF:APB2总线外设
0x4001 4000-0x4001 7FFF:预留
0x4001 8000-0x4002 33FF:AHB总线外设

在这里插入图片描述
SRAM的最低1MB区域,地址范围是 0X2000 0000-0X200FFFFF。
片内外设最低1MB区域,地址范围是 0X4000 0000-0X400F FFFF,在这个地址范围内包括了APB1、APB2、AHB 总线上所有的外设寄存器。

在 SRAM 区中还有 32MB 空间,其地址范围是 0X2200 0000-0X23FF FFFF,它是 SRAM 的 1MB 位带区膨胀后的位带别名区,通过访问位带别名区就可以实现访问位带中每一位的目的。
片内外设区的 32MB 地址范围是0X4200 0000-0X43FF FFFF。

(2)使用指针来访问位带别名区的地址
对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n,n值的范围是 0-7,则该比特在别名区的地址为:
AliasAddr=0x42000000+ (A-0x40000000) * 8 * 4 + n * 4

(A-0x40000000) * 8 * 4中(A-0x40000000) * 8表示在某个位前面有的位总数,所有位总数再乘以4表示总的位膨胀后的字节数,一个位膨胀为4个字节。
n*4表示当前位膨胀为4个字节,AliasAddr单位为字节。

对于 SRAM 位带区的某个比特,记它所在字节的地址为 A,位序号为 n,n 值的范围是 0-7,则该比特在别名区的地址为:
AliasAddr= =0x22000000 + (A-0x20000000) * 8 * 4 +n * 4

综上,通过一个宏来定义外设位带别名区地址和 SRAM 位带别名区地址,并把位带地址和位序号作为这个宏定义的参数。
公式:

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))

addr & 0xF0000000 是区分操作的是 SRAM 还是外设,即获取最高位的值是 4 还是 2。
如果操作的是外设,那么addr & 0xF0000000 结果是 0x40000000,后面+0x2000000 就等于 0X42000000(0X42000000 是外设别名区起始地址)。
如果操作的是SRAM,那么addr & 0xF0000000结果是0x20000000,后面+0x2000000 就等于 0X22000000,(0X22000000是SRAM别名区的起始地址)。

addr & 0x000FFFFF 屏蔽了高三位,相当于减去0X20000000或0X40000000,屏蔽高三位是因为 SRAM 和外设的位带区最高地址是 0X200F FFFF和 0X400F FFFF,SRAM 或者外设位带区上任意地址减去其对应的起始地址,总是低5位有 效 ,所以这里屏蔽高3位就相当于减去0X20000000或者0X40000000。<<5 相当于 * 8 * 4,<<2 相当于 * 4。

(3)寄存器位操作
知道外设单元寄存器地址,即可通过指针形式操作这些位带别名区地址,实现位带区对应位的操作。
位带别名区内地址:

//把 addr 地址强制转换为 unsigned long 类型的指针
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
//把位带别名区内地址转换为指针 ,获取地址内的数据
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))

寄存器地址:

#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808

位带地址://n值小于16

#define PAout(n)  BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n)   BIT_ADDR(GPIOA_IDR_Addr,n) //输入

定义管脚:

#define led1 PCout(0) //D1 指示灯连接的是 PC0 管脚
#define led2 PCout(1) //D2 指示灯连接的是 PC1 管脚

使用:

led1=0;//输出低电平,灯点亮  输出高电平,灯熄灭
led2=1;

3.2 位带点亮第一个led灯

在工程目录下新建一个 Public 文件夹,用于存放公共应用程序文件,新建 system.c 和 system.h 文件用于位带设置。

[system.h]

#ifndef _system_H
#define _system_H

#include "stm32f10x.h"

#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 GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#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 GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#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 PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_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)  //输入

#endif

[system.c]

#include "system.h"

[led.h]

#ifndef _led_H //防止头文件被重复包含,避免引起编译器错误
#define _led_H

#include "system.h" //已包含#include "stm32f10x.h"

#define LED_PORT_RCC RCC_APB2Periph_GPIOC 
#define LED_PIN  (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_PORT GPIOC
void LED_Init(void);

#define led1 PCout(0)
#define led2 PCout(1)
#define led3 PCout(2)

#endif

[led.c]

#include "led.h"

void LED_Init()
{
  GPIO_InitTypeDef GPIO_InitStructure;//定义GPIO引脚结构体变量
  //1、开启外设时钟
  RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);
  //2、GPIO引脚结构体变量赋值
  GPIO_InitStructure.GPIO_Pin=LED_PIN; //选择你要设置的IO口(引脚)
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //设置推挽输出模式
  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
  //3、GPIO端口和相应引脚初始化
  GPIO_Init(LED_PORT, &GPIO_InitStructure);//初始化GPIO(端口)
  //4、GPIO引脚设置数据输出
  GPIO_SetBits(LED_PORT,LED_PIN);//输出高电平,灯熄灭
}

[main.c]

#include "system.h"
#include "led.h"

void delay(u32 i)//延时函数
{
  while(i--);
}

int main()
{
  LED_Init();
  while(1)
  {
    //输出低电平,灯点亮;输出高电平,灯熄灭
    led1=!led1;
    delay(6000000);
  }
}

注:
main.c 文件中可以直接调用 system.h,既可包含 stm32f10x.h 内容也能包含位操作功能。
volatile关键字,volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。

编辑 2023.03.26 22:18 首次编辑

注:本文旨于自己的学习笔记,禁止转载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值