所有资料百度云:点此跳转
首先我们讲讲GPIO(general-purpose input/output ),也就是芯片的引脚
在开发板的两边有两列排针将该芯片的部分引脚引出,仔细观察还能发现和arduino板子兼容的排母。
GPIO可以输出高低电平或者通过它们读入引脚的状态。单片机还通过他们来实现数据的交互(usart,spi,iic),控制硬件(LED、蜂鸣器、继电器)等功能。
GPIO的具体硬件电路如下所示
不同的电路连接对应了不同的GPIO工作模式,这里我们对电路具体运行情况不做讲解,感兴趣的可以自己查查。
我们来看GPIO的工作模式:
(1)GPIO_Mode_AIN 模拟输入
(2)GPIO_Mode_IN_FLOATING 浮空输入
(3)GPIO_Mode_IPD 下拉输入
(4)GPIO_Mode_IPU 上拉输入
模拟输入,I/O端口的模拟信号(电压信号,而非电平信号)直接模拟输入到片上外设模块,比如ADC模块等等。
浮空,顾名思义就是浮在空中,上面用绳子一拉就上去了,下面用绳子一拉就沉下去了. 也就是说,I/O的电平状态是不确定的,完全由外部输入决定。
上拉,VDD和上拉电阻之间的开关闭合,在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在高电平;并且在I/O端口输入为低电平的时候,输入端的电平也还是低电平
下拉,Vss和下拉电阻之间的开关闭合,在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在低电平;并且在I/O端口输入为高电平的时候,输入端的电平也还是高电平
(1)GPIO_Mode_Out_OD 开漏输出
(2)GPIO_Mode_Out_PP 推挽输出
(3)GPIO_Mode_AF_OD 复用开漏输出
(4)GPIO_Mode_AF_PP 复用推挽输出
开漏,输出端相当于三极管的集电极,要得到高电平需要上拉电阻,适合做电流型驱动。
推挽,就是有推有拉,任何时候IO口的电平都是确定的,不需要外接上拉或者下拉电阻.可以输出强高低电平,连接数字器件
复用,为了节省IO口资源,同一个IO口可能集成了多个功能。但是要用到的某个端口上电初始化后默认功能并不是想要用到的功能,这样就要用到复用功能,这个我们暂时不会用到
我们常用的就是推挽输出了
回顾一下微原,我们对单片机编程实际上就是对一系列寄存器赋值,也就是往各种地址上写0或1。
GPIOx_CRL和GPIOx_CRH是大家比较熟悉的两个寄存器
在这里补充一下,我们对每个引脚的命名是Pxy,x是字母,y是数字
比如之前提到的Nucleo板上的LED灯,是由PA5控制,即表示A组的第5个引脚
这里的GPIOx_CRH,x表示字母,每一组都有与其对应的寄存器
比如PA5是A组,对应了GPIOA_CRH和GPIOA_CRL,高低两个寄存器,每个寄存器是32位
描述一个引脚需要4位,每个寄存器描述8个引脚,一组一共有16个引脚,也就是说最大编号是Px15(从0开始)
描述一个引脚需要4位,前两位是CNFy后 两位是MODEy,MODEy用来描述输入还是输出,是输出最大速度是多少。CNFy进一步描述工作模式
还是以LED为例,我们要将它配成推挽输出,即该引脚对应寄存器的值为0011,写成16进制即为3
GPIOA -> CRL |= 0X00300000;
GPIOA -> ODR |= 1<<5; //1左移5位赋值
GPIO_ODR是输出寄存器,它是低16位有效,每一位代表一个引脚,通过配置它来设置io口的输出状态,让PA5输出1,即绿灯亮
GPIO_IDR是输入寄存器,通过它可以看到引脚的电平状态,同样也是低16位有效,每一位代表一个引脚,在开发板上我们可以看到一个蓝色的user按键B1,查阅电路图,它与PC13相连
也就是说我们可以通过读取PC13的电平状态,来检测按键是否被按下。未按下:PC13直接与VDD相连,被拉高为1。按下:PC13与GND相连,被拉低为0;由于已经有了硬件的上拉和下拉,所以我们直接设置浮空输入即可。
GPIOC -> CRH |= 0X00400000;//0100 浮空输入模式
State = ((GPIOC -> IDR&(1<<13))>>13); //变量State中保存了按键的状态
以上是繁琐的寄存器操作,接下来我将展示cubemx的直观简洁开发过程
打开cubemx,新建工程,选择板子
使用默认设置,选yes。
可以看到该工程已自动为你标出板子上的按键和LED灯
点开左边的GPIO详细界面,它已自动设置好了两引脚的工作模式,即浮空输入和推挽输出
剩下的一切配置与前期文章相同,生成代码,跳到keil中。
GPIO配置在自动生成的MX_GPIO_Init(void)函数中,这里的代码通过宏定义和结构体增强了可读性,再也不用记一个个繁琐零碎的寄存器,再进行开发了。
宏定义部分在gpio.h中,有兴趣可以翻着看看
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LD2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);
B1_Pin和LD2_Pin是用户标签,同样增强了可读性,你也可以在cubemx中自定义成别的名字。
当你在keil中开了自动补全,打出LD2时,它就会弹出LD2_Pin,既提高了速度,又省去了记忆PC13编号的麻烦
HAL_GPIO_WritePin(LD2_GPIO_Port,LD2_Pin,1); //点灯
State = HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin);//读取按键状态
hal库函数比起寄存器是极大地增强了可读性,让开发变得便捷
点开左边的函数视图,我们可以看到hal库中和gpio有关的函数,任选一个函数点击,会自动跳转到该函数定义处。
每个函数前有一段注释,指明了它的功能及参数类型,不难发现,HAL_GPIO_WritePin这个函数底层实际就是在对寄存器进行操作,这就是封装化的编程思想
当我们在做项目时,零散的寄存器编程方式对开发人员极不友好,可读性和维护性都很差。学校推荐的mebd这种开发平台则更加封装化, 和ST 公司的HAL 库不同,Mbed不需要对硬件进行设置和初始化,唯一要做的就是指定硬件的管脚。更类似于arduino。
而半图形化的cubemx+hal库的开发方式位于两者之间,既省去了较为繁琐的配置,又能加强对底层的功能的了解,是一个不错的切入点。