❤ 2019.11.13


    之前写过各个模块的测试程序,但是并没有将各个模块整合起来,所以总体来说工作量还是很大的。


    


❤ 2019.11.13


〇 总体流程


    这个版本的目标是能够实现电机随上位指令执行相应动作。


    其实说起来挺简单,我接收上位指令,然后控制驱动器,再接收反馈,再发送反馈信息。



 


〇 总体思路


    要实现总体目标,首先要把目标拆分成一个一个的小目标(此小目标非彼小目标。。。)。


    我的计划是:(可能会有改动)


    首先实现接收并解析上位机指令(之前已经实现了部分),然后实现同时控制多个驱动器,然后实现读取驱动器状态,然后实现读取5600模拟量,最后实现向上位发送反馈信息。


    


❤ 2019.11.14


〇 接收上位指令程序(先实现接收并解析舵机角度指令)


    ♣ 第一步,先确定CAN指令的ID。


    虽然有通讯协议,但是鉴于过于难以理解(或者说懒得看。。。),我决定用can分析仪直接测试。。。


    因为上位通讯占用了一个,所以还得用一个Pcan来接收。


    这个是四个舵机的位置信息对应的can指令:




    从这里可以看出的是,舵机的静态控制指令分为两部分,第一条ID为02A82000,包含指令次数信息,第二条ID为02AC2001,包含四个舵机的角度信息,关于角度和其双字节数据的对应关系并不能直观的看出来(其实之前我已经将其“破解”了)。


    在这里其实我不太了解这个指令次数的含义。


    首先把我破解的上位机角度数据的格式记录一下。一条Can指令共8个字节,包含四个舵机的角度指,每个角度值为16位有符号整型数值,传输方式为低位优先,最小分辨率为0.002°。


 


    ♣ 第二步,配置cana和canb


    因为我在我设计的PCB上CanA是用来和上位通讯,CanB用来和驱动器通讯,但是研旭的开发板只引出了CanB,所以我要先把can总线驱动匹配到我的pcb上,因为两个can口负责两种不同的工作,所以我决定把can驱动分成两个文件,分别配置CanA和CanB。


    这里要特别说明一下,更改can总线的输出引脚的时候一定要重新配置下GPIO的设置,虽然是常识,但是我之前调试的时候就在这里卡住了好久,怎么都不能在研旭的开发板上把ti的例程调试成功,切记!




都要改!


    修改过之后经过一些调试,已经可以实现CabB的正常通讯,我用的是ti的官方例程进行的测试,不要问为什么,问就是懒!


    但是在我设计的pcb中,和上位机通讯的是CanA,所以这里先调试CanA口。


    首先把can的驱动程序分成CanA和CanB。


→→→→→


    但是这时候,我突然想到,这样的话我需要改很多东西,于是我又有了偷懒的想法,我把之前的bsp_can改成bsp_canb,然后新建一个bsp_can,把新的bsp_cana和和bsp_canb都include进去。


    不过还是需要改好多东西。。。


    让后就变成了这样。。。



    编译一下,出错了



    还没有改bsp_cana里的内容,下面把里面的canb都换成cana。


    在把文件里的canb修改成cana之后,记得修改库文件里cana的初始化参数,比如波特率什么的。



    这个是把波特率配置成500k,其实要求的是1M,不过先用较低速率调试嘛。


    除了cana的初始化函数之外,还需要修改cana的中断,canb的中断我选择了中断线0.所以cana我选用了中断线1,(关于中断线0和1有什么区别和作用我还不是很了解,能不能同时用0或者1我也没试过,,,(等会儿可以试试。。。))。


    先要修改bsp_cana里面的初始化函数的中断配置部分。



———————————————————————————————————————————————————————————————————————————


    这里有个小地方注意一下,如果把中断改到中断线1,那么设置是0xFFFFFFFF,而不是1.。。。(感觉自己好傻。。)



    另外我看别人说,中断线0比中断线1的优先级要高,所以一般把系统级的中断,比如溢出、错误、丢失等,配置到中断线0,把邮箱中断配置到中断线0.


