imuncle.github.io icon indicating copy to clipboard operation
imuncle.github.io copied to clipboard

多摩川编码器使用总结

Open imuncle opened this issue 6 years ago • 7 comments
trafficstars

在驱动步进电机成功之后(可看这篇文章了解详情),剩下的就是读取编码器的值了。

编码器使用的是多摩川(Tamagawa)的39位高精度编码器(23位单圈精度+16位多圈精度),支持RS485通信,波特率为2.5Mbps,数据长度为八位。型号为TS5700N8401,属于SA48系列编码器。

使用起来很简单,只要数据手册正确以及模块能满足功能。。。这里就记录一下我这一周所踩的坑。

RS485通信

RS485通信是从RS232中衍生出来的,解决了RS232不能联网通信的痛点。

RS485与串口通信非常相似,也是两根线,但它可以像CAN通信一样,一条总线上搭载多个设备,实现联网通信。两根线分别为A线和B线,这里就说说我踩的第一个坑:

编码器的说明书里把两个数据线写作: image

这我怎么知道哪根是A线哪根是B线啊,百度也百度不到,拿头来接吗。。

没办法,只能试错法,结果一次“成功”,我成功收到了数据。结果后来仔细一看这数据是我自己的发出去的,当时就自闭了,为啥我会收到我自己发的数据?

后来的某个晚上我突然开窍,原来是接反了,A、B两线反接就跟TX和RX短接一样,RX的信号立马被TX捕获,造成了接收到编码器数据的假象。

USART低位优先

搞懂了这一点后我把线反接回来,但这下一点反馈信息都没有了,编码器就像死了一样。没办法,看通信协议哪里写错了吧。

(这里又要吐槽一下,这编码器的数据手册是真的难找,淘宝店家给的和公司网站找到的都只有编码器的性能参数和接线方法,死活没有通信协议,后来我从CSDN上才找到一个型号很相近的TS5700N8501编码器的数据手册,这下才开工)

下载地址: https://download.csdn.net/download/ppl002/10787122

读了数据手册才知道编码器可以读取绝对值角度数据,也可以读写数据到内置的EEPROM中,当然我这里只需要读取绝对值角度数据就行了。

通信协议如下: image

image

image

image

我先读取编码器的单圈数值吧,所以我要请求DATA_ID_0

起始位&停止位

手册里写的是CF由10bits,但众所周知USART一次只能发送8bits的数据,而这里显然是不能分两次发送的。

后来我才领悟到原来这里的起始位和停止位并不需要手动发送,因为USART的通信协议中就自带了起始位和停止位。所以这里我只需要发送0100 0000就行了,也就是0x40

但是发送过去毫无反应,再次自闭。

后来我又突然想起,USART是先发送低位数据再发送高位数据的,所以这里发送的不应该是0x40,而应该是0000 0010,也就是0x02!!

MAX485

修正之后,还是没有接收到任何编码器的反馈数据,不由得反思起来。

终于,我把目光看向了手上的485芯片。因为我是直接用的参加RoboMaster比赛使用的STM32开发板,上面并没有485收发芯片,所以我单独买了一个485芯片。我买的芯片型号是MAX13487EESA,一查数据手册发现这货最高只支持到500Kbps,远远不能满足编码器2.5Mbps的需求。没办法,换吧。

然后我换了MAX485芯片,它所支持的最高频率刚好就是2.5Mbps。

大功告成

等了两天,MAX485终于到货了,把线接上,把写好的代码烧进去,进入调试,哈哈哈!终于收到编码器的反馈数据了。

但是,嗯?感觉还是有点问题,数据不太对。

然后我就开始漫无目的地百度搜索多摩川编码器反馈值乱码,还真给我搜到了。原来是编码器刚上电的时候只有5位精度,需要手动旋转11.25度一样才能达到正常精度,怎么会有这种操作。

原文链接

于是我先上电,扭一下编码器,然后进入调试,一看,哈!数据正常了!而且单圈精度并不是像说明书写的那样是17位,而是23位!!!

尾声

至此,多摩川编码器基本上算是被我征服了。犯了很多低级的错误,踩了很多坑,记录下来引以为戒。

最后附上我的部分代码:

  • while循环
while (1)
  {
	nrz_read(DATA_ID_3);
	HAL_Delay(1);
	rx_parse(USART6_RX_Buff);
	HAL_Delay(10);
  }
  • nrz_read函数
