STM32实现四驱小车(一)硬件与软件准备

26 篇文章 30 订阅

一. 绪论

匆匆忙忙的2020年结束了,在机器人平台开发方面算是搞清了些端倪。近来总算有时间回顾过往,稍作整理。计划写一个专题系列,内容即为“机器人控制系统设计与实现”,这将会是我的系列文章的宗旨。机器人平台从地面无人车到空中无人机到水下无人潜航器,这将是一个可上九天揽月、可下五洋捉鳖的宏大蓝图。讲述内容从控制算法到嵌入式软件开发,从计算机仿真到单片机实战,从底层驱动到上层应用。所有的代码源于项目实战,讲述方式将是雅俗共赏。记录与分享心得的过程是快乐的,既是悦己、也可及人。如果有志同道合的朋友们可以动动小爪点个关注,我们一起成长、一起进步叭!

第一篇从实现一个四轮四驱小车开始~

二. 轮式机器人概述

在机器人的大家庭里,轮式机器人独领风骚。轮式机器人常见的有两轮平衡车(二驱差速)、四轮车(四驱差速)、三轮车(二驱)、阿克曼转向小车(与汽车最为相似)、麦克纳姆轮(Mecanum wheel)小车、全向轮(omni wheel)小车等。感兴趣的小伙伴可以分别去了解运动与控制原理,市场上的绝大部分地面移动机器人大抵都是基于以上平台了。这里我们实现一个四轮小车,四轮都有电机驱动,左右电机各为一组,控制左右侧电机差速即可实现转向,很简单有木有? 但是麻雀虽小,五脏俱全,在此篇中我们基于STM32实现机器人的控制,使用UCOS-III操作系统,实现遥控器通信、传感器数据读取、航向角串级PID控制、电机PID控制全部功能。上位机地面站的开发我放在下一篇四轴飞行器的讲解中。

三. 硬件准备

1. 机械底盘

搭建四轮小车的机械部分就不细讲了,有条件的自己设计机架机加工或3D打印,没条件的直接买一个四轮底盘即可,电机、轮子配套,成本不高。

2. 电机选择

电机选择就很多了,小型四轮车常见的是编码器电机,如图1所示,一般是六根接线,两根编码器电源线,两根电源线,两根编码器信号线(A相B相)。只需要一路编码器就可用于测速,两路可以测方向,控制板需要解析编码器信号做速度闭环控制。
在这里插入图片描述
另外一种是总线电机,常见的是CAN总线电机、串口总线电机,这种电机一般会有配套的驱动器,控制板只需要给定速度、位置信号。如图所示是大疆的M3508行星减速电机与配套的C620电调(RoboMaster用的电机同一系列),可以使用CAN总线控制和PWM两种控制方式。本篇我们就用这个电机,基于CAN总线方式去做速度闭环。
在这里插入图片描述

3. 驱动板

驱动板是小车的大脑,由于本篇所实现的功能只是实现基本运动,就是遥控器控制小车直行和转弯,所以只需要一个底层驱动控制板,在后续增加高级功能之后会叠加上层控制器(如树莓派、英伟达)。
板子可以买,以可以画。主要是要有IIC、串口、CAN总线、编码器接口等外设接口和电源接口。前述的编码器电机的接口是通用的,淘宝上有很多板子是带了这种接口的,如果没有的话将电机的线分别接到对应的IO口即可,只是不太好看。
本文选择自主开发的使用STM32F7主控的驱动板,时钟频率216MHz,一般的智能小车应用都足够了。

4. 传感器

传感器主要是需要姿态传感器,一般使用九轴惯性传感器,即三轴陀螺仪、三轴加速度计、三轴磁力计。最终目的就是解算三轴姿态角,这里简单粗暴,直接买了一个姿态角传感器,如图所示是维特智能的传感器,串口输出三轴姿态角,其内部还是使用姿态解算算法算出来的。如果使用MPU9250等模块就需要自己进行解算了,后面有时间专门写一篇滤波算法与姿态解算。
在这里插入图片描述

