一、前言
平常使用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文件夾裏