当前位置:   article > 正文

凌霄飞控添加灵活格式帧数据传输到上位机_凌霄飞控与openmv通信

凌霄飞控与openmv通信

      一、介绍  

        官方的凌霄飞控程序里面将匿名通信协议封装成通用型了,初学者想要根据飞控的通信协议框架编写自己的数据传输比较困难,即通过使用匿名上位机的灵活格式帧,功能码(0XFx)进行数据传输。

 接下来,我将给大家介绍如何添加代码,完成自己的数据上传到上位机的功能。

二、具体操作

        首先,我们需要对凌霄飞控程序有个基本了解,这里我不多介绍,只介绍关于通信协议的部分。

        我们查找源码,可以发现飞控数据与上位机通信是在1ms定时器里面通过使用ANO_LX_Data_Exchange_Task()函数进行的

         我们进一步查找可以发现它里面都是调用Check_To_Send()函数,有很多这个函数,函数的参数是匿名通信的功能码,所以这里使用不同的功能码就可以传输功能码对应的“数据类型”,光流数据、GPS数据、CMD指令等等。

我们找到了第一个需要增加语句的部分,我们要在这个函数里面添加一个功能码为0xF1的语句

Check_To_Send(0xF1);    //灵活格式帧(Open_MV数据)

 

        进一步寻找Check_To_Send()函数的作用,里面有很多结构体参数判断,这里我们先不管这部分,后面会说明这部分的功能,直接找到最重要的函数Frame_Send()

         注意Frame_Send()函数的参数frame_num,这个是我们在Check_To_Send()函数填写的功能码,注意这一点,非常重要,后面都是根据功能码来填写参数的

       

        我们进一步寻找Frame_Send()函数的具体功能,查看之后我们应该知道这个函数是匿名通信协议框架,具体情况观看B站UP主“匿名_茶不思”大佬的匿名通信协议介绍

匿名上位机V7版--0基础教程3--从0开始写匿名协议发送源码_哔哩哔哩_bilibili

  1. static void Frame_Send(u8 frame_num, _dt_frame_st *dt_frame)
  2. {
  3. u8 _cnt = 0; //地址偏移: 每写完一次自动++ 实现地址偏移
  4. send_buffer[_cnt++] = 0xAA; //帧头 [HEAD] (固定值)
  5. send_buffer[_cnt++] = dt_frame->D_Addr; //目标地址 [D_ADDR](将发送框架里面的地址存入缓存数组)
  6. send_buffer[_cnt++] = frame_num; //功能码 [ID] (0X010X30等等)
  7. send_buffer[_cnt++] = 0; //数据长度 [LEN]
  8. //==填充需要发送的数据
  9. Add_Send_Data(frame_num,&_cnt, send_buffer);//发送数据 [DATA] (根据功能码对应填充格式)
  10. //数据长度更新
  11. send_buffer[3] = _cnt - 4;
  12. //==进行数据校验 和校验 + 附加校验
  13. u8 check_sum1 = 0, check_sum2 = 0;
  14. for (u8 i = 0; i < _cnt; i++)
  15. {
  16. check_sum1 += send_buffer[i]; //和校验
  17. check_sum2 += check_sum1; //附加校验
  18. }
  19. send_buffer[_cnt++] = check_sum1; //将校验结果存入数组
  20. send_buffer[_cnt++] = check_sum2;
  21. //==校验结果数据传递
  22. if (dt.wait_ck != 0 && frame_num == 0xe0)
  23. {
  24. dt.ck_back.ID = frame_num; //ID
  25. dt.ck_back.SC = check_sum1; //和检验
  26. dt.ck_back.AC = check_sum2; //附加校验
  27. }
  28. //==发送数组函数(需要发送的数组,数组长度)
  29. ANO_DT_LX_Send_Data(send_buffer, _cnt);
  30. }

