串口总线舵机之舵机命令
前言:关于一些前期配置,测试情况请观看下面这个博客驱动串口总线舵机文章目录一、舵机指令包格式二、串口舵机连接1.硬件连接2.74HC126D三、关于ctypes四、串口舵机命令代码从今天开始学习幻尔科技总线舵机通信协议一、舵机指令包格式帧头: 连续收到两个 0x55 ,表示有数据包到达。ID:每个舵机都有一个 ID 号。ID 号范围 0~253,转换为十六进制 0x00~0xFD。广播 ID: I
前言:
关于一些前期配置,测试情况请观看下面这个博客
驱动串口总线舵机
从今天开始学习
幻尔科技总线舵机通信协议
关于这部分在SerialServoCmd文件里
一、舵机指令包格式
帧头: 连续收到两个 0x55 ,表示有数据包到达。
ID: 每个舵机都有一个 ID 号。ID 号范围 0~253,转换为十六进制 0x00~0xFD。广播 ID: ID 号 254(0xFE) 为广播 ID,若控制器发出的 ID 号为 254(0xFE),所有的舵机均接收指令,但都不返回应答信息,(读取舵机 ID 号除外,具体说明参见下面指令介绍)以防总线冲突。
数据长度: 等于待发送的数据(包含本身一个字节)长度,即数据长度 Length加 3 等于这一包指令的长度,从帧头到校验和。
指令: 控制舵机的各种指令,如位置、速度控制等。
参数: 除指令外需要补充的控制信息。
校验和: 校验和 Checksum,计算方法如下:
Checksum = ~ (ID + Length + Cmd+ Prm1 + … Prm N)若括号内的计算和超出 255,则取最低的一个字节,“~”表示取反。
说明:
数据长度为 数据长度+指令+ 参数+校验和 参数个数 = 数据长度-3
二、串口舵机连接
1.硬件连接
2.74HC126D
74HC126D介绍:
功能描述:
可以看出,当OE输出高电平时 输入是高电平那么输出就是高电平,输入是低电平输出就是低电平。
当OE为低电平时,不管输入状态是什么,输出都是高阻抗关断状态(抽象理解为悬空)。
高阻输出一般是指数字电路输出时不为高电平或低电平,而是相当于断开的一种状态,输出点的电位由后面的电路决定。
这个芯片的作用就是,当需要写入的时候,拉低TX_CON,这样,串口TX发送什么,输出就是什么。拉高RX_CON,这样,串口接收RX就相当于悬空,什么也不干。接收数据也是如此.
三、关于ctypes
关于ctypes的介绍
ctypes
四、串口舵机命令代码
# 串口舵机的命令
#!/usr/bin/python3
# encoding: utf-8
import serial # 导入串口库
import pigpio # 导入pigpio库 由c语言编写的库函数 并提供python接口
import time # 导入时间库
import ctypes # ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用C DLL中的函数。
LOBOT_SERVO_FRAME_HEADER = 0x55 # 机架头
LOBOT_SERVO_MOVE_TIME_WRITE = 1 # 移动时间写入
LOBOT_SERVO_MOVE_TIME_READ = 2 # 移动时间读
LOBOT_SERVO_MOVE_TIME_WAIT_WRITE = 7 # 移动时间等待写
LOBOT_SERVO_MOVE_TIME_WAIT_READ = 8 # 移动时间等待读
LOBOT_SERVO_MOVE_START = 11 # 移动开始
LOBOT_SERVO_MOVE_STOP = 12 # 移动停止
LOBOT_SERVO_ID_WRITE = 13 # 舵机ID写
LOBOT_SERVO_ID_READ = 14 # 舵机ID读
LOBOT_SERVO_ANGLE_OFFSET_ADJUST = 17 # 角度偏移调整
LOBOT_SERVO_ANGLE_OFFSET_WRITE = 18 # 角度偏移写
LOBOT_SERVO_ANGLE_OFFSET_READ = 19 # 角度偏移读
LOBOT_SERVO_ANGLE_LIMIT_WRITE = 20 # 角度限制写
LOBOT_SERVO_ANGLE_LIMIT_READ = 21 # 角度限制读
LOBOT_SERVO_VIN_LIMIT_WRITE = 22 # VIN限制写
LOBOT_SERVO_VIN_LIMIT_READ = 23 # VIN限制读
LOBOT_SERVO_TEMP_MAX_LIMIT_WRITE = 24 # TEMP最大限度写
LOBOT_SERVO_TEMP_MAX_LIMIT_READ = 25 # TEMP最大限度读
LOBOT_SERVO_TEMP_READ = 26 # TEMP读
LOBOT_SERVO_VIN_READ = 27 # VIN读
LOBOT_SERVO_POS_READ = 28 # POS读
LOBOT_SERVO_OR_MOTOR_MODE_WRITE = 29 # 模式写
LOBOT_SERVO_OR_MOTOR_MODE_READ = 30 # 模式读
LOBOT_SERVO_LOAD_OR_UNLOAD_WRITE = 31 # 加载或卸载写
LOBOT_SERVO_LOAD_OR_UNLOAD_READ = 32 # 加载或卸载读
LOBOT_SERVO_LED_CTRL_WRITE = 33 # LED控制写
LOBOT_SERVO_LED_CTRL_READ = 34 # LED控制读
LOBOT_SERVO_LED_ERROR_WRITE = 35 # LED错误写
LOBOT_SERVO_LED_ERROR_READ = 36 # LED错误读
pi = pigpio.pi() # 初始化pigpio库,创建一个实例
# ser=serial.Serial("/dev/ttyUSB0",9600,timeout=0.5) #使用USB连接串行口
# ser=serial.Serial("/dev/ttyAMA0",9600,timeout=0.5) #使用树莓派的GPIO口连接串行口
serialHandle = serial.Serial("/dev/ttyAMA0", 115200) # 初始化串口, 波特率为115200
# 使用pigpio配置驱动串口的引脚模式为输出
def portInit(): # 配置用到的IO口
# 说明 pigpio使用的是BCM编码
# RX_CON和TX_CON是使能信号 来决定TX还是RX输出servo signal
pi.set_mode(17, pigpio.OUTPUT) # 配置RX_CON 即 GPIO17 为输出
pi.write(17, 0)# GPIO 17 低电平
pi.set_mode(27, pigpio.OUTPUT) # 配置TX_CON 即 GPIO27 为输出
pi.write(27, 1) # GPIO 27 高电平
portInit() # 执行端口初始化
# 配置为串口写模式
def portWrite(): # 配置单线串口为输出
pi.write(27, 1) # 拉高TX_CON 即 GPIO27 串口发送的是什么输出就是什么
pi.write(17, 0) # 拉低RX_CON 即 GPIO17 相当于悬空接受引脚,什么都不干
# 配置为串口读模式
def portRead(): # 配置单线串口为输入
pi.write(17, 1) # 拉高RX_CON 即 GPIO17
pi.write(27, 0) # 拉低TX_CON 即 GPIO27 悬空
# 复位,重新打开串口
def portRest(): # 端口复位
time.sleep(0.1) # 延迟100us
serialHandle.close()# 关闭串口
pi.write(17, 1)
pi.write(27, 1)
serialHandle.open() # 打开串口
time.sleep(0.1)
# 校验和 = ~(ID + Length+ Cmd + pr1+ .. + prn) 若超出255,则取最低的一个字节
def checksum(buf):
# 计算校验和
sum = 0x00
for b in buf: # 求和
sum += b # 累加
sum = sum - 0x55 - 0x55 # 去掉命令开头的两个 0x55
sum = ~sum # 取反
return sum & 0xff # 取最低的一个字节
# 串口舵机写命令
# 指令包格式:0x55,0x55 ID号 数据长度,指令,参数1...参数n,校验和
# 数据长度等于待发送的数据(包含本身1个字节)
def serial_serro_wirte_cmd(id=None, w_cmd=None, dat1=None, dat2=None):
portWrite() # 端口写
# bytearray() 方法返回一个新字节数组。这个数组里的元素是可变的,并且每个元素的值范围: 0 <= x < 256
'''
如果 source 为整数,则返回一个长度为 source 的初始化数组;
如果 source 为字符串,则按照指定的 encoding 将字符串转换为字节序列;
如果 source 为可迭代类型,则元素必须为[0 ,255] 中的整数;
如果 source 为与 buffer 接口一致的对象,则此对象也可以被用于初始化 bytearray。
如果没有输入任何参数,默认就是初始化数组为0个元素。
'''
# b'\x55\x55') [0x55,0x55]
buf = bytearray(b'\x55\x55') # 帧头 buf = [0x55,0x55]
buf.append(id) # buf = [0x55,0x55,id]
# 指令长度
if dat1 is None and dat2 is None:# dat1和dat2都为空
buf.append(3) # buf = [0x55,0x55,id,3] 3个表示指令长度 指令 检验和
elif dat1 is not None and dat2 is None: # dat1不为空dat2为空
buf.append(4) # buf = [0x55,0x55,id,4] 4个表示指令长度,指令 dat1(低8位),校验和
elif dat1 is not None and dat2 is not None: #dat1 和dat2都不为空
buf.append(7) # buf = [0x55,0x55,id,7] 7个表示指令长度,指令,dat1(高8位,低8位) dat2(高8位,低8位),校验和
buf.append(w_cmd) # 把指令也添加到列表 # buf = [0x55,0x55,id,x,w_cmd]
# 写数据
if dat1 is None and dat2 is None: # dat1和dat2都为空
pass # buf = [0x55,0x55,id,x,w_cmd]
elif dat1 is not None and dat2 is None: # dat1不为空dat2为空
# (dat1 & 0xff) 取最低位
buf.append(dat1 & 0xff) # buf = [0x55,0x55,id,x,w_cmd,dat1]
elif dat1 is not None and dat2 is not None: # dat1 和 dat2 都不为空
buf.extend([(0xff & dat1), (0xff & (dat1 >> 8))]) # 分低8位 高8位 放入缓存
buf.extend([(0xff & dat2), (0xff & (dat2 >> 8))]) # 分低8位 高8位 放入缓存
# 可能是buf = [0x55,0x55,id,x,w_cmd,dat1,dat2]
# 校验和
buf.append(checksum(buf)) # 到这数据有三种情况
serialHandle.write(buf) # 发送给串口
# 串口舵机读命令 先发送读命令 再接收,不单独使用
def serial_servo_read_cmd(id=None, r_cmd=None):
portWrite() # 端口写
buf = bytearray(b'\x55\x55') # 帧头
buf.append(id)# 添加舵机ID到列表
buf.append(3) # 指令长度
buf.append(r_cmd) # 指令
buf.append(checksum(buf)) # 校验和
serialHandle.write(buf) # 发送
time.sleep(0.00034) # 延迟 3.4us
# 获取指定读取命令的数据
def serial_servo_get_rmsg(cmd):
serialHandle.flushInput() # 清空接收缓存
portRead() # 将单线串口配置为输入
time.sleep(0.005) # 稍作延时,等待接收完毕
count = serialHandle.inWaiting() # 获取接收缓存中的字节数
if count != 0: # 如果接收到的数据不空
recv_data = serialHandle.read(count) # 读取count字节个数据
try:
# recv_data[2] : id recv_data[3]: 数据长度
if recv_data[0] == 0x55 and recv_data[1] == 0x55 and recv_data[4] == cmd: # 帧头正确 命令符合 匹配
dat_len = recv_data[3] # 数据长度
serialHandle.flushInput() # 清空接收缓存
# 数据长度为 数据长度+指令+ 参数+校验和 参数个数 = 数据长度-3
if dat_len == 4: # dat1为一个8位的参数dat2为空
# print ctypes.c_int8(ord(recv_data[5])).value # 转换成有符号整型
return recv_data[5] # 返回这个8位的参数
elif dat_len == 5: # dat1为一个16位的参数dat2为空
pos = 0xffff & (recv_data[5] | (0xff00 & (recv_data[6] << 8))) # 一个16位的数据
return ctypes.c_int16(pos).value
elif dat_len == 7: # dat1位16位的参数,dat2为16位的参数
pos1 = 0xffff & (recv_data[5] | (0xff00 & (recv_data[6] << 8)))
pos2 = 0xffff & (recv_data[7] | (0xff00 & (recv_data[8] << 8)))
return ctypes.c_int16(pos1).value, ctypes.c_int16(pos2).value
else: # 数据不正确,不符合接收数据的格式
return None
except BaseException as e:
print(e) # 打印异常
else: # 接收数据为空
serialHandle.flushInput() # 清空接收缓存
return None
更多推荐
所有评论(0)