切换到宽版
  • 605阅读
  • 5回复

DIY 航模遥控器 [复制链接]

上一主题 下一主题
离线zxhgr
 

发帖
4
M币
-455
专家
1
粉丝
0
这是自己设计制作的PCB板 航模遥控器
供大家一起学习交流  
DIY 航模遥控器
DIY 航模遥控器
DIY 航模遥控器
DIY 航模遥控器
DIY 航模遥控器
DIY 航模遥控器
遥控器我采用的是STM32作为主控芯片,综合考虑这款芯片的性价比最高。无线发射与接收模块采用经济适用的24L01+模块,这款模块成本低,技术成熟、稳定性好空中传输速率选择在250K,以提高传输距离。采用跳频通讯方式能够很好地避开通讯中的信道冲突。理论上可以65535个遥控器仪器同时使用,相互不会产生干扰。
        采用硬件SPI通讯,使用W25X40 FLASH芯片作为存储单元。使用低成本的12864液晶作为显示单元。使用TL431基准芯片为ADC采样提供基准电压信号,是遥控器的ADC采集稳定可靠。左手油门和右手油门可以软件设定,但是需要硬件支持。
       ShockBurst模式下nRF24L01可以与成本较低的低速MCU相连高速信号处理是由芯片内部的射频协议处理的nRF24L01提供SPI接口数据率取决于单片机本身接口速度ShockBurst模式通过允许与单片机低速通信而无线部分高速通信减小了通信的平均消耗电流在ShockBurstTM接收模式下当接收到有效的地址和数据时IRQ通知MCU随后MCU可将接收到的数据从RXFIFO寄存器中读出在ShockBurstTM发送模式下nRF24L01自动生成前导码及CRC校验。数据发送完毕后IRQ通知MCU减少了MCU的查询时间也就意味着减少了MCU的工作量同时减少了软件的开发时间nRF24L01内部有三个不同的RX FIFO寄存器6个通道共享此寄存器和三个不同的TXFIFO寄存器在掉电模式下待机模式下和数据传输的过程中MCU可以随时访问FIFO寄存器这就允许SPI接口可以以低速进行数据传送并且可以应用于MCU硬件上没有SPI接口的情况下

遥控器支持模拟器,可以直接连接解密狗,输出标准的PPM信号。使用这个遥控器可以再电脑上模拟飞行。
下面接收遥控器和接收机之间的通许协议。通讯协议分为4种类型,分别是遥控器发出的PCM帧和广播帧。接收机发出的对码帧和数据帧。
遥控数据格式由32个字节数据组成。
遥控和接收机对码原理:
遥控器部分工作原理:遥控器上电后,首先检查上次对码的接收机信息,并发送上一次对码成功信息的地址,1秒后没有收到上次对码成功的接收机信息,遥控器会同时发送广播信息以接收其它接收机的配对信息。当遥控器接收到接收机的配对信息并确认后,停止发送广播信息。只发送接收机的信息。1秒以后仍然没有接收到信息,遥控器会扫频发送广播信息。
接收机部分工作原理:接收机上电后,首先检查上一次对码的遥控器信息,并等待接收遥控器信息。短接对码接口后,接收机会将广播地址打开,扫频接收广播信息。直到对码成功。

1.        遥控发出PCM信号格式
信号内容如下所示
第1字节:帧头0x5A
第2字节:帧头0xA5
第3字节:帧识别码  0x00广播信息帧  0x01 PCM信号帧 0x02 信息帧
第4字节:PCM通道数 遥控器发出的PCM通道取值范围4-16
第5-7字节 :CH1和CH2通道数据
第8-10字节 :CH3和CH4通道数据
第11-13字节 :CH5和CH6通道数据
第14-16字节 :CH7和CH8通道数据或者功能码
第17-19字节 :CH9和CH10通道数据或者功能码
第20-22字节 :CH11和CH12通道数据或者功能码 失控保护(CH1-CH2)
第23-25字节 :CH13和CH14通道数据或者功能码 失控保护(CH3-CH4)
第26-28字节 :CH15和CH16通道数据或者功能码 失控保护(CH5-CH6)
第29字节 : 频率表位置0-15
第30-31字节:识别码 遥控器地址最后2个字节以识别是否配对的遥控器发出指令
第32字节 :校验和 去除帧头和校验和之外的所有数据不记溢出累加和
图表如下所示
1-2Byte        3Byte        4Byte        5-13Byte        14-28 Byte        29Byte        30-31Byte        32Byte
帧头        帧识别码        PCM通道数        CH1-CH4通道        CH1-CH4通道或功能码        频率表位置        遥控器识别码        校验和