在这个函数里面我们最需要注意的就中间的Add_Send_Data()函数,这个函数是填写具体数据的,我们需要根据自己的需要填写数据

        我们进入Add_Send_Data函数

  1. static void Add_Send_Data(u8 frame_num, u8 *_cnt, u8 send_buffer[])
  2. {
  3. s16 temp_data;
  4. s32 temp_data_32;
  5. //根据需要发送的帧ID,也就是frame_num,来填充数据,填充到send_buffer数组内
  6. switch (frame_num)
  7. {
  8. case 0x00: //CHECK返回 数据校验帧
  9. {
  10. send_buffer[(*_cnt)++] = dt.ck_send.ID;
  11. send_buffer[(*_cnt)++] = dt.ck_send.SC;
  12. send_buffer[(*_cnt)++] = dt.ck_send.AC;
  13. }
  14. break;
  15. case 0x0d: //电池数据
  16. {
  17. for (u8 i = 0; i < 4; i++)
  18. {
  19. send_buffer[(*_cnt)++] = fc_bat.byte_data[i];
  20. }
  21. }
  22. break;
  23. case 0x30: //GPS数据
  24. {
  25. //
  26. for (u8 i = 0; i < 23; i++)
  27. {
  28. send_buffer[(*_cnt)++] = ext_sens.fc_gps.byte[i];
  29. }
  30. }
  31. break;
  32. case 0x33: //通用速度测量数据
  33. {
  34. //
  35. for (u8 i = 0; i < 6; i++)
  36. {
  37. send_buffer[(*_cnt)++] = ext_sens.gen_vel.byte[i];
  38. }
  39. }
  40. break;
  41. case 0x34: //通用距离测量数据
  42. {
  43. //
  44. for (u8 i = 0; i < 7; i++)
  45. {
  46. send_buffer[(*_cnt)++] = ext_sens.gen_dis.byte[i];
  47. }
  48. }
  49. break;
  50. case 0x40: //遥控数据帧
  51. {
  52. for (u8 i = 0; i < 20; i++)
  53. {
  54. send_buffer[(*_cnt)++] = rc_in.rc_ch.byte_data[i];
  55. }
  56. }
  57. break;
  58. case 0x41: //实时控制数据帧
  59. {
  60. for (u8 i = 0; i < 14; i++)
  61. {
  62. send_buffer[(*_cnt)++] = rt_tar.byte_data[i];
  63. }
  64. }
  65. break;
  66. case 0xe0: //CMD命令帧
  67. {
  68. send_buffer[(*_cnt)++] = dt.cmd_send.CID;
  69. for (u8 i = 0; i < 10; i++)
  70. {
  71. send_buffer[(*_cnt)++] = dt.cmd_send.CMD[i];
  72. }
  73. }
  74. break;
  75. case 0xe2: //PARA返回
  76. {
  77. temp_data = dt.par_data.par_id;
  78. send_buffer[(*_cnt)++] = BYTE0(temp_data);
  79. send_buffer[(*_cnt)++] = BYTE1(temp_data);
  80. temp_data_32 = dt.par_data.par_val;
  81. send_buffer[(*_cnt)++] = BYTE0(temp_data_32);
  82. send_buffer[(*_cnt)++] = BYTE1(temp_data_32);
  83. send_buffer[(*_cnt)++] = BYTE2(temp_data_32);
  84. send_buffer[(*_cnt)++] = BYTE3(temp_data_32);
  85. }
  86. break;
  87. case 0xF1: //灵活格式数据
  88. {
  89. //==将int型数据拆成高8位和低8位进行发送
  90. send_buffer[(*_cnt)++] = BYTE0(openmv.red_receive_distance );
  91. send_buffer[(*_cnt)++] = BYTE1(openmv.red_receive_distance);
  92. }
  93. break;
  94. default: break;
  95. }
  96. }

我们发现里面使用Switch-case语句进行选择,选择条件就是frame_num——功能码。我们再看case里面的语句,发现有的使用for语句,有的直接使用 BYTE0(temp_data_32),看到BYTE1()是不是比较熟悉了?没错,这个就是例程里面的数据拆分函数,将数据拆分完后存到数组里面

        虽然前面有使用for循环进行数据填充的,但是我们的灵活格式帧因为数据长度不确定,所以直接用最简单的方式即可——像例程一般,直接根据自己数据的类型进行数据拆分

  1. case 0xF1: //灵活格式数据
  2. {
  3. //==将int型数据拆成高8位和低8位进行发送
  4. send_buffer[(*_cnt)++] = BYTE0(openmv.red_receive_distance );
  5. send_buffer[(*_cnt)++] = BYTE1(openmv.red_receive_distance);
  6. }
  7. break;

