文章目录

  • 背景
  • 一、硬件驱动器
  • 二、软件驱动器

本文记录一段旅程–控制无源蜂鸣器嗡嗡作响。

背景

小时候,看科幻电影都是瞪大了眼睛,竖直了耳朵,生怕错过了精彩的情节。仙女座星系,宇宙飞船距某颗类地行星10千公里;南极圈,海冰和冰山在海平面上肆意分布,科考船即将抵达科考站。忽然,飞船,科考船报警器发出间断性的低鸣,各仓位笼罩在频闪的红光中,飞船即将与一颗陨星交汇,必须要尽快脱离当前轨道;科考船前方3公里的冰山海平面以下的结构会对科考船造成威胁,必须重新规划路线。舰长召集大副,领航员快速拟定应对计划,各部门紧急就位,齐心协力,化险为夷。
我决定使用手里的资源模拟报警器的运作方式。这次的主角是声音,而声源是一颗无源蜂鸣器,还是拿出手里的51单片机开发板,泡上一杯茶,开始一段新的旅行。


一、硬件驱动器

先看一看开发板原理图中关于蜂鸣器的部分。器件的驱动顺序是:单片机IO->ULN2003D->蜂鸣器。


我不知道ULN2003D是如何驱动蜂鸣器工作的,需要先了解一下ULN2003D的信息做好攻略再下手。看了一眼开发板上实际用的芯片是ULN2003A,因此查也应该查ULN2003A,不然可能因为一些细节问题抓耳挠腮自找无趣。查了一下数据手册,自我感觉有两张图比较重要。第一个是下面的芯片内部框图。

可以看到这芯片其实由7组相同的基本单元组成,不言而喻,另一个重要的是基本单元的描述图。

通过上面的信息加上一点点手册上的细节,可以产生新的两张图,一张用来描述这个蜂鸣器电路的硬件电气特性,一张用来描述它的数字特性。

我只想知道怎么使用这个芯片,所以我只关注ULN2003A的传输特性。手册中Iin最大值是25mA,意味这它不希望前端提供过大的电流把它冲烂,Iout最大值为500mA意味着当负载工作需要的电流大于500mA时,驱动器会表示它无能为力,可究竟蜂鸣器需要多大的驱动电流我也不知道,因为没找到相应的手册,只能在实际工况测试一下满不满足。

接下来是蜂鸣器模块的数字特性。芯片内部框图中特意描述的续流二极管我给省去了,因为我觉得作者的意图是想告诉我当应用于感性负载时你应该把这个续流二极管用起来,这属于电气特性。5V,TTL电平意味着我可以不加思考地将STC89C52单片机的IO口连到达林顿管的输入管脚上;建立时间限制了驱动器能跟踪输入信号的最大频率,否则输出会根据输入信号的频率与这个临界值的差值大小会产生不同程度的失真,再深究下去就要把拉普拉斯请出来了,还是不麻烦他老人家了。

无源蜂鸣器的激励信号并不是简单的逻辑1或逻辑0,而是具有一定频率的激励源,因为声音是物体机械震动产生的。但究竟这个激励源是正弦波还是方波还是什么别的稀奇古怪的东西就不得而知,遇事不决先看攻略,打开demo,先ctrl+c,ctrl+v再说。然后我就搞清楚了demo里面是用方波来驱动无源蜂鸣器的,然而具体别的波行不行,我暂时也不在乎。demo里面给的驱动信号是像下面这个样子的。

这是一个500Hz的方波信号,持续一段时间之后就停了,或许是怕一直响扰民被投诉,也或许是自己听多了都上头,总的来说考虑得还是比较周到。

二、软件驱动器

设计软件之前先重新泡一杯茶。就好像旅途中准备去下一个目的地之前总是想先找点东西填肚子喝点水才有劲一样,看着一片片茶叶在茶汤中慢慢落下总有说不出的惬意。

出发前回顾一下目的地在哪里。飞船里的报警器的声音有缓和一点的,也有急促一点的,我应该同时包含几种不同蜂鸣间隔的报警音。还有就是demo里面发出来的声音音调和我在电影里听到的也不太一样,也得试出来。所以我决定走下面图示的路线。

通过多路复用器可以选择要关闭报警还是其他什么花里胡哨的报警类型,为了让人能明显听得出蜂鸣器在响一下停一下,这些间隔时间是100ms起步的,然后复用器输出与蜂鸣器的发声驱动信号相与得到实际的蜂鸣器驱动信号,目的地就确定下来了。至于使用一个定时器Timer0作为脉冲的时基,一是为了节约定时器,二是为了方便修改时间参数。

