用单片机制作简单的频率计

点击上方“果果小师弟”,选择“置顶/星标公众号

干货福利,第一时间送达!

摘要

利用定时器产生PWM波。然后利用32的外部中断和定时器来测量32输出的波形 硬件:STM32F103C8T6核心板、示波器、串口调试助手 所用到的的引脚为PA8和PA0。

测量方案

在第一次外部中断(上升沿触发)到之时,开启定时器,同时计数器清零。然后等待第二次中断到来,在第二次外部中断(上升沿触发)到之时,获取计数器的计数值,同时关闭计数器。因为知道了计数器计数一个数的时间,所以在第二次外部中断(上升沿触发)到之时,获取计数器的计数值,通过这个值就知道一个脉冲的时间周期。时间周期的倒数就是外部信号的频率。

一、利用TIM1的CH1产生PWM波

pwm.c

#include "pwm.h"void TIM1_PWM_Init(u16 arr,u16 psc)
{  GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_OCInitTypeDef  TIM_OCInitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);  //使能GPIO外设时钟使能                                                                       //设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = arr; //输出PWM的频率为200 000/100=2 000 HZ=2K 实际示波器测量 2.00055K TIM_TimeBaseStructure.TIM_Prescaler =psc; //驱动(单片机提供给)计数器的时钟是72 000 000/36 0=200kHZTIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_Pulse = 3600; //设置待装入捕获比较寄存器的脉冲值 这个值要为arr:自动重装值的一半,占空比才为50%TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMxTIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能 TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器 TIM_Cmd(TIM1, ENABLE);  //使能TIM1   
}

pwm.h

#ifndef __PWM_H
#define __PWM_H
#include "sys.h"void TIM1_PWM_Init(u16 arr,u16 psc);#endif

main.c

#include "delay.h"
#include "sys.h" 
#include "pwm.h"
int main(void)
{ delay_init(); //延时函数初始化  //10k  7199//20k  3599//8k   8999TIM1_PWM_Init(7199,0);  //不分频,输出PWM频率=72000K/(7199+1)=10Khzwhile(1){}
}

定时器1的通道1对应的是PA8引脚,连接示波器可以测出波形

二、将PA8与PA0相连接

这里利用PA8输出的PWM波形让PA0外部中断引脚测量。

三、外部中断和定时器测量频率

在配置定时器时最重要的就是配置定时器的预分频系数和重装载值。定时器的本质就是一个计数器,计数到我们设定的值后就会溢出,也就是重新从0开始开始计数。设置预分频系数就是设置计数器的频率,假设为71,F1的系统时钟为72M,经过72分频,给计数器的时钟频率就是1M,周期就是1/1M=1us。也是就1us计一个数。那么计几个数呢?这就要看重装载值ARR,这里我们设置为0XFFFF,也就是计数65536个数,就是计满整个寄存器的值。为什么要分频系数为72,重装载值为0XFFFF?这里给出详细的分析过程。

1 为什么要分频系数为72

F1的系统时钟为72M,F1的系统时钟为72M,如果不分频的话,提供给定时器的时钟就直接是72MHZ。72MHz是个什么概念?72MHz它对应的周期就是(1/72000000)秒,也就是计数器从0计数到最大值65535,只需要花费(65535/72000000)秒≈1ms。这句话的意思就是如果你不分频,计数器最大只能定时1ms。那么你的定时器每隔1ms就会溢出一次。如果经过72分频,给计数器的时钟频率就是1M,周期就是1/1M=1us,也是就1us计一个数。换句话就是可以采样的波形频率为1M,提高了采样频率。另一方面也是容易计算,计一个数1us,计count个数就是count个us,频率就是1000000/count(HZ)。

2 为什么要重装载值为0XFFFF

