模吧

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

楼主: 冷血动物

低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器

  [复制链接]
 楼主| 发表于 2013-12-4 21:58:23 | 显示全部楼层
– 两个具有独立预分频器8 位定时器/ 计数器, 其中之一有比较功能
    单片机定时器、计数器是一个寄存器,在硬件结构上,定时器(计数器)被设计为,每输入一个脉冲,其数值就加1或减1。由于每个脉冲代表的时间是确定的,因此,定时器(计数器)中的数据的值就代表了时间。
    每个脉冲到底代表多少时间?是靠两个数据来确定的,一个是单片机所用的主频,一个是分频系数,输入定时器(计数器)的脉冲是通过单片机主频分频后(有点类似于减速齿轮系统的减速比)得到的。
     我举个例子,在一个主频为1M的单片机系统中,1M是指晶振(或其它频率源)每秒可发出1百万个脉冲,它的每个脉冲代表的时间是1/1百万,即1百万分之1秒,即1us(微秒)。如果输入定时器(计数器)的分频系数为1,即减速比为1:1,输入定时器(计数器)的频率仍为每秒1百万个,那么这样输入定时器(计数器)的一个脉冲代表的时间仍为1us(微秒),即定时器(计数器)的数值1就代表1us。
    那么在上述的例子中,输入定时器(计数器)的分频系数为2,即减速比为1:2,输入定时器(计数器)的脉冲个数就为每秒1百万/2,即50万,那么这样输入定时器(计数器)的一个脉冲代表的时间就为1秒/50万。即1000000微秒/500000=2微秒。即定时器(计数器)的数值1就代表2us。
    8位定时器(计数器)是指该定时器的数值有8个二进制位,由于定时器数值可看成是无符号的整数,8位的无符号二进制数,最大的数为11111111,(10进制的255),最小的数为00000000(10进制的0)。
    定时器(计数器)的数值到底是每个脉冲加1还是每个脉冲减1,这个是用户自己可以通过相应的寄存器设置的。当定时器(计数器)的数值设置为每个脉冲加1的时候,到了255的时候,如果再来一个脉冲,定时器(计数器)就自动回到0,又从零开始计数。与此同时,如果设置了定时器中断,定时器就发出一个中断信号,告诉处理核心,“嗨,我设置的时间已经到了”,这时处理核心就把当前正在执行的程序中断,而转到执行预先设定好的定时器中断程序。当设置为每个脉冲减1的时候与之类似。即当定时器(计数器)数值减到0的时候,如果再来一个脉冲,定时器(计数器)就自动回到255,又从255开始计数。
程序在执行的过程中,也可以随时访问定时器(计数器)获得当前的数值,也可以改变定时器(计数器)的值。如当只想在10个脉冲发生的时候就执行中断程序,可将定时器(计数器)的值预设为246。
定时器中断和可访问当前的数值,就构成了多种多样的与时间相关的应用。定时器(计数器)的比较功能是指,有输出比较功能的引脚的输出电平与定时器(计数器)中的值和预先在输出比较寄存器中设定的比较值相关,当定时器(计数器)中的值和输出比较寄存器中的预设值相等时,输出比较引脚的电平可以发生置高电平(置位)或置低电平(复位)或取反的操作,具体是哪种操作又依赖于比较功能的模式设定。
    定时器(计数器)的概念对于遥控器制作非常重要,必须要充分理解.
 楼主| 发表于 2013-12-4 21:58:33 | 显示全部楼层
从电脑上实现ppm编码 然后通过usb接口连上飞梦的发射端信号线现实吗?还是要在电脑和发射端信号线之间加上一个单片机?
还有个问题avr studio和altium是不是功能差不多?听说altium也可以实现单片机开发?新手道听途说来的 lz莫怪 _____________________________________________________________________________
在电脑上实现PPM编码,然后通过USB接口连上飞梦的发射端信号线不现实。USB接口除了电源和地,余下的两根线是信号线,是传输数字信号的,不是输出引脚,无法直接输出高或低电平。
如果一定要这样做,可以用并口(或USB转并口),并口是直接的输出口,可以直接输出高或低电平,是TTL电平,可以直接和飞梦发射端信号线相接,当然为保险起见,加个74ls244,245之类的单向或双向总线收发器,可以保护两端的设备。
或者如果非要用USB口,用单片机模拟USB驱动芯片,然后再接飞梦发射端,但这样做,单片机上还得编程序,这样就看不出优势了。很麻烦。

AVR Studio和Altium Designer是完全不同功能的两个软件,AVR Studio是avr系列单片机的程序开发套装,AD是电路设计软件,是用来画电路图和电路板的。两个软件我都在用,但没听说过AD还有单片机程序开发的功能,也许是我自己不知道。
 楼主| 发表于 2013-12-4 21:58:49 | 显示全部楼层
一个具有预分频器、比较功能和捕捉功能的16 位定时器/ 计数器
    16位定时器的最大计数值为65535。
    具有捕捉功能的引脚,当其电平发生变化的时候,,输入捕捉即被激发,当前16 位的定时器(计数器)数据被拷贝到输入捕捉寄存器中,同时输入捕捉标志位被设置为1。如果输入捕捉中断打开,输入捕捉标志将产生输入捕捉中断。

–三通道PWM
        PWM的全称叫脉宽调制信号,所谓脉宽,其实就是指脉冲的宽度,脉冲是指电平从低到高再到低的过程所形成的信号(或相反,从高到低再到高,称为负脉冲)。一个周期性的脉冲信号有两个主要指标,一个是脉冲的频率,即每秒种有多少个脉冲周期。第二个是脉冲的占空比,即脉冲的高低电平所占时间的比例。高电平为占比,低电平为空比。
     举个例:你要到一个地方去,要走上坡路前往,你已经走得很累了,于是每走一步就需要休息一下,那么频率就是你每分钟走的步数。占空比就是你走一步所耗的时间和你每走一步休息的时间的比值。你走一步所耗的时间加上每走那步所休息的时间就是脉冲周期时间。
    所有的单片机可以用普通端口,用程序控制发出PWM信号,即置低电平—延时—置高电平—延时—置低电平,周而复始即可。
    这里所说的三通道PWM是指硬件PWM。即程序不需要去重复上句所述的周期,仅需要设置好频率、占空比等指标,硬件就自动在PWM引脚上发出PWM信号。

-8 路10 位ADC
        ADC的全称是模数转换器。这个名字太拗口了。它可以完成模拟电信号至数字电信号的转换。这个概念仍然不好理解,我们可以把它理解为ADC输入模拟电信号,测量其电压的高低,然后输出一个数字来表示这个电压的高低。
    先谈谈熟悉ADC所必须掌握的两个概念,一个是参考电压(基准电压),一个是ADC的位数。ADC要衡量一个电压的高低,必须有个标准,参考电压就是这个标准。ADC的位数表明了ADC的分辨率,即ADC可以表现多小的变化。
