当前位置: 首页 > article >正文

stm32hal库寻迹+蓝牙智能车(STM32F103C8T6)

简介:

        这个小车的芯片是STM32F103C8T6,其他的芯片也可以照猫画虎,基本配置差不多,要注意的就是,管脚复用,管脚的特殊功能,(这点不用担心,hal库每个管脚的功能都会给你罗列,很方便的.)由于我做的比较简单,只是用到了几个简单外设.主要是由带霍尔编码器电机的车模,电机驱动tb6612,(注意:选择驱动的时候不要选择那种小红色的驱动,那种最大负载电压11v,如果到时候pwm调到最大,可能会烧穿,比较危险),一个七路灰度传感器,随便一个12v电源都可以,一个stlink.杜邦线若干.(这些材料我基本都是学校实验室顺的,有什么用什么,用的可能比较杂)

芯片:STM32F103C8T6的开发板

驱动:我用的是tb6612双路驱动

七路寻迹模块:

电源;我看网上基本都是左边这种,这种可能另外需要的一个电源模块,虽然起到了一定的保护作用,但资深老玩家认为没什么必要.12v总不能把我电死.我用的是右边这个.加个转接.

                                          

                                                                             

蓝牙模块:

小车的样子,接线比较杂乱

 

小车的基本流程:

        1. 环境的配置,这个就不过多赘述了,我看网上的资料基本都很全了.用的是keil+图形化界面,之后在出一期博客,关于keil和STM32CubeMx的配置,这个环境配置的东西不少.

        2. 串口通信设置设置.

        3. pwm输出设置.

        4. 驱动电机设置.

         5. 编码器设置.

        6. 蓝牙设置.

        7. 寻迹设置.

(一些基本的io控制就不作阐述了)

一. 通过STM32CubeMx创建工程

1.1 安装开发包

点击"install"进行安装。(安装时需要登录)

1.2 选择开发的STM32芯片

1.3 配置时钟

1.4 生成keil工程

之后通过Keil打开工程编译.

二. USB串口通信

        由于设备的局限性没有转串口设备,我直接用的是USB虚拟串口(建议大家不要选择这种方式,这种方式会埋个雷的,由于正常的串口通信的串口中断是一个独立的中断函数,但是USB虚拟串口需要再主函数不停地执行,进行轮巡,还是比较浪费资源的,而且在后面的蓝牙控制也会埋雷,总之建议大家有条件的还是买一个转串口设备)

2.1 hal库配置

2.1.1 进入调试模式

2.1.2 选择外部晶振

2.1.3 USB工作模式

2.1.4 中间节组件

2.1.4 生成

2.2 keil设置

首先会多出一些目

这里我是新建了一个文件夹stm32handler,所有的外设都会在这里面写.

2.2.1 keil中串口的代码

.c代码

