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 安装开发包
1.2 选择开发的STM32芯片
1.3 配置时钟
1.4 生成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配置参数说明
- 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
- 自动重装载预装载使能。这意味着自动重装载值可以在运行时更新。
- Prescaler (PSC - 16 bit... 71)
- 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 信号的初始电平。
- Mode PWM mode 1
在函数运行的时候通过下图这个函数进行改变通道风比较值进行设置占空比.
关于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();
}

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,其他的芯片也可以照猫画虎,基本配置差不多,要注意的就是,管脚复用,管脚的特殊功能,(这点不用担心,hal库每个管脚的功能都会给你罗列,很方便的.)由于我做的比较简单,只是用到了几个简单外设.主要是由带霍尔编码器电机的车模,电机…...
JavaScript知识点4
1.解释一下这段JavaScript代码 var fruits ["Apple", "Orange", "Apple", "Mango"]; var a fruits.indexOf("Apple",-1); console.log("index"a); 输出的a值为-1,indexOf的第二个参数是-1…...

形式化数学编程在AI医疗中的探索路径分析
一、引言 1.1 研究背景与意义 在数字化时代,形式化数学编程和 AI 形式化医疗作为前沿领域,正逐渐改变着我们的生活和医疗模式。形式化数学编程是一种运用数学逻辑和严格的形式化语言来描述和验证程序的技术,它通过数学的精确性和逻辑性,确保程序的正确性和可靠性。在软件…...

QT 引入Quazip和Zlib源码工程到项目中,无需编译成库,跨平台,加密压缩,带有压缩进度
前言 最近在做项目时遇到一个需求,需要将升级的文件压缩成zip,再进行传输; 通过网络调研,有许多方式可以实现,例如QT私有模块的ZipReader、QZipWriter;或者第三方库zlib或者libzip或者quazip等࿱…...

Ubuntu 安装 Nginx并配置反向代理
Ubuntu版本:Ubuntu 24.04.2 LTS 一、安装Nginx 更新系统软件包 安装前需确保系统处于最新状态,避免依赖冲突 sudo apt update && sudo apt upgrade -y 安装Nginx主程序 Ubuntu官方仓库已包含稳定版Nginx,直接安装即可 sudo…...
GitHub SSH连接问题解决指南
🔍 GitHub SSH连接问题解决指南 问题描述 遇到错误:ssh: connect to host github.com port 22: Connection refused 说明您的网络环境无法访问GitHub的SSH端口22,常见原因: 防火墙/网络运营商限制(国内常见…...
C++ 跨平台的 GetCurrentThreadId() 获取当前线程ID实现
支持:C11 及早前标准库版本,而无需使用:std::this_thread::get_id()。 支持:NDK/ANDROID、Windows、Linux、MacOS X 等多个操作系统平台。 int64_t GetCurrentThreadId() noexcept { #if defined(_WIN32) || defined(_WIN64)retu…...

钉钉MAKE AI生态大会思考
1. 核心特性 1.1 底层模型开放 除原有模型通义千问外,新接入猎户星空、智普、MinMax、月之暗面、百川智能、零一万物。 1.2 AI搜索 AI搜索贯通企业和个人散落在各地的知识(聊天记录、文档、会议、日程、知识库、项目等),通过大模型对知识逻辑化,直接生成搜索的答案,并…...
SQL笔记#复杂查询
一、视图 1、视图和表 使用试图时会执行SELECT语句并创建一张临时表。视图中保存的是SELECT语句;表中保存的是实际数据。 2、创建视图的方法 CREATE VIEW 视图名称(<视图列名1>,<视图列名2>,……) AS <SELECT语句> CREATE VIEW ProductSum (prod…...

【Linux】基于UDP/TCP套接字编程与守护进程
目录 一、网路套接字编程 (一)基础概念 1、源IP地址与目的IP地址 2、端口号 3、TCP与UDP 4、网络字节序 (二)套接字编程接口 1、socket 常见API 2、sockaddr结构 (三)UDP套接字 1、UDP服务器创建…...
springboot 引入前端
前端 打包 npm run build vue.config.js 文件 publicPath 默认建议保持 / publicPath: ‘/’ 后端 目录 粘贴下面目录之一: src/main/resources/static/ src/main/resources/public/ 补充(用的少) server:servlet:context-path: /thirdAdm…...

RTSP/Onvif安防平台EasyNVR接入EasyNVS显示服务缺失的原因与解决方案
EasyNVS云管理平台具备强大的汇聚与管理功能,支持EasyGBS、EasyNVR等平台的接入,能够将接入的视频资源进行统一输出,提供远程可视化运维等管理功能,特别适合解决设备现场没有固定公网IP但仍需在公网直播的需求。 在某次用户现场部…...

算法系列之回溯算法
在计算机科学领域,算法是解决问题的核心。回溯算法作为一种经典的算法设计技巧,以其试错和回退的思想,在解决许多复杂问题时展现出强大的能力。本文将深入探讨回溯算法,包括其核心概念、实现步骤、代码示例以及适用场景࿰…...
Uniapp 小程序接口封装与使用
深入理解 Uniapp 小程序接口封装与使用 在 Uniapp 小程序开发中,接口请求是获取和交互数据的关键部分。合理地封装接口不仅能提高代码的可维护性,还能增强项目的健壮性。今天,我们就来详细探讨一下如何在 Uniapp 中进行接口封装、引入以及使…...

Harmony开发笔记(未完成)
一、感想 作为一名拥有11年经验的Android开发者,我亲历了Android从高速发展到如今面临“僧多粥少”的过程。技术的世界瞬息万变,没有一种技术能够让人依赖一辈子。去年初,我自学了鸿蒙系统,并顺利通过了鸿蒙官方的初级和高级认。…...

观成科技:海莲花“PerfSpyRAT”木马加密通信分析
1.概述 在2024年9月中旬至10月,东南亚APT组织“海莲花”通过GitHub发布开源安全工具项目,针对网络安全人员发起了定向攻击。通过对相关攻击活动进行分析,可以将其与一些海莲花的样本关联起来。这些样本的通信数据结构与海莲花此前使用的攻击…...
Spring Boot @Async 注解深度指南
Spring Boot Async 注解深度指南 一、核心使用要点 启用异步支持 必须在启动类或配置类添加 EnableAsync,否则异步不生效。 SpringBootApplication EnableAsync public class Application { ... }线程池配置 默认问题:Spring 默认使用 SimpleAsyncTaskEx…...

windows设置暂停更新时长
windows设置暂停更新时长 win11与win10修改注册表操作一致 ,系统界面不同 1.打开注册表 2.在以下路径 \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键新建 DWORD 32位值,名称为FlightSettingsMaxPauseDays 根据需求填写数…...

Orange 开源项目 - 集成百度智能云-千帆大模型
1 集成百度智能云-千帆大模型 百度智能云-千帆ModelBuilder百度智能云千帆大模型服务与开发平台ModelBuilder(以下简称千帆ModelBuilder)是面向企业开发者的一站式大模型开发及服务运行平台。千帆ModelBuilder不仅提供了包括文心一言底层模型和第三方开源…...

特斯拉 FSD 算法深度剖析:软件层面全解读
一、引言 特斯拉的 FSD(Full Self-Driving)系统作为自动驾驶领域的前沿成果,其软件层面的算法设计至关重要。本文将从软件的角度,深入探讨特斯拉 FSD 所采用的算法,包括感知、规划、控制等多个方面,以期为…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...