4.4.1端口复用和重映射
内置外设:集成在单片机内部的可以被指令直接控制的外部设备,如串口控制模块,SPI模块,I2C模块,A/D模块,PWM模块,CAN模块,EEPROM,比较器模块等端口复用功能
What:(正点原子p126)STM32有很多的内置外设,这些外设的外部引脚都是与GPIO复用的。也就是说,一个GPIO如果可以复用为内置外设的功能引脚,那么当这个GPIO作为内置外设使用的时候,就叫做复用。
个人理解:内置外设的接口和GPIO的接口是一致的,我们可以操作GPIO的接口来控制内置外设的功能。实际上给GPIO的接口赋予了多个功能,通过I/O的工作模式来切换不同形态。
Can: 提高复用,节省资源
Use: 典型使用的有
具体可参考《STM32中文参考手册V10》P109起
这里我们用到的是USART1,PA9、PA10引脚默认是串口1的使用,即端口复用
复用端口初始化步骤
- 使能端口和串口的时钟。这里用到USART1和两个IO
RCC_APB2PriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Priph_USART1,ENABLE);
- 端口模式配置。我们可以理解为设置模式就可切换功能,具体如何配置,查看《STM32中文参考手册V10》P110(上图有贴出来)
4.4.2端口重映射
What:(正点原子p127)为了使不同器件封装的外设I0功能数量达到最优,可以把一些复用功能重新映射到其他些引脚.上。STM32中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。在STM32中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。
个人理解:将内置外设的输入输出端口改变到其他端口
Can: 每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,
Use: 简单的讲就是把管脚的外设功能映射到另一个管脚,但不是可以随便映射的,具体对应关系《STM32中文参考手册V10》的P116 页“8. 3复用功能和调试配置”有讲解。
端口重映射步骤如下
- 使能GPIOB和串口时钟(同复用一样)
- 使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
- 开启重映射
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);//接口——引脚重映射功能(接口——重映射——串口名,能)
串口设置步骤
- 串口时钟使能,GPIO时钟使能
- 串口复位(可以省略)
- GPIO 端口模式设置
- 串口参数初始化
- 开启中断并且初始化NVIC (如果需要开启中断才需要这个步骤)
- 使能串口
USART_Cmd(USART1,ENABLE);
- 编写中断处理函数
串口状态:
USART_GetFlagStatus(USART1,USART_FLAG_EXNE);
//RXNE寄存器是否非空
发送完成:USART_GetFlagStatus(USART1,USART_FALG_TC)
//TC发送完成注意:必须在usart.h中设置EN_USART1_RX为1,复用的IO在逻辑分析窗口是无法显示的
设计案例并实现
实验要求
将前面3章的知识运用起来,设计一个多功能按键选择实验
- 利用3个按键,KEY0、KEY1、WK_UP分别控制,DS0、DS1、BEEP
- 串口通信,分别控制三个按钮是否有效
- 通讯头0xC5 + 命令(0xa0、0xb1、0xab、0xcc)
- 命令解释:0xa0打开KEY0,0xb1打开KEY1,0xab打开WK_UP,0xcc关闭所有按键功能
- 将接收到的命令发回给上位机表示收到,Ack_Cmd()
- 如果没有检测到通讯头,就重新接收
实验部分代码
/*******usart.c********/
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA==0)
{
if(Res!=0xC5)
{
USART_RX_STA=0;
}else{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res;//c5
USART_RX_STA++;//1
}
}
else if(USART_RX_STA>0){
if(USART_RX_STA>3)
{
USART_RX_STA=0;
}
if(USART_RX_STA==3)
{
USART_RX_STA|=0x8000;
}
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res;
USART_RX_STA++;
}
}
}
/************main.c**************/
u8 Ack[3];
u16 t=0;
u16 len;
void Ack_Cmd(void)
{
Ack[0]=0xC5;
Ack[1]=USART_RX_BUF[1];
for(t=0;t<2;t++)
{
USART_SendData(USART1, Ack[t]);//向串口4发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
}
int main(void)
{
u16 key;
u16 temp0=0,temp1=0,temp2=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
KEY_Init(); //初始化与按键连接的硬件接口
BEEP_Init(); //蜂鸣
while(1)
{
key = KEY_Scan(0);
switch(key)
{
case KEY0_PRES:
{
if(temp0){
delay_ms(10);
LED0=~LED0;
}
break;
}
case KEY1_PRES:
{
if(temp1){
delay_ms(10);
LED1=~LED1;
}
break;
}
case WKUP_PRES:
{
if(temp2){
delay_ms(10);
BEEP=~BEEP;
}
break;
}
}
if(USART_RX_STA&0x8000)//接收完成的判断
{
USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);
delay_ms(20);
Ack_Cmd();
delay_ms(20);
if(USART_RX_BUF[1]==0xa0) temp0=1;
else if(USART_RX_BUF[1]==0xb1) temp1=1;
else if(USART_RX_BUF[1]==0xab) temp2=1;
else if(USART_RX_BUF[1]==0xcc) {temp0=0;temp1=0;temp2=0;}
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启接收中断
USART_RX_STA=0;
}
}
}
实验现象
一开始三个按键都是无效的,通过串口发送正确格式的命令(C5开头+一个字节的命令+调试助手自带的空格\换行),分别发送对应按键命令后,得到命令答复,再次按下按键,可以发现led灯可以亮灯\熄灭,蜂鸣器响\不响,再发送0xcc命令后,所有按键控制的功能都失效了。
实验不足
按下按键,灯会有颤动虽然加了delay_ms延缓,其次命令返回有事不准又是准,观察得出可能是缓存的问题,上个命令下次才出现。
总结
初次学习很多都不是很理解,不过向前学不应该停下纠结,想必随着后来接触越来越多,以往不懂的地方也能潜移默化的明白了。个人综合前面学习知识设计的案例,可能存在不足,大家也可以自己设计案例
文章参考文献均来自正点原子哦