2.        遥控发出对码信号格式
信号内容如下所示
遥控器发出对码信号是在指定的地址发送,也就是遥控器和接收机有一个固定的广播地址,遥控器在没有对码成功之前,会定期的发送广播信号,时间间隔为1秒。向广播地址发送遥控器的信息。接收机在对码状态会接收该广播地址的信息,收到信息后,接收机发送要求配对请求给遥控器,遥控器允许后配对成功。
   第1字节:帧头0x5A
第2字节:帧头0xA5
第3字节:帧识别码  0x00广播信息帧  0x01 PCM信号帧 0x02 信息帧
       第4字节:遥控通道类别 如0x06说明6通道遥控器  0x08说明8通道遥控器
       第5字节:操作方式 0 美国手  1 日本手  2 中国手  3 自定义
第6字节:跳频频道数量
       第7-11字节:遥控器接收地址
       第12-27字节:跳频频率表 16个字节
       第28字节:遥控器类别
第29字节:频率表位置0-15  
第30-31字节:识别码 遥控器地址最后2个字节以识别是否配对的遥控器发出指令
第32字节 :校验和 去除帧头和校验和之外的所有数据不记溢出累加和


图表如下所示
1-2Byte        3Byte        4Byte        5Byte        6Byte       7-11Byte        12-27Byte        28Byte        29Byte       30-31Byte        32Byte
帧头        帧识别码        通道类别        操作方式        跳频数量        遥控接收地址        跳频频率表        遥控器类别        频率表位置        遥控器识别码        校验和
3.        接收机发出对码信号格式
信号内容如下所示
第1字节:帧头0x5A
第2字节:帧头0xA5
第3字节:帧识别码  0x80对码信息帧  0x82 信息帧
第4字节:跳频频道数量
第5字节:电池类别
第6字节:PPM通道数
第7-11字节:接收机数据接收地址
第12-27字节:跳频频率表 16个字节
第28字节:接收机类别码
第29字节: 频率表位置0-15
第30-31字节: 识别码 接收机地址最后2个字节以识别是否配对的接收机发出指令
第32字节 :校验和 去除帧头和校验和之外的所有数据不记溢出累加和
图表如下所示
1-2Byte        3Byte        4Byte        5Byte        6Byte       7-11Byte        12-27Byte        28Byte        29Byte       30-31Byte        32Byte
帧头        帧识别码        跳频频道数量        电池类别        PPM通道数        接收机接收地址       跳频频率表        接收机类别码        频率表位置        接收机识别码        校验和
4.        接收机发出的数据信号格式
信号内容如下所示
第1字节:帧头0x5A
第2字节:帧头0xA5
第3字节:帧识别码  0x80对码信息帧  0x82 信息帧
第4字节:接收机对码遥控类别次数 不同遥控器次数
第5字节:电池类别
第6字节:PPM通道数
第7字节:接收机类别码
第8-9字节:电池电压
第10-11字节:电池电流
第12-28字节:功能扩展
第29字节 :频率表位置0-15
第30-31字节: 识别码 接收机地址最后2个字节以识别是否配对的接收机发出指令
第32字节 :校验和 去除帧头和校验和之外的所有数据不记溢出累加和
图表如下所示
1-2Byte        3Byte        4Byte        5Byte        6Byte       7Byte        8-9Byte        10-11Byte        12-28Byte        29Byte       30-31Byte        32Byte
帧头        帧识别码        对码次数        电池类别        PPM通道数        接收机类别码       电池电压        电池电流        功能扩展        频率表位置        接收机识别码        校验和

