一、前言
平常使用STM32F1和F4,程序不少参考的正点原子的教程,代码里都包含头文件他们的sys.h,这里面主要是实现了stm32的位带操作,位带是啥,博主也没深入研究,但是就是能直接读写GPIO口的某一位,例如:
PAout(1) = 1;//GPIOA Pin1 输出高电平
if(PAin(2)==1);//判断GPIOA Pin2 是否为高电平
用起来清晰直观,但是最近使用STM32F0,才发现F0没有位带,自然正点原子sys.h里位带操作就没法移植到F0了,那是不是没办法了呢,百度一下发现可以利用位域来实现仿位带操作,下面就来写一个STMF0的sys.h,废话不多说,直接看下面代码。
二、代码实现
代码分三步,可以新建名为sys.h的头文件,放在里面:
(1)因为GPIOX->ODR和GPIOX->IDR寄存器都是低16位有效,所以先定义一个结构体位域,大小为2字节(16位),每一位表示一个IO口。而且要用volatile 防止被编译器优化,如果不加volatile ,像PAout(0)=1;PAout(1)=1;原本先后置1,可能会被优化成GPIOA->ODR=0x03,一起置1了,这可是致命错误,在模拟I2C、SPI时序的时候会出错。
//定义GPIO结构体位域
typedef struct
{
volatile unsigned short bit0 : 1;
volatile unsigned short bit1 : 1;
volatile unsigned short bit2 : 1;
volatile unsigned short bit3 : 1;
volatile unsigned short bit4 : 1;
volatile unsigned short bit5 : 1;
volatile unsigned short bit6 : 1;
volatile unsigned short bit7 : 1;
volatile unsigned short bit8 : 1;
volatile unsigned short bit9 : 1;
volatile unsigned short bit10 : 1;
volatile unsigned short bit11 : 1;
volatile unsigned short bit12 : 1;
volatile unsigned short bit13 : 1;
volatile unsigned short bit14 : 1;
volatile unsigned short bit15 : 1;
} GPIO_Bit_TypeDef;
(2)将 寄存器地址&(GPIOX->ODR)和& (GPIOX->IDR)强制转换为GPIO_Bit_TypeDef* 指针,并利用宏定义替换为标识符PORTX_OUT或PORTX_IN
#define PORTA_OUT ((GPIO_Bit_TypeDef *)(&(GPIOA->ODR)))
#define PORTA_IN ((GPIO_Bit_TypeDef *)(&(GPIOA->IDR)))
#define PORTB_OUT ((GPIO_Bit_TypeDef *)(&(GPIOB->ODR)))
#define PORTB_IN ((GPIO_Bit_TypeDef *)(&(GPIOB->IDR)))
#define PORTC_OUT ((GPIO_Bit_TypeDef *)(&(GPIOC->ODR)))
#define PORTC_IN ((GPIO_Bit_TypeDef *)(&(GPIOC->IDR)))
#define PORTD_OUT ((GPIO_Bit_TypeDef *)(&(GPIOD->ODR)))
#define PORTD_IN ((GPIO_Bit_TypeDef *)(&(GPIOD->IDR)))
#define PORTE_OUT ((GPIO_Bit_TypeDef *)(&(GPIOE->ODR)))
#define PORTE_IN ((GPIO_Bit_TypeDef *)(&(GPIOE->IDR)))
#define PORTF_OUT ((GPIO_Bit_TypeDef *)(&(GPIOF->ODR)))
#define PORTF_IN ((GPIO_Bit_TypeDef *)(&(GPIOF->IDR)))
#define PORTG_OUT ((GPIO_Bit_TypeDef *)(&(GPIOG->ODR)))
#define PORTG_IN ((GPIO_Bit_TypeDef *)(&(GPIOG->IDR)))
(3)利用宏函数和##字符串拼接,实现IO口操作,函数名称和正点原子sys.h里的一致,方便其他程序移植。这里提一下“##”拼接,这是一种宏的特殊符号,##表示把左右字符串拼接在一起
例如:#define GPIO(io) GPIO##io 则 GPIO(A) 会被替换成 GPIOA
在下面这里如果输入n为4,就会变成 bit4,n为8,就会变成bit8
#define PAout(n) (PORTA_OUT->bit##n)
#define PAin(n) (PORTA_IN->bit##n)
#define PBout(n) (PORTB_OUT->bit##n)
#define PBin(n) (PORTB_IN->bit##n)
#define PCout(n) (PORTC_OUT->bit##n)
#define PCin(n) (PORTC_IN->bit##n)
#define PDout(n) (PORTD_OUT->bit##n)
#define PDin(n) (PORTD_IN->bit##n)
#define PEout(n) (PORTE_OUT->bit##n)
#define PEin(n) (PORTE_IN->bit##n)
#define PFout(n) (PORTF_OUT->bit##n)
#define PFin(n) (PORTF_IN->bit##n)
#define PGout(n) (PORTG_OUT->bit##n)
#define PGin(n) (PORTG_IN->bit##n)
三、例程
实际使用起来就很简单了,和在F1、F4上的一样,以点灯为例,初始化GPIO后,直接赋值就行了
#define LED PAout(4)
void LED_Init(void);
//点灯测试
int main(void)
{
LED_Init();
delay_init();
while (1)
{
LED=1;
delay_ms(500);
LED=0;
delay_ms(500);
}
}
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //使能PA端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_NOPULL;//无上下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);
//
GPIO_SetBits(GPIOA,GPIO_Pin_4); //PA.4 输出高
}
感谢该博主提供的思路http://www.cnblogs.com/endlesscoding/p/7429743.html
四、例程下载
打包上面的例程,sys.h在Driver文件夹里