ATtiny88初体验(二):呼吸灯

前面的“点灯”实验实现了间隔点亮/熄灭LED,但是间隔时间和亮度都没法控制,为了解决这个问题,可以使用ATtiny88的定时器模块。

ATtiny88单片机含有2个定时器,定时器0是8bit的,定时器1是16bit的,其中定时器1支持PWM功能。查询手册得知,定时器1的两个通道的输出脚为PB1和PB2,而MH-ET LIVE Tiny88核心板的LED连接的是PD0脚,因此无法利用定时器1的PWM功能控制板载LED。这里介绍基于定时器0,通过软件模拟PWM控制板载LED的方法。

定时器0介绍

ATtiny88的定时器0是一个8bit的定时器,拥有两个独立的输出比较单元,支持CTC模式,拥有三个独立的中断源(TOV0,OCF0A,OCF0B)。

定时器0的时钟源可以是由内部时钟源分频而来,也可以是来自T0(PD4)引脚的外部时钟源。

注意:在使用定时器0时,务必确保 PRR 寄存器中的 PRTIM0 位值为0。

普通模式

在普通模式下, TCNT0 寄存器的值从0x00一直增加到0xFF,然后回到0x00,如此往复。当 TCNT0 寄存器的值回到0x00时, TOV0 标志位置位,同时触发 TIMER0_OVF 中断。

TCNT0 寄存器的值与 OCR0x 寄存器的值相等时, OCF0x 标志位将在下一个时钟置位,同时触发 TIMER0_COMPx 中断。

CTC模式

在CTC(Clear Timer on Compare Match)模式下, TCNT0 寄存器的值从0x00一直增加到和 OCR0A 寄存器相等,然后回归到0x00。当 TCNT0 寄存器的值回到0x00时, OCF0A 标志位置位,同时触发 TIMER0_COMPA 中断。

寄存器

  • CTC0 :CTC模式, 0 为普通模式, 1 为CTC模式。

  • CS0[2:0] :时钟源选择。

  • OCIE0B :置 1 时使能 TIMER0_COMPB 中断。
  • OCIE0A :置 1 时使能 TIEMR0_COMPA 中断。
  • TOIE0 :置 1 时使能 TIMER0_OVF 中断。

  • OCF0B :定时器0输出比较B匹配标志位,执行中断处理函数时自动清除,或者可以写 1 清除。
  • OCF0A :定时器0输出比较A匹配标志位,执行中断处理函数时自动清除,或者可以写 1 清除。
  • TOV0 :定时器0溢出标志位,执行中断处理函数时自动清除,或者可以写 1 清除。

控制LED闪烁周期

利用ATtiny88定时器0的CTC模式可以灵活控制LED的闪烁周期, OCR0A 寄存器的值可以通过下式计算得到:

\[OCR0A = time \times f_{T0} - 1 = time \times \frac{f_{IO}}{prescaler} - 1
\]

代码文件的组织结构如下:

.
├── Makefile
├── inc
└── src
└── main.c

其中 src/main.c 源文件的内容如下:

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h> int main(void)
{
cli(); // disable global interrupt DDRD |= _BV(DDD0); // set PD0 as output
PORTD |= _BV(PORTD0); // PD0 outputs high level TCNT0 = 0; // clear counter
OCR0A = 249; // reload value, 1ms
TCCR0A = _BV(CTC0) | _BV(CS01) | _BV(CS00);
// CTC mode, prescaler = 64, clk_t0 = 250KHz
TIFR0 = _BV(OCF0A); // clear OCF0A flag
TIMSK0 = _BV(OCIE0A); // enable TIMER0_COMPA interrupt sei(); // enable global interrupt for (;;); // wait for interrupt
} ISR(TIMER0_COMPA_vect)
{
static uint16_t count = 0; uint8_t sreg = SREG; // store the status register
if (++count == 500) {
count = 0;
PIND = _BV(PIND0); // toggle PD0 every 500ms
}
SREG = sreg; // restore the status register
}

上述代码设置定时器0的时钟分频系数为64,即 \(f_{T0} = \frac{16MHz}{64} = 250KHz\) ;设置定时周期为1毫秒,即 \(OCR0A = 10^{-3}s \times 250KHz - 1 = 249\) ;同时,开启 TIMER0_COMPA 中断。在中断函数中,每500个周期翻转一次PD0的电平状态,实现了LED以1秒为周期的闪烁功能。