#include "serial.h"void getserial_val()
{if(Recv_dlen)//判断是否接收到数据,接收置位处理在static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)函数{USBD_CDC_SetTxBuffer(&hUsbDeviceFS, (uint8_t*)&UserRxBuffer, Recv_dlen);//把接收到的数据拷贝到发送USBD_CDC_TransmitPacket(&hUsbDeviceFS);//发送Recv_dlen=0;//长度清零}}

.h代码

#ifndef __SERIAL_H_
#define __SERIAL_H_#include "main.h"
#include "usb_device.h"
#include <stdio.h>extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint32_t Recv_dlen;
extern uint8_t UserRxBuffer[1024];void getserial_val();#endif

(注意:每次写完代码要调用的时候,都要在keil中添加.h文件)

main函数

        在while函数中调用,这步操作就是用来接收数据如果没有这步操作,在串口助手发送数据,就会无法接收.

重定向usb_printf函数,使用这个函数,就跟C语言里面的printf函数一样,打印到串口

void usb_printf(const char *format, ...)    // usb_printf()重定向 
{va_list args;uint32_t length;va_start(args, format);length = vsnprintf((char *)UserTxBufferFS, APP_TX_DATA_SIZE, (char *)format, args);va_end(args);CDC_Transmit_FS(UserTxBufferFS, length);
}

2.3 测试

大家可以任意地方使用usb_printf函数.进行测试.

三. pwm输出设置

        简单介绍一下pwm,pwm就是一种周期波形,通过控制占空比,就是高电平占总周期的多少,占得越多,电压越大,电压越大,电机转的越快.这就是很简单的一种说法.

3.1 CubeMX配置

        输入的时钟频率为72MHZ

3.1.1 pwm定时器的设置

定时器1,由于我的是两路驱动,只需要设置输出两路pwm.如果是四路可以另外设置

        下面设置pwm的相关参数还是比较重要的.这里最终设置的输出频率一定要符合自己电机输出的评率.不同的电机输出频率不一样.大家最好问问客服.假如我的电机需要的频率是20kHZ,你只输出了10KHZ.这样就会有一定的问题.不只体现在转速上,还会振荡,漂移,电机发热一系列问题

        电机的输出所需频率为20KHZ

        所以:f=时钟频率/(预分频+1)*(重载值+1)

        这里的话 20k=72M(71+1)*(49+1)

        大家也可以凑其他数,让输出频率达到自己所需.

3.1.2配置参数说明
  1. Counter Settings(计数器设置)
    • Prescaler (PSC - 16 bit... 71)
      • 预分频器值为 71。
    • Counter Mode Up
      • 计数器模式为向上计数模式。在这种模式下,计数器从 0 开始,每次时钟脉冲到来时加 1,直到达到自动重装载值。
    • Counter Period (AutoR... 49)
      • 自动重装载值为 49。这决定了计数器的周期,即从 0 计数到这个值后重新开始计数。
    • Internal Clock Division (... No Division)
      • 内部时钟不分频。这意味着使用内部时钟源时,不进行额外的分频操作。
    • Repetition Counter (RC... 0)
      • 重复计数器值为 0。重复计数器用于在高级定时器中控制 PWM 信号的重复频率。
    • auto - reload preload Enable
      • 自动重装载预装载使能。这意味着自动重装载值可以在运行时更新。
  2. PWM Generation Channel 1(PWM 生成通道 1)
    • Mode PWM mode 1
      • PWM 模式 1。在这种模式下,当计数器小于比较值时为有效电平,当计数器大于等于比较值时为无效电平。
    • Pulse (16 bits value) 49
      • 脉冲值(比较值)为 49。这个值与自动重装载值一起决定了 PWM 的占空比。
    • Output compare preload Enable
      • 输出比较预装载使能。允许在运行时更新比较值。
    • Fast Mode Disable
      • 快速模式禁用。
    • CH Polarity Low
      • 通道极性为低。这决定了 PWM 信号的初始电平。

        在函数运行的时候通过下图这个函数进行改变通道风比较值进行设置占空比.

        

        关于pwm和相关定时器的初始化,在生成的时候,已经初始化完成,所以不需要再keil中另外操作

四. 驱动电机设置

        在配置之前需要了解这个驱动的相关知识

        1. VCC
                逻辑电平输入端,给电机驱动模块供电,3.3v或5v


        2. STBY
                使能引脚,高电平(3.3v)使能,低电平失能

        3.GND
                电机驱动模块地端

        4.PWMA
                PWM输入引脚,根据接收到的PWM信号的占空比,输出电压


        5.AIN1与AIN2
                电机控制模式输入端,控制电机正反转

        两路同理

4.1 CubeMX配置

        配置AIN1 AIN2 BIN1 BIN2 STBY 五路输出IO

       

没有什么,特殊操作,简单的五路输出IO.设置同样的其他四个.

4.2 keil中的设置

.c文件

        要注意的就是方向,要知道速度不能为负,如果传进来的速度为负,就好比是反向速度,这样就要通过重新设置驱动的方向(方向是由驱动决定的.),然后对速度取反.

#include "motor.h"/*电机pwm启动
*/
void Pwm_Init()
{HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
}
/*电机启动
*/
void motor_on()
{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}
/*设置电机速度
*/
void motor_speed(int16_t speed_left,int16_t speed_right)
{if(speed_left>0){HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1,(50-0.5*speed_left));}else{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET);__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1,(50-0.5*(-speed_left)));}if(speed_right>0){HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_2,(50-0.5*speed_right));}else{HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_2,(50-0.5*(-speed_right)));}
}/*按键中断初始化
*/
void Button_Init()
{HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
void Morot_off()
{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);motor_speed(0,0);
}

按键中断是为了当时好测试一点,可以不加.另外我是直接通过设置速度,没有像什么常见的正转,反转,等等这样的操作.如果想的话.可以这样

.h文件