虚拟定时器在前一篇《【电子电路基础实验】LED闪烁实验》中已经提到过了,就不说了。

虚拟逻辑多路复用器是这次要建造的新的工具。

  1. selfActionSignal是自身动作信号,顾名思义,当自己的输出发生变化时,复用器会设置这个标志来通知别的虚拟器件,至于别的器件作什么响应跟自己没有关系,反正我通知到了。
  2. currentOutput就是我自己当前的实际输出。
  3. 枚举变量linkCH用来确定currentOutput是链接到的哪个输入通道。
  4. inputChannel1-3就是复用器的3个输入端口。
  5. step函数,让复用器完整地运行一次,获取输入,更新输出。
  6. processSignal函数,看一看当前输出端口应该链接到哪个输入端口。
typedef struct logicMUX          //逻辑复用器
{unsigned char selfActionSignal;unsigned char currentOutput;enum{OUT_LINK_TO_CH1 = 0,OUT_LINK_TO_CH2,OUT_LINK_TO_CH3,}linkCH;unsigned char inputChannel1;unsigned char inputChannel2;unsigned char inputChannel3;void (*step)(struct logicMUX* multiplexer);void (*processSignal)(unsigned char signal, unsigned char value, struct logicMUX* multiplexer);
}logicMUX_t;

尽管c语言中有逻辑与运算“&&”,但是我还是需要将它显式地表达出来,以构造完备的虚拟逻辑器件集和。

uint8_t logic_and2Input(uint8_t a, uint8_t b)
{if(a && b)return 1;elsereturn 0;
}

新的设备到这里就介绍完成了,接下来就可以构造整个工程的代码了。