我这里是传输一个s16型的数据,所以需要拆分两次。这部分代码根据自己的实际需要进行修改,我相信看过视屏的同学应该能自己编写这一部分代码了。

        这部分语句就是需要我们自己添加的第二部分代码。

        注意因为这个函数里面没有数据这个参数选项,我们需要使用结构体进行数据传递:openmv.red_receive_distance,使用结构体进行参数传递是最方便的。还需要注意的一点是,我们通信函数是放在1ms定时器里面的,结构体数据的更新也尽量要同步,所以我们可以将结构体参数更新放到1ms调度器里面,这样数据就不会不同步了

        接下来回到Frame_Send()函数,在例程中我们需要自己计算数据的长度,并事先赋值好,这样比较容易出错,所以在凌霄飞控的通信协议框架中,我们是再填写完数据之后再确定数据长度的,在缓存数组的第四位send_buffer[3]存放的是数据长度,我们之前是直接给0,填写完数据之后我们就可以更新数据长度了

  1. //数据长度更新
  2. send_buffer[3] = _cnt - 4;

下面的和校验和附加校验,然后是将校验结果存放到相关结构体里面,这部分应该没问题。

        根据例程,我们应该使用数组发送函数进行数据发送,通信就完成了

  1. //==发送数组函数(需要发送的数组,数组长度)
  2. ANO_DT_LX_Send_Data(send_buffer, _cnt);

这里使用的是一个嵌套重映射:ANO_DT_LX_Send_Data()函数 -> UartSendLXIMU()函数 -> DrvUart5SendBuf()函数,这3个函数是等效的,为什么要这么麻烦的重重嵌套,我也不知道,也许是方便修改成自己的函数。

        到这里其实一个通信函数已经写好了,在例程中,我们是直接将框架函数放到调度器里面的,但是因为凌霄程序里面使用了层层调用,我们不是直接放在调度器里面的,而是放在1ms定时器里面,所以我们需要添加一部分代码。

        我们回到Check_To_Send()函数,这里面一堆结构体参数

  1. static void Check_To_Send(u8 frame_num)
  2. {
  3. //==定时触发发送
  4. if (dt.fun[frame_num].fre_ms)
  5. {
  6. //==初始相位小于定时发送周期,等待时间
  7. if (dt.fun[frame_num].time_cnt_ms < dt.fun[frame_num].fre_ms)
  8. {
  9. dt.fun[frame_num].time_cnt_ms++;
  10. }
  11. else
  12. {
  13. dt.fun[frame_num].time_cnt_ms = 1;
  14. dt.fun[frame_num].WTS = 1; //标记等待发送
  15. }
  16. }
  17. else
  18. {
  19. //等待外部触发
  20. }
  21. //==等待发送
  22. if (dt.fun[frame_num].WTS)
  23. {
  24. dt.fun[frame_num].WTS = 0;
  25. //==进行数据填充、实际发送
  26. Frame_Send(frame_num, &dt.fun[frame_num]);
  27. }
  28. }

