固件库方式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 首次编辑
注:本文旨于自己的学习笔记,禁止转载。