基础知识
STM32F103RCT6有(16*3+3)个IO口,每个IO口有以下工作模式:
(1) GPIO_Mode_AIN 模拟输入(开关1、2均打开,开关3打开)
(2) GPIO_Mode_IN_FLOATING 浮空输入(开关1、2均打开,开关3关闭)(悬空时不确定)
(3) GPIO_Mode_IPD 下拉输入(开关1打开,开关2闭合,开关3闭合)(悬空时保持高电平)
(4) GPIO_Mode_IPU 上拉输入(开关1闭合,开关2打开,开关3闭合)(悬空时保持低电平)
(5) GPIO_Mode_Out_OD 开漏输出(P-MOS一直处于关闭状态,当输出高电平,N-MOS管关闭,输出由外部决定,当输出低电平时,N-MOS管闭合,输出为低电平。)
(6) GPIO_Mode_Out_PP 推挽输出(当输出为高电平时,P-MOS处于开启状态,N-MOS处于关闭状态,输出高电平。当输出为低电平时,P-MOS处于关闭状态,N-MOS处于开启状态,输出低电平。)
(7) GPIO_Mode_AF_OD 复用开漏输出(与开漏输出模式类似。输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。)
(8) GPIO_Mode_AF_PP 复用推挽输出(与推挽输出类似,输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。)
GPIO端口结构.PNG
开漏输出:只可以输出强低电平,高电平得靠外部电阻拉高。输出端相当于三极管的集电极。适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内);
推挽输出:可以输出强高、低电平,连接数字器件。
寄存器
STM32 的每个 IO 端口都有 7 个寄存器来控制。他们分别是:配置模式的 2 个 32 位的端口配置寄存器 CRL 和 CRH;2 个 32 位的数据寄存器 IDR 和 ODR;1 个 32 位的置位/复位寄存器BSRR;一个 16 位的复位寄存器 BRR;1 个 32 位的锁存寄存器 LCKR。
端口配置寄存器每位rw。
输出模式位.PNG
端口配置表.PNG
CRH.PNG
CRL.PNG
数据寄存器只有低16位有效。
ODR.PNG
IDR.PNG
置位/复位寄存器BSRR 写入0无效 BR写入1清除对应ODR位为0,BS写入1设置对应的ODR位为1
BSRR.PNG
复位寄存器BRR写入0无效写入1清除对应ODR位为0(作用与BSRR的BR相同)
BRR.PNG
锁存寄存器 LCKR,用于锁定配置寄存器 CRL和CRH的值。一旦被锁住,只有再次“复位GPIO口”才能解锁。锁键的写入序列:写1 -> 写0 -> 写1 -> 读0 -> 读1。在操作锁键的写入序列时,不能改变LCK[15:0]的值。
LCKR.PNG
为了优化 64 脚或 100 脚封装的外设数目,可以把一些复用功能重新映射到其他引脚上。设置 复用重映射和调试I/O配置寄存器(AFIO_MAPR)实现引脚的重新映射。这时,复用功能不再映射到它们的原始分配上。即AFIO寄存器。(先不学习)
开发板代码分析
通过代码控制 ALIENTEK MiniSTM32 开发板上的两个 LED:DS0 和 DS1 交替闪烁,实现类似跑马灯的效果。DS0 接 PA8,DS1 接 PD2。
寄存器
配置GPIO前要先配置所在的时钟。
APB2ENR.PNG
RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
RCC->APB2ENR|=1<<5; //使能 PORTD 时钟
PA8 PD2输出高低电平,推挽输出,CNFMODE配置:0011(50MHz)(11二进制中为3)
GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X00000003;//PA8 推挽输出
GPIOA->ODR|=1<<8; //PA8 输出高
GPIOD->CRL&=0XFFFFF0FF;
GPIOD->CRL|=0X00000300;//PD.2 推挽输出
GPIOD->ODR|=1<<2; //PD.2 输出高
设置输出高低电平,开发板中附带了sys.h内含有位操作定义,可直接使用
LED0=0; LED1=1;(#define LED0 PAout(8) // DS0 #define LED1 PDout(2) // DS1)
若无位操作,可以通过ODR寄存器来控制输出高低电平。
#define LED0 (1<<8) //led0 PA8
#define LED1 (1<<2) //led1 PD2
#define LED0_SET(x) GPIOA->ODR=(GPIOA->ODR&~LED0)|(x ? LED0:0)
#define LED1_SET(x) GPIOD->ODR=(GPIOD->ODR&~LED1)|(x ? LED1:0)
库函数
库函数的使用需要添加相应的库函数文件。
时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
RCC_APB2Periph_GPIOD, ENABLE); //使能 GPIOA,GPIOD 端口时钟
PA8 PD2输出高低电平,推挽输出。初始化配置函数,输入配置参数。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 PA8
GPIO_SetBits(GPIOA,GPIO_Pin_8); //PA8 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD2 推挽输出
GPIO_Init(GPIOE, &GPIO_InitStructure); //初始化 PD2
GPIO_SetBits(GPIOD,GPIO_Pin_2); //PD2 输出高
设置输出高低电平
GPIO_SetBits(GPIOA, GPIO_Pin_8); //GPIOA->BRR=GPIO_Pin_8;
GPIO_ResetBits (GPIOA, GPIO_Pin_8); //GPIOA->BSRR=GPIO_Pin_8;
实践
MDK工程框架搭建
- 新建工程时,选择CORE DSP GPIO Startup文件(与开发板实例中的不同)
CORE为Cortex-M3内核支持库,DSP为信号处理算法库、GPIO为通用目的输入、输出口驱动库、Startup为芯片启动代码库。 - 分组管理添加工程,以及路径配置
- 配置输出和仿真设置
main 函数为入口 运行时main函数里用到的函数需要声明,这些声明在头文件里。
寄存器工程实例
includes.h 包含了所有需要的头文件。
#include "includes.h"
void LEDInit(void)
{
RCC->APB2ENR |=(1<<2)|(1<<5);
GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X00000003;//PA8 推挽输出
GPIOA->ODR|=1<<8; //PA8 输出高
GPIOD->CRL&=0XFFFFF0FF;
GPIOD->CRL|=0X00000300;//PD.2 推挽输出
GPIOD->ODR|=1<<2; //PD.2 输出高
}
void LED(Int08U w, LEDState s)
{
switch(w)
{
case 0:
if(s==LED_ON)
GPIOA->BRR=(1<<8);
else
GPIOA->BSRR=(1<<8);
break;
case 1:
if(s==LED_ON)
GPIOD->BRR=(1<<2);
else
GPIOD->BSRR=(1<<2);
break;
}
}
#include "includes.h"
void Delay(Int32U);
int main(void)
{
LEDInit();
for(;;)
{
LED(0,LED_ON);
LED(1,LED_OFF);
Delay(500);
LED(0,LED_OFF);
LED(1,LED_ON);
Delay(500);
}
}
void Delay(Int32U u)
{
Int32U i,j;
for(i=0;i<u;i++)
for(j=0;j<12000;j++);
}
变量类型重命名
#ifndef _VARTYPES_H
#define _VARTYPES_H
typedef unsigned char Int08U;
typedef signed char Int08S;
typedef unsigned short Int16U;
typedef signed char Int16S;
typedef unsigned int Int32U;
typedef signed int Int32S;
typedef float float32;
typedef enum{LED_ON,LED_OFF} LEDState;
#endif
库函数工程实例
库函数和寄存器不同的是,项目管理处需要添加库函数需要的文件,本例程添加“gpio”和“rcc”即可。程序部分只需要修改“led.c”部分。
库函数下载地址 https://download.csdn.net/download/alanzjl/8638291
libraries 中 inc存放.h文件 src存放.c文件,可以从别的文件夹如例程中拷贝stm32f10x_conf.h文件。
在C/C++选项卡中添加两个全局宏定义常量STM32F10X_HD和USE_STDPERIPH_DRIVER。
需要修改的led.c文件:
#include "includes.h"
void LEDInit(void)
{
GPIO_InitTypeDef g;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD,ENABLE);
g.GPIO_Pin=GPIO_Pin_8;
g.GPIO_Mode=GPIO_Mode_Out_PP;
g.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &g);
g.GPIO_Pin=GPIO_Pin_2;
g.GPIO_Mode=GPIO_Mode_Out_PP;
g.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &g);
}
void LED(Int08U w, LEDState s)
{
switch(w)
{
case 0:
if(s==LED_ON)
GPIO_ResetBits(GPIOA,GPIO_Pin_8);
else
GPIO_SetBits(GPIOA,GPIO_Pin_8);
break;
case 1:
if(s==LED_ON)
GPIO_ResetBits(GPIOD,GPIO_Pin_2);
else
GPIO_SetBits(GPIOD,GPIO_Pin_2);
break;
}
}
加入库函数时,可以使用库函数提供的API函数,也可以直接使用寄存器方法。
STM32Cube应用
STM32Cube使用比较简单,外设的配置可以自动生成。而且可以生成报表。只需要填写主函数的逻辑关系即可。填写时可通过hal_gpio.h查看函数名称。
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
HAL_Delay(1000);
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
总结
- 目标确定,分析要用到哪些外设;
- 写主函数,分析需要调用哪些子函数;
- 写子函数,分析用哪种方式配置外设(包括所在时钟);
- 添加头文件等整理项目。
网友评论