STM32
建立一个新工程
步骤:
-
建立工程文件夹,Keil中新建工程,选择型号
-
工程文件夹中建立Start、Library、User文件夹,复制固件库中的文件到 工程文件夹
-
Start:STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm
- startup_xx.s -->复位中断
- 调用Systemlnit、调用main函数
- system_xx.c/.h -->定义systemlnit
- stm32f10x.h -->外设寄存器描述
- stm32f10x_adc.c/.h、misc.c/.h·····–>库函数
- core_cm3.c/.h -->内核寄存器描述
- stm32f10x_conf.h -->库函数配置
- startup_xx.s -->复位中断
-
Library:STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\inc
STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\src
-
User:STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template
- main.c–>定义main
- stm32f10x_it.c/.h -->定义中断处理函数
-
-
工程里建立Start、Library、User同名称的group,然后将文件夹内的文件添加到工程分组里
-
工程选项,C/C++,Include Paths内声明所有包含头文件的文件夹
-
工程选项,C/C++,Define内定义USE_STDPERIPH_DRIVER
-
工程选项,Debug,下拉列表选择对应调试器,Setting -->Flash Download–>Reset and Run
代码测试
#include "stm32f10x.h"
int main(void)
{
RCC->APB2ENR = 0x00000010;//IO端口C时钟使能置1开启时钟
GPIOC->CRH = 0x00300000;//PC13口模式
GPIOC->ODR = 0x00002000;//ODR13口
while(1)
{
}
}
- IOPCEN:IO端口C时钟使能 (I/O port C clock enable) 位4 由软件置’1’或清’0’
- 0:IO端口C时钟关闭;
- 1:IO端口C时钟开启。
- 端口配置高寄存器
- 21和20口为11–>输出模式,最大速度50MHz
- 23和 22口为00–>通用推挽输出模式
- 端口输出数据寄存器
- ODR13写1高电平,0低电平;下面图(8.2.4)横线部分都给0,可得到0x00002000(灯为低电平亮全为0亮,为2灭)
- ODR13写1高电平,0低电平;下面图(8.2.4)横线部分都给0,可得到0x00002000(灯为低电平亮全为0亮,为2灭)
遇到的问题
问题一:ST_LINK一直闪烁
详细可查看这篇博文https://blog.csdn.net/bean_business/article/details/109129337
解决方法:
- 安装ST_LINK驱动,安装完成后可在设备管理器中进行查看
问题二:Keil无法在非管理员身份下运行
解决方法
- 在环境变量中更改TEMP和TMP的值为默认值将“变量值”修改为 %USERPROFILE%/Local Settings/TEM
问题三:插入头文件但总是出现红色波浪线(以及编译后报错)
详细可查看这篇博文https://blog.csdn.net/qq_45899177/article/details/135854420
解决方法
- 下载并安装V5版本的ARM编译器
- 点击Keil三小方块–>Folders/Extensions–>Use ARM Compiler 方框旁三个小点
- Add another ARMCompoler Version to list
- 点击魔法棒–>Target -->ARM Compiler -->Version 5
问题四 :显示 0 Error(s) 1Warning(s)
解决方法
- 如果是在调试时出现的,那么往下在多敲一行就好了(也就是要留一个空行)
库函数实现点灯
#include "stm32f10x.h"
int main(void)#include "stm32f10x.h" // Device header
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//时钟配置
GPIO_InitTypeDef GPIO_InitSture;//结构体定义起别名
结构体内部要配置的3个
GPIO_InitSture.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitSture.GPIO_Pin = GPIO_Pin_13;
GPIO_InitSture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitSture);//结构体地址传递
GPIO_SetBits(GPIOC,GPIO_Pin_13);//高电平设置
// GPIO_ResetBits(GPIOC,GPIO_Pin_13);//低电平设置
while(1)
{
}
}
GPIO通用输入输出口
简介
GPIO是通用输入输出端口的简称
- 将STM32芯片的GPIO引脚与外部设备连接起来,即可实现与外部通信、控制以及数据采集的功能。
- GPIO被分为很多组,每组有16个引脚
- 最基本的输入功能是检测外部输入电平,如把GPIO引脚连接到按键,然后通过电平高低区分按键是否被按下。
- 最基本的输出功能是由STM32控制引脚输出高、低电平,实现开关控制,如把GPIO引脚接入LED灯,就可以控制LED灯的亮灭;引脚接入继电器或三极管,就可以通过继电器或三极管控制外部大功率电路的通断。
GPIO的基本结构
- Vcc:(c–>circuit)电路,接入电路的电压
- Vdd:(D–>Device)器件,器件内部工作电压
- Vss:(S–>series)公共连接,电路公共接地端电压
电路图简介
- 钳位(保护)二极管:保护二极管和上/下拉电阻,防止从外部引脚输入的电压过高或过低。
- P-MOS管和N-MOS管:二者构成CMOS反相器电路,使端口具有推挽输出和开漏输出两种输出模式
- 推挽输出,输入高电平–>反相–>P-MOS管导通–>N-MOS管关闭–>输出高电平(低电平时N-MOS管导通)(低电平0V、高电平3.3V)(P-MOS灌电流,N-MOS拉电流)
- 开漏输出,P-MOS管不工作,输低电平,N-MOS导通,输出接地。(输0接地、输1断开)(无上拉电阻)
- 输出数据寄存器
- 复用功能输出
- 输入数据寄存器
- 复用功能输入
- 模拟输入输出
工作模式简介
模式名称 | 性质 | 特征 |
---|---|---|
浮空输入 | 数字输入 | 可读取引脚电平,引脚悬空 ,则电平不确定 |
上拉输入 | 数字 | 可读取引脚电平,内部连接上拉电阻 ,悬空时默认高电平 |
下拉输入 | 数字 | 可读取引脚电平,内部连接下电阻,悬空时默认低电平 |
模拟输入 | 模拟 | GPIO无效,引脚直接接入内部ADC |
开漏输出 | 数字 | 可输出引脚电平,高电平为高阻态,低电平接VSS |
推挽输出 | 数字 | 可输出引脚电平,高电平接VDD,低电平接VSS |
复用开漏输出 | 数字 | 由片上外设控制,高电平为高阻态,低电平接VSS |
复用推挽输出 | 数字 | 由片上外设控制,高电平接VDD,低电平接VSS |
LED和蜂鸣器
-
LED
- 正向通电点亮,反向通电不亮
- 长引脚为正极,短引脚为负极。
-
蜂鸣器
- 有源:内部自带振荡源,将正负极接直流电压即可持续发声
- 无源:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音
PNP三极管最好接上面,NPN三极管最好接下面
GPIO
点亮/熄灭LED灯
#include "stm32f10x.h" // Device header
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
//点亮LED灯//
// GPIO_ResetBits(GPIOA,GPIO_Pin_0);
// GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
//熄灭LED灯//
// GPIO_SetBits(GPIOA,GPIO_Pin_0);
// GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
while(1)
{
}
}
LED闪烁
#include "stm32f10x.h" // Device header
#include "Delay.h" // 延时函数
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出,低电平有驱动能力
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
//点亮LED
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
// GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);//延时
//熄灭LED
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
// GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);//延时
}
}
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);
}
}
经验证任意点亮LED代码与任意LED熄灭代码混用也可以实现功能(即writebit和Setbit一起用也OK)
以及如果需要更改字符,比如用0,1来表示高低电平,则需要调用GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);并将Bit_SET更改为(BitAction) 1,Bit_RESET更改为(BitAction) 0
问题探讨
一:LED操作中将LED灯的短脚接负极,长脚接A0,LED灯仍然保持闪烁
- 说明在推挽模式下,高低电平都有驱动能力
- 当更换为开漏输出,只在低电平驱动能力(即,长脚接正极,短脚接A0口)
LED流水灯
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(500);
}
}
问题探讨
二:为什么是GPIOA而不是GPIOB
(哈哈,当你提出这种问题的时候就说明其实还是有发动自己的小脑瓜在思考嘛:happy:)
- 查阅资料发现这仅仅是由于STM32的芯片决定的。
- 首先咱们要弄清楚GPIOA、GPIOB····是什么?
- 是什么呢?其实不管是 GPIO什么都只是GPIO端口中众多寄存器中的一种而已。
- 而我们使用GPIOA仅仅是因为我们只是改变的PA0~PA7口的高低电平状态而已。
蜂鸣器
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//连接B12口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
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_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(700);
}
}
刚刚提出的问题在这里是不是就用上了呢,哈哈,蜂鸣器我们用的是B12口,所以使用了GPIOB寄存器~🖐
GPIO输入
按键驱动
键盘须知:
- 判别是否有键按下
- 行线输出电压为高电平/低电平,高电平–>键断开,低电平–>键闭合
- 识别哪一个键被按下
- 根据键值,找到相应键值的处理程序入口
键盘分类:
- 独立式按键
- 电路简单,各条检测线独立 ,识别按下按键的软件编写简单
- 不适用于键盘按键数目较多的场合,会占用较多的I/O口
- 按下按键–>低电平,其他按键相连的检测线仍为高电平
- 矩阵式按键
- 用于按键树木较多的场合,可节省较多的I/O口
- 无按键按下–>行线为高电平,
- 有按键按下–>行线电平状态将由与此行线相连的列线电平决定(列低–>行低)
按键驱动:
按键开关的两端分别连接在行线和列线上,通过键盘开关机械触电的断开、闭合,其按键电压输出波形会有一段起伏,按下开关是会有5~10ms左右的抖动期。
消除抖动的方法:
- 软件延时
- 在检测到有键按下时,该键所对应的行线为低电平,执行一段延时10ms的子程序后,确认该行线电平是否仍为低电平,若仍为低电平则确认该行确实有键按下。
- 采用专用的键盘/显示器接口芯片(不过多介绍,暂时用不上)
关于STM32C语言相关介绍可查看这篇文档 C.md
按键控制LED
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
uint8_t KeyNum;
int main(void)
{
LED_Init();
Key_Init();
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
LED1_Turn();
}
if(KeyNum == 2)
{
LED2_Turn();
}
}
}
Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
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;
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 = 2;
}
return KeyNum;
}
LED.c
#include "stm32f10x.h" // Device header
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
GPIO_SetBits(GPIOA,GPIO_Pin_1 | GPIO_Pin_2);
}
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
void LED1_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
}
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_2);
}
void LED2_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}
}
遇到的问题
问题一:代码无法自动补全
法一:
详情请看这篇博文https://blog.csdn.net/qq_45138815/article/details/117465568
- 点击扳手🔧按钮–>Text Completion -->勾选Symbols after ···
法二:
- 按下ctrl + alt +空格
问题二:只有一个灯闪烁(在第一次调试时两个灯都亮)
- 在复制LED1_ON和LED1_OFF修改为相应的LED2_ON和LED2_OFF时,忘记修改引脚了
问题三:Hardware\Key.c(18): error: #20: identifier “unit8_t” is undefined
- 宝贝,看清楚应该是uint8_t
光敏传感器控制蜂鸣器
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();
}
}
}
LightSensor.c
#include "stm32f10x.h" // Device header
void LightSensor_Init(void)
{
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);
}
uint8_t LightSensor_Get(void)
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);
}
Buzzer.c
#include "stm32f10x.h" // Device header
void Buzzer_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
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);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
void Buzzer_ON(void)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}
void Buzzer_OFF(void)
{
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
void Buzzer_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
}
OLED
简介
OLED是有机发光二极管
- 自发光、不需要背光源、主打发光、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制造较简单
发光原理:
- 有机发光显示技术由非常薄的有机材料图层和玻璃基板构成,有电荷通过时发光。
使用0.96寸OLED模块
- 小巧玲珑、占用接口少、简单易用
- 供电:3~5.5V
- 通信协议:12C/SPI
- 分辨率:128*64
- 4行16列
调试方式
- 串口调试
- 通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息
- 显示屏调试
- 直接将显示屏连接到单片机,将调试信息打印在显示屏上
- Keil调试模式
- 借助Keil软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能
驱动函数
函数 | 作用 |
---|---|
OLED_Init(); | 初始化 |
OLED_Clear(); | 清屏 |
OLED_ShowChar(1,1,‘A’); | 显示一个字符 |
OLED_ShowString(1,3,‘HelloWorld!’); | 显示字符串 |
OLED_ShowNum(2,1,12345,5); | 显示十进制数字 |
OLED_ShowSignedNum(2,7,-66,2); | 显示有符号十进制数字 |
OLED_ShowHexNum(3,1,0xAA55,4); | 显示十六进制数字 |
OLED_ShowBinNum(4,1,0xAA55,16); | 显示二进制数字 |
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
int main(void)
{
OLED_Init();
OLED_ShowChar(1, 1, 'A');
OLED_ShowString(1,3,"HelloWorld!");
OLED_ShowNum(2,1,12345,5);
OLED_ShowSignedNum(2,7,-66,2);
OLED_ShowHexNum(3,1,0xAA55,4);
OLED_ShowBinNum(4,1,0xAA55,16);
while(1)
{
}
}
EXIT外部中断
中断
概念:指内部或外部事件使CPU暂停当前程序,转去执行中断服务程序的一种工作机制。
- 中断源:中断请求的来源
- 中断控制:中断的允许/禁止,中断优先级与优先级嵌套
- 中断响应:保护断点,转去执行中断服务程序
STM32最多能够接受84个中断,包括16个内核中断和68个外部中断(STM32F103系列只有60个中断)
STM32将中断分为5组。
组别 | AIRCR[10:8] | BIT[7:4]分配情况 | 分配结果 |
---|---|---|---|
0 | 111 | 0:4 | 0位抢占优先级(取值为0),4位响应优先级(取值为0-15) |
1 | 110 | 1:3 | 1位抢占优先级(取值为0-1),3位响应优先级取值为0-17) |
2 | 101 | 2:2 | 2位抢占优先级(取值为0-3),2位响应优先级取值为0-3) |
3 | 100 | 3:1 | 3位抢占优先级(取值为0-7),1位响应优先级(取值为0-1) |
4 | 011 | 4:0 | 4位抢占优先级(取值为0-15),0位响应优先级(取值为0) |
中断优先级:
- 当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套:
- 当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。
EXIT外部中断
- 可以检测GPIO的电平信号,当其指定的GPIO口产生电平信号变化时,EXIT将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXIT对应的中断程序
- 支持的触发方式:上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有GPIO口(相同的Pin不能同时触发中断 )
- 通道数:16个 GPIO_Pin,外加PVD输出,RTC闹钟,USB唤醒,以太网唤醒
- 触发响应方式:中断响应/事件响应
对射式红外传感器计次
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
int main(void)
{
OLED_Init();
countsensor_Init();
OLED_ShowString(1,1,"Count");
while(1)
{
OLED_ShowNum(1,7,CountSensor_Get(),5);
}
}
CountSensor.c
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count;
void countsensor_Init(void)
{
//配置时钟
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_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//配置AFIO 将EXTI的第14个线路配置为中断模式,下降沿触发,开启中断
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_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
//EXTI_Trigger_Rising上升沿触发,即遮挡时计数器加一
//EXTI_Trigger_Falling下降沿触发,即遮挡后挪开挡光片时计数器加一
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
//返回计数器的值
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14) == SET)
{
CountSensor_Count++;
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
遇到的问题
问题1:为什么光敏传感器遮挡后还是很亮
解答:
- 宝贝亲亲遇到这种问题的时候可能需要调一下光敏传感器的阈值呢~
问题2:为什么红外传感器遮挡后仍然亮
解答:
- 😄 也是被笑到了呢,宝贝你看看你遮对地方没有啊,人家是对射式红外,有没有可能你要遮的是两个黑东西中间那部分呢。
旋转编码器计次
Encoder.c
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;
void Encoder_Init(void)
{
//配置时钟
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);
//配置AFIO 将EXTI的第14个线路配置为中断模式,下降沿触发,开启中断
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
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_Rising;
EXTI_Init(&EXTI_InitStructure);
//配置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;
}
//向PB0口转动一下减一
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
Encoder_Count --;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
//向PB1口转动一次加一
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0)
{
Encoder_Count ++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"
int16_t Num;
int main(void)
{
OLED_Init();
Encoder_Init();
OLED_ShowString(1,1,"Num:");
while(1)
{
Num += Encoder_Get();
OLED_ShowSignedNum(1,5,Num,5);
}
}
TIM定时中断
简介TIM
- 分类
定时器 | TIMx | 配备 | 功能 | 总线 |
---|---|---|---|---|
通用定时器 | TIM2、TIM3、 TIM4、 TIM5 | 1个16位自动加载计数器,1个16位可编程预分频器,4个独立通道 | 使用外部信号控制定时器、产生中断或DMA、触发输入作为外部时钟或按周期的电流管理··· | APB1 |
高级定时器 | TIM1、TIM8 | 1个16位自动加载计数器,1个16位可编程预分频器,4个独立通道 | 适合更复杂的场景 | APB2 |
基本定时器 | TIM6、TIM7 | 各配备1个 16位自动装载计数器、 | 产生DAC触发信号、也可当作通用的16位时基计数器 | APB1 |
(STM32F103C8T6定时器:TIM1、TIM2、TIM3、TIM4)
- 时钟源
除内部时钟源以外其他三种时钟源均通过TRGI(触发)输入。
通用定时器的时钟输入源
- 内部时钟输入源
- 时钟模式1:外部输入引脚(TIx)包括比较/捕获引脚TIIF_ED、TI1FP1和TI2FP2,计数器在选定引脚的上升沿或下降沿开始计数
- 时钟模式2:外部触发输入引脚(ETR),计数器在ETR引脚的上升沿或下降沿开始计数
- 内部触发输入(ITRx ,x=0,1,2,3),一个定时器作为另一个定时器的预分频器
计数器计数频率:CK_CNT = CK_PSC/(PSC + 1)
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)
定时器中断
定时时间的计算:
定时时间由TIM_TimeBaseInitTypeDef中的TIM_Prescaler和TIM_Period进行设定
- TIM_Period表示需要经过TIM_Period次计数才会发生一次更新或中断,而TIM——Prescaler表示时钟预分频数
- 设脉冲频率为TIMxCLK,定时公式为
- T = (TIM_Period + 1) * (TIM_Prescaler + 1) / TIMxCLK
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Num");
while(1)
{
OLED_ShowNum(1,5,Num,5);
//CNT计数值的情况
//OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);
}
}
//定时器中断函数
void TIM2_IRQHandler(void)
{
//检查中断标志位
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
Num++;
//清除标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟
TIM_InternalClockConfig(TIM2);//默认调用内部时钟,不写也行
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInisStructure;
TIM_TimeBaseInisStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInisStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInisStructure.TIM_Period = 10000 - 1;//自动重装 ARR 1~65536
TIM_TimeBaseInisStructure.TIM_Prescaler = 7200 - 1 ;//预分频 PSC 1~65536
TIM_TimeBaseInisStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInisStructure);
//手动清除更新标志位
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//使能更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
/*
//定时器中断函数
void TIM2_IRQHandler(void)
{
//检查中断标志位
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
//清除标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
定时器外部中断
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Num");
OLED_ShowString(2,1,"CNT:");
while(1)
{
OLED_ShowNum(1,5,Num,5);
//CNT计数值的情况
OLED_ShowNum(2,5,Timer_GetCounter(),5);
}
}
//定时器中断函数
void TIM2_IRQHandler(void)
{
//检查中断标志位
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
Num++;
//清除标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//ETR外部时钟2
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0X00);
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInisStructure;
TIM_TimeBaseInisStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInisStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInisStructure.TIM_Period =10 - 1;//自动重装 ARR 1~65536
TIM_TimeBaseInisStructure.TIM_Prescaler =1 - 1;//预分频 PSC 1~65536
TIM_TimeBaseInisStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInisStructure);
//手动清除更新标志位
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//使能更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
/*
//定时器中断函数
void TIM2_IRQHandler(void)
{
//检查中断标志位
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
//清除标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
TIM输出比较
-
输出PWM波形(驱动电机)
- 通过比较CNT与CCR寄存器值的关系,对输出电平进行置1、置0或翻转的操作
- PWM 脉冲宽度调制
- 在具有惯性的系统中,可以通过对一系列的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
- 频率 = 1/Ts (Ts = 高电平Ton +低电平 Toff的时间)
- Freq = CK_PSC / (PSC + 1) / (ARR + 1)
- 占空比=Ton/Ts
- Duty = CCR / (ARR + 1)
- 分辨率 = 占空比变化步距
- Reso = 1 / (ARR + 1)
-
每个高级定时器和通用定时器都拥有4个输出比较通道
-
高级定时器的前3个通道额外拥有死区生成和互补输出的功能
输出比较的8种模式:
-
模式 描述 冻结 CNT = CCR时,REF保持为原状态 匹配时置有效电平 CNT = CCR时,REF置有效电平 匹配时置无效电平 CNT = CCR 时,REF置无效电平 匹配时电平翻转 CNT = CCR时,REF电平翻转 强制为无效电平 CNT与CCR无效,REF强制为无效电平 强制为有效电平 CNT与CCR无效,REF强制为有效电平 PWM模式1 向上计数:CNT<CCR时,REF置有效电平,CNT>=CCR时,REF置无效电平向下计数:CNT>CCR时,REF置无效电平,CNT<=CCR时,REF置有效电平 PWM模式2 向上计数:CNT<CCR时,REF置无效电平,CNT>=CCR时,REF置有效电平向下计数:CNT>CCR时,REF置有效电平,CNT<=CCR时,REF置无效电平
舵机
-
根据输入PWM信号占空比来控制输出角度的装置
-
输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms
-
输入信号脉冲宽度 舵机输出轴转角 0.5ms -90° 1ms -45° 1.5ms -0° 2ms 45° 2.5ms 90°
-
直流电机
-
将电能转换为机械能,电极正接,电机正转。
-
属于大功率器件,需要配合电机驱动电路来操作。
-
- VM电机电源,一般与电机额定电压保持一致
- VCC 控制器的电源保持一致(STM32~3.3V,51-5V)
PWM驱动LED呼吸灯
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint8_t i;
int main(void)
{
OLED_Init();
PWM_Init();
while(1)
{
for(i=0;i<=100;i++)
{
PWM_SetCompare1(i);
Delay_ms(10);
}
for(i=0;i<=100;i++)
{
PWM_SetCompare1(100-i);
Delay_ms(10);
}
}
}
PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//AFIO 重映射更改复用的引脚
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//打开AFIO时钟
//GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//重映射引脚
//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//接触调试端口
//GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用开漏推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//选择时基单元的时钟
TIM_InternalClockConfig(TIM2);//默认调用内部时钟,不写也行
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInisStructure;
TIM_TimeBaseInisStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInisStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInisStructure.TIM_Period = 1000 - 1;//自动重装 ARR 1~65536
TIM_TimeBaseInisStructure.TIM_Prescaler = 720 - 1 ;//预分频 PSC 1~65536
TIM_TimeBaseInisStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInisStructure);
//初始化输出比较单元
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;//设置输出使能
TIM_OCInitstructure.TIM_Pulse = 0;//设置CCR
TIM_OC1Init(TIM2,&TIM_OCInitstructure);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
//单独更改通道1的CCR值
TIM_SetCompare1(TIM2,Compare);
}
PWM驱动舵机
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"
uint8_t KeyNum;
float Angle;
int main(void)
{
OLED_Init();
Servo_Init();
Key_Init();
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.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
//选择时基单元的时钟
TIM_InternalClockConfig(TIM2);//默认调用内部时钟,不写也行
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInisStructure;
TIM_TimeBaseInisStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInisStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInisStructure.TIM_Period = 20000 - 1;//自动重装 ARR 1~65536
TIM_TimeBaseInisStructure.TIM_Prescaler = 72 - 1 ;//预分频 PSC 1~65536
TIM_TimeBaseInisStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInisStructure);
//初始化输出比较单元
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;//设置输出使能
TIM_OCInitstructure.TIM_Pulse = 0;//设置CCR
TIM_OC2Init(TIM2,&TIM_OCInitstructure);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare2(uint16_t Compare)
{
//单独更改通道2的CCR值
TIM_SetCompare2(TIM2,Compare);
}
Servo.c
#include "stm32f10x.h" // Device header
#include "PWM.h"
//舵机初始化
void Servo_Init(void)
{
PWM_Init();
}
//舵机设置角度
void Servo_SetAngle(float Angle)
{
//对Angle 进行缩放
//0 -->500 180 -->2500
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
PWM驱动直流电机
main.c
#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();
Motor_Init();
Key_Init();
OLED_ShowString(1,1,"Speed:");
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Speed += 20;
if(Speed > 100)
{
Speed = -100;
}
}Motor_SetSpeed(Speed);
OLED_ShowSignedNum(1,7,Speed,3);
}
}
PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
//选择时基单元的时钟
TIM_InternalClockConfig(TIM2);//默认调用内部时钟,不写也行
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInisStructure;
TIM_TimeBaseInisStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInisStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInisStructure.TIM_Period = 1000 - 1;//自动重装 ARR 1~65536
TIM_TimeBaseInisStructure.TIM_Prescaler = 720 - 1 ;//预分频 PSC 1~65536
TIM_TimeBaseInisStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInisStructure);
//初始化输出比较单元
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;//设置输出使能
TIM_OCInitstructure.TIM_Pulse = 0;//设置CCR
TIM_OC3Init(TIM2,&TIM_OCInitstructure);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare3(uint16_t Compare)
{
//单独更改通道3的CCR值
TIM_SetCompare3(TIM2,Compare);
}
Motor.c
#include "stm32f10x.h" // Device header
#include "PWM.h"
//初始化
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
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_4);
GPIO_SetBits(GPIOA,GPIO_Pin_5);
PWM_SetCompare3(-Speed);
}
}
遇到的问题
电机转的慢,带不动风扇
解决方法:
- 方法一 :
- 用手指轻轻拨动一下扇叶
- 方法二:
- 将STLINK连接到充电器(或其他)的USB口上
TIM输入捕获
- 输入捕获
- 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将锁存到CCR中,用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
- 每个高级定时器和通用定时器都拥有4个 输入捕获通道
- 可配置为PWM1模式,同时测量频率和占空比
- 可配合主从触发模式,实现硬件全自动测量
- 频率测量
- 测频法:在闸门时间T内,对上升沿计次,得到N,则频率fx = N/T
- (高频信号)
- 测量结果更新慢,数值相对稳定
- 正负一误差
- 测周法:两个上升沿内,以标准频率fx = fc /N
- (低频信号)
- 更新快,数据跳变也快
- 正负一误差
- 中界频率:测频法与测周法误差相等的频率点 fm = sqrt (fc/T)
- 测频法:在闸门时间T内,对上升沿计次,得到N,则频率fx = N/T
本节课使用测周法
输入捕获模式测频率
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"
int main(void)
{
OLED_Init();
PWM_Init();
IC_Init();
OLED_ShowString(1,1,"Freq:00000Hz");
PWM_SetPrescaler(720-1); //Freq = 72M / (PSC + 1) /100
PWM_SetCompare1(50); //Duty = CCR / 100
while(1)
{
OLED_ShowNum(1,6,IC_GetFreq(),5);
}
}
PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
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
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
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;
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
void PWM_SetPrescaler(uint16_t Prescaler)
{
TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);
}
IC.c
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
/*RCC开启时钟,打开GPIO,TIM时钟
GPIO初始化,把GPIO配置为输入模式(上拉/浮空)
配置时基单元,让CNT计数器在内部时钟的驱动下自增运行
配置输入捕获单元
选择从模式的触发源 -->TIFP1
选择触发之后执行的操作 执行Reset操作
调用TIM_Cmd函数,开启定时器
*/
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
//选择时基单元的时钟
TIM_InternalClockConfig(TIM3);
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInisStructure;
TIM_TimeBaseInisStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInisStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInisStructure.TIM_Period = 65536 - 1;//自动重装 ARR 1~65536
TIM_TimeBaseInisStructure.TIM_Prescaler = 72 - 1 ;//预分频 PSC 1~65536
TIM_TimeBaseInisStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInisStructure);
//初始化输入捕获单元
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//捕获通道 --> 使能TIMx通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF; //选择输入滤波器,0x0 ~ 0xF(不使用:0),滤波器计次不会改变信号的原有频率
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//输入捕获的触发边沿,
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//确定输入捕获预分频器,对信号本身进行计次,会改变频率
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择捕获通道ICx的信号输入通道
TIM_ICInit(TIM3,&TIM_ICInitStructure);
//配置TRGI的触发源
TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
//配置从模式为Reset
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
//调用TIM_Cmd函数,启动定时器
TIM_Cmd(TIM3,ENABLE);
}
//返回最新一个周期的频率值
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
问题
为什么显示为0000Hz
解决方法:
- 将main.c中的SetPrescaler改为与PWM中函数一致即可。
PWMI模式测频率占空比
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"
int main(void)
{
OLED_Init();
PWM_Init();
IC_Init();
OLED_ShowString(1,1,"Freq:00000Hz");
OLED_ShowString(2,1,"Duty:00%");
PWM_SetPrescaler(720-1); //Freq = 72M / (PSC + 1) /100
PWM_SetCompare1(50); //Duty = CCR / 100
while(1)
{
OLED_ShowNum(1,6,IC_GetFreq(),5);
OLED_ShowNum(2,6,IC_GetDuty(),2);
}
}
IC.c
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
/*RCC开启时钟,打开GPIO,TIM时钟
GPIO初始化,把GPIO配置为输入模式(上拉/浮空)
配置时基单元,让CNT计数器在内部时钟的驱动下自增运行
配置输入捕获单元
选择从模式的触发源 -->TIFP1
选择触发之后执行的操作 执行Reset操作
调用TIM_Cmd函数,开启定时器
*/
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
//选择时基单元的时钟
TIM_InternalClockConfig(TIM3);
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInisStructure;
TIM_TimeBaseInisStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInisStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInisStructure.TIM_Period = 65536 - 1;//自动重装 ARR 1~65536
TIM_TimeBaseInisStructure.TIM_Prescaler = 72 - 1 ;//预分频 PSC 1~65536
TIM_TimeBaseInisStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInisStructure);
//初始化输入捕获单元
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//捕获通道 --> 使能TIMx通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF; //选择输入滤波器,0x0 ~ 0xF(不使用:0),滤波器计次不会改变信号的原有频率
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//输入捕获的触发边沿,
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//确定输入捕获预分频器,对信号本身进行计次,会改变频率
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择捕获通道ICx的信号输入通道
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);//自动配置为相反模式
//第一种方法
// TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//捕获通道 --> 使能TIMx通道1
// TIM_ICInitStructure.TIM_ICFilter = 0xF; //选择输入滤波器,0x0 ~ 0xF(不使用:0),滤波器计次不会改变信号的原有频率
// TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;//输入捕获的触发边沿,
// TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//确定输入捕获预分频器,对信号本身进行计次,会改变频率
// TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;//选择捕获通道ICx的信号输入通道
// TIM_ICInit(TIM3,&TIM_ICInitStructure);
//配置TRGI的触发源
TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
//配置从模式为Reset
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
//调用TIM_Cmd函数,启动定时器
TIM_Cmd(TIM3,ENABLE);
}
//返回最新一个周期的频率值
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
//获取占空比
uint32_t IC_GetDuty(void)
{
return (TIM_GetCapture2(TIM3) +1) * 100 /(TIM_GetCapture1(TIM3) + 1);
}
遇到的问题
为什么占空比显示为00%
解决方法:
- IC.c中应该为(TIM_GetCapture2(TIM3) +1)
TIM编程器接口
编码器接口简介
- Encoder Interface 编码器接口
- 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置,旋转方向和旋转速度
-
-
正转都向上计数,反转都向下计数
-
- 每个高级定时器和通用定时器都有1个编码器接口
- 两个输入引脚借用了输入捕获的通道1和通道2
编码器测速
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_Init();
Timer_Init();
Encoder_Init();
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);
}
}
Encoder.c
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInisStructure;
TIM_TimeBaseInisStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInisStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInisStructure.TIM_Period = 65536 - 1;//自动重装 ARR 1~65536
TIM_TimeBaseInisStructure.TIM_Prescaler = 1 - 1 ;//预分频 PSC 1~65536
TIM_TimeBaseInisStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInisStructure);
//配置两个通道的滤波器和极性
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//捕获通道 --> 使能TIMx通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF; //选择输入滤波器,0x0 ~ 0xF(不使用:0),滤波器计次不会改变信号的原有频率
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//捕获通道 --> 使能TIMx通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF; //选择输入滤波器,0x0 ~ 0xF(不使用:0),滤波器计次不会改变信号的原有频率
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
TIM_Cmd(TIM3,ENABLE);
}
int16_t Encoder_Get(void)
{
int16_t Temp;
Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
return Temp;
}