#include "reg52.h"#define NULL 0
//理论11.0592MHz晶振,400us溢出一次,定时器数367个数
//实际1.198us数一个数,计满400us要数334个数
#define TIMER0_MIDDLE_VALUE 334
/********************************************************************** TYPEDEF*/
typedef unsigned int uint16_t;
typedef unsigned char uint8_t;typedef struct virtualTimer       //虚拟定时器,定时时间与使用的定时器定时间隔有关系
{unsigned short counter;unsigned short reloadTime;unsigned short reloadFlag;
}virtualTimer_t;typedef struct logicMUX         //逻辑复用器
{unsigned char selfActionSignal;unsigned char currentOutput;enum{OUT_LINK_TO_CH1 = 0,OUT_LINK_TO_CH2,OUT_LINK_TO_CH3,}linkCH;unsigned char inputChannel1;unsigned char inputChannel2;unsigned char inputChannel3;void (*step)(struct logicMUX* multiplexer);void (*processSignal)(unsigned char signal, unsigned char value, struct logicMUX* multiplexer);
}logicMUX_t;
/********************************************************************** VIRTUAL DEVICE*/
//virtual timers
#define BEEP_BLINK_PERIOD1    1000    //1000*400us=400000us = 400ms
#define BEEP_BLINK_PERIOD2    3000    //3000*400us=1200000us=1.2s
#define BEEP_DRIVE_PERIOD     10      //value*0.4ms = 4ms -> 驱动频率的倒数sbit BEEP=P2^5;    //将P2.5管脚定义为BEEP
uint8_t beepDriveSignal = 0;
uint8_t beepBlinkSignal1 = 0;
uint8_t beepBlinkSignal2 = 0;
virtualTimer_t beepDriveTimer = {BEEP_DRIVE_PERIOD,BEEP_DRIVE_PERIOD,1};
virtualTimer_t beepBlinkTimer1 = {BEEP_BLINK_PERIOD1,BEEP_BLINK_PERIOD1,1};
virtualTimer_t beepBlinkTimer2 = {BEEP_BLINK_PERIOD2,BEEP_BLINK_PERIOD2,1};//virtual logic MUX
void beepSignalMUXStep(struct logicMUX* multiplexer);
void beepSignalMUXStepprocessSignal(unsigned char signal, unsigned char value, struct logicMUX* multiplexer);
logicMUX_t beepSignalMUX =
{0,  //selfActionSignal0,  //currentOutputOUT_LINK_TO_CH2,0,0,0,    //input signalbeepSignalMUXStep,beepSignalMUXStepprocessSignal
};//logic and
uint8_t logic_and2Input(uint8_t a, uint8_t b);/********************************************************************** FUNCTION BODY*/
//function entry
void main(){    TMOD = 0x01;        //T0方式1->16位不自动重装定时器TMOD &= ~(1<<2);    //T0定时器模式TMOD &= ~(1<<3);    //T0启停仅受TCON的TR0控制TH0 = (65535 - TIMER0_MIDDLE_VALUE) / 256;    TL0 = (65535 - TIMER0_MIDDLE_VALUE) % 256;ET0 = 1;            //T0中断使能EA = 1;             //总中断使能TR0 = 1;            //T0使能while(1){                        }
}//sub function for virtual logic MUX
void beepSignalMUXStep(struct logicMUX* multiplexer){switch((uint8_t)(multiplexer->linkCH)){case (uint8_t)OUT_LINK_TO_CH1:multiplexer->currentOutput = multiplexer->inputChannel1;break;case (uint8_t)OUT_LINK_TO_CH2:multiplexer->currentOutput = multiplexer->inputChannel2;break;case (uint8_t)OUT_LINK_TO_CH3:multiplexer->currentOutput = multiplexer->inputChannel3;break;default:break;}multiplexer->selfActionSignal = 1;
}void beepSignalMUXStepprocessSignal(unsigned char signal, unsigned char value, struct logicMUX* multiplexer){if(signal){switch(value){case 0:multiplexer->linkCH = OUT_LINK_TO_CH1;break;case 1:multiplexer->linkCH = OUT_LINK_TO_CH2;break;case 2:multiplexer->linkCH = OUT_LINK_TO_CH3;break;default:break;}}
}//logic and
uint8_t logic_and2Input(uint8_t a, uint8_t b){if(a && b)return 1;elsereturn 0;
}//时基, 400us中断一次
void T0_interrupt(void) interrupt 1 {   //至多211.58usTH0 = (65535 - TIMER0_MIDDLE_VALUE) / 256;   //重装初值TL0 = (65535 - TIMER0_MIDDLE_VALUE) % 256;if(beepDriveTimer.counter != 0)--beepDriveTimer.counter;if(beepBlinkTimer1.counter != 0)--beepBlinkTimer1.counter;if(beepBlinkTimer2.counter != 0)--beepBlinkTimer2.counter;//更新需要重载的虚拟定时器if(!beepDriveTimer.counter && beepDriveTimer.reloadFlag)beepDriveTimer.counter = beepDriveTimer.reloadTime;if(!beepBlinkTimer1.counter && beepBlinkTimer1.reloadFlag)beepBlinkTimer1.counter = beepBlinkTimer1.reloadTime;if(!beepBlinkTimer2.counter && beepBlinkTimer2.reloadFlag)beepBlinkTimer2.counter = beepBlinkTimer2.reloadTime;//产生信号源if(beepDriveTimer.counter > BEEP_DRIVE_PERIOD / 2)       //蜂鸣器驱动信号beepDriveSignal = 1;elsebeepDriveSignal = 0;if(beepBlinkTimer1.counter > (BEEP_BLINK_PERIOD1 / 2))   //蜂鸣器闪烁信号1beepBlinkSignal1 = 1;elsebeepBlinkSignal1 = 0; if(beepBlinkTimer2.counter > BEEP_BLINK_PERIOD2 / 3)    //蜂鸣器闪烁信号2beepBlinkSignal2 = 1;elsebeepBlinkSignal2 = 0;//逻辑复用器工作//beepSignalMUX.processSignal(0,0,&beepSignalMUX);      //复用器截获通道选择信号并修改通道值beepSignalMUX.inputChannel1 = 0;                      //复用器抽取输入beepSignalMUX.inputChannel2 = beepBlinkSignal1;beepSignalMUX.inputChannel3 = beepBlinkSignal2;beepSignalMUX.step(&beepSignalMUX);                 //复用器运行一次,更新输出  //与非门工作,更新蜂鸣器驱动信号BEEP = logic_and2Input(beepSignalMUX.currentOutput,beepDriveSignal);//回收各逻辑器件的动作信号beepSignalMUX.selfActionSignal = 0;
}

