我的第一辆智能小车
PS:本人是成都大学大一的一名学生,所以也是初学者,对这方面非常感兴趣,在一位老师的建议下开始买了stm32正点原子的板子开始学习hal库,几个星期以后买了配件开始边学边做stm32小车,本文适用于想完成一个基础智能两轮车加一个万向轮的初学者,大佬还请勿喷,非常欢迎各位指出错误的地方,和我有待提升的地方,我一定虚心求教。
说实话我找到网上我能找到的都是标准库,想找一个免费hal库的参考真的很难,资料也不齐,希望我自己学习纯手写成功的寻迹小车能帮到各位初学者以及欢迎大佬批评指正。
我会放免费的完整的资料,因为个人问题我没有正规的建立文件夹使用正点原子的那一套,因为有点点小麻烦,抱歉啦。后续我会改掉这个坏习惯,这个我手写的代码呢共初学者借鉴(也是我学习之路的苦,希望对大家有帮助),供大佬们批评指正,我一定虚心求教。
材料
1.stm32f407zgt6最小系统开发板
2.l298n电机驱动模块1个
3.两个电机+两个橡胶轮子
4.循迹红外模块1个
5.电池(干电池或者锂电池+减压器)
PS:我最开始不懂嘛,怕买错之类的,就直接在网上买的散件,不过提供的是标准库代码,很无奈,没有参考价值。自己边学边做终于完成了寻迹小车hal库
代码说明:这篇博客只是记载和解说,完整在我下一个博客,我马上发,完全免费的
我会放免费的完整的资料,因为个人问题我没有正规的建立文件夹使用正点原子的那一套,因为有点点小麻烦,抱歉啦。后续我会改掉这个坏习惯,这个我手写的代码呢共初学者借鉴(也是我学习之路的苦,希望对大家有帮助),供大佬们批评指正,我一定虚心求教。
下面的.c我就不放.h了,因为我刚刚学,我听从了前辈的建议在.h里面定义好引脚那些,嘻嘻
这是主函数main.c
里面最后的#ifdef #endif那些我是复制的,我也不知道为什么,但是通过这3个星期接触stm32我知道这里要加。还有错误函数void Error_Handler(void),我大概知道为什么,因为我没有这个也会正常运行,加了也会,但规范都让加,我也跟着哈哈哈哈(欢迎大佬给小白我解惑)
#include "main.h"
#include "tim.h"
#include "display.h"
#include "motor.h"
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
DISPLAY_Init();
TIM4_Init();
xunji_Init();
motor_Init();
Change_speed(62);
while (1)
{
if(L1==1&&R1==0)
{
Turn_Left();
DISPLAY_LEFT();
}
if(L1==0&&R1==1)
{
Turn_Right();
DISPLAY_RIGHT();
}
if(L1==1&&R1==1)
{
Forward();
DISPLAY_GO();
}
if(L1==0&&R1==0)
{
Stop();
DISPLAY_STOP();
}
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
PS:
里面的void SystemClock_Config(void);我使用的是正点原子提供的sys.c文件,我认为配置的时钟源那些,HSE啊那些都是一样的,APB1,APB2那些经过预分频配置都是72MHZ,没必要自己写我觉得,直接用正点原子sys.c文件就OK了。但是我这上面为了联系一下时钟的知识还是自己写的,如果直接拷贝一份正点原子sys.c也是可以的哈(通过学习正点原子hal库我大概知道怎么配置,我也不知道我这种理解对不对,欢迎大佬批评指正)
思路
那就是现初始化,然后在循环里面调用xunji函数读取电平高低判断有没有路,然后对应调用电机函数正反转和数码管的显示。这就主函数是思路。下面将介绍具体函数:数码管display,pwm函数tim.c , 循迹函数xunji.c , 电机函数motor.c
解说
.h文件里面定义了L1,R1那些,L1那些是读取循迹模块也就是PA12,PA13引脚的高低电平,如果是0说明是低电平起那面没有路,1表示高电平前面有路。
这里是display.c
PS:是我用的共阳数码管函数,相当于装饰啦啦,前进显示8上面那一杠,左转显示8的左边那一竖,右转显示8的右边那一竖,停止显示8的中间的那一杠。(不知道这样符不符合我们单片机的标准,我也是才接触3个星期嘛,也不懂,这只是我幼稚的想法,欢迎大佬批评指正)
#include "display.h"
void DISPLAY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
}
void DISPLAY_RIGHT()
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
}
void DISPLAY_LEFT()
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4|GPIO_PIN_5, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
}
void DISPLAY_GO()
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
}
void DISPLAY_STOP()
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_7, GPIO_PIN_SET);
}
使能PA0-7,分别对应共阳数码管的8个引脚,低电平两,高电平灭。
解释一下这是个函数:
先初始化各引脚。
void DISPLAY_RIGHT():右转时8的右边那一竖亮,对应PA1-2。
void DISPLAY_LEFT();左转时8的左边那一竖亮,对应PA4-5。
void DISPLAY_GO():前进时8的上面那一杠亮,对应PA0。
void DISPLAY_STOP():停止时8的中间那一杠亮,对应PA6。
我的幼稚想法啊啊啊各位大佬,别喷我,我们只需要在主函数里面调用这四个函数就行了。
这里是tim.(我认为也是最难的PWM)
#include "tim.h"
TIM_HandleTypeDef htim4;
void TIM4_Init(void)
{
//HAL_TIM_Base_Init();
//HAL_TIM_PWM_Init();
//HAL_TIMEx_MasterConfigSynchronization(); ÅäÖö¨Ê±Æ÷Òç³öʼþÓÃ×÷´¥·¢Æ÷
//HAL_TIM_PWM_ConfigChannel(); ÓÃÓÚÉèÖö¨Ê±Æ÷µÄPWMͨµÀ
//HAL_TIM_PWM_Start(); Æô¶¯Êä³öPWM
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_OC_InitTypeDef sConfigOC;
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_TIM4_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
htim4.Instance = TIM4;
htim4.Init.Prescaler = 36-1;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 100-1 ;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4);
}
我认为这是最难的,我是边学边写的,我看正点原子的视频上面回调函数我也不懂,于是乎我就根据步骤自己手写的,不知道我这个手写的代码符不符合规范,欢迎各位大佬批评指正。
对应PA6-9对应TIM4的1234通道,根据步骤配置使能就欧克了。
这里是xunji.c
PS:各位宝宝原谅我用拼音,大佬别喷我,我是小白,中国人形象生动嘛哈哈哈哈
#include "xunji.h"
#include "motor.h"
void Change_speed(uint16_t speed)
{
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, speed);
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_2, speed);
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3, speed);
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_4, speed);
}
void xunji_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = xunji_R_Pin|xunji_L_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(xunji_R_Port, &GPIO_InitStruct);
}
这里直接使能循迹的PA12和PA13就OK了 然后最开始的是速度的,通过设置比较直,改变占空比。
这里是motor.c
#include "motor.h"
void motor_Init()
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
xunji_Init();
}
void Forward(void)
{
HAL_GPIO_WritePin(motor_L1_Port,motor_L1_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(motor_L2_Port,motor_L2_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(motor_L2_Port,motor_R1_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(motor_L2_Port,motor_R2_Pin,GPIO_PIN_RESET);
}
void Stop()
{
HAL_GPIO_WritePin(motor_L1_Port,motor_L1_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(motor_L2_Port,motor_L2_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(motor_L2_Port,motor_R1_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(motor_L2_Port,motor_R2_Pin,GPIO_PIN_RESET);
}
void Turn_Left()
{
HAL_GPIO_WritePin(motor_L1_Port,motor_L1_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(motor_L2_Port,motor_L2_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(motor_L2_Port,motor_R1_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(motor_L2_Port,motor_R2_Pin,GPIO_PIN_RESET);
}
void Turn_Right()
{
HAL_GPIO_WritePin(motor_L1_Port,motor_L1_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(motor_L2_Port,motor_L2_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(motor_L2_Port,motor_R1_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(motor_L2_Port,motor_R2_Pin,GPIO_PIN_RESET);
}
.h文件我就不放了,我会放完整资料供大家免费学习和大佬给我批评指正。
控制小车四个电机正反转和停止的函数,其实很简单,给对引脚一个高低电平就能转,然后再调试封装成前进,左右转,停止,最后在主函数里面通过循迹模块读取的高低电平来决定调用哪个函数。
各位我有些疑问:
1.为什么有些函数不用在.h里面声明也不会爆警告呀?
2.L298N驱动板能不能直接他12V的那个接口直接接电池电源呀?
我也是小白,在努力学习变强的小白,欢迎大家提问和欢迎大佬批评指正。