我来举个例子。ADC的参考电压为5V,其位数为10位。那么当ADC输入一个5V电压的时候,ADC输出的数字为10位二进制数的最大值,即2的10次方,即1024。当ADC输入为2.5V的时候,ADC输出的数字为512,当ADC输入为0V的时候,输出的数字为0。这样,ADC输出的数字每增减1,表明电压变化为5V/1024,约为4.88毫伏。
    如果ADC参考电压为5V,其位数只有8位,当输入5V的电压时,ADC输出的数字为2的8次方,即256,当输入2.5V的电压时,ADC输出的数字为128,当输入0V的电压时,ADC输出的数字为0。这样,ADC输出的数字每增减1,表明电压变化为5V/256,约为19.5毫伏。
    这样大家可以看到,8位的ADC,在5V参考电压的时候,其电压变化超过19.5毫伏数字才会发生变化,而同样条件下的10位ADC,其电压变化超过4.88毫伏即发生变化。
 楼主| 发表于 2013-12-4 21:59:01 | 显示全部楼层

– 片内模拟比较器
Atmega8的模拟比较器有两个输入脚,一个输出脚,两个输入脚为AIN0和AIN1,当AIN0 上的电压比AIN1上的电压要高时,输出脚输出高电平,当AIN0 上的电压比AIN1上的电压要低时,输出脚输出低电平。
与此同时,比较器的输出脚还可触发自己专有的、独立的中断。用户可以选择输出脚的信号是以上升沿、下降沿还是交替变化的边沿来触发中断。

    (单片机的基本概念和ATMEGA8的主要功能讲解暂时告一段落,开始进入实操和讲解结合的内容,下次讲解内容为:5分钟学会输出端口控制,如有还希望讲解单片机或ATMEGA8的某些基本概念的请提出)
 楼主| 发表于 2013-12-4 21:59:40 | 显示全部楼层
二、5分钟学会输出端口控制
Atmega8单片机一共有3个8位的I/O端口。PORTB,PORTC和PORTD,每个端口对应8个引脚,每个引脚均可以单独设为输入或输出端口,部分引脚还有其它复用功能,但当使用其它复用功能,其不能同时作为输入或输出端口使用。

Atmega8单片机输出的是TTL电平,其引脚输出为高电平时,其电压等于Vcc,其引脚输出为低电平时,其电压为0。高电平的输出驱动能力较强,大概在20ma左右,可以直接驱动发光二极管。

输出端口控制的过程非常简单,两个步骤,一个步骤是设置端口方向为输出方向,一个步骤是设置输出的是高电平还是低电平。要学会输出端口,我们仅需学会两个寄存器的设置。一个是DDRX,一个是PORTX。其中X是端口编号,B端口对应DDRB和PORTB,C端口对应DDRC和PORTC,D端口对应DDRD和PORTD。DDRX是X端口的输入输出方向,PORTX是X端口的输出电平。
每个端口对应8个引脚,如B端口对应PB0,PB1,PB2,PB3,PB4,PB5,PB6,PB7。DDRX和PORTX均为8位寄存器,以B端口例,DDRB其每一位的值从低到高位依次对应PB0,PB1至PB7,1表示该引脚为输出方向,0表示该引脚为输入方向。PORTB其每一位的值从低到高位依次对应PB0,PB1至PB7,1表示该引脚为输出高电平,0表示该引脚为输出低电平。

DDRB
PB7        PB6        PB5        PB4        PB3        PB2        PB1        PB0

PORTB
PB7        PB6        PB5        PB4        PB3        PB2        PB1        PB0


从上图可以看出,DDRB对应B端口每个引脚的输入输出方向,每个引脚可以单独设置。假设我们希望设置所有引脚为输出方向,则DDRB的每一位均为1,即二进制数的11111111,这个数,如果用十六进制来表示,就是0xff,如果用十进制来表示,就是255。
PORTB对应B端口每个引脚的输出电平,假设我们希望设置PB7至PB4为高电平,PB3至PB0为低电平,则PORTB的高四位为1,低四位为0,即二进制数的11110000,这个数,如果用十六进制数来表示,就是0xf0,如果用十进制来表示,就是240。这个换算关系如果不能一眼看出来,可用windows附件中的计算器,将其设置为科学型,换算非常方便,可以在十六进制,十进制,二进制中自由转换,是我们学习的好助手。
下面让我们投入实战吧。
先看下面的例子。

#include <avr/io.h>

int main()
{
DDRB = 0xff;
PORTB = 0xf0;
}

这个例子只有两条语句,执行之后,可用万用表测量,PB7—PB4四个引脚输出电压为电源电压,PB3—PB0四个引脚输出电压为0V。

这个例子试验成功之后,大家仔细体会DDRB、PORTB和B端口各引脚的关系,自己试试如何设置其它端口和引脚。

下面我们再讲一讲如何单独设置某一个引脚而不改变其它引脚的电压。
常用的AVR Studio不支持直接对端口位的操作。即我们不能直接写出这样的语句PORTB3= 1;我们通常要设置某一位为1或者0的时候,要用到“&”和“|”操作符。

假设我们要单独设置PB3为高电平,我们一般采用如下的语句
    PORTB |= 0x08;
这个语句如果要便于理解,可以把它转换成等同语句如下:
   PORTB = PORTB | 0b00001000;

要理解上面的语名,让我们先看“|”操作符。
“|”是位操作符,其名称为“或”,意思是把操作符两边的数均看成是二进制数,对应的位进行“或”的操作,结果存入到等号左边的结果数中。“或”的操作的含义是,操作符两边的数对应的位中,只要有一个为1,结果的对应位就为1,当两边的数对应位均为0时,结果的对应位就为0。
对二进制位而言,“|”有下面的结果

1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
由上可见,任何数“|”1,其结果均为1,任何数“|”0,其结果保持不变。

理解了“|”操作符,再让我们回头来看语句。
   PORTB = PORTB | 0b00001000;

我们以图形表示如下:
PORTB
PB7        PB6        PB5        PB4        PB3        PB2        PB1        PB0

0b00001000
0        0        0        0        1        0        0        0

结果
PB7        PB6        PB5        PB4        1        PB2        PB1        PB0

以最高位PB7为例,假设PB7为1,对应的0b00001000的最高位为0,结果的最高位为1 | 0 = 1。假设PB7为0,结果的最高位为0 | 0 = 0。
而以PB3为例,假设PB3为0,对应的0b00001000的对应位为1,结果的对应位为1 | 1 = 1。假设PB3为1,结果的最高位为1 | 1 = 1。

由上表我们得出结果,如果要单独设PB3为1,就让PORTB和二进制数00001000相“或”。要单独设其它引脚为1,依次类推,如我们希望设置PB0,PB3,PB5为1,就让PORTB和二进制数00101001相“或”即可。

假设我们要单独设置PB3为低电平,我们一般采用如下的语句
    PORTB &= 0xf7;
这个语句如果要便于理解,可以把它转换成等同语句如下:
   PORTB = PORTB & 0b11110111;

要理解上面的语名,让我们看看“&”操作符。
“&”是位操作符,其名称为“与”,意思是把操作符两边的数均看成是二进制数,对应的位进行“与”的操作,结果存入到等号左边的结果数中。“与”的操作的含义是,操作符两边的数对应的位中,只要有一个为0,结果的对应位就为0,当两边的数对应位均为1时,结果的对应位就为1。
对二进制位而言,“&”有下面的结果

