从零开始做一辆简易麦克纳姆轮小车

一、前期准备

麦克纳姆轮小车(Mecanum wheel robot)是一种能够实现全向移动的机器人,其核心在于使用了特殊设计的麦克纳姆轮。要从头开始制作一辆麦克纳姆轮小车,你可能需要准备以下组件和工具:

1. 材料和部件

麦克纳姆轮:选择合适尺寸和承载能力的麦克纳姆轮。这些轮子通常由橡胶或塑料制成,并带有自由旋转的滚轮。可以在专业的机器人配件商店或在线市场购买。

电机:根据设计需求选择步进电机或伺服电机。电机的扭矩和转速应与麦克纳姆轮的规格相匹配。例如,对于小型机器人,可以使用NEMA17步进电机。

电机控制器:如果使用步进电机,则需要相应的驱动器,如A4988模块;若使用伺服电机,则需ESC电子调速器。确保控制器可以处理所需的电流和电压。

电池:依据电机和控制板的电源要求,选择合适的锂电池或镍氢电池。容量需要足够以支持预期的操作时间。

微控制器:Arduino UNO、Nano或Raspberry Pi等开发板是常见选择。它们用于接收传感器数据和控制电机。

结构框架:可使用木板、亚克力板或3D打印部件来构建车体。设计时要考虑强度和重量。

传感器:超声波传感器用于避障,编码器可用于轮速反馈,提高运动精度。

其他电子元件:包括电线、插座、电阻、LED指示灯等。
2. 工具

螺丝刀:一套交叉和一字螺丝刀,适用于不同大小的螺丝。

烙铁和焊锡:用于电子元件的焊接工作,确保连接牢固可靠。

剥线钳和剪刀:用于准备电线和切割材料。

尺子和标记笔:测量和标记材料以便准确切割。

锯子或激光切割机:根据所选材料类型进行切割。

3D打印机:如果有3D设计文件,可以直接打印复杂的零件。

螺丝和螺母套装:用于组装结构的各种尺寸螺丝和螺母。
3. 软件

编程环境:Arduino IDE用于编写和上传代码到Arduino板;PyCharm或Thonny用于Raspberry Pi的Python编程;Keil5 和 Cubemx 用于单片机编程。

CAD软件:如AutoCAD或SolidWorks,用于设计和修改车辆的结构部件。

固件库:如Arduino的Stepper或Servo库,简化电机控制代码的编写。
4. 组装步骤

设计框架:在CAD软件中设计车体框架,考虑到所有部件的布局。

打印/制造部件:使用3D打印机打印非标准部件,或者按照设计切割木板或亚克力板。

安装电机:将电机固定到框架上,并确保麦克纳姆轮能自由旋转不受阻碍。

连接电子部件:按照电路图连接电机控制器、电池、微控制器和传感器。

上传代码:编写控制代码并在微控制器上运行测试。

调试:检查所有运动是否符合预期,调整代码以优化性能。

在这里我使用的芯片型号是STM32F103C8T6,编程环境为 Keil5 、Cubemx,电机驱动模块为TB6612,蓝牙模块为HC-05或者HC-08,地板轮子和电机在网上购买即可。

电机驱动基础知识具体可看我之前写的文章:https://blog.csdn.net/m0_74712453/article/details/140008931?spm=1001.2014.3001.5501

二、麦克纳姆轮工作原理

麦克纳姆轮的核心原理是轮子上小滚子的独特布局和角度,它们能够将轮毂传来的力分解为水平和垂直于滚子轴线的两个分力。通过适当地控制四个麦克纳姆轮的相对速度和方向,可以实现车辆在不改变车体方向的前提下向任意方向移动。例如,当所有轮子向前旋转时,如果相邻轮子的旋转方向相反,车辆将侧向滑动;而如果是对角线上的轮子旋转方向相同,则可以实现原地旋转。

前面提到麦轮分A,B两种,如果A轮向前运动时同时向右运动,即斜向右前方运动,那么相反,A轮向后运动的同时会向左运动,即斜向左后方运动;相应B轮就可以斜向左前和右后方运动。

