完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
本次为各位小伙伴带来的是一种非常普遍且便宜易实现的短距离无线通讯-红外通讯,电视机、空调虽无线控制方式也是五花八门了,但红外控制仍然占据着一席之地,本文从原理上介绍到最终实现控制舵机,当然不只是舵机,路铺好了,走什么车还不是自己说了算嘛,哈哈~闲话少说,开干!
完成目标
1.1 红外信号调制解调原理 平时所使用的红外遥控器传输的信号是经过调制过的信号,调制、解调是无线通信的经常用的通信手段,通信原理、高频电子课程上大家想必都已经很熟悉了。红外遥控器所使用的是38KHZ的载波频率,下面结合红外通讯做点简单介绍。 调制:就是用待传送信号去控制某个高频信号的幅度、相位、频率等参量变化的过程,即用一个信号去装载另一个信号。比如我们的红外遥控信号要发送的时候,先经过38K调制,如图所示。 原始信号就是我们要发送的一个数据“0”位或者一位数据“1”位,而所谓38K载波就是频率为38K的方波信号,调制后信号就是最终我们发射出去的波形。我们使用原始信号来控制38K载波,当信号是数据“0”的时候,38K载波毫无保留的全部发送出去,当信号是数据“1”的时候,不发送任何载波信号。 正常来讲,如果对经过调制的信号进行解调处理,是需要很多工作的,比如,信号检测、前级放大、后级放大、滤波、解调电路等,最后解调出原始信号,但红外通信的一体化接收头HS0038B,已经把这些电路全部集成到一起了,我们只需要把这个电路接上去,就可以直接输出我们所要的基带信号了,如图2所示,下图是我实际设计验证过的电路,也是很多开源设计上的原理,比较简单,只需要很少的外围器件。 上图所示电路,当HS0038监测到有38K的红外信号时,就会在OUT引脚输出低电平,当没有38K的时候,OUT引脚就会输出高电平。原始信号对我们来说就是纯粹的高低电平了,采集的方法也有很多,此次我们是用单片机资源,输入捕获功能来采集数据。 1.2 红外通信协议介绍 红外通信协议也是多种多样,只介绍一种常用的编码协议,NEC协议,载波也就是上面说到的38KHZ载波,NEC协议是基于PWM(脉冲宽度调制)来传输数据的。其特征如下:
一个逻辑 1 传输需要 2.25ms(560us脉冲+1680us 低电平), 一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。 而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。 NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。 下面结合我们逻辑分析仪截取的数据波形,对着协议对上面提到的协议格式进行分析,下面是按键2的波形,对应的数据为98即01100010: 从波形图中我们可以看出,显示同步码,9ms的低电平+4.5ms的高电平,紧接着是8位地址码,地址码为0,后面是地址反码,FF,在后面是数据码,刚好是98,也是我们2键按下后发送的数据,最后是数据反码。 可以看到在数据之后,还收到了几个脉冲,这是 NEC 码规定的连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平+97.94ms 高电平组成),如果在一帧数据发送完毕之后,按键仍然没有放开,则发射重复码,即连发码,可以通过统计连发码的次数来标记按键按下的长短/次数。 至此,关于红外的一些基础知识就算啰嗦完了,没懂的可以加小飞哥好友,进群,和小伙们一起讨论哈。 2 舵机控制原理 关于舵机的部分可以参考:舵机控制篇 3 定时器输入捕获原理 输入捕获模式可以用来测量脉冲宽度或者测量频率。我们以测量脉宽为例,用一个简图来说明输入捕获的原理,如下图: 假定定时器工作在向上计数模式,图中 t1~t2 时间,就是我们需要测量的高电平时间。测量方法如下:首先设置定时器通道 x 为上升沿捕获,这样, t1 时刻,就会捕获到当前的 CNT 值,然后立即清零 CNT,并设置通道 x为下降沿捕获,这样到 t2 时刻,又会发生捕获事件,得到此时的 CNT 值,记为 CCRx2。这样,根据定时器的计数频率,我们就可以算出 t1~t2 的时间,从而得到高电平脉宽。 在 t1~t2 之间,可能产生 N 次定时器溢出,这就要求我们对定时器溢出,做处理,防止高电平太长,导致数据不准确。如图所示, t1~t2之间, CNT计数的次数等于:N*ARR+CCRx2,有了这个计数次数,再乘以 CNT 的计数周期,即可得到 t2-t1 的时间长度,即高电平持续时间。这就是输入捕获的原理,简单来说就是测量高低电平的时间,进而求得脉冲频率或者数据编码格式。 3.1 硬件连接
4.1 cubemx配置 输入捕获配置 使用PB9引脚,也即是TIM4的4通道,分频系数配置为83,计数单位1us,超时时间设置为10ms,输入捕获触发方式为上升沿触发,下面有个滤波器需要特别注意下,手册中对滤波器的介绍是:
4.2 代码实现 本次使用的主要是定时器相关的功能,cubemx配置比较简单,配置完成后,根据上面的原理介绍,我们来编写应用层函数。 红外NEC协议解码实现 根据上面NEC编码的介绍,我们只需要通过输入捕获功能检测高低电平时间,进而计算出逻辑0和逻辑1,得到传输的数据。 首先定义个红外接收相关的结构体: typedef struct Remote{ uint8_t RmtSta; //捕获状态 //[7]:0,没有成功的捕获;1,成功捕获到一次. //[6]:0,还没捕获到低电平;1,已经捕获到低电平了. //[5:0]:捕获低电平后溢出的次数(对于 32 位定时器来说,1us 计数器加 1,溢出时间:4294 秒) uint16_t Dval; //下降沿时计数器的值 uint32_t RmtRec; //红外接收到的数据 uint8_t RmtCnt; //按键按下的次数 }Remotepara; 输入捕获定时器初始化代码,这部分也是cubemx自动生成的,已经进群的小伙伴会在源码地址下载源码,贴出来主要是给不方便下载的小伙伴们看,下载源码的直接跳过哈。 /* TIM4 init function */ void MX_TIM4_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_IC_InitTypeDef sConfigIC = {0}; htim4.Instance = TIM4; htim4.Init.Prescaler = 83; htim4.Init.CounterMode = TIM_COUNTERMODE_UP; htim4.Init.Period = 10000; htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim4) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } if (HAL_TIM_IC_Init(&htim4) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0; if (HAL_TIM_IC_ConfigChannel(&htim4, &sConfigIC, TIM_CHANNEL_4) != HAL_OK) { Error_Handler(); } } void RemoteInit() { HAL_TIM_Base_Start_IT(&htim4); //开启定时器4定时器中断 HAL_TIM_IC_Start_IT(&htim4,TIM_CHANNEL_4); //开始捕获TIM4的通道4 Remotepara RemoteParameters = {0}; } 接下来的输入捕获部分是我们NEC解码的关键部分,先看代码,也是参考网上的开源代码,根据上面的NEC编码,一步步实现。 /定时器输入捕获中断回调函数 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) //捕获中断发生时执行 { if(htim->Instance==TIM4) { if(RDATA) //上升沿捕获 { TIM_RESET_CAPTUREPOLARITY(&htim4,TIM_CHANNEL_4); //一定要先清除原来的设置!! TIM_SET_CAPTUREPOLARITY(&htim4,TIM_CHANNEL_4,TIM_ICPOLARITY_FALLING); //CC4P=1 设置为下降沿捕获 __HAL_TIM_SET_COUNTER(&htim4,0); //清空定时器值 RemoteParameters.RmtSta|=0X10; //标记上升沿已经被捕获 }else //下降沿捕获 { RemoteParameters.Dval=HAL_TIM_ReadCapturedValue(&htim4,TIM_CHANNEL_4); //读取CCR4也可以清CC4IF标志位 TIM_RESET_CAPTUREPOLARITY(&htim4,TIM_CHANNEL_4); //一定要先清除原来的设置!! TIM_SET_CAPTUREPOLARITY(&htim4,TIM_CHANNEL_4,TIM_ICPOLARITY_RISING); //配置TIM4通道4上升沿捕获 if(RemoteParameters.RmtSta&0X10) //完成一次高电平捕获 { if(RemoteParameters.RmtSta&0X80) //接收到了引导码 { if(RemoteParameters.Dval>300&&RemoteParameters.Dval<800) //560为标准值,560us { RemoteParameters.RmtRec<<=1; //左移一位. RemoteParameters.RmtRec|=0; //接收到0 } else if(RemoteParameters.Dval>1400&&RemoteParameters.Dval<1800) //1680为标准值,1680us { RemoteParameters.RmtRec<<=1; //左移一位. RemoteParameters.RmtRec|=1; //接收到1 } else if(RemoteParameters.Dval>2200&&RemoteParameters.Dval<2600) //得到按键键值增加的信息 2500为标准值2.5ms { RemoteParameters.RmtCnt++; //按键次数增加1次 RemoteParameters.RmtSta&=0XF0; //清空计时器 } } else if(RemoteParameters.Dval>4200&&RemoteParameters.Dval<4700) //4500为标准值4.5ms { RemoteParameters.RmtSta|=1<<7; //标记成功接收到了引导码 RemoteParameters.RmtCnt=0; //清除按键次数计数器 } } RemoteParameters.RmtSta&=~(1<<4); } } } 定时器4中断回调函数,主要用来计算定时器溢出次数。 //定时器中断回调函数中调用此函数,计算溢出次数 void RemoteDataTimerCallBack() { if(RemoteParameters.RmtSta&0x80) //上次有数据被接收到了 { RemoteParameters.RmtSta&=~0X10; //取消上升沿已经被捕获标记 if((RemoteParameters.RmtSta&0X0F)==0X00)RemoteParameters.RmtSta|=1<<6; //标记已经完成一次按键的键值信息采集 if((RemoteParameters.RmtSta&0X0F)<14)RemoteParameters.RmtSta++; else { RemoteParameters.RmtSta&=~(1<<7); //清空引导标识 RemoteParameters.RmtSta&=0XF0; //清空计数器 } } } 遥控器实际键值数据校验 上面两个函数就实现了对NEC编码的解码工作,接下来对数据进行处理,对地址码、数据原码、数据反码进行判断,是否有误码。 //处理红外键盘 //返回值: //0,没有任何按键按下 //其他,按下的按键键值. uint8_t Remote_Scan(void) { uint8_t sta=0; uint8_t t1,t2; if(RemoteParameters.RmtSta&(1<<6)) //得到一个按键的所有信息了 { t1=RemoteParameters.RmtRec>>24; //得到地址码 t2=(RemoteParameters.RmtRec>>16)&0xff; //得到地址反码 if((t1==(uint8_t)~t2)&&t1==REMOTE_ID) //检验遥控识别码(ID)及地址 { t1=RemoteParameters.RmtRec>>8; t2=RemoteParameters.RmtRec; if(t1==(uint8_t)~t2)sta=t1; //键值正确 RemoteParameters.RmtRec=0; } if((sta==0)||((RemoteParameters.RmtSta&0X80)==0)) //按键数据错误/遥控已经没有按下了 { RemoteParameters.RmtSta&=~(1<<6); //清除接收到有效按键标识 RemoteParameters.RmtCnt=0; //清除按键次数计数器 RemoteParameters.RmtRec=0; } } return sta; } 遥控器标签键值与实际键值对应处理 对于解码来说,得到的不过是遥控器发送来的数据,但是市面上的NEC编码遥控器遵从的只是协议一致,每个遥控器的按键对应的实际键值并不是一致的,比如A厂家的遥控器“按键1”对应的码值为100,B厂家的“按键1”可能是码值100,但也有可能是其他码值,此时就需要我们把遥控器标签上对应的键跟实际接收的键值进行匹配,下面是我接收到的我所购买的遥控器的实际键值,也就是图示遥控器标签对应的键值,实际测试跟正点原子的是不一样的,正点原子的键更多些,键值有他自己的定义。 对程序设计者来说,实际键值有了,至于面向用户是什么意义,操作空间就非常大了,下面就我手里的遥控器,将码值跟标签值一一对应起来,代码如下,一顿操作猛如虎,就完成了我们键值的对应工作: char *KeyValueConvert(uint8_t keyValue) { char *str=0; switch (keyValue) { case 162: str = "1"; break; case 98: str = "2"; break; case 226: str = "3"; break; case 34: str = "4"; break; case 2: str = "5"; break; case 194: str = "6"; break; case 224: str = "7"; break; case 168: str = "8"; break; case 144: str = "9"; break; case 104: str = "*"; break; case 152: str = "0"; break; case 176: str = "#"; break; case 24: str = "UP"; break; case 16: str = "LEFT"; break; case 56: str = "OK"; break; case 90: str = "RIGHT"; break; case 74: str = "DOWN"; break; } //printf("The KeyValue is %s rn",(char *)(str)); return str; } 来看下实际效果,我是采用万能的串口输出,跟我们遥控器的键值就完美的对应起来了,可能有心细的小伙伴已经看到了,下面方向键,串口还打印出了,旋转度数,是的,接下来我们来通过红外遥控器对舵机进行控制。 我们是用上、下、左、右方向键和OK键分别对应舵机的0度、45度、90度、135度、180度5个角度,先来看一下4路PWM输出的代码,两个输入参数,第一个控制那一路输出,第二个参数设置旋转的角度0-180度: /* USER CODE BEGIN 1 */ void PWM_Stop(void) { HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_1); HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_2); HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_3); HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); } void PWM_Output(uint8_t PWM_Channel,int Angle) { switch (PWM_Channel) { case 1: switch(Angle) { case 0: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); break; case 45: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,1000); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); break; case 90: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,1500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); break; case 135: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,2000); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); break; case 180: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,2500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); break; default: HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_1); } break; case 2: switch(Angle) { case 0: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2); break; case 45: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,1000); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2); break; case 90: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,1500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2); break; case 135: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,2000); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2); break; case 180: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,2500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2); break; default: HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_2); } break; case 3: switch(Angle) { case 0: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_3,500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3); break; case 45: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_3,1000); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3); break; case 90: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_3,1500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3); break; case 135: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_3,2000); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3); break; case 180: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_3,2500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3); break; default: HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_3); } break; case 4: switch(Angle) { case 0: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_4,500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4); break; case 45: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_4,1000); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4); break; case 90: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_4,1500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4); break; case 135: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_4,2000); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4); break; case 180: //HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_4,2500); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4); break; default: HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_4); } break; } } 然后我们是用红外遥控器来控制舵机转动不同的角度,实现代码如下: //以下是红外控制舵机测试代码 **********************************************/ HW_KeyValue=Remote_Scan(); if(HW_KeyValue) { keystr = KeyValueConvert(HW_KeyValue); if(!memcmp(keystr,"*",strlen("*"))) { PWM_Stop(); printf("rnrotate stoprn"); } else if(!memcmp(keystr,"UP",strlen("UP"))) { PWM_Output(1,0); PWM_Output(2,0); PWM_Output(3,0); PWM_Output(4,0); printf("rnrotate 45度rn"); } else if(!memcmp(keystr,"RIGHT",strlen("RIGHT"))) { PWM_Output(1,45); PWM_Output(2,45); PWM_Output(3,45); PWM_Output(4,45); printf("rnrotate 90度rn"); } else if(!memcmp(keystr,"DOWN",strlen("DOWN"))) { PWM_Output(1,90); PWM_Output(2,90); PWM_Output(3,90); PWM_Output(4,90); printf("rnrotate -45度rn"); } else if(!memcmp(keystr,"LEFT",strlen("LEFT"))) { PWM_Output(1,135); PWM_Output(2,135); PWM_Output(3,135); PWM_Output(4,135); printf("rnrotate -90度rn"); } else if(!memcmp(keystr,"OK",strlen("OK"))) { PWM_Output(1,180); PWM_Output(2,180); PWM_Output(3,180); PWM_Output(4,180); printf("rnrotate 90度rn"); } printf("rnThe Key Value is %s rn",(char *)(keystr)); } /**********************************************/ 由于只有一个舵机,只能先通过逻辑分析仪来测试输出波形,目前是设计的4路输出相同,小伙伴们可以根据自己的需要去设计具体的转动角度。 |
|
|
|
只有小组成员才能发言,加入小组>>
3278 浏览 9 评论
2956 浏览 16 评论
3458 浏览 1 评论
9002 浏览 16 评论
4051 浏览 18 评论
1115浏览 3评论
573浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
571浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2303浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1859浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-25 10:21 , Processed in 1.238024 second(s), Total 81, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号