1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
由上可见,任何数“&”1,其结果保持不变,任何数“&”0,其结果为0。

理解了“&”操作符,再让我们回头来看语句。
   PORTB = PORTB & 0b11110111;

我们以图形表示如下:
PORTB
PB7        PB6        PB5        PB4        PB3        PB2        PB1        PB0

0b00001000
1        1        1        1        0        1        1        1

结果
PB7        PB6        PB5        PB4        1        PB2        PB1        PB0

以最高位PB7为例,假设PB7为1,对应的0b11110111的最高位为1,结果的最高位为1 & 1 = 1。假设PB7为0,结果的最高位为0 & 1 = 0。
而以PB3为例,假设PB3为0,对应的0b11110111的对应位为0,结果的对应位为0 & 0= 0。假设PB3为1,结果的对应位为1 & 0 = 0。

由上表我们得出结果,如果要单独设PB3为0,就让PORTB和二进制数11110111相“与”。要单独设其它引脚为,依次类推,如我们希望设置PB0,PB3,PB5为0,就让PORTB和二进制数11010110相“与”即可。
下面,仔细想想这两个程序的输出结果应该是多少,然后用万用表测量其结果。

#include <avr/io.h>

int main()
{
DDRB = 0xff;
PORTB = 0xf0;
     PORTB = PORTB & 0b10101111;
}


#include <avr/io.h>

int main()
{
DDRB = 0xff;
PORTB = 0xf0;
     PORTB = PORTB | 0b00000101;
}

理解会了这个,遥控器已经可以做一半了。
让我们运行下面的程序

#include <avr/io.h>
#include <util/delay.h>

int main()
{
DDRB = DDRB | 0x01;
PORTB = PORTB & 0xfe;
while(1)
  {
   PORTB = PORTB | 0x01;
   _delay_us(300);   //延时300微秒
   PORTB = PORTB & 0xfe;
   _delay_us(1200);

   PORTB = PORTB | 0x01;
   _delay_us(300);
   PORTB = PORTB & 0xfe;
   _delay_us(1200);

   PORTB = PORTB | 0x01;
   _delay_us(300);
   PORTB = PORTB & 0xfe;
   _delay_us(1200);

   PORTB = PORTB | 0x01;
   _delay_us(300);
   PORTB = PORTB & 0xfe;
   _delay_us(1200);

   PORTB = PORTB | 0x01;
   _delay_us(300);
   PORTB = PORTB & 0xfe;
   _delay_us(1200);

   PORTB = PORTB | 0x01;
   _delay_us(300);
   PORTB = PORTB & 0xfe;
   _delay_us(1200);

   _delay_ms(11);   //延时12毫秒
}
}


这个程序很简单,相信应该大家都能看懂了。但这个就是一个六通道的遥控器的核心程序了。这个程序在PB0引脚发出的波形,接在飞梦等发射端上,接收机上直接可以接收到信号,可以驱动每个通道的舵机置于中位。
当大家能够运行这个程序并看到舵机运行到中位的时候,相信大家都有了很大的信心,遥控器也没有我们想像的那么难啊。
 楼主| 发表于 2013-12-4 22:00:34 | 显示全部楼层
三、10分钟学会输入端口控制
Atmega8的每一个端口都可以作为输入端口。输入端口有两种状态,一种是高阻态,高阻输入可以认为输入电阻是无穷大的,认为I/O对前级影响极小,而且不产生电流(不衰减),而且在一定程度上也增加了芯片的抗电压冲击能力。高阻态多用于高阻模拟信号输入,例如ADC数模转换器输入,模拟比较器输入。另外一种状态是上拉电阻使能态。上拉电阻使能态当外部没有输入时,其内部上拉电阻强制将输入电平拉至高电平。其电平被外部电压拉低时将输出电流。
Atmega8的输入涉及到三个寄存器,一个是DDRX,一个是PORTX,一个是PINX。还涉及到SFIOR寄存器的PUD标志位,这个标志位在SFIOR寄存器的第2位(最低位为0位,最高位为第7位。)
DDRX是用于设置端口是输入还是输出,当DDRX的某一位为0时,其对应的引脚设置为输入引脚。PORTX用于设置端口的状态,当PORTX的某一位为0时,其对应的引脚设置为高阻态,当PORTX的某一位为1,且PUD标志位为0时,其对应引脚为上拉电阻禁用态。当PORTX的某一位为1,且PUD标志位为1时,其对应引脚也为高阻态。
DDRX、PORTX和PUD的关系可见下表:


DDRXPORTX和PUD的关系表.jpg

我们可以用下面的语句来设置PUD为1
SFIOR |=0x04;
用下面语句来设置PUD为0
SFIOR &= 0xfb;

用下面的语句来设置某一个引脚对应的DDRX或PORTX为1
DDRX |= 1<<Y;

其中DDRX可换为PORTX,X为B、C、D,Y为该引脚的编号。如要设置PD3的语句如下:
DDRD |= 1<<3;

1<<3操作符的意思是1左移3位,1左移3位的结果为0b00001000,换算为十六进制为0x08,所以上面的语句等价于
DDRD |= 0x08;
用下面的语句来设置某一个引脚对应的DDRX或PORTX为0
DDRX &= ~(1<<Y)
其中DDRX可换为PORTX,X为B、C、D,Y为该引脚的编号。如要设置PD3的语句如下:
DDRD &= ~(1<<3);

<<为左移操作符,1<<3的意思是1左移3位,1左移3位的结果为0b00001000,换算为十六进制为0x08,~操作符的意思是按位取反,0b00001000按位取反的结果为0b11110111,换算为十六进制为0xf7。
所以上面的语句等价于
DDRD |= 0xf7;

上面的设置我们学会以后,就用到PINX了。
PINX就是端口X所有8个引脚的当前值了。当PINX的某一位为1时,其对应引脚输入为高电平(在上拉电阻使能态中,某引脚无输入时,上拉电阻强制拉高作用使得PINX的对应位仍为1),当PINX的某一位为0时,其对应引脚的输入为低电平。
    为了判断某个引脚到底是高电平还是低电平,我们可以用下面的语句

   if  ( ((PINX & (1<<Y))>>Y) == 1)
   {
  当对应引脚输入为高电平时执行括号里面的语句。
   }
或者:
   if (((PINX & (1<<Y))>>Y) == 0)
    {
   当对应引脚输入为低电平时执行括号里面的语句。
    }

其中X为B、C、D,Y为该引脚的编号。如要判断PD3引脚的语句如下:
   if  (((PIND & (1<<3))>>3) == 1)
     {
      当PD3引脚输入为高电平时执行括号里面的语句。
     }
或者:
   if (((PIND & (1<<3))>>3) == 0)
    {
     当PD3引脚输入为低电平时执行括号里面的语句。
    }

下面让我们来进行实际程序的练习吧。
#include <avr/io.h>