#ifndef __MOTOR_H_
#define __MOTOR_H_#include "main.h"
#include "tim.h"extern TIM_HandleTypeDef htim1;void Pwm_Init();
void motor_on();
void motor_speed(int16_t speed_left,int16_t speed_right);
void Button_Init();
void Morot_off();#endif

(注意,在执行代码之前一定要打开motor_on,和初始化定时器)

4.3 测试

        测试的时候就是通过改变通道的比较值来设定占空比从而设定速度.

        即__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1,(50-0.5*speed_left));

        上述这个函数.(50-0.5*speed_left),最大占空比是49/49=100;但是由于设置的初始比较值为49是满占空比,所以这只电机速度的时候就要用49-(所需速度).(我这里是为了更好的设置速度,所以*0.5,这样最大速度就是100,而不是50)

五. 编码器设置

        简单介绍一下霍尔编码器的工作原理.简单来说用的这个双通道计数,会有两个方波.就是A相和B相.因为A相和B相的相位差90度,可以理解为B相比A相快一点.若同时捕获 A、B 通道信号的上升沿和下降沿,A 相的上升沿、下降沿以及 B 相的上升沿、下降沿都能触发计数.计几次数就是几倍频.在一个信号周期内可以计数四次,实现 4 倍频计数。以 4 倍频为例,如果编码器码盘的分辨率为 N 线,那么电机转一圈,计数器的计数值为 4N

        关于方向,当 A 相检测到上升沿脉冲时(由低电平变为高电平),如果此时 B 相为高电平,则判断为正转,计数器进行加 1 操作1      .当 A 相检测到上升沿脉冲时,若 B 相为低电平,则判断为反转,计数器进行减 1 操作1

5.1 CubeMX配置

        定时器的编码器模式设置:(一个定时器的编码器只能读取一个轮子的计数)

左轮

右轮

        这样写两个定时器来读取两个轮子的计数,有点浪费资源.不过更简单.还有另外一种方式,就是通过一个定时器来读取两个轮子的计数,通过定时器的定时中断.

定时器二中断

        这个定时器的作用就是用来定时读取编码器的值,并且通过定时中断函数来定时将编码器的值归零.因为编码器的值不能无限大么一定要定时清零

使能中断

5.2 keil设置

.c文件

