续上文,mcush库中ShellLabCAN类完成了can命令的封装,让我们能用Python代码控制到底层总线,但这还不够。CAN的应用协议层是非常开放的,各行各业总结出了不少行业标准,如果能再抽象封装一层,直接调用行业标准接口,就能无缝对接现有的众多优秀产品。
这篇介绍的CANopen就是非常优秀的协议,设计灵活性和扩展性都很好,由于有一个官方非盈利性组织CiA(CAN in Automation)的运作,已发布了在众多行业领域的应用规范模板,参照这些规范诞生了大量的接口统一的工业产品,因此也成了事实上的行业标准。
本篇不对CANopen的细节展开,有兴趣请自行搜索相关资料,也可参考知乎上一些优秀的教程介绍,例如:CANOpen系列教程zhuanlan.zhihu.com
回到mcush库,从ShellLabCAN类派生出的ShellLabCANopen类,通过调用底层读写函数实现了用SDO报文控制对象字典,对于固定类型(字节、双字节、长整数、浮点)的对象,控制起来就非常方便了。
下面是Shell Lab测试台软件自带的示例“CANopen scan”,用于扫描所有设备:
s = ShellLabCANopen(PORT)
#s.canBaudrate( 1000000 )
s.canResetInput() # clear all pending messages
getLogPanel(switch=True, clear=True)
# scan all CAN-IDs
found = []
for id in range(1, 0x7F):
info( 'Scan ID:%d'% id )
# send node guarding request
s.writeNodeGuardRequest( id )
#time.sleep(0.1)
# check for response
for cid, ext, rtr, dat in s.canRead():
#logAdd( 'id=0x%X, ext=%d, rtr=%d, dat=%s'% (cid, ext, rtr, dat) )
if ((cid&0x780)==NDGRD) and (not rtr) and (len(dat)==1):
status = s2B(dat[0]) & 0x7F
if status == STATUS_PRE_OPERATIONAL:
status_str = 'pre_operational'
elif status == STATUS_OPERATIONAL:
status_str = 'operational'
elif status == STATUS_STOPPED:
status_str = 'stopped'
elif status == STATUS_CONNECTING:
status_str = 'connecting'
else:
status_str = 'unknown'
logAdd( 'id=0x%X, status=0x%X,%s'% (cid&0x7F, status, status_str) )
found.append( cid&0x7F )
info( 'Found%ddevices'% len(found) )
for id in found:
logAdd( '===== CAN-ID 0x%Xdevice info ====='% id )
val = s.readUINT32(id, 0x1000, 0)
logAdd( 'device type: 0x%08X(%d)'% (val, val&0xFFFF) )
val = s.readUINT8(id, 0x1001, 0)
logAdd( 'device error: 0x%02X'% val )
val = s.readUINT32(id, 0x1002, 0)
logAdd( 'status: 0x%08X'% val )
val = s.readObject(id, 0x1008, 0)
logAdd( 'device name:%s'% val )
val = s.readObject(id, 0x1009, 0)
logAdd( 'hardware ver:%s'% val )
val = s.readObject(id, 0x100A, 0)
logAdd( 'software ver:%s'% val )
val = s.readUINT16(id, 0x100C, 0)
logAdd( 'guard time: 0x%04X'% val )
val = s.readUINT8(id, 0x100D, 0)
logAdd( 'life time factor: 0x%02X'% val )
val = s.readUINT32(id, 0x1018, 1)
logAdd( 'vendor id: 0x%08X'% val )
val = s.readUINT32(id, 0x1018, 2)
logAdd( 'product code: 0x%08X'% val )
val = s.readUINT32(id, 0x1018, 3)
logAdd( 'revision number: 0x%08X'% val )
val = s.readUINT32(id, 0x1018, 4)
logAdd( 'serial number: 0x%08X'% val )
代码先是扫描所有ID,发送网络管理报文,等待对方回应,解析回应报文,获取对方设备状态(正常、停止、设置)。然后对扫描到的设备依次“体检”:对常规项(设备类型、软硬件版本号等)查询一遍,并打印出日志。
连上一块CANopen数字IO卡,扫描结果如下:
对这块数字量输出卡,下面的示例脚本循环将所有IO输出端口翻转:
ID = 0x20 # 必须指定从设备地址
s = ShellLabCANopen(PORT)
#s.canBaudrate( 1000000 )
#s.resetNode( ID )
s.startNode( ID ) # 确认进入工作状态
counter = 0
bytes = s.readUINT8(ID, 0x6200, 0) # 读出端口对应多少个控制字节
while True:
info( 'Count:%d'% counter )
#s.writeRPDO1( ID, '\xFF' ) # 通过PDO方式控制开
for i in range(bytes):
s.writeUINT8(ID, 0x6200, i+1, 0xFF) # 控制所有字节,打开各个字节对应的8个端口
time.sleep(0.5)
#s.writeRPDO1( ID, '\x00' ) # 通过PDO方式控制关
for i in range(bytes):
s.writeUINT8(ID, 0x6200, i+1, 0x00) # 控制所有字节,关闭各个字节对应的8个端口
time.sleep(0.2)
counter += 1
上面的代码用SDO报文控制相应的输出对象字典,实现了所有端口的翻转,除此之外也能使用RPDO消息报文来控制,效率会更高(但各个设备定义可能不同)。
同理也可以读取数字输入板卡的输入对象字典,获取输入端口的状态。
注:
mcush库安装方式:sudo pip install mcush