GPIO的简介

对于STM32单片机都知道,GPIO口有8种输入输出模式,引脚电平:0V~3.3V,部分引脚可容忍5V,输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等,输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等。
这8种模式分别对应为
浮空输入:引脚悬空,但读取电平时容易收到外界的影响,需要接连续的驱动源。
上拉输入:可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
下拉输入:可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
模拟输入:GPIO无效,引脚直接接入内部ADC
开漏输出:可输出引脚电平,高电平为高阻态,低电平接VSS
推挽输出:可输出引脚电平,高电平接VDD,低电平接VSS
复用推挽输出:由片上外设控制,用于绑定好的输出引脚,高电平接VDD,低电平接VSS
复用开漏输出:由片上外设控制,高电平为高阻态,低电平接VSS

举例说明GPIO的配置

OPA

以CH32V307VCT6内部自带的运算放大器为例,其作用时做比较器来使用,其两个输入信号,很明显是模拟量,而内部输出也会接一个GPIO口,注意这里GPIO的作用并不是强驱动,而是起到一个读取电平的作用。
由此可见,其两个输入引脚所对应的GPIO口,要设置为模拟输入模式,而用于读取输出信号的引脚可以配置为浮空输入模式(因为运放输出信号很明显是一个持续的驱动源)、上拉输入、下拉输入(后两种模式更稳定一些)。
在配置引脚时,我们可以查阅引脚复用表,这里只附上一部分,想要详细了解可以查阅手册
在这里插入图片描述
配置代码如下:

/*
本OPA配置用于检测三相无刷直流电机的反电动势变化,从而识别幻想
故此需要配置2*3+3=9个引脚
6个输入引脚
OPA1CH0P 接A相反电动势
OPA1CH1N 接中性点
P与N分别代表正负输入端,0与1代表OPA的正负极各有两个通道,任选一个即可

OPA2CH0P 接B相反电动势
OPA2CH1N 接中性点

OPA3CH0P 接C相反电动势
OPA3CH1N 接中性点

3个输出读取引脚(都选择通道0)
OPA1_OUT0
OPA2_OUT0
OPA3_OUT0

具体引脚所对应的GPIO口请查阅手册
*/
#define PHASE_A_OPA         OPA1            // A相检测使用OPA1
#define PHASE_B_OPA         OPA2            // B相检测使用OPA2
#define PHASE_C_OPA         OPA3            // C相检测使用OPA3

#define A_PHASE_CHP        CHP0            // A相反电动势接CHP0
#define A_PHASE_CHN        CHN1            // 中性点电压接CHN1
#define A_PHASE_OUTPUT     OUT_IO_OUT0     // A相OPA输出到OUT_IO_OUT0

#define B_PHASE_CHP        CHP0            // B相反电动势接CHP0
#define B_PHASE_CHN        CHN1            // 中性点电压接CHN1
#define B_PHASE_OUTPUT     OUT_IO_OUT0     // B相OPA输出到OUT_IO_OUT0

#define C_PHASE_CHP        CHP0            // C相反电动势接CHP0
#define C_PHASE_CHN        CHN1            // 中性点电压接CHN1
#define C_PHASE_OUTPUT     OUT_IO_OUT0     // C相OPA输出到OUT_IO_OUT0

