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库每个管脚的功能都会给你罗列,很方便的.)由于我做的比较简单,只是用到了几个简单外设.主要是由带霍尔编码器电机的车模,电机…...
云服务中的“高可用性架构”是怎样的?
在云计算中,“高可用性架构”(High Availability Architecture,简称HA架构)是指通过设计和配置,使得系统、服务或应用能够在发生硬件故障、软件故障、网络问题等情况下,依然保持正常运行,最大程…...
StableDiffusion+ComfyUI
一、AI、AIGC、AIAgent基本概念 图形生成大模型:StableDiffusion(逼真,开源)、Midjourney(艺术性,商业)、FLUX(复杂场景,开源商业)工作流程构建工具:ComfyUI智能体:COZE、Dify、FastGPTAI视频编辑工具&…...
MATLAB基础学习相关知识
MATLAB安装参考:抖音-记录美好生活 MATLAB基础知识学习参考:【1小时Matlab速成教程-哔哩哔哩】 https://b23.tv/CnvHtO3 第1部分:变量定义和基本运算 生成矩阵: % 生成矩阵% 直接法% ,表示行 ;表示列 a [1,2,3;4,5,6;7,8,9];%…...
设计模式 之 工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)(C++)
文章目录 C 工厂模式引言一、简单工厂模式概念实现步骤示例代码优缺点 二、工厂方法模式概念实现步骤示例代码优缺点 三、抽象工厂模式概念实现步骤示例代码优缺点 C 工厂模式 引言 在 C 编程中,对象的创建是一个常见且基础的操作。然而,当项目规模逐渐…...
windows下docker使用笔记
目录 镜像的配置 镜像的拉取 推荐镜像源列表(截至2025年2月测试有效) 配置方法 修改容器名字 如何使用卷 创建不同的容器,每个容器中有不同的mysql和java版本(不推荐) 1. 安装 Docker Desktop(Win…...
SQLMesh 系列教程6- 详解 Python 模型
本文将介绍 SQLMesh 的 Python 模型,探讨其定义、优势及在企业业务场景中的应用。SQLMesh 不仅支持 SQL 模型,还允许通过 Python 编写数据模型,提供更高的灵活性和可编程性。我们将通过一个电商平台的实例,展示如何使用 Python 模…...
【时时三省】(C语言基础)用N-S流程图表示算法
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 N-S流程图 既然用基本结构的顺序组合可以表示任何复杂的算法结构,那么,基本结构之间的流程线就是多余的了。1973年,美国学者I.Nassi和B .Shneiderman提出…...
【HarmonyOS Next】鸿蒙监听手机按键
【HarmonyOS Next】鸿蒙监听手机按键 一、前言 应用开发中我们会遇到监听用户实体按键,或者扩展按键的需求。亦或者是在某些场景下,禁止用户按下某些按键的业务需求。 这两种需求,鸿蒙都提供了对应的监听事件进行处理。 onKeyEvent 默认的…...
Unreal5从入门到精通之在编辑器中更新 UserWidgets
前言 在虚幻中创建越来越复杂和灵活的 UserWidget 蓝图时,一个问题是它们在编辑器中的外观与它们在游戏中的最终外观可能有很大不同。 库存面板示例 假设你想创建一个通用的库存显示小部件。我们可以在整个 UI 中使用它,无论我们需要在哪里显示某些内容。 标题,描述所显示…...
Django 5实用指南(五)模板系统
Django5的模板系统是其核心功能之一,允许开发者将动态数据嵌入到HTML模板中,并根据不同的业务需求渲染页面。Django模板系统基于 Django模板语言(DTL),它提供了一些强大的功能,如模板标签、过滤器、条件语句…...
游戏引擎学习第114天
打开内容并回顾 目前正在讨论一个非常重要的话题——优化。当代码运行太慢,无法达到所需性能时,我们该怎么办。昨天,我们通过在代码中添加性能计数器,验证了一些性能分析的数据,这些计数器帮助我们了解每个操作需要的…...
Python 赋能 AI:从零实现图像分类
人工智能(AI)热度持续攀升,而 Python 作为 AI 开发的利器,以其简洁易学、生态丰富的特点,成为众多开发者的首选。本文以图像分类为例,带你用 Python 实现一个简单的 AI 模型。 1. 环境准备 首先,我们需要安装一些必要的 Python 库: pip install tensorflow keras nu…...
UE引擎游戏加固方案解析
据VGinsights的报告,近年来UE引擎在过去几年中市场占比显著增长,其中亚洲市场增幅达到了30%,随着UE5的推出和技术的不断进步,UE引擎在独立开发者和移动游戏开发中的应用也在逐步增加。 UE引擎的优势在于强大的画面表现与视觉特效…...
Http升级为Https - 开发/测试服环境
1.应用场景 主要用于开发/测试服环境将http升级为https, 防止前端web(浏览器)出现Mixed Content报错; 2.学习/操作 1.文档阅读 deepseek 问答; 2.整理输出 报错信息: Mixed Content: The page at <URL> was loaded over HTTPS, but requested an insecure XMLHttpRequ…...
SaaS系统租户隔离方案分析:基于域名与请求头的比较
在设计SaaS系统时,租户隔离是非常重要的设计考虑因素。租户隔离的方式决定了系统的可扩展性、安全性和维护性。常见的租户隔离方案包括基于域名和基于**请求头(header)**的隔离方式。每种方式都有其优缺点,具体选择应根据系统的需…...
调用click.getchar()时Windows PyCharm无法模拟键盘输入
文章目录 问题描述解决方案参考文献 问题描述 调用 click.getchar() 时,Windows PyCharm 无法模拟键盘输入 解决方案 Run → Edit Configurations… → Modify options → Emulate terminal in output console 参考文献 Terminal emulator | PyCharm Documentati…...
科普:“docker”与“docker compose”
一、安装Docker Desktop 安装Docker Desktop,则既安装了Docker,也安装了Docker Compose 从Docker Desktop官方下载页面(https://www.docker.com/products/docker-desktop/),选择适合Windows系统的版本进行下载安装。 验证: do…...
Windows 快速搭建C++开发环境,安装C++、CMake、QT、Visual Studio、Setup Factory
安装C 简介 Windows 版的 GCC 有三个选择: CygwinMinGWmingw-w64 Cygwin、MinGW 和 mingw-w64 都是在 Windows 操作系统上运行的工具集,用于在 Windows 环境下进行开发和编译。 Cygwin 是一个在 Windows 上运行的开源项目,旨在提供类Uni…...
【分布式理论12】事务协调者高可用:分布式选举算法
文章目录 一、分布式系统中事务协调的问题二、分布式选举算法1. Bully算法2. Raft算法3. ZAB算法 三、小结与比较 一、分布式系统中事务协调的问题 在分布式系统中,常常有多个节点(应用)共同处理不同的事务和资源。前文 【分布式理论9】分布式…...
GPT2 模型训练
GPT2 预训练模型 基座 专门供给别人使用的。 对中文分词是一个字一个字分,是Bert的分类方法 好处:灵活。 词库可以适应任何文章。 坏处:训练的难度更大。需要增加数据量 中文分词如果按词组分词 好处:需要的数据量小&#…...
蓝桥杯备考:递归初阶
什么是递归? 相信我们已经不陌生了,函数自己调用自己就叫递归 为什么要有递归? 当处理主问题时,遇到子问题,子问题的解决方法和主问题是一样的,这时候我们就要用到递归 解决流程:问题—》相…...
[C语言]指针进阶压轴题
下面代码打印结果是什么? #include<stdio.h> int main() {char* c[] { "ENTER","NEW","POINT","FIRST" };char** cp[] { c 3,c 2,c 1,c };char*** cpp cp;printf("%s\n", **cpp);printf("%s\n…...
YOLOv11-ultralytics-8.3.67部分代码阅读笔记-build.py
build.py ultralytics\data\build.py 目录 build.py 1.所需的库和模块 2.class InfiniteDataLoader(dataloader.DataLoader): 3.class _RepeatSampler: 4.def seed_worker(worker_id): 5.def build_yolo_dataset(cfg, img_path, batch, data, mode"train"…...
智能体(AI Agent、Deepseek、硅基流动)落地实践Demo——借助大模型生成报表,推动AI赋能企业决策
文章目录 一、 引言二、 系统设计与技术细节2.1 系统架构2.2 核心组件说明 三、 Demo 代码推荐博客: 四、输出年度营销报告1. 总销售额 根据提供的数据,年度总销售额为:740.0。2. 各产品销售额3. 各地区销售额4. 各产品在各地区的销售情况 分…...
mac os设置jdk版本
打开环境变量配置文件 sudo vim ~/.bash_profile 设置不同的jdk版本路径 # 设置JAVA_HOME为jdk17路径 export JAVA_HOME$(/usr/libexec/java_home -v 17)# 设置JAVA_HOME为jdk8路径 export JAVA_HOME$(/usr/libexec/java_home -v 1.8) 设置环境变量 # 将jdk加入到环境变量…...
Llama 3.1 本地电脑部署 Linux系统 【轻松简易】
本文分享在自己的本地电脑部署 llama3.1,而且轻松简易,快速上手。 这里借助Ollama工具,在Linux系统中进行大模型部署~ Llama3.1,有三个版本:8B、70B、405B Llama 3.1 405B 是第一个公开可用的模型,在常识…...
计算机网络安全之一:网络安全概述
1.1 网络安全的内涵 随着计算机和网络技术的迅猛发展和广泛普及,越来越多的企业将经营的各种业务建立在Internet/Intranet环境中。于是,支持E-mail、文件共享、即时消息传送的消息和协作服务器成为当今商业社会中的极重要的IT基础设施。然而࿰…...
docker 部署JAR
docker pull openjdk:23 使用Docker运行生成的JAR包是一个将应用程序容器化的好方法,它确保了你的应用可以在任何安装了Docker的环境中以相同的方式运行。以下是创建一个Docker镜像并运行包含你Java应用程序的JAR包的基本步骤。1. 准备 Dockerfile首先,在…...
深研究:与Dify建立研究自动化应用
许多个人和团队面临筛选各种网页或内部文档的挑战,以全面概述一个主题。那么在这里我推荐大家使用Dify,它是一个用于LLM应用程序开发的低代码,开源平台,它通过自动化工作流程的多步搜索和有效汇总来解决此问题,仅需要最小的编码。 在本文中,我们将创建“ Deepresearch”…...
第1章:LangChain4j的聊天与语言模型
LangChain4J官方文档翻译与解析 目标文档路径: https://docs.langchain4j.dev/tutorials/chat-and-language-models/ 语言模型的两种API类型 LangChain4j支持两种语言模型(LLM)的API: LanguageModel:这种API非常简单,…...
IPv6报头40字节具体怎么分配的?
目录 IPv6报头结构 字段详解 示例代码:IPv6报头的Python实现 输出示例 IPv6协议是为了解决IPv4地址耗尽问题而设计的下一代互联网协议。与IPv4相比,IPv6不仅提供了更大的地址空间,还简化了报头结构,提高了网络设备的处理效率。…...
Ubuntu ARM / aarch64 CPU 镜像下载:如何在 ARM 设备上安装和使用 Ubuntu
随着 ARM 架构的逐渐普及,尤其是在移动设备和高效能计算设备中的应用,许多开发者和用户开始关注基于 ARM 架构的操作系统。Ubuntu 作为一款广泛使用的 Linux 发行版,自然也为 ARM 架构提供了优化的版本。本文将详细介绍如何下载适用于 ARM / …...
图论入门算法:拓扑排序(C++)
上文中我们了解了图的遍历(DFS/BFS), 本节我们来学习拓扑排序. 在图论中, 拓扑排序(Topological Sorting)是对一个有向无环图(Directed Acyclic Graph, DAG)的所有顶点进行排序的一种算法, 使得如果存在一条从顶点 u 到顶点 v 的有向边 (u, v) , 那么在排序后的序列中, u 一定…...
MySQL中count(1)和count(*) 的区别
MySQL中count(1)和count(*) 的区别 在 MySQL 中,COUNT(1) 和 COUNT(*) 均用于统计查询结果中的行数,但它们在语义及其背后的机制上有一些区别。 基本功能 COUNT(*):统计表中所有行的数量,无论列是否为 NULL。 COUNT(1)…...
Android 14输入系统架构分析:图解源码从驱动层到应用层的完整传递链路
一、资料快车 1、深入了解Android输入系统:https://blog.csdn.net/innost/article/details/47660387 2、书籍 - Android系统源代码情景分析 二、Perface 1、参考: 2、系统程序分析方法 1)加入log,并跟着log一步步分析 -logc…...
Web入侵实战分析-常见web攻击类应急处置实验2
场景说明 某天运维人员,发现运维的公司站点被黑页,首页标题被篡改,你获得的信息如下: 操作系统:windows server 2008 R2业务:公司官网网站架构:通过phpstudy运行apache mysqlphp开放端口&…...
Jenkins 配置 Credentials 凭证
Jenkins 配置 Credentials 凭证 一、创建凭证 Dashboard -> Manage Jenkins -> Manage Credentials 在 Domain 列随便点击一个 (global) 二、添加 凭证 点击左侧 Add Credentials 四、填写凭证 Kind:凭证类型 Username with password: 配置 用…...
Android Http-server 本地 web 服务
时间:2025年2月16日 地点:深圳.前海湾 需求 我们都知道 webview 可加载 URI,他有自己的协议 scheme: content:// 标识数据由 Content Provider 管理file:// 本地文件 http:// 网络资源 特别的,如果你想直接…...
selenium爬取苏宁易购平台某产品的评论
目录 selenium的介绍 1、 selenium是什么? 2、selenium的工作原理 3、如何使用selenium? webdriver浏览器驱动设置 关键步骤 代码 运行结果 注意事项 selenium的介绍 1、 selenium是什么? 用于Web应用程序测试的工具。可以驱动浏览…...
Linux中POSIX应用场景
Linux 提供了丰富的 POSIX(Portable Operating System Interface)标准接口,这些接口可以帮助开发者编写可移植、高效的应用程序。POSIX 标准定义了一系列系统调用和库函数,涵盖了文件操作、进程管理、线程管理、信号处理、同步机制…...
Boringssl介绍
BoringSSL 是 Google 从 OpenSSL 分支出来的一个开源 TLS/SSL 库,旨在为 Google 的产品和服务提供一个更加轻量、安全和现代化的加密库。它是 OpenSSL 的一个替代品,专注于简化代码、提高安全性和减少潜在的攻击面。 以下是对 BoringSSL 的详细介绍&…...
react实例与总结(二)
目录 一、脚手架基础语法(16~17) 1.1、hello react 1.2、组件样式隔离(样式模块化) 1.3、react插件 二、React Router v5 2.1、react-router-dom相关API 2.1.1、内置组件 2.1.1.1、BrowserRouter 2.1.1.2、HashRouter 2.1.1.3、Route 2.1.1.4、Redirect 2.1.1.5、L…...
僵尸进程、孤儿进程
一、僵尸进程、孤儿进程 僵尸进程、孤儿进程 1、僵尸进程: (1)定义:子进程死了(内存、打开的文件等都释放了),但没有完全死(数据结构<包含进程ID,进程状态、运行时…...
快速入门Springboot+vue——MybatisPlus多表查询及分页查询
学习自哔哩哔哩上的“刘老师教编程”,具体学习的网站为:7.MybatisPlus多表查询及分页查询_哔哩哔哩_bilibili,以下是看课后做的笔记,仅供参考。 多表查询 多表查询[Mybatis中的]:实现复杂关系映射,可以使…...
redis解决高并发看门狗策略
当一个业务执行时间超过自己设定的锁释放时间,那么会导致有其他线程进入,从而抢到同一个票,所有需要使用看门狗策略,其实就是开一个守护线程,让守护线程去监控key,如果到时间了还未结束,就会将这个key重新s…...
深入解析 MySQL 8 C++ 源码:二级索引回表操作
在数据库系统中,索引是优化查询性能的关键技术之一。MySQL 的 InnoDB 存储引擎支持多种索引类型,其中二级索引(非聚簇索引)和聚簇索引(主键索引)是最常见的两种。然而,由于二级索引的叶子节点只…...
MySQL如何解决幻读?
目录 一、什么是幻读? 1.1 幻读的定义 1.2 幻读的示例 1.3 幻读产生的原因? 1.4?读已提交(Read Committed) 1.4.1 确定事务等级 1.4.2 非锁定读取 准备 示例 结论 1.4.3 锁定读取 准备 示例 分析 结论 1.5?可重…...
RabbitMQ的脑裂(网络分区)问题
问题描述: Mnesia reports that this RabbitMQ cluster has experienced a network partition. There is a risk of losing data 一、什么是MQ脑裂? 网络分区 如果另一个节点在一段时间内(默认为 60 秒)无法与其联系࿰…...
【网络安全 | 漏洞挖掘】价值$40000:从路径遍历升级至RCE
未经许可,不得转载。 文章目录 路径遍历RCE路径遍历 在进行目标侦查和端口扫描时,我发现了一个使用 8443 端口的子域名:http://admin.target.com:8443。许多人可能会忽略返回 404 的子域名,但我并没有。 对 http://admin.target.com:8443/FUZZ 进行模糊测试时,我发现了一…...