视频链接:[1-1] 课程简介_哔哩哔哩_bilibili
STM32F103c8t6引脚定义表:(橙色是电源相关的引脚,蓝色是最小系统相关的引脚。绿色的是IO口和功能引脚,FT意为能容忍5V电压,主功能是上电默认的功能,默认复用功能是IO口上连接的外设功能引脚,加粗的引脚是能直接用的,没加粗的引脚可能要进行其他配置才能使用)
3-1 GPIO输出
GPIO叫通用输入输出端口,有八种配置模式
这里解释一下各种模式(原文章:STM32 GPIO八种输入输出模式 - 知乎 ):
1. GPIO_MODE_AIN 模拟输入
输入信号不经施密特触发器直接接入,输入信号为模拟量而非数字量,其余输入方式输入数字量。
2. GPIO_MODE_IN_FLOATING 浮空输入
输入信号经过施密特触发器接入输入数据存储器。当无信号输入时,电压不确定。因为浮空输入既高阻输入,可以认为输入端口阻抗无穷大,这样可以检测到微弱的信号。(相当于电压表测电压,如果电压表内阻不够大而外部阻抗比较大,则电压表分压会比较小)。此时输入高电平即高电平,输入低电平即低电平。但是外界没有输入时输入电平却容易受到外界电磁以及各种玄学干扰的影响。如按键采用浮空输入,则在按键按下时输入电平为低,但是当松开按键时输入端口悬空,外界有微弱的干扰都会被端口检测到。此时端口可能高,也可能低。
3. GPIO_MODE_IPD 下拉输入
浮空输入在外界没有输入时状态不确定,可能对电路造成干扰。为了使得电路更加稳定,不出现没有输入时端口的输入数据被干扰 (比如手碰一下电压就发生变化)。这时就需要下拉电阻(或上拉电阻),此电阻与端口输入阻抗相比仍然较小。有输入信号时端口读取输入信号,无输入信号时端口电平被拉到低电平(高电平)。
4. GPIO_MODE_IPU 上拉输入
上拉输入与下拉输入类似,只是无输入信号时端口电平被拉到高电平。例如按键信号,当按下时输入低电平,松开时电平被拉到高电平。这样就不会出现按键松开时端口电平不确定的情况。即不知道时按下还是松开。
5. GPIO-MODE_OUT_OD 开漏输出
开漏输出即漏极开路输出。这种输出方式指场效应管漏极开路输出。需要接上拉电阻才能输出1。漏极经上拉电阻接到电源,栅极输出0时,场效应管截止(阻抗无线大),电压被分到场效应管上,此时输出为1。当栅极输出1时,场效应管导通,输出端口相当于接地,此时输出0。开漏输出高电平时是由外接电源输出的,因此可以实现高于输出端口电压的输出。可以实现电平的转换。开漏输出可以实现线与功能,方法是多个输出共接一个上拉电阻。但是漏极开路输出上升沿慢,因为上升沿是外接电源对上拉电阻以及外接负载充电。当上拉电阻较大或者负载容性较大时时间常数较大,充电较慢。需要较快反映时可以采用下降沿触发,此时没有电阻接入,电路的时间常数较小,充电较快。
6. GPIO_MODE_OUT_PP 推挽输出
推挽输出既可以输出1,又可以输出0。但是无法调节输出电压,因为输出高低电平均为三极管输入端电压,此电压在由芯片内部供电,无法改变。推挽输出任意时刻只有一路工作。上图为输出高电平时电路工作状态。只有三极管导通电阻,无外接电阻。因此推挽输出损耗小、速度快。
7. GPIO_MODE_AF_OD 复用开漏输出
STM32单片机内部有其他的外设,比如定时器、DAC等。复用开漏输出与普通开漏输出区别在于,开漏输出输出的是输出数据寄存器中的数据,复用开漏输出输出的是来自外设的数据。
8. GOIO_MODE_AF_PP 复用推挽输出
复用推挽输出原理与复用开漏输出原理相同
官方GPIO相关库函数说明:
下面是标准库有关GPIO的所有函数,可在"stm32f10x_gpio.h"中查看。
//重置函数,将GPIO的所有配置重置,恢复为上电状态
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
//重置函数,将AFIO的所有配置重置,恢复为上电状态
void GPIO_AFIODeInit(void);
//GPIO的初始化函数,在GPIO相关时钟使能后,通过这个函数配置GPIO的各种参数
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
//将传入的结构体的所有变量重置,以方便再配置其他端口
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
//位读取GPIO的输入信号,直接读取指定的端口的信号,返回高SET或低RESET
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//读取GPIOx的全部端口,返回寄存器中的16位数据
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
//位读取GPIO输出信号,直接读取指定的GPIO的输出信号,返回高SET或低RESET
//当我们不知道当前GPIO输出什么信号,可以用这个函数读取
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//读取GPIOx的全部输出信号,返回寄存器中的16位数据
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
//直接拉高指定端口的电平状态
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//直接拉低指定端口的电平状态
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//直接对指定端口写入高或低电平
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
//对GPIOx写入16位的数据来控制全部端口,参数PortVal以十六进制输入
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
//锁定指定端口的配置,防止再被修改
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//用于配置 GPIO 引脚作为事件输出,以便与其他外设进行事件触发或通知
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
//用于启用或禁用GPIO事件输出功能
void GPIO_EventOutputCmd(FunctionalState NewState);
//用来进行引脚重映射的函数,当引脚的默认功能与我们的规划有冲突时,可以将一些引脚重映射
//参数1是重映射的方式,参数2是新的状态
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
//配置AFIO的数据选择器,以此来选择我们想要的中断引脚
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
//以太网相关
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);
3-2 LED闪烁&LED流水灯&蜂鸣器
想要驱动GPIO外设,分三个步骤。使能时钟RCC,初始化GPIO,调函数置高低电平
//任务1 LED闪烁 接PA0低电平点亮
void task01()
{
//初始化APB2总线时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//初始化GPIO,初始化GPIOA的PA0
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
}
}
//任务2 LED流水灯 接A0~A7八颗灯珠,低电平点亮
void task02()
{
//使能PB2总线时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//初始化GPIOA A0~A7
GPIO_InitTypeDef GPIO_initstructure;
GPIO_initstructure.GPIO_Pin = GPIO_Pin_All;
GPIO_initstructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_initstructure);
while(1)
{
GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 0010
Delay_ms(500);
GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 0100
Delay_ms(500);
GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 1000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0001); //0000 0000 0001 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0001); //0000 0000 0010 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0001); //0000 0000 0100 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0001); //0000 0000 1000 0000
Delay_ms(500);
}
}
//任务3 蜂鸣器叫唤 接PB12,低电平触发,有源蜂鸣器
void task03()
{
//使能PB2时钟总线
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//初始化GPIOB12
GPIO_InitTypeDef GPIO_Initstructure;
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_12;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_Initstructure);
while(1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(500);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(500);
}
}
3-3 GPIO输入
3-4 按键控制LED&光敏传感器控制蜂鸣器
使用模块化编程,当遇到多外设时,将每个设备的代码分成.c和.h分开来写,有利于使代码结构清晰,也便于移植。
mian.c
#include "stm32f10x.h" // Device header
#include "key.h"
#include "led.h"
#include "buzz.h"
#include "light_sensor.h"
//按键控制LED亮灭&光敏传感器控制蜂鸣器buzz叫唤,模块化编写
//任务1,按键控制LED亮灭
//按键为B1 B11,按下低电平 灯为A1 A2低电平点亮
void task01()
{
LED_Init();
KEY_Init();
while(1)
{
uint8_t key_temp = Read_Key();
if(key_temp == 1)
{
LED_Turn(LED1_Pin);
}
else if(key_temp == 2)
{
LED_Turn(LED2_Pin);
}
}
}
//任务2,光敏传感器控制蜂鸣器buzz交换,有光不叫,无光叫唤
//蜂鸣器为B12,低电平叫 光敏传感器二值化,接B13
void task02()
{
Buzz_Init();
Init_Light_Sensor();
uint8_t temp = 0;
while(1)
{
temp = Read_LightSensor();//无光回0,有光回1
if(temp == 0)//无光,buzz启动
{
Buzz_ON();
}
else if(temp == 1)//有光,buzz关闭
{
Buzz_OFF();
}
else
{
}
}
}
KEY.c
#include "Device/Include/stm32f10x.h" // Device header
#include "delay.h"
//初始化按键IO口
void KEY_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
//读取按键,按键1按下就返回1,按键2按下就返回2
uint8_t Read_Key(void)
{
uint8_t temp = 0;
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
Delay_ms(10);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0);
temp = 1;
return temp;
}
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0)
{
Delay_ms(10);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0);
temp = 2;
return temp;
}
}
KEY.h
#ifndef _KEY_H_
#define _KEY_H_
void KEY_Init(void);
uint8_t Read_Key(void);
#endif
Delay.c
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
Delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
LED.c
#include "Device/Include/stm32f10x.h" // Device header
#include "led.h"
//初始化LED的io口,A1和A2设置为推挽输出
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
//设定LED亮灭,参数1填入选中LED,参数2填入设定值
void LED_Set(uint8_t LEDx_Pin,uint8_t RESET_OR_SET)
{
if(RESET_OR_SET == SET)
{
GPIO_SetBits(GPIOA,LEDx_Pin);
}
else if(RESET_OR_SET == RESET)
{
GPIO_ResetBits(GPIOA,LEDx_Pin);
}
}
//设置LED翻转
void LED_Turn(uint8_t LEDx_Pin)
{
if(GPIO_ReadOutputDataBit(GPIOA,LEDx_Pin) == 0)
{
GPIO_SetBits(GPIOA,LEDx_Pin);
}
else
{
GPIO_ResetBits(GPIOA,LEDx_Pin);
}
}
LED.h
#ifndef _LED_H_
#define _LED_H_
#define LED1_Pin GPIO_Pin_1
#define LED2_Pin GPIO_Pin_2
#define SET 1
#define RESET 0
void LED_Init(void);
void LED_Set(uint8_t LEDx_Pin,uint8_t RESET_OR_SET);
void LED_Turn(uint8_t LEDx_Pin);
#endif
buzz.c
#include "Device/Include/stm32f10x.h" // Device header
#include "buzz.h"
//初始化蜂鸣器buzz
void Buzz_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_12);//默认设为高电平,防止蜂鸣器叫唤
}
//开启蜂鸣器buzz
void Buzz_ON(void)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}
//关闭蜂鸣器buzz
void Buzz_OFF(void)
{
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
buzz.h
#ifndef _BUZZ_H_
#define _BUZZ_H_
void Buzz_Init(void);
void Buzz_ON(void);
void Buzz_OFF(void);
#endif
light_sensor.c
#include "Device/Include/stm32f10x.h" // Device header
#include "light_sensor.h"
//初始化传感器的io口
void Init_Light_Sensor()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
//读取光敏传感器的值,无光返回0,有光返回1
uint8_t Read_LightSensor()
{
uint8_t temp = 0;
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13) == 0)//有光
{
temp = 1;
return temp;
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13) == 1)//无光
{
temp = 0;
return temp;
}
return 3;
}
light_sensor.h
#ifndef _LIGHT_SENSOR_H_
#define _LIGHT_SENSOR_H_
void Init_Light_Sensor(void);
uint8_t Read_LightSensor(void);
#endif