#include "encoder.h"int32_t leftSpeed;
int32_t rightSpeed;void Encoder_Init()
{HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_1); // 开启编码器AHAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_2); // 开启编码器BHAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_1); // 开启编码器AHAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_2); // 开启编码器BHAL_TIM_Base_Start_IT(&htim2);                // 使能定时器2中断
}void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{static unsigned char i = 0;if (htim == (&htim2)){//1.获取电机速度yleftSpeed = (short) __HAL_TIM_GET_COUNTER(&htim3)*3.125;  rightSpeed = (short) __HAL_TIM_GET_COUNTER(&htim4)*3.125; // TIM4计数器获得电机脉冲,该电机在10ms采样的脉冲/18则为实际转速的rpm__HAL_TIM_SET_COUNTER(&htim3,0);  // 计数器清零__HAL_TIM_SET_COUNTER(&htim4,0);  // 计数器清零i++;if(i>20){// 打印定时器4的计数值,short(-32768——32767)usb_printf("leftSpeed = %d ,rightSpeed = %d \r\n",leftSpeed,rightSpeed);	i=0;}		//Control();}}

可以通过输出编码器到串口的值来测试是否正确.(这样的输出可能会非常快)

.h文件

#ifndef __ENCODER_H_
#define __ENCODER_H_#include "main.h"
#include "tim.h"
#include "usb_device.h"
#include "trace.h"extern TIM_HandleTypeDef htim4;
extern TIM_HandleTypeDef htim3;
extern TIM_HandleTypeDef htim2;void Encoder_Init();extern int32_t leftSpeed;
extern int32_t rightSpeed;extern int32_t Speed_Middle;          // 中值速度
extern int32_t max;											//输出最大值
extern int32_t min;									//输出最小值
extern int32_t Motor_Left, Motor_Right;		//输出左右轮速度#endif

5.3 测试

一定要初始化编码器相关定时器

        我给大家提个建议这个问题是我最早用TI芯片发现的但是现在也不知道是什么原因导致的.就是在定时中断的时候不要使用串口输出的函数,在这里面就是uart_printf.如果使用就是跳出定时中断函数.这个问题我找了一些资料但是还是没有发现原因.大家如果知道可以给我说说

        我这里输出是因为设置leftSpeed  ,rightSpeed 为全局变量,在Control中输出的.

六.蓝牙控制小车设置

        之前给大家说的使用USB虚拟串口的雷就是在这里.

6.1 蓝牙模块的基本介绍

        这里使用串口助手的波特率一定要与蓝牙模块的波特率一致,9600.

本来使用正常的串口转接口是要进行测试的

在正常的串口是使用一个串口中断识别不同的串口,

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,
uint16_t Size)
{
if(huart == &huart1){
HAL_UART_Transmit(&huart3, (const
uint8_t*)uart1_rx_buf, Size, HAL_MAX_DELAY);
}else if(huart == &huart3){
bluetooth_control_car(uart3_rx_buf);
HAL_UART_Transmit(&huart1, (const
uint8_t*)uart3_rx_buf, Size, HAL_MAX_DELAY);
}
uart_interrupt_init();
}
进行测试,发送AT指令
        但是由于设备的局限性,我试了一下,发送指令没什么用.就直接控制小车了.测试不了.

6.2 CubeMX配置

串口uart设置

使能串口中断

6.3keil设置

#include "bluetooth.h"uint8_t USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART2_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14~0:接收到的有效字节数目
uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存char uart2_rx_buf[1024]={0};
int rev;
int speed=20;//蓝牙串口初始化
void Blue_Init()
{HAL_UARTEx_ReceiveToIdle_IT(&huart2, (uint8_t*)uart2_rx_buf, sizeof(uart2_rx_buf)); //开启串口2的接收中断
}
//串口中断函数,用于接受数据
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,uint16_t Size)
{if(huart == &huart2){//HAL_UART_Transmit(&hUsbDeviceFS, (const uint8_t*)uart2_rx_buf, Size, HAL_MAX_DELAY);rev=uart2_rx_buf[0]-48;Blue_control(rev);usb_printf("uart2_rx_buf=%d\r\n",uart2_rx_buf[0]);Blue_Init();}
}//蓝牙控制函数
void Blue_control(int res)
{switch (res){case 0: motor_speed(speed,speed); break;	//直行case 1: motor_speed(-speed,-speed); break;	//反行case 2: motor_speed(speed,0); break;		//左转case 3: motor_speed(0,speed); break;	//右转case 4: motor_speed(speed,-speed); break;	//正自身旋转;case 5: motor_speed(-speed,speed); break;	//反自身旋转;case 6: speed+=10; break;	//速度增加10case 7: speed-=10; break;	//速度减小10case 8: motor_speed(0,0);;	//停止default: motor_speed(0,0); break;}
}

        至于为什么  rev=uart2_rx_buf[0]-48;  它返回来的数据要-48,这是因为我发送的时候发送0,它给我返回来的就是48.所以就进行了这一步操作.

.h文件

#ifndef __BLUETOOTH_H_
#define __BLUETOOTH_H_#include "main.h"
#include "motor.h"
#include "usb_device.h"extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint32_t Recv_dlen;
extern uint8_t UserRxBuffer[1024];extern UART_HandleTypeDef huart2;//声明USART2的HAL库结构体#define USART2_REC_LEN  200//定义USART2最大接收字节数extern uint8_t  USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.末字节为校验和
extern uint16_t USART2_RX_STA;//接收状态标记
extern uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart);
void Blue_Init();
void Blue_control(int res);
#endif

6.4 测试

        微信搜索串口蓝牙助手小程序

找到对应的蓝牙

发送数据进行测试

蓝牙控制小车

七. 寻迹小车设置

        寻迹小车,主要依靠七路灰度传感器,进行寻迹.

7.1 CubeMX配置

        只需要设置七路普通的输入IO即可

其他六路同理

7.2 keil设置

        这里的寻迹,涉及到了一个知识点就是pid控制算法.如果刚开始弄可以不加pid,直接多对速度进行操作.pid也就是对速度进行了一个控制.

        简单讲一下pid.pid就是三个参数,p-比例,i-积分,d-微分.p就是直接控制响应速度变化的量.联系积分和微分的物理.积分就是累积量.根据过去的速度对当前速度的调整,就是i.微分就是变化率,也就是预判未来速度,对当前速度做出的调整.

        一般初学者都会面临pid的调参,我建议大家刚开始选择增量式pid.增量式pid的参数相比较于位置式的参数还是好调的.因为增量式的误差大多只会对前两个误差进行保留,而位置式是会对误差一直进行累计.所以就要对积分进行限幅.但是位置式的pid响应更好,效果更卓越.

        pid我觉的也是一门博大精深的学问,很值得好好学习一下.这里有一个网站,在我学习pid的时候,这个网站,我认为还是比较全而且,总结的很好的网站.

PID控制器开发笔记.md · Lfj/PID控制算法 - Gitee.com

        这个博客时让初学者借鉴一下,所以pid的代码我虽然会写,但是执行的时候没有用到,直接对速度加减来操作的.

.c文件

#include "trace.h"int32_t Speed_Middle = 40;          // 中值速度
int32_t max=80;											//输出最大值
int32_t min=-80;										//输出最小值
int32_t Motor_Left, Motor_Right;		//输出左右轮速度的中间值
int32_t	Motor_Left_speed,Motor_Right_speed;//输出左右轮速度float Kp = 1;float Ki = 0.1;float Kd = 0;float integral = 0;		//累计误差float prev_error = 0;	//上次误差float prev_integral = 0;//寻迹函数
int Incremental_Quantity() {int value = 0;if (!P1) // 检测到最右端value += 36;if (!P2)value += 24;if (!P3)value += 12;if (!P4)value += 0;if (!P5)value -= 12;if (!P6)value -= 24;if (!P7)value -= 36;return value;
}//控制小车
void Control()
{int32_t leftTarget,rightTarget;	//目标设置左右轮速度int32_t bias;										//巡线偏差bias=Incremental_Quantity();leftTarget = Speed_Middle-bias;rightTarget = Speed_Middle+bias;//usb_printf("leftTarget=%d\r\n",leftTarget);//usb_printf("rightTarget=%d\r\n",rightTarget);Motor_Left  = Limit(leftTarget, max, min);Motor_Right = Limit(rightTarget, max,min);//usb_printf("Motor_Left=%d\r\n",Motor_Left);//usb_printf("Motor_Right=%d\r\n",Motor_Right);//Motor_Left_speed = (int32_t)PID_Compute(leftSpeed,Motor_Left);//Motor_Right_speed = (int32_t)PID_Compute(rightSpeed,Motor_Right);Motor_Left_speed = (int32_t)PID_Update(leftSpeed,Motor_Left);Motor_Right_speed = (int32_t)PID_Update(rightSpeed,Motor_Right);usb_printf("Motor_Left_speed=%d\r\n",Motor_Left_speed);usb_printf("Motor_Right_speed=%d\r\n",Motor_Right_speed);usb_printf("leftspeed=%d\r\n",leftSpeed);usb_printf("rightSpeed=%d\r\n",rightSpeed);motor_speed(Motor_Left,Motor_Right);//motor_speed(50,50);
}int32_t Limit(int32_t IN, int32_t limit, int32_t limiter)
{int32_t OUT = IN;if (OUT > limit)OUT = limit;if (OUT < limiter)OUT = limiter;return OUT;
}// 初始化PID控制器
void PID_Init(PIDController *pid, float Kp, float Ki, float Kd, float setpoint) {pid->Kp = Kp;pid->Ki = Ki;pid->Kd = Kd;pid->setpoint = setpoint;pid->integral = 0.0;pid->prev_error = 0.0;
}// 位置式PID计算
float PID_Compute(float current_value,float setpoint) 
{float error = setpoint - current_value;  // 计算当前误差integral += error;  // 累积误差float derivative = error - prev_error;  // 计算误差的微分// 计算PID输出float output = Kp * error + Ki * integral + Kd * derivative;prev_error = error;  // 更新上一次的误差return output;
}
//增量式pid计算
float PID_Update( float measured_value,float setpoint) 
{float error = setpoint - measured_value; // 当前误差float integral = prev_integral + error; // 积分项float derivative = error - prev_error; // 微分项// 计算控制增量float delta_output = Kp * error + Ki * integral +Kd * derivative;// 更新状态prev_error = error;prev_integral = integral;return delta_output;
}

        这里的Control函数我是直接在读取编码器的那个定时函数里面执行的.

.h文件

#ifndef __TRACE_H_
#define __TRACE_H_#include "main.h"
#include "motor.h"#define P1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define P2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define P3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define P4 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10)
#define P5 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)
#define P6 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)
#define P7 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)// 定义PID结构体
typedef struct {float Kp;  // 比例系数float Ki;  // 积分系数float Kd;  // 微分系数float setpoint;  // 设定值float integral;  // 误差积分float prev_error;  // 上一次的误差
}	PIDController;extern int32_t Speed_Middle;          // 中值速度
extern int32_t max;											//输出最大值
extern int32_t min;									//输出最小值
extern int32_t Motor_Left, Motor_Right;		//输出左右轮速度extern PIDController pid;
extern int32_t leftSpeed;
extern int32_t rightSpeed;extern int Incremental_Quantity();
extern void Control();
extern int32_t Limit(int32_t IN, int32_t limit, int32_t limiter);
extern void PID_Init(PIDController *pid, float Kp, float Ki, float Kd, float setpoint);
extern float PID_Compute(float current_value,float setpoint);
extern float PID_Update( float measured_value,float setpoint);#endif

7.4 测试

寻迹小车

总结

        这篇博客主要是对刚学习单片机的初学者,想要做个小东西,没有什么方向,做一个借鉴.主要是蓝牙控制和七路灰度传感器的寻迹.

相关文章:

stm32hal库寻迹+蓝牙智能车(STM32F103C8T6)

简介: 这个小车的芯片是STM32F103C8T6&#xff0c;其他的芯片也可以照猫画虎,基本配置差不多,要注意的就是,管脚复用,管脚的特殊功能,(这点不用担心,hal库每个管脚的功能都会给你罗列,很方便的.)由于我做的比较简单,只是用到了几个简单外设.主要是由带霍尔编码器电机的车模,电机…...

centos22.04 dpkg -l 输出状态标识含义

dpkg -l 输出状态标识含义 dpkg -l 命令用于列出系统中已安装的软件包,每行输出的前两个字符是软件包状态的标识,不同的组合代表不同的状态,具体含义如下: 第一个字符:表示期望的状态(Desired state) u:未知(Unknown)i:安装(Install)r:移除(Remove)p:清除(Pu…...

前端TypeScript 面试题及参考答案

目录 解释 unknown 与 any 的区别,如何安全使用 unknown 类型? 如何用类型守卫处理联合类型变量的方法调用? 实现一个工具类型 Nullable ,使 T 可被赋值为 null/undefined 如何用 keyof 和 in 关键字实现枚举类型到联合类型的转换? 类型断言 as 与尖括号语法有何差异…...

基于 Vue.js 和 Element UI 实现九宫格按钮拖拽排序功能 | 详细教程与代码实现

在Vue.js项目中使用vue-element-template&#xff08;基于Element UI&#xff09;实现按钮的九宫格拖拽排序功能&#xff0c;可以通过以下步骤实现。我们将使用vuedraggable库来实现拖拽排序功能。 1. 安装依赖 首先&#xff0c;确保你已经安装了vuedraggable库&#xff1a; …...

Spring Framework测试工具MockMvc介绍

目录 一、基本概念 二、主要特点 三、使用场景 四、工作原理 五、示例代码 接口创建 测试类创建 六、注解解释 AutoConfigureMockMvc WebMvcTest 一、基本概念 MockMvc实现了对Http请求的模拟&#xff0c;能够直接使用网络的形式&#xff0c;转换到Controller的调用…...

nginx 正向代理与反向代理

1. 正向代理&#xff08;Forward Proxy&#xff09; 正向代理是指 代理客户端 访问目标服务器&#xff0c;通常用于访问受限资源或隐藏客户端 IP。 工作原理 客户端请求代理服务器&#xff08;如 nginx&#xff09;。代理服务器代表客户端向目标网站发起请求。目标网站返回内…...

VUE 获取视频时长,无需修改数据库,前提当前查看视频可以得到时长

第一字段处 <el-table-column label"视频时长" align"center"> <template slot-scope"scope"> <span>{{ formatDuration(scope.row.duration) }}</span> </template> </el-ta…...

使用Jenkins实现Windows服务器下C#应用程序发布

背景 在现代化的软件开发流程中&#xff0c;持续集成和持续部署&#xff08;CI/CD&#xff09;已经成为不可或缺的一部分。 Jenkins作为一款开源的自动化运维工具&#xff0c;能够帮助我们实现这一目标。 本文将详细介绍如何在Windows服务器下使用Jenkins来自动化发布C#应用…...

蓝桥杯练习代码

一、最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ""。 示例 1: 输入:strs = ["flower","flow","flight"] 输出:"fl"示例 2: 输入:strs = ["dog",&q…...

算法-二叉树篇11-左叶子之和

左叶子之和 力扣题目链接 题目描述 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 解题思路 层次遍历的时候&#xff0c;保留每层第一个节点并相加即可。 题解 class Solution { public:int sumOfLeftLeaves(TreeNode* root) {if(root NULL){return 0;}re…...

[java基础-JVM篇]1_JVM自动内存管理

JVM内存管理涉及但不限于类加载、对象分配、垃圾回收等&#xff0c;本篇主要记录运行时数据区域与对象相关内容。 内容主要来源《深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践》与官方文档&#xff0c;理解与表述错漏之处恳请各位大佬指正。 目录 运行时数据区域 栈 栈…...

Junit框架缺点

JUnit 是 Java 生态中最流行的单元测试框架&#xff0c;广泛应用于单元测试和集成测试中。尽管它功能强大且易于使用&#xff0c;但也存在一些缺陷和局限性。以下是 JUnit 的主要缺点&#xff1a; 1. 功能相对固定 问题&#xff1a;JUnit 的核心功能相对固定&#xff0c;缺乏灵…...

机器学习数学基础:34.克隆巴赫α系数

克隆巴赫α系数&#xff08;Cronbach’s Alpha&#xff09;超详细教程 专为小白打造&#xff0c;零基础也能轻松学会&#xff01; 一、深度理解α系数 克隆巴赫α系数&#xff08;Cronbach’s Alpha&#xff09;是在评估测验质量时极为关键的一个指标&#xff0c;主要用于衡量…...

【Linux】vim 设置

【Linux】vim 设置 零、起因 刚学Linux&#xff0c;有时候会重装Linux系统&#xff0c;然后默认的vi不太好用&#xff0c;需要进行一些设置&#xff0c;本文简述如何配置一个好用的vim。 壹、软件安装 sudo apt-get install vim贰、配置路径 对所有用户生效&#xff1a; …...

JavaScript系列(90)--前端脚手架开发

前端脚手架开发 &#x1f6e0;️ 前端脚手架是现代前端开发流程中的重要工具&#xff0c;它能够帮助开发者快速初始化项目结构、配置开发环境、设置构建流程&#xff0c;从而提高开发效率和标准化项目结构。本文将详细介绍前端脚手架的开发原理、实现方式以及最佳实践。 脚手…...

工程实践中常见的几种设计模式解析及 C++ 实现

工程实践中常见的几种设计模式解析及 C 实现 在软件工程中&#xff0c;设计模式是一种通用的解决方案&#xff0c;用于解决常见问题和优化代码结构。它们通过提供一种规范化的编程思想&#xff0c;帮助开发者写出更高效、可维护和可扩展的代码。本文将介绍几种在工程实践中常见…...

基于Python+django+mysql旅游数据爬虫采集可视化分析推荐系统

2024旅游推荐系统爬虫可视化&#xff08;协同过滤算法&#xff09; 基于Pythondjangomysql旅游数据爬虫采集可视化分析推荐系统 有文档说明 部署文档 视频讲解 ✅️基于用户的协同过滤推荐算法 卖价就是标价~ 项目技术栈 Python语言、Django框架、MySQL数据库、requests网络爬虫…...

Oracle 12c Docker安装问题排查 sga_target 1536M is too small

一、问题描述 在虚拟机环境&#xff08;4核16GB内存&#xff09;上部署 truevoly/oracle-12c 容器镜像时&#xff0c;一切运行正常。然而&#xff0c;当在一台 128 核 CPU 和 512GB 内存的物理服务器上运行时&#xff0c;容器启动时出现了 ORA-00821 等错误&#xff0c;提示 S…...

es-head(es库-谷歌浏览器插件)

1.下载es-head插件压缩包&#xff0c;并解压缩 2.谷歌浏览器添加插件 3.使用...

C++大整数类的设计与实现

1. 简介 我们知道现代的计算机大多数都是64位的&#xff0c;因此能处理最大整数为 2 64 − 1 2^{64}-1 264−1。那如果是超过了这个数怎么办呢&#xff0c;那就需要我们自己手动模拟数的加减乘除了。 2. 思路 我们可以用一个数组来存储大数&#xff0c;数组中的每一个位置表…...

Linux网络基础(协议 TCP/IP 网络传输基本流程 IP VS Mac Socket编程UDP)

文章目录 一.前言二.协议协议分层分层的好处 OSI七层模型TCP/IP五层(或四层)模型为什么要有TCP/IP协议TCP/IP协议与操作系统的关系(宏观上是如何实现的)什么是协议 三.网络传输基本流程局域网(以太网为例)通信原理MAC地址令牌环网 封装与解包分用 四.IP地址IP VS Mac地址 五.So…...

Web开发:ORM框架之使用Freesql的导航属性

一、什么时候用导航属性 看数据库表的对应关系&#xff0c;一对多的时候用比较好&#xff0c;不用多写一个联表实体&#xff0c;而且查询高效 二、为实体配置导航属性 1.给关系是一的父表实体加上&#xff1a; [FreeSql.DataAnnotations.Navigate(nameof(子表.子表关联字段))]…...

NLP07-朴素贝叶斯问句分类之数据集加载(1/3)

一、概述 数据集加载&#xff08;Dataset Loading&#xff09;是机器学习、自然语言处理&#xff08;NLP&#xff09;等领域中的一个重要步骤&#xff0c;指的是将外部数据&#xff08;如文件、数据库、网络接口等&#xff09;加载到程序中&#xff0c;以便进行后续处理、分析…...

Rk3568驱动开发_点亮led灯(手动挡)_5

1.MMU简介 完成虚拟空间到物理空间的映射 内存保护设立存储器的访问权限&#xff0c;设置虚拟存储空间的缓冲特性 stm32点灯可以直接操作寄存器&#xff0c;但是linux点灯不能直接访问寄存器&#xff0c;linux会使能mmu linux中操作的都是虚拟地址&#xff0c;要想访问物理地…...

LangChain构建行业知识库实践:从架构设计到生产部署全指南

文章目录 引言:行业知识库的进化挑战一、系统架构设计1.1 核心组件拓扑1.2 模块化设计原则二、关键技术实现2.1 文档预处理流水线2.2 混合检索增强三、领域适配优化3.1 医学知识图谱融合3.2 检索结果重排序算法四、生产环境部署4.1 性能优化方案4.2 安全防护体系五、评估与调优…...

Vscode编辑器:解读文件结构、插件的导入导出、常用快捷键配置技巧及其常见问题的解决方案

一、文件与文件夹结构 1.文件结构 文件名作用.babelrc配置 Babel 编译选项&#xff0c;指定代码转译规则。.editorconfig定义项目代码格式规范&#xff0c;如缩进风格和空格数量等。.eslintignore列出 ESLint 忽略的文件或文件夹。.eslintrc.js配置 ESLint 的规则和插件。.gi…...

androidstudio 运行项目加载很慢,优化方法

一、Android Studio 运行项目加载缓慢可能由多种原因引起&#xff0c;以下是一些优化建议&#xff1a; 1. 升级硬件配置 内存&#xff1a;建议至少 8GB&#xff0c;16GB 或以上更佳。 SSD&#xff1a;使用 SSD 替代 HDD 以加快读写速度。 CPU&#xff1a;多核处理器有助于提…...

Vue性能翻倍秘籍

导读&#xff1a;某电商大促因工程化缺失导致页面崩溃&#xff01;本文通过双11级别流量压测&#xff0c;揭秘Vue项目性能优化的6大核心策略&#xff0c;涵盖构建提速、首屏优化、SSR实战等全链路方案。 工程化缺失引发的灾难现场 血泪案例&#xff1a; 某电商大促活动因工程化…...

线性回归 (Linear Regression)案例分析1

广告费用与产品销量 工欲善其事必先利其器数据分析1. 检查缺失值、异常值3. 散点图查看特征、响应相关性3. 热力图查看特征、响应相关性 特征工程1、导入必要工具包2、读取数据3、数据标准化4、保存特征工程的结果到文件&#xff0c;供机器学习模型使用 模型选择读取数据数据准…...

uni-app集成sqlite

Sqlite SQLite 是一种轻量级的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;广泛应用于各种应用程序中&#xff0c;特别是那些需要嵌入式数据库解决方案的场景。它不需要单独的服务器进程或系统配置&#xff0c;所有数据都存储在一个单一的普通磁盘文件中&am…...