例:以小车车头为正方向,约定轮子前进时的方向为电机正转,轮子后退时的方向为电机反转。

拿A轮来说,辊子移动方向上由于滚动从而无法提供前进的力,而在辊子轴线方向上辊子无法滚动并且与地面摩擦产生辊子轴向上的摩擦力,即斜向右前或左后方向,从而A轮的速度方向是斜向右前或左后;同理可分析B轮。

根据我们高中学到的物理知识,我们知道速度是可以正交分解的,决定车体运动取决于四个麦轮的合速度方向。

那么A轮就可以分解成轴向向右和垂直轴向向前的速度分量,或者说轴向向左和垂直轴向向后的速度分量。

这样,B轮的速度分量和A轮互为镜像关系了。

三、麦克纳拉姆轮安装组合及运动分析

1.麦克纳姆轮正确安装组合

从上面我们可以知道麦克纳姆轮主要分为A,B轮,知道对应轮子的速度分量后我们就可以对四轮的麦克纳姆轮底盘进行排列组合,例如:AABB,AAAA,BBBB等,但是通过速度分量分析我们可以得知并不是每一种组合都可以实现全方向移动,在这里我列出麦克纳姆轮一种错误的安装组合,其余可以自行推理,例如错误例子AAAA型:左图为俯视图,右图为麦轮与地面接触的部分,投影到地面的情况!

由图我们可以得知:当四个轮子同时向前转动的时候,每个轮子都会有一个向左的速度分量,这样就会导致整个底盘前进的时候必然会同时向左运动;同理,后退的时候必然会向右运动,这样就没办法使用了,这个东西不受控制到处乱跑,这不是我们想要的全向移动。

所以我们可以通过组装正确方式,来确保四个轮子同时向前或者向后转动时,实现整个底盘前进或者后退,不发生其他偏移。

如下图所示,一种正确的安装组合:

当四个轮子都向前转的时候,AB轮可以相互抵消轴向速度,只剩下向前的速度,这样底盘就是向前直行、不会跑偏,后退也是一样的;

如果当A轮正转、B轮反转的时候,向前向后的速度会抵消,仅剩下向右的速度,那么底盘就会向右平移;相反,如果A轮反转,B轮正转,就会向左平移;其他方向都可以自行推理,我在上一篇已经列出常见麦轮组合运动方向。

2.麦克纳姆轮常见运动方式

根据上面的原理,我们可以推理得出麦克纳姆轮常见的运动方式。

1.对角线方向


2.前进和后退

3.左移和右移

4.绕正前方左右摆尾

5.绕正后方左右摆尾

6.绕左右上角转动

7.绕左右下角转动

四、软件程序代码编写

在这里我们先实现蓝牙控制小车可以前进后退左右移动,具体涉及编码器和pid的知识后面我有时间再单独写一篇总结一下。

1.接线

电机驱动模块与单片机对应引脚接线:

  • AIN1,AIN2 对应 PB2,PB10 左上轮
  • BIN1,BIN2 对应 PB1,PB0 左下轮
  • DIN1,DIN2 对应 PB5,PB6 右上轮
  • CIN1,CIN2 对应 PB8, PB7 右下轮

HC-08蓝牙模块与单片机接线:

  • 蓝牙模块TX接RX(PA10),RX接TX(PA9),VCC接5V,GND接GND

2.Cubemx配置

SYS:Debug设置成Serial Wire

RCC:配置外部高速晶振

配置时钟树:

配置八个GPIO口为输出模式:

配置UART:设置波特率为9600,因为蓝牙模块默认波特率为9600,开启NVIC中断接收信息。

配置完后最后点击 generate code 生成代码。

3.代码示例

(1)实现前进和后退

//前进
void goForward(void)
{
	// 左轮前进 左上轮 PB2,PB10 左下轮 PB1,PB0
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
	
	// 右轮前进 右上轮 PB5,PB6 右下轮PB8 , PB7    
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
}

