STM32的GPIO知识(F103C8T6为例)

        学习过51单片机的同学应该知道IO引脚,表示这个引脚既有输入也有输出的功能,STM32的GPIO也是如此,它的引脚同样也有输入输出的功能。接下来我们来看一下这个芯片的引脚布局。

 

通过引脚的名字我们可以看出以下的现象

①48和47,36和35,23和24这三组引脚是供电用的,8和9是模拟供电。供电电压一般为3.3V。

        这是因为STM32主打的是高性能、低功耗的特色。如果学过初中电学你应该知道一个简单的功率的公式:P=UI,P就是功率,U是电压,I是电流,那我们的电压是减少了,电流也不可能会增大非常大,那么整体的功率是比较小的。为什么要多组供电呢,STM32的多组供电对应不同的区域,如果只用一组供电,如果电源出问题,那么整个芯片都无法工作,而多组供电起码能保证一部分区域能够工作。多组供电还提高了IO的驱动能力,STM32的引脚有几十甚至百来个,只用一组供电,供给到每个IO的电流就会比较小,那么有可能无法驱动外设。因此多组供电也保证了驱动能力。它们之间还有0.1uf的电容,这个电容用于滤波,消除杂乱无章的交流信号,使输出电压更加稳定。

②IO口除了简单的输入输出的功能还具备了复用以及重映射的功能

        STM32只有48个引脚,再去除供电引脚之外也就只剩下可怜的三十多个引脚,如果只是一般的项目,那确实没必要关心,毕竟引脚数量够了。但事实总是相反,如果遇到一个比较大的项目,需要涉及到很多模块,那复用的功能就派上用场。复用的意思就是IO口身兼数职,它不仅可以当做普通的IO口也可以作为定时器、串口、I2C通信等引脚

        如图,PA9以及PA10除了作为普通的IO口之外这两个还可以作为串口通信和定时器通道。

重映射又是什么意思呢 

        举个例子,如果我想让芯片不仅实现串口功能还要实现定时器的功能,当然别的引脚也具备串口和定时器的功能,我们可以分别找到两个引脚作为串口,另外两个引脚作为定时器,任务自然完成。但如果我要求你必须使用串口1和定时器1的3和4通道来完成这个实验呢,你要知道一个引脚不能完成两个复用功能,这个时候我们需要把其中一个复用功能来映射到其他引脚,那么被映射的引脚就具备这个复用功能。

        经过查找我们发现PB6和PB7可以被映射串口1的功能,因此我们可以把串口1的功能映射到PB6和PB7,这样的话,PB6和PB7就具备串口1的功能。即重映射完成。使用重映射可以更加充分利用我们的芯片引脚达到我们想要的目的。 

         有关于GPIO的结构以及八种模式,可以去看正点原子的讲解,还是挺到位的。本人文笔有限,不太能用文字来写出这些东西。

第3讲 GPIO八种工作模式分析_哔哩哔哩_bilibili

        但我们还是可以总结一下GPIO八种工作模式的特点。

①浮空输入:浮空时,IO的状态飘忽不定,易受外界影响,因为此时的IO口没接东西,内部的上下拉电阻也是断开的,呈现高阻态的变化。

②输入上拉:空闲时,IO口是高电平。值得注意的是,这里的上拉是弱上拉。强弱上拉的区别,你可以粗俗的认为,电阻小的是强上拉,电阻大的是弱上拉,毕竟内部的上拉电阻是几十K。

③输入下拉:空闲时,IO口是低电平。

④模拟输入:与ADC、DAC有关。只能接收模拟信号,如果你输入一个数字信号,没卵用。

⑤开漏输出:不能输出高电平,除非外部或内部有上拉电阻。

⑥开漏复用功能:不能输出高电平,除非外部或内部有上拉电阻。输出由片上外设控制,片上外设可以理解为芯片内部的模块,比如ADC,TIM等。

⑦推挽输出:驱动能力强,可输出高低电平。

⑧推挽复用功能:驱动能力强,可输出高低电平。输出由片上外设控制,片上外设可以理解为芯片内部的模块,比如ADC,TIM等。

        我们这款型号在输出时,内部的上下拉电阻无效。

        这里给出一个视频,可以让大家直观的看到这些输入的样子。

上拉下拉浮空和模拟输入到底有什么区别?_哔哩哔哩_bilibili

        接下来我们介绍与GPIO相关的寄存器

       ① 端口配置低寄存器(GPIOX_CRL)(x=A....E)

      

         首先rw表示的是read和wirte的意思,表示这些空间可写可读,如果只有一个r或者一个w那就表示这个空间只能读或写。

        你在代码里会看到很多GPIOA,GPIOB这些东西。GPIOA我们可以理解为一个群落,这个群落有十六个引脚(编号从0到15)。GPIOB,GPIOC都是同理,它们也有各自的十六个引脚。

        CRL寄存器控制低七位的引脚,拿GPIOA举例子就是这个寄存器控制的是编号0到编号7的引脚。为什么这么说呢?你看MODE0 CNF0   MODE1 CNF1,显而易见这些数字就代表着引脚的编号是0-7。控制一个引脚,需要同时对CNFX和MODEX赋值才行。比方说我们想让GPIOA的三号引脚配置为开漏输出模式,最大速度为50MHz。那我们需要这个图来配置我们的引脚模式。

         首先我们要对MODE进行赋值,因为我们说的引脚是三号引脚,那么选择的是MODE3,给它赋值11,接着再对CNF赋值,CNF3赋值为01,那么总体就是赋值0111。