int main()
{
  DDRD = 0xf0;                        //设置PD7至PD4为输出引脚,PD3至PD0为输入引脚。
  PORTD = PORTD & 0x0f;     //PORTD高四位置0,PD7至PD4初始化时输出低电平
  PORTD = PORTD | 0x0f;       //PORTD低四位置1,PD3至PD0端口设置上拉电阻
  SFIOR &= 0xfb;                     //允许使用上拉电阻。
  while(1)                                  //永远往复执行下面的内容
   {
      if  (((PIND & (1<<3)) >>3)== 1)    //如果PD3输入为高电平
      {
          PORTD |= 1<<7;                       //PD7为高电平
          PORTD &= ~(1<<6);                //PD6为低电平
      }
     else if  (((PIND & (1<<3))) == 0)
      {
          PORTD |= 1<<6;                      //PD6为高电平
          PORTD &= ~(1<<7);                //PD7为低电平
      }
   }
}

上面这段程序的执行结果是:当PD3和VCC相连或悬空时,PD7为输出电压为VCC,PD6输出电压为0,当PD3和GND相连时,PD7为输出电压为0,PD6输出电压为VCC。
 楼主| 发表于 2013-12-4 22:00:57 | 显示全部楼层
Atmega8单片机一共有3个计数器,T/C0,T/C1,T/C2。其中T/C0是通用的单通道8 位定时器/ 计数器模块。每个计数器的功能和使用方法都有所区别。我们从最简单的T/C0开始讲解。
    T/C0主要特点如下:单通道计数器、频率发生器、外部事件计数器、10 位的时钟预分频器。我们分别结合程序实例进行讲解。
   
    要学会T/C0,我们要注意四个寄存器的定义。
   
    T/C 控制寄存器TCCR0;
    TCCR0的低三位为CS02,CS01和CS00,其用于选择T/C 的时钟源。时钟源是指输入计数器、定时器的脉冲源。当无时钟时,计数器/定时器不工作。其可以选择atmega8主频的1倍、1/8,1/64,1/256和1/1024,也可选择由引脚T0(PD4)输入。

CS02        CS01        CS00        说明                                   设置语句(示例)

0        0        0        无时钟,                                            TCCR0 &= 0xf8;
0        0        1        clkI/O/1( 没有预分频)                         TCCR0 &= 0xf8; TCCR0 |=0x01;
0        1        0        clkI/O/8( 来自预分频器)                      TCCR0 &= 0xf8; TCCR0 |=0x02;
0        1        1        clkI/O/64( 来自预分频器)                    TCCR0 &= 0xf8; TCCR0 |=0x03;
1        0        0        clkI/O/256( 来自预分频器)                  TCCR0 &= 0xf8; TCCR0 |=0x04;
1        0        1        clkI/O/1024( 来自预分频器)                TCCR0 &= 0xf8; TCCR0 |=0x05;
1        1        0        时钟由T0引脚输入,下降沿触发             TCCR0 &= 0xf8; TCCR0 |=0x06;
1        1        1        时钟由T0引脚输入,上升沿触发             TCCR0 |= 0x07;

      T/C 寄存器TCNT0;
      通过T/C 寄存器可以直接对计数器的8 位数据进行读写访问。可以直接读出TCNT0的值,也可以写入TCNT0。
我们来举一个例子来理解TCNT0。当atmega8单片机的主频为8M时,如果CS2:0为001,即没有预分频时,1us时间为计数器的8个点。即8M/1000*1000=8;
      我们看如下程序示例:

    TCCR0 &= 0xf8;
    TCCR0 |=0x01;
    TCNT0 = 0;
      …..(中间执行其它语句)
    A=TCNT0/8;

      这段程序的执行结果是,变量A中所保存的数值,即为中间执行其它语句所耗用的时间,单位为微秒(前提条件是中间执行其它语句的耗时不超过TCNT0所表示的最大值,即256/8=32微秒。)
      通过以上这段例子,大家对计数器有了一个初步的认识了。当然,如果TCNT0所表示的最大值太小,有两种办法可以处理。
      第一种方法是改变T/C0的预分频设置。如在上面的例子中,当预分频设置为1/1024时,1us时间为计数器的8/1024个点。也就是说,计数器的1个点代表128us。但大家可以看到,当预分频设置越高,计数器所代表的时间虽然越多,但精度越差。当没有预分频时,计数器的精度为1us,但当1/1024预分频的时候,计数器的精度为128us。
第二种方法便是合理使用定时器中断,要使用好定时器中断,先让我们来看另外两个寄存器。
      T/C 中断屏蔽寄存器TIMSK;
      T/C 中断标志寄存器TIFR;
 楼主| 发表于 2013-12-4 22:01:53 | 显示全部楼层
T/C 中断屏蔽寄存器TIMSK;
    TIMSK也是一个8位的寄存器,其最低位TOIE0是溢出中断使能位。这里有一个概念,溢出。计数器(定时器)是从最小值0,每隔一定时间(这个时间长短由单片机主频和分频系数确定)加1,8位的计数器(定时器)所能记录的最大数为255。当计数器为255时,又加1的时候,就会发生溢出。
    当TOIE0 和状态寄存器的全局中断使能位都为”1” 时,计数器(定时器)的溢出中断使能。当计数器(定时器)发生溢出,中断服务程序得以执行。
    溢出之后,计数器(定时器)又从零开始计数。
    我们看如下的语句:

    TIMSK |= 0x01;
       SREG  |= 0x80;

       第一条语句是置TIMSK的最低位TOIE0,第二条语句是开全局中断。上面两条语句的结果是溢出中断使能。
在AVR Studio 4中,溢出中断程序是这样定义的:
   ISR(TIMER0_OVF_vect)
      {
      }
     其中TIMER0_OVF_vect是一个常量,其定义在AVR Studio 4安装目录的avr/io.h文件中,其表示是计数器(定时器)0的溢出中断程序。

    T/C 中断标志寄存器TIFR;
    TIFR也是一个8位寄存器,其第0位TOV0为计数器(定时器)0的溢出标志。当T/C0 溢出时, TOV0硬件置1。执行相应的中断服务程序时此位硬件清零。此外,TOV0也可以通过写1 来清零。

    让我们来看看下面的程序:

#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER0_OVF_vect)   
{
  unsigned int i;
  TCCR0 = 0;
  PORTB = PORTB & 0x0f;
  for(i=0;i<3000;i++)
      asm ("nop");
  PORTB = PORTB | 0xf0;   
  TCCR0 = 0x03;
  TCNT0 = 0;
}

int main(void)
{
  DDRB = DDRB | 0xf0;
  DDRB = DDRB & 0xf0;
  TCCR0 = 0;   
  TCNT0 = 0;
  TCCR0 = 0x03;
  TIMSK |= 0x01;
  SREG |= 0x80;
  while(1);
}

这段程序我先不讲解,布置一个作业,请大家看看这段程序的功能是什么?

