简介
出于各种各样的原因,我们没有使用常见的stm32来进行can通信,也没有使用tx2自带的can口,而是采用串口来发送和接收can数据报文。事实证明,这是可行的,但现在网络上关于这方面的资料较少,故博主将探索过程和所得记下,希望可以帮助到其他有同样任务的人,虽然我所使用的开发板为NVIDIA的tx2,但基本想法是适用于几乎所有的开发板的(只要有串口USB)。本文仅供参考,如有不足或错误,欢迎讨论!
1.硬件
我所使用的USB-can模块是维特智能的USB-CAN适配器
资料详情见链接资料详情-维特智能
使用手册很详细,有问题可以直接找客服和技术支持
实物与图片不一样,有五个接头,只需要找到TX和RX就行。
我所使用的电机和电调为大疆的m3508和c620,电调can两根信号线与USB-can模块连接即可。
电机的连线由于不同电机不同,请自行查询使用手册。重点在于can总线id的设置。
2.基本思路
usb-can的使用范例都是基于pc机的串口助手,不过既然用串口助手可以work,用开发板的串口同样可以。我们将用板子的串口代替串口助手,发送和接收报文,但这样做的话我们需要解析can的报文。所幸并不复杂。
我们使用的是AT指令。根据上面的USB-can模块使用手册以及大疆c620电调的can发送、接收报文格式,(见下)我们可以看出:
想要成功控制电机,我们通过串口应该发送的格式如下(举个例子)
41 54 40 00 00 00 08 00 FF 00 FF 00 FF 00 FF 0D 0A
其中,41 54为帧头,40 00 00 00为ID(这个值是根据c620电机的标识符0x200得到的,由于id为ID域的前11位,因此要移位,值也就变了),08为数据长度,00 FF 00 FF 00 FF 00 FF为数据域,也就是四个轮子的电流值,0D 0A为帧尾。
!!注意,以上指令是以十六进制发送的,也就是用串口助手的话,要勾选上hex
接收报文格式类似,见c620使用手册
因此,我们的串口收到的报文应该长这样(类似的格式,这里以电调1为例)
41 54 40 20 00 00 08 00 FF 00 FF 00 FF 00 FF 0D 0A
还是要注意id域的值是要移位的!!
3.具体细节
3.1串口助手使用细节
如果使用pc的串口助手,详情见上面模块的指导书,这里简述。
AT+CG
进入配置模式,AT+USART_PARAM
查询/设置串口参数,由于c620电机can总线的频率为1M,为了不丢包,串口的波特率也要接近1M,所以我们选择921600或1M。但这样在串口助手上会发生恐怖的事,因为太快了电脑反应不过来,或者需要很长时间反应,建议不要轻易尝试。
配置完成后,AT+AT
进入AT指令模式,此时我们可以发送报文了,格式见上述发送报文的样例。成功的话轮子会转动(但可能转一下很快就停了)。在进入AT指令后,我们也会不断的收到电调的返回报文。
3.2开发板串口使用细节
但是,真正要使用的话,大多数情况还是要用开发板的。所以下面重点介绍板子串口的代码。
3.2.1 开发板串口配置
tx2串口配置网上有很多相关blog,此处不再赘述。贴个链接
tx2串口配置
注意修改波特率(由于奇怪的原因,tx2波特率最高只能设置到460800,但是也能用)
cfsetispeed(&opt, B460800);
cfsetospeed(&opt, B460800);
3.2.2 发送
其次就是发送报文的生成。我采用的方法是进行一系列的字符串操作,把int类型的控制值(电流)转换成字符串嵌入发送报文,重点和难点在于要实现十六进制发送,但一般我们的控制量都是十进制的,所以要做个转换。这个网上也有blog。
还有就是正反转的问题,正数和负数的补码不同,需要分情况。
具体代码见文件
(此处的代码只贴了一个轮子的)(避免过长)
char HexChar(char c)
{
if((c>='0')&&(c<='9'))
return c-'0';
else if((c>='A')&&(c<='F'))
return c-'A'+10;
else if((c>='a')&&(c<='f'))
return c-'a'+10;
else
return 0x10;
}
int Str2Hex(string str, char *data)
//send in hex
{
int t,t1;
int rlen=0;
int len=str.size();
if(len==1){
char h=str[0];
t=HexChar(h);
data[0]=t;
rlen++;
}
for(int i=0;i<len;){
char l,h=str[i];
if(h==' '){
i++;
continue;
}
i++;
if(i>=len){
break;
}
l=str[i];
t=HexChar(h);
t1=HexChar(l);
if((t==16)||(t1==16))
break;
else
t=t*16+t1;
i++;
data[rlen]=t;
rlen++;
}
return rlen;
}
void tohex(char spd[],char spd_hex[])
//special function to fit the format of AT command, like "1234" to " 12 34"
{
int j=6;
for(int i=5;i>=0;i--){
if(!spd[i])
j--;
}
int k=5;
for(j--;j>=0;j--){
if(spd_hex[k]==' ')
k=k-1;
spd_hex[k]=spd[j];
k--;
}
}
int wspd1=2000;
void atsend(int fd){
char writebuffer[60]="";
char head[ ]="41 54 40 00 00 00 08";
char tail[ ]=" 0D 0A";
char writedata[33]="";
//write speed to hex
char spd1[6]=""
//write speed format
char new1[7]=" 00 00";
//negative data format
string _strnew1="0000";
//write speed to hex
char mm1[ ]="";
//write speed format
char _charnew1[7];
if(wspd1<0)
{
sprintf(mm1,"%X",wspd1);//transfer to hex
for(int i=4;i<8;i++)
_strnew1[i-4]=mm1[i];//keep four letters of hex(take -100 for example, mm1 is FFFFFF9C, we just need FF9C)
_strnew1=_strnew1.insert(0," ");
_strnew1=_strnew1.insert(3," ");//_strnew1 is now " FF 9C", to fit the format of AT command
strcpy(_charnew1,_strnew1.c_str());//turn string to char[]
strcat(writedata,_charnew1);//construct writedata
}
else
{
sprintf(spd1,"%X",wspd1);
//turn to standard format
tohex(spd1,new1);
//construct writedata
strcat(writedata,new1);
}
//construct writebuffer
strcat(writebuffer,head);
strcat(writebuffer,writedata);
strcat(writebuffer,tail);
printf("writerbuffer=%s\n",writebuffer);//writerbuffer is like 41 54 40 00 00 00 08 00 FF 00 FF 00 FF 00 FF 0D 0A
char data[18]={};
Str2Hex(writebuffer,data);//data actually what we send(send in hex)
int w;
printf("%s\n", data);
w = write(fd, data, 17);
printf("write %d \n", w);
}
我的方法很笨,大佬勿喷。
附上头文件
#include<termios.h>
#include<fcntl.h>
#include<unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<time.h>
#include<string>
#include<iostream>
#include<math.h>
此时,我们只需要修改外部定义的int wspd1=2000;
,即可改变AT报文,即可控制给电机的电流值。
3.2.3 接收
接收函数如下
//retrun speed
int speed1=0;
int speed2=0;
int speed3=0;
int speed4=0;
int cread(int fd, char buff[], int lenth){
int len;
memset(buff, 0, lenth);
len = read(fd, buff, lenth);
printf("LEN:%d\n", len);
if(len!=17)
cout<<"wrong len"<<endl;
else if(buff[0]!=0x41)
cout<<"wrong AT command"<<endl;
else
//buff is like 41 54 40 20 00 00 08 00 FF 00 FF 00 FF 00 FF 0D 0A
int id=buff[3]>>4;
int _speed=(buff[9]*256+buff[10]);//unsigned from 0 to 65535
signed short int speed=0;
speed=_speed;//signed, + and - means positive or negaive rotation
switch(id)
{
case 2:speed1=speed;break;
case 4:speed2=speed;break;
case 6:speed3=speed;break;
case 8:speed4=speed;break;
}
cout<<speed1<<endl;//for debug
cout<<speed2<<endl;
cout<<speed3<<endl;
cout<<speed4<<endl;
return len;
}
将接收报文中的数据读出、保存在外部变量speed中
3.2.4 主函数
下面为主函数(只有读取数据),用开发板很明显要两个进程
int main(){
int fd;
char str[32];
char begin_CG[ ]="AT+CG\r\n";
char begin_AT[ ]="AT+AT\r\n";
fd = open_port();
set_opt(fd);
//上面两个函数为串口配置函数,,自己写一下,我就不贴了
csend(fd, begin_CG, 7);
while(cread(fd, str, 32));//读取之前可能中途截断残留的返回报文
csend(fd, begin_AT, 7);
sleep(1);
cread(fd, str, 4);//read head(useless message)
while(1){cread(fd, str, 17);}
//main loop.17 is the length of AT message
close(fd);
return 0;
}
void csend(int fd, char buff[], int length){
int w;
printf("%s\n", buff);
w = write(fd, buff, length);
printf("write %d \n", w);
}
4.结语
由于网上有关can的教程都是基于stm32的,所以特意写下这个特殊的方法,以及我在整个过程中遇到的困难和解决办法。文章很冗长,是因为我想把每一个细节都讲清楚,希望可以帮助到有需要的人!
这只是tx2与电调的通讯模块,真正要使用can控制轮子的话还需要加一个pid控制,之后有可能再补充pid的代码。
具体代码见我的资源。(csdn上传文件好像只能这样)如果愿意下载谢谢你的打赏!
不过,我知道大家都是白嫖人,所以我贴个网盘。
链接:https://pan.baidu.com/s/1lZP_gV3xwxQyBOv3VlYewg
提取码:pgkw
复制这段内容后打开百度网盘手机App,操作更方便哦
(其中的demo.zip为stm32的样例代码)
(代码里串口的配置要根据自己的情况做修改啊!)