STM32
GPIO通用输入输出口
GPIO:8种输入输出模式
浮空输入 | 可读取引脚电平,若引脚悬空,电平不确定 |
上拉输入 | 可读取引脚电平,内部接上拉电阻,悬空时默认为高电平 |
下拉输入 | 可读取引脚电平,内部接下拉电阻,悬空时默认为低电平 |
模拟输入 | GPIO无效,引脚直接接入内部ADC |
开漏输出 | 可输出引脚电平,高电平为高阻态,低电平接VSS |
推挽输出 | 可输出引脚电平,高电平接VDD,低电平接VSS |
复用开漏输出 | 由片上外设提供,高电平为高阻态,低电平接VSS |
复用推挽输出 | 由片上外设提供,高电平接VDD,低电平接VSS |
GPIO: 引脚电平在0-3.3v之间,部分可以容忍5v的电平
GPIO:在输出模式下可以控制端口输出高低电平,驱动外部设备
GPIO:输入模式下可以读取端口高低电平和电压,获取外部输入的数据
GPIO:函数
复位GPIO | void GPIO_DeInit(GPIO_TypeDef* GPIOx); |
复位AFIO | void GPIO_AFIODeInit(void); |
初始化GPIO | void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); |
结构体初始化 | void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct); |
获取输入数据寄存器中的一位 | uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); |
获取输入数据寄存器中的所有数据 | uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); |
获取输出数据寄存器中的某一位 | uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); |
读取输出数据寄存器中的所有值 | uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); |
把指定的端口设置为高电平 | void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); |
GPIO 把指定的端口设置为低电平 | void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); |
GPIO写入数据位,根据第三个参数的值设置指定的端口 | void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); |
第一个参数指定端口,第二个参数可以同时对16个端口进行写入操作 | void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal); |
RCC:时钟
开启AHB外设时钟控制 | void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState); |
开启RCCAPB2外设时钟控制 | void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState); |
开启RCCAPB1外设时钟控制 | void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState); |
GPIO外设控制
1:第一步开启RCCAPB2外设时钟控制
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
2: 初始化GPIO结构体
GPIO_InitTypeDef GPIO_InitStructure; //初始化GPIO结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO引脚,赋值为第0号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
#if
GPIO8种输入输出模式
typedef enum
{
GPIO_Mode_AIN = 0x0, // 模拟输入
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入
GPIO_Mode_IPD = 0x28, //上拉输入
GPIO_Mode_IPU = 0x48, //下拉输入
GPIO_Mode_Out_OD = 0x14, //开漏输出
GPIO_Mode_Out_PP = 0x10, //推挽输出
GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
GPIO_Mode_AF_PP = 0x18 //复用推挽输出
}GPIOMode_TypeDef;
// GPIO的引脚每一个GPIO口有16个引脚
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
// GPIO输入输出的频率
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
#endif
3:初始化GPIO
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO:ResetBits控制灯的闪烁
接线图工程模版
#include "stm32f10x.h" // Device header
#include "Delay.h"
// 操作GPIO 1: 使用rcc开启gpio时钟
// 使用GPIO_Init函数初始化GPIO
// 使用输入或输出函数控制GPIO口
int main(void){
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// GPIO的结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 选择输出模式为推挽输出
// 选择输出模式为推挽输出PP表示的是推挽输出模式,OD表示开漏输出模式
// 推挽输出高电平和低电平均有输出能力,开漏输出自由在低电平的情况下才有驱动能力
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 选择输出的引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 输出速度
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO初始化结构体的地址放到GPIO——Init的第二个参数
GPIO_Init(GPIOA,&GPIO_InitStructure);
// GPIO设置A0口的灯亮,最后一个参数Bit_RESET表示的是清除端口的值为0,清除端口的值为1
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);// Bit_RESET时LED灯是点亮状态,当为Bit_SET时LED灯是熄灭状态
while(1){
// 实现led灯光闪烁的功能需要在主循环中写上点亮led灯熄灭led灯光的操作并在while中循环
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(500);
// 添加延时函数进行延时
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(500);
/* 使用GPIO点亮led灯
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
*/
}
}
GPIO:控制LED灯闪烁
#include "stm32f10x.h" // Device header
#include "Delay.h"
// 操作GPIO 1: 使用rcc开启gpio时钟
// 使用GPIO_Init函数初始化GPIO
// 使用输入或输出函数控制GPIO口
int main(void){
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// GPIO的结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 选择输出模式为推挽输出
// 选择输出模式为推挽输出PP表示的是推挽输出模式,OD表示开漏输出模式
// 推挽输出高电平和低电平均有输出能力,开漏输出自由在低电平的情况下才有驱动能力
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 选择输出的引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 输出速度
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO初始化结构体的地址放到GPIO——Init的第二个参数
GPIO_Init(GPIOA,&GPIO_InitStructure);
// GPIO设置A0口的灯亮,最后一个参数Bit_RESET表示的是清除端口的值为0,清除端口的值为1
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);// Bit_RESET时LED灯是点亮状态,当为Bit_SET时LED灯是熄灭状态
while(1){
// 实现led灯光闪烁的功能需要在主循环中写上点亮led灯熄灭led灯光的操作并在while中循环
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(500);
// 添加延时函数进行延时
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(500);
/* 使用GPIO点亮led灯
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
*/
}
}
GPIO:控制蜂鸣器发声
#include "stm32f10x.h" // Device header
#include "Delay.h"
/*
1: 操作GPIO 1: 使用rcc开启gpio时钟
2: 使用GPIO_Init函数初始化GPIO
3: 使用输入或输出函数控制GPIO口
*/
int main(void){
// 开启GPIOA时钟,该程序连接的是GPIOA的端口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
// GPIO的结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 选择输出模式为推挽输出PP表示的是推挽输出模式,OD表示开漏输出模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 选择输出的引脚,GPIO_Pin_All将16个端口都设置为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
// 输出速度
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO初始化结构体的地址放到GPIO——Init的第二个参数
GPIO_Init(GPIOB,&GPIO_InitStructure);
while(1){
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(700);
}
}
STM32库函数使用方式
第一种方式是,打开头文件,拉到最下面,查看有哪些函数,然后查看函数的定义函数的使用方法是
第二种方式,查看库函数用户手册,所有函数的介绍个使用方法
第三种方式,最后一种方式是百度,借助别人的代码进行使用
GPIO:LED流水灯
步骤:1:开启RCCAPB2外设时钟控制
2:初始化GPIO结构体
3: 初始化GPIO
接线原理图
代码实现
#include "stm32f10x.h" // Device header
#include "Delay.h"
/*
1: 操作GPIO 1: 使用rcc开启gpio时钟
2: 使用GPIO_Init函数初始化GPIO
3: 使用输入或输出函数控制GPIO口
*/
uint16_t arr[16] = {0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080};
int main(void){
// 函数的声明
void LED_Run(void);
void LED_All(void);
// 开启GPIOA时钟,该程序连接的是GPIOA的端口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// GPIO的结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 选择输出模式为推挽输出PP表示的是推挽输出模式,OD表示开漏输出模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 选择输出的引脚,GPIO_Pin_All将16个端口都设置为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
// 输出速度
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO初始化结构体的地址放到GPIO——Init的第二个参数
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1){
//LED_Run();
LED_All();
}
}
void LED_Run(void){
//控制A号引脚,对引脚的位置进行按位取反
GPIO_Write(GPIOA,~0x0001); // 0000 0000 0000 0001 二进制转换为16进制的写法
Delay_ms(500);
GPIO_Write(GPIOA,~0x0002); // 0000 0000 0000 0010 二进制转换为16进制的写法
Delay_ms(500);
GPIO_Write(GPIOA,~0x0004); // 0000 0000 0000 0100 二进制转换为16进制的写法
Delay_ms(500);
GPIO_Write(GPIOA,~0x0008); // 0000 0000 0000 1000 二进制转换为16进制的写法
Delay_ms(500);
GPIO_Write(GPIOA,~0x0010); // 0000 0000 0001 0000 二进制转换为16进制的写法
Delay_ms(500);
GPIO_Write(GPIOA,~0x0020); // 0000 0000 0010 0000 二进制转换为16进制的写法
Delay_ms(500);
GPIO_Write(GPIOA,~0x0040); // 0000 0000 0100 0000 二进制转换为16进制的写法
Delay_ms(500);
GPIO_Write(GPIOA,~0x0080); // 0000 0000 1000 0000 二进制转换为16进制的写法
Delay_ms(500);
}
void LED_All(void){
// for循环
int len = sizeof(arr)/sizeof(arr[0]);
int i = 0;
for(i = 0; i< len; i++){
GPIO_Write(GPIOA,~arr[i]); // 0000 0000 0000 0001 二进制转换为16进制的写法
Delay_ms(500);
}
}
GPIO:按键控制LED亮灭
步骤:1:创建LED.C文件和LED.H文件
2: 创建KEY.H文件和KEY.C文件
3:开起RCC外设时钟控制
4: 分别初始化GPIO
5: 编写实现功能函数
接线图
KEY.C文件
#include "stm32f10x.h" // Device header
#include "Delay.h"
void Key_Init(void)
{
// RCCAPB2外设时钟控制,开启GPIOB的外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 1; //置键码为1
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 2; //置键码为2
}
return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
KEY.H文件
#ifndef __KEY_H__
#define __KEY_H__
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
LED.C文件
#include "stm32f10x.h" // Device header
/**
* 函 数:LED初始化
* 参 数:无
* 返 回 值:无
*/
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); //设置PA1和PA2引脚为高电平
}
/**
* 函 数:LED1开启
* 参 数:无
* 返 回 值:无
*/
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为低电平
}
/**
* 函 数:LED1关闭
* 参 数:无
* 返 回 值:无
*/
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为高电平
}
/**
* 函 数:LED1状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为低电平
}
}
/**
* 函 数:LED2开启
* 参 数:无
* 返 回 值:无
*/
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为低电平
}
/**
* 函 数:LED2关闭
* 参 数:无
* 返 回 值:无
*/
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为高电平
}
/**
* 函 数:LED2状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED2_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为低电平
}
}
LED.H文件
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED1_Turn(void);
void LED2_ON(void);
void LED2_OFF(void);
void LED2_Turn(void);
#endif
main.c文件
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
uint8_t KeyNum; //定义用于接收按键键码的变量
int main(void)
{
LED_Init(); //LED初始化
Key_Init(); //按键初始化
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
LED1_Turn(); //LED1翻转
}
if (KeyNum == 2) //按键2按下
{
LED2_Turn(); //LED2翻转
}
}
}
GPIO:光敏传感器控制蜂鸣器
步骤:1:分别初始化蜂鸣器和光敏电阻的GPIO,并开启RCC外设手时钟控制的时钟
2:编写函数实现在光线较暗的情况下蜂鸣器发出声音
接线图
BUZZER.C和BUZZER.H
Buzzzer.c
#include "stm32f10x.h" // Device header
void Buzzer_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
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); //将PA1和PA2引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_12); //设置PA1和PA2引脚为高电平
}
void Buzzer_ON(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //设置PA1引脚为低电平
}
void Buzzer_OFF(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12); //设置PA1引脚为高电平
}
void Buzzer_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOB, GPIO_Pin_12); //则设置PA1引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //则设置PA1引脚为低电平
}
}
Buzzer.h
#ifndef __BUZZER_H__
#define __BUZZER_H__
void Buzzer_Init(void);
void Buzzer_ON(void);
void Buzzer_OFF(void);
void Buzzer_Turn(void);
#endif
LightSensor.c 和 LightSensor.h
LightSensor.c
#include "stm32f10x.h" // Device header
void LightSensor_Init(void){
// 初始化程序开启GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
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);
GPIO_SetBits(GPIOB, GPIO_Pin_13);
}
// 编写返回端口值的函数
uint8_t LightSensor_Get(void){
return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13);
}
LightSensor.h
#ifndef __LIGHT_SENSOR_H__
#define __LIGHT_SENSOR_H__
void LightSensor_Init(void);
uint8_t LightSensor_Get(void);
#endif
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"
int main(void)
{
// 初始化蜂鸣器
Buzzer_Init();
// 初始化光敏传感器
LightSensor_Init();
while (1)
{
if(LightSensor_Get() == 1){ // 光线比较暗的情况下
Buzzer_ON(); // 蜂鸣器鸣叫
}else{
Buzzer_OFF();
}
}
}
EXTI中断
中断的定义:
在主程序运行的过程中,出现特定的中断触发条件(也就是中断源)使CPU暂停当前正在运行的程序转而去处理中断程序,处理完成后又返回之前暂停的程序继续执行
中断的优先等级,当有多个中断源同时申请中断时,CPU会根据当前中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。
中断嵌套:当一个中断程序正在运行时,又有一个更紧急优先级更高的中断源申请中断,GPU再次暂停当前的中断程序,转而去处理新的中断程序,处理完成后依次进行返回。
中断的执行流程
EXTI:外部中断函数
重定义初始化,恢复默认状态 | void EXTI_DeInit(void); |
中断初始化 | void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct); |
结构体初始化引出参数 | void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct); |
软件触发外部中断 | void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line); |
获取指定的中断标志位 | FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line); |
清除指定的中断标志位 | void EXTI_ClearFlag(uint32_t EXTI_Line); |
获取中断标志位 | ITStatus EXTI_GetITStatus(uint32_t EXTI_Line); |
清除中断挂起标志位 | void EXTI_ClearITPendingBit(uint32_t EXTI_Line); |
AFIO:中断引脚选择和复用功能引脚重映射
AFIO函数
复位AFIO的外设 | void GPIO_AFIODeInit(void); |
锁定GPIO配置 | void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); |
AFIO事件输出配置(使用不多) | void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource); |
AFIO的事件输出功能,使用不多 | void GPIO_EventOutputCmd(FunctionalState NewState); |
配置引脚重映射,第一个是重映射的方式,第二个参数是新的状态 | 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); |
1:AFIO中断引脚选择:是一个中断引脚选择器可以在前面GPIO外设的16个引脚中选择一个连接到EXTI边沿检测及控制中所以
2:相同的Pin不能同时触发中断,因为PA0,PB0,PC0通过AFIO选择后只有其中一个可以连接到EXTI的通道0上,PB1,PC1等也只有一个可以接入EXTI上
复用功能引脚重映射
NVIC:嵌套中断向量控制器->用于统一分配中断优先级和管理中断
NVIC函数:
选择中断分组,参数为中断分组的方式 | void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); |
根据结构体里面指定的参数初始化NVIC | void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct); |
设置中断向量表 | void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset); |
系统低功耗配置 | void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState); |
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource); |
NVIC的基本结构
NVIC优先级
def : nvic的优先级可以由优先级寄存器的4为决定,可以对这4位进行切分,分为高N为的抢占式优先级和低n位的响应式优先级(4-n),抢占式优先级可以嵌套中断,响应式优先级高的可以优先排队,抢占式优先级和响应式优先级相同的可以按照中断号排队。
分组方式 | 抢占式优先级 | 响应式优先级 |
0 | 0 取值为0 | 4取值为0-15 |
1 | 1取值为0-1 | 3取值为0-7 |
2 | 2取值为0-3 | 2取值为0-3 |
3 | 3取值为0-7 | 1取值为0-1 |
4 | 4取值为0-15 | 0 取值为0 |
响应式优先级:也可以称为插队式优先级哪个优先级高优先处理哪个
抢占式优先级:优先级高的可以优先被处理,相当于CPU可以暂时中断当前处理的程序,优先处理优先级更高的程序,该程序执行完成后再执行原先没有执行完毕的程序,也可以称之为嵌入式中断优先级
EXTI:对射式红外传感器统计次数
步骤:
1: 开启RCCAPB和RCCAFIO中断引脚控制
2: 配置GPIO输入输出
3: 配置EXTI外部中断
4: 配置NVIC(嵌套中断向量控制器)
接线图:
CounterSensor.c
#include "stm32f10x.h"
uint16_t CountSensor_Count;
void CountSensor_Init(void){
// 开启RCC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
// 配置AFIO中断引脚选择
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
// 初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
// 引出GPIO参数
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_14 ;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
// 初始化GPIO
GPIO_Init(GPIOB,&GPIO_InitStructure);
// AFIO中断引脚选择
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
// 初始化中断引脚选择
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
// NVIC中断分组,配置抢占式优先级和响应式优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// NVIC配置
// 初始化NVIC结构体
NVIC_InitTypeDef NVIC_InitStructure;
// 将NVIC的结构体全都引出来
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // 配置NVIC中断线
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE ; //使能NVIC中断线
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1 ; // 配置NVIC的抢占式优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 配置NVIC的响应式优先级
}
// 获取传感器计数的值
uint16_t CountSensor_Get(void){
return CountSensor_Count;
}
//编写中断函数
void EXTI15_10_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line14) == SET){ // 判断是外部中断14号线发生中断
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14) == 0){
CountSensor_Count ++; // 每次触发一次中断自动增加一次
}
// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
CounterSensor.h
#ifndef __COUNT_SENSOR_H__
#define __COUNT_SENSOR_H__
void CountSensor_Init(void);
void EXTI15_10_IRQHandler(void);
uint16_t CountSensor_Get(void);
#endif
KEY.C
#include "stm32f10x.h" // Device header
#include "Delay.h"
// 按键初始化
void Key_Init(void){
// 开启RCCAPB外设时钟控制
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
// 初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
//配置GPIO输入输出模式
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU ;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
// 初始化GPIO
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
// 按键控制
uint8_t Key_GetNum(void){
uint8_t KeyNum = 0;
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0){
// 延时消除按键的抖动
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0);
Delay_ms(20);
KeyNum =1;
}
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0);
Delay_ms(20);
KeyNum =1;
}
return KeyNum;
}
KEY.H
#ifndef __KEY_H__
#define __KEY_H__
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CounterSensor.h"
int main(void)
{
// 模块初始化
OLED_Init();
CountSensor_Init();
// 显示静态字符串
OLED_ShowString(1,1,"Count:");
while (1)
{
OLED_ShowNum(1,7,CountSensor_Get(),5);
}
}
EXTI:外部中断旋转编码器计次
旋转编码器简介
旋转编码器原理:输出一个正交波形,两个波形之间相差的角度为90度,识别编码器是正向选装还是反向旋转。
旋转编码器的硬件电路
接线图
步骤:
1:RCC开启APB2与AFIO时钟
2:开启GPIO
3:配置外部中断EXTI
4:配置NVIC
5:使能NVIC
Encoder.c 代码
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;
void Encoder_Init(void){
// 开启GPIO和AFIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
// 开启GPIO时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0 | GPIO_Pin_1 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
// 配置中断引脚
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line =EXTI_Line0 | EXTI_Line1 ;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger =EXTI_Trigger_Falling;
// 配置NVIC中断引脚控制
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置中断分组
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
// 配置中断通道
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}
// 使用中间变量对选装编码器的计次进行初始化
int16_t Encoder_Get(void){
int16_t temp;
temp = Encoder_Count;
Encoder_Count = 0;
return temp;
}
// 第一个中断
void EXTI0_IRQHandler(void){
//判断中断是否发生
if(EXTI_GetITStatus(EXTI_Line0) == SET){
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
{
Encoder_Count --; //此方向定义为反转,计数变量自减
}
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET) //判断是否是外部中断1号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) //PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
{
Encoder_Count ++; //此方向定义为正转,计数变量自增
}
}
EXTI_ClearITPendingBit(EXTI_Line1); //清除外部中断1号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
Encoder.h代码
#ifndef __ENCODER_H__
#define __ENCODER_H__
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
main.c代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"
int16_t Num; //定义待被旋转编码器调节的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Encoder_Init(); //旋转编码器初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
while (1)
{
Num += Encoder_Get(); //获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
OLED_ShowSignedNum(1, 5, Num, 5); //显示Num
}
}
编译后没有发生错误
TIM定时器
定时器基本概念:
定时器是计算机系统中的一种重要的计时和事件触发机制。它是一种硬件或软件组件,用于按照预定的时间间隔执行任务、触发事件或生成中断。
定时器的主要功能包括:
1. 计时功能:定时器可以通过内部或外部的时钟源来测量时间间隔。它可以提供毫秒、微秒、纳秒等不同精度的计时。
2. 事件触发功能:定时器可以设置一个固定的时间间隔,当时间间隔到达时,就会触发预先定义的事件,例如发送一个中断信号、执行特定的任务或调用一个回调函数。
3. 周期性执行任务:定时器可以反复执行同一个任务,例如定期执行一个程序或周期性地发送数据。这对于需要按固定时间间隔进行重复操作的应用程序非常有用。
4. 超时检测:定时器可以用于超时检测,例如在网络通信中,可以设置一个定时器来检测数据包是否在预定的时间内到达,如果没有到达,则可以重新发送数据包或采取其他应对措施。
5. 节能功能:在一些移动设备和嵌入式系统中,定时器可以用于管理功耗。通过定时器,设备可以在空闲或非活动状态下进入省电模式,并在预定的时间间隔后自动唤醒。
定时器可以由硬件实现,如专用的定时器芯片或计时器模块,也可以由操作系统或编程语言提供的定时器库函数进行软件实现。
总之,定时器在计算机系统中扮演着重要的角色,用于计时、事件触发、周期性执行任务和超时检测等应用。它们在各个领域中都有广泛的应用,包括操作系统、通信、网络、嵌入式系统、游戏开发等。
定时器使用到的库函数
恢复定时器缺省配置 | void TIM_DeInit(TIM_TypeDef* TIMx); |
时基单元初始化 | void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); |
初始化时基单元结构体 | void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); |
使能计数器 | void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); |
使能中断输出信号 | void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState); |
选择内部时钟 | void TIM_InternalClockConfig(TIM_TypeDef* TIMx); |
选择ITR其他定时器时钟 | void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource); |
选择TIX捕获通道的时钟 | void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter); |
选择外部时钟模式一输入时钟 | void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter); |
选择外部时钟模式2时钟 | void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter); |
配置引脚的极性,滤波等时钟 | void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter); |
单独写预分频的值,第一个参数是 写入的预分频值,第二个参数是写入的模式 | void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode); |
改变计数器的时钟模式 | void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_tTIM_CounterMode); |
配置自动重装器预装功能 | void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState); |
给计数器写入一个值 | void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter); |
获取当前计数器的值 | uint16_t TIM_GetCounter(TIM_TypeDef* TIMx); |
给自动重装器写入一个值 | void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload); |
获取当前计数器的值 | uint16_t TIM_GetCounter(TIM_TypeDef* TIMx); |
获取当前预分频器的值 | uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx); |
获取标志位和清除标志位函数
// 获取标志位和清除标志位
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);// 清除标志位
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);// 获取中断标志位
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);// 清除中断标志位
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
定时器的基本类型
基本定时器框图
基本定时器的理解
通用定时器程序框图
….
定时器的基本结构
计数器的计数频率公式:
计数器计数频率 : ck_cnt = ck-psc /(psc + 1)
计数器溢出频率: ck-cnt_ov = ck-psc /(psc + 1) / (arr + 1)
定时器:TIM编码器接口
def:编码器接口定义
1: 编码器接口可以接收增量(正交)编码器信号,根据编码器产生的正交脉冲信号自动控制CNT自增或自减,从而指示编码器的位置,旋转方向和旋转速度。
2:每个高级定时器和通用定时器都拥有一个编码器接口
3:两个通用的引脚分别借用了输入捕获通道1和输入捕获通道二
正交编码器波形
中间相差大概90度
正向旋转
边沿 | 另一相状态 |
A相上升沿 | B相低电平 |
A相下降沿 | B相高电平 |
B相上升沿 | A相高电平 |
B相下降沿 | A相低电平 |
反向旋转
边沿 | 另一相状态 |
A相上升沿 | B相高电平 |
A相下降沿 | B相低电平 |
B相上升沿 | A相低电平 |
B相下降沿 | A相高电平 |
旋转编码器计数:正转是向上计数,反转是向下计数
编码器接口的基本结构
1:不反向根据高低电平输出波形
2:反向取反高低电平反转
配置GPIO的输入模式:
配置GPIO的输入模式是可以看接在这个引脚的外部模块输出电平,如果外部模块默认高电平就选择上拉模式,如果外部模块默认低电平就选择下拉模式,配置引脚的电平和外部的默认模式保持一致
定时器:旋转编码器计数编码
接线图
Encoder.c代码
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;
void Encoder_Init(void){
// RCC开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
// 开起AFIO中断引脚控制
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
// GPIO初始化
GPIO_InitTypeDef GPIO_InitStruce;
// 选择模式
GPIO_InitStruce.GPIO_Mode =GPIO_Mode_IPU;
// 选择引脚
GPIO_InitStruce.GPIO_Pin =GPIO_Pin_0 | GPIO_Pin_1 ;
// 选择时钟频率
GPIO_InitStruce.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化GPIO
GPIO_Init(GPIOB,&GPIO_InitStruce);
// 将外部中断0号线映射到GPIOB,选择PB0作为中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
// 将外部中断1号线映射到GPIOB,选择PB1作为中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
/*
中断初始化
*/
// 定义结构体变量
EXTI_InitTypeDef EXTI_InitStructure;
// 选择外部中断0号线和1号线
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
// 使能外部中断线
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
// 选择外部中断的模式
EXTI_InitStructure.EXTI_Mode =EXTI_Mode_Interrupt ;
// 选择外部中断的触发方式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
// 初始化外部中断
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
// 选择NVIC的EXTI0号线
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
// 使能外部中断线
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
// 使能外部中断线
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}
int16_t Encoder_Get(void){
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}
void EXTI0_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line0) == SET){
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0){
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
{
Encoder_Count --; //此方向定义为反转,计数变量自减
}
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET) //判断是否是外部中断1号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) //PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
{
Encoder_Count ++; //此方向定义为正转,计数变量自增
}
}
EXTI_ClearITPendingBit(EXTI_Line1); //清除外部中断1号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
Encoder.h
#ifndef __ENCODER_H__
#define __ENCODER_H__
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
main.c代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"
int16_t Num; //定义待被旋转编码器调节的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Encoder_Init(); //旋转编码器初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
while (1)
{
Num += Encoder_Get(); //获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
OLED_ShowSignedNum(1, 5, Num, 5); //显示Num
}
}
定时器: 定时器外部时钟
第一步:开启APB2和APB1外设时钟控制
第二步:初始化GPIO口
第三步:配置外部时钟
第四步:时基单元初始化
第五步:配置嵌套中断向量控制器
1:RCCAPB2开启GPIO和TIM时钟
// 开启GPIOA外设时钟控制
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// 开启定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
2:初始化GPIO时钟
// 初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
// 输入模式选择上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
// 选择GPIOA的第0号引脚PA0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 配置GPIO的时钟频率
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
// 使能GPIO时钟
GPIO_Init(GPIOA,&GPIO_InitStructure);
3:配置外部时钟
// 外部时钟配置,配置时基单元的时钟源
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0f);
// 第一个参数表示当前使用的定时器
// 第二个参数表示选择预分频,此处表示不分频
// 第三个参数表示上升沿触发还是下降沿触发
// 第四个参数表示滤波的大小
4:时基单元初始化(时基单元的配置包括,CNT计数器,ARR重装计数器,PSC预分频器)
// 时基单元初始化:配置预分频器,重装计数器,计数器
TIM_TimeBaseInitTypeDef TIM_InitStructure;
// 配置时钟是否分频
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 配置时钟的计数模式
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 配置重装计数器ARR
TIM_InitStructure.TIM_Period =10 - 1 ;
// 配置预分频
TIM_InitStructure.TIM_Prescaler =1 - 1 ;
// 配置重复计数器,高级定时才会使用到
TIM_InitStructure.TIM_RepetitionCounter = 0;
// 初始化时基单元
TIM_TimeBaseInit(TIM2,&TIM_InitStructure);
// 配置中断输出控制,清除更新中断标志位
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//使能定时器更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
5:配置NVIC(嵌套中断向量控制器)
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
// 选择NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
// 指定的NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE ;
// 指定NVIC的中断线NVIC优先级
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 指定NVIC的中断线NVIC响应式优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
// 将结构体变量交给NVIC_Init,配置NVIC外设
NVIC_Init(&NVIC_InitStructure);
// 使能定时器
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
函数统计定时器中断的计数值
// 看计数器的值
uint16_t Timer_GetCounter(void){
// 返回定时器TIM2的计数CNT
return TIM_GetCounter(TIM2);
}
定时器c语言文件Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
// 开启GPIOA外设时钟控制
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// 开启定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
// 初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
// 输入模式选择上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
// 选择GPIOA的第0号引脚PA0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 配置GPIO的时钟频率
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
// 使能GPIO时钟
GPIO_Init(GPIOA,&GPIO_InitStructure);
// 外部时钟配置,配置时基单元的时钟源
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0f);
// 第一个参数表示当前使用的定时器
// 第二个参数表示选择预分频,此处表示不分频
// 第三个参数表示上升沿触发还是下降沿触发
// 第四个参数表示滤波的大小
// 时基单元初始化:配置预分频器,重装计数器,计数器
TIM_TimeBaseInitTypeDef TIM_InitStructure;
// 配置时钟是否分频
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 配置时钟的计数模式
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 配置重装计数器ARR
TIM_InitStructure.TIM_Period =10 - 1 ;
// 配置预分频
TIM_InitStructure.TIM_Prescaler =1 - 1 ;
// 配置重复计数器,高级定时才会使用到
TIM_InitStructure.TIM_RepetitionCounter = 0;
// 初始化时基单元
TIM_TimeBaseInit(TIM2,&TIM_InitStructure);
// 配置中断输出控制,清除更新中断标志位
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//使能定时器更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
// 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
// 选择NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
// 指定的NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE ;
// 指定NVIC的中断线NVIC优先级
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 指定NVIC的中断线NVIC响应式优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
// 将结构体变量交给NVIC_Init,配置NVIC外设
NVIC_Init(&NVIC_InitStructure);
// 使能定时器
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
// 看计数器的值
uint16_t Timer_GetCounter(void){
// 返回定时器TIM2的计数CNT
return TIM_GetCounter(TIM2);
}
定时器头文件Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
uint16_t Timer_GetCounter(void);
#endif
编写main函数验证定时器
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
// 初始化oled
OLED_Init();
// 初始化定时器
Timer_Init();
// 使用OLED显示字符串
OLED_ShowString(1,1,"Num:");
OLED_ShowString(2,1,"CNT:");
while (1)
{
OLED_ShowNum(1, 5, Num, 5);
OLED_ShowNum(2, 1, Timer_GetCounter(), 5);
}
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
编译结果展示
PWM输出比较
这部分的内容是比较重要的,如果想要在实际的应用中使用驱动舵机或者是直流电机这部分的内容必须很好的掌握
输出比较(OC Output Compare)
输出比较 | OC Output Compare |
输入捕获 | IC Input Compare |
输入捕获输出比较单元 | CC Compare/Capture |
CC表示的意思是捕获比较,CCR表示的是捕获比较寄存器
注:
1: 输出比较可以通过比较CNT(计数器)与CCR寄存器之间的关系,来对输出的电平进行设置为1设置为0的操作或者翻转操作或者用于输出具有一定占空比的PWM波形
2:每个高级定时器和通用定时器都有4个输出比较单元
3:每个高级定时器的前三个通道都拥有死区生成和互补输出功能(可以理解为刹车功能)
PWM简介
PWM:脉冲宽度调制在具有惯性的系统中通过一系列脉冲宽度进行调制,来等效的获得所需要的模拟量,常用于电机控制速度等领域。
PWM(脉冲宽度调制的参数)
占空比等效于PWM模拟出来的电压的多少,占空比越大等效出的模拟电压越趋近于高电平,占空比越小等效出来的模拟电压越趋近于低电平,分辨率表示的是占空比变化的精细程度,按照实际项目的需求使用PWM波形可以在数字系统等效输出模拟量,可以实现LED控制亮度和控制电机的速度等操作 。
计算公式
1: 频率 = 1/Ts
2: 占空比 = Ton / Ts
占空比决定了等效出来的模拟电压大小,占空比越大等效出来的模拟电压就越接近于高电平,占空比越小等效出来的模拟电压越接近于低电平
3: 分辨率 = 占空比变化的步距
分辨率表示占空比变化的细腻程度如:1% , 2% ,3%那么占空比的分辨率为1%
将占空比设置为50%时表示高低电平的时间是一样的,即是高低电平所占的比例是一样的
占空比为20%表示高电平的时间占20%其余的都是低电平的时间
通用输出比较通道
CCR:捕获比较寄存器
CNT:计数器
高级输出比较通道(了解即可)
输出模式控制器如何工作(重要)
PWM的基本结构
PWM(脉冲宽度调制)外设应用简介
电机驱动模块的硬件电路
PWM库函数简介
输出比较模块实用到的库函数
// 配置输出比较模块
//第一个参数的意思是选择定时器,第二个参数的意思是配置结构体
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct); // 给输出比较结构体赋值一个默认值
配置输出模式的库函数
// 配置强制输出模式
void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
CCR寄存器配置函数
//配置CCR寄存器的预装功能:就是影子寄存器的意思,就是你写入的值不会立即生效而是在更新事件之后才能生效
void TIM_CCPreloadControl(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
快速使能函数配置
// 配置快速使能:用的不是很多不需要掌握
void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
PWM极性设置函数
// 单独设置输出比较的极性:带N的是高级定时器里互补通道的配置,在结构体初始化部分也可以设置级性,这里的极性是单独设置的,结构体初始化部分是一起修改的结果是一样的。
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
单独修改使能输出参数
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
输出比较模式函数选择:单独修改输出比较模式
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);
单独更改CCR寄存器值的函数,比较重要(在运行时修改占空比需要使用到这4个函数)
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
STM32F103引脚定义参考表
PWM(脉冲宽度调制)实战检验LED呼吸灯
DEMO1:PWM实现呼吸灯
/*
步骤: PWM如何配置
1: RCC开启时钟,将TIM外设和GPIO外设的时钟打开
2: 配置时基单元,包括PSC预分频器,CNT计数器,ARR自动重装器
3: 配置输出比较单元,包括CCR的值输出比较模式,级性选择,输出使能等
4: 配置GPIO吧PWM对应的GPIO口初始化为复用推挽输出的配置
*/
占空比的计算注:72M = 72000000
PWM.C文件
#include "stm32f10x.h" // Device header
/*
步骤: PWM如何配置
1: RCC开启时钟,将TIM外设和GPIO外设的时钟打开
2: 配置时基单元,包括PSC预分频器,CNT计数器,ARR自动重装器
3: 配置输出比较单元,包括CCR的值输出比较模式,级性选择,输出使能等
4: 配置GPIO吧PWM对应的GPIO口初始化为复用推挽输出的配置
*/
void PWM_Init(void){
// 开启TIM2通用定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
// RCC开启GPIO时钟和定时器TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// 初始化GPIO的结构体
GPIO_InitTypeDef CPIO_InitStructure;
// 使用复用开漏输出,由片上外设提供
CPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
CPIO_InitStructure.GPIO_Pin =GPIO_Pin_0 ;
CPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&CPIO_InitStructure);
// 初始化时基单元,配置时钟源,选择TIM2的内部时钟如果不设置默认也是使用内部时钟
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
// 设置分频,是机分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 设置计数模式,是向上计数还是向下计数
TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up ;
// ARR计数周期(重装计数器)
TIM_TimeBaseInitStructure.TIM_Period =100-1 ; // 设置频率为1000的50%占空比分辨率为1%的pwm波形
// 预分频器psc的值
TIM_TimeBaseInitStructure.TIM_Prescaler =720-1 ;
// 重复计数器高级定时器才有
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
// 初始化时基单元
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
// PWM(脉冲宽度调制) 输出比较初始化
TIM_OCInitTypeDef TIM_OCInitStructure;
//输出比较中如果结构体没有完整被赋值最好给出一个初始化的值避免一些问题
TIM_OCStructInit(&TIM_OCInitStructure);
// 输出比较的模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 输出比较的极性
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 输出使能
TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable ;
// 初始CCR的值
TIM_OCInitStructure.TIM_Pulse =0 ;
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
// 定时器使能
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
void PWM_SetCompare1(uint16_t Compare){
// 设置CCR1的值
TIM_SetCompare1(TIM2,Compare);
}
PWM.H文件
#ifndef __PWM_H_
#define __PWM_H_
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif
main.c文件调用
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint8_t i; //定义for循环的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
PWM_Init(); //PWM初始化
while (1)
{
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
Delay_ms(10); //延时10ms
}
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
Delay_ms(10); //延时10ms
}
}
}
PWM(脉冲宽度调制)PWM驱动舵机
PWM.C代码:该部分代码对psc以及arr的值做出修改
#include "stm32f10x.h" // Device header
void PWM_Init(void){
// 开启时钟,这里TIM2是通用寄存器
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
// GPIO初始化代码
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
// 使用复用开漏推挽输出模式,这里的定时器使用的是片上外设的功能
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
// 选择时基单元的时钟,选择内部时钟的模式,定时器默认使用的是内部单元的时钟
TIM_InternalClockConfig(TIM2);
// 配置时基单元,初始化结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
// 将结构体成员都引用出来放置在这个位置
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 配置参数是否分屏
TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up; // 选择计数的模式选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 20000 -1; // 表示ARR自动重装器的值,这两个参数的取值都要在0-65535之间
TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; // PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器的值
// 初始化结构体并将结构体的地址放置在init函数中
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
// 给结构体赋初始值
TIM_OCStructInit(&TIM_OCInitStructure);
// 设置输出比较的模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 设置输出比较的极性,选择高极性
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 设置输出使能,输出状态
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable ;
//设置CCR,设置ccr寄存器的值
TIM_OCInitStructure.TIM_Pulse = 0; // CCR
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare2(uint16_t Compare){
TIM_SetCompare2(TIM2,Compare);
}
PWM.H代码
#ifndef __PWM_H_
#define __PWM_H_
void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);
#endif
SERVO.C部分代码
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Servo_Init(void){
// 初始化PWM底层
PWM_Init();
}
// 设置舵机的角度0 500,180 2500,输入角度/180 = x /2500 ---> 输入角度 / 180 * 2500 = x
// 求取一个线性函数,y =kx+b 已知(0,500)和(180,2500)求取k和b的值
void Servo_SetAngle(float Angle){
// 调用PWM_SetCompare2,计算参数
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
SERVO.H代码
#ifndef __SERVO_H_
#define __SERVO_H_
void Servo_Init(void);
void Servo_SetAngle(float Angle);
#endif
main函数部分代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "KEY.H"
uint8_t i;
// 按键键码
uint8_t KeyNum;
float Angle;
int main(void)
{
// 初始化oled
OLED_Init();
Servo_Init();
// 初始化按键
Key_Init();
// OLED显示角度的值
OLED_ShowString(1,1,"Angle:");
while (1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1){
Angle += 30;
if(Angle > 180){
Angle = 0;
}
}
Servo_SetAngle(Angle);
OLED_ShowNum(1, 7, Angle, 3);
}
}
PWM(脉冲宽度调制)PWM驱动直流电机
PWM.C部分代码
#include "stm32f10x.h" // Device header
void PWM_Init(void){
// 开启时钟,这里TIM2是通用寄存器
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
// GPIO初始化代码
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
// 使用复用开漏推挽输出模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
// 选择时基单元的时钟,选择内部时钟的模式,定时器默认使用的是内部单元的时钟
TIM_InternalClockConfig(TIM2);
// 配置时基单元,初始化结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
// 将结构体成员都引用出来放置在这个位置
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 配置参数是否分屏
TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up; // 选择计数的模式选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 -1; // 表示ARR自动重装器的值,这两个参数的取值都要在0-65535之间
TIM_TimeBaseInitStructure.TIM_Prescaler = 36-1; // PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器的值
// 初始化结构体并将结构体的地址放置在init函数中
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
// 给结构体赋初始值
TIM_OCStructInit(&TIM_OCInitStructure);
// 设置输出比较的模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 设置输出比较的极性,选择高极性
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 设置输出使能,输出状态
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable ;
//设置CCR,设置ccr寄存器的值
TIM_OCInitStructure.TIM_Pulse = 0; // CCR
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare3(uint16_t Compare){
TIM_SetCompare3(TIM2,Compare);
}
PWM.H代码
#ifndef __PWM_H_
#define __PWM_H_
void PWM_Init(void);
void PWM_SetCompare3(uint16_t Compare);
#endif
MOTOR.C代码
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Motor_Init(void){
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 初始化电机方向控制角度
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
PWM_Init();
}
// 设置直流电机速度的函数
void Motor_SetSpeed(int8_t Speed){
// 判断电机是正转还是反转
if(Speed >= 0){
// 控制方向角度
GPIO_SetBits(GPIOA,GPIO_Pin_4); // 通过电机的两个引脚控制电机是正方向旋转还是反方向旋转
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
// 控制直流电机的速度
PWM_SetCompare3(Speed);
}else{
// 控制电机反转
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
GPIO_SetBits(GPIOA,GPIO_Pin_4);
// 控制直流电机的速度
PWM_SetCompare3(-Speed);
}
}
MOTOR.H代码
#ifndef __MOTOR_H
#define __MOTOR_H
void Motor_Init(void);
// 设置直流电机速度的函数
void Motor_SetSpeed(int8_t Speed);
#endif
MAIN.代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Key.h"
uint8_t KeyNum; //定义用于接收按键键码的变量
int8_t Speed; //定义速度变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Motor_Init(); //直流电机初始化
Key_Init(); //按键初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Speed:"); //1行1列显示字符串Speed:
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
Speed += 20; //速度变量自增20
if (Speed > 100) //速度变量超过100后
{
Speed = -100; //速度变量变为-100
//此操作会让电机旋转方向突然改变,可能会因供电不足而导致单片机复位
//若出现了此现象,则应避免使用这样的操作
}
}
Motor_SetSpeed(Speed); //设置直流电机的速度为速度变量
OLED_ShowSignedNum(1, 7, Speed, 3); //OLED显示速度变量
}
}
PWM输入捕获
输入捕获def
输入捕获简称IC全程Input Capture(输入捕获)
输入捕获模式下当通道输入引脚出现指定的电平跳变时,当CNT计数器的值将被锁存到CCR(寄存器中)用于测量PWM波形,频率,占空比,脉冲间隔,点平的持续时间等
输入捕获:检测引脚电平跳变执行动作
输出比较:引脚是输出端口,输入捕获:引脚是输入端口
输入捕获电路原理图
输入捕获和输出比较测试方法
注:
1 —> 测频法在闸门的时间T内,对上升沿计次,得到N的频率则频率 Fx = N / T
2 —> 测周法两个上升沿内以标准的频率Fc计次,得到N则频率 Fx = Fc / N
3 —> 中界频率:测频法与测周法误差相等的频率点Fm = 根号下Fc / T
STM32频率的测量:高频适合使用的方法是测频法,低频适合使用的是测周法,(其中使用测频法测量频率比较稳定,使用测周法测量频率的方式没有这么稳定,因为测周法只会通过一次的测量就能得出结果所以测试出来的频率波动相对较大)在测量频率的过程中会存在误差,所以当N的值越大的时候误差越小的
输入捕获主从触发模式
主从触发模式解读
输入捕获基本结构图
PWM基本结构
输入捕获接线图
输入捕获函数
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct); | 结构体配置输入捕获单元 |
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct); | 初始化输入捕获单元,可以配置两个通道 |
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct); | 给输入捕获结构体赋一个初始值 |
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource); | 选择输入触发源TRGI |
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource); | 选择输出触发源TRGO |
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode); | 选择从模式 |
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC); | 分别单独配置通道 1 2 3 4 分频器 |
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC); | |
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC); | |
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC); | |
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx); | 分别读取4个通道的CCR |
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx); | |
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx); | |
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx); | |
输出比较模式下:CCR是只写的,要使用SetCompare写入 | |
输入捕获模式下:CCR是只读的,要使用GetCapture读出 |
输入捕获代码实战
IC.C代码
#include "stm32f10x.h"
// 初始化的步骤 1: RCC开启时钟将GPIO和TIM的时钟开启
// GPIO初始化将GPIO初始化为输入模式一般为上拉输入或者是浮空输入
// 第三步:配置时基单元让CNT计数器在内部时钟的驱动下进行自增
// 第四步:配置输入捕获单元包括输入,极性,直连通道还是交叉通道,分频参数等
// 第五步:选择从模式的触发源触发源选择为TI1FP1,使用调用库函数的方式给一个参数
// 第六步:选择触发之后执行的操作执行reset操作,使用库函数的方式实现
// 第七步:调用TIM_CMD函数开启定时器
void IC_Init(void){
// 开启时钟,这里TIM2是通用寄存器
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
// 使用复用开漏推挽输出模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
// 选择时基单元的时钟,选择内部时钟的模式,定时器默认使用的是内部单元的时钟
TIM_InternalClockConfig(TIM3);
/*
PWM频率的公式:== 更新频率 = 72M/(PSC+1)/(ARR+1)
*/
// 配置时基单元,初始化结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
// 将结构体成员都引用出来放置在这个位置
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 配置参数是否分屏
TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up; // 选择计数的模式选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 65536 -1; // 表示ARR自动重装器的值,这两个参数的取值都要在0-65535之间
TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; // PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器的值
// 时基单元初始化使用TIM3
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
/*
初始化输入捕获单元:初始化输入捕获单元的结构体
*/
// 初始化结构体变量
TIM_ICInitTypeDef TIM_ICInitStructure;
// 选择输入捕获的通道
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
// 输入捕获的滤波器,增加滤波器的参数可以滤除更多的干扰
TIM_ICInitStructure.TIM_ICFilter = 0xF;
// 选择极性,所谓的极性指的就是电平的高低,也就是上升沿触发还是下降沿触发
TIM_ICInitStructure.TIM_ICPolarity =TIM_ICPolarity_Rising;
// 配置触发信号分频器,不分频就是每隔一次都有效,1分频就是每隔一次有效
TIM_ICInitStructure.TIM_ICPrescaler =TIM_ICPSC_DIV1;
// 触发信号从那个引脚输入,配置数据选择器,这里选择的就是直接连接的通道
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 配置触发源,配置TRGI的触发源为TI1FP1,
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
// 选择的是从模式
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
// 启动定时器,使能定时器
TIM_Cmd(TIM3,ENABLE);
}
// 读取CCR寄存器
uint32_t IC_GetFreq(void){
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
IC.H
#ifndef __IC_H
#define __IC_H
void IC_Init(void);
uint32_t IC_GetFreq(void);
#endif
MAIN.C
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"
int main(void)
{
// 初始化oled
OLED_Init();
PWM_Init();
IC_Init();
OLED_ShowString(1, 1, "Freq:00000Hz");
PWM_SetPrescaler(720-1); // 计算频率的公式,Freq = 72M / (PSC + 1) / (ARR + 1)/100
PWM_SetCompare1(50); // 计算占空比的公式 Duty = CCR / 100
// 输入捕获代码
while (1)
{
OLED_ShowNum(1,6,IC_GetFreq(),5);
}
}
此处PWM沿用的是上面的PWM代码不做一一展示
TIM旋转编码器接口
编码器接口简介
正交编码器(正交波形)
旋转编码器简介
编码器接口基本结构
旋转编码器的工作模式
工作模式1:不反向
工作模式2:反向
编码器接口测试速度
接线图如下所示
根据结构流程编写旋转编码器计次代码
Encoder.c部分代码
#include "stm32f10x.h" // Device header
// 1: RCC开启时钟,开启GPIO和定时器的时钟
// 2: 配置GPIO将PA6和PA7配置为输入模式
// 3: 配置时基单元,选择不分频的方式,自动重装给到最大65535
// 4: 配置输入捕获单元,这里的输入捕获单元只有滤波器和极性两个单元有用和编码器没有关系
// 5: 配置编码器接口模式
// 6: 调用TIM_CMD启动定时器即可
// 编写初始化函数
void Encoder_Init(void){
// RCC开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
// 开启定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
// GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
// 配置输入模式为上拉输入
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU ;
// 配置引脚为CPIOA_7 和 GPIOA_6引脚
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6 | GPIO_Pin_7 ;
// 配置时钟的频率
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化GPIO
GPIO_Init(GPIOA,&GPIO_InitStructure);
// 时基单元初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
// 时钟分频选择不分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 配置计数模式选择向上计数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 配置计数周期,也就是重装计数器ARR的值这里选择配置为最大值
TIM_TimeBaseInitStructure.TIM_Period =65536 -1;
// 配置预分频器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
// 配置从装计数器,高级定时器才会用到
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
// 输入捕获单元初始化,定义输入捕获的结构体变量
TIM_ICInitTypeDef TIM_ICInitStructure;
// 给输入捕获的结构体变量赋值一个默认的值,避免可能存在的问题
TIM_ICStructInit(&TIM_ICInitStructure);
// 配置输入捕获的通道
TIM_ICInitStructure.TIM_Channel =TIM_Channel_1 ;
// 配置输入捕获的滤波,这里设置为最大值
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //选择配置定时器通道2
TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
#if 0
第一个参数表示的是定时器,选择的是哪一个定时器
第二个参数表示旋转编码器接口的那一个模式,是向上计数,还是向下计数,还是上下都计数(这里一般选择第三种)
此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置
#endif
// 配置旋转编码器接口
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
// 使能定时器
TIM_Cmd(TIM3,ENABLE);
}
int16_t Encoder_Get(void){
/*使用Temp变量作为中继,目的是返回CNT后将其清零*/
int16_t Temp;
// 使用了一个中间变量进行了置换
Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3, 0);
return Temp;
}
Encoder.h部分代码
#ifndef __ENCODER_H
#define __ENCODER_H
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
main.c部分代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"
int16_t Speed;
int main(void)
{
// 初始化oled
OLED_Init();
Timer_Init();
// 初始化定时器
Encoder_Init();
// 使用OLED显示字符串
OLED_ShowString(1,1,"Speed:");
while (1)
{
OLED_ShowSignedNum(1, 7, Speed, 5);
}
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Speed = Encoder_Get();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
编译通过
串口通信
串口(USART)
USART(串口通信协议)-CSDN博客文章浏览阅读467次,点赞5次,收藏10次。def:串口是一种应用广泛的通讯接口,串口的成本低,容易使用,通信线路简单,可以实现两个设备之间的相互通信【串口通信方式既可以同步通信也可以异步通信】def:单片机的串口可以使单片机与单片机,单片机与电脑,单片机与各式各样的模块进行通信,极大的拓展了单片机的应用范围,增强了单片机系统的硬件实力 注:注:注:图中的意思是可以有八位,第一个位是起始位,最后一位是截止位,中间的数据位数是数据位在没有进入起始位之间电平的极性【注:呈现出高电平特性】,在进入起始位后电平转换为低点平表示开始发送数据,数据呈现出8https://blog.csdn.net/qq_45973003/article/details/138001414
……
作者:_沧浪之水_