1、芯片的引脚分布
STM32F103C8T6这款芯片一共有48个引脚,他们分为了特殊功能引脚和普通的IO引脚。其中特殊功能的引脚有如下几个:
如图:被红色框住的就是特殊功能的引脚,包括了3个供电引脚,晶振,复位,下载,启动配置。而被黑色框住的就是普通功能引脚,未被特化,可编程。
其中普通功能引脚一共有35个,我们将他分为3组:分别为PA,PB,PC。其中PA(0~15)一共16个引脚,PB(0~15)一共16个引脚,PC(13~15)一共3个引脚。我们通过编程实现这35个引脚实现输入输出进而实现需要的功能。其中具体引脚功能如下图所示:
STM32F103C8T6的最小系统板的引脚图所示,其中被红色框住的就是普通的引脚IO口,用于编程。
2、引脚输出
最小系统板的引脚功能图所示。引脚的输出分为通用功能,复用功能,和重映射功能。其中输入没有分为这些功能,==输入分为上拉输入,下拉输入,浮空输入,模拟输入
==。
- 通用功能输出: 引脚就是一个普通的IO口,由芯片CPU直接控制它的输出高电平/低电平。
- 复用功能输出: STM32中不同的片上外设的功能引脚固定在不同的输入输出引脚上面。例如串口通信1的输入Rx为PA10,输出引脚Tx为PA10。我们需要使用串口通信1时,引脚初始化就不再是配置成通用模式,而是需要配置为复用模式。
- 复用重映射功能输出: 一个引脚可能连接着多个片上外设。==例如:需要使用串口通信时(USART1),就要使用PA9和PA10。使用定时器TIM1时,也要使用PA9和PA10。当我们想要同时使用串口通信1和定时器TIM1时,就需要重映射一个片上外设的引脚。
1、通用输出推挽模式
通用输出推挽模式的引脚结构如下图所示:
程序写1时:上面的开关闭合,下面的开关打开,上面PMOS导通,输出一个高电平。
程序写0时:上面的开关打开,下面的开关闭合,上面NMOS导通,输出一个低电平。
总结:程序写1,输出高电平。程序写0,输出低电平;所以0和1都有驱动引脚的能力。
2、通用输出开漏模式
通用输出开漏模式的引脚结构如下图所示:
如图:开漏模式时,上面的PMOS一直都是断开的状态。
当程序写0时:下面的NMOS闭合,输出一个低电平。
当程序写1时:下面的NMOS也断开,引脚输出是一个高阻抗状态。
总结:程序写1时,引脚为高阻抗,程序写0时,引脚输出低电平;所以只有0才有驱动能力【注】当引脚为高阻抗时,引脚也可以作为输入状态。
3、引脚输入
1、上拉输入
上拉/下拉输入模式的引脚结构如下图所示:
当引脚工作在上拉模式时:引脚默认为上拉电阻的开关闭合,下拉电阻开关断开。
当IO端口悬空时: 引脚默认输入高电平1,寄存器IDR读取为1
当IO端口输入0时:下拉开关闭合,引脚输入低电平0,寄存器IDR读取为0
当IO端口输入1时:引脚输入高电平1,寄存器IDR读取为1
2、下拉输入
当工作在下拉模式时:上拉电阻的开关断开,下拉电阻开关闭合。
当IO端口悬空时: 引脚默认输入低电平0,寄存器IDR读取为0
当IO端口输入0时:引脚输入低电平0,寄存器IDR读取为0
当IO端口输入1时:引脚输入高电平1,寄存器IDR读取为1
3、浮空输入模式:
当工作在浮空模式时:上拉电阻的开关断开,下拉电阻开关断开。
当IO端口输入0时:引脚输入为低电平0,寄存器IDR读取为0
当IO端口输入1时:引脚输入为高电平1,寄存器IDR读取为1
当IO端口悬空时: 寄存器IDR读取不确定,容易被外部电磁波干扰
4、模拟输入模式:
主要作用于片上外设ADC(模数转换)
4、引脚最大输出速度
最大输出速度:IO允许输出电平的最大切换频率。当输出寄存器写1时,引脚不会立马变为高电平,它有个缓冲时间(图中的上升时间和下降时间)。而这个时间我们可以通过程序来改变,一般有3个挡位。
而STM32的最大输出速度有3个挡位:2MHZ,10MHZ,50MHZ。频率最大,上升时间和下降时间越小,输出速度就越快,功耗就越大。
5、LED流水灯
通过第一章的学习,我们已然知晓了LED的点亮和熄灭的方式,下面学习流水灯的制作流程。流水灯呈现的样子:就是第一个LED灯点亮,延迟一段时间,第一个LED灯熄灭+第二个LED灯点亮,延迟一段时间,第二个LED灯熄灭+第三个LED灯点亮…依次循环下去。其中延迟一段时间就是让单片机不做任何与LED灯有关的事情,这里使用延迟函数来实现。
在工程目录里面新建Driver文件夹用来保存配置片上外设的代码文件,如下图所示:
LED连接的实物图如下图所示:
①LED.c文件的代码如下:
#include "stm32f10x.h"
#include "LED.h"
/*
* @Function:PA0~PA7引脚选择性的初始化
* @para:对应的GPIOA的引脚PA_0~PA_7
*/
void LED_Init(uint32_t PA_x)
{
/* 1. 打开GPIOA的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* 2. 将PA0~PA7配置为通用推挽输出模式:CNF = 00,MODE = 11(50MHz) */
if(PA_x != PA_ALL)//引脚部分初始化
{
GPIOA->CRL |= PA_x;
GPIOA->CRL &= ~(PA_x << 2);
}
else//引脚全部初始化
{
GPIOA->CRL |= PA_ALL;
GPIOA->CRL &= PA_ALL;
}
}
/*
* @Function:输出1点亮LED灯
* @para:对应的GPIOA的引脚输出值LED0~LED7
*/
void LED_ON(uint16_t LEDx)
{
GPIOA->ODR |= LEDx;
}
/*
* @Function:熄灭LED灯
* @para:对应的GPIOA的引脚输出值LED0~LED7
*/
void LED_OFF(uint16_t LEDx)
{
GPIOA->ODR &= ~LEDx;
}
/*
* @Function:翻转LED灯
* @para:对应的GPIOA的引脚输出值LED0~LED7
*/
void LED_Turn(uint16_t LEDx)
{
/* 如果是关闭那么就打开,如果是打开那么就关闭 */
if((GPIOA->ODR & LEDx) == 0)//灯关闭
{
LED_ON(LEDx);
}else{
LED_OFF(LEDx);
}
}
②LED.h文件的代码如下:
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
#define PA_0 0x00000003 //GPIO_CRH_MODE0
#define PA_1 0x00000030 //GPIO_CRH_MODE1
#define PA_2 0x00000300 //GPIO_CRH_MODE2
#define PA_3 0x00003000 //GPIO_CRH_MODE3
#define PA_4 0x00030000 //GPIO_CRH_MODE4
#define PA_5 0x00300000 //GPIO_CRH_MODE5
#define PA_6 0x03000000 //GPIO_CRH_MODE6
#define PA_7 0x30000000 //GPIO_CRH_MODE7
#define PA_ALL 0x33333333 //GPIO_CRH_MODE
#define LED0 GPIO_ODR_ODR0
#define LED1 GPIO_ODR_ODR1
#define LED2 GPIO_ODR_ODR2
#define LED3 GPIO_ODR_ODR3
#define LED4 GPIO_ODR_ODR4
#define LED5 GPIO_ODR_ODR5
#define LED6 GPIO_ODR_ODR6
#define LED7 GPIO_ODR_ODR7
void LED_Init(uint32_t PA_x);
void LED_ON(uint16_t LEDx);
void LED_OFF(uint16_t LEDx);
void LED_Turn(uint16_t LEDx);
#endif
③主函数main.c文件的代码如下:
#include "stm32f10x.h"
#include "Delay.h"
#include "LED.h"
#define timer 500
/*
LED流水灯,LED灯的负极连接着GND,正极连接着单片机的引脚(PA0~PA4)
一共5个LED灯,引脚输出1点亮,输出0熄灭
*/
int main(void)
{
LED_Init(PA_ALL);//将所有的引脚都配置为通用输出推挽模式
while(1)
{
LED_ON(LED0);//点亮LED0
Delay_ms(timer);
LED_OFF(LED0);//熄灭LED0
LED_ON(LED1);//点亮LED1
Delay_ms(timer);
LED_OFF(LED1);//熄灭LED1
LED_ON(LED2);//点亮LED2
Delay_ms(timer);
LED_OFF(LED2);//熄灭LED2
LED_ON(LED3);//点亮LED3
Delay_ms(timer);
LED_OFF(LED3);//熄灭LED3
LED_ON(LED4);//点亮LED4
Delay_ms(timer);
LED_OFF(LED4);//熄灭LED4
}
}
代码优化如下:
#include "stm32f10x.h"
#include "Delay.h"
#include "LED.h"
#define timer 500
/*
LED流水灯,LED灯的负极连接着GND,正极连接着单片机的引脚(PA0~PA4)
一共5个LED灯,引脚输出1点亮,输出0熄灭
*/
int main(void)
{
LED_Init(PA_ALL);//将所有的引脚都配置为通用输出推挽模式
while(1)
{
for(uint8_t i = 0; i<5 ;i++)
{
LED_ON(LED0 << i);
Delay_ms(timer);
LED_OFF(LED0 << i);
}
}
}
实物演示效果如下:
流水灯
代码优化2:制作来回流水灯
#include "stm32f10x.h"
#include "Delay.h"
#include "LED.h"
#define timer 500
/*
LED流水灯,LED灯的负极连接着GND,正极连接着单片机的引脚(PA0~PA4)
一共5个LED灯,引脚输出1点亮,输出0熄灭
*/
int main(void)
{
LED_Init(PA_ALL);//将所有的引脚都配置为通用输出推挽模式
while(1)
{
/*
代码改进:来回流水灯
*/
for(uint8_t i = 0; i<4 ;i++)
{
LED_ON(LED1 << i);
Delay_ms(timer);
LED_OFF(LED1 << i);
}
for(uint8_t i = 0; i<4 ;i++)
{
LED_ON(LED3 >> i);
Delay_ms(timer);
LED_OFF(LED3 >> i);
}
}
}
演示效果如下:
VID_20241004_105203
6、按键控制LED
使用按键控制则需要按键检测,检测引脚的输入输入电平,所以需要用到端口输入寄存器GPIO_IDR,如下图所示:
所以被检测的引脚要配置为输入模式。
实物连接如下图所示:
①Key.c文件的代码如下:
#include "stm32f10x.h"
#include "Delay.h"
/*
* @Function:按键引脚(GPIOB0)的初始化
*/
void Key_Init(void)
{
//1、开始GPIOB的时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
//2、配置引脚GPIOB0的输入模式:上拉输入,MODE0 = 00,CNF0 = 10,ODR0 = 1。
GPIOB->CRL &= ~GPIO_CRL_MODE0;
GPIOB->CRL &= ~GPIO_CRL_CNF0_0;
GPIOB->CRL |= GPIO_CRL_CNF0_1;
GPIOB->ODR |= GPIO_ODR_ODR0;//表示上拉模式
}
/*
* @Function:按键按下并返回一个值
*/
uint8_t Key_Num(void)
{
uint8_t Number = 0;
//引脚配置为上拉输入,则默认输入为高电平,按键按下由高电平变为低电平
if((GPIOB->IDR & GPIO_IDR_IDR0)!= 0)//代表按键没有按下
{
Delay_ms(5);//软件消抖
if((GPIOB->IDR & GPIO_IDR_IDR0)== 0)//代表按键按下
{
Number = 1;
}
}
return Number;
}
②LED.c文件代码如下:
#include "stm32f10x.h"
#include "LED.h"
/*
* @Function:PA0~PA7引脚选择性的初始化
* @para:对应的GPIOA的引脚PA_0~PA_7
*/
void LED_Init(uint32_t PA_x)
{
/* 1. 打开GPIOA的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* 2. 将PA0~PA7配置为通用输出开漏模式 */
if(PA_x != PA_ALL)
{
GPIOA->CRL |= PA_x;
GPIOA->CRL &= ~(PA_x << 2);
}else{
GPIOA->CRL |= PA_ALL;
GPIOA->CRL &= PA_ALL;
}
}
/*
* @Function:输出1点亮LED灯
* @para:对应的GPIOA的引脚输出值LED0~LED7
*/
void LED_ON(uint16_t LEDx)
{
GPIOA->ODR |= LEDx;
}
/*
* @Function:熄灭LED灯
* @para:对应的GPIOA的引脚输出值LED0~LED7
*/
void LED_OFF(uint16_t LEDx)
{
GPIOA->ODR &= ~LEDx;
}
/*
* @Function:翻转LED灯
* @para:对应的GPIOA的引脚输出值LED0~LED7
*/
void LED_Turn(uint16_t LEDx)
{
/* 如果是关闭那么就打开,如果是打开那么就关闭 */
if((GPIOA->ODR & LEDx) == 0)//关闭
{
LED_ON(LEDx);
}else{
LED_OFF(LEDx);
}
}
③主函数main.c文件的代码如下:
#include "stm32f10x.h"
#include "Delay.h"
#include "LED.h"
#include "Key.h"
/*
按键控制LED
*/
int main(void)
{
LED_Init(PA_0); //将GPIOA0引脚都配置为通用输出推挽模式
Key_Init(); //初始化GPIOB0
LED_OFF(LED0); //关闭LED0
while(1)
{
if(Key_Num() == 1)//代表按键按下
{
LED_Turn(LED0);//LED灯翻转
}
}
}
演示效果如下:
按键控制LED