5. 电池

建议选用锂电池。

四. 软件准备——UCOS-III操作系统

本系列文章都会使用操作系统,UCOS-III和FreeRTOS原理差不多,只不过前者商用后者免费,我使用的是UCOS-III。如果没有嵌入式操作系统的小伙伴们自行补补课叭,不会也没关系。不影响核心代码部分的阅读。

在空的操作系统的工程模板中,创建4个任务,一个CommunicateTask,SensorTask,StabilizationTask,MotorTask,分别进行遥控器通信、传感器数据读取、姿态控制、电机控制。使用操作系统的优势就是实时性,请大家牢记这个概念。 在操作系统下,每个任务都像是同时运行的,其实是分时复用,只有在多核的处理器上才能实现真正的同时。

首先在main文件的起始部分定义任务优先级、任务堆栈、堆栈大小、任务函数等。这里的startTask用于创建其他任务,创建完之后就挂起自己。

//任务优先级
#define START_TASK_PRIO 3
//任务堆栈大小
#define START_STK_SIZE 128
//任务控制块
OS_TCB StartTaskTCB;
//任务堆栈
CPU_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *p_arg);

//communicate任务
//设置任务优先级
#define COMMUNICATE_TASK_PRIO 5					// SBUS 信号的更新是在串口中断中进行的
//任务堆栈大小
#define COMMUNICATE_STK_SIZE 512
//任务控制块
OS_TCB CommunicateTaskTCB;
//任务堆栈
CPU_STK COMMUNICATE_TASK_STK[COMMUNICATE_STK_SIZE];
//led0任务
void communicate_task(void *p_arg);

//stabalizer任务
//设置任务优先级
#define STABILIZATION_TASK_PRIO 4
//任务堆栈大小
#define STABILIZATION_STK_SIZE 2048
//任务控制块
OS_TCB StabilizationTaskTCB;
//任务堆栈
CPU_STK STABILIZATION_TASK_STK[STABILIZATION_STK_SIZE];
//led0任务
void stabilization_task(void *p_arg);

//Motor电机任务
//设置任务优先级
#define MOTOR_TASK_PRIO 7
//任务堆栈大小
#define MOTOR_STK_SIZE 512
//任务控制块
OS_TCB MotorTaskTCB;
//任务堆栈
CPU_STK MOTOR_TASK_STK[MOTOR_STK_SIZE];
//motor任务
u8 motor_task(void *p_arg);

//sensorTask 参数配置任务 在线调试参数并写入flash
//设置任务优先级
#define SENSOR_TASK_PRIO 6
//任务堆栈大小
#define SENSOR_STK_SIZE 512
//任务控制块
OS_TCB SensorTaskTCB;
//任务堆栈
CPU_STK SENSOR_TASK_STK[SENSOR_STK_SIZE];
//motor任务
u8 sensor_task(void *p_arg);

在main函数中系统初始化、外设初始化、创建开始任务

