初学STM32之编码器测速以及测频法的实现
资料来着江协科技

这篇是编码器测速,江科大的源码在测速的时候,定时器TIM2是一直在跑的,不受其它控的,它就一直隔1S读一次CNT的值。它也不管是否有输入信号。源码程序修改一下是可以实现对PWM信号以测频法的方式读取。
笔者稍微改了一下这源码程序,让TIM3有信号输入时,TIM2才开始工作计数。源码在读连续信号的时候还是好用的,在读离散信号的时候可能就不怎么好用了。
比如我希望在某个IO口检测到一段1KHZ的频率的方波(或几次计数),方波的持续时间达到300ms,就开启某个功能,源码这个方案就不太好用,因此稍微修改了下程序。让其也满足这个条件
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"
#include "LED.h"int16_t Num; //定义在定时器中断里自增的变量
uint16_t i = 0; //中断次数指示,TIM2是1S中断int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化Timer_Init(); //定时中断初始化Encoder_Init();// LED_Init();/*显示静态字符串*/OLED_ShowString(1, 1, "CNT:"); //1行1列显示字符串Num:while (1){if(Num != 0)OLED_ShowSignedNum(1, 5, Num, 5); //不断刷新显示Num变量}
}/*** 函 数:TIM2中断函数* 参 数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行* 函数名为预留的指定名称,可以从启动文件复制* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断{Num = TIM3_GetNumber(); //TIM3的CNT值赋值给Num,该值在1s中断中 TIM_SetCounter( TIM3, 0); //TIM3的CNT清零 TIM_Cmd(TIM2, DISABLE); //关闭TIM2定时器TIM_SetCounter( TIM2, 0); //TIM2 CNT计数清0 TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); //使能TM3捕获中断 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位}
}
Encoder.c
#include "stm32f10x.h" // Device headervoid Encoder_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //开启TIM3外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA外设时钟GPIO_InitTypeDef GPIO_InitStruct; //GPIO功能设置GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU ;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz ;GPIO_Init( GPIOA, &GPIO_InitStruct);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //时基单元设置TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1 ;//该处设置对于目前的程序信号好像不起作用,最起码结果上不起作用因此默认为不分频TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up ;TIM_TimeBaseInitStruct.TIM_Period = 65536-1 ; //ARRTIM_TimeBaseInitStruct.TIM_Prescaler = 1-1 ; //Psc,设置为不分频即1个触发信号触发一次TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);TIM_ICInitTypeDef TIM_ICInitStruct; //信号输入捕获设置,设置了两个通道TIM_ICStructInit(&TIM_ICInitStruct);TIM_ICInitStruct.TIM_Channel = TIM_Channel_1 ;TIM_ICInitStruct.TIM_ICFilter = 0x0F ;TIM_ICInit(TIM3, &TIM_ICInitStruct);TIM_ICInitStruct.TIM_Channel = TIM_Channel_2 ;TIM_ICInitStruct.TIM_ICFilter = 0x0F ;TIM_ICInit(TIM3, &TIM_ICInitStruct);TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, //编码器设置TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); //TM3捕获1中断使能// 配置TIM3中断NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);TIM_Cmd( TIM3, ENABLE); //时钟使能}uint16_t TIM3_GetNumber(void)
{return TIM_GetCounter(TIM3);}void TIM3_IRQHandler(void)
{if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET){ TIM_Cmd(TIM2, ENABLE); // 使能TIM2定时器 TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); // 清除中断标志TIM_ITConfig(TIM3, TIM_IT_CC1, DISABLE);//TIM3关闭捕获中断}
}
修改后的代码功能:
TIM3通道1检测到输入捕获的时候会进入中断使能定时器2,定时器2开始计时,当定时器2溢出时进入定时器2的中断读取TIM3 CNT里的值并且清0,(显然如果TIM3的输入信号频率过快的话会产生一点延时)并关闭定时器2,使能定时器3捕获中断,退出TIM2中断如果编码器还在转动又会马上进入TIM3捕获中断开启定时器2,让其再开启定时功能。
以此为基础重写了之前的测频法程序。
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "ICfeq.h"uint16_t Num; //定义在定时器中断里自增的变量
uint16_t i = 0; //中断次数指示,TIM2是1S中断int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化Timer_Init(); //定时中断初始化IC_Init();/*显示静态字符串*/OLED_ShowString(1, 1, "CNT:"); //1行1列显示字符串Num:while (1){if(Num != 0)OLED_ShowNum(1, 5, Num, 5); //不断刷新显示Num变量}
}/*** 函 数:TIM2中断函数* 参 数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行* 函数名为预留的指定名称,可以从启动文件复制* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断{//Delay_ms( 10);Num = TIM3_GetNumber(); //TIM3的CNT值赋值给Num,该值在1s中断中 // Delay_ms( 100);TIM_SetCounter( TIM3, 0); //TIM3的CNT清零 TIM_Cmd(TIM2, DISABLE); //关闭TIM2定时器TIM_SetCounter( TIM2, 0); //TIM2 CNT计数清0 TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); //使能TM3捕获中断 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位}
}
ICfeq.c
#include "stm32f10x.h" // Device headervoid IC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //开启TIM3外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA外设时钟GPIO_InitTypeDef GPIO_InitStruct; //GPIO功能设置GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU ;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 ;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz ;GPIO_Init( GPIOA, &GPIO_InitStruct);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //时基单元设置TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1 ;//该处设置对于目前的程序信号好像不起作用,最起码结果上不起作用因此默认为不分频TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up ;TIM_TimeBaseInitStruct.TIM_Period = 65536-1 ; //ARRTIM_TimeBaseInitStruct.TIM_Prescaler = 1-1 ; //Psc,设置为不分频即1个触发信号触发一次TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);TIM_ICInitTypeDef TIM_ICInitStruct; //信号输入捕获设置TIM_ICStructInit(&TIM_ICInitStruct);TIM_ICInitStruct.TIM_Channel = TIM_Channel_1 ;TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising ;TIM_ICInitStruct.TIM_ICFilter = 0x0F ;TIM_ICInit(TIM3, &TIM_ICInitStruct);
// TIM_SelectInputTrigger( TIM3, TIM_TS_TI1FP1);TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_External1);TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); //TM3捕获1中断使能// 配置TIM3中断NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);TIM_Cmd( TIM3, ENABLE); //时钟使能}uint16_t TIM3_GetNumber(void)
{return TIM_GetCounter(TIM3);}void TIM3_IRQHandler(void)
{if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET){ TIM_Cmd(TIM2, ENABLE); // 使能TIM2定时器 TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); // 清除中断标志TIM_ITConfig(TIM3, TIM_IT_CC1, DISABLE);//TIM3关闭捕获中断}
}
程序的其它组件部分参考江科大的文件,TIM2的定时是1S,输入信号是1KHZ的PWM,输入端口是PA6,现在的程序与笔者之前的测频法比较。现在的程序更合理点,如果在主函数中断赋值语句前插入延时语句
Delay_ms( 10);Num = TIM3_GetNumber(); //TIM3的CNT
,那么最终得到的CNT值是会变大,前一个测频法程序是不会的,前一个测频法只能测试连续输入PWM。如果信号比较离散的话,它测试结果会变的不准确。

分享一下学习过程中发生的错误,之前这个代码一直有个BUG,编码器旋钮你随便转的话可能会导致程序死机卡死。后面想了不少时间,找的问题是中断优先级照成的。
TIM3的中断控制着TIM2的中断,并且进入中断会使自身中断关闭。TIM2中断会关闭自身的定时器并使能TIM3中断,如过有中断嵌套的话,位置不对就会死机。笔者之前的中断优先级设置的不合理,就出现死机了。
相关文章:
初学STM32之编码器测速以及测频法的实现
资料来着江协科技 这篇是编码器测速,江科大的源码在测速的时候,定时器TIM2是一直在跑的,不受其它控的,它就一直隔1S读一次CNT的值。它也不管是否有输入信号。源码程序修改一下是可以实现对PWM信号以测频法的方式读取。 笔者稍微改…...
Linux 内核知识体系[1]
1 Linux内核知识体系 2.Linux内核学习路线 2.1基础知识准备 操作系统基础:了解操作系统的概念和基本原理,包括进程管理、内存管理、文件系统、输入输出等。 书籍:《操作系统:设计与实现》(Andrew S. Tanenbaum&…...
408 计算机网络 知识点记忆(7)
前言 本文基于王道考研课程与湖科大计算机网络课程教学内容,系统梳理核心知识记忆点和框架,既为个人复习沉淀思考,亦希望能与同行者互助共进。(PS:后续将持续迭代优化细节) 往期内容 408 计算机网络 知识…...
Mac 下载 PicGo 的踩坑指南
Mac 下载 PicGo 的踩坑指南 一、安装问题 下载地址:https://github.com/Molunerfinn/PicGo/releases 下载之后直接安装即可,此时打开会报错:Picgo.app 文件已损坏,您应该将它移到废纸篓。 这是因为 macOS 为了保护用户不受恶意…...
消失的它:揭开 CoreData 托管对象神秘的消失之谜(上)
概述 使用 CoreData 作为 App 持久存储“定海神针”的小伙伴们想必都知道,我们需要将耗时的数据库查询操作乖巧的放到后台线程中,以便让主线程负责的 UI 获得风驰电掣般地享受。 不过,如何将后台线程中查询获得的托管对象稳妥的传送至主线程…...
电流互感器的两相星形接线的建模与仿真
微♥“电击小子程高兴的MATLAB小屋”获取巨额优惠 1.模型简介 本仿真模型基于MATLAB/Simulink(版本MATLAB 2016Rb)软件。建议采用matlab2016 Rb及以上版本打开。(若需要其他版本可联系代为转换) 2.仿真模型 3.仿真结果 3.1一次…...
Day3—循环起来吧
第一章一切从安装有利工具开始 第二章Python基本语法 第三章结构化程序设计 3.1条件语句 3.1.1if语句 只需要判断一个条件,并且在该条件为 True 时执行特定的代码块,而在条件为 F…...
forms+windows添加激活水印
formswindows添加激活水印 多语言水印文本,根据系统语言自动切换。水印显示在每个屏幕的右下角,位置动态调整。半透明灰色文字,微软雅黑字体。窗口无边框、置顶、透明背景,不干扰用户操作。支持多显示器。高DPI适配。 效果图&am…...
014_多线程
多线程 多线程创建线程方式一:继承Thread类方式二:实现Runable接口方式三:实现Callbale接口 Thread的常用方法线程安全线程同步方式一:同步代码块同步方法方式三:Lock锁 线性池创建线程池处理Runnable任务处理Callable…...
R语言基础包可视化(一:axis函数)
R语言基础包可视化(一:axis函数) 背景axis函数(坐标轴函数)各参数的图片示例hadj和padjline和poslty,lwd,lwd.ticksgap.axis总结背景 之前在介绍正态Q-Q图的过程中,画过标准正态分布的随机数、分数数、分布函数、密度函数的图像,相关的文章连接参考此处:R语言正态Q-Q图…...
Spring MVC 处理 HTTP 状态码、响应头和异常的完整示例
Spring MVC 处理 HTTP 状态码、响应头和异常的完整示例 1. 正常响应处理 通过 ResponseEntity 可以灵活控制 HTTP 状态码、响应头和响应体。 代码示例:创建资源返回 201 并设置 Location 头 import org.springframework.http.HttpHeaders; import org.springfram…...
Linux安装postgresql17
1、下载 wget https://ftp.postgresql.org/pub/source/v17.4/postgresql-17.4.tar.gz 2、上传解压 tar -zxvf postgresql-17.4.tar.gz 3、安装依赖 yum install -y perl-ExtUtils-Embed readline-devel zlib-devel pam-devel libxml2-devel libxslt-devel openldap-devel …...
从零搭建微服务项目Pro(第0章——微服务项目脚手架搭建)
前言: 在本专栏Base第0章曾介绍一种入门级的微服务项目搭建,尽管后续基于此框架上实现了Nacos、Eureka服务注册发现、配置管理、Feign调用、网关模块、OSS文件存储、JSR参数校验、LogBack日志配置,鉴权模块、定时任务模块等,但由于…...
dolphinscheduler创建文件夹显示存储未启用的问题--已解决
只要修改api-server/comf/common.properties和standalone-server/conf/common.properties里面的内容就可以了,应为你要靠standalone-server这个服务启动dolphinscheduler-web,其他就算怎么改你重启dolphinscheduler的时候系统也不会识别新的common.prope…...
C++线段树详解与实现技巧
📚 C++线段树详解与实现技巧 线段树(Segment Tree)是一种高效处理 区间查询 和 区间更新 的数据结构,时间复杂度为 O(log n)。本文结合代码实例,详解其核心原理与实现细节。 🌳 线段树结构特点 完全二叉树:使用数组存储,父子节点关系通过下标计算。区间划分:每个节…...
聊一聊原子操作和弱内存序
1、原子操作概念 在并发编程中,原子操作(Atomic Operation)是实现线程安全的基础机制之一。从宏观上看,原子操作是“不可中断”的单元,但若深入微观层面,其本质是由底层处理器提供的一组特殊指令来保证其原…...
VIRT, RES,SHR之间的关系
VIRT、RES 和 SHR 是进程内存使用的三个关键指标,它们之间的关系反映了进程的内存分配和使用情况。以下是它们的定义和关系: VIRT(虚拟内存):表示进程分配的虚拟内存总量,包括所有代码、数据、共享库、堆栈…...
Ubuntu中部署MeloTTS
0. 环境 ubuntu server 22.04 cuda version 12.4 python version 3.9 1. 安装python依赖 git clone https://github.com/myshell-ai/MeloTTS.git cd MeloTTS注意不是执行 pip install melotts 如果国内服务器无法从github中下载源码,那么可以把github改为gitc…...
2024年React最新高频面试题及核心考点解析,涵盖基础、进阶和新特性,助你高效备战
以下是2024年React最新高频面试题及核心考点解析,涵盖基础、进阶和新特性,助你高效备战: 一、基础篇 React虚拟DOM原理及Diff算法优化策略 • 必考点:虚拟DOM树对比(同级比较、Key的作用、组件类型判断) •…...
Adobe After Effects的插件--------Optical Flares之Options概述
Optical Flares插件的Options是对整个效果的组装和设置。点击该按钮会弹出一个组装室弹窗。 Options组装室就是对每个【镜头对象】进行加工处理,再将其组装在一起,拼凑成完整的光效。 接下来是我对组装室的探索: 面板 面板中有预览、堆栈、编辑和浏览按钮,其作用是调节窗…...
深入解读 React 纯组件(PureComponent)
什么是纯组件? React 的纯组件(PureComponent)是 React.Component 的一个变体,它通过浅比较(shallow comparison)props 和 state 来自动实现 shouldComponentUpdate() 方法,从而优化性能。 核心特点 1. 自动浅比较: PureCompon…...
微信小程序事件详解
微信小程序中的事件绑定是实现交互功能的核心机制之一。通过事件绑定,开发者可以监听用户的操作行为(如点击、输入、滑动等),并根据需要执行相应的逻辑处理。 以下是关于微信小程序事件绑定的详细说明: 一、事件绑定的…...
字符串与相应函数(上)
字符串处理函数分类 求字符串长度:strlen长度不受限制的字符串函数:strcpy,strcat,strcmp长度受限制的字符串函数:strncpy,strncat,strncmp字符串查找:strstr,strtok错误信息报告:strerror字符操作,内存操作函数&…...
Laravel源码进阶
Laravel源码进阶 版本 laravel5.8 生成服务容器 public index.php //compose必要操作 require __DIR__./../vendor/autoload.php; //容器文件 $app require_once __DIR__./../bootstrap/app.php;-bootstrap/app.php //初始化容器 构造函数中执行这个几个方法 //$this->…...
镜舟科技亮相 2025 中国移动云智算大会,展示数据湖仓一体创新方案
4月10-11日,2025 中国移动云智算大会在苏州金鸡湖国际会议中心成功举办。大会以“由云向智,共绘算网新生态”为主题,汇聚了众多行业领袖与技术专家,共同探讨了算力网络与人工智能的深度融合与未来发展趋势。 作为中国领先的企业级…...
2025蓝桥杯省赛C/C++研究生组游记
前言 至少半年没写算法题了,手生了不少,由于python写太多导致行末老是忘记打分号,printf老是忘记写f,for和if的括号也老是忘写,差点连&&和||都忘记了。 题目都是回忆版本,可能有不准确的地方。 …...
重读《人件》Peopleware -(6)Ⅰ管理人力资源Ⅴ-帕金森定律重探 Parkinson’s Law Revisited
1954年,英国作家C. Northcote Parkinson引入了一个概念:工作会膨胀以填满分配给它的时间,这个概念现在被熟知为帕金森定律。如果你不知道很少有管理者接受过任何管理培训的话,你可能会以为他们都参加过一个关于帕金森定律及其影响…...
Linux-内核驱动-led
登记设备号(后面可以动态分配) 自己定义内核函数 登记设备名字和功能 exit和init在内核启动自动执行 这样定义直接操作物理地址 ioctl 定义了设备文件的各种操作,并准备将其注册到内核中。 代码中声明了一个cdev结构体变量cdev,这…...
记录一次因ASM磁盘组空间不足,导致MAP进程无法启动
生产中 ADG 库出现告警,检查发现 map 进程异常: 检查 alter 日志,出现: ORA-19504:failed to create file "DATAC1/casarch/2_162186_1067953047.arc" ORA-17502:ksfdcre:4 Failed to create file ... ORA-15041:diskgroup "DATAC1" space exhausted OR…...
可能存在特殊情况,比如控制台显示有延迟、缓冲问题等影响了显示顺序。
从控制台输出看,正常逻辑应是先执行 System.out.println(" 未处理异常演示 "); 输出对应文本,再因 arr 为 null 访问 length 触发 NullPointerException 输出异常信息。可能存在特殊情况,比如控制台显示有延迟、缓冲问题等影响…...
