【制作】金沙滩51单片机红外遥控步进电机:正反转、加减速
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月没发完整,抱歉哈~
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步