本文主要介绍怎么通过遥控接收机的信号控制单片机的IO口。试验器材:
天地飞WFT09II 9通道2.4G遥控器+接收机
STC15F104W单片机
这里接收机输出的是频率为50hz,周期为20ms的pwm波形,通过单片机外部中断口接收pwm信号,启用定时器/计数器对pwm高电平的持续时间进行计数。
Arduino中的pulseIn函数可以直截了当地读取pwm的值,可以参考其底层的实现方法,这里有一个链接介绍pulseIn函数读取pwm的思想。
http://blog.csdn.net/soso90soso/article/details/78599621?locationNum=5&fps=1
文中关键部分:
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout) {
pinMode(pin, INPUT);
uint32_t start = micros();
while(digitalRead(pin) == state && (micros() - start) < timeout);
while(digitalRead(pin) != state && (micros() - start) < timeout);
start = micros();
while(digitalRead(pin) == state && (micros() - start) < timeout);
return micros() - start;
}
微妙级,死循环读取引脚电平。因为不确定IO口接收到输入的pwm的瞬间是高电平还是低电平,所以While循环里先判断高电平,再判断低电平,再判断一次高电平,从而获得准确的占空比。
至于怎么在单片机上读取pwm呢?在网上查找一些资料后,我了解到可以通过外部中断+定时器来实现。最初我参考这个源码:
//转发请注明:http://bbs.5imx.com
//芯片型号为STC15F100W-STC15F104W,烧写程序时请选择IRC频率12M.
//STC15F100W芯片的P3.2脚为模型接收机PWM信号输入脚.
//本程序可根据个人需求更改判断变量a的值来达到相应的要求.
//变量a的值单位为微秒:1000us=1ms,1500=1.5ms,2000=2.0ms.
#include
sfr AUXR = 0x8E;
unsigned int a=0;
sbit led1=P3^3;
sbit led2=P3^4;
sbit led3=P3^5;
bit HL;
void Timer0Init(void)
{
AUXR &= 0x7F;
TMOD &= 0xF0;
TL0 = 0x00;
TH0 = 0x00;
TF0 = 0;
TR0 = 0;
}
void main()
{
EA = 1;
INT0 = 1;
EX0 = 1;
IT0 = 0;
Timer0Init();
while(1){
if(a>980&&a<1020)led2=0; else led2=1;
if(a>1480&&a<1520)led3=0; else led3=1;
}
}
void INT_0 (void) interrupt 0 using 2
{
HL = INT0;
if(HL==1)TR0 = 1;
if(HL==0){
TR0 = 0;
a = TH0;
a = a*256+TL0;
TL0 = 0x00;
TH0 = 0x00;
}
}
这个代码里面有一个问题就是,当单片机满足中断条件,就会不断跳到外部中断处理函数中,感觉这样太占用cpu资源,可以编写一个函数,在监听pwm值时开启外部中断,在获取到pwm值时关闭中断。具体方法如下:
#include
sfr AUXR = 0x8E;
unsigned int a0=0;
unsigned int a1=0;
//sbit led1=P3^3;
sbit led2=P3^4;
sbit led1=P3^5;
bit HL;
void pulseIn0();
void pulseIn1();
void Timer0Init(void)
{
AUXR &= 0x7F;
TMOD &= 0xF0;
TL0 = 0x00;
TH0 = 0x00;
TF0 = 0;
TR0 = 0;
}
void main()
{
EA = 1;
//INT0 = 1;
EX0 = 0;
IT0 = 0;
EX1 = 0;
IT1 = 0;
Timer0Init();
while(1){
pulseIn1();
if(a0>980&&a0<1020)led2=0; else led2=1;
pulseIn0();
if(a1>1480&&a1<1520)led1=0; else led1=1;
}
}
void INT_0 (void) interrupt 0 using 2
{
HL = INT0;
if(HL==1)TR0 = 1;
if(HL==0){
TR0 = 0;
a0 = TH0;
a0 = a0*256+TL0;
TL0 = 0x00;
TH0 = 0x00;
EX0 = 0;
}
}
void INT_1 (void) interrupt 2 using 2
{
HL = INT1;
if(HL==1)TR0 = 1;
if(HL==0){
TR0 = 0;
a1 = TH0;
a1 = a1*256+TL0;
TL0 = 0x00;
TH0 = 0x00;
EX1 = 0;
}
}
void pulseIn0(){
while(INT0==1);
EX0 = 1;
}
void pulseIn1(){
while(INT1==1);
EX1 = 1;
}
STC15F104W单片机外部中断0(INT0)和外部中断1(INT1)触发有两种
触发方式,上升沿或下降沿均可
触发方式和仅下降沿
触发方式。
程序中
IT0 = 0;
IT1 = 0;
所以上升沿或下降沿后均可产生外部中断,我设想是通过上升沿触发中断,
void pulseIn0(){
while(INT0==1);
EX0 = 1;
}
该段代码的意思是,如果接收到的pwm信号正处于高电平,先进入死循环,等pwm信号处于低电平再开启中断,那么下一个上升沿即可触发中断,触发中断后立即启用定时器0正好可以对高电平进行计数,到下一个下降沿时关闭中断。
试验效果如下:
INT1口接收到的pwm信号满足a1>1480&&a1<1520,led1熄灭。
而且用Arduino读取到的pwm值也在这个区间内,由此可见,用这种方法读取pwm的值精度是很高的,以后可以用STC15F104W单片机代替Arduino,通过pwm控制单片机的IO口。