———————————————————————————————————————————————————————————————————————————


    然后修改外部中断配置文件bsp_pie中的部分。



    另外还有GPIO的配置,我选用的是GPIO18和19作为CanA的端口。



    然后修改下主函数,编译,测试,OK~非常完美!


 


    ♣ 第三步,编写舵机角度指令解析程序。


    首先说一个我发现的虽然没什么用但是让我很困惑的事情,can总线模块的邮箱的MBOX这个结构体有个奇怪的地方,就是就是ReceiveData的字节和字不是对应的,这可能和低位优先和高位优先有关系。



    其实获取角度数据还是很容易的,首先定义一个全局变量,然后把can收到的数据存进去就可以。


    全局变量。


    关于volatile这个关键字今天学到了很多内容,等我稍后整理一下。



    这个是接收中断



 


    上位指令除了舵机角度数据外,其实还有比如说自检指令、区分单次舵机控制和多次舵机控制的指令等等,不过为了能够尽快进行整体调试,我打算先能实现整个流程,然后再来完善细节,所以接下来先调试DSP与驱动器的通讯以及控制多个舵机的功能。


 


〇 与驱动器的通讯及多驱动器控制程序(仅包括角度控制)


    首先介绍一下老师选择的厉害的不要不要的驱动器。



        而且这个驱动器还有自己的软件可以编程序,感觉完全不需要再加个dsp嘛,(而且他本身就是个dsp。。。),这个驱动器用在这里真是大材小用,但是没办法,谁让这是老师的想法呢,我就是个干活的。。。。


 


    ♣ 第一步,确定驱动器的can总线ID及指令格式


    确定can总线的ID及指令格式,需要用他的软件EasyMotionStudio。


    把驱动器连接好后,启动软件。


    设置好电机参数巴拉巴拉。


    我选择“梯形”模式,配置界面如下。


    


    关于电机的控制方式,昨天我和朱工探讨过,这个驱动器是支持绝对位置动作的,也就是用内置的霍尔开关编码器。但是由于内部编码器是安装在电机输出轴上的,最终输出的精度受到减速器精度的影响,所以外部编码器还是有必要的,也就是说,不可以直接把上位指令的角度信息直接发送给驱动器,而需要将上位指令用外部编码器的反馈信息进行修正之后再发送给驱动器,至于其处理方法在后面再说。


    但是,这里主要是测试通讯,ad模块还没有加进来,所以这里先用绝对位置做测试。


    上面的参数设置对应的语句如下



    语句对应的can指令如下



    可以看出,这一段指令用了7个ID,如果再加上用来反馈驱动器状态的语句至少还要加上2个ID,但是F28335每个can线路只有32个mailbox,四个驱动器平均每个只能分到8个ID,也就是说,不够。不过在我试验之下发现有的语句是可以省略的。


    首先加速的和速度的设置,为了发挥出电机的最大性能,这里一般应该设置为最大值(具体是多少我也不知道)



    然后这几句,我也不知道是干嘛的,试了一下没有也可以动。



   UDP是必须的,相当于execute。



    这两句是等待运行完成,应该也可以省略,但是不知道会不会有其他影响。



    


    所以精简一下对应的can指令就是这要的,这是can信号分析仪的指令格式。


  1. 0480209E 00 0F 00 00 ext data
  2. 00002108 ext data


    让后把其他驱动器做相同的配置,并且记录相应ID。


 


    ♣ 第二步,配置dsp的canb初始化程序编写发送函数


    首先配置邮箱ID和发送字符数


    


    


 


    ♣ 第二步,编写发送函数


    发送函数包含接收到的角度数据转换为驱动器能识别的角度数据格式,运行的角度数据写入邮箱发送缓存,执行发送几部分。


    上位发送来的角度数据是16位有符号整型,-20°~+20°(但是老师说的是运动范围只有20°。。。待确认),分辨率0.002°,对应数值范围是-10000~+10000,驱动器识别的角度格式为32位有符号整型,但是因为有减速比的设置,指令的角度数据是实际运行角度乘以48,分辨率0.1°(分辨率有点低。。。)。


    所以换算一下就是:运行角度数据 = 指令角度数据 / 500 _ 48


    下面建立全局变量



    再编写发送函数



 


    运行测试,预料之中的并没有成功,查看can指令发现了问题



    顺序反了。。。也就是说发送邮箱的发送顺序是从大数字到小数字的。


    下面我把顺序调整过来。




    运行测试,bingo!


    