然后我们再回过头来看,我们在上次谈到,如果要计时,但这个时间超过计数器的最大时间时,第一种方法是改变T/C0的预分频设置,第二种方法便是合理使用定时器中断。那我再布置第二个作业,单片机主频为8M,预分频设置为001时,如何用T/C0延时1000微秒?请大家编出完整的,可以运行的程序。
请大家一定要重视这两个作业,从这一课开始,我们要逐步增加练习了,这种练习有两个好处,一是增强大家自己编程序的能力,二是增强大家阅读他人程序的能力。在讲解定时器、ADC、按键驱动和EEPROM读写时,我会各布置一个读程序,一个写程序。当大家能够顺利地读我布置的程序,能够写我要求编制的程序时,大家就会真正去理解一个遥控器程序了。
唉,这段时间的业余时间全花在遥控器的讲解上面了,平衡仪和陀螺仪的原型设计和原型程序都已经出来了,一直没有时间来画电路图和PCB,做不出成品出来,用试验小板搭的电路根本没办法放在450上来测试。也不敢这样测,杜邦线万一哪一根接触不良,就有可能导致坠机。
电调现在最基本的功能已经编出来了,试了几种电机效果还不错,但要真正能够用,还得增加很多设置程序,如电调的基本设置程序、电压的监测程序什么的,还得考虑很多意外的处理程序。最近年底了,上班也特别忙。
时间真是不够用啊,不过请大家放心,我肯定会先把遥控器这个帖子写完。由于大家手头有esky 0404g控的并不多,加上2.4g的esky的高频发射板(本来是接收四通道的PPM信号的)在PPM信号扩展到5通道时工作正常,扩展到6通道时出现了偶而的不稳定现象,考虑到通用性的原因,遥控器我已经改为8个通道(5个比例通道,3个开关通道),高频发射和接收用了飞梦2.4g改装套装和8通接收机。上机测试非常稳定。最后公布的源程序我准备公布这个版本的。
 楼主| 发表于 2013-12-4 22:02:06 | 显示全部楼层

atmega8就是一种单片机,这个系列的有很多种,这是最低端的一种,支持ISP下载。不建议从坏电调上拆下来。电调上的M8不知道锁了串行没有,如果锁了串行,就不能用ISP下载了,要用高压编程器恢复才能正常使用,而且我看新西达电调上用的M8是最小的那种MLF封装,业余条件下很不好焊接。我用的是atmega8a-au,TQFP封装的,大小正合适。网上买很便宜,我买的价格为5.3元/片。
 楼主| 发表于 2013-12-4 22:02:45 | 显示全部楼层
这是某位朋友的提问:
我想请教一下,你的PPM输出编码如何实现?我现在用定时器来控制PPM输出,但是效果不理想,定时短了,单片机处理不过来,定时长了,电位器控制颗粒度增大了
--------------------------------------------------------------------------------------------------------
PPM输出编码是控制用编码,其对时间准确度的要求很苛刻,用定时器中断来做的话,很难保证波形的精度。
应该直接用程序延时来做。程序的延时不能用_delay_ms或_delay_us之类的代码。这种代码也不能保证延时的精度。我举个例子,一个通道的PPM编码如何实现。

     High = CurrPluseTime.ChannelHigh[0];
     Low = CurrPluseTime.ChannelLow[0];
     TimePoint = TCNT1;
     PORTB = PORTB & 0xfd;
     DelayTime(High);
     PORTB = PORTB | 0x02;
     DelayTime(Low);

其中DelayTime的代码如下:

void  DelayTime(unsigned int Time)
{
unsigned int i,j,k;

i = TCNT1;
k = Time * 2;
j = i+k;

if (j > i)
   {
    while(1)
       {
         k = TCNT1;
         if ((k>j)|(k<i))return;
       }
   }
else
   {
    while(1)
       {
         k = TCNT1;
         if ((k >j) &( k<i)) return;
      }
   }
}
上面的程序就是建立在对定时器(计数器)充分理解的基础上的。所以这也是我一再写这些基础的知识的原因。只有把这些基础的东西理解透彻了,遥控器的程序真的很简单的。

我所说的两个作业现在还没有人回答。难道说大家真的全部都已经对定时器(计数器)很熟悉了?
 楼主| 发表于 2013-12-4 22:03:55 | 显示全部楼层
最近确实没有更新,一是因为回答我问题的只有一个人,二是因为快过年了,实在太忙。不过见还是有人对这个挺感兴趣,本周末我会抽时间继续更新的。

另外不知道谁手上有AD6.9的L3G4200D、ADXL345、HMC5883L的库,我的450直升机用的平衡仪和锁尾陀螺仪基于原型模型的试验程序已经编好了,要上机试必须要画板子,自己画封装实在是太麻烦了!先谢谢了!

如果试制成功的话,高性能平衡仪和锁尾陀螺仪的成本只需要65元。岂不爽歪歪?(板子10元,L3G4200D 20元,ADXL345,6元,HMC5883L,12元,ATMEGA32A-AU,12元,阻容晶振等5元)
 楼主| 发表于 2013-12-4 22:05:02 | 显示全部楼层
本来想继续讲解定时器\计数器的,如果只做遥控器的话,就理解到上面所讲解的内容即可。如果要做平衡仪、锁尾陀螺仪或电调,则还有更进一步的内容需要掌握,但考虑到已经有部分同学反映现在讲解的难度已经偏高了,所以先就讲到这里,等到大家自己真正做遥控器或修改我所提供的遥控器源程序的时候,就会对上面的内容有更深的认识。当大家理解到这些概念的时候,也就会觉得它其实真的很简单。等到那个时候,再结合平衡仪、陀螺仪或电调来讲解,可能效果会更好一些。
所以现在让我们进入下一课:
五、10分钟学会ADC
      首先大家一定要知道ADC到底是干什么的。如果还有人不知道ADC是干什么的,请翻回我的教程的单片机基本概念的章节,对ADC是干什么的有比较仔细的描述。
      在遥控器程序中,操纵ADC是一个比较重要的环节,所有比例通道的摇杆所在的位置,均是由ADC检测电压来得到的。我们都知道,遥控器的比例通道实际上就是摇杆控制旋转电位器的位置来实现的。遥控器所用的旋转电位器有三只脚,左面和右面的脚的阻值是一个定值,中间的脚与左面的脚(或右面的脚)的阻值随着电位器的位置而变化,因此,如果两边的脚分别接GND和VCC,则中间的脚的电压会随着电位器的位置变化而改变,然后通过ADC转换成数字信号,单片机就知道现在摇杆所在的位置了。
      本想再照两张实例图的,但相机被别人拿走了,手机照相效果太差。只有等春节后别人还给我再补上了。
      要熟练操作Atmega8单片机的ADC,要熟悉四个寄存器:
      ADMUX、ADCSRA、ADCL及ADCH。让我们依次来了解它们。
      ADC 多工选择寄存器- ADMUX
        
ADMUX寄存器依次从高到底位如下:
第7位 第6位 第5位 第4位 第3位 第2位 第1位 第0位
REFS1 REFS0 ADLAR – MUX3 MUX2 MUX1 MUX0
   

REFS1:0: 参考电压选择,通过这几位可以选择参考电压。如果在单片机的AREF 引脚上施加了外部参考电压,内部参考电压就不能被选用了。不知道什么是ADC参考电压的,请翻回我的教程的单片机基本概念的章节。

REFS1 REFS0 参考电压选择
0 0 AREF脚施加的电压
0 1 AVCC
1 0 保留
1 1 2.56V的片内基准电压源, 需要在AREF 引脚外加滤波电容


第5位ADLAR影响ADC转换结果在ADC数据寄存器中的存放形式。ADLAR置位(设置为1)时转换结果为左对齐,否则为右对齐。一般情况下,我们不去动ADLAR的值,直接使用右对齐的方式,也是我们习惯的方式。