遥控器软件选择FreeRTOS  ,现将部分代码分享出来
int main(void)
{        
         __set_PRIMASK(1);//关全局中断

   W25_dat.Sector=0xffff;//设置W25x40内存数据不可用

   BSP_Config();//硬件及软件配置

         AppResourcesInit();//任务资源初始化        

   #if TASK_DEBUG
         xTaskCreate(Simulation_Task,"Simulation_Task1",512,NULL,1,NULL);        //仿真调试监视任务
         xTaskCreate(UartDebug_Task,"UartDebug_Task",512,NULL,3,NULL);        //串口仿真调试监视任务
         UartRxQueue = xQueueCreate(10, sizeof(P_DAT) );//创建串口接收队列
   #endif
         xTaskCreate(Dog_Task,"Dog_200mS",256,NULL,5,NULL);        //看门狗监视任务        
         xTaskCreate(AppInit_Task,"AppInit",512,NULL,9,&AppInitHandleTaskStart);        //应用初始化任务




         vTaskStartScheduler();//开始调度任务
        while(1)
        {}
}
void AppInit_Task( void *pvParameters )          //应用初始化任务
{

        #if TASK_DEBUG        
  u8 buf[20];
        #endif
  portTickType xLastWakeTime;        
        const TickType_t xTicksToWait = 50 / portTICK_PERIOD_MS; /* 延迟50ms */
        xLastWakeTime=xTaskGetTickCount();//获取当前心跳值        
        #if TASK_DEBUG              
     DPrintfGetTheKey(); //获取打印资源                        
                 DPrintf("\r\n初始化任务已经启动\r\n");                        
     DPrintfGiveUpKey();//释放打印资源                        
        #endif

  for( ;; )
        {                  
                vTaskDelayUntil(&xLastWakeTime,xTicksToWait);

    Spi2GetTheKey();//获取Spi2的钥匙              

                /**********************FLASH芯片初始化************************/
                W25_dat.ChipID=W25_DeviceID();//读取芯片的8位型号ID  唤醒芯片              
                W25_dat.Jedec_Id=W25_JEDEC_ID();//读取芯片的制造商ID和16位型号ID
    if(W25_ReadSector(0)==0)W25_ReadSector(0);//读指定扇区的数据到内存 如果失败在读取一次

                if((W25_dat.page[0][0]!=0x18)||(W25_dat.page[0][1]!=0x18))//判断FLASH是否需要初始化
                 {//以下更新内存
                         W25_dat.page[0][0]=0x18;W25_dat.page[0][1]=0x18;
                         ee_wrfun();//初始化写EE数据
                         for(;W25_WeadeSector()==0;);//将内存的数据写入FLASH                                
                 }
                else//读取已经初始化的数据
                {
                        ee_rdfun();//初始化读EE数据
                }              
                Spi2GiveUpKey();//归还Spi2的钥匙

                /***************打印输出初始化数据*****************/
                #if TASK_DEBUG              
     DPrintfGetTheKey(); //获取打印资源                        
                 DPrintf("\r\nFLASH初始化完成\r\n");                                
                 U8_Uchar(&W25_dat.ChipID,buf,3);buf[3]='\r';buf[4]='\n';buf[5]=0;
                 DPrintf("\r\nFLASH ID编号:");DPrintf((const signed char *)buf);              

                 DPrintf("\r\n初始化任务删除自身任务\r\n");                        
     DPrintfGiveUpKey();//释放打印资源                        
                #endif        
    LcmInit();//液晶初始化?
    lcd_rst_flag=1;//液晶完成初始化
                AppTaskCreate();//创建任务
                vTaskDelete(AppInitHandleTaskStart);//应用初始化任务执行一次后删除任务
        }
}
void Dog_Task( void *pvParameters )          //看门狗监视任务
{
  EventBits_t uxBits;
  portTickType xLastWakeTime;        
        const TickType_t xTicksToWait = 200 / portTICK_PERIOD_MS; /* 延迟200ms */
        xLastWakeTime=xTaskGetTickCount();//获取当前心跳值
        /*
          开始执行启动任务主函数前使能独立看门狗。
          设置LSI是128分频,下面函数参数范围0-0xFFF,分别代表最小值3.2ms和最大值13107.2ms
          下面设置的是1s,如果1s内没有喂狗,系统复位。
        */
        bsp_InitIwdg(0x138);

#if TASK_DEBUG        
    DPrintfGetTheKey(); //获取打印资源                        
                DPrintf("\r\n监视任务已经启动 \r\n");              
    DPrintfGiveUpKey();//释放打印资源        
#endif
  for( ;; )
        {  
                vTaskDelayUntil(&xLastWakeTime,xTicksToWait);
    /* 等待所有任务发来事件标志 */
                uxBits = xEventGroupWaitBits(DogEventGroup, /* 事件标志组句柄 */
                                                                 TASK_BIT_0_1,       /* 等碩ASK_BIT_0_1被设置 */
                                                                pdTRUE,             /*退出前TASK_BIT_0_1被清除,这里是TASK_BIT_0_1都被设置才表示“退出”*/
                                                                 pdTRUE,             /* 设置为pdTRUE表示等待TASK_BIT_0_1都被设置*/
                                                                 xTicksToWait);          /* 等待延迟时间 */

                if((uxBits & TASK_BIT_0_1) == TASK_BIT_0_1)
                {
                        IWDG_Feed();//喂狗                        
                        #if TASK_DEBUG              
      //DPrintfGetTheKey(); //获取打印资源        
                        //DPrintf("喂狗成功\r\n");        
      //DPrintfGiveUpKey();//释放打印资源                        
                        #endif                        
                }
          else
                {
                        #if TASK_DEBUG        
      //DPrintfGetTheKey(); //获取打印资源                        
                        //DPrintf("等待喂狗指令\r\n");        
     // DPrintfGiveUpKey();//释放打印资源                                      
                        #endif              
                }
        }
}
void UartDebug_Task( void *pvParameters )//串口仿真调试任务
{
        u8 buf[512];
  P_DAT p_dat;        
        u16 len;
        for(;;)
        {
                xQueueReceive(UartRxQueue,&p_dat,portMAX_DELAY);

                if(p_dat.id==1)                        
                {
                        len=UsartTaskRead(&p_dat,buf,USART1_MAX_LEN);//解析读取数据
                        Uart1GetTheKey();//获取串口1的钥匙
                        Usart1Tx(buf,len);         //发送数据      
                        Uart1GiveUpKey();//归还串口1的钥匙                        
                }              
        }

}
void Simulation_Task( void *pvParameters )//仿真调试监视任务
{
        portTickType xLastWakeTime;        
  uint8_t pcWriteBuffer[512];
        const TickType_t xTicksToWait = 5000 / portTICK_PERIOD_MS; /* 延迟5000ms */
        xLastWakeTime=xTaskGetTickCount();//获取当前心跳值

        DPrintfGetTheKey(); //获取打印资源        
        DPrintf("\r\n仿真任务已经启动 \r\n");                
  DPrintfGiveUpKey();//释放打印资源

        for(;;)
        {
                vTaskDelayUntil(&xLastWakeTime,xTicksToWait);//延迟10s

                DPrintfGetTheKey(); //获取打印资源
                DPrintf("=================================================\r\n");
                DPrintf("   任务名称  任务状态 优先级  剩余栈  任务序号 \r\n\r\n");
                vTaskList( (char *)&pcWriteBuffer );                                                
                DPrintf((const signed char *)pcWriteBuffer);

                DPrintf("=================================================\r\n");
                DPrintf("  任务名称     运行计数      使用效率 \r\n\r\n");
                vTaskGetRunTimeStats( (char *)&pcWriteBuffer );                                                
                DPrintf((const signed char *)pcWriteBuffer);        

                DPrintfGiveUpKey();//释放打印资源
        }

}
void AppTaskCreate(void)//创建任务
{  
        RF2401xQueue = xQueueCreate(10, sizeof(RF_DAT) );//创建RF接收队列
        xTaskCreate(Task0,"Task0",512,NULL,3,NULL);
        xTaskCreate(ADC_Task,"ADC_Task",256,NULL,8,NULL);        
        xTaskCreate(URT_DEBUG_Task,"URT_DEBUG_Task",512,NULL,5,NULL);
        xTaskCreate(NRF2401_Task,"NRF2401_Task",512,NULL,9,NULL);
        xTaskCreate(NRF2401TX_Task,"NRF2401TX_Task",512,NULL,9,NULL);//最高优先级
        xTaskCreate(Task_lcd,"Task_lcd",512,NULL,3,NULL);
        xTaskCreate(Tim5mS_Task,"Tim10mS",512,NULL,8,NULL);        //5毫秒定时器任务

}