控制LED亮度

利用ATtiny88定时器0的普通模式,可以实现软件PWM功能,PWM的频率和占空比可以通过下式计算得到:

\[f_{PWM} = \frac{f_{T0}}{256} = \frac{f_{IO}}{prescaler \times 256}
\]
\[duty = \frac{OCR0A + 1}{256}
\]

代码文件的组织结构如下:

.
├── Makefile
├── inc
└── src
└── main.c

其中 src\main.c 源文件的内容如下:

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h> int main(void)
{
cli(); // disable global interrupt DDRD |= _BV(DDD0); // set PD0 as output
PORTD &= ~_BV(PORTD0); // PD0 outputs low level TCNT0 = 0; // clear counter
OCR0A = 0; // clear compare value
TCCR0A = _BV(CS01) | _BV(CS00); // normal mode, prescaler = 64, clk_t0 = 250KHz, f_pwm = 977Hz
TIFR0 = _BV(OCF0A) | _BV(TOV0); // clear OCF0A & TOV0 flag
TIMSK0 = _BV(OCIE0A) | _BV(TOIE0); // enable TIMER0_COMPA & TIMER0_OVF interrupt sei(); // enable global interrupt for (;;); // wait for interrupt
} ISR(TIMER0_COMPA_vect)
{
uint8_t sreg = SREG; // store the status register
PORTD &= ~_BV(PORTD0); // PD0 outputs low level
SREG = sreg; // restore the status register
} ISR(TIMER0_OVF_vect)
{
static uint8_t count = 0;
static int8_t inc = 1; uint8_t sreg = SREG; // store the status register
PORTD |= _BV(PORTD0); // PD0 outputs high level
if (++count == 10) {
OCR0A += inc; // increase / decrease PWM duty every 10 cycles
if (OCR0A == 0xFF) {
inc = -1;
} else if (OCR0A == 0) {
inc = 1;
}
count = 0;
}
SREG = sreg; // restore the status register
}

上述代码设置定时器的时钟分频系数为64,则PWM的频率为 \(f_{PWM} = \frac{16MHz}{64 \times 256} \approx 977Hz\) ,并开启了 TIMER0_COMPATIMER0_OVF 中断。在 TIMER0_COMPA 中断里PD0输出低电平,在 TIMER0_OVF 中断里PD0输出高电平,另外每10个周期增加/减少一次占空比。

将代码下载到单片机后,板载LED将呈现呼吸灯的效果,由灭慢慢变亮,再由亮慢慢变灭。同时,将PD0引脚连接到示波器,可以看到频率为977Hz左右的PWM波,且占空比在规律变化,如下图所示:

注意:这种方法产生的PWM最低占空比为 \(\frac{1}{255}\) ,最高占空比介于 \(\frac{255}{256}\) 和1之间,这是因为虽然理论上 OCR0A 寄存器设置为255时占空比为1,但是在这种情况下 TIMER0_COMPATIMER0_OVF 这两个中断会同时产生,而 TIMER0_COMPA 中断的优先级比 TIMER0_OVF 高,因此会先执行 TIMER0_COMPA 中断,执行完后再执行 TIMER0_OVF 中断,所以PD0会输出一个时间很短的低电平脉冲,导致实际占空比达不到1。

参考资料

  1. ATtiny88 Datasheet
  2. Programming and Interfacing ATMEL's AVRs

