写这篇文章,纯粹是想为博客拉点点击量。在博客园,游客访问好像是不计入阅读量的,而作为一个十八线博主,注册用户的访问应该以搜索引擎为主,博客园首页为次,个位数的粉丝就别谈了。

所以,希望各位从搜索引擎点进来的朋友,多多评论,有问题咱们一起讨论。

我写过AVR单片机教程,设计过自己的Arduino板,希望你相信我能给你带来收获。

我不想听你放那么多屁,我只想知道周期为1ms的定时器中断怎么写!

什么是定时器

在ATmega328P单片机中,定时/计数器(Timer/Counter)是这样的组件:它需要一个时钟源,驱动一个8或16位的计数器递增或递减,当计数器等于一个值时,会触发一些操作,如产生中断、翻转引脚电平等。由于定时器的时钟源是系统时钟或外接晶振(一种产生频率精准的波的器件)分频得到的,一旦设置好定时器的工作参数,直到下次调整参数,定时器都会按照预期工作,与CPU执行的代码无关。

为什么要用定时器

之前有过这样的经历:跟一个优秀作品设计者聊了几句,他说同时控制舵机和扬声器很难控制好延时,扬声器输出的音乐节奏会乱。我第一反应当然是他没有用定时器中断,一问果然如此,并且他不知道中断也不知道定时器。

还有一位同学,写TI计算器的程序。在他的一个作品中,每次循环的计算量不定,循环间隔也不定,导致游戏效果不好。他的解决方法是根据计算量计算出循环最后需要的延时,使得循环间隔基本保持不变。

这种思路是相当优秀的。但是如果有定时器可用的话,编程难度会降低,循环间隔的一致性也会更好,是更加优秀的解决方案。

其实你一直在用定时器

Arduino Uno Rev3的3、5、6、9、10、11号端口可以使用analogWrite和tone函数,它们的功能都是利用定时器实现的。用函数确实方便,但是只知使用而不知其原理就只能停留在技术的表面——Arduino的强大封装对开发者的学习有两面性。

定时器其实不知道什么3号端口,它只知道OC2B。两种表示之间的对应关系如下表:端口编号硬件符号3PD3(PCINT19/OC2B/INT1)

5PD5(PCINT21/OC0B/T1)

6PD6(PCINT22/OC0A/AIN0)

9PB1(PCINT1/OC1A)

10PB2(PCINT2/SS/OC1B)

11PB3(PCINT3/OC2A/MOSI)

寄存器

寄存器是开发者与硬件打交道的方式。从编程的语法上,可以把寄存器当作是变量,可以对它赋值,也可以读取它的数值。

寄存器中的位有几种不同的组织结构,它们的存取方式也不尽相同:

TCCR1B寄存器中有4组参数:ICNC1、ICES1、WGM1[3:2]、CS1[2:0]。现在你完全无需理解这些字母的含义,但是得对这些数字有个概念:WGM1[3:2]表示从WGM13到WGM12;TCCR1B中的1表示该寄存器属于定时器1,ICNC1和WGM13等名字中的1也是;CS12中的2表示该位为CS1[2:0]位域(bitfield)中的第2位(最低位为第0位)。

ICNC1和ICES1都是1位的位域,它们的值可以是0或1;WGM1[3:2]是2位的位域,它的值可以是00、01、10、11;CS1[2:0]同理。

你也许一眼就能看出二进制的11在十进制中是3,但是你很可能看不出23对应10111。在Arduino编程中(语言为C++),二进制数可以直接写,无需与十进制或十六进制转换。Arduino提供的方法是B10111,GCC提供的是0b10111(0b前缀字面量是C++14标准才规定的)。后者是我一直以来的习惯。

假如我要把这4个参数分别写为1、0、0b00、0b101,就要写:

TCCR1B =     1 << ICNC1

|     0 << ICES1

|  0b00 << WGM12

| 0b101 << CS10

;

全是0的可以不写,写是为了可读性。ICNC1是寄存器的第7位,所以代码中它的值就是7,其他位同理。

如果要判断ICNC1位是否为1:

if (TCCR1B & 1 << ICNC1)

// ...

如果要读取WGM1[3:2]位:

uint8_t wgm = (TCCR1B & 0b11 << WGM12) >> WGM12;

有的位因为不存在而不能写,如TCCR1B的第5位;有的位即使存在但是只读所以也不能写;有的位域分布于多个寄存器中,如WGM1[3:0],低两位在TCCR1A,高两位在TCCR1B。

除了一个或多个位的位域以外,有些寄存器是整体使用的:

可以直接当变量读写:

OCR0A = 233;

uint8_t ocr0a = OCR0A;

还有16位寄存器,虽然读写不能用一句汇编搞定,但是高级语言层面上可以:

TCNT1 = 10086;

uint16_t tcnt1 = TCNT1;

不超过255的话可以只写低字节TCNT1L。

定时器相关寄存器总览:

定时器的工作模式

读数据手册无疑是深入了解单片机的最好方法,可惜很多人没这个耐心,几十页的英语也不是每个人都吃得消的。有些中文书打着介绍AVR单片机的幌子翻译数据手册,不仅没有营养还漏洞百出,我不也推荐。写这篇文章,也有避免后人重蹈覆辙的目的。

