本文以CANOPEN协议对接电机为例,大概讲解一下驱动的原理

首先介绍一下每个文件的作用
- candev.lua 是can驱动的文件。
- opencan.lua 是opencan协议解析。
- readmotor.lua 定时发送读取电机寄存器报文。
- recmotor.lua 解析读取到的电机速度和编码器,转变成里程计的部分。[重要]
- setvel.lua 接受控制器发过来的电机速度,转换成电机需要的速度,发到电机上。[重要]
- settheta.lua 接受控制器发过来的电机角度,转换成电机需要的电机位置。[重要]
setvel.lua 部分代码
--循环发送电机速度
while true do
	local data = veldev:read()
	if data ~= nil and data.msg ~= nil then
		rtime = 0
		if data.msg.wheelvel ~= nil then
			local vel = data.msg.wheelvel * 60.0 * para.walk_dir * para.walk_ratio
			rbasetime = rbasetime + 1
			if rbasetime > 100 then
				rbasetime = 0
				--读取IO信息
				readBase()
			end
			
			if motorLogic.setStop == true then
				vel = 0
				print("stop")
			end
			
			print("motor vel : "..data.msg.wheelvel..'\t'..vel)
			setMotoVel(vel)
		end
	else
		rtime = rtime + rdelay
		rbasetime = rbasetime + rdelay
		
		--超时没有接受到电机转速,则重新加入一次域数据
		if rtime > 1000 then
			rtime = 0
			readBase()
			setMotoVel(0.0)
			veldev:addRegion("setwheelvel")
			print("timeout")
		end
		
	end
end
其中最为重要是电机转速单位
--data.msg.wheelvel 为控制内部发出来的电机速度,单位是转/秒 r/s 相对机器人方向往前为正,往后为负
--para.walk_dir 轮子实际的方向,该值一般是1或者-1
--para.walk_ratio 轮子转速比,控制器是以机器人行驶轮的单位为标准,电机还需要通过一轮减速比限速
--60代表秒转换分钟,大部分的电机是以RPM为单位,即是单位 转每分钟
--最终得出电机的实际转速
--不同电机的单位不一样,多数驱动代码只改此部分
local vel = data.msg.wheelvel * 60.0 * para.walk_dir * para.walk_ratio
settheta.lua部分代码
while true do
	local data = veldev:read()
	if data ~= nil and data.msg ~= nil then
		rtime = 0
		if data.msg.wheelTheta ~= nil then
			local position = (data.msg.wheelTheta / (math.pi * 2.0)) * para.wheel_encode * para.speedratio
			controlTheta = data.msg.wheelTheta
			
			rbasetime = rbasetime + 1
			if rbasetime > 100 then
				rbasetime = 0
				--读取IO信息
				readBase()
			end
			
			print("motor position : "..data.msg.wheelTheta..'\t'..position)
			setMotoPosition(position)
		end
	else
		rtime = rtime + rdelay
		rbasetime = rbasetime + rdelay
		
		--超时没有接受到电机转速,则重新加入一次域数据
		if rtime > 300 then
			rtime = 0
			readBase()
			veldev:addRegion("setwheelvel")
			print("timeout")
		end
		
	end