最大采样间隔是跟定时器的中断间隔相关的,定时器产生溢出中断后计数值CNT会自动清0,定时器的中断间隔由分频系数Prescaler和自动重装载寄存器Period决定,分频系数前面已经确定,那最大采样间隔只需要考虑自动重装载寄存器Period的设置,比如频分析系数71,自动重装寄存器值65535,则中断间隔=65536/72000000/72=65.536ms,即最大采样间隔65.536ms,如果65.536ms内没有检测到一个脉冲,则这么设定间隔是不合理的,必须想办法牺牲最小的采样时间1us(扩大分频系数)或者扩大自动重装寄存器值(16位<65535)来增加定时器中断间隔,也可以编写自己的应用函数来计算溢出的定时时间。

一般来说我们使用外部中断是不需要用到定时器的,看原子和野火的外部中断实验也没有用到外部中断。但是现在不是利用外部中断简单的处理一件事,而是利用外部中断测量频率,而测频率就涉及到时间,而只要涉及到时间,就需要用到定时器了。测量外部信号的频率,就是测量PWM波对吧!如果我们测量到一个周期的时间,那么不就知道了信号的频率了吗?

测量方案:在第一次外部中断(上升沿触发)到之时,开启定时器,同时计数器清零。然后等待第二次中断到来,在第二次外部中断(上升沿触发)到之时,获取计数器的计数值,关闭计数器。因为我们知道了计数器计数一个数的时间,所以我们到在第二次外部中断(上升沿触发)到之时,获取计数器的计数值,通过这个值就知道一个脉冲的时间周期。时间周期的倒数就是外部信号的频率。

具体代码如下:

void EXTI0_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line0)!= RESET){EXTI_ClearITPendingBit(EXTI_Line0);//清除EXTI0线路挂起位 if(CaptureNumber == 0)//第1次上升沿触发{TIM_Cmd(TIM2,ENABLE);//使能定时器2TIM_SetCounter(TIM2,0); //清零计数器的值,因为一开始就开始计数了CaptureNumber++;}else if(CaptureNumber==1)//第2次上升沿触发{   TimeCntValue = TIM_GetCounter(TIM2);Capture = TimeCntValue;CaptureNumber = 0; TIM_Cmd(TIM2,DISABLE);//使能定时器2    }} 
}
int main(void)
{ float x;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);delay_init();uart_init(115200);TIM2_Init();TIM1_PWM_Init(7199,0);  //不分频,输出PWM频率=72000K/(7199+1)=10KhzEXTIA0_Init();while(1){printf("Fre=%.2f kHz\r\n",1000000/Capture);delay_ms(1000); }
}

当然你可能觉得这只是测量信号的一个周期脉冲不够准确,那么也可以测量100次脉冲的时间再除以100,就是一个脉冲的时间,然后再取倒数就可以算出频率,这种方法也是可以的。具体代码如下:

void EXTI0_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line0)!= RESET){EXTI_ClearITPendingBit(EXTI_Line0);//清除EXTI0线路挂起位 if(CaptureNumber == 0)//第1次上升沿触发{TIM_Cmd(TIM2,ENABLE);//使能定时器2TIM_SetCounter(TIM2,0); //清零计数器的值,因为一开始就开始计数了CaptureNumber++;}else if(CaptureNumber>0&& CaptureNumber<100){ TimeCntValue0 = TIM_GetCounter(TIM2);   CaptureNumber++;}else if(CaptureNumber==100)//第100次上升沿触发{   TimeCntValue = TIM_GetCounter(TIM2);Capture = TimeCntValue/100;CaptureNumber = 0; TIM_Cmd(TIM2,DISABLE);//使能定时器2    }} 
}int main(void)
{ float x;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);delay_init(); uart_init(115200);   TIM2_Init();TIM1_PWM_Init(7199,0);  //不分频,输出PWM频率=72000K/(7199+1)=10KhzEXTIA0_Init();while(1){printf("Fre=%.2f kHz\r\n",1000000/Capture);delay_ms(1000); }
}

程序流程图

串口打印结果

当然测量信号频率的方法可以直接利用TIM的输入捕获的方法就可以实现。用外部中断只是另一种测量方案,具体用哪一种还要看具体情况。

