目录
1:关于GPIO的学习
GPIO一些基本信息
GPIO:就是一个引脚作为输入或者输出。
GPIO的八种工作模式:四种输入、四种输出模式;(输入输出是相对于CP
U来说的)以及四种输出最大速度
输入:外部数据输入到开发板
输出:开发板的数据输出到外部设备
八种工作模式
补充:对于标准施密特触发器,当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;
(1):上拉输入(GPIO_Mode_IPU)
上拉输入就是信号进入芯片后加了一个上拉电阻,再经过施密特触发器转换成0、1信号,一般此时输入引脚电平为高电平;
(2):下拉输入(GPIO_Mode_IPD)
上拉输入就是信号进入芯片后加了一个下拉电阻,再经过施密特触发器转换成0、1信号,一般此时输入引脚电平为低电平;
(3):模拟输入 (GPIO_Mode_AIN )
将IO口作为模拟输入接口,输入的可能是变化的值,接收外部的模拟信号输入,信号进入后不经过上拉电阻或者下拉电阻,关闭施密特触发器,经由另一线路把电压信号传送到单片机上外设模块。
(4):浮空输入(GPIO_Mode_IN_FLOATING)
信号进入芯片内部后,既没有接上拉电阻也没有接下拉电阻,经由触发器输入。配置成这个模式后,用电压变量引脚电压为1点几伏,这是个不确定值。复位上电的时候,引脚不确定电平的高低。由于其输入阻抗比较大,一般把这种模式用于标准的通讯协议,比如IIC、USART的等。
(5)推挽输出
推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。
1、普通推挽输出(GPIO_Mode_Out_PP):
一般用在0V和3.3V的场合。线路经过两个P_MOS 和N_MOS 管,负责上拉和下拉电流。输出电平:推挽输出的低电平是0V(0),高电平是3.3V(1)。
2、复用推挽输出(GPIO_Mode_AF_PP):
复用功能,不只是单纯的作为输入输出,可以作为其他功能的引脚。其他同普通推挽。
(6)开漏输出
常说的与推挽输出相对的就是开漏输出,对于开漏输出和推挽输出的区别最普遍的说法就是开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动。
1、普通开漏输出(GPIO_Mode_Out_OD):
一般用在电平不匹配的场合,如需要输出5V的高电平。
若需输出高电平,就需要再外部接一个上拉电阻,电源为5V,把GPIO设置为开漏模式,当输出高组态时,由上拉电阻和电源向外输出5V的电压。
特性: 它具“线与”特性,即很多个开漏模式引脚连接到一起时,只有当所有引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的电压为外部 、上拉电阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当于短路接地,使得整条线路都为低电平0伏。
2、复用开漏输出(GPIO_Mode_AF_OD):
复用功能,不只是单纯的作为输入输出,可以作为其他功能的引脚。其他同普通开漏。
GPIO口的输出模式下,有3 种输出速度可选(2MHz 、10MHz和50MHz)这个速度是指GPIO口驱动电路的响应速度,而不是输出信号的速度。
关于寄存器的配置信息
每组GPIO端口的寄存器包括:
每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL(低位1-7),GPIOx_CRH(高位8-16)) (4~16)
两个32位数据寄存器(GPIOx_IDR(输入电平(0低))和GPIOx_ODR(输出电平(0低)、输入模式配置上下拉))
一个32位置位/复位寄存器(GPIOx_BSRR(低位(1低,0无影响ODR)、高位(1高,0无影响ODR)))
一个16位复位寄存器(GPIOx_BRR(同BSRR高位作用))
一个32位锁定寄存器(GPIOx_LCKR)。
(可以自由编程,必须按照32位字被访问)
2:按键输入实验的实现
期望实现的效果:
按下按键KEY0,LED0亮 同时 蜂鸣器响200ms后停止
按下按键KRY1,LED1亮 同时 蜂鸣器响300ms后停止
按下按键WK_UP,LED0和LED1均灭,蜂鸣器不响
一些基本关于实验的基本信息:
对于LED灯:高电平灯灭,低电平灯亮
对于按键:KEY0和KEY1均为低电平有效,WK_UP为高电平有效,并且两者外部都没有上下拉电阻,需要在 STM32F1 内部设置上下拉。
代码实现思路:
(1)设置LED,蜂鸣器,按键的初始情况;其中LED初始给高电平,BEEP初始给低电平,KEY0/1初始设置上拉,WK_UP初始设置下拉;
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE,ENABLE);//使能
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_SetBits(GPIOB,GPIO_Pin_5);//输出高电平
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_SetBits(GPIOE,GPIO_Pin_5);//输出高电平
}
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//输出低电平
}
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOA,ENABLE);//使能
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//设置上拉输入
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_3;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;//下拉输入
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;
GPIO_Init(GPIOA,&GPIO_InitStruct);
}
(2)读入按键输入的情况,并对其进行判断与反馈;(巧妙利用全局变量设置了两种模式)
//mode=0:不支持连按 1:支持连按
//返回值为1:KEY1按下
// 2:KEY0按下
// 3:WK_UP按下
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键松开
if(mode==0) key_up=1;
if(key_up==1&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(30); //防抖
key_up=0;
if(KEY0==0) return 1;
if(KEY1==0) return 2;
if(WK_UP==0) return 3;
}
else if(KEY0==1&&KEY1==1&&WK_UP==0) key_up=1;
return 0;
}
/*补充个宏定义*/
#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
3:得到返回值后,控制LED和BEEP的开与关
void main(void)
{
delay_init();
LED_Init();
BEEP_Init();
KEY_Init();
while(1)
{
u8 key_op=0;
key_op=KEY_Scan(0);//不连按
if(key_op==1){LED0=0;BEEP=1;delay_ms(200);BEEP=0;continue;}
else if(key_op==2) {LED1=0;BEEP=1;delay_ms(500);BEEP=0;continue;}
else if(key_op==3) {LED0=1;LED1=1;delay_ms(200);continue;}
else continue;
}
}
3:关于端口复用
端口复用简介:
stm32有很多的内置外设,这些外设的外部引脚都是与GPIO复用的。也就是说,一个GPIO如果可以复用为内置外设的功能引脚,那么当这个GPIO作为内置外设使用的时候,就叫做复用。如:PA9,PA10可复用为功能串口1的发送接收引脚。
其意义为时IO口得到最大限度的使用。
端口复用的代码配置:
(以PA9,PA10复用为串口1为实列)
GPIO端口复用时钟使能:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO, ENABLE)
用外设时钟使能:
RCC_APB2PeriphColckCmd(RCC_APB2Periph_USART1, ENABLE)
端口模式配置:(详细配置参考STM32中文参考手册“8.1.11”)
GPIO_Init()
根据文档我们需要配置:
PA9-推挽复用输出
PA10-浮空或上拉输入
//IO时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//外设时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//初始化IO为对应的模式-配置PA9-复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // PA9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置PA10-浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;// 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
4:NVIC中断优先级
NVIC中断优先级管理方法
对STM32中断进行分组,组0~4。同时,对每个中断设置一个抢占优先级和一个响应优先级值;
分组配置是在寄存器SCB->AIRCR中配置:
我们在代码中都会发现有一个名为NVIC优先级分组(NVIC_PriorityGroup)的概念
(我们设置中断优先级分组只设置一次,在代码开头处设置,设置完分组后不要随意更改,避免中断管理混乱)
0是最高,15是最低
通过上面的表格,我们可以发现NVIC中断优先级分组可以分为5组,就那分组2为例:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//在misc.c文件中
则表示有2位抢占优先级和2位响应优先级
则抢占优先级PreemptionPriority 可以设置为 0,1,2,3
响应优先级Subpriority 可以设置为 0,1,2,3
抢占优先级和响应优先级区别
高优先级(值越小越高)的抢占优先级是可以打断正在进行的低抢占优先级中断的;
抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断;
抢占优先级相同的中断,当两个中断同时发生的情况下,高响应优先级先执行;
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行。
举例:
假定设置中断优先级组为2,
设置中断3(RTC中断)的抢占优先级为2,响应优先级为1;
设置中断6(外部中断0)的抢占优先级为3,响应优先级为0;
设置中断7(外部中断1)的抢占优先级为2,响应优先级为0;
则:中断7>中断3>中断6
中断处理代码参数设置
结构体理解:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
typedef struct
{
uint8_t NVIC_IRQChannel; //设置中断通道
uint8_t NVIC_IRQChannelPreemptionPriority; //设置抢占优先级
uint8_t NVIC_IRQChannelSubPriority; //设置相应优先级
FunctionalState NVIC_IRQChannelCmd; //使能
}NVIC_InitTypeDef;
初始设置(以串口1为例):
void USART1_Init(void){
//这里是配置允许定时器中断
NVIC_InitTypeDef NVIC_Initstructure;
//第一个参数指定具体是那种中断,例如串口1是USART1_IRQn
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
//串口1中断,中断类型参考stm32f10x.h中的IRQn_Tpye;//
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority= 2; //响应优先级位2
NVIC_InitStructure.NVIC_IRQChannelCmd= ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
//根据上面指定的参数初始化NVIC寄存器//
}
//另外记得在主函数配置中断优先级分组
NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2)
5:串口通信实验
基本原理
通信方式:
分为并行通信和串行通信两种:
并行通信 | 串行通信 | |
---|---|---|
原理 | 数据各个位同时传输 | 数据按位顺序同时传输 |
优点 | 传输速度快 | 使用串口数量少 |
缺点 | 使用串口数量多 | 传输速度慢 |
串行通信分类:
(1)按照数据传送方向,分为:
- 单工:数据传输只支持数据在一个方向上传输;
- 半双工:允许数据在两个方向上传输。但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;它不需要独立的接收端和发送端,两者可以合并一起使用一个端口。
- 全双工:允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,需要独立的接收端和发送端。
(2)按照通信方式,分为:
- 同步通信:带时钟同步信号传输。比如:SPI,IIC通信接口。
- 异步通信:不带时钟同步信号。比如:UART(通用异步收发器),单总线。(通讯中需要双方规约好数据的传输速率(也就是波特率))
关于stm32串行通信:
(1)STM32的串口通信接口有两种,分别是:UART(通用异步收发器)、USART(通用同步异步收发器)。而对于大容量STM32F10x系列芯片,分别有3个USART和2个UART。
(2)stm32中UART参数:
串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口,通讯双方的数据包格式要规约一致才能正常收发数据。STM32中串口异步通信需要定义的参数:起始位、数据位(8位或者9位)、奇偶校验位(第9位)、停止位(1,15,2位)、波特率设置。
UART串口通信的数据包以帧为单位,常用的帧结构为:1位起始位+8位数据位+1位奇偶校验位(可选)+1位停止位。
代码实现
期望代码实现要求:
输入“w” 控制LED0开,输入”s”控制LED1开,输入“a”控制 BEEP 响,输入“d”控制 BEEP不响,输入“o”控制两灯均关
同时屏幕上显示此时操做状态;其他按键均无反应;
(1)按照参口手册配置好复用的GPIO口
void GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruc;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStruc.GPIO_Mode=GPIO_Mode_AF_PP;//推挽输出
GPIO_InitStruc.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruc.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStruc);
GPIO_InitStruc.GPIO_Mode=GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_InitStruc.GPIO_Pin=GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStruc);
}
(2)配置好串口
void USART1_Init(void)
{
USART_InitTypeDef USART_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
USART_InitStruct.USART_BaudRate=115200; //配置波特率
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //硬件流控制
USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;//USART的模式
USART_InitStruct.USART_Parity=USART_Parity_No;//校验位
USART_InitStruct.USART_StopBits=USART_StopBits_1;//停止位
USART_InitStruct.USART_WordLength=USART_WordLength_8b;//字长
/*数据和通信设备匹配就行*/
USART_Init(USART1,&USART_InitStruct);
USART_Cmd(USART1,ENABLE);//串口使能
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//接收完成中断
}
补充个 USART_ITConfi 中断使能:
USART_IT | 描述 |
USART_IT_PE | 奇偶错误中断 |
USART_IT_TXE | 发送中断 |
USART_IT_TC | 传输完成中断 |
USART_IT_RXNE | 接收中断 |
USART_IT_IDLE | 空闲总线中断 |
USART_IT_LBD | LIN 中断检测中断 |
USART_IT_CTS | CTS 中断 |
USART_IT_ERR | 错误中断 |
(3)配置好串口:
void NVIC_Init(void)
{
NVIC_InitTypeDef NVIC_InitStruc;
NVIC_InitStruc.NVIC_IRQChannel=USART1_IRQn;//串口中断类型
NVIC_InitStruc.NVIC_IRQChannelCmd=ENABLE;//使能
NVIC_InitStruc.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级设置为1
NVIC_InitStruc.NVIC_IRQChannelSubPriority=1;//响应优先级设置为1
NVIC_Init(&NVIC_InitStruc);
}
(4) 通过中断接收(设置协议)
USART_ReceiveData(DEBUG_USARTx)——接收函数
USART_SendData(DEBUG_USARTx, x)——发送函数
USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE)——判断标志位函数
void USART1_IRQHandler(void) //串口中断设置
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断是否接受中断(0x0d,0x0a)
{
Res =USART_ReceiveData(USART1); //读取接收数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误啦,重新开始
else USART_RX_STA|=0x8000; //接收完成啦
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//长度过大,接收失败
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS
OSIntExit();
#endif
}
(4)主函数执行数据
#include "stm32f10x.h"
#include "led.h"
#include "beep.h"
#include "sys.h"
#include "usart.h"
#include "delay.h"
int main(void)
{
u16 len;
u16 i;
u8 tf=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
LED_Init();
BEEP_Init();
uart_init(115200); //波特率设置
while(1)
{
if(USART_RX_STA&0x8000)//接收完毕
{
len=USART_RX_STA&0x3fff;//字符长度
if(len==1)
{
if(USART_RX_BUF[0]=='w') {LED0=0;printf("LED0亮\r\n");USART_RX_STA=0;continue;}
if(USART_RX_BUF[0]=='s') {LED1=0;printf("LED1亮\r\n");USART_RX_STA=0;continue;}
if(USART_RX_BUF[0]=='a') {BEEP=1;printf("BEEP响\r\n");USART_RX_STA=0;continue;}
if(USART_RX_BUF[0]=='d') {BEEP=0;printf("BEEP不响\r\n");USART_RX_STA=0;continue;}
if(USART_RX_BUF[0]=='o') {LED1=1;LED0=1;printf("两灯均灭\r\n");USART_RX_STA=0;continue;}
}
printf("传输数据: \r\n");
for(i=0;i<len;i++)
{
USART_SendData(USART1,USART_RX_BUF[i]);//传输数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
printf("\r\n");
USART_RX_STA=0;
}
}
}
注:主函数记得给中断分组:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);