❤ 2019.11.28


〇 读取5600反馈


    本来计划先读取驱动器状态,不过之前已经实现驱动器向dsp发送状态信息,所以这里就先不做这个,先读取5600模拟量。


    在正是编程序之前,先把控制器装到盒子里去,因为早晚要装的啊哈哈哈。。。。


    。。。。


    在经过几天的调试与纠结之后,    终于塞进去了。。。。


    虽然出了一些小问题,但是总算能跑之前的程序了,但是电机不转!


    一阵排查之后发现最可能的问题是,我把驱动器的Reset和Enable引脚和DSP连接了,根据说明书,Reset引脚是高电平有效,Enable也是高电平有效,然后DSP的IO口默认是高电平,所以驱动器一直处于Reset状态。




    所以先要写一段IO口的控制程序。


    很简单嘛~(为后面被打脸埋下了伏笔。。。)


    首先把相应IO口配置为普通IO口,然后把方向配置为输出。



    然后通过宏定义把IO口的输入输出寄存器定义为变量。



    然后在初始化语句中加入IO口的初始值。



    运行,然后问题出现了。。。IO的输出并不是和我所设置的一样的。。。。


    然后去掉宏定义,直接用寄存器设置。。。并不行


    但是我只用一条语句是可以的,多条语句放在一起就不行了。。。


    啊啊啊啊啊啊啊啊啊啊。。。


    后来我放弃用这个寄存器了,虽然在手册里说是可以的



    然后我还是用了GPxSET和GPxCLEAR寄存器。。。



    虽然没解决问题,但是程序暂时可以用了,只是如果需要对IO口进行操作会比较不一样。


 


    主要部件都可以装在盒子里了,下面就开始正式写adc功能的代码。


    先把adc功能添加到程序里,再编写函数,实现角度的读取和转换,最后编写通过5600反馈的闭环控制函数


    ♣ 第一步添加adc功能


    首先把之前调试好的bsp_ADC文件添加到工程里,能在工程里读出各驱动器5600的反馈值。


    话说,从模块化可移植的角度来说,相关模块的变量应该放在相应的bsp文件里定义与声明吧。。。。


    初始化程序


  1. void ADC_Init(void)
  2. {
  3. EALLOW;
  4. SysCtrlRegs.HISPCP.all = ADC_MODCLK; // HSPCLK = SYSCLKOUT/ADC_MODCLK,这个是设置全局的高速外设的工作频率,按说不应该在这里设置的,但是其他地方没办法设置6分频,所以只能在这里设置了
  5. // HSPCLK=25MHz, 将高速外设的工作频率设置为6分频(25MHz),ADC工作的最高工作频率可以配置为25MHz。
  6. EDIS;
  7. InitAdc(); //官方ADC初始化函数
  8. AdcRegs.ADCTRL1.bit.ACQ_PS = ADC_SHCLK; // 设置采集窗口的大小(具体有什么影响还不清楚),Sequential mode: Sample rate = 1/[(2+ACQ_PS)_ADC clock in ns]
  9. // = 1/(3_40ns) =8.3MHz (for 150 MHz SYSCLKOUT)
  10. // = 1/(3_80ns) =4.17MHz (for 100 MHz SYSCLKOUT)
  11. // If Simultaneous mode enabled: Sample rate = 1/[(3+ACQ_PS)_ADC clock in ns]
  12. AdcRegs.ADCTRL3.bit.ADCCLKPS = ADC_CKPS; //ADC工作在25MHz下,不分频(这里没有办法设置成3分频或者6分频,所以在前面设置了总的高速外设频率25MHz)
  13. AdcRegs.ADCTRL1.bit.SEQ_CASC = 1; // 级联模式,SEQ1和SEQ2级联起来,作为一个16状态序列发生器操作,1 Cascaded mode,(我只需要4个采集端口,所以其实不需要级联,不过有什么影响还不清楚)
  14. AdcRegs.ADCTRL1.bit.CONT_RUN = 1; // 连续采样模式,Setup continuous run
  15. AdcRegs.ADCTRL1.bit.SEQ_OVRD = 1; // 使能排序覆盖,Enable Sequencer override feature,(具体有什么区别我还没太清楚)
  16. AdcRegs.ADCMAXCONV.bit.MAX_CONV1 = 0x3; // 设置ad转换通道数量,4个,(n+1),因为是级联模式,所以只用CONV1
  17. // 设置采样通道
  18. AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x2; //驱动器1反馈
  19. AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x4; //驱动器2反馈
  20. AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 0x6; //驱动器3反馈
  21. AdcRegs.ADCCHSELSEQ1.bit.CONV03 = 0x0; //驱动器4反馈
  22. //AdcRegs.ADCTRL2.bit.INT_ENA_SEQ1 = 1; //使能SEQ1中断
  23. //AdcRegs.ADCTRL2.bit.INT_MOD_SEQ1 = 0; //设置SEQ1中断方式,每个SEQ1序列转换结束时,置位SEQ1的中断标志位
  24. AdcRegs.ADCTRL2.bit.SOC_SEQ1 = 1; //启动SEQ1转换序列
  25. }



    这个初始化程序基本就是ti的官方程序稍作修改,有个地方需要注意下就是adc模块的最大速率是25MHz,如果系统时钟是150MHz的话需要6分频,adc挂载的高速外设总线可以直接设置6分频,但是这会影响其他的外设的速率设置。adc自带的时钟可以设置分频系数,但是无法设置3分频或6分频,也就是说想要得到最高的25NHz只能在高速外设总线那里设置6分频。这个地方以后如果有其他外设的话需要注意,不过这个工程里没啥影响,因为只有adc。


    然后在主函数里添加测试语句段



    测试结果符合预期。


 


    ♣ 第二步,编写角度读取与转换程序


    因为5600磁编码器存在输出不稳定的情况,所以接收到的模拟量需要滤波,这里用算术平均值法,读取若干次,然后取平均值,至于这个方法造成的延迟问题后面再说吧。。。


    首先把驱动器设置好,把绝对位置控制修改为相对位置。为什么要先修改驱动器程序呢?因为dsp的ad模块最大输入电压是3V,虽然我查到资料说超过3V的部分会一直显示为最大值,但是书上说会烧坏adc模块。。。。好吧,宁可信其有,先通过驱动器设置调整电机的位置,使5600的输出值位于1.5V附近,5600用3.3V驱动时最大输出是3.3V,舵机有效行程是+-20°,所以这样就不会超过量程了。


    在用EasyMotionStudio连接驱动器的时候出现了意想不到的事情。如果直接用USB转232串口连接驱动器一切正常,但是把驱动器装到安装板上,再用USB转232连航空插头上的引出的数据线时就会出现无法建立连接的情况。然而,当DSP处于运行状态,然后让电机动一动,不管动的是不是引出232串口线的那个驱动器,然后再连接就正常了。。。我去,这是什么鬼问题。。。。


    我去!我天!我把5600接的是5V!我的妈呀!虽然说舵机的工作范围比较小,但是增大了超过额定电压的概率和幅度,万一不小心。。。墨菲定律。。。


    我计算了一下,


    运行角度按照+-20°来算,就是40°,40/360=1/9, 5_1/9约=0.55V,这个变化幅度挺小的。


    为了不让adc超出量程,我把舵机位置调整到编码器输出电压的30%,5_0.3=1.5V,这样(应该)就不会有问题了。


   


    但是我有个新问题,电压变化幅度这么小,精度够么?


    我计算了一下。


    在使用5V供电的情况下,0.55/3约=0.18,0.18_4095(F28335AD模块3V电压对应的输出值)=737(个值),


    但是呢,我做了个实验,在舵机位置不动的情况下,连续读取多次adc模块采集的结果,每次采集10个结果,记录每次的最大值与最小值,连续采集10次。



    然后取所有值里面的最大值和最小值,相减,除以最大运动范围对应的结果数,得到惊人的10.6%!


    也就是说误差居然达到了10%!!!


    不知道现在这个方案还有没有做下去的必要了。。。。


    我想了几个修改方案,