int main(void)
{
	OS_ERR err;
	CPU_SR_ALLOC();

	Write_Through();		 //Cahce强制透写
	MPU_Memory_Protection(); //保护相关存储区域
	Cache_Enable();			 //打开L1-Cache

	Stm32_Clock_Init(432, 25, 2, 9); //设置时钟,216Mhz
	HAL_Init();						 //初始化HAL库
	delay_init(216);				 //延时初始化
	uart1_init(100000);				 //串口1初始化
	uart2_init(115200);				 //串口2初始化
	uart3_init(115200);				 //串口3初始化

	IIC_Init();						 //IIC通讯口初始化
	//uart_imu_Init();		  		 // 初始化串口IMU
	//JY901_Init();					// 初始化IIC IMU , IMU的初始化都只需要执行一次,之后注释掉这两行代码
	
	MY_ADC_Init();
	KEY_Init();						 //按键初始化
	LED_Init();						 //初始化LED
	PWM_Init();

	CAN1_Mode_Init(CAN_SJW_1TQ, CAN_BS2_6TQ, CAN_BS1_11TQ, 3, CAN_MODE_NORMAL); //CAN初始化正常模式,波特率1000Kbps

	OSInit(&err);		 //初始化UCOSIII
	OS_CRITICAL_ENTER(); //进入临界区
	//创建开始任务
	OSTaskCreate((OS_TCB *)&StartTaskTCB,							//任务控制块
				 (CPU_CHAR *)"start task",							//任务名字
				 (OS_TASK_PTR)start_task,							//任务函数
				 (void *)0,											//传递给任务函数的参数
				 (OS_PRIO)START_TASK_PRIO,							//任务优先级
				 (CPU_STK *)&START_TASK_STK[0],						//任务堆栈基地址
				 (CPU_STK_SIZE)START_STK_SIZE / 10,					//任务堆栈深度限位
				 (CPU_STK_SIZE)START_STK_SIZE,						//任务堆栈大小
				 (OS_MSG_QTY)0,										//任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
				 (OS_TICK)0,										//当使能时间片轮转时的时间片长度,为0时为默认长度,
				 (void *)0,											//用户补充的存储区
				 (OS_OPT)OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR, //任务选项
				 (OS_ERR *)&err);									//存放该函数错误时的返回值
	OS_CRITICAL_EXIT();												//退出临界区
	OSStart(&err);													//开启UCOSIII
	while (1)
		;
}

开始任务的内容如下,内容就是创建其他四个任务,之后把自己挂起。

//开始任务函数
void start_task(void *p_arg)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	p_arg = p_arg;

	CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
	OSStatTaskCPUUsageInit(&err); //统计任务
#endif

#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了测量中断关闭时间
	CPU_IntDisMeasMaxCurReset();
#endif

#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
	//使能时间片轮转调度功能,设置默认的时间片长度
	OSSchedRoundRobinCfg(DEF_ENABLED, 1, &err);
#endif
	__HAL_RCC_CRC_CLK_ENABLE(); //使能CRC时钟
	OS_CRITICAL_ENTER();		//进入临界区

	//communicate任务 通信任务
	OSTaskCreate((OS_TCB *)&CommunicateTaskTCB,
				 (CPU_CHAR *)"Communicate task",
				 (OS_TASK_PTR)communicate_task,
				 (void *)0,
				 (OS_PRIO)COMMUNICATE_TASK_PRIO,
				 (CPU_STK *)&COMMUNICATE_TASK_STK[0],
				 (CPU_STK_SIZE)COMMUNICATE_STK_SIZE / 10,
				 (CPU_STK_SIZE)COMMUNICATE_STK_SIZE,
				 (OS_MSG_QTY)0,
				 (OS_TICK)10,
				 (void *)0,
				 (OS_OPT)OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_SAVE_FP,
				 (OS_ERR *)&err);
	//Stabilizaiton任务 姿态控制
	OSTaskCreate((OS_TCB *)&StabilizationTaskTCB,
				 (CPU_CHAR *)"Stabilization task",
				 (OS_TASK_PTR)stabilization_task,
				 (void *)0,
				 (OS_PRIO)STABILIZATION_TASK_PRIO,
				 (CPU_STK *)&STABILIZATION_TASK_STK[0],
				 (CPU_STK_SIZE)STABILIZATION_STK_SIZE / 10,
				 (CPU_STK_SIZE)STABILIZATION_STK_SIZE,
				 (OS_MSG_QTY)0,
				 (OS_TICK)10,
				 (void *)0,
				 (OS_OPT)OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR |OS_OPT_TASK_SAVE_FP,
				 (OS_ERR *)&err);
	//Motor任务
	OSTaskCreate((OS_TCB *)&MotorTaskTCB,
				 (CPU_CHAR *)"Motor task",
				 (OS_TASK_PTR)motor_task,
				 (void *)0,
				 (OS_PRIO)MOTOR_TASK_PRIO,
				 (CPU_STK *)&MOTOR_TASK_STK[0],
				 (CPU_STK_SIZE)MOTOR_STK_SIZE / 10,
				 (CPU_STK_SIZE)MOTOR_STK_SIZE,
				 (OS_MSG_QTY)0,
				 (OS_TICK)10,
				 (void *)0,
				 (OS_OPT)OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_SAVE_FP,
				 (OS_ERR *)&err);
	//Sensor任务
	OSTaskCreate((OS_TCB *)&SensorTaskTCB,
				 (CPU_CHAR *)"Sensor task",
				 (OS_TASK_PTR)sensor_task,
				 (void *)0,
				 (OS_PRIO)SENSOR_TASK_PRIO,
				 (CPU_STK *)&SENSOR_TASK_STK[0],
				 (CPU_STK_SIZE)SENSOR_STK_SIZE / 10,
				 (CPU_STK_SIZE)SENSOR_STK_SIZE,
				 (OS_MSG_QTY)0,
				 (OS_TICK)10,
				 (void *)0,
				 (OS_OPT)OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR |OS_OPT_TASK_SAVE_FP,
				 (OS_ERR *)&err);
	OS_TaskSuspend((OS_TCB *)&StartTaskTCB, &err); //挂起开始任务
	OS_CRITICAL_EXIT(); //退出临界区
}

