1. 引言
硬件:raspberry 4B 外挂 mcp2515模块
系统:ubuntu22.04
软件: vscode
语言:c++ 和 python
前面已经修改系统配置文件,成功加载mcp2515模块并通过命令行测试can回环模式loopback成功树莓派(Ubuntu22.04)+MCP2515实现can通讯_树莓派 can-CSDN博客
2. c++实现can通讯控制电机
我用的电机是faulhaber,通讯协议canopen,用的pcan_view分析仪
用c++实现can通讯,需要include对应的can.h头文件进行can收发函数的调用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <iostream>
using namespace std;
int main()
{
int s,nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame[6]={{0}};
/*struct can_frame {
canid_t can_id; //can标识符
__u8 can_dlc; //数据场的长度
__u8 data[8]; //数据
}*/
//创建socketCAN套接字
s=socket(PF_CAN, SOCK_RAW, CAN_RAW);
//指定can0设备
strcpy(ifr.ifr_name,"can0");
ioctl(s,SIOCGIFINDEX,&ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s,(struct sockaddr*)&addr, sizeof(addr));//将套接字与can0绑定
//禁用过滤规则,本进程不接收报文,只负责发送
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
//NMT上电
frame[0].can_id = 0x000; //如果为扩展帧,那么frame.can_id = CAN_EFF_FLAG | 0x11;
frame[0].can_dlc = 1 ; //数据长度
frame[0].data[0]= 0x01; //数据内容
nbytes = write(s, &frame[0], sizeof(frame[0])); //发送frame[0],发送数据使用write函数实现
printf("nbytes=%d\n",nbytes);
if(nbytes != sizeof(frame[0])) //如果nbytes不等于帧长度,就说明发送失败
{
printf("Send Error frame[0]\n!");
}
sleep(0.2); //延时0.2s=200ms
//sdo_controlword_0x06---shut down
frame[1].can_id = 0x601;
frame[1].can_dlc = 0x08;
frame[1].data[0]= 0x2F;
frame[1].data[1]= 0x60;
frame[1].data[2]= 0x60;
frame[1].data[3]= 0x00;
frame[1].data[4]= 0x01;
frame[1].data[5]= 0x00;
frame[1].data[6]= 0x00;
frame[1].data[7]= 0x00;
nbytes = write(s, &frame[1], sizeof(frame[1])); //发送frame[1]
if(nbytes != sizeof(frame[1]))
{
printf("Send Error frame[1]\n!");
}
sleep(0.2);
//sdo_controlword_0x07---switch on
frame[2].can_id = 0x601;
frame[2].can_dlc = 0x08;
frame[2].data[0]= 0x23;
frame[2].data[1]= 0x7A;
frame[2].data[2]= 0x60;
frame[2].data[3]= 0x00;
frame[2].data[4]= 0xE8;
frame[2].data[5]= 0x03;
frame[2].data[6]= 0x00;
frame[2].data[7]= 0x00;
nbytes = write(s, &frame[2], sizeof(frame[2])); //发送frame[1]
if(nbytes != sizeof(frame[2]))
{
printf("Send Error frame[2]\n!");
}
sleep(0.2);
//sdo_OperationMode_0x08---modes of operation----csp
frame[3].can_id = 0x601;
frame[3].can_dlc = 0x08;
frame[3].data[0]= 0x2B;
frame[3].data[1]= 0x40;
frame[3].data[2]= 0x60;
frame[3].data[3]= 0x00;
frame[3].data[4]= 0x0F;
frame[3].data[5]= 0x00;
frame[3].data[6]= 0x00;
frame[3].data[7]= 0x00;
nbytes = write(s, &frame[3], sizeof(frame[3])); //发送frame[1]
if(nbytes != sizeof(frame[3]))
{
printf("Send Error frame[3]\n!");
}
sleep(0.2);
//sdo_controlword_0x0F电机上电初始化
frame[4].can_id = 0x601;
frame[4].can_dlc = 0x08;
frame[4].data[0]= 0x2b;
frame[4].data[1]= 0x40;
frame[4].data[2]= 0x60;
frame[4].data[3]= 0x00;
frame[4].data[4]= 0x7f;
frame[4].data[5]= 0x00;
frame[4].data[6]= 0x00;
frame[4].data[7]= 0x00;
nbytes = write(s, &frame[4], sizeof(frame[4])); //发送frame[1]
if(nbytes != sizeof(frame[4]))
{
printf("Send Error frame[4]\n!");
}
sleep(0.2);
close(s);
return 0;
}
3. python实现can通讯控制电机
过程中也遇到不少问题,参考的一些连接放在这里:
教程1:8. CAN总线通讯 — [野火]Python应用开发实战指南——基于LubanCat-i.MX6ULL-MP157开发板 文档 (embedfire.com)
教程2:使用Python玩转CAN通讯_python实现can通信-CSDN博客
用python实现can通讯,需要import can库才能进行can收发函数的调用
提示:python的标准库默认已经在“自家仓库”里了,直接 import 到项目就可以调用
python的三方库需要先用命令 pip install 从互联网“搬运到自家仓库”,再 import 到项目才可以调用
python can库为第三方库,因此需要两步:
pip install python-can
""" python can 测试 """
import sys
import time
import threading
import can
import numpy
import canlib
import keyword
'''
def msg_recv(device_x):
"接收消息功能"
print("success: msg_recv Thread is running!")
# 将can_mask转换为二进制形式,can_mask中为1的位,用于过滤接收到的帧
# 举例 id: 0 0
# mask: 1 0 则接收到消息的ID中,mask为1对应id中的位,必须与id一致,为0
# 如接收到了四个id的消息 id1: 0 0 此条消息被放行
# id2: 0 1 此条消息被放行
# id3: 1 0 此条消息被过滤
# id4: 1 1 此条消息被过滤
# 过滤器配置示例如下。第一条规则,接收所有标准帧,第二条规则,接收拓展帧中id为0x300的消息。
can_filters = [
{"can_id": 1, "can_mask": 0x0, "extended": False},
{"can_id": 0x300, "can_mask": 0x1FFFFFFF, "extended": True},
]
# 应用过滤器配置
device_x.set_filters(can_filters)
# 查询退出线程是否退出,如果为真,则说明用户期望程序退出,退出本线程循环,线程结束
while tasks_quitThread.is_alive():
try:
# 接收can消息
msg = device_x.recv(1)
if msg is not None:
print("success: ", msg)
except can.CanError:
print("error: 接收消息时出错,请检查设备是否启用及状态正常")
'''
def msg_send(device_x):
"发送消息功能"
print("success: msg_send Thread is running!")
# 构造发送的CAN消息结构,ID为0xC0FFEE,数据内容包含在data中,is_extended_id为拓展ID标识
msg1 = can.Message(arbitration_id=0x000, data=[0x01], is_extended_id=False)
msg2 = can.Message(arbitration_id=0x601, data=[0x2F, 0x60, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00], is_extended_id=False)
msg3 = can.Message(arbitration_id=0x601, data=[0x23, 0x7A, 0x60, 0x00, 0xE8, 0x03, 0x00, 0x00], is_extended_id=False)
msg4 = can.Message(arbitration_id=0x601, data=[0x2B, 0x40, 0x60, 0x00, 0x0F, 0x00, 0x00, 0x00], is_extended_id=False)
msg5 = can.Message(arbitration_id=0x601, data=[0x2B, 0x40, 0x60, 0x00, 0x7F, 0x00, 0x00, 0x00], is_extended_id=False)
# 查询退出线程是否退出,如果为真,则说明用户期望程序退出,退出本线程循环,线程结束
while tasks_quitThread.is_alive():
try:
# 发送构造的CAN消息
device_x.send(msg1)
time.sleep(0.2)
device_x.send(msg2)
time.sleep(0.2)
device_x.send(msg3)
time.sleep(0.2)
device_x.send(msg4)
time.sleep(0.2)
device_x.send(msg5)
time.sleep(0.2)
# 打印发送提示
print(f"success: 消息已发送至 {device_x.channel_info}")
except can.CanError:
print("error: 消息发送出错,请检查设备是否启用及状态正常!")
# 两秒后再次发送,sleep函数的单位为s
time.sleep(1)
def tasks_quit():
"程序退出功能"
print("success: tasks_quit Thread is running!")
exitright = "e"
while exitright not in ["q", "Q"]:
# 获取用户输入,如果为q则退出程序
exitright = input(
"""
***********************************
**输入字母q后, 按下回车以退出程序**
***********************************
"""
)
# 线程退出
# 打印运行程序前提示信息
print(
"information: 执行本程序前, 请先启用can设备。命令如下:\
\nsudo ip link set can0 type can bitrate 500000\nsudo ip link set can0 up"
)
# 打开CAN设备,CAN设备类型为socketcan,channel为can0,可使用ifconfig -a命令查看。
with can.interface.Bus(bustype="socketcan", channel="can0", bitrate=500000) as device_can0:
# 创建线程:监听程序退出线程、发送can消息线程、接收can消息线程
try:
# 创建线程
print("information: 开始创建 tasks_quitThread 线程!")
tasks_quitThread = threading.Thread(target=tasks_quit, daemon=True)
print("information: 开始创建 msg_sendThread 线程!")
msg_sendThread = threading.Thread(target=msg_send, daemon=True, args=(device_can0,))
# print("information: 开始创建 msg_recvThread 线程!")
# msg_recvThread = threading.Thread(target=msg_recv, daemon=True, args=(device_can0,))
# 开启线程
print("information: 开始启动 tasks_quitThread 线程!")
tasks_quitThread.start()
print("information: 开始启动 msg_sendThread 线程!")
msg_sendThread.start()
# print("information: 开始启动 msg_recvThread 线程!")
# msg_recvThread.start()
except:
print("error: 创建或启动线程中出错!")
sys.exit()
# 等待线程结束
tasks_quitThread.join()
print("information: tasks_quitThread结束")
msg_sendThread.join()
print("information: msg_sendThread结束")
# msg_recvThread.join()
# print("information: msg_recvThread结束")
# 所有正常线程结束,退出程序
sys.exit()