后台回复:Freqtest,获取测频率代码。

推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

嵌入式Linux

微信扫描二维码,关注我的公众号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/465937.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解Linux内核之内核抢占

1.开场白环境&#xff1a;处理器架构&#xff1a;arm64内核源码&#xff1a;linux-5.11ubuntu版本&#xff1a;20.04.1代码阅读工具&#xff1a;vimctagscscope我们或许经常听说过内核抢占&#xff0c;可是我们是否真正理解它呢&#xff1f;内核抢占和抢占式内核究竟有什么关系…

Python+Selenium学习笔记10 - send_keys上传文件

在火狐浏览器上传文件 上传前&#xff0c;同一个HTML文件在火狐和Edge浏览器显示有些不同 这是Firefox浏览器的显示 这是Edge浏览器 上传后 1 # coding utf-82 3 from selenium import webdriver4 import os5 import time6 7 dr webdriver.Firefox()8 file_path "file:…

不错,又有东西可以领!顺带开发个炫彩灯

什么是涂鸦Arduino SDK?Arduino 是全球最流行的开源硬件平台&#xff0c;涂鸦官方推出的 Arduino 开发驱动库&#xff0c;使用任意 Arduino 开发板涂鸦通用模组即可快速实现设备联网&#xff0c;开发属于自己的 IoT 项目。本次实战营适合你吗?没基础 —— Arduino 开发需要掌…

GNS3从入门到精通

GNS3是一款优秀的具有图形化界面的模拟器。可以运行在多平台上&#xff08;Windows&#xff0c;Linux&#xff0c;MacOS等&#xff09;。其最大的特点就是搭建拓扑极其简单&#xff0c;且支持保存startup-config&#xff0c;供下次实验中继续导入使用&#xff0c;而且所有设备导…

Android App优化之ANR详解

引言 背景:Android App优化, 要怎么做?Android App优化之性能分析工具Android App优化之提升你的App启动速度之理论基础Android App优化之提升你的App启动速度之实例挑战Android App优化之Layout怎么摆Android App优化之ANR详解Android App优化之消除卡顿Android App优化之内存…

面试官让你用C语言实现大数相乘,慌吗?

在之前的笔试题解析里面&#xff0c;我写了大数相加的问题&#xff0c;这里再剖析一个大数相乘&#xff0c;顾名思义&#xff0c;大数相乘就是这个数已经大到最大的数据类型都没有办法保存了。我们看看最大的数据类型可以保存多大的数据。#include "stdio.h" #includ…

每周分享之cookie详解

本章从JS方向讲解cookie的使用。&#xff08;实质上后端代码也是差不多用法&#xff0c;无非读取和设置两块&#xff09; 基本用法&#xff1a;document.cookie"usernamepengpeng"; 修改的时候也是这句&#xff0c;重新赋值即可。 一般的&#xff0c;cookie是记域名的…

每天都用,但是你一定不知道麦克风的灵敏度是什么

我们讨论音频器件的时候&#xff0c;就不得不去讨论灵敏度&#xff0c;麦克风、喇叭、蜂鸣器这些器件都会涉及到灵敏度。灵敏度体现的是输出和输入的关系&#xff0c;因为和声音有关&#xff0c;我们必须要知道声压&#xff0c;理解灵敏度、测量灵敏度&#xff0c;并从中选择合…

邓总的vim配置,需要的自己拿走~

我比较喜欢直接用source insight看代码&#xff0c;不过邓总很喜欢用vim&#xff0c;今天特意让他整理了他的vim 配置&#xff0c;喜欢的同学可以自行下载。在公众号后台回复「vim」获取下载链接VIM 配置查看本机 VIM cscope ctagsvim Ubuntu自带&#xff0c;cscope 、ctags…

html嵌套html解决办法(object/object)

后台管理系统多用到了页面嵌套页面的场景&#xff0c;下面是我在工作中解决的方法&#xff0c;利用<object></object>的data属性&#xff0c;下面试w3c的介绍&#xff1a; data 属性用于指定供对象处理的数据文件的 URL。 该属性的值是文件的 URL&#xff0c;该 UR…

