B站來的小伙伴們,終於找到了遙控電機的代碼和當時寫的博客,哈哈
2018年12月11日
博客原文:
今天老師簡單講了一下紅外與步進電機,作為單片機開發板上唯一的無線通信協議和唯一的能驅動機器“動起來”的器件,emmmm......就像冬天里的溫度一樣讓人向往
來吧,先說說題目
題目
寫程序使遙控器上的“8”控制逆時針旋轉,“9”控制順時針旋轉,控制電機能加速減速。
遙控器控制的話,那就NEC協議咯,會得到一個用戶碼和一個按鍵碼,只要判斷這個按鍵碼和用戶碼就可以知道別人在遙控器上按的是哪個了。那這樣的話,還要知道對應按鍵的用戶碼和按鍵碼才行啊。知道哪個按下后就可以控制節拍正、反循環來控制電機的旋轉了嘛。控制每個拍的延時時間就控制了轉速嘛。這么一說,是不是挺簡單的啊,哈哈..
資料和原理圖
1.遙控器資料
這是我手機遙控器上的用戶碼和按鍵碼示意圖,用的是一個步步高DVD的遙控器
下面是分享的二維碼
emmm........單片機配套的大家可以等下自己去測試用戶碼和鍵碼是什么。
2.接線原理圖
3.NEC紅外協議原理圖
4.流程圖
嗯,就這些了吧。
代碼、注釋和流程
1.我們要先獲得遙控的用戶碼和按鍵碼,先來寫個NEC協議用戶碼和按鍵碼的顯示吧
紅外協議NEC顯示用戶碼和按鍵碼
#include <reg52.h>
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //數碼管顯示字符轉換表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //數碼管顯示緩沖區
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char T0RH = 0; //T0重載值的高字節
unsigned char T0RL = 0; //T0重載值的低字節
bit irflag;
unsigned char ircode[4];
void InitInfrared(void);
void ConfigTimer0(unsigned int ms);
void main()
{
EA = 1; //開總中斷
ENLED = 0; //使能選擇數碼管
ADDR3 = 1;
InitInfrared(); //初始化紅外功能
ConfigTimer0(1); //配置T0定時1ms
PT0 = 1; //配置T0中斷為高優先級,啟用本行可消除接收時的閃爍
while (1)
{
if (irflag) //接收到紅外數據時刷新顯示
{
irflag = 0;
LedBuff[5] = LedChar[ircode[0] >> 4]; //用戶碼顯示
LedBuff[4] = LedChar[ircode[0]&0x0F];
LedBuff[1] = LedChar[ircode[2] >> 4]; //鍵碼顯示
LedBuff[0] = LedChar[ircode[2]&0x0F];
}
}
}
/* 配置並啟動T0,ms-T0定時時間 */
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp; //臨時變量
tmp = 11059200 / 12; //定時器計數頻率
tmp = (tmp * ms) / 1000; //計算所需的計數值
tmp = 65536 - tmp; //計算定時器重載值
tmp = tmp + 18; //補償中斷響應延時造成的誤差
T0RH = (unsigned char)(tmp>>8); //定時器重載值拆分為高低字節
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0為模式1
TH0 = T0RH; //加載T0重載值
TL0 = T0RL;
ET0 = 1; //使能T0中斷
TR0 = 1; //啟動T0
}
/* 數碼管動態掃描刷新函數,需在定時中斷中調用 */
void LedScan()
{
static unsigned char i = 0; //動態掃描索引
P0 = 0xFF; //關閉所有段選位,顯示消隱
P1 = (P1 & 0xF8) | i; //位選索引值賦值到P1口低3位
P0 = LedBuff[i]; //緩沖區中索引位置的數據送到P0口
if (i < sizeof(LedBuff)-1) //索引遞增循環,遍歷整個緩沖區
i++;
else
i = 0;
}
/*
NEC數據格式:引導碼、用戶碼、用戶碼(或者用戶碼反碼)、按鍵鍵碼和鍵碼反碼,最后一個停止位。
引導碼:9ms的載波+4.5ms的空閑。
比特值“0”:560us的載波+560us的空閑。
比特值“1”:560us的載波+1.68ms的空閑。
*/
sbit IR_INPUT = P3^3; //紅外接收引腳
void InitInfrared() //初始化紅外,定時器1,中斷
{
IR_INPUT = 1; //輸入置高電平
TMOD &= 0X0F; //定時器模式,1,1
TMOD |= 0x10;
TR1 = 0; //定時器1運行
ET1 = 0; //定時器1中斷打開
IT1 = 1; //外部中斷1下降沿觸發
EX1 = 1; //打開外部中斷
}
unsigned int GetLevTime(bit nu)
{
TH1 = 0; //初始化定時器值
TL1 = 0;
TR1 = 1; //開始計時
while(IR_INPUT == nu) //等電平變化
{
if(TH1 > 0x30) //13.33ms
{
break; //信號超時,退出
}
}
TR1 = 0; //關閉定時器
return(TH1 * 256 + TL1); //返回定時器的值
}
void EXINT1_ISR() interrupt 2 //外部中斷1
{
unsigned char i, j; //循環輔助(接收4*8個位)
unsigned int time; //高低電平時間暫存值
unsigned char byt; //接收到的字節
time = GetLevTime(0); //檢測低電平時間
if((time <7833) || (time > 8755)) //8.5-9.5ms
{
IE1 = 0;
return; //如果沒在限定時間內,退出
}
time = GetLevTime(1); //檢測高電平時間
if((time<3686) || (time > 4608)) //4.0-5.0ms
{
IE1 = 0;
return; //如果沒在限定時間內,退出
}
//以上為引導碼部分
for(i=0; i<4; i++)
{//循環接收4個字節(用戶碼,用戶反碼,按鍵碼,按鍵反碼)
for(j=0; j<8; j++)
{//循環接收8位(從高位到低位)
time = GetLevTime(0); //得到低電平時間
if((time<313) ||(time >718)) //340-780us
{
IE1 = 0;
return;
}
time = GetLevTime(1); //得到高電平時間
if((time>313) && (time <718)) //340-780us
{
byt >>= 1; //接收到的是0(先高位,再低位)
}
else if((time>1345) && (time<1751))
{
byt >>= 1;
byt |= 0x80; //接收到的是1(先高位,再低位)
}
else //不符合1,也不符合0
{
IE1 = 0; //清空外部1中斷標志
return; //退出
}
}
ircode[i] = byt; //把收到的字節存到數組內
}
irflag = 1; //接收成功標標志
IE1 = 0; //清空外部1中斷標志
}
/* T0中斷服務函數,執行數碼管掃描顯示 */
void InterruptTimer0() interrupt 1
{
TH0 = T0RH; //重新加載重載值
TL0 = T0RL;
LedScan(); //數碼管掃描顯示
}
運行這個代碼后,按遙控器(記得把卡在下面的卡片拔掉)就可以顯示對應的按鍵碼了,記下6個按鍵碼和一個用戶碼等下有用。
2.獲得了按鍵碼和用戶碼之后,可以開始調整代碼加入電機控制的相關代碼了
#include <reg52.h>
unsigned char code BeatCode[8] = { //電機節拍
0x0E, 0x0C, 0x0D, 0x09,
0x0B, 0x03, 0x07, 0x06};
unsigned char T0RH = 0; //T0重載值的高字節
unsigned char T0RL = 0; //T0重載值的低字節
bit irflag = 0; //紅外接收標志,收到一幀正確數據后置1
unsigned char ircode[4]; //紅外代碼接收緩沖區
void InitInfrared(void); //初始化紅外
void ConfigTimer0(unsigned int ms);
unsigned char beatsta = 0; //電機狀態,0:停止,2:逆(左),1:順(右)
unsigned char rev = 7; //默認電機速度為7(2 - 60)
void main()
{
EA = 1; //開總中斷
InitInfrared(); //初始化紅外功能
ConfigTimer0(rev); //配置T0
PT0 = 1; //配置T0中斷為高優先級,啟用這行可以防止在遙控的時候電機卡
//大家可以試試去掉這行,連續遙控順轉,可以明顯感覺到按下遙控的時候電機卡了一下
while (1)
{
if (irflag) //接收到紅外數據時刷新電機狀態
{
irflag = 0;
//手機遙控器
if(ircode[0] == 0x49)//用戶碼49
{
switch(ircode[2])
{
//下面是電機旋轉控制
case 0x12://按鍵12
beatsta = 2;break;//逆(左)
case 0x4B://按鍵4B
beatsta = 1;break;//順(右)
case 0x0A://按鍵0A
beatsta = 0;break;//停止
//下面是電機轉速控制
case 0x17://減速
rev++;break;
case 0x09://加速
rev--;break;
case 0x15://默認速度
rev = 7;break;
default:break;
}
}
//配套遙控器
if(ircode[0] == 0x00)//用戶碼00
{
switch(ircode[2])
{
//下面是電機旋轉控制
case 0x52://按鍵52
beatsta = 2;break;//逆(左)
case 0x4A://按鍵4A
beatsta = 1;break;//順(右)
case 0x42://按鍵42
beatsta = 0;break;//停止
//下面是電機轉速控制
case 0x40://減速
rev++;break;
case 0x43://加速
rev--;break;
case 0x44://默認速度
rev = 7;break;
default:break;
}
}
if(rev<2)rev = 2; //值控制
if(rev>60)rev = 60;
ConfigTimer0(rev); //重新配速
}
}
}
/* -----------------------配置並啟動T0,ms-T0定時時間------------------- */
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp; //臨時變量
tmp = 11059200 / 12; //定時器計數頻率
tmp = (tmp * ms) / 1000; //計算所需的計數值
tmp = 65536 - tmp; //計算定時器重載值
tmp = tmp + 18; //補償中斷響應延時造成的誤差
T0RH = (unsigned char)(tmp>>8); //定時器重載值拆分為高低字節
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0為模式1
TH0 = T0RH; //加載T0重載值
TL0 = T0RL;
ET0 = 1; //使能T0中斷
TR0 = 1; //啟動T0
}
/*--------------------------電機動作-----------------------*/
void beat()
{
unsigned char tmp;
static unsigned char i = 1; //當前節拍
tmp = P1;
tmp = tmp & 0xF0; //清空P1低四位
tmp = tmp | BeatCode[(i-1)]; //寫入節拍
P1 = tmp;
switch(beatsta) //電機狀態
{
case 0:break; //停止
case 1:i--;break; //順時針
case 2:i++;break; //逆時針
default:break;
}
if(i>8)i = 1; //循環
if(i<1)i = 8;
}
/*--------------------------NEC紅外遙控模塊--------------------------------*/
/*
NEC數據格式:引導碼、用戶碼、用戶碼(或者用戶碼反碼)、按鍵鍵碼和鍵碼反碼,最后一個停止位。
引導碼:9ms的載波+4.5ms的空閑。
比特值“0”:560us的載波+560us的空閑。
比特值“1”:560us的載波+1.68ms的空閑。
*/
sbit IR_INPUT = P3^3; //紅外接收引腳
void InitInfrared() //初始化紅外,定時器1,中斷
{
IR_INPUT = 1; //輸入置高電平
TMOD &= 0X0F; //定時器模式,1,1
TMOD |= 0x10;
TR1 = 0; //定時器1運行
ET1 = 0; //定時器1中斷打開
IT1 = 1; //外部中斷1下降沿觸發
EX1 = 1; //打開外部中斷
}
unsigned int GetLevTime(bit nu)
{
TH1 = 0; //初始化定時器值
TL1 = 0;
TR1 = 1; //開始計時
while(IR_INPUT == nu) //等電平變化
{
if(TH1 > 0x30) //13.33ms
{
break; //信號超時,退出
}
}
TR1 = 0; //關閉定時器
return(TH1 * 256 + TL1); //返回定時器的值
}
void EXINT1_ISR() interrupt 2 //外部中斷1
{
unsigned char i, j; //循環輔助(接收4*8個位)
unsigned int time; //高低電平時間暫存值
unsigned char byt; //接收到的字節
time = GetLevTime(0); //檢測低電平時間
if((time <7833) || (time > 8755)) //8.5-9.5ms
{
IE1 = 0;
return; //如果沒在限定時間內,退出
}
time = GetLevTime(1); //檢測高電平時間
if((time<3686) || (time > 4608)) //4.0-5.0ms
{
IE1 = 0;
return; //如果沒在限定時間內,退出
}
//以上為引導碼部分
for(i=0; i<4; i++)
{//循環接收4個字節(用戶碼,用戶反碼,按鍵碼,按鍵反碼)
for(j=0; j<8; j++)
{//循環接收8位(從高位到低位)
time = GetLevTime(0); //得到低電平時間
if((time<313) ||(time >718)) //340-780us
{
IE1 = 0;
return;
}
time = GetLevTime(1); //得到高電平時間
if((time>313) && (time <718)) //340-780us
{
byt >>= 1; //接收到的是0(先高位,再低位)
}
else if((time>1345) && (time<1751))
{
byt >>= 1;
byt |= 0x80; //接收到的是1(先高位,再低位)
}
else //不符合1,也不符合0
{
IE1 = 0; //清空外部1中斷標志
return; //退出
}
}
ircode[i] = byt; //把收到的字節存到數組內
}
irflag = 1; //接收成功標標志
IE1 = 0; //清空外部1中斷標志
}
/* ---------------------------------定時器中斷----------------------------- */
void InterruptTimer0() interrupt 1
{
TH0 = T0RH; //重新加載重載值
TL0 = T0RL;
beat(); //電機刷新
}
注釋都比較全,就不過多解釋啦~
到這里,就能完成視頻中的功能了,今年4月沒發完整,抱歉哈~