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. Spring源码:Bean生命周期(五)

    前言 在上一篇文章中,我们深入探讨了 Spring 框架中 Bean 的实例化过程,该过程包括从 Bean 定义中加载当前类.寻找所有实现了 InstantiationAwareBeanPostPro ...

  2. 选择结构do...while语句

    // do..while语句 #include<stdio.h> int main() { int a = 0; do { a++; printf("HelloWorld\n&q ...

  3. 运行和编译时期资源加载的不同【vue】

    开发语言都有编译和运行两个阶段,很多时候这个也会带来许多bug 如:一个项目在开发阶段测试没有问题,然上线发布后就会有这样那样的问题,譬如说图片的加载,静态数据(js,css,json)读取错误 一 ...

  4. 警惕看不见的重试机制:为什么使用RPC必须考虑幂等性

    0 文章概述 在RPC场景中因为重试或者没有实现幂等机制而导致的重复数据问题,必须引起大家重视,有可能会造成例如一次购买创建多笔订单,一条通知信息被发送多次等问题,这是技术人员必须面对和解决的问题. ...

  5. qq飞车端游最全按键指法教学

    目录 起步篇 超级起步 弹射起步 段位起步 基础篇 点飘 撞墙漂移 撞墙点喷 进阶篇 双喷 叠喷 断位漂移 段位双喷 侧身漂移 快速出弯 CW WCW CWW 牵引 甩尾点飘 甩尾漂移 右侧卡 左侧卡 ...

  6. Java中读取用户输入的是谁?Scanner类

    前言 我们在初学 Java 编程的时候,总是感觉很枯燥乏味,想着做点可以交互的小系统,可以让用户自由输入,系统可以接收做出反映.这就要介绍一下 Java 中的 Scanner 类了. 一.Scanne ...

  7. 聊聊MAUI、WinUI3和WPF的优势及劣势

    今天在群里聊到WinUI3的学习及发展,还有他那堪比玩具的使用体验,正好梳理一篇关于WinUI3.MAUI和WPF优劣势,我整理的不是很好,所以又让ChatGPT在生成了一遍,感觉整体还可以.看完可以 ...

  8. json数据的解析

    php声明没有键的数组,用json_encode输出: $array=array("1","2","3"); echo json_encod ...

  9. LLE算法在自然语言处理中的应用:从文本到实体识别和关系抽取

    目录 文章介绍: 自然语言处理(Natural Language Processing,NLP)是人工智能领域的重要分支,它研究如何将人类语言转化为计算机可理解的格式.NLP的应用非常广泛,从语言翻译 ...

  10. Codeforces Round #879 (Div. 2) A-E

    比赛链接 A 代码 #include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { i ...