1、使用放大电路,把输出信号的幅值放大到0-2.5V左右,缺点是放大系数有一定的误差,导致最终结果可能误差较大


2、使用驱动器的编码器,不使用外置编码器,然后精确测量传动比,提高半闭环控制精度,缺点长时间运行之后可能会有累积误差


3、使用5048的数字信号反馈,避免adc模块带来的二次误差,缺点是没有根本改变运动幅值太小的问题。后来我有用电位器测试了一下,adc的误差是很低的,基本在5以内,也就是说主要的误差来自于编码器。


4、朱工说给ad端口加个500欧并联电阻,降低ad的阻抗,虽然没明白能起到什么作用,但是好厉害的样子。


5、想到再说


    


    我想了一下,还想确实应该在adc的输出端口加个接地的电阻,这样在不接编码器的时候adc读到的数据就是0,现在的情况是如果不接编码器,adc的读书就是一个不确定值,可能会对后面的程序有影响


 


    不过呢,还是要把这个部分的工作完成的。


    ○ 首先要有个读取舵机初始位置并将其设置为零点的程序。


    首先读取一定次数的当前编码器的输出值,然后求算数平均数,并将其保存到相应变量中。


    获取原点函数


  1. void ServoOrigin_Init(void)
  2. {
  3. Uint16 index = 0;
  4. Uint16 Reading = 1; //正在读取编码器反馈电压标记
  5. //读取一定次数的编码器反馈值,每次读取操作中间间隔10us(需要么?我也不知道)
  6. while(Reading)
  7. {
  8. while(AdcRegs.ADCST.bit.INT_SEQ1 == 0);
  9. AdcRegs.ADCST.bit.INT_SEQ1_CLR = 1;
  10. InitSampleTableServo1[index] = ( (AdcRegs.ADCRESULT0)>>4);
  11. InitSampleTableServo2[index] = ( (AdcRegs.ADCRESULT1)>>4);
  12. InitSampleTableServo3[index] = ( (AdcRegs.ADCRESULT2)>>4);
  13. InitSampleTableServo4[index] = ( (AdcRegs.ADCRESULT3)>>4);
  14. SumServo1 += InitSampleTableServo1[index];
  15. SumServo2 += InitSampleTableServo2[index];
  16. SumServo3 += InitSampleTableServo3[index];
  17. SumServo4 += InitSampleTableServo4[index];
  18. index++;
  19. if(index==INIT_BUF_SIZE)
  20. {
  21. index = 0;
  22. Reading = 0;
  23. }
  24. DELAY_US(10);
  25. }
  26. //计算读到的值的算数平均数
  27. OriginServo1 = SumServo1 / INIT_BUF_SIZE;
  28. OriginServo2 = SumServo2 / INIT_BUF_SIZE;
  29. OriginServo3 = SumServo3 / INIT_BUF_SIZE;
  30. OriginServo4 = SumServo4 / INIT_BUF_SIZE;
  31. }



    不过在这个过程中我遇到了一个问题,就是把所有数据求和的时候,得到的数据很小,一开始以为是方法的问题,就换成了for循环,但还是不行,想了想,一定是溢出了。我之前定义的变量是16位无符号整型,换成32位无符号整型,OK~



 


    ○ 然后就是读取当前舵机角度程序


    我的计划是ad转换一直进行,每完整的采集一次完整的数据之后就转换为角度数据存储在变量里。 


    首先关于5600的返回值,在之前的测试中我发现adc读取到的数值非常不稳定,最大值和最小值之差非常大,(误差达到了惊人的10%),但是在用万能表测试发现输出的波动并不大,朱工用示波器检测后,发现5600的输出值整体来说还是比较稳定的,但是每隔15us左右就会产生一次非常大的毛刺,其波动值甚至达到了伏级!F28335的adc模块最高工作频率是25MHz,采样带宽12.5MHz,其实最开始我对这个参数是没有什么概念的,但是经朱工一提醒,我算了一下,12.5MHz意味着adc最快采集速度是每0.08us一次,虽然我用多次采集求算术平均值的方法以减少干扰和误差,但是由于ad采集速度太快,以至于很有可能采集的数据全在5600输出值发生强烈波动的区间上,(至于波动的原因我不是很清楚,我觉得最好用硬件的方法滤掉,但是朱工说很难),这样因为采集的数据中包含的误差信号过多,所以即使用算术平均值滤波也无法得出5600的实际输出值。


    最后朱工建议的方法就是像初始化的时候一样,每隔10us采集一次,一共采集100次,再求算术平均值,这要采集一次用约1ms的时间,上位指令的发送周期是5ms,这要从理论上既保证了采集到的数据的准确度,又满足了响应速度的要求。不过我觉得这没有最大发挥F28335的性能,应该用实时采集,然后过滤掉突变数据,只保留有效数据的方法,这个以后再说吧。


    目前的读取舵机当前角度数据的函数是这样的。


  1. void ReadCurrentAngle(void)
  2. {
  3. ReadingEncoder = 1;
  4. array_index = 0;
  5. while(ReadingEncoder)
  6. {
  7. while(AdcRegs.ADCST.bit.INT_SEQ1 == 0);
  8. AdcRegs.ADCST.bit.INT_SEQ1_CLR = 1;
  9. SampleTableServo1[array_index] = ( (AdcRegs.ADCRESULT0)>>4);
  10. SampleTableServo2[array_index] = ( (AdcRegs.ADCRESULT1)>>4);
  11. SampleTableServo3[array_index] = ( (AdcRegs.ADCRESULT2)>>4);
  12. SampleTableServo4[array_index] = ( (AdcRegs.ADCRESULT3)>>4);
  13. SumServo1 += SampleTableServo1[array_index];
  14. SumServo2 += SampleTableServo2[array_index];
  15. SumServo3 += SampleTableServo3[array_index];
  16. SumServo4 += SampleTableServo4[array_index];
  17. array_index++;
  18. if(array_index==BUF_SIZE)
  19. {
  20. ReadingEncoder = 0;
  21. array_index = 0;
  22. ServoAngle1 = (int16)(SumServo1 / BUF_SIZE - OriginServo1) _ 26.3736264;
  23. ServoAngle2 = (int16)(SumServo2 / BUF_SIZE - OriginServo2) _ 26.3736264;
  24. ServoAngle3 = (int16)(SumServo3 / BUF_SIZE - OriginServo3) _ 26.3736264;
  25. ServoAngle4 = (int16)(SumServo4 / BUF_SIZE - OriginServo4) _ 26.3736264;
  26. //这个系数是,当前相对零点的读数值设为n,n / 4095 * 3 /(5 / 360) / 0.002,
  27. SumServo1 = 0;
  28. SumServo2 = 0;
  29. SumServo3 = 0;
  30. SumServo4 = 0;
  31. }
  32. }
  33. DELAY_US(READ_DELAY);
  34. }



    在测试过程中遇到了个小问题,最后得到的角度数据总是溢出,而且是一会儿正常,一会儿溢出。根据经验我知道是一个计算式中包含不同种数据格式产生的问题。


    最开始我是这要写的,SumServo1为无符号整型,BUF_SIZE是个整数,OriginServo1是个无符号整型,ServoAngle1是个整型



    经过各种分析与试验,最后发现是在减法运算时,如果结果大于0,那就是正常的,如果结果小于0,因为参与运算的都是无符号类型数据,于是会把有符号的数值存为无符号数据再继续参与运算,于是就出错了。


    然后改了一下,好了



   


    ♣ 第三步,修改舵机角度发送程序,使系统根据当前位置与上位指令,控制舵机以相对位置方式运行。


    只要把之前的canb发送程序里面的运行角度值由接收到的角度值直接运算改为先减去当前角度值在进行运算就可以了


  1. void <span class=”hljs-title function_“>CANB_SendDegreeRelative(void)
  2. {
  3. if(<span class=”hljs-title class_“>AngleDataAvailable == 1) //检测上位角度数据指令标记,1为有效,0为无效
  4. {
  5. MotionAngle1 = (InstructionAngle1 - ServoAngle1) / 500 _ 48; //将上位角度指令转化为驱动器能识别的格式
  6. MotionAngle2 = (InstructionAngle2 - ServoAngle2) / 500 _ 48;
  7. MotionAngle3 = (InstructionAngle3 - ServoAngle3) / 500 _ 48;
  8. MotionAngle4 = (InstructionAngle4 - ServoAngle4) / 500 _ 48;
  9. ECanbMboxes.MBOX1.MDL.all = MotionAngle1; //将角度值存入发送缓存
  10. ECanbMboxes.MBOX5.MDL.all = MotionAngle2;
  11. ECanbMboxes.MBOX9.MDL.all = MotionAngle3;
  12. ECanbMboxes.MBOX13.MDL.all = MotionAngle4;
  13. ECanbRegs.CANTRS.all = 0x00003333; //启动发送
  14. AngleDataAvailable = 0; //上位角度数据有效位清零
  15. }
  16. }



    同时驱动器程序也要从绝对位置改为相对位置。



    运行,然后就出问题了,当我不断发送同一位置的指令时,理论上应该只动一次,但是舵机却一直会动。


    通过调试,我发现当我发送+10°的指令时,dsp读到的位置却是负的,也就是说编码器的方向和电机的运行方向是相反的。


    我测试了一下,电机正向运行时,面对输出轴时,输出轴顺时针运动,(面对电机的霍尔开关编码器时,也是顺时针运动,此时面对编码器时,在驱动器设置界面,编码器运动的显示方向与实际方向是一致的)。当电机正向运行时,5600输出的电压是逐渐减小的,也就是说,面向输出轴时,编码器的方向是逆时针方向为正。按照右手定则,应该是逆时针为正,所以得出的结论,电机的运行方向是反的。


    我最开始打算直接把电机的正负极对调,但是转念一想,这样电机的运行方向和电机编码器的正方向就相反了,于是只好在程序里做调整。


    因为默认5600方向与指令方向相同,所以需要修改的就是电机的运行方向。


    在运行角度赋值这里加个负号,搞定~



    但是,但是,又出问题了!理论上来讲,上位指令精度达到了0.002°,5600反馈的精度有40°/737(前面算过)约=0.054°,驱动器运行角度这里稍微有点问题,就是只能是整数,但是朱工通过设置减速比使角度能够达到0.1°,但是在测试过程中,上位指令变动幅度大于1°时,dsp才会发出动作信号,这不科学呀!


    经过分析,我敏锐的感觉,这个又是因为运算过程中数据类型的限制引发的错误!


    经过排查,发现问题在这里


    


    先做除法相当于使精度降低了两个数量级,改一下


    


    但是,但是!问题又又又出现了!现在舵机会出现一种情况,就是在连续发送指令的时候没有问题,但是我直接输入某一个角度的时候,舵机不响应或者只转动很小的角度。这又是什么神仙问题!


    经过排查,我觉得问题依然出自这几个角度计算式中,我把每一步分开,用temp1到3来分别缓存每一步的结果



    我以为会在某一步里出现问题,但是结果却是没有问题!我去,为啥连起来算就不行,分开算就行!?


    这时,我略一沉思,发现事情非常简单,还是数据类型的问题。指令角度和当前角度都是16位整型,当变化角度很小的时候,他们相减的值乘以48没有问题,但是当变化角度比较大时,他们相减的值乘以48就会溢出。


    所以要在相减之后把数据类型强制转换为32位整型,如下



    暂时可以用了。。。希望别再有问题了。。。


    精度现在没啥问题了,可是又有了新的问题,抖动。。。。角度变化速率慢的时候一直抖,抖得很严重。。。。很明显是5600信号不稳定的问题。。。。看来只好改滤波算法了。