end
其中最为重要是电机转速单位
--data.msg.wheelTheta 为控制内部发出来的电机角度,单位是弧度,范围是[-PI,PI]。
--para.speedratio轮子转速比,控制器是以机器人行驶轮的单位为标准,电机还需要通过一轮减速比限速
--para.wheel_encode 轮子自身转一圈需要的编码数
--最终得出电机的实际转速
--不同电机的单位不一样,多数驱动代码只改此部分
local position = (data.msg.wheelTheta / (math.pi * 2.0)) * para.wheel_encode * para.speedratio
recmotor.lua部分代码,该部分则是把电机的速度和编码器上传到控制器算法上。
注意单位转换,上传的单位是编码器转一圈的单位。速度则是转/秒 r/s
--获取can的数据
while true do
	--读取CAN接受到的数据,100毫秒超时一次
	local rarr = candev.read()
	if rarr ~= nil then
		for i = 1,8,1 do
			if rarr[i] == nil then
				rarr = nil
				break
			end
		end
	end
	
	if rarr ~= nil and rarr["stdid"] ~= nil then
	
	if rarr ~= nil and #rarr >= 8 and rarr["stdid"]  == 0x181 then
		local data = rarr[5] + bit.lshift(rarr[6],8) + bit.lshift(rarr[7],16) + bit.lshift(rarr[8],24)
		motorLogic.encode[para.wheel_id] = limit32Bit(data)
		motorLogic.encodeRec[para.wheel_id] = true
	end
	
	if rarr ~= nil and #rarr >= 8 and rarr["stdid"]  >= 0x581 and rarr["stdid"] <= 0x587 then
		--解析can中的数据
		candel:readReceive(rarr["stdid"],rarr)
		
		local id = candel.read.id - 0x580
		--存放电机位置
		if candel.read.index == 0X28B0 and candel.read.sunindex == 0x04 then
			motorLogic.encode[para.walk_id] = candel.read.data
			motorLogic.encodeRec[para.walk_id] = true
		end
		--存放电机速度
		if candel.read.index == 0X28B0 and candel.read.sunindex == 0x03 then
			motorLogic.gvel[para.walk_id] = candel.read.data
			motorLogic.gvelRec[para.walk_id] = true
		end
		--存放电机位置
        if candel.read.index == 0x6063 then
            motorLogic.encode[id] = candel.read.data
            motorLogic.encodeRec[id] = true
        end
        --存放电机速度
        if candel.read.index == 0x606c then
            motorLogic.gvel[id] = candel.read.data
            motorLogic.gvelRec[id] = true
        end
	end
	end
	--如果电机编码器都更新了,则更新里程计
	if motorLogic.encodeRec[para.walk_id] == true and
	   motorLogic.encodeRec[para.wheel_id] == true
	 then
		
		motorLogic.encodeRec[para.walk_id] = false
		motorLogic.encodeRec[para.wheel_id] = false
		
		local dis1 = limit32Bit(motorLogic.encode[para.wheel_id])
		local dis2 = limit32Bit(motorLogic.encode[para.walk_id])
		
	   --上传两轮里程计
		local msg = {}
		msg.wheelTheta = dis1 / para.speedratio
		currentTheta = (msg.wheelTheta / para.wheel_encode) * math.pi * 2.0
		
		msg.wheelTheta = currentTheta 
		msg.encode = limit32Bit(motorLogic.encode[para.walk_id]) / para.walk_ratio
		--print("wheelencode : "..msg.wheelTheta..'\t'..msg.encode)
		veldev:write("wheelencode",msg)
	end
	--如果电机的速度都更新了,则更新速度
	if motorLogic.gvelRec[para.walk_id] == true or
		motorLogic.gvelRec[para.wheel_id] == true
		then
		motorLogic.gvelRec[para.walk_id] = false
		motorLogic.gvelRec[para.wheel_id] = false
		
		local msg = {}
		msg.wheelTheta = currentTheta
		msg.vel = limit32Bit(motorLogic.gvel[para.walk_id]) / (para.walk_ratio * 60.0 * 2730.0)
		--print("vel : "..msg.wheelTheta..'\t'..msg.vel)
		--上传两轮速度
		veldev:write("wheelvel",msg)
	end
end
以下是电机模块的配置参数describe.json
- startup 需要上电自启动的脚本
- describe 该功能包的描述
- speedratio 行走电机转速比
- speedratio 转向电机转速比
- encodeValue 行走电机转一圈的编码值
- wheel_encode 转向电机转一圈的编码值
- walk_id 行走电机ID
- walk_dir 行走电机运行方向
- wheel_id 转向电机1ID
- bps 电机波特率
- dev 电机接到CAN1上还是CAN2上
- stopButton 急停接到的IO上[0-15]
- stopTrigger 急停触发的电平[0 1] -1失效
- resetButton 复位按钮接到的IO上[0-15]
- resetTrigger 复位按钮触发的电平[0-1]  -1失效
- antiCollision 触碰防撞接到的IO上[0-15]
- antiCollisionTrigger 触碰防撞的触发电平[0-1]  -1失效
- startIO 电机模块启动需要打开的IO[0-15]
- enable 是否使能改模块,true使能 false失能
{
	"startup": [
		"readmotor.lua",
		"recmotor.lua",
		"setVel.lua",
		"setTheta.lua"
	],
	"describe": "opencan协议",
	"speedratio": 175,
	"wheel_encode": 10000,
	"wheel_id": 7,
	"wheel_dir": 1,
	"walk_id": 1,
	"walk_dir": 1,
	"walk_ratio": 30,
	"walk_encode": 10000,
	"bps": 500,
	"dev": "CAN1",
	"stopButton": 15,
	"stopTrigger": 0,
	"resetButton": 14,
	"resetOut": 14,
	"resetTrigger": 0,
	"antiCollision": -1,
	"antiCollisionTrigger": 0,
	"startIO": 9,
	"enable": true
}