MUX3:0: 模拟通道选择位
ATMEGA8单片机一共有8个ADC输入引脚,但它每次只能处理一个ADC引脚,具体处理哪一个引脚,就由MUX3:0这四位来确定。

MUX3..0 单端输入
0000 ADC0
0001 ADC1
0010 ADC2
0011 ADC3
0100 ADC4
0101 ADC5
0110 ADC6
0111 ADC7

ADC 控制和状态寄存器A -  ADCSRA

ADCSRA寄存器依次从高到底位如下:
第7位 第6位 第5位 第4位 第3位 第2位 第1位 第0位
ADEN ADSC ADFR ADIF ADIE ADPS2 ADPS1 ADPS0

• 第7位ADEN: 启动ADC
ADEN置位即启动ADC,否则ADC功能关闭。
• 第6位ADSC: ADC开始转换
在单次转换模式下,ADSC 置位将启动一次ADC 转换。在连续转换模式下,ADSC 置位将启动首次转换。
•第5位ADFR: ADC 连续转换选择
该位置位时,运行在连续转换模式。该模式下,ADC 不断对数据寄存器ADCH和ADCL进行采样与更新。该位清零,终止连续转换模式。
•第4位ADIF: ADC中断标志
•第3位ADIE: ADC 中断使能
若ADIE 及SREG 的位I 置位, ADC 转换结束中断即发生ADC中断。
•ADPS2:0: ADC 预分频器选择位
由这几位来确定XTAL 与ADC 输入时钟之间的分频因子。一般情况下我们可以不去动这个参数。

数据寄存器ADCH和ADCL
ADC 转换结束后,转换结果存于这两个寄存器之中。ADCL中为低8位,ADCH中为高两位(10位ADC),读取ADCL 之后,ADC 数据寄存器一直要等到ADCH 也被读出才可以进行数据更新。必须先读出ADCL 再读ADCH。
我们可以采取如下语句来读写和合并ADCH和ADCL中的数据:
      l=ADCL;   
      h=ADCH;
      h<<=8;
      h|=l;
注意其中的l和h均需为int型变量,最后的ADC数据存在h中。


ADC的寄存器的功能讲完了,让我们看看如下的程序示例:

void adc_init(void)
{      
   DDRC = DDRC & 0x0f;  //设置引脚PC4,5,6,7为输入引脚
  PORTC = PORTC & 0x0f;  //关闭引脚PC4,5,6,7上拉电阻。
  ADCSRA = 0x00;  //关闭ADC
   ADMUX = 0x44;  //选择AVCC为参考电压,输入引脚选择为ADC4(引脚PC4)
  ACSR=1<<ACD;  //关闭模拟比较器
  ADCSRA = 0x84;  // 启动ADC,单次,16分频
}

unsigned int adc_read(unsigned char ADCChannel)
{
  unsigned int data,h,l;
  unsigned char i;
  unsigned long int p;
  ADMUX = (ADMUX & 0xf0)+(ADCChannel & 0x0f);  //将选取的通道值设置进ADMUX中
  for (i=0;i<4;i++)                       //连续测量4次取平均值
    {
      ADCSRA|=0x40;                        //开始转换
      while ((ADCSRA&0x10)==0);            //等待转换结束
      ADCSRA|=0x10;                        //清标志位
      l=ADCL;                         //读取ADCL值
      h=ADCH;                        //读取ADCH值
      h<<=8;                          //后面两句为合并数据值
      h|=l;                             
      data+=h;                        //求4次测量数据的总和
     }
   data/=4;                               //除以4得出4次测量平均值
return data;
}
      程序adc_init是一个比较典型的ADC初始化程序,在ADC初始化中,一般先设置要使用ADC的几个引脚为输入引脚,并关闭其上拉电阻。然后关闭ADC,关闭模拟比较器,选择参考电压和输入引脚,最后设置启动ADC的基本参数。
      程序adc_read则是一个比较典型的读取ADC的程序。它有一个输入参数,即当前要读取哪一个ADC通道。其返回的值即为读取的ADC数值。为增加数据的稳定性,我们对同一个通道读取4次取平均值。当然,这不是必须的。
      有了这两个程序,就可以编制读写ADC的程序了。为了增加实际可操作性,我们下面将给出一个读取ADC值并在LCD1602液晶下显示的一个示例程序。这个程序将用到LCD1602显示相关的文件,我们把它打包在lcd1602.h和lcd1602.c两个文件中,构成lcd1602的驱动。Lcd1602的驱动程序较为复杂,如果大家有兴趣,我在最后可以予以讲解,现在暂时只需了解该驱动程序有哪些函数可以直接用就行了。本示例程序给出的lcd1602驱动程序是基于硬件接线为4位数据线驱动方式,只适用于atmega8。其硬件接线如下图所示:
低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器  作者:冷血动物 3123
 楼主| 发表于 2013-12-4 22:06:25 | 显示全部楼层
唉,一开年就忙得不得了,加上原来的LCD1602被我踩碎了,又不能把已经装在遥控器上的另一块拆下来验证需要讲解的程序,所以拖了这么久。不过,LCD1602的问题我已经解决了,讲解的程序我验证无误后才公布的,大家等待已久的更新来了!

    今天让我们来学习一个读取ADC值并在LCD1602液晶下显示的一个示例程序。LCD1602的接线法如上节课的图所示。电位器的接法是两个固定端一端接VCC,一端接地,可变端接ADC6(PC6)。
    LCD1602的驱动程序打包在lcd1602.h和lcd1602.c两个文件中,我先介绍一下可以直接用的主要函数。
    void lcd_init(void)  //以四位数据线(四线制)方式初始化lcd1602
      SetCursor(y, x)    //将当前光标设置到Y行X字符处
    void lcd_data(unsigned char temp1)  //向当前的光标处写入一个字符
    void lcd_string(char *data)         //向当前的光标处写入一个字符串
    void lcd_clear(void)     //清除lcd1602的所有显示内容

    下面我们看看具体的程序:
#include "Lcd1602.c"
#include "adc.c"
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

void adc_display(void)
{
unsigned int a[4];
unsigned char i;
unsigned int r;
adc_init();      //初始化ADC

while (1)
{
_delay_ms(300);  //每次采集间隔300ms
  SetCursor(1,1);   //光标定位在第1行第1字符

  r=adc_read(6);    //读取ADC6
  a[0]=r/1000;     //下面把读取的ADC6按四位数转化成字符,并显示在LCD1602上
  a[1]=(r%1000)/100;
  a[2]=(r%100)/10;
  a[3]=r%10;
   for(i=0;i<4;i++)
    {
        lcd_data(a+0x30);   //1+0x30=0x31,等于字符“1”的值,2+0x30=0x32,等于字符“2”的值,依次类推。
    }
}
}

int main(void)
{
        lcd_init();         // 初始化lcd1602
    SetCursor(1,2);      //下面四条语句为显示欢迎词,同时也验证lcd1602显示正常
        lcd_string("Good Morning");
        SetCursor(2,3);
        lcd_string("Nice day!");
        _delay_ms(1000);    //延时1秒
    lcd_clear();         //清屏
    adc_display();       //循环显示ADC采集值。
}