void Tim5mS_Task( void *pvParameters )          //5毫秒定时器任务
{ u8 i,rf_da;
        RF_DAT  rf_dat;
  portTickType xLastWakeTime;        
        const TickType_t xTicksToWait = 5/ portTICK_PERIOD_MS; /* 延迟5ms */
        xLastWakeTime=xTaskGetTickCount();//获取当前心跳值
  for( ;; )
        {                  
                vTaskDelayUntil(&xLastWakeTime,xTicksToWait);//延迟5ms
                rf_da++;
    if(rf_da>=2){rf_da=0;rf_dat.id=1; rf_dat.p=NULL;xQueueSend(RF2401xQueue,&rf_dat,pdPASS);}

    if(bz_con){bz_con--;GPIO_SetBits(GPIOA,GPIO_Pin_12);}//蜂鸣器控制
    else{GPIO_ResetBits(GPIOA,GPIO_Pin_12);}

    key_bmbuf[key_bmcnt]=GPIO_ReadInputData(GPIOB);
               key_bmcnt++;if(key_bmcnt>=20){key_bmcnt=0;if(key_bmflag==0){key_bmflag=1;for(i=0;i<50;i++){key_bmbuff=key_bmbuf;}}}
                UsartRxQuery();//串口接收查询函数        
                /* 发送事件标志,表示任务正常运行 */
                xEventGroupSetBits(DogEventGroup, TASK_BIT_1);
        }
}

