测试环境
CubeMX
MDK5 IDE
STM32F429IGT6 芯片
正点原子开发板 阿波罗
STM32F103C8T6 芯片
Stm32最小系统板
RT-Thread操作系统
面向对象模块接口设计
LPD3806-400BM-G5-24C 脉冲AB相增量式光电旋转编码器
大疆M3508直流无刷电机3
大疆C620无刷电机调速器3
两个3508电机用来驱动摩擦轮,仅速度控制
一个3508电机用来驱动取球爪子,角度控制
但由于3508电机位置控制的问题配合编码器读取爪子转动角度
原因:https://www.yuque.com/wangxi_chn/qaxke0/ulrbrk#ZzYzP
硬件条件
- 因为使用的是正点原子开发板,芯片引脚被多处复用,定时器的两个引脚无法正常读取脉冲数(如果使用的是实验室自己绘制的核心板就不会有这个问题)
- 所以这里借用了一个Stm32最小系统板只用来读取编码器,然后通过串口把数据发送给开发板
两块板通过串口线连接,同时正点原子开发板为最小系统板供电,共地
代码工程
stm32f103_MIN.rar
stm32f429_ALIENTEX.rar
工程基础:
LED频闪指示灯模块: https://www.yuque.com/wangxi_chn/qaxke0/mltilp
DJIC610620电机群模块:https://www.yuque.com/wangxi_chn/qaxke0/ulrbrk
蓝牙驱动遥控模块:https://www.yuque.com/wangxi_chn/qaxke0/dmwgwv
串口板级通信模块:https://www.yuque.com/wangxi_chn/qaxke0/xpkyvm
ROTARY编码器驱动模块:https://www.yuque.com/wangxi_chn/qaxke0/ketr3p工程组成
-
STM32F429主控 main
初始化配置
MODULE_LED dev_led_state =
{
.pin = GET_PIN(B, 0),
.LED_TIME_CYCLE = 4000,
.LED_TIME_OUTPUT = 200
};
频闪系统运行状态显示灯
MODULE_BLUETOOTHHC06 dev_communicate =
{
.Property_UartDevName = "uart2",
};
蓝牙模块
MODULE_BSPCOMMUNICATE dev_BspCommunicate =
{
.Property_UartDevName = "uart3",
};
串口板级通信模块
和最小系统板通信,读取编码器信息
MODULE_DjiC610620GROUP dev_DjiC610620group =
{
.Property_CanDevName = "can1",
.Value_module_DjiC610620[DjiC610620ID_1] =
{
.Enable = 1,
.Mode = DjiC610620MODE_SPEED,
.ENCODER = DjiC610620SELF,
.PID_Speed =
{
.Property_Kp = 6, .Property_Ki = 2, .Property_Kd = 0, .Property_dt = 0.005,
.Property_integralMax = 500, .Property_AimMax = 9000, .Property_OutputMax =16300,
.Property_integralErrMax = 500,
}
},
.Value_module_DjiC610620[DjiC610620ID_2] =
{
.Enable = 1,
.Mode = DjiC610620MODE_SPEED,
.ENCODER = DjiC610620SELF,
.PID_Speed =
{
.Property_Kp = 6, .Property_Ki = 2, .Property_Kd = 0, .Property_dt = 0.005,
.Property_integralMax = 500, .Property_AimMax = 9000, .Property_OutputMax =16300,
.Property_integralErrMax = 500,
}
},
.Value_module_DjiC610620[DjiC610620ID_3] =
{
.Enable = 1,
.Mode = DjiC610620MODE_ANGLE,
.ENCODER = ROTARYENCODER,
.PID_Speed =
{
.Property_Kp = 6, .Property_Ki = 1, .Property_Kd = 0, .Property_dt = 0.005,
.Property_integralMax = 2000, .Property_AimMax = 9000, .Property_OutputMax =16300,
.Property_integralErrMax = 1000,
},
.PID_Angle =
{
.Property_Kp = 2, .Property_Ki = 0.5, .Property_Kd = 0, .Property_dt = 0.005,
.Property_integralMax = 2000, .Property_AimMax = 60000, .Property_OutputMax =1000,
.Property_integralErrMax = 10000,
},
},
};
电机群模块
- 分别使能三个ID为1、2、3的3508电机
- 1、2为速度模式,3为角度模式
- 配置1,2电机的速度控制PID,3电机的角度控制PID
- 注意角度控制PID PID_Angle 的 Property_OutputMax 即为达到目标角度的速度大小
- 注意设置电机的编码来源:电机1和2使用自身编码器,电机3使用ROTARY编码器
这里ROTARY编码器没有集成在里面,如果 .ENCODER = ROTARYENCODER, 需要自己手动实现对电机角度的赋值,可看后面内容
Module_Led_Config(&dev_led_state);
dev_led_state.Set(&dev_led_state,9);
Module_DjiC610620Group_Config(&dev_DjiC610620group);
Module_BlueToothHC06_Config(&dev_communicate);
Module_BspCommunicate_Config(&dev_BspCommunicate);
使能配置
/* system running shine led thread */
rt_thread_t led_thread = rt_thread_create("ledshine", led_shine_entry, RT_NULL,
512, RT_THREAD_PRIORITY_MAX - 3, 20);
if (led_thread != RT_NULL){
rt_thread_startup(led_thread);
}
/* motor control thread */
rt_thread_t motor_thread = rt_thread_create("motor", motor_entry, RT_NULL,
1024, RT_THREAD_PRIORITY_MAX - 6, 20);
if (motor_thread != RT_NULL){
rt_thread_startup(motor_thread);
}
/* blue tooth updata thread */
rt_thread_t bluetupdata_thread = rt_thread_create("bluetupdata", bluetupdata_entry, RT_NULL,
512, RT_THREAD_PRIORITY_MAX - 5, 20);
if (bluetupdata_thread != RT_NULL){
rt_thread_startup(bluetupdata_thread);
}
/* blue tooth control thread */
rt_thread_t bluetcontrol_thread = rt_thread_create("bluetcontrol", bluetcontrol_entry, RT_NULL,
512, RT_THREAD_PRIORITY_MAX - 5, 20);
if (bluetcontrol_thread != RT_NULL){
rt_thread_startup(bluetcontrol_thread);
}
/* bsp communicate thread */
rt_thread_t bspCommnicate_thread = rt_thread_create("bspCommnicate", bspCommnicate_entry, RT_NULL,
512, RT_THREAD_PRIORITY_MAX - 6, 20);
if (bspCommnicate_thread != RT_NULL){
rt_thread_startup(bspCommnicate_thread);
}
配置线程
static void motor_entry(void *parameter)
{
while (1)
{
//get data from outside
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].Value_motor_TotalAngle =
-(dev_BspCommunicate.Value_RotaryEncoder * 8);
/* Communicate with motor group */
dev_DjiC610620group.Method_Feed(&dev_DjiC610620group);
rt_thread_mdelay(1);
dev_DjiC610620group.Method_Send(&dev_DjiC610620group);
}
}
在电机线程中,实现读取报文,计算PID,发送报文
- 注意这里电机3的角度从编码器中获得,即从板级通信中获得,角度值已在板级通信的线程中更新
- 加上负号是为了和电机自带的编码器角度读数方向一致,避免电机PID逻辑错误
乘上了8是为了让编码器获得的脉冲数和电机自带编码器的数量级一致,方便统一PID参数的大小
static void bluetcontrol_entry(void *parameter)
{
static rt_uint32_t time_1000m = 0;
while(1)
{
rt_thread_mdelay(1);
time_1000m++;
switch(dev_communicate.Value_keyMask)
{
case (0x0001<<0): //speed up the friction wheel
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_1].Value_motor_AimRPM -= 200;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_2].Value_motor_AimRPM += 200;
break;
case (0x0001<<1): //start up the friction wheel
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_1].Value_motor_AimRPM = -2000;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_2].Value_motor_AimRPM = 2000;
break;
case (0x0001<<2): //slow down the friction wheel
if(dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_2].Value_motor_AimRPM >= 200)
{
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_1].Value_motor_AimRPM += 200;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_2].Value_motor_AimRPM -= 200;
}
break;
case (0x0001<<3): //raise up the paw
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Property_Kp = 3;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Property_Ki = 0.5;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Speed.Value_integral = 0;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Value_integral = 0;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Property_integralErrMax = 1000;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Property_OutputMax = 1500;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].Value_motor_AimAngle = -2200;
break;
case (0x0001<<4): //stop the friction wheel
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_1].Value_motor_AimRPM = 0;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_2].Value_motor_AimRPM = 0;
break;
case (0x0001<<5): //put down the paw
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Property_Kp = 3;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Speed.Property_Kp = 4;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Property_Ki = 0.1;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Speed.Value_integral = 0;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Value_integral = 0;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Property_integralErrMax = 30;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].PID_Angle.Property_OutputMax = 300;
dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].Value_motor_AimAngle = 0;
break;
case (0x0001<<6):
break;
case (0x0001<<7):
break;
case (0x0001<<8):
break;
case (0x0001<<9):
break;
case (0x0001<<10):
break;
case (0x0001<<11):
break;
}
dev_communicate.Value_keyMask = 0x0000;
if(time_1000m >= 1000)
{
time_1000m = 0;
/* 可以在这里数据回显,打印等,可调整发送频率 */
// dev_communicate.Method_Send(&dev_communicate,PARAM_SHOW_1,
// dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_2].Value_motor_AimRPM);
// dev_communicate.Method_Send(&dev_communicate,WAVE_SHOW,
// dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_2].Value_motor_AimRPM);
// rt_device_read( dev_DjiC610620group.Value_module_DjiC610620[DjiC610620ID_3].Encoder_dev, 0,
// &count, 1);
// rt_kprintf("get count %d\n",count);
}
}
}
通过蓝牙按键实现动作控制
初始化配置
MODULE_BSPCOMMUNICATE dev_BspCommunicate =
{
.Property_UartDevName = "uart2",
};
串口板级通信模块
int main(void)
{
rt_err_t ret = RT_EOK;
rt_device_t pulse_encoder_dev = RT_NULL; /* 脉冲编码器设备句柄 */
/* 查找脉冲编码器设备 */
pulse_encoder_dev = rt_device_find(PULSE_ENCODER_DEV_NAME);
if (pulse_encoder_dev == RT_NULL)
{
rt_kprintf("pulse encoder sample run failed! can't find %s device!\n", PULSE_ENCODER_DEV_NAME);
return RT_ERROR;
}
/* 以只读方式打开设备 */
ret = rt_device_open(pulse_encoder_dev, RT_DEVICE_OFLAG_RDONLY);
if (ret != RT_EOK)
{
rt_kprintf("open %s device failed!\n", PULSE_ENCODER_DEV_NAME);
return ret;
}
rt_pin_mode(GET_PIN(B, 6), PIN_MODE_INPUT_PULLUP);
rt_pin_mode(GET_PIN(B, 7), PIN_MODE_INPUT_PULLUP);
这里没有将编码器功能封装成模块,直接使用
rt_pin_mode(GET_PIN(C, 13), PIN_MODE_OUTPUT);
发送状态指示灯
Module_BspCommunicate_Config(&dev_BspCommunicate);
使能板级通信模块
while(1)
{
rt_thread_mdelay(1);
/* 读取脉冲编码器计数值 */
rt_pin_write(GET_PIN(C, 13), PIN_LOW);
rt_device_read(pulse_encoder_dev, 0, &(dev_BspCommunicate.Value_RotaryEncoder), 1);
if(abs(dev_BspCommunicate.Value_RotaryEncoder)>20000)
{
/* 清空脉冲编码器计数值 */
rt_device_control(pulse_encoder_dev, PULSE_ENCODER_CMD_CLEAR_COUNT, RT_NULL);
}
dev_BspCommunicate.Method_Send(&dev_BspCommunicate,M0_D1,dev_BspCommunicate.Value_RotaryEncoder);
rt_pin_write(GET_PIN(C, 13), PIN_HIGH);
}
没有使用多余线程,这里只使用了主线程工作
- LPD3806-400BM-G5-24C 脉冲AB相增量式光电旋转编码器,不能抗冲击,当落爪子力度较大,会导致读到异常数据
- 这里判断读数大于20000强制编码器读数清零,
-
存在问题
由于3508电机位置控制缺陷,原因:https://www.yuque.com/wangxi_chn/qaxke0/ulrbrk#ZzYzP
- 需要额外配置编码器,因此建议角度控制电机选用其他类型(舵机、步进电机、3863这类内部集成脉冲编码器的电机)
取球爪的PID是个较为复杂的过程
换用3863电机,角度控制,但是力量会很大,可以使用速度角度控制(目前机械方案修改方向)
- 还需仔细研究串级PID结构控制原理