原来,我有这样期望

我妈小时候一直跟我们讲他们村里的一个人&#xff0c;我妈说他们家特有钱&#xff0c;他们家的粮仓里装满了花生和大米&#xff0c;而让他们变得这么富有的原因是—勤劳和省。我还上小学那几年&#xff0c;水稻的产量很低「袁隆平的杂交水稻是在后来几年才普及的」&#xff0c;…

详解SMS下OSD2008

百忙有闲来和大家分享一下接着上篇的SMS的一个应用—OSD&#xff0c;长话短说&#xff0c;开始吧&#xff01;老规矩&#xff0c;先看看拓扑图在来构思下要做的步骤&#xff0c;一 、SMS-OSD前的装备工作 二 、安装OSD组件 三 、创建捕获光盘 四 、捕获模板系统镜像 五 、创建、…

一文读懂 | 进程怎么绑定 CPU

昨天在群里有朋友问&#xff1a;把进程绑定到某个 CPU 上运行是怎么实现的。首先&#xff0c;我们先来了解下将进程与 CPU 进行绑定的好处。进程绑定 CPU 的好处&#xff1a;在多核 CPU 结构中&#xff0c;每个核心有各自的L1、L2缓存&#xff0c;而L3缓存是共用的。如果一个进…

Spark天堂之门解密

本课主题 什么是 Spark 的天堂之门Spark 天堂之门到底在那里Spark 天堂之门源码鉴赏引言 Spark 天堂之门就是SparkContext&#xff0c;这篇文章会从 SparkContext 创建3大核心对象 TaskSchedulerImpl、DAGScheduler 和 SchedulerBackend 开始到注册给 Master 这个过程中的源码鉴…

C语言,使用union了解内存

今天一个读者朋友给我发的一段代码&#xff0c;这段代码让他有了疑惑。代码如下&#xff1a;#include "stdio.h" int main() {typedef union{short i;char j[2];}DATA;DATA a;a.j[0] 10;a.j[1] 1;printf("%x\n",a.i);return 0; }他的几个测试代码以及输出…

我做技术的这十年,我不做技术的这一年~

我和明哥认识是因为之前他在群里跟我们分享一件事情&#xff0c;当时因为明哥相信网上认识的一个朋友&#xff0c;说是要一起开发一个项目&#xff0c;结果他被骗了几万块钱。然后聊着聊着&#xff0c;我觉得明哥太实诚了&#xff0c;后面继续接触&#xff0c;知道他做的一些决…

Oracle Golden Gate概要

Oracle GoldenGate简介 Oracle Golden Gate用于源数据库与目标数据库的数据复制备份&#xff1b;可以在异构的环境(各种操作系统和数据库)之间实现数据亚秒级的实时复制备份&#xff1b;以及可以在实时数据仓库、数据同步、集中/分发、容灾、数 据库升级和迁移等多个场景下应用…

android 音频加载hal so调试

1. 整个加载流程图 2. 加载hal so的代码位置 2.1 在audiopolicymanager中的加载位置 diff --git a/frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp index 632290a9…

鸿蒙的路还很长

这是昨晚看到我的老领导发的和鸿蒙有关的文章&#xff0c;我在下面评论了&#xff0c;作为科技自媒体屌丝本屌&#xff0c;我昨晚也是看了鸿蒙2.0的发布会&#xff0c;也有一些观点。鸿蒙OS是什么&#xff1f;鸿蒙os是一个操作系统&#xff0c;而且是面向智能终端的&#xff0c…

有关Accordion组件的研究——Silverlight学习笔记[27]

Accordion组件在开发中常用于信息的分类显示。本文将为大家介绍该组件的特性以及通过一个实例讲述该组件的基本运用。组件所在命名控件&#xff1a;System.Windows.Controls组件常用方法&#xff1a;SelectAll&#xff1a;选择所有位于Accordion组件中的Accordion项。&#xff…