memleak

导航

AVR8位单片机PWM、ADC的使用

在32位机价格一路卷到几毛钱的今天,8位机的生存空间被一再压缩,目前市场上常见的8位机基本就4种架构:8051,STM8,PIC,AVR。51应该是大部分电子类专业学生的启蒙老师,经典的bit操作和寄存器,还记得点亮第一个LED的喜悦,不过在轻量化,智能化,小封装的各种需求下,8位机逐渐不够看了,尤其是智能化,8位机粗糙的ALU和简陋的外设完全不够用。但是8位机仍有部分暂时无法取代的优势,比如极低功耗,5V逻辑电平无需转换,价格便宜,还有就是存量市场,旧方案一般不会轻易改动。

我现在手里的就是存量方案,之前已经稳定使用,所以不能轻易换,芯片是ATtiny85,ATmel(被Microchip收购)公司出品,在各大电子商城上可以看到价格并不便宜,即使最大批量还是要10块钱一个,体积也不算小,属实是坑爹,不过已经成熟的方案也没什么好抱怨的,接下来记录一下一些常规用法。

image
image

我们使用的是SOP8封装,初次使用需要注意这个SOP8比常规的SOP8胖一些,所以画封装时,两列引脚间距要加宽,两外它有6个通用IO,不过注意到PB5和RESET复用,这个脚一般不能当IO用,需要上拉,想要用作IO除非引脚非常紧张,而且要通过改熔丝位来启用它,一旦启用,就没法当RESET用,也没法烧录了。

所以强烈不建议用PB5作IO。

ATtiny85用SPI口下载程序,需要用到PB0,PB1,PB2,PB5加上VCC和GND,6个引脚。

VCC   <-> +1.8V ~ +5.5V
MISO  <-> MISO
MOSI  <-> MOSI
SCL   <-> SCL
RESET <-> RESET
GND   <-> GND

下载器需要购买AVR-ISP或者PICKIT,部分商家出售仿品。

这次我们用PB1,PB4作PWM输出,对应OC1A,OC1B,PB2做ADC采样,这样设置没有冲突,OC1A和OC1B使用了芯片的Timer/Counter1,相比Timer/Counter0,无法使用相位可调的PWM,不过项目只需要用到PWM调光,因此普通PWM足够。至于ADC,是打算用来换色的,通过不同电平来决定亮起的颜色。所以在上面的基础还要再添加一个IO输出,对于ATtiny85来说依然够用。

接线不在这里展开,这篇笔记用于记录程序写法,AVR单片机开发需要使用ATmel Studio,集成了工具链无需自行配置,而库文件则是官方的avrlib,需要注意AVR不支持bit操作,而是纯寄存器操作,从51转过来的需要时间适应。

下面简单介绍一下AVR单片机的PWM和ADC外设:

一个PWM波形需要有频率和占空比,AVR单片机如何通过Timer/Counter输出周期信号呢,方法就是问现成的周期信号借,而现有的周期信号就是主时钟,主时钟给你了信号,但是占空比是固定的,怎么调整?办法就是数数,数到设定的脉冲数,信号就发生跳变,再数到下一个设定的脉冲数,再发生跳变,这样就通过数数和跳变,生成了一个新的周期信号。下图是datasheet中的原理图,OCn(也就是输出通道)会在TCNTn达到寄存器OCRnx设定值时被置0,数到最大值255时再次置1,周期是固定的,即0数到255的时间,另一路

OCn

输出的极性相反,据此可以得到输出PWM的频率为:

\[f_{PWM}=\frac{f_{clk}}{N\cdot256} \]

其中fclk为主时钟频率,一般Attiny85内置晶振是8M,但主时钟是默认经过分频的1M,想用8M时钟需要禁用熔丝位CLKDIV,N为分频倍率,由CSx设定。
image
由于我们使用的不是Timer/Counter0而是Timer/Counter1,他们的配置和功能不完全一样,所幸的是,都能输出PWM波形(Timer/Counter1没有介绍PWM波形生成原理,故只能贴一张Timer/Counter0的)。Timer/Counter1配置PWM输出原理基本一致,根据上面的介绍,完成PWM配置需要设置分频,设置计数值OCRnx,还有输出极性,输出通道,除了这4项,还有一些配置,这里总结一下:

CS[13:10]		分频1~65536倍,Timer/Counter0最多256倍,属于TCCR1
PMW1A、PWM1B		OC1A、OC1B通道PWM使能,PWM1A属于TCCR1,PWM1B属于GTCCR
OCR1A、OCR1B		两路PWM计数值,达到后OC1A,OC1B分别输出0,都是独立8位寄存器
OCR1C			计数封顶值TOP,达到后OCn置位,Timer/Counter1独有,
			比如TC0的计数器固定在255时置位,TC1可以修改为128,64等
			独立8位寄存器
