AFIO时钟误区及其用法解析

AFIO时钟误区解析

AFIO误区

我们一般错误理解为“启用IO口的复用功能就要使能AFIO时钟”,但是我们回想一下GPIO的结构图仔细了解一下GPIOx_Pin的结构:

 

在上图中,从端口引脚传入的信号进入了复用输入通道,而且我们看到流入普通GPIO输入通道的信号同样可以流入复用功能输入通道,这表明“与引脚复用输入通道相连的内外设(内外设就是IIC这类设备)这些设备根本不需要AFIO时钟就可以接收到所在引脚输入的信号”。

复用功能为什么不用AFIO?

我们还了解一个名词叫“复用功能”,复用功能不同于端口重映射,复用功能是“上图中复用输入输出通道直接相连的内外设”,我们只要将输出模式配置为复用输出模式,输入配置为对应内外设要求的输入模式(浮空,上下拉),此时我们的内外设输入输出通道就配置好了。

复用功能与端口重映射有本质的不同,引脚复用是这个引脚本来就有的功能,我们知道一个引脚有许多功能,如下表所示:PC6除了正常的GPIO功能外还可以作为I2S2_MCK或TIM8_CH1或SDIO_D6使用。

 

但是端口重映射之后,这个原引脚的某个外设功能迁移至其他引脚,原引脚的内外设功能失效。

AFIO有何神奇作用?

你知道了复用与重映射的区别,但是你知道这个引脚功能是怎么样迁移的吗?千万别以为是改变了引脚的接线线路,而是AFIO起作用了,由于复用IO时钟使能,此时本来属于原引脚的输入输出信号通过内部其他通道引导至重映射引脚的①,②位置,作为新的输入输出引脚。此时,我们可以把AFIO当作一个信号迁移人员。当我们需要将“不与①位置直接相连的位置接受①通道读入的引脚信号“,或者当我们需要将”不与②通道直接相连位置的信号引入②通道进行输出“,就必须使用AFIO这个信号搬运工。

需要信号搬运工(AFIO)的地方主要是“信号发出源与复用输出通道不直接相连“或者”信号接受源不直接与复用输入通道相连“:

① 当端口引脚重映射时,需要使能AFIO时钟?

 

例如上述的TIM1引脚的重映射,我们知道TIM1_CH1原引脚应为PA8:

 

此时,我前面提过“如果要将PA8的输入输出信号转移至PE9的输入输出信号需要一个媒介这个媒介就是AFIO“,那此时必须提供AFIO时钟。

② 当我们使用的内外设不是在APB2总线上时,我们需要通过AFIO充当一个信号中继。

例如:我们一般输出PWM时,都用APB1总线上TIM2,TIM3等等,很少用到APB2总线上的TIM1,TIM8这个两个高级定时器。当我们使用非APB2总线上的内外设功能时,由于不同总线上输入输出信号无法直接互传,此时需要一个信号搬运工来使得不同总线上的脉冲信号正确的传输不至于脉冲信号在不同驱动时钟下错乱。例如如下代码段:

 

连接在APB1总线上的TIM3输出PWM时,需要使能AFIO时钟:

void TIM3_PWM_Init(u16 arr,u16 psc)
{  
 GPIO_InitTypeDef GPIO_InitStructure;
 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
 TIM_OCInitTypeDef  TIM_OCInitStructure;
 
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
 
 GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    
 
   //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM3
 TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
 
 //初始化TIM3 Channel2 PWM模式  
 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
 TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2

 TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
 
 TIM_Cmd(TIM3, ENABLE);  //使能TIM3
}

 

连接到APB2总线上的ADC模数转换不需要使能AFIO时钟:

void  Adc_Init(void)
{  
 ADC_InitTypeDef ADC_InitStructure; 
 GPIO_InitTypeDef GPIO_InitStructure;

 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE );   //使能ADC1通道时钟
 

 RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

 //PA1 作为模拟通道输入引脚                         
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  //模拟输入引脚
 GPIO_Init(GPIOA, &GPIO_InitStructure); 

 ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值

 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
 ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
 ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
 ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   

  
 ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
 
 ADC_ResetCalibration(ADC1); //使能复位校准  
  
 while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
 
 ADC_StartCalibration(ADC1);  //开启AD校准
 
 while(ADC_GetCalibrationStatus(ADC1));  //等待校准结束
 
// ADC_SoftwareStartConvCmd(ADC1, ENABLE);  //使能指定的ADC1的软件转换启动功能

}

 

③ EXTI外部中断线映射需要使能AFIO时钟:

EXTI中断线与GPIO复用输入通道并没有直接相连,有些人疑问EXTI为什么没有EXTI专用的时钟?这是因为EXTI不是内外设,是一种用于检测外部电平变化的“信号传输媒介“,就相当于”处理一下输入信号,然后将处理好的信号传输给NVIC中断判断优先级控制器“。

 

从上图中,我们可以知道信号传输媒介也就是信号搬运工都是AFIO来充当的,这里也不例外,EXTI相当于一种将GPIO引脚信号映射到NVIC当中去的一种AFIO功能,既然EXTI充当信号映射的媒介,那么EXTI的时钟就由AFIO提供。EXTI时钟配置实例:

// GPIOx引脚外部中断属性配置
void GPIO_EXTI_Config()
{
    EXTI_InitTypeDef EXTI_InitStructure;
    
    //配置复用IO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    // 中断线映射
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource8); // 选择PA8映射至EXTI8中断线
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource13); //选择PC13映射至EXTI13中断线
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource0|GPIO_PinSource4); //选择PD0,PD4分别映射至EXTI0,EXTI4中断线
    // UP,DOWN,LEFT按键未触发时处于下拉状态
    EXTI_InitStructure.EXTI_Line = EXTI_Line0|EXTI_Line4|EXTI_Line8;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 中断屏蔽位置位
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_Init(&EXTI_InitStructure);
    // TEMPER按键未触发时处于上拉状态
    EXTI_InitStructure.EXTI_Line = EXTI_Line13;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 中断屏蔽位置位
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_Init(&EXTI_InitStructure);
}

 

为什么AFIO时钟在APB2总线上?

因为我们发现,端口重映射之后,映射的新引脚都是GPIO的引脚,GPIOx又是APB2的外设,AFIO作为一个积极的信号搬运工,使用APB2给其提供时钟是在合适不过了。

  • 58
    点赞
  • 169
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥肥胖胖是太阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值