产品设计过程及成本分析
        首先设计的是遥控器和接收机,当时考虑遥控器是不是要采购一个外壳,但是在淘宝搜索了好久也没有找到什么合适的 后来找到广东航模配件店购买了2套外壳 一套黑色12元 一套白色13元。
        接下来选择高频模块,最后选择了24L01模块 接收机部分才有低成本的 200米 模块4.5元   遥控器采用32元的100豪瓦的模块收发距离2500米的哪一种 ,两种模块结合使用经过测试 空旷传输800米没有问题 指示500米以上方向性比较强。
      在接下来就是选择液晶显示 最后考虑成本选择了一款10元左右的12864液晶模块,以上图形大家看到的液晶就是此液晶和背光。当然以上都是不包含运费的价格。
     以后就是设计原理图和PCB  遥控器的PCB做了有两款 一款是有外壳的 一款是没有外壳的  接收机开始设计了一款但是体积比较大 后来又设计了一款 小体积的 以减轻重量  2*3CM的尺寸

通道1-4可以任意设计曲线输出  例如油门曲线 分为9个调整点  连点之间可以选择 自己设计的方程 例如 直线方程 、曲线方程、2次方方程、3次方方程等等。同时9个点的位置可以随意设计 例如 默认10%、 20%、 30%、 40%、 50%、 60%、 70%、80%、 90%自己可以更改为5%、 16%、 30%、 38%、 50%、 65%、 78%、88%、 96% 等等  
4个通道都有9点 可以任意设计 。
        再次说一下混控设置功能,一般的控,混控都设计好了 ,但是这款遥控器的混控,使用者可以任意设计 固定翼 三角翼 V型尾翼可以任意设计 同时可以设计自己的 异形控制滚控,极大的方便设计开发的需求。设计好后保存数据不丢失。
        这一款控的设计理念是“平台在手、任意掌控”它只是提供一个遥控器的平台 ,任何动作及位置都可以随意设计 特别适合老手 以及自己动手设计飞机模型的朋友。

这款遥控器采用STM32 芯片   12864液晶  串口下载程序已经做到板子上  只需要一根串口线就可以下载  采用18650电池用电  电池壳固定尺寸已经画到线路板上   可以连接使用手机充电器充电,充电电路也已经集成到线路板上 ,可以使用充电宝供电。充电口采用和手机一样的接口 方便实用。
        震撼的外观、集成多种功能 、方便程序调试 。是DIY遥控器的不二选择

AD10.8 软件 打开共享出原理图 和PCB图纸
PCB图.rar (321 K) 下载次数:3
原理图1.rar (23 K) 下载次数:1
原理图2.rar (16 K) 下载次数:1









[ 此帖被zxhgr在2017-03-08 14:58重新编辑 ]
本帖最近打赏记录:共1条打赏专家+1
發騷友 专家 +1 以資鼓勵 03-08
离线莫名11

发帖
239
M币
402
专家
1
粉丝
9
只看该作者 1楼 发表于: 03-09
厉害了我的哥
离线mirocus

发帖
20920
M币
71
专家
7
粉丝
104
只看该作者 2楼 发表于: 03-17
顶一个,我做过萝莉控,还有红外四通控
离线上进科技

发帖
196
M币
392
专家
0
粉丝
22
只看该作者 3楼 发表于: 03-20
回 mirocus 的帖子
mirocus:顶一个,我做过萝莉控,还有红外四通控 (2017-03-17 00:48) 回 mirocus 的帖子

不错不错,最近正想研究这个。多谢分享!
离线youxia115

发帖
19
M币
1028
专家
0
粉丝
1
只看该作者 4楼 发表于: 03-20
楼主真牛,支持DIY
离线blxylbx

发帖
456
M币
4288
专家
0
粉丝
24
只看该作者 5楼 发表于: 03-28
做好了么,还有后续么?
快速回复
限80 字节
温馨提示:所有技术区严禁灌水,“沙发”“顶”字样;禁止广告贴;以免被删除
 
上一个 下一个