当然,除了有代码示例以外,本文再“详解”也详细不过数据手册,不过至少可以让你对定时器有个大致的印象,不致于让你读的时候一头雾水。

ATmega328P有3个定时器:定时器0、定时器1和定时器2(简单粗暴)。0和2都是8位的,2支持异步工作;1是16位的,精度更高,支持更多工作模式。我接触过其他型号的单片机,AVR的定时器是相对简单的。

定时器有3种工作模式:普通模式、CTC模式、PWM模式,其中PWM还分快速PWM、相位矫正(波形居中)PWM、相位与频率矫正PWM(频率可以任取,仅限定时器1)。

先讲各种模式中共通的部分。定时器需要一个时钟源,它可以是:时钟源适用范围无所有

clkI/O/N,N=1,8,64,256,1024clkI/O/N,N=1,8,64,256,1024

(clkI/OclkI/O为系统时钟,16MHz)定时器0/1

T0(4)引脚上升/下降沿定时器0

T1(5)引脚上升/下降沿定时器1

clkT2S/N,N=1,8,32,64,128,256,1024clkT2S/N,N=1,8,32,64,128,256,1024

(clkT2SclkT2S为系统时钟或外置32kHz晶振)定时器2

工作模式之间的区别在于计数器的变化方向与范围,介绍之前需要先下3个定义:名称描述BOTTOM0,计数器的最小值

MAX对8位定时器为0xFF,对16位定时器为0xFFFF,计数器的最大可能值

TOP计数器达到这个值时,可能会被清零,或变化方向改变

对定时器0和2,可以为MAX或OCRnA

对定时器1,可以为0x00FF、0x01FF、0x03FF、OCR1A或ICR1普通模式中,计数器从0开始增长到MAX,然后溢出回到0,周而复始。频率为(clkclk为定时器时钟频率)

clkMAX+1clkMAX+1

CTC模式和快速PWM模式中,计数器从0开始增长到TOP,然后不再继续增长而是直接回到0,重新开始增长。频率为

clkTOP+1clkTOP+1

两种相位矫正PWM模式中,计数器从0到TOP,再从TOP回到0,如此循环。频率为

clk2TOPclk2TOP

计数器比较

当计数器的值与OCRnA或OCRnB相等时,可以对OCnx的电平进行一些操作。所有模式下,OCnx都可以不连接定时器。

非PWM模式下,可以把OCnx置为低电平、高电平或翻转电平,tone就是这样实现的;

PWM模式下,有正相和反相两种模式,正相为OCRnx越大占空比越高,analogWrite就是这样实现的;反相反之;有些配置下OCnA可以被翻转,请参考数据手册。

由于引脚电平可以有宏观表现,我们终于可以开始写代码了。

先试试tone。在9号端口上连接一个蜂鸣器,使用定时器1的CTC模式,产生440Hz方波:

void setup() {

pinMode(9, OUTPUT);

TCCR1A = 0b01 << COM1A0 | 0b00 << WGM10;

TCCR1B = 0b01 << WGM12 | 0b001 << CS10;

OCR1A = 18181;

}

void loop() {

}

OCR1A = 18181是怎么来的呢?每次计数器与OCR1A相等电平翻转,两次为一周期,频率为clk2(OCR1A+1)clk2(OCR1A+1)。先取clkclk为不分频试试,算出OCR1A为18181,没有超过最大值65535,因此就取这个。如果超过了,就要把定时器频率下调,直到OCRnx合理为止。

void setup() {

pinMode(3, OUTPUT);

TCCR2A = 0b10 << COM2B0 | 0b11 << WGM20;

TCCR2B = 0 << WGM22 | 0b100 << CS20;

}

int brightness = 0;

int fadeAmount = 5;

void loop() {

OCR2B = brightness;

brightness = brightness + fadeAmount;

if (brightness <= 0 || brightness >= 255)

fadeAmount = -fadeAmount;

delay(30);

}

如果要让程序以频率为参数计算出合适的分频系数与OCRnx值,可以参考tone的实现。

再试试analogWrite。在3号端口上连接一个LED,使用定时器2的快速PWM模式,实现呼吸灯的效果:

在快速PWM模式中,正相输出占空比不能为0,反相输出占空比不能为1,如果要达到这两个值,需要断开引脚与定时器的连接,用digitalWrite等方法输出。

定时器中断

懒得写了,我抄我自己:中断,是单片机的精华。

当一个事件发生时,CPU会停止当前执行的代码,转而处理这个事件,这就是一个中断。触发中断的事件成为中断源,处理事件的函数称为中断服务程序(ISR)。

中断在单片机开发中有着举足轻重的地位——没有中断,很多功能就无法实现。比如,在程序干别的事时接受UART总线上的输入,而uart_scan_char等函数只会接收调用该函数后的输入,先前的则会被忽略。利用中断,我们可以在每次接受到一个字节输入时把数据存放到缓冲区中,程序可以从缓冲区中读取已经接收的数据。