//后退
void goBack(void)
{
	// 左轮后退 左上轮 PB2,PB10 左下轮 PB1,PB0
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
	
	// 右轮后退 右上轮 PB5,PB6 右下轮PB8 , PB7
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
}

(2)实现左移和右移

//左移
void goLeft(void)
{
	// 左上轮后退 左上轮 PB2,PB10 左下轮前进 左下轮 PB1,PB0
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);

	
	// 右上轮前进 右上轮 PB5,PB6  右下轮后退 右下轮 PB8,PB7
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
}

//右移
void goRight(void)
{
	// 左上轮前进 左上轮 PB2,PB10 左下轮后退 左下轮 PB1,PB0
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
	
	// 右上轮后退 右上轮 PB5,PB6  右下轮前进 右下轮 PB8,PB7
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
}

###(3)蓝牙接收信息

可以使用HC-05(下图蓝色)或者HC-08(下图绿色),这两种我都测试通过。

使用串口中断测试收发的数据,串口重映射设置:

重映射代码:

int fputc(int ch, FILE *f)
{      
	unsigned char temp[1]={ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);  
	return ch;
}

中断接收信息代码:

// 串口接收缓存
uint8_t buf=0;

// 定义最大接收字节数
#define UART1_REC_LEN 200

// 接收缓冲,串口接收到的数据存放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];

//  接收状态
//  bit15,     接收完成标志
//  bit14,     接收到0x0d
//  bit13~0,   接收到的有效字节数目
uint16_t UART1_RX_STA=0;

#define SIZE 12

char buffer[SIZE];

// 接收完成回调函数,收到数据后在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	// 判断中断是有哪个串口触发的
	if(huart->Instance == USART1)
	{
		// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
		if((UART1_RX_STA & 0x8000) == 0)
		{
			// 如果已经收到了 0x0d (回车)
			if(UART1_RX_STA & 0x4000)
			{
				// 则接着判断是否收到 0x0a (换行)
				if(buf == 0x0a)
				{
					// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
					UART1_RX_STA |= 0x8000;

					// 控制小车运动方向指令
					if(!strcmp(UART1_RX_Buffer, "M1"))
					{
						goForward();
						printf("goForward success\r\n");
					}
					else if(!strcmp(UART1_RX_Buffer, "M2"))
					{
						goBack();
						printf("goBack success\r\n");
					}
					else if(!strcmp(UART1_RX_Buffer, "M3"))
					{
						goLeft();
						printf("goLeft success\r\n");
					}
					else if(!strcmp(UART1_RX_Buffer, "M4"))
					{
						goRight();	
						printf("goRight success\r\n");
					}
					else
					{
						if(UART1_RX_Buffer[0] != '\0')
						{
							stop();
							printf("Ö¸Áî·¢ËÍ´íÎó£º%s\r\n", UART1_RX_Buffer);
						}
					}
					
					memset(UART1_RX_Buffer, 0, UART1_REC_LEN);
					UART1_RX_STA = 0;
				}
				else
					// 否则认为接收错误,重新开始
					UART1_RX_STA = 0;
			}
			// 如果没有收到了 0x0d (回车)
			else	
			{
				// 则先判断收到的这个字符是否是 0x0d (回车)
				if(buf == 0x0d)
				{
					// 是的话则将 bit14 位置为1
					UART1_RX_STA |= 0x4000;
				}
				else
				{
					// 否则将接收到的数据保存在缓存数组里
					UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
					UART1_RX_STA++;
					
					// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
					if(UART1_RX_STA > UART1_REC_LEN - 1)
						UART1_RX_STA = 0;
				}
			}
		}
		// 重新开启中断
		HAL_UART_Receive_IT(&huart1, &buf, 1);
	}
}

在main.c函数中记得开启接收中断

// 开启接收中断
HAL_UART_Receive_IT(&huart1, &buf, 1);

手机打开蓝牙助手,记得把发送新行勾上,不然中断接收不到数据。

最后就可以实现手机通过蓝牙模块控制小车运动: