试用5 CAN总线主机
龙芯2k500系统集成了两路can总线接口,我们采用CAN0接口完成设备信息管理的功能。
CAN0设备硬件接线
2k500提供的两路CAN接口为TTL电平的,需要通过外接CAN的物理层才可以实现正常通讯过程,这里选用飞思卡尔的Mxdock的外设板接口提供的CAN物理层来扩展。
CAN0接口从J52端子的19,20引出,如下图所示,同时我们还需要5V电源给mxdock板供电,所以还需要23,24两个引脚。
CAN0设备配置
进入2k500系统后,我们执行
ifconfig –a
可以看到系统存在的所有连接接口,其中就包含CAN0和CAN1 。
这里我们就配置CAN0的接口,在终端我们输入如下命令,配置CAN0 为 50Kbps 。由于我们采用导线连接2k500和mxdock,如果速度太快通讯经常失败,50Kbps是我测试通讯效果比较好的总线速度。
ip link set can0 up type can bitrate 50000
执行效果:
通过执行命令
ip -s –h –d link show can0
查看一下我们设置的是否成功了,如下图所示:
从图中我们可以看到设定的波特率为50000 sample-point 0.850 ,说明我们的设置成功了。
基于CAN总线的主机设计思路
1)网络架构图
2)程序结构
创建独立的can接收线程,在线程中按设备id解析数据后填充到数据缓冲区中。
void * task_can_receive(void* param)
{
int s, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
struct can_filter rfilter[1];
printf("task can receive begin...\n");
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
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));
while(1)
{
nbytes = read(s, &frame, sizeof(frame));
if(nbytes > 0)
{
printf("ID=0x%X DLC=%d data[0]=0x%X\n", frame.can_id, frame.can_dlc, frame.data[0]);
}
if(frame.can_id == 0x01 )
{
if(frame.data[0] == 0x01)
{
yc[0][0] = (float)(frame.data[1] * 256 + frame.data[2]) / 0x500;
yc[0][1] = (float)(frame.data[3] * 256 + frame.data[4]) / 0x500;
}
elseif(frame.data[0] == 0x02)
{
memcpy(&yx[0][0], &frame.data[1], 4);
}
}
elseif(frame.can_id == 0x02 )
{
if(frame.data[0] == 0x01)
{
yc[1][0] = (float)(frame.data[1] * 256 + frame.data[2]) / 0x500;
yc[1][1] = (float)(frame.data[3] * 256 + frame.data[4]) / 0x500;
}
elseif(frame.data[0] == 0x02)
{
memcpy(&yx[1][0], &frame.data[1], 4);
}
}
fresh_data_flag = 1;
}
close(s);
}
3) 巡检线程
在主线程中,根据配置的设备数量,定时巡检各个设备。
frame.can_id = server_id;
frame.can_dlc = 1;
frame.data[0] = 0x06;
can_send_pack(&frame);
if(server_id < SERVER_NO)
server_id ++;
else
server_id = 1;
usleep(900 * 1000);
4) redis信息写入
但CAN接收线程成功的接收到设备信息后,会见设备上送的内容存入缓冲区中,同时设置一个全局标志,表明信息已经被更新了,主线程检查到更新标志后,将数据写入到实时数据库中。
if(fresh_data_flag)
{
fresh_data_flag = 0;
printf("%d:%f,%f,%d,%d,%d,%d",server_id,yc[server_id-1][0],yc[server_id-1][1],yx[server_id-1][0],yx[server_id-1][1],
yx[server_id-1][2],yx[server_id-1][3]);
if(server_id == 0x01)
{
for (int i = 0; i < 2; i++) {
//set
reply = (redisReply *)redisCommand(redis_handle , "SET CAN_D1_YC%d%0.3f", i, yc[0][i]);
if (reply!=NULL && reply->type==REDIS_REPLY_STATUS)
{
printf("%s\n", reply->str);
}
freeReplyObject(reply);
}
for (int i = 0; i < 4; i++) {
//set
reply = (redisReply *)redisCommand(redis_handle , "SET CAN_D1_YX%d%1d", i, yx[0][i]);
if (reply!=NULL && reply->type==REDIS_REPLY_STATUS)
{
printf("%s\n", reply->str);
}
freeReplyObject(reply);
}
}
}
设备模拟的基本设计思路
为了简化CAN设备的设计,采用python语言,利用 slcan协议实现CAN总线的设备模拟。设备主要提供模拟量采集和开关量采集模拟,CAN总线通讯的模拟等。
CAN总线设备创建:
bus = can.Bus(interface = 'slcan' , channel = 'COM4' , bitrate = 50000, receive_own_messages = False )
创建一个基于线程的类,模拟设备
class canThread( threading.Thread) :
__run_flag = True
def __init__( self, ThreadID, name, delay):
threading.Thread.__init__ ( self)
self.threadID = ThreadID
self.name = name
self.delay = delay
self.__run_flag = True
def run(** self**):
print("开启线程: " + self.name)
while self.__run_flag :
self.can_recv_thread()
@ staticmethod
def send_msg( did, da) :
msg = can.Message(arbitration_id = did, data = da, is_extended_id =False)
try:
''' 发送信息
bus.send(msg)
print(msg)
# print("Message sent on {}".format(bus.channel_info))
except can.CanError:
print("Message NOT sent")
@staticmethod
def can_recv_thread(self):
# bus = can.interface.Bus();
# ''' 接收信息 '''
msg = bus.recv(100)
try:
# bus.send(msg)
# print("canrecv:{} {}".format(msg.arbitration_id, msg.data[0]))
# 解析必须为本机id:0x01
if msg.arbitration_id == 0x01:
# 巡检命令 : 0x06
if msg.data[0] == 0x06:
da = []
# 遥测
da.append(0x01)
k = random.random()
yc[0] = int(0x500 * k)
k = random.random()
yc[1] = int(0x500 * k)
da.append(int(yc[0] / 256))
da.append(int(yc[0] % 256))
da.append(int(yc[1] / 256))
da.append(int(yc[1] % 256))
self.send_msg(0x01, da)
print("send yc:{}".format(msg))
da.clear()
# 遥信
da.append(0x02)
k = random.random()
if k < 0.5:
yx[0] = 0
else:
yx[0] = 1
k = random.random()
if k < 0.5:
yx[1] = 0
else:
yx[1] = 1
k = random.random()
if k < 0.5:
yx[2] = 0
else:
yx[2] = 1
k = random.random()
if k < 0.5:
yx[3] = 0
else:
yx[3] = 1
da.append(yx[0])
da.append(yx[1])
da.append(yx[2])
da.append(yx[3])
self.send_msg(0x01, da)
print("send yx:{}".format(msg))
# print(msg.data[0]) # 接收回来的第一个字节的数据
# print(msg.arbitration_id) # 接收回来的ID
# return msg
except can.CanError:
print("Message NOT sent")
提供模拟量和开关量状态,通过产生随机数来填充设备的模拟量缓冲区和开关量缓冲区,这样当主机巡检的时候,设备端可以上送变化的数据量。
检查巡检命令是否为本机地址,类中检查巡检命令,如果巡检命令为模拟的设备id,当前模拟的设备就会做出响应,否则不响应。
实际运行效果展示
编译后,将生成的主机程序上传到ls2k500系统中,并执行。实际程序输出如下。
运行python设计的模拟设备端程序,在接收到主机发送给的巡检命令后,上送模拟量和开关量信息。
在ls2k500系统中和PC上分别运行一个CAN总线监视器,可以观察到主机和设备发送的数据,印证了主辅机交互的数据。
实践证明,CAN总线可以正确完成数据收发,主辅机运行正常,数据收发正常。