至于定时器0为什么要设置成400us中断一次,为什么蜂鸣器的驱动频率是250Hz,为什么设置400ms和1.2s的闪烁周期,为什么要把所有的函数都写在中断函数里面,这样会不会出问题,我是怎么知道时钟频率有误差的,且听我娓娓道来。

  1. 定时器0为什么要设置成400us中断一次?
    在构造程序的时候先随便设置一个中断时间,比如100us,当然这个间隔要足以为系统中最高频率的虚拟定时器服务,比如我认为我的最高频率有可能到500Hz,2ms周期,那么100us=0.1ms的中断间隔凭第六感感觉就行,实际行不行还得实际试。
    设置一个初步的中断时间之后就暂时不管了,编辑完所有的其他部分的代码,最后来看看中断服务程序里面的代码段全部执行一轮需要多长时间,比如上面的定时器0的中断服务程序跑完最多(确保所有最长的条件分支得到执行)需要花费211.58us,那么在这种情况下,100us中断一次就显得不合适,毕竟上一次中断服务还没结束这一次中断就来了显得有些愚蠢,至少应当保证中断间隔大于等于中断服务程序的最大时间,但是如果两个时间相等,那么主程序就永远得不到执行了,毕竟一直在被打断,所以还是要留出一点冗余时间让cpu干点主循环的活。
    折个衷,就定了个400us,因为300us不容易算出整数,这让我很恼火。
    下面是上面的代码的主循环和中断对cpu占用的时间流图。这里又引出了另一个值得探讨的问题,假如说生产者会在定时器中断函数中会获取一些信息,而主循环中有某个消费者中会使用这些信息,假设在下图的条件下主循环跑完一轮需要1s,那么主循环会在发生若干次中断后才能完整地执行一轮,出现供过于求的情况,为了维系稳定,生产者把获取的信息扔掉那消费者获取的信息就会脱节,最后冒出各种各样的问题。显然时间协调对于构造系统来说很重要,需要刻意地去处理,搞一刀切坚决不在中断中处理过多的东西就高枕无忧的行为绝不可取。
  1. 为什么蜂鸣器的驱动频率是250Hz,为什么设置400ms和1.2s的蜂鸣器闪烁周期?
    因为我试了一下感觉这样和电影里面比较像,感觉可以就可以。
  1. 为什么要把所有的处理都写在中断函数里面,这样会不会出问题?
    首先这样做在这个工程中不会出问题,在第一个问题中有所讨论。但是不同的工程情况不同,当有中断嵌套的时候事情就没那么容易把握了,目前我还没找到控制这种复杂度的方法,坚持下去肯定会找到的。
    所有的处理语句都写在中断函数中是因为定时器打断主循环的时机是任意的,更新驱动输出的代码放在主循环中会造成蜂鸣器的驱动信号更新时间不确定,产生下面图示的驱动频率不稳定的现象。

    观察下面的伪代码,更新蜂鸣器的驱动信号是在主循环的step3进行的,假设定时器中断运行完之后,下一次中断来临之前主循环能跑完两轮,以确保定时器更新以后主循环一定会紧接着更新输出。定时器在驱动信号刚好更新输出结束的语句和驱动信号即将更新前的语句两个不同的位置打断主循环,两个位置更新输出会产生step4+step1+step2的运行时差,如果时差足够短,蜂鸣器的驱动频率就是稳定的,耳朵听不出来蜂鸣器抖动,否则耳朵会告诉你你该修改程序了。
void main(void){//初始化while(1){//step1:产生信号源//step2:逻辑复用器工作//step3:与非门工作,更新蜂鸣器驱动信号//step4:回收各逻辑器件的动作信号}
}
void T0_interrupt(void) interrupt 1 { //重载定时器//虚拟定时器的计数器自减//更新需要重载的虚拟定时器
}
  1. 我怎么知道时钟不准。
    按精准的11.0592MHz晶振计算,定时器数一个数要花1.09us的时间,计满400us大约数367个数。那么要计满100ms就需要计400us时间单位250次,可实际情况并非如此。计满250次实际延时了110ms,则定时器440us溢出一次,反推回去定时器应该是1.198us数一个数,说明时钟不准。

