一、先看IO端口位的结构
上面部分是输入,下面是输出。
1、I/O输入:
首先,从I/O引脚开始,有两个保护二极管,主要作用是对输入电压限幅,保护内部电路。上面二极管接VDD为3.3V,下面二极管接VSS为0V。当输入电压为超过3.3V,则会导通上面的二极管;当输入电压小于0V也就是负电压,则会导通下面的二极管;如果输入电压为0~3.3V,则二极管不会导通,会正常输入。
接着,来到上拉电阻和下拉电阻。上拉电阻接的VDD,下拉电阻接的VSS,开/关是可以通过程序配置的。如果上面导通,下面断开,就是上拉输入模式;如果下面导通,上面断开,就是下拉输入模式;如果上面和下面都断开,则为浮空输入模式。上拉和下拉的作用为了给输入提供一个默认的输入电平。如果什么都不接,输入为浮空模式,输入的电平就很容易受到干扰。这里的阻值都是很大的,是一种弱上拉和弱下拉,目的是尽量不影响正常的输入操作。
接着,来到TTL肖特基触发器,实际上这个是施密特触发器,这里翻译错误。这个施密特触发器的作用是对输入电压进行整形的。它的执行逻辑是:如果输入电压大于某一阈值,输出就会瞬间升为高电平;如果输入电压小于某一阈值,输出就会瞬间降为低电平;这样可以有效地避免因信号波动造成的输出抖动现象。如下图,输入的电压不稳定,有两个阈值,高于高阈值或小于高阈值且大于低阈值都为高电平;低于低阈值或大于低阈值且小于高阈值都为低电平。
接着,经过施密特整形的波形就可直接写入数据寄存器中了,就可以直接读取高低电平了。
接着,至片上外设是连接外设的,其中有模拟输入,连接到ADC上的,因为ADC需要接收模拟量,所以是连接到施密特触发电路前面的。另一个是复用功能输入,是连接到其他需要读取端口的外设上,比如串口的输入引脚等,这根线接收的是数字量,所以在施密特触发器后面。
2、I/O输出:
首先,数字部分可以由输出数据寄存器或片上外设控制。如果选择输出数据寄存器进行控制,就是普通的IO口输出,写这个寄存器的某一位就可以操作对应的某个端口;
位设置/清除寄存器可以用来单独操作输出数据寄存器的某一位,而不影响其他位,在C语言中就是&=和|=,则在stm32中电路设置好了,只需要配置就行。如果我们要对某一位进行置1的操作(类似于|=),在位设置寄存器的对应位写1即可,剩下不需要操作的位写0,这样它内部就会有电路,自动将输出数据寄存器中对应位置为1,而剩下写0的位则保持不变;如果我们要对某一位进行置0的操作(类似于&=),在位设置寄存器的对应位写0即可,剩下不需要操作的位写1。
接着,通过输出控制,来到两个MOS管,上面为P-MOS,下面为N-MOS,这个MOS管就是一种电子开关,通过信号来控制开关的导通和关闭,开关负责将IO口接到VDD或者VSS,这里可以选择推推挽、开漏或者关闭三种输出方式。
在推挽输出模式下,P-MOS和N-MOS均有效。数据寄存器为1时,上管导通,下管断开,输出直接接到VDD,就是输出高电平;数据寄存器为0时,下管导通,上管断开,输出直接接到VSS,就是输出低电平;在这种模式下,高低电平均有较强的驱动能力,所以推挽输出模式也可以叫强推输出模式。在推挽输出模式下,stm32对IO口具有绝对的控制权,高低电平都由stm32说的算。
在开漏输出模式下,这个P-MOS是无效的,只有N-MOS在工作。数据寄存器为1时,下管断开,这时输出相当于断开,也就是高阻模式。数据寄存器为0时,下管导通,这时输出直接接到VSS,也就是输出低电平。作用:可以作为通信协议的驱动方式。另外开漏模式还可以用于输出5V的电平信号,但必须要有上拉电阻的配合,因为本身是不能输出高电平,比如在IIC总线中。
剩下一个状态就是关闭,这个是当引脚配置为输入模式的时候,两个MOS管都无效,也就是输出关闭,端口的电平由外部信号控制。一个端口只有一个输出,但可以由多个输入。
二、GPIO模式
端口可以配置成8个模式,如下:
三、枚举类型
关键字枚举:enum
用途:定义一个取值受限制的整形变量,用于限制变量取值范围;宏定义的集合,里面的值也可以被其他类型引用。其实枚举本质也就是宏定义
定义枚举变量:
enum{FALSE =0,TRUE=1} EnumName;
引用枚举成员:
EnumName =FALSE;
EnumName =TRUE;//限制了EnumName;
由于名字过长,一般用typedef进行取个名字。
typedef enum{
FALSE =0,
TRUE=1
}week_t;
然后用week_t定义枚举类型。在32中直接写具体值时必须强转化为枚举类型。
四、GPIO输出点亮LED
利用A端0口即PA0,首先配置时钟使能,GPIO初始化。
注意:PA15、PB3、PB4是调试端口,别选,如果当作普通端口,还需要配置。
用到的函数:
GPIO_InitTypeDef GPIO_InitStruct; //写在最前面
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能
//GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD; //开漏输出,没有高电平驱动那个能力
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 ; //PC13口
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; //选择50HZ就可以了
GPIO_Init(GPIOA,&GPIO_InitStruct); //GPIO初始化
输出函数:
将GPIO某端某口即某一位引脚设置为高电平
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
或
//BitAction BitVal参数为:
// Bit_SET: to set the port pin
GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
将GPIO某端某口即某一位引脚设置为低电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
或
//BitAction BitVal参数为:
//Bit_RESET: to clear the port pin
GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
延时函数:
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
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能
//GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD; //开漏输出,没有高电平驱动那个能力
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 ;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; //选择50HZ就可以了
GPIO_Init(GPIOA,&GPIO_InitStruct);
while(1)
{
GPIO_SetBits(GPIOA,GPIO_Pin_0); //设置为高电平
Delay_ms(500);
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //设置为低电平
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)1); //也可以直接写数,但必须转为枚举类型BitAction
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)0);
Delay_ms(500);
}
}
五、LED流水灯
使用的是GPIOA端0~7引脚作为流水灯的输出引脚。
可以利用如下函数,可直接向GPIO端口写入值,也就是多位数据。
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)
main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_All; //PA所有引脚
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; //选择50HZ就可以了
GPIO_Init(GPIOA,&GPIO_InitStruct);
while(1)
{
GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 0001,写入值
Delay_ms(100);
GPIO_Write(GPIOA,~0x0002); //0000 0000 0000 0010
Delay_ms(100);
GPIO_Write(GPIOA,~0x0004); //0000 0000 0000 0100
Delay_ms(100);
GPIO_Write(GPIOA,~0x0008); //0000 0000 0000 1000
Delay_ms(100);
GPIO_Write(GPIOA,~0x0010); //0000 0000 0001 0000
Delay_ms(100);
GPIO_Write(GPIOA,~0x0020); //0000 0000 0010 0000
Delay_ms(100);
GPIO_Write(GPIOA,~0x0040); //0000 0000 0100 0000
Delay_ms(100);
GPIO_Write(GPIOA,~0x0080); //0000 0000 1000 0000
Delay_ms(100);
}
}
六、让蜂鸣器响起来
这里蜂鸣器用的有源的,自带振荡源,只需给蜂鸣器一个高电平或低电平即可。我用的蜂鸣器是高电平驱动的。
main.c,代码如下:使用的是B12引脚。
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; //选择50HZ就可以了
GPIO_Init(GPIOB,&GPIO_InitStruct);
while(1)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_12,Bit_SET);
Delay_ms(100);
GPIO_WriteBit(GPIOB,GPIO_Pin_12,Bit_RESET);
Delay_ms(100);
GPIO_WriteBit(GPIOB,GPIO_Pin_12,Bit_SET);
Delay_ms(100);
GPIO_WriteBit(GPIOB,GPIO_Pin_12,Bit_RESET);
Delay_ms(700);
}
}
七、光敏传感器控制蜂鸣器
光敏传感器使用的是DO引脚输出,只输出0和1。原理是利用的电阻分压。光暗DO输出1,光强DO输出1。DO与B13引脚相连接。
首先,利用分模块文件进行编写程序,代码便于管理和移植。
蜂鸣器代码:
Buzzer.c"
#include "stm32f10x.h" // Device header
void Buzzer_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
void Buzzer_ON(void) //响
{
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
void Buzzer_OFF(void) //不响
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}
Buzzer.h
#ifndef _BUZZER_H
#define _BUZZER_H
void Buzzer_Init(void);
void Buzzer_ON(void);
void Buzzer_OFF(void);
#endif
光敏传感器:
LightSernsor.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
void LightSensor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉电阻
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
uint8_t LightSensor_GetNum(void)
{
uint8_t LightSensorNum=0;
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0) //光暗DO输出1,光强DO输出0
{
LightSensorNum=1;
}
return LightSensorNum;
}
LightSernsor.h:
#ifndef _LIGHTSENSOR_H
#define _LIGHTSENSOR_H
void LightSensor_Init(void);
uint8_t LightSensor_GetNum(void);
#endif
main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"
uint8_t LightSensorNum=0;
int main(void)
{
Buzzer_Init();
LightSensor_Init();
while(1)
{
LightSensorNum=LightSensor_GetNum();
if(LightSensorNum==1) //光强,使蜂鸣器响
{
Buzzer_ON();
}
else
{
Buzzer_OFF();
}
}
}