头文件ADC.C中只有我们在上一节课讲过的两个函数,即adc_init和adc_read。所有的文件我均打包在ADC.rar 文件中,在AVR Studio 4中编译通过。
低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器  作者:冷血动物 9053 ADC.rar (4.71 KB, 下载次数: 63)
 楼主| 发表于 2013-12-4 22:08:08 | 显示全部楼层
六、10分钟学会驱动按键
单片机的按键驱动方法有很多种,由于航模遥控器按键主要用于设置各类参数时使用,一般所需要的按键数并不多。我们就选用最简单,也最容易理解的矩阵式按键驱动方法吧。
让我们看看下图。

低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器  作者:冷血动物 8142

这个图可能有些初接触按键的同学理解起来有困难,让我们看看等效的下图。

低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器  作者:冷血动物 3215

其中RX、TX、PB0、PD3分别为atmega8的四个引脚。可以选择任何的通用输入输出引脚,而不仅仅局限于上面的4个。在上图中,我们可以把RX、TX称为行,PB0、PD3称为列。
在初始化的时候,我们设置行引脚为输出,列引脚为输入。输入引脚设置上拉电阻有效。输出脚最初均输出高电平。
在开始检测按键的时候,我们先设置RX为0,TX为1,再检测PB0和PD3的状态。如果PB0为0,则证明S1键被按下,PB0被RX下拉至低电平。如果PD3为0,则证明S3键被按下,PD3被RX下拉至低电平。
然后再设置RX为1,TX为0,再检测PB0和PD3的状态。如果PB0为0,则证明S2键被按下,PB0被TX下拉至低电平。如果PD3为0,则证明S4键被按下,PD3被TX下拉至低电平。
这就是矩阵式按键驱动的最常规的做法。示例程序如下:Press_init为按键初始化程序,GetPress则是检测当前按键值的程序,每调用一次GetPress,其返回值为当前按下的按键号。如果返回值为0,则表示没有任何按键被按下。

void press_init(void)
{

DDRD= DDRD | 0x03;  //PD0、1设置输出
DDRD= DDRD & 0xF7; //PD3设置输入
DDRB= DDRB & 0xFE; //PB0设置输入

PORTD = PORTD | 0x08;  //PD3设置上拉
PORTB = PORTB | 0x01;  //PB0设置上拉
}


int getpress(void)
{int x,y,i,j;

PORTD = PORTD | 0x01;  //PD0置1
PORTD = PORTD & 0xFD;  //PD1置0
_delay_us(2);
  x= (PIND & 0x08) >> 3;
  i=  PINB & 0x01;


PORTD = PORTD | 0x02;  //PD1置1
PORTD = PORTD & 0xFE;  //PD0置0
_delay_us(2);
  y = (PIND & 0x08) >> 3;
  j = PINB & 0x01;

  PORTD = PORTD | 0x03;  //PD0\PD1置1

  if (j==0) return 1;
  else if (i==0) return 2;
  else if (x==0) return 3;
  else if (y==0) return 4;
  else return 0;

}
 楼主| 发表于 2013-12-4 22:08:24 | 显示全部楼层
七、十分钟学会EEPROM读写
Atmega8单片机的片内EEPROM为512字节,其读写较为简单,最主要的几个操作函数为:
eeprom_is_ready();  检测EEPROM是否准备好。OK返回1(返回EEWE位)
eeprom_busy_wait();  等待EEPROM操作完成
eeprom_read_byte (const uint8_t *addr);  读取指定地址的一个字节8bit的EEPROM数据
eeprom_read_word (const uint16_t *addr); 读取指定地址的一个字16bit的EEPROM数据
eeprom_read_block (void *buf, const void *addr, size_t n); 读取由指定地址开始的指定长度的EEPROM数据
eeprom_write_byte (uint8_t *addr, uint8_t val); 向指定地址写入一个字节8bit的EEPROM数据
eeprom_write_word (uint16_t *addr, uint16_t val); 向指定地址写入一个字16bit的EEPROM数据
eeprom_write_block (const void *buf, void *addr, size_t n); 由指定地址开始写入指定长度的EEPROM数据,
要使用以上的函数,必须在程序开始的头文件定义中加入 #include <avr/eeprom.h>

在遥控器程序的编制中,我们使用EEPROM保存多个遥控模型的设置数据,由于每个遥控模型的设置数据远远超过2个字节,因此我们使用的函数主要是eeprom_read_block和eeprom_write_block。我们主要结合程序讲解这两个函数,其它的读写字和字节的函数较为简单,大家可以自己试试编编程序往EEPROM中写入一个字节,然后再读出来。注意以下几点:
1、在读出一个字前,一定要加上while(!eeprom_is_ready());语句等待eeprom准备好再读
2、在写入一个字前,一定要加上eeprom_busy_wait();等待eeprom的其它操作完成。
3、eeprom的地址是从0至511。(0至0x200)
我们看以下的程序:


void ReadModelSav(unsigned char ModelNo)
{
   unsigned int i,j,k,l;
   if ((ModelNo == 1 ) | (ModelNo == 2 ) | (ModelNo == 3 ))
     {
       k = sizeof(CurrModelSav);   
       l = k * (ModelNo-1)+1;
       while(!eeprom_is_ready());
       eeprom_read_block((void*)&CurrModelSav,(const void*)l,k);
           if (CurrModelSav.PlanesNo != ModelNo )   //如果读出来的对不上模型编号,则初始化eprom中的模型数据
             {
           
          for(i=0;i<8;i++)
                    {
                          CurrModelSav.RevSw[i]= true;  //初始化为正向
                        CurrModelSav.SubTrim[i]=0;
                        CurrModelSav.TrvlAdj[i]=0;
                        }                  
          CurrModelSav.CpMod = 1; // 1,airplane,2,ccpm,3,vtail
          j=0;
                  for(i=0;i<5;i++)
                    {
             CurrModelSav.ThroCurv[i] = j;
             CurrModelSav.PitCurv[i] = j;
                         j = j + 25;
             }
          CurrModelSav.GyroSens = 0;
                  CurrModelSav.PlanesNo = ModelNo;
          eeprom_busy_wait();
                  eeprom_write_block((const void*)&CurrModelSav,(void*)l,k);
                }

           //用读出或默认的数据初始化CurrPluseTime
         for(i=0;i<6;i++)
                     {
                          CurrPluseTime.ChannelHigh[i] = OriChannelHigh;   //高电平统一为400ms
              CurrPluseTime.ChannelLow[i]  = OriChannelCenter;  //低电平先取中值
              CurrPluseTime.ChannelStart[i] = OriChannelStart + ((CurrModelSav.SubTrim[i]*OriChannelLength)/100) - ((CurrModelSav.TrvlAdj[i]*OriChannelLength)/200);
                          CurrPluseTime.ChannelLength[i] = OriChannelLength + ((OriChannelLength * CurrModelSav.TrvlAdj[i])/100);
              }
     }
}