COM1A[1:0]、COM1B[1:0]	设置输出极性,COM1A属于TCCR1,COM1B属于GTCCR
TCNT1			计数值,启动后开始根据时钟周期数自增,独立8位寄存器
CTC1			使能该位,让TCNT1达到TOP后清零,否则TCNT1在达到TOP后会
			继续自增(OCn也不会在达到TOP时清零)
DDRB			将PB1,PB4配置为输出,独立8位寄存器

其他的寄存器我们可以暂时忽略。
由此我们可以写出一个简单的初始化代码:

#include<avr/io.h>
#include<util/delay.h>		//这两个头文件分别包含外设和延时各项定义

void init_PWM(void)
{
	DDRB |= (1 << PB1) | (1 << PB4);
	TCNT1 = 0;				//TCNT1清零
	OCR1C = 255;				//计数封顶值255
	OCR1A = 64;				//OC1A占空比25%
	OCR1B = 128;				//OC1B占空比50%
	TCCR1 |= (1 << CTC1)| (1 << CS12) | (1 << CS11) | (1 << CS10)
		| (1 << PWM1A) | (1 << COM1A1);	//64倍分频,启用PWM功能,OC1A极性不翻转
	GTCCR |= (1 << PWM1B) | (1 << COM1B1);	//启用通道B的PWM功能,OC1B极性不翻转
}

这样一个PWM初始化函数就写好了。接下来我们再看一看ADC的原理和使用。
ADC的原理可以分为FLASH,SAR,积分器,∑-∆,流水线等,FLASH是最简单的一种,就是用到大量并列的比较器和一串分压电阻,每个比较器都接到输入电压和一串的分压节点上,这样通过比较器输出的01值表示电平的数值。FLASH实现最简单,转换速度也快,但是电路资源消耗较大,功耗也大,不适合做成高分辨率的ADC。其他几种ADC原理比较复杂,感兴趣的可自行搜索。

ADC的流程包括采样,保持,量化,编码,采样就是按时间间隔采集引脚上的电平,保持就是在不采样的时候,维持住之前采样的电平,量化就是把采到电压分成离散的电平值,编码就是把量化后的电平值通过二进制表示。

ATtiny85的ADC最高10bit,也可以设置为8bit,但由于本身是8位机,读出10位ADC结果要读两个寄存器,精度要求不高的场合,直接用8bit就够了。ATtiny85有4路可选的ADC通道,PB2,PB3,PB4,PB5(RESET脚,不推荐),但是它内部只有一个ADC转换器,所以不能同时使用多路输入,不过它还有一路内置的温度采样同样用到了ADC,但不需要外接引脚,此外,ATtiny85的ADC支持差分输入;ATtiny85的参考电压可以设为内部的1.1V,2.56V,外部VCC,或者用户自定义的VREF;ADC采样周期类似于Timer,都需要问主时钟借,所以也同样需要分频器这个东西。

根据上面的描述,配置ADC需要设置位数/分辨率,参考电压,输入通道,还有一些依附的参数,下面总结一下用到的寄存器:

MUX[3:0]			输入选择,可配置单通道,差分,温度采样,属于ADMUX寄存器
REF[2:0]			选择参考电压,属于ADMUX寄存器
ADLAR				转换结果左对齐,作用就是设置分辨率为8bit,不启用则为10bit
ADEN				启用ADC,属于ADCSRA寄存器
ADSC				触发一次ADC转换,完成后自动清0,属于ADCSRA
ADPS[2:0]			设置分频倍率,属于ADCSRA
ADCH、ADCL			转换结果保存位置,若ADLAR=1,则只需要读取ADCH,独立8位寄存器
ADTS[2:0]			设置ADC触发方式,属于ADCSRB寄存器

现在我们配置一个由PB2输入,8位分辨率,手动触发的ADC,示例程序如下:

void init_ADC(void)
{
	ADMUX |= (1 << MUX0) | (1 << ADLAR);	//8bit,PB2输入
	ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADEN);	//64倍分频,启用ADC
	//delay_ms(1);	//等待电平稳定
	//ADCSRA |= (1 << ADSC);	//预采样
	//while(ADCSRA & (1 << ADSC));
	//void(ADCH);	//丢弃预采样数据
}

uint8_t read_ADC(void)
{
	ADCSRA |= (1 << ADSC);	//触发采样
	while(ADCSRA & (1 << ADSC);	//等待转换完成
	return ADCH;		//返回8位结果
}

值得一提的是,启动后第一次采样消耗的时间比后续的采样长不少,如果需要做上电采样的话,需要注意电源时序,另外,上电后的第一组采样可能在电平没完全建立的情况下产生,若使用内部参考电压,则电平稳定时间更长,所以建议在初始化程序中进行一次预采样并丢弃该组数据。

posted on 2026-10-17 22:44  行星齿轮  阅读(6)  评论(0)    收藏  举报