void OPA_CommutationConfig(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    OPA_InitTypeDef OPA_InitStructure = {0};
    /* 使能GPIO时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);

    /* 配置A相OPA1输入引脚 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;  // PB15: A相反电动势
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入模式
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;  // PA6:中性点
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入模式
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 配置B相OPA2输入引脚 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;// PB14:B相反电动势
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入模式
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;  // PA5:中性点
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入模式
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 配置C相OPA3输入引脚  */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;// PB13:C相反电动势
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入模式
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;// PC2:中性点
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入模式
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    /*配置A B C相OPA的读取引脚*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//OPA的输出直接通过对应的GPIO口读取即可
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 配置A相OPA1为比较器模式 */
    OPA_StructInit(&OPA_InitStructure);
    OPA_InitStructure.OPA_NUM = PHASE_A_OPA;              // A相检测使用OPA1
    OPA_InitStructure.PSEL = A_PHASE_CHP;                 // 正输入接反电动势  A相反电动势接CHP0
    OPA_InitStructure.NSEL = A_PHASE_CHN;                 // 负输入接中性点     中性点电压接CHN1
    OPA_InitStructure.Mode = A_PHASE_OUTPUT;              // 输出模式  A相OPA输出到OUT_IO_OUT0(通道0)
    OPA_Init(&OPA_InitStructure);//应用配置
    OPA_Cmd(PHASE_A_OPA, ENABLE);//使能OPA

    /* 配置B相OPA2为比较器模式 */
    OPA_StructInit(&OPA_InitStructure);
    OPA_InitStructure.OPA_NUM = PHASE_B_OPA;              // B相检测使用OPA1
    OPA_InitStructure.PSEL = B_PHASE_CHP;                 // 正输入接反电动势  B相反电动势接CHP0
    OPA_InitStructure.NSEL = B_PHASE_CHN;                 // 负输入接中性点     中性点电压接CHN1
    OPA_InitStructure.Mode = B_PHASE_OUTPUT;              //输出模式  A相OPA输出到OUT_IO_OUT0(通道0)
    OPA_Init(&OPA_InitStructure);
    OPA_Cmd(PHASE_B_OPA, ENABLE);

    /* 配置C相OPA3为比较器模式 */
    OPA_StructInit(&OPA_InitStructure);
    OPA_InitStructure.OPA_NUM = PHASE_C_OPA;              // C相检测使用OPA1
    OPA_InitStructure.PSEL = C_PHASE_CHP;                 // 正输入接反电动势  C相反电动势接CHP0
    OPA_InitStructure.NSEL = C_PHASE_CHN;                 // 负输入接中性点     中性点电压接CHN1
    OPA_InitStructure.Mode = C_PHASE_OUTPUT;              //输出模式  A相OPA输出到OUT_IO_OUT0(通道0)
    OPA_Init(&OPA_InitStructure);
    OPA_Cmd(PHASE_C_OPA, ENABLE);
}

外部中断的配置

为什么要配置外部中断呢?由于三相无刷直流电机在运行的过程种某一相的感应电动势是会变化的,其OPA对应的输出电平是会发生变化的,从而以此来判断换相时机(每当感应电动势过0时,对应着上升沿与下降沿的情况)。如何通过一直CPU轮询判断的方式是很耗费资源的,因此这里采用外部中断来触发换向。
下面先简单介绍一下外部中断的内容:

外部中断简介

EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。
支持的触发方式:上升沿/下降沿/双边沿/软件触发
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断

外部中断的基本结构

也以stm32为例,不同的PIN对应着不同的中断通道,但是相同的PIN无法同时触发外部中断。并且EXTI1~EXTI4有着单独的通道,而其他外部中断与通道就不是一一对应了,其CH32V307VCT6也与stm32类似。
在这里插入图片描述

AFIO复用IO口

由于每一个外部中断通道对应着很多触发信号,所以需要对触发信号进行选择,所以AFIO的作用便是中断引脚选择。
但其实AFIO还有另外一个功能就是复用功能重定义,就是将默认的复用功能,换成重定义的功能。
在这里插入图片描述

代码配置

/* 外部中断配置 - 三个OPA输出分别连接EXTI0/1/2 */
void EXTI_AND_NVIC_Configuration(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    EXTI_InitTypeDef EXTI_InitStructure = {0};
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    /*配置 A B C的OPA输出为外部中断触发源*/
    //A相(PA3)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);

    //B相(PA2)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2);

    //C相(PA1)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);

    /* 配置EXTI线 - 双边沿触发 */
    EXTI_InitStructure.EXTI_Line = EXTI_Line1 | EXTI_Line2 | EXTI_Line3;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//上升沿下降沿都触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    /*配置中断*/
    NVIC_InitTypeDef NVIC_InitStructure;
    // 配置EXTI1中断
    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // 配置EXTI2中断
    NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; // 不同的子优先级
    NVIC_Init(&NVIC_InitStructure);

    // 配置EXTI3中断
    NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 不同的子优先级
    NVIC_Init(&NVIC_InitStructure);
}

至此艺术已成!后续将继续更新其他外设的配置方式,本文章基于对三相无刷直流电机来循序渐近的配置。插图来自于江科大自动协

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