【电子电路基础实验】无源蜂鸣器相关推荐

  1. 【电子电路基础实验】数码管

    文章目录 前言 一.硬件特性 二.软件驱动器 1.软件与数码管的接口 2.多路数据块复用器 3. 回到开头的进度条 文章记录一段旅程–使用数码管显示它能显示的任何内容. 前言   数码管的特点是比较亮 ...

  2. 【电子电路基础实验】LED点阵(上--硬件部分)

    文章目录 背景 主题活动 一. 74HC595和LED点阵之间的电气与数字接口 电气接口 数字接口 记录一段旅程–使用74HC595串转并8位移位寄存器来控制LED点阵. 背景 点亮了一颗led,就会 ...

  3. 【电子电路基础实验】LED闪烁实验

    目录 背景 硬件驱动器 软件驱动器 像航障灯那样闪烁 想怎么闪就怎么闪 本文记录一段旅程–让一颗LED灯闪烁起来. 背景 我随手拿了一块普中的51单片机的开发板,上面恰好有很多led灯,我想让其中一个 ...

  4. 计算机电路基础放大电路实验,计算机电路基础实验报告.doc

    计算机电路基础实验报告 班级姓名学号 实验一戴维南定理的实验 实验一 戴维南定理的实验 71计算公式: 71 计算公式: 一.预习要求 复习戴维南定理的内容,实验前将步骤4计算结果填入表格,并注意思考 ...

  5. 计算机电路基础实验一仪器使用,计算机电路基础实验指导.doc

    计算机电路基础实验指导 <> 实 验 指 导 计算机科学与技术系 2014年4月 课程编码: 教学课时数:学时,其中课堂讲授学时,实验12学时. 学分:学分 适用专业: 开课单位:撰写人: ...

  6. 模拟电子电路基础——放大器理解与梳理

    本文以杭州电子科技大学刘圆圆老师主编的<模拟电子电路基础>为基础,结合清华大学电子学教研组编著的<模拟电子技术基础>进行相关知识的整理理解分析 放大电路 放大电路能够线性或者非 ...

  7. 红警职教智能硬件电子电路基础版教材与配套视频资源即将开发完毕

    经过数月的精心准备,红警智能硬件电路基础与无人机可视化编程视频与在线直播课程即将上线啦~~~! 本系列课程是由公司硬件工程师团队自主开发并分享,适合零基础,电子电路基础等内容,了解智能家居.智能交通. ...

  8. 电子电路基础 (1)——电子电路常识

    一.电子电路常识 原理图 万用表 示波器 1.1 电流 电荷运动称为电流,即电荷在导体内有规律的流动称为电流.电流用I表示,单位为安培,用A表示. 1.2 电子电路 电子电路就是由电阻.线圈.电容.和 ...

  9. 计算机电子电路基础教程视频,电子电路基础详细视频教学(100集)

    本教学视频通过强调电子电路系统设计者所需的实用方法,即对电路的基本原理.经验准则以及大量实用电路设计技巧的全面总结,侧重探讨了电子学及其电路的设计原理与应用.它不仅涵盖了电子学通常研究的全部知识点,还 ...

最新文章

  1. Android超简单的进度条源码
  2. C# -- 文件的压缩与解压(GZipStream)
  3. Coriant助力Aureon部署100Gbps光纤网络
  4. 【算法】 哈希表 自己模拟hashMap
  5. linux进程被杀掉日志,Linux进程突然被杀掉(OOM killer),查看系统日志
  6. python是什么类型的编程语言-2.python是什么编程语言。
  7. 显示2位小数 python3_python3+ 和 Python2+的一些区别
  8. DBC 2000 安装
  9. 鸿蒙系统的特点,鸿蒙系统有什么特别之处
  10. 一峰说:SpringCloud的基础了解和使用
  11. 上拉/下拉电阻选值怎么定
  12. Web Directions South 2012的四个大创意
  13. 白嫖阿里-----搭建个人服务
  14. oracle12c配置安装,oracle12c安装配置
  15. 智能电话机器人介绍(AI语音机器人)
  16. 算法 | 虚树学习笔记
  17. 不同主体的微信小程序相互跳转
  18. labspec6教程_LabSpec6软件功能–光谱扫描方式-Horiba.PDF
  19. 深度学习目标检测论文1(YOLOv1论文的翻译)
  20. 骑士周游(马走棋盘)及剪枝分析

热门文章

  1. rowspan table布局_table合并单元格colspan和rowspan
  2. 《天龙八部》之《苏幕遮》
  3. jzoj5920. 【NOIP2018模拟10.21】风筝(dp,最长上升子序列)
  4. 通信工程本科可以考计算机研究生吗,本科的通信有哪些专业考研
  5. 再谈CPU使用率100%的问题
  6. Google Cloud Spanner 究竟是什么?
  7. 关于积分过期的设计思路方案(原创)
  8. 如何使用小程序云开发实现一个简单的留言板
  9. pppoe错误代码 linux,PPPoE常见拨号错误代码分析及解决办法
  10. 蓝桥杯 历届真题 耐摔指数【第九届】【省赛】【C组】C++ 动态规划