②端口配置高寄存器

 

         配置方式与低寄存器一致,这里不做赘述。

③ODR(端口输出数据寄存器)

        如果我们想让任意一个引脚的模式为上拉输入模式。很简单MODE赋值00,CNF赋值10即可,但是你有没有看到“10:上拉/下拉输入模式”这几个字,那到底是上拉呢还是下拉?这就需要我们的ODR寄存器了。

 因为我们的一个群落最多也就16个引脚,因此16-31的空间是保留的,没有什么意义。如果我们要让十号引脚为上拉模式,我们只需把ODR10给高电平即可,如果是给低电平那就是下拉。

它除了配置上下拉之外还可以输出高低电平,只要给对应的位置1那就是给高电平,给0就是低电平。

 ④IDR(端口输入数据寄存器)

        

 

         该寄存器是用于读取IO口的高低电平的,IDR4就代表四号引脚。

⑤BSRR(端口位设置/清除寄存器)

        

        我们根据文字可知,对这个寄存器写0并没有什么用,不会产生影响。只有写1才行,它是用来 设置ODR寄存器的值。这个寄存器可以分成两组,一组0-15,一组16-31。如果我们想让十号引脚输出高电平,则0-15中的BS10写1。而如果我们想让十号引脚输出低电平,则在16-31这个组中,对BS10写1,那么就会让其输出高电平。

BRR和LCKR这两个寄存器用得比较少,这里不做介绍。

        那我们就可以总结这些寄存器是干什么的。

        CRL与CRH是用来配置工作模式与输出速度。

        IDR是用来输入数据。ODR是输出数据。

        BSRR和BRR是设置ODR的值。

        LCKR用来锁住CRL和CRH,不让值改变。

        我们可以发现BSRR和ODR都可以达到改变输出的作用,那么我们建议最好用BSRR来输出,因为使用ODR,在读和写修改访问之间产生中断时,可能会有风险,BSRR则没有风险。因为ODR可读也可写,而BSRR是只能写不能读,因此在安全性上,BSRR就略胜ODR一筹。

接下来我们就介绍GPIO常用的库函数。

①缺省初始化,恢复为上电后的默认状态

//缺省初始化,缺省就是默认的意思
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
//使用案例
GPIO_DeInit(GPIOA);

②复用引脚的缺省初始化

void GPIO_AFIODeInit(void);

③引脚初始化,需要建一个结构体才可以使用

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
//使用案例
GPIO_InitTypeDef GPIO_InitStructure;//使用初始化需要建立一个结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//将引脚设置为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;//指定哪个引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//指定引脚的输出速度
GPIO_Init(GPIOA,&GPIO_InitStructure);//选择GPIOA内的引脚

④直接给结构体成员赋默认值

void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
//使用案例
GPIO_StructInit(&GPIO_InitStruct);

⑤读取一个输入引脚的高低电平

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//使用案例
//如果读取到GPIOA的0号引脚为低电平则num自增。
uint8_t num = 0;
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0)
{
    num++;
}

⑥读取GPIO这个群落的16个输入引脚的电平

uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);

⑦读取一个输出引脚的高低电平

uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

⑧读取整个16个输出引脚的高低电平

uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

⑨指定一个GPIO的某个引脚是高电平

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//使用案例,让GPIOA的0号引脚置高电平
GPIO_SetBits(GPIOA, GPIO_Pin_0);

 ⑩指定一个GPIO的某个引脚是低电平

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

11.指定一个GPIO的某个引脚是低电平还是高电平

void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
//使用案例,让GPIOA的0号引脚置低电平
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, 0);//或者可以这么写
//使用案例,让GPIOA的0号引脚置高电平
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, 1);//或者可以这么写

12.指定一个GPIO的所有引脚是低电平还是高电平

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

13.锁定引脚的配置不被改变

void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

14.指定一个GPIO的某个引脚作为事件输出引脚

void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

15.使能或失能事件输出

void GPIO_EventOutputCmd(FunctionalState NewState);

16.改变指定引脚的映射

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

17.选择引脚作为外部中断线

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

18.以太网设置

void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);

那么如何初始化我们的引脚,以下是代码示例

void main()
{ 
//GPIO的引脚都是挂靠在APB2时钟树下的
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//第一步:使能RCC时钟,不然没用
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
    while(1)
    {
        //填写功能
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值