这个程序是遥控器中用来读取保存的模型数据的,它支持3个不同模型的数据。它的输入值为要读取的模型编号,运行结束后,它把读出的数据保存在CurrModelSav这个预先定义的模型数据结构体里。
这个程序先是计算要读取的模型数据的起始地址,然后读出模型数据。用读出的模型数据中的模型编号对比,如果读出的编号与要读的编号一致,则读出的数据正确,则用读出的数据来初始化要控制模型的各类数据,如果读出的数据不正确,则说明EEPROM没有保存正确的模型数据,则用默认值初始化控制模型的各类数据,并把默认值保存到EEPROM中去。
 楼主| 发表于 2013-12-4 22:09:44 | 显示全部楼层
讲解到这里,基本上一个遥控器可能涉及到的单片机基础知识已经讲解得差不多了,还差LCD1602的驱动程序的讲解了,不过这个难度比较高,可能有些同学不一定喜欢,所以暂时放在一边,如果有同学需要详细讲解,请提出,我再予以讲解。下面,我们将正式进入大家最关心的部分了!
三、        舵机测试仪程序讲解
舵机测试仪的烧录和使用我们已经在前面58楼仔细讲解过了,让我们来揭开它的程序的神秘面纱吧。
注意,这个程序用到了adc.c这个文件,这个文件里面只有两个函数,一个是adc_init,用于ADC的初始化,一个是adc_read,用于读取特定的ADC通道的值。这两个函数已经在前面的十分钟学会ADC章节予以了讲解,这里就不再重复了。

#include "adc.c"
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void)
{
    unsigned int TimeStart,TimeEnd1,TimeEnd2,r,TempTime;

    adc_init();
    _delay_ms(100);
        
    TCCR1A = 0;   
    TCCR1B = 0x03;  // 1/64分频,1ms为250个点,20ms为5000个点
        
   DDRB = 0xff;
    PORTB = 0x0;
   
    while (1)
     {
      PORTB |=  0x01;  // PB0置1,选取的PB0作为舵机的信号线输出
      r=adc_read(7);  //选取的ADC7接可变电阻器的中间线
      TimeStart = TCNT1;
      r = 180 + r /4;
      TimeEnd1 = TimeStart + r;
      TimeEnd2 = TimeStart + 5000;


      //高电平延时至ADC7采集的电位器所代表的时间
   if (TimeEnd1 > TimeStart)   
      {
       while(1)
        {
         TempTime = TCNT1;
         if ((TempTime > TimeEnd1) | (TempTime < TimeStart))
           {
             PORTB &= 0xFE;  // PB0置0
             break;               
           }
        }
      }
     else if (TimeEnd1 < TimeStart)
       {
        while(1)
           {
           TempTime = TCNT1;
           if ((TempTime > TimeEnd1) & (TempTime < TimeStart))
            {
                   PORTB &= 0xFE;  //PB0置0
             break;               
            }
           }
        }

    //低电平延时到20ms
   if (TimeEnd2 > TimeStart)
     {
      while(1)
        {
           TempTime = TCNT1;
           if ((TempTime > TimeEnd2) | (TempTime < TimeStart))
         {
                break;               
          }
         }
      }
   else if (TimeEnd2 < TimeStart)
     {
      while(1)
        {
          TempTime = TCNT1;
          if ((TempTime > TimeEnd2) & (TempTime < TimeStart))
        {
               break;               
        }
         }
     }
  }
}


舵机测试仪的算法很简单,就是先采集ADC7所接的电位器的值,计算出延时后计数器的值,然后用不断读取计数器值与计数器结束值相比较的方式来精确控制时间。因为计数器可能在延时过程中溢出,所以要区别结束值>开始值和结束值<开始值两种不同的状态。
舵机测试仪的源程序打包如下,   低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器  作者:冷血动物 8399 SevoTest.rar (2.39 KB, 下载次数: 81)
在AVR Studio 4 中编译测试无误。单片机为atmega8,采用外置16M晶振。
 楼主| 发表于 2013-12-4 22:09:57 | 显示全部楼层
为了把开源自制航模电子设备项目更好地发展下去,在此诚邀以下几个方面的志同道合者加入...
1、硬件套件供应,提供自制航模电子设备的套件;
2、有一定基础的程序开发者,可以加入平衡仪等下一步开发者;
3、有网站基础的网站开发者,可以打造我们自己的开源航模电子设备网站。
要求:有责任心,以服务大家为准则,套件供应不以赢利为目的;程序和网站开发者需认同自己源程序的完全公开和免费非商业应用。
不知道有朋友有意向加入没有,如果有,请在下面跟贴。
 楼主| 发表于 2013-12-4 22:11:00 | 显示全部楼层
今天先把8通道遥控器的电路图和PCB文件传上来,是基于AD69的。下一步我将发布源程序并予以讲解。
不知道有没有哪位朋友愿意为大家提供PCB和套件?

另外,草原鹰,你会用AD69画电路图和PCB吗?
低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器  作者:冷血动物 5196 8通道遥控器.rar (207.62 KB, 下载次数: 186)
 楼主| 发表于 2013-12-4 22:11:52 | 显示全部楼层
下一步我们将进入公布及讲解8通道遥控器源程序的阶段,也是大家最关心,最重要的阶段。出于众所周知的原因,特公布以下声明。同意以下声明者,本人授权可学习所公布的电路图、源程序,试验所公布的HEX文件,不同意者,不予授权。在此声明前及后任何与本声明不一致的说法,以本声明为准。
1、航模为具有危险性的产品。本人所公布电路图、源程序、HEX文件均为试验用,不是正式航模产品。公布目的是同被授权人探讨、共同学习航模设备设计、制造技术,而不是操控航模,本人不保证所公布电路图、源程序、HEX文件的安全性、可靠性,本人不对任何情况下使用电路图、源程序、HEX文件的任何负面后果负责。
2、所公布电路图、源程序及HEX文件版权归本人所有,公布电路图、源程序及HEX文件不等同于本人放弃版权。被授权人可用于非商业用途的学习,不得用于商业用途。被授权人有权转载,但转载的同时必须转载此声明并注明原始出处及版权所有者。被授权人有权利用本电路图、源程序及HEX文件、核心算法等在出版物上发表论文,但第一作者须为版权所有者。
3、被授权人如需用于商业用途,须征得版权所有者的书面授权。
 楼主| 发表于 2013-12-4 22:13:00 | 显示全部楼层
现在发布HEX文件和源程序,请各位下载前,自行阅读417楼的声明,不接受此声明者,本人不授权下载和学习HEX文件和源程序。如转载,请务必同时转载417楼的声明,并保持本两个压缩包的密码不变。未按此要求转载者,本人视为非授权转载,造成后果者,由转载者自行承担。

两个压缩包的解压密码为:我同意417楼声明
低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器  作者:冷血动物 3225 发布源程序.rar (11.53 KB, 下载次数: 179) 低成本自制航模电子设备系列之一:和我一起用ATMEGA8做遥控器  作者:冷血动物 7872 HEX.rar (7.95 KB, 下载次数: 106)

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

QQ|关于模吧|APP下载|广告报价|手机版|企业会员|商城入驻|联系我们|模吧 ( 黔ICP备2022002348号-1 )

© 2013-2020 Moz8.com 模吧,玩出精彩!