AVR单片机支持多种中断,包括外部引脚中断、定时器中断、总线中断等。每一个中断被触发时,通过中断向量表跳转到对应ISR。如果一个中断对应的ISR不存在,链接器会把复位地址

[1] [2]

atmega328p引脚图_ATmega328P定时器详解相关推荐

  1. 0809连接单片机c语言,adc0809引脚图及功能详解,adc0809与51单片机连接电路分析

    描述 adc0809是采样频率为8位的.以逐次逼近原理进行模-数转换的器件.其内部有一个8通道多路开关,它可以根据地址码锁存译码后的信号,只选通8路模拟输入信号中的一个进行A/D转换. 1.主要特性 ...

  2. adc0809引脚图及功能详解

    下载地址:http://www.51hei.com/bbs/dpj-56180-1.html adc0809是采样频率为8位的.以逐次逼近原理进行模-数转换的器件.其内部有一个8通道多路开关,它可以根 ...

  3. STM32 定时器详解

    STM32 定时器详解 吃了一个猛亏,自己理解花了大半天时间,结果一看代码发现巨简单 算了,把自己理解的放上来吧 目录 STM32 定时器详解 前言 一.定时器种类和区分 二.时钟源 三.计数过程 3 ...

  4. 不带头节点的链表有哪些缺点_23张图!万字详解「链表」,从小白到大佬!

    链表和数组是数据类型中两个重要又常用的基础数据类型. 数组是连续存储在内存中的数据结构,因此它的优势是可以通过下标迅速的找到元素的位置,而它的缺点则是在插入和删除元素时会导致大量元素的被迫移动,为了解 ...

  5. Jmeter性能测试工具Timer定时器详解

    jmeter提供了很多元件,帮助我们更好的完成各种场景的性能测试,其中,定时器(timer)是很重要的一个元件,jemter提供了9种定时器,下面一一介绍: 一.定时器的作用域 1.定时器是在每个sa ...

  6. 雪碧图PHP,Webpack中雪碧图插件使用详解

    背景 在开发过程中,我们需要用到很多图标,这些图标的大小不是很大,但是每次需要向服务器发送请求,从而加重服务器的负担,尤其是当网站处于高访问量的情况下或网络不稳定的时候,服务器性能会明显下降.这种情况 ...

  7. hugegraph图数据库索引详解

    hugegraph图数据库索引详解 版权声明:转载请注明出处 https://blog.csdn.net/u010260089/article/details/86712983 前言 在<技术文 ...

  8. 图之邻接矩阵详解(C语言版)

    文章目录 一.定义 二.结构 三.常用操作 结语 附录 一.定义 图的邻接矩阵是一种采用邻接矩阵数组表示顶点之间相邻关系的存储结构.设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:       ...

  9. 原创:Spark中GraphX图运算pregel详解

    原创:Spark中GraphX图运算pregel详解 由于本人文字表达能力不足,还是多多以代码形式表述,首先展示测试代码,然后解释: package com.txq.spark.test import ...

  10. STM32的定时器详解(嵌入式学习)

    STM32的定时器详解 0. 前言 1. Systick定时器 概念 工作原理 时钟基准 Systick练习 2. HAL_Delay函数分析 3. 定时器 基本概念 定时器分类 定时器组成 计数器 ...

最新文章

  1. SD-WAN如何安装在企业WAN中?—Vecloud
  2. linux 中解析命令行参数 (getopt_long用法)
  3. Graph Theory 图论 贪心 栈 思维
  4. linux之V4L2摄像头应用流程
  5. unity3d曲线text文本
  6. 蓝桥杯JAVA省赛2013-----B------4(黄金连分数)
  7. DM9000调试记录
  8. 使用Github官方提供的gitignore过滤Git提交的文件
  9. 树和森林与二叉树的转换、树和森林的遍历
  10. [luogu2414 NOI2011]阿狸的打字机 (AC自动机)
  11. EmEditor正则表达式例子
  12. Activity详解(生命周期、以各种方式启动Activity、状态保存,完全退出等)
  13. android recyclerview 滚动监听,Android RecyclerView(九)滑动监听综述
  14. 爬虫第十一式:用selenium爬取民政部行政区划代码
  15. Ubuntu 18.04.3 双屏显示 N卡驱动 问题解决
  16. 你知道上海社保缴费基数吗?上海各类人员的社保缴费基数
  17. 罗技G502 HERO 主要的DPI灯光突然不亮了
  18. 手撸spring源码分析IOC实现原理
  19. RGB 转换至 YCbCr (YUV) 的计算公式
  20. 资金流入流出预测(上)(阿里云天池大赛)

热门文章

  1. extjs表格编辑、EditorGridPanel
  2. 魔兽争霸3运行速度慢的一些优化办法。
  3. lg v35 thinkq刷韩版android9.0教程
  4. word表格三线表线宽度
  5. python 微博自动点赞软件_python3 爬虫学习: 自动给你心上人的微博点赞
  6. 云南建投安二司的数字化创新之路:建筑行业里的样本力量
  7. JMeter下载及安装详细教程
  8. sql按照字符串格式拼接
  9. java笔记框架部分
  10. c语言 iostream,C语言 我应该在哪里使用iostream类?