我们对结构体参数进行分析,发现它判断的值其实是在ANO_DT_Init()函数里面设置的,

  1. void ANO_DT_Init(void)
  2. {
  3. //========定时触发
  4. //电压电流数据
  5. dt.fun[0x0d].D_Addr = 0xff;
  6. dt.fun[0x0d].fre_ms = 100; //触发发送的周期100ms
  7. dt.fun[0x0d].time_cnt_ms = 1; //设置初始相位,单位1ms
  8. //遥控器数据
  9. dt.fun[0x40].D_Addr = 0xff;
  10. dt.fun[0x40].fre_ms = 20; //触发发送的周期100ms
  11. dt.fun[0x40].time_cnt_ms = 0; //设置初始相位,单位1ms
  12. //灵活格式帧
  13. dt.fun[0xF1].D_Addr = 0xff;
  14. dt.fun[0xF1].fre_ms = 500; //触发发送的周期500*1ms
  15. dt.fun[0xF1].time_cnt_ms = 0; //设置初始相位,单位1ms
  16. //========外部触发
  17. //GPS 传感器信息
  18. dt.fun[0x30].D_Addr = 0xff;
  19. dt.fun[0x30].fre_ms = 0; //0 由外部触发
  20. dt.fun[0x30].time_cnt_ms = 0; //设置初始相位,单位1ms
  21. //通用速度型传感器数据
  22. dt.fun[0x33].D_Addr = 0xff;
  23. dt.fun[0x33].fre_ms = 0; //0 由外部触发
  24. dt.fun[0x33].time_cnt_ms = 0; //设置初始相位,单位1ms
  25. //通用测距传感器数据
  26. dt.fun[0x34].D_Addr = 0xff;
  27. dt.fun[0x34].fre_ms = 0; //0 由外部触发
  28. dt.fun[0x34].time_cnt_ms = 0; //设置初始相位,单位1ms
  29. //实时控制帧
  30. dt.fun[0x41].D_Addr = 0xff;
  31. dt.fun[0x41].fre_ms = 0; //0 由外部触发
  32. dt.fun[0x41].time_cnt_ms = 0; //设置初始相位,单位1ms
  33. //CMD 命令帧
  34. dt.fun[0xe0].D_Addr = 0xff;
  35. dt.fun[0xe0].fre_ms = 0; //0 由外部触发
  36. dt.fun[0xe0].time_cnt_ms = 0; //设置初始相位,单位1ms
  37. //参数写入、参数读取返回
  38. dt.fun[0xe2].D_Addr = 0xff;
  39. dt.fun[0xe2].fre_ms = 0; //0 由外部触发
  40. dt.fun[0xe2].time_cnt_ms = 0; //设置初始相位,单位1ms
  41. }

这里设置了定时触发和外部触发,像电池电压、遥控数据,它们是变化的,我们是需要它经常返回上位机的。像光流、GPS、CMD命令等这些数据是外部传感器发送给飞控的,不受飞控控制,飞控只是数据传输到上位机的中介,所以设置为外部触发。

        我们自己发送的灵活格式帧,是需要它定时返回上位机的(跟放在调度器里面一样),所以我们需要配置为定时触发,根据需要设置dt.fun[0xF1].fre_ms的值。

  1. //灵活格式帧
  2. dt.fun[0xF1].D_Addr = 0xff;
  3. dt.fun[0xF1].fre_ms = 500; //触发发送的周期500*1ms
  4. dt.fun[0xF1].time_cnt_ms = 0; //设置初始相位,单位1ms

        这里是需要添加的第三部分代码

        在Check_To_Send()函数里面使用if语句判断dt.fun[0xF1].fre_ms,如果大于0,就进入定时触发,标记dt.fun[frame_num].WTS标志位,否则等待外部触发。

  1. if (dt.fun[frame_num].fre_ms)
  2. {
  3. //==初始相位小于定时发送周期,等待时间
  4. if (dt.fun[frame_num].time_cnt_ms < dt.fun[frame_num].fre_ms)
  5. {
  6. dt.fun[frame_num].time_cnt_ms++;
  7. }
  8. else
  9. {
  10. dt.fun[frame_num].time_cnt_ms = 1;
  11. dt.fun[frame_num].WTS = 1; //标记等待发送
  12. }
  13. }

定时时间到了,dt.fun[frame_num].WTS标志位为1,下面即进入发送框架函数

  1. if (dt.fun[frame_num].WTS)
  2. {
  3. dt.fun[frame_num].WTS = 0;
  4. //==进行数据填充、实际发送
  5. Frame_Send(frame_num, &dt.fun[frame_num]);
  6. }

这部分功能实际跟直接将发送框架函数放到调度器里面是相同的,但是由于这个是一个通用型的发送框架,我们需要做适当的调整。

        凌霄飞控的通用通信协议函数介绍到这里。我们只需要添加上面3部分代码即可实现发送自己的灵活格式帧数据。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/474164
推荐阅读
  

闽ICP备14008679号