紧接着写四个主要任务,这四个函数是主体部分,分别实现遥控器通信、传感器数据读取、姿态控制、电机伺服。四个函数全是空的,啥也没干,只有一个while循环中delay_ms(10),意思就是每隔10ms执行一次这个任务(是不是就是裸机里面的while(1)循环?)后续文章会依次往各个任务里面加内容。

//通信任务
void communicate_task(void *p_arg)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	while (1)
	{
		delay_ms(10);
	}
}

void stabilization_task(void *p_arg)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	{
		delay_ms(10);
	}
}
u8 sensor_task(void *p_arg)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	{
		delay_ms(10);
	}
}
u8 motor_task(void *p_arg)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	{
		delay_ms(10);
	}
}

以上关于操作系统的代码部分如果没有操作系统基础的小伙伴们可能有点云里雾里,可以不用管。只需要明白,它其实是把一个main函数变成了多个main函数,名义上的main函数只进行初始化和任务创建,每个void XXX_task()就是一个main函数,多个main函数分时复用,就像多个任务同时执行。

下一篇开始进行核心代码的实现。

  • 25
    点赞
  • 171
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
1.时钟源 外部时钟 2.GPIO (实验:点亮LED灯,获取引脚的电平高低,将对应引脚置高置低)。 3.PWM-脉宽调制 主要是调节占空比(在小车中用来实现小车的加减速)。 4.TIMX定时器 5.红外遥控 主要运用了EXTI(外部中断/事件控制器),系统延时(系统滴答定时器SysTick)。 6.超声波避障 了解了超声波工作的原理,主要运用了TIM2定时器(用来定时测距),GPIO口。 7.红外探测 通过对障碍物和光的感应来返回电平的状态,从而达到避障和巡线效果。 8.测速码盘 (通过检测码盘上的凹槽数来获取脉冲数,通过计算公式得到小车当前速度) TIM3定时器(定时测速)。 9.PID算法 为了使小车的速度更快的达到目标值,获取更稳定的速度。 10.系统的使用 我们在小车内加入系统,对小车的数据获取,状态实现任务化,使小车内的运作更加有序。我们主要使用μC/OS-II系统内核来实现系统。在实现过程中,我们了解了系统的任务调度以及任务运行和处理器之间的关系(多任务同时运行)。 μC/OS-II内核中的任务控制块,任务状态,以及多任务和任务的优先级类似操作系统中的进程操作。 11.任务间的通信 主要用到信号量(主要就是PV操作 P操作:占用资源。V操作:释放资源);邮箱(邮箱可以使一个任务或者中断服务子程序向另一个任务发送指针型的变量,通常该指针指向包含了“消息”特定的数据结构)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何为其然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值