void nrz_read(uint8_t data_id)
{
	ti.data_id = data_id;
	nrz_rx_cnt = 0;
	tx_prepare(&tx, &tx_size, &rx_size);
	nrz_tx(tx_size, &tx);
}
  • tx_prepare函数
void tx_prepare(uint8_t *tx, uint8_t *tx_size, uint8_t *rx_size)
{
    switch(ti.data_id)
    {
        case DATA_ID_0:
            *tx = 0x02;
            *tx_size = 1;
            *rx_size = 6;
            break;

        case DATA_ID_1:
            *tx = 0x8A;
            *tx_size = 1;
            *rx_size = 6;
            break;

        case DATA_ID_2:
            *tx = 0x92;
            *tx_size = 1;
            *rx_size = 4;
            break;

        case DATA_ID_3:
            *tx = 0x1A;
            *tx_size = 1;
            *rx_size = 11;
            break;

        case DATA_ID_7:
            *tx = 0xBA;
            *tx_size = 1;
            *rx_size = 6;
            break;

        case DATA_ID_8:
            *tx = 0xC2;
            *tx_size = 1;
            *rx_size = 6;
            break;

        case DATA_ID_C:
            *tx = 0x62;
            *tx_size = 1;
            *rx_size = 6;
            break;

        case DATA_ID_6:
            *tx++ = 0x32;
            *tx++ = ti.tx.adf;
            *tx++ = ti.tx.edf;
            *tx_size = 4;
            *rx_size = 4;
            *tx = crc(tx - 3, *tx_size - 1);
            break;

        case DATA_ID_D:
            *tx++ = 0xEA;
            *tx++ = ti.tx.adf;
            *tx_size = 3;
            *rx_size = 4;
            *tx = crc(tx - 2, *tx_size - 1);
            break;

        default:
            break;
    }
}
  • rx_parse函数
void rx_parse(uint8_t *p)
{
    switch(ti.data_id)
    {
        case DATA_ID_0:
        {
            ti.rx.cf = p[0];
            ti.rx.sf = p[1];
            ti.rx.abs = p[2] | (p[3] << 8) | (p[4] << 16);
            ti.rx.crc = p[5];
            break;
        }
        case DATA_ID_7:
            break;
        case DATA_ID_8:
            break;
        case DATA_ID_C:
            ti.rx.cf = p[0];
            ti.rx.sf = p[1];
            ti.rx.abs = p[2] | (p[3] << 8) | (p[4] << 16);
            ti.rx.crc = p[5];
            break;

        case DATA_ID_1:
            ti.rx.cf = p[0];
            ti.rx.sf = p[1];
            ti.rx.abm = p[2] | (p[3] << 8) | (p[4] << 16);
            ti.rx.crc = p[5];
            break;

        case DATA_ID_2:
            ti.rx.cf = p[0];
            ti.rx.sf = p[1];
            ti.rx.enid = p[2];
            ti.rx.crc = p[3];
            break;

        case DATA_ID_3:
            ti.rx.cf = p[0];
            ti.rx.sf = p[1];
            ti.rx.abs = p[2] | (p[3] << 8) | (p[4] << 16);
            ti.rx.enid = p[5];
            ti.rx.abm = p[6] | (p[7] << 8) | (p[8] << 16);
            ti.rx.almc = p[9];
            ti.rx.crc = p[10];
            break;

        case DATA_ID_6:
            break;
        case DATA_ID_D:
            ti.rx.cf = p[0];
            ti.rx.adf = p[1];
            ti.rx.edf = p[2];
            ti.rx.crc = p[3];
            break;

        default:break;
    }
}

imuncle avatar Mar 04 '19 10:03 imuncle

老哥,你好,能请教一下你是用SCI来实现多摩川的这个通信协议的嘛?我看多摩川使用的是NRZ格式,而SCI模块恰好支持NRZ格式

243459529 avatar Jun 27 '24 11:06 243459529

不是喔,我用的是普通的UART,没有用额外的模块

imuncle avatar Jun 27 '24 14:06 imuncle

请问能够借鉴一下相关程序嘛,可以有偿

243459529 avatar Jun 28 '24 03:06 243459529

源码:https://github.com/imuncle/Embedded_Peripheral_Libs/tree/master/Tamagawa

imuncle avatar Jun 28 '24 03:06 imuncle

哇!非常感谢!抱拳

243459529 avatar Jun 28 '24 11:06 243459529

我发40可以有回信息,但是发送02没有信息返回,我是直接用的rs485转usb模块

sfeng97 avatar Sep 13 '24 14:09 sfeng97