ATtiny88初体验(二):呼吸灯的更多相关文章

  1. 【原创】Jquery初体验二

    快速导航 一.传统方式生成Table 二.使用jquery.tmpl插件快速生成Table 三.Jquery中的操作class的几个方法 四:jq里面的克隆 五:属性过滤器 六:表单元素过滤器 一.传 ...

  2. Ruby on rails初体验(二)

    体验一中添加了一个最基本的支架和一个简单的数据迁移,实现了一个基本的增删改查的功能列表.体验二中要在次功能上继续丰满一下功能.实现如下效果: 在每个公司中都包含有不同的部门,按照体验一中的方法,添加一 ...

  3. Spring Cloud Alibaba 初体验(二) Nacos 服务注册与发现 + 集成 Spring Cloud Gateway

    一.服务注册 添加依赖: <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>s ...

  4. jquery.fn.extend与jquery.extend--(初体验二)

    1.jquery.extend(object); 为扩展jQuery类本身.为类添加新的方法. jquery.fn.extend(object);给jQuery对象添加方法. $.extend({ a ...

  5. JSON初体验(二):Gson解析

    今天,我们来介绍一下Gson的jar包的用法. JSON解析之Gson 特点:编码简介,谷歌官方推荐 数据之间的转换: 1.将json格式的字符串{}转换成为java对象 API: <T> ...

  6. node初体验(二)

    1.静态资源访问,需要设置路由和响应标头 2.url模块.path模块.querystring模块 Url { protocol: null, slashes: null, auth: null, h ...

  7. Python导出Excel为Lua/Json/Xml实例教程(二):xlrd初体验

    Python导出Excel为Lua/Json/Xml实例教程(二):xlrd初体验 相关链接: Python导出Excel为Lua/Json/Xml实例教程(一):初识Python Python导出E ...

  8. Java8初体验(二)Stream语法详解

    感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com 上篇文章Java8初体验(一)lambda表达式语法比 较详细的介绍了lambda表达式的方方面面,细心的读者会发现那篇文章的例 ...

  9. 微信小程序初体验,入门练手项目--通讯录,部署上线(二)

    接上一篇<微信小程序初体验,入门练手项目--通讯录,后台是阿里云服务器>:https://www.cnblogs.com/chengxs/p/9898670.html 开发微信小程序最尴尬 ...

  10. Java8初体验(二)Stream语法详解(转)

    本文转自http://ifeve.com/stream/ Java8初体验(二)Stream语法详解 感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com上篇文章Java8初体验(一 ...

随机推荐

  1. vue项目提示TypeError: e.call is not a function

    最近运行vue项目老是提示TypeError: e.call is not a function 如上一运行到该页面就会提示这个错误,虽然页面功能都没受到影响,但是这个必定会给后期发布后的项目带来极大 ...

  2. 百度云原生数据库GaiaDB的HTAP与多地多活技术实践

    摘要:云原生数据库在使用存算分离技术后,可以在完全兼容MYSQL协议和语法的情况下,极大提升单实例所能承载的数据规模与吞吐能力上限.但除了对客户端兼容外,对整个数据生态(地域容灾,数据分析,备份恢复) ...

  3. 如何从AWS中学习如何使用AWS的AmazonDynamoDB存储卷

    目录 引言 随着云计算.大数据和人工智能等技术的发展,AWS(亚马逊云)成为了备受瞩目的云计算平台之一.AWS提供了许多先进的云计算服务和功能,包括Amazon DynamoDB(Amazon Dyn ...

  4. 查看C语言程序对应的汇编代码

    在终端输入 gcc -S main.c 命令的意思是 编译不汇编 mian.c 可以换成想要汇编的C语言程序 然后生成 main.s 使用文本编辑器查看即可

  5. 2020 OC项目集成flutter

    看了网上基本上都是相同套路,但是到我这就不行了,终于解决了, 主要就是profile文件编写, 1.在oc项目的同级目录项创建flutter模块 就是在oc项目的父级目录下,执行 flutter cr ...

  6. CMU15-445 Project4 Concurrency Control心得

    一.概述 过瘾!过瘾!过瘾!P4 真过瘾!写 P3 的博客时我说过"感觉自己在数据库方面真正成长了",但写完 P4 之后最大的感受就是,我终于理解了 andy 在第一课说过的&qu ...

  7. vue基本操作[上] 续更----让世界感知你的存在

    Vue引用js文件的多种方式 1.vue-cli webpack全局引入jquery (1)首先 npm install jquery --save (--save 的意思是将模块安装到项目目录下,并 ...

  8. DataArts Studio实践丨通过Rest Client 接口读取RESTful接口数据的能力

    本文分享自华为云社区<DataArts Studio 通过Rest Client 接口读取RESTful接口数据的能力,通过Hive-SQL存储>,作者: 张浩奇 . Rest Clien ...

  9. Vue-treeselect 实现下拉树懒加载,末节点不要箭头

    项目需要,可选择的下拉树,由于数据过多,显示要有层级感,所以使用了懒加载模式 vue-treeselect官网:https://www.vue-treeselect.cn/ 1.前端代码 1.下载依赖 ...

  10. java 线程等待和唤醒方法

    java线程状态变迁图 从图中可以看出Java 线程等待方法是将线程从Runnable状态转换为Waiting状态,Java线程的唤醒方法是将线程从Waiting状态唤醒进入Runnable状态 在J ...