PWM信号控制电机

1:环境
STM32F103C8T6
KEIL5.38
2个电机
2个轮子
1个L298N
STLINKV2
CH340
1个4位独立按键
杜邦线若干

2:代码

key.h

#ifndef __KEY_H
#define __KEY_H#include "stm32f10x.h"extern volatile  uint8_t  key_t ;
extern volatile  uint8_t  key_t0;
// 函数声明
void Key_Init(void);
uint8_t Key_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void KEY_Configuration(void);#endif

key.c

#include "key.h"
#include "delay.h"
#include "usart.h"
/*** 按键初始化* 配置 PA0、PB1、PA2、PA3 为浮空输入模式*/
void Key_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;// 使能 GPIOB 时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 配置 PA0、PA1、PA2、PA3 为浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//// 上拉输入(未按为高,按下为低)GPIO_Init(GPIOB, &GPIO_InitStructure);
}// 按键 GPIO 和中断配置
void KEY_Configuration(void) {GPIO_InitTypeDef GPIO_InitStructure;EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;// 使能 GPIOA 和 AFIO 时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);// 配置 PA1 和 PA2 为浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入(未按为高,按下为低)GPIO_Init(GPIOA, &GPIO_InitStructure);// 连接 EXTI 线路GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);// 配置 EXTIEXTI_InitStructure.EXTI_Line = EXTI_Line1 | EXTI_Line0;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger =EXTI_Trigger_Rising_Falling ;  //EXTI_Trigger_Rising 上升沿触发,根据实际按键修改EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);// 配置 NVIC// 配置 EXTI1 中断NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x05;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 配置 EXTI2 中断NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x05;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}/*** 检测按键状态(带消抖)* @param GPIOx: GPIO 端口 (GPIOA, GPIOB 等)* @param GPIO_Pin: GPIO 引脚 (GPIO_Pin_0, GPIO_Pin_1 等)* @return: 1-按键按下,0-按键未按下*/
uint8_t Key_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) {if (GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 0) {  // 检测到按键按下delay_ms(20);  // 消抖延时if (GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 0) {  // 确认按下while (GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 0);  // 等待释放return 1;  // 返回按键按下状态}}return 0;  // 按键未按下
}static  uint32_t key1_count =0;
static  uint32_t key2_count =0;static  uint8_t  key1_state = 0; //0 未按下,1 按下
static  uint8_t  key2_state = 0;volatile  uint8_t  key_t =0; 
volatile  uint8_t  key_t0 =0; 
void EXTI1_IRQHandler(void) {if(EXTI_GetITStatus(EXTI_Line1) != RESET) { //SET:表示对应中断线有未处理的中断请求  RESET:表示无中断请求// 按键处理代码uint8_t pinState1 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1);delay_ms(20);uint8_t pinState2 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1);if (pinState1 == pinState2) {if (pinState2 == 0) {// 确认是按下事件  下降沿//Key6_PressedCallback();key1_state= 1; //if(key1_state ==0)} else {// 确认是释放事件//Key6_ReleasedCallback();if(key1_state == 1){key1_state =0;key_t++;USART1_SendString("key1  press\r\n");//,[%d]++key1_count);}}}// ...//	  printf("key1  pinState1[%d]pinState2[%d]\r\n",pinState1,pinState2);// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line1);}
}// EXTI1 中断服务函数
void EXTI0_IRQHandler(void) {if(EXTI_GetITStatus(EXTI_Line0) != RESET) {// 按键处理代码uint8_t pinState1 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);delay_ms(20);uint8_t pinState2 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);if (pinState1 == pinState2) {if (pinState2 == 0) {// 确认是按下事件  下降沿//Key6_PressedCallback();key2_state= 1; //if(key2_state ==0)} else {// 确认是释放事件//Key6_ReleasedCallback();if(key2_state == 1){key2_state=0;key_t0++;USART1_SendString("key2  press");//"[%d]\r\n",++key2_count);}}}//		 printf("key2  pinState1[%d]pinState2[%d]\r\n",pinState1,pinState2);// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line0);}
}delay.h```c
#ifndef DELAY_H
#define DELAY_H
#include "stm32f10x.h"
void SysTick_Init(void);
// 精确延时函数(毫秒)
void delay_ms(uint32_t nms);
void delay_s(uint32_t ns);//精确延时函数(毫秒)没上限
void delay_ms_safe(uint32_t nms);
#endif

delay.c

#include "delay.h"
int f_us=9;	//1us 的次数
int f_ms=9000; //1ms的次数
// SysTick初始化 - 配置为HCLK/8 (72MHz/8 = 9MHz) //即 9,000,000 次 / 秒 或 9,000 次 / 毫秒
void SysTick_Init(void) {// 设置SysTick时钟源为HCLK/8SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
}
//STM32 的 SysTick 定时器是一个24 位递减计数器,其最大值为 2^24 - 1 = 16,777,215。因此,SysTick->LOAD 的值不能超过这个范围。  16,777,215/9000 =  1864.135
//nms <= 1864
// 精确延时函数(毫秒)
void delay_ms(uint32_t nms) {uint32_t temp;// 设置重载值SysTick->LOAD = nms * f_ms - 1;// 清空当前值SysTick->VAL = 0x00;// 使能SysTick定时器SysTick->CTRL |= (0x01 << 0);do {temp = SysTick->CTRL;} while ((temp & (0x01 << 0)) && (!(temp & (0x01 << 16))));// 关闭SysTick定时器SysTick->CTRL &= ~(0x01 << 0);
}void delay_s(uint32_t ns){uint32_t n =0;for(n=0;n<ns;n++){delay_ms(1000);}
}////精确延时函数(毫秒)没上限
void delay_ms_safe(uint32_t nms) {while (nms > 1864) {delay_ms(1864);nms -= 1864;}delay_ms(nms);
}
motor.h```c
#ifndef __MOTOR_H
#define __MOTOR_H#include "stm32f10x.h"// 函数声明
void Motor_Init(void);
//void LeftMotor_SetSpeed(int16_t speed);
//void RightMotor_SetSpeed(int16_t speed);
void Car_Forward(uint16_t speed,uint16_t delayms);
void Car_Backward(uint16_t speed,uint16_t delayms);
void Car_TurnLeft(uint16_t speed,uint16_t delayms);
void Car_TurnRight(uint16_t speed,uint16_t delayms);
void Car_Stop(uint16_t delayms);//急速/旋转  
void Car_SpinLeft(uint16_t speed,uint16_t delayms);
void Car_SpinRight(uint16_t speed,uint16_t delayms);#endif

motor.c

#include "motor.h"
#include "delay.h"const uint16_t  CAR_MAX_SPEED = 100;  //最大速度
/*** 电机控制初始化* 配置TIM4_CH1(PB6)到TIM4_CH3(PB9)为PWM输出*/
void Motor_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;// 使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 配置PWM输出引脚 (PB6和PB8)
//    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_8;  // PB6(TIM4_CH1), PB8(TIM4_CH3)
//    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         // 复用推挽输出
//    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//    GPIO_Init(GPIOB, &GPIO_InitStructure);// 配置方向控制引脚   //TIM4   ch1  pb6 ch2 pb7 ch3 pb8 ch4 pb9  查看手册的GPIO 引脚定义GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;  // PB6-PB9GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出  //GPIO_Mode_Out_PP;  // 通用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);//1. 合适的频率范围
//普通直流电机:推荐频率为 10kHz ~ 20kHz。
//低于 10kHz 时,电机可能会产生可听噪声(蜂鸣声),且转矩波动较大。
//高于 20kHz 时,人耳听不到噪声,但电机驱动电路(如 H 桥)的开关损耗会增加,可能导致发热。
//步进电机:通常需要更高的频率(20kHz ~ 50kHz),以保证平滑运行。
//伺服电机:一般使用固定频率(如 50Hz),但通过改变脉冲宽度(占空比)控制角度。
//2. 频率对电机的影响
//频率过低(如低于 5kHz):
//电机可能抖动或发出明显噪音,因为电流变化跟不上 PWM 切换速度,导致转矩不连续。
//频率过高(如高于 30kHz):
//驱动电路的 MOSFET 或三极管开关损耗增加,效率降低,甚至可能因过热损坏。
//3. 常见智能小车的选择
//玩具级小车:5kHz ~ 10kHz(成本低,但可能有噪音)。
//普通 DIY 小车:15kHz ~ 20kHz(兼顾静音和效率)。
//高性能小车:20kHz ~ 30kHz(追求极致平滑,但需优化散热)。// 配置TIM4时基 //初始化TIM4,PWM频率=72MHz/36/100=20k   //一秒有 2万 个方块波
//PWM频率=:系统时钟/(TIM_Prescaler+1)/(TIM_Period+1)
//PWM 频率计算
//系统时钟:STM32F103 默认系统时钟为 72MHz(APB1 定时器时钟 = 72MHz)。
//预分频系数:TIM_Prescaler = 36 - 1 → 实际分频为 36。
//自动重装值:TIM_Period = 100 - 1 → 实际周期为 100。//void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);//void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4)
//TIM_SetCompare1(TIM4, speed);   0<=speed <=TIM_Period  //speed 的取值范围TIM_TimeBaseStructure.TIM_Period = CAR_MAX_SPEED-1;//100 - 1;//arr;//自动重装值TIM_TimeBaseStructure.TIM_Prescaler =36 - 1;//psc; //时钟预分频数TIM_TimeBaseStructure.TIM_ClockDivision = 0;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM向上计数模式TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //初始化TIM4//初始化TIM4_CH1的PWM模式TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;// 使能输出TIM_OCInitStructure.TIM_Pulse = 0; //					初始占空比为0TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性高TIM_OC1Init(TIM4, &TIM_OCInitStructure);//TIM4_CH1TIM_OC2Init(TIM4, &TIM_OCInitStructure);//TIM4_CH2TIM_OC3Init(TIM4, &TIM_OCInitStructure);//TIM4_CH3TIM_OC4Init(TIM4, &TIM_OCInitStructure);//TIM4_CH4//      //初始化TIM4_CH2的PWM模式
//      TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1
//      TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;// 使能输出
//      TIM_OCInitStructure.TIM_Pulse = 0;								//初始占空比为0
//      TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性高
//       //TIM4_CH2初始化,OC2
//      TIM_OC2Init(TIM4, &TIM_OCInitStructure);//       //初始化TIM4_CH3的PWM模式
//      TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//      TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//      TIM_OCInitStructure.TIM_Pulse = 0;
//      TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//      TIM_OC3Init(TIM4, &TIM_OCInitStructure);//      //初始化TIM4_CH4的PWM模式
//      TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//      TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//      TIM_OCInitStructure.TIM_Pulse = 0;
//      TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//      TIM_OC4Init(TIM4, &TIM_OCInitStructure);//使能4个通道的预装载寄存器TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);//OC1TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);//OC2TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);//OC3TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);//OC4TIM_ARRPreloadConfig(TIM4, ENABLE); //使能重装寄存器TIM_Cmd(TIM4, ENABLE);//使能定时器TIM4,准备工作 
}void Motor_Init_2(void) {GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;// 使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 配置方向控制引脚   //TIM4   ch1  pb6 ch2 pb7 ch3 pb8 ch4 pb9  查看手册的GPIO 引脚定义GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;  // PB6-PB9GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 通用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);TIM_TimeBaseStructure.TIM_Period = CAR_MAX_SPEED-1;//100 - 1;//arr;//自动重装值TIM_TimeBaseStructure.TIM_Prescaler =36 - 1;//psc; //时钟预分频数TIM_TimeBaseStructure.TIM_ClockDivision = 0;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM向上计数模式TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //初始化TIM4//初始化TIM4_CH1到CH4的PWM模式TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;// 使能输出TIM_OCInitStructure.TIM_Pulse = 0; //					初始占空比为0TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性高TIM_OC1Init(TIM4, &TIM_OCInitStructure);//TIM4_CH1TIM_OC2Init(TIM4, &TIM_OCInitStructure);//TIM4_CH2TIM_OC3Init(TIM4, &TIM_OCInitStructure);//TIM4_CH3TIM_OC4Init(TIM4, &TIM_OCInitStructure);//TIM4_CH4//使能4个通道的预装载寄存器TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);//OC1TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);//OC2TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);//OC3TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);//OC4TIM_ARRPreloadConfig(TIM4, ENABLE); //使能重装寄存器TIM_Cmd(TIM4, ENABLE);//使能定时器TIM4,准备工作 
}////* 控制左电机
//// * @param speed: 速度值 (-1000~1000),负号表示反转//void LeftMotor_SetSpeed(int16_t speed) {
//    // 限制速度范围
//    if (speed > 1000) speed = 1000;
//    if (speed < -1000) speed = -1000;//    // 控制方向
//    if (speed >= 0) {
//        GPIO_SetBits(GPIOA, GPIO_Pin_1);    // IN1 = HIGH
//        GPIO_ResetBits(GPIOA, GPIO_Pin_2);  // IN2 = LOW
//    } else {
//        GPIO_ResetBits(GPIOA, GPIO_Pin_1);  // IN1 = LOW
//        GPIO_SetBits(GPIOA, GPIO_Pin_2);    // IN2 = HIGH
//        speed = -speed;                     // 转为正数用于PWM
//    }//    // 设置PWM占空比
//    TIM_SetCompare1(TIM4, speed);
//}//// * 控制右电机
//// * @param speed: 速度值 (-1000~1000),负号表示反转
// 
//void RightMotor_SetSpeed(int16_t speed) {
//    // 限制速度范围
//    if (speed > 1000) speed = 1000;
//    if (speed < -1000) speed = -1000;//    // 控制方向
//    if (speed >= 0) {
//        GPIO_SetBits(GPIOA, GPIO_Pin_3);    // IN3 = HIGH
//        GPIO_ResetBits(GPIOA, GPIO_Pin_4);  // IN4 = LOW
//    } else {
//        GPIO_ResetBits(GPIOA, GPIO_Pin_3);  // IN3 = LOW
//        GPIO_SetBits(GPIOA, GPIO_Pin_4);    // IN4 = HIGH
//        speed = -speed;                     // 转为正数用于PWM
//    }//    // 设置PWM占空比 (使用TIM4_CH3)
//    TIM_SetCompare3(TIM4, speed);
//}// 前进
void Car_Forward(uint16_t speed,uint16_t delayms) {if(speed > CAR_MAX_SPEED){speed = CAR_MAX_SPEED;}TIM_SetCompare1(TIM4, speed);TIM_SetCompare2(TIM4, 0);TIM_SetCompare3(TIM4, speed);TIM_SetCompare4(TIM4, 0);delay_ms(delayms);  //延迟 表示 当前操作 持续多久 ms
}// 后退
void Car_Backward(uint16_t speed,uint16_t delayms) {if(speed > CAR_MAX_SPEED){speed = CAR_MAX_SPEED;}TIM_SetCompare1(TIM4, 0);TIM_SetCompare2(TIM4, speed);TIM_SetCompare3(TIM4, 0);TIM_SetCompare4(TIM4, speed);delay_ms(delayms);
}// 左转  //左轮不动,右轮动
void Car_TurnLeft(uint16_t speed,uint16_t delayms) {if(speed > CAR_MAX_SPEED){speed = CAR_MAX_SPEED;}TIM_SetCompare1(TIM4, 0);TIM_SetCompare2(TIM4, 0);TIM_SetCompare3(TIM4, speed);TIM_SetCompare4(TIM4, 0);delay_ms(delayms);
}// 右转 //左轮动,右轮不动
void Car_TurnRight(uint16_t speed,uint16_t delayms) {if(speed > CAR_MAX_SPEED){speed = CAR_MAX_SPEED;}TIM_SetCompare1(TIM4, speed);TIM_SetCompare2(TIM4, 0);TIM_SetCompare3(TIM4, 0);TIM_SetCompare4(TIM4, 0);delay_ms(delayms);
}// 停止
void Car_Stop(uint16_t delayms) {TIM_SetCompare1(TIM4, 0);TIM_SetCompare2(TIM4, 0);TIM_SetCompare3(TIM4, 0);TIM_SetCompare4(TIM4, 0);delay_ms(delayms);
}//急速/旋转  左转
void Car_SpinLeft(uint16_t speed,uint16_t delayms){if(speed > CAR_MAX_SPEED){speed = CAR_MAX_SPEED;}TIM_SetCompare1(TIM4, 0);TIM_SetCompare2(TIM4, speed);TIM_SetCompare3(TIM4, speed);TIM_SetCompare4(TIM4, 0);delay_ms(delayms);
}//急速/旋转  右转
void Car_SpinRight(uint16_t speed,uint16_t delayms){if(speed > CAR_MAX_SPEED){speed = CAR_MAX_SPEED;}TIM_SetCompare1(TIM4, speed);TIM_SetCompare2(TIM4, 0);TIM_SetCompare3(TIM4, 0);TIM_SetCompare4(TIM4, speed);delay_ms(delayms);
}

main.c


#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "key.h"
#include "motor.h"#define LED_PIN            GPIO_Pin_13
#define LED_PORT           GPIOC// 配置LED
void LED_Configuration(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);GPIO_InitStructure.GPIO_Pin = LED_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(LED_PORT, &GPIO_InitStructure);GPIO_SetBits(LED_PORT, LED_PIN);  // 初始熄灭LED
}
#ifdef  SETUP_KEY_PRESS
// 按键 GPIO 和中断配置
void KEY_Configuration(void) {GPIO_InitTypeDef GPIO_InitStructure;EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;// 使能 GPIOA 和 AFIO 时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);// 配置 PA1 和 PA2 为浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入(未按为高,按下为低)GPIO_Init(GPIOA, &GPIO_InitStructure);// 连接 EXTI 线路GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2);// 配置 EXTIEXTI_InitStructure.EXTI_Line = EXTI_Line1 | EXTI_Line2;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger =EXTI_Trigger_Rising_Falling ;  //EXTI_Trigger_Rising 上升沿触发,根据实际按键修改EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);// 配置 NVIC// 配置 EXTI1 中断NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 配置 EXTI2 中断NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}static  uint32_t key1_count =0;
static  uint32_t key2_count =0;static  uint8_t  key1_state = 0; //0 未按下,1 按下
static  uint8_t  key2_state = 0;
//2次中断
//按下前      按下瞬间        保持按下         松开瞬间      松开后
// 高电平        下降沿          低电平           上升沿        高电平
//  (1)          ↓              (0)              ↑           (1)
// EXTI0 中断服务函数//双边沿触发 + 状态机:
//捕获完整按下 - 释放周期
//通过状态机过滤抖动和异常触发
//消抖策略:
//软件延时:20ms 通常足够
//硬件滤波:并联 0.1μF 电容到地void EXTI1_IRQHandler(void) {if(EXTI_GetITStatus(EXTI_Line1) != RESET) { //SET:表示对应中断线有未处理的中断请求  RESET:表示无中断请求// 按键处理代码uint8_t pinState1 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1);delay_ms(20);uint8_t pinState2 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1);if (pinState1 == pinState2) {if (pinState2 == 0) {// 确认是按下事件  下降沿//Key6_PressedCallback();key1_state= 1; //if(key1_state ==0)} else {// 确认是释放事件//Key6_ReleasedCallback();if(key1_state == 1){key1_state =0;printf("key1  press[%d]\r\n",++key1_count);}}}// ...//  printf("key1  pinState1[%d]pinState2[%d]\r\n",pinState1,pinState2);// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line1);}
}// EXTI1 中断服务函数
void EXTI2_IRQHandler(void) {if(EXTI_GetITStatus(EXTI_Line2) != RESET) {// 按键处理代码uint8_t pinState1 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2);delay_ms(20);uint8_t pinState2 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2);if (pinState1 == pinState2) {if (pinState2 == 0) {// 确认是按下事件  下降沿//Key6_PressedCallback();key2_state= 1; //if(key2_state ==0)} else {// 确认是释放事件//Key6_ReleasedCallback();if(key2_state == 1){key2_state=0;printf("key2  press[%d]\r\n",++key2_count);}}}//	 printf("key2  pinState1[%d]pinState2[%d]\r\n",pinState1,pinState2);// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line2);}
}
#endif#include "motor.h"
#include "key.h"int main(void) {// 初始化系统时钟(需根据实际电路配置,此处省略,可参考标准库例程)SystemInit();SysTick_Init();LED_Configuration();// 初始化USART1USART1_Init();// 初始化电机控制//  Motor_Init();// 初始化按键//  Key_Init();KEY_Configuration();Motor_Init();// 初始状态:停止
//    Car_Stop(1000);#ifdef SETUP_KEY_PRESSKEY_Configuration();
#endif	GPIO_ResetBits(LED_PORT, LED_PIN);  // 点亮LED指示错误delay_s(1);GPIO_SetBits(LED_PORT, LED_PIN);// 打印日志// printf("USART Test: Hello, F103C8T6!\r\n");//char  szbuf[BUFFER_SIZE+24]={0,};USART1_SendString("USART Test:Hello,F103C8T6[car8]!");int i = 0;uint8_t  last_key_1  =0;while (1) {// 循环打印示例
#ifdef __RC_MSG_2sprintf(szbuf,"current num=%d \n",i++);printf((const char*)szbuf);// printf("Log: Running...\r\n");GPIO_ResetBits(LED_PORT, LED_PIN);  // 点亮LED指示错误delay_ms(800);GPIO_SetBits(LED_PORT, LED_PIN);delay_ms(200);
#else// 检查是否收到消息
//        if (messageReceived) {
//            messageReceived = 0;  // 清除标志
//			
//					sprintf(szbuf,"%s-[%d]\r\n",rxBuffer,i++);
//            printf((const char*)szbuf);
//				//	USART1_SendString(szbuf);
//          
//				 GPIO_ResetBits(LED_PORT, LED_PIN);  // 点亮LED指示错误
//				 delay_ms(500);
//				 GPIO_SetBits(LED_PORT, LED_PIN);
//					
//        }
//				if(Key_Scan(GPIOA,GPIO_Pin_0) ==1){
//					 USART1_SendString("car forward \n");
//						Car_Forward(80,100);  // 前进,速度80%
//            delay_ms(500);     // 防止连续触发
//				}else if(Key_Scan(GPIOA,GPIO_Pin_1) ==1){
//						USART1_SendString("car backward \n");
//						 Car_Backward(80,100); // 后退,速度80%
//            delay_ms(500);     // 防止连续触发
//				}else{
//					delay_ms(100);
//				}if(key_t > last_key_1){last_key_1 =  key_t;USART1_SendString("car forward \n");Car_Forward(90,100);  // 前进,速度80% //线接反了,实际位退delay_ms(200);Car_Stop(1000);}if(key_t0 > 0){key_t0 =0;USART1_SendString("car backward \n");Car_Backward(90,100); // 后退,速度80%  //线接反了,实际位前进delay_ms(200);Car_Stop(1000);}delay_ms(50); #endif			}
}

3:说明
1>GPIO 引脚规划
这里PWM 使用的TIM4 PB6-PB9
4位按钮使用 PA0-PA3
USART串口使用的 USART1,PA9 PA10
LED 使用是 PC13 自带的哪个

PWM 主要是通过定时器 分片控制电机 假如IN1 IN2 控制左边电机(如果实际的情况刚好反了,可以把线调换下-就是输出口的2根线)
设置指定通道的占空比后,需要 delay_ms 维持一段时间,一般50-100ms 足够了

IN1 NI2 说明
H L 前进
L H 后退
L L 停止

EG: 假如接线没接反 假如 TIM_TimeBaseStructure.TIM_Period = CAR_MAX_SPEED-1;//100 - 1;//arr;//自动重装值 CAR_MAX_SPEED 假定位100 speed 假定位80
TIM_SetCompare1(TIM4, 0); //IN1 输出低电平
TIM_SetCompare2(TIM4, speed);//IN2 80%输出高电平
TIM_SetCompare3(TIM4, 0); //IN3 输出低电平
TIM_SetCompare4(TIM4, speed); //IN4 80%输出高电平
delay_ms(delayms); //上面的状态持续多久

结果为 后退 持续 delayms 毫秒,代码里也有说明,可以参考,这里拿来再说一遍

下面图网上抄的
在这里插入图片描述
在这里插入图片描述

2>供电
L298N 使用 CH340 5V 供电,有点不足够,在.4.95V-5.10V 有点波动,驱动电机有能力有点差
有能力的还是上2节锂电池吧,当前只是 测试,无所谓了
在这里插入图片描述

在这里插入图片描述

4:测试结果 如果对你又帮助,麻烦点个赞,加个关注
结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90920.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90920.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源赋能产业,生态共筑未来 | 开源科学计算与系统建模(openSCS)分论坛圆满举行

2025开放原子开源生态大会于7月23日-24日在北京国家会议中心召开。本届大会以“开源赋能产业&#xff0c;生态共筑未来”为主题&#xff0c;汇聚政、产、学、研、用、金、创、投等各领域开源力量&#xff0c;聚焦开源政策导向、生态发展趋势、开源产业实践&#xff0c;共探中国…

Android广播机制体系初识

Android广播机制体系大白话把Android的广播机制想象成小区里的“大喇叭”谁在喊话&#xff1f;任何App或系统都能当“大喇叭”&#xff0c;比如喊一嗓子“电量不足啦&#xff01;”&#xff08;这就是发送广播&#xff09;谁在听&#xff1f;其他App只要“竖起耳朵”&#xff0…

微信小程序点击输入框时,顶部导航栏被遮挡问题如何解决?

前言 不知道大家开发微信小程序的时候有没有遇到这么一个问题&#xff0c;就是在表单页面中&#xff0c;点击输入框后&#xff0c;输入框顶起会把顶部栏给遮挡住&#xff0c;如下图所示&#xff1a;遇到这种情况有没有解决的办法呢&#xff1f;能不能既将页面顶起&#xff0c;同…

通过具有一致性嵌入的大语言模型(LMMs)实现端到端乳腺癌放射治疗计划制定|文献速递-医学影像算法文献分享

Title题目End-to-end breast cancer radiotherapy planning via LMMs with consistencyembedding通过具有一致性嵌入的大语言模型&#xff08;LMMs&#xff09;实现端到端乳腺癌放射治疗计划制定01文献速递介绍近年来&#xff0c;受大型语言模型&#xff08;LLM&#xff09;启发…

vscode npm run build打包报ELIFECYCLE

npm run build打包报ELIFECYCLE 是内存溢出解决方案&#xff1a;修改build脚本 &#xff1a;"build": "node --max_old_space_size4096 node_modules/vue/cli-service/bin/vue-cli-service.js build",

【lucene】BlockMaxConjunctionScore

BlockMaxConjunctionScorer 是 Lucene 8.5 引入的一个高性能交集打分器&#xff08;conjunction scorer&#xff09;&#xff0c;专门用于处理 多条件“与”查询&#xff08;AND 查询&#xff09; 的场景。它基于 Block-Max WAND&#xff08;BMW&#xff09;算法&#xff0c;可…

Androidstudio 上传当前module 或本地jar包到maven服务器。

1.设置gradle版本到8.0 gradle-wrapper.properties文件中设置&#xff1a; distributionUrlhttps\://mirrors.aliyun.com/macports/distfiles/gradle/gradle-8.0-bin.zip 2.设置项目根目录build.gradle 设置agp版本和maven插件版本&#xff08;和gralde版本有对应关系&#xff…

Python动态规划:从基础到高阶优化的全面指南

动态规划&#xff08;Dynamic Programming&#xff09;是解决复杂优化问题的核心技术&#xff0c;也是算法领域的明珠。本文将深入探讨Python实现动态规划的全方位技术&#xff0c;涵盖基础概念、经典问题、优化技巧和实际工程应用&#xff0c;带您掌握这一强大工具的精髓。一、…

视觉大模型部署实践篇(Docker+dify+ollama安装)

一、概述 目的:实现一个本地化部署的大模型,通过工作流对图像进行一些处理。基于此,我选择了Docker+Dify+Ollama的部署。 具体实现逻辑:Docker来运行dify,dify用来绘制大模型的工作流或者rag等,Ollama用来部署本地大模型,dify调用Ollama部署的大模型进行推理。 二、Dock…

服务器启动日志等级

目录 标准日志等级 服务器启动阶段常见日志 日志配置建议 常见服务器/工具的日志等级配置方式 ET框架 Apache/Nginx 等 Web 服务器 Docker 容器 服务器启动过程中的日志等级是帮助开发者和运维人员理解系统状态的重要工具。常见的日志等级及其含义如下&#xff1a; 标准…

linux_centos7安装jdk8_采用jdk安装包安装

你问我为什么不用yum? 我yum安装不了&#xff0c;我也解决不了qwq. 文章目录一.下载安装包1.找到安装包下载位置2.上传安装包到linux3.解压jdk安装包4.配置环境一.下载安装包 1.找到安装包下载位置 去官网找到你要下载jdk版本&#xff1a; Oracle官网 下面演示安装jdk8的&am…

Linux驱动23 --- RkMedia 使用

目录 一、上电自动挂载 二、RkMedia 2.1 认识 RkMedia rtsp rtmp RTSP 和 RTMP 的选择 2.2 安装 VLC 2.2 RkMedia 例程使用 一、上电自动挂载 cd /etc/init.d/ vi Smyprofile.sh 添加这个内容 #!/bin/sh ifconfig eth0 192.168.66.88 mount -t nfs 192.168.66.66…

Linux:线程同步与线程互斥

线程互斥竞态条件当多个线程&#xff08;或进程&#xff09;并发访问和操作同一个共享资源&#xff08;如变量、文件、数据库记录等&#xff09;时&#xff0c;最终的结果依赖于这些线程执行的相对时序&#xff08;即谁在什么时候执行了哪条指令&#xff09;。 由于操作系统调度…

HTML 常用标签速查表

HTML 常用标签速查表 &#x1f9f1; 结构类标签 标签含义用途说明<html>HTML文档根元素所有HTML内容的根节点<head>头部信息放置元信息&#xff0c;如标题、引入CSS/JS等<body>页面内容主体所有可视内容的容器&#x1f4dd; 文本与标题标签 标签含义用途说…

1.gradle安装(mac)

1.下载二进制包 官网下载&#xff1a;Gradle Releases 国内镜像&#xff08;腾讯云&#xff09;&#xff1a;https://mirrors.cloud.tencent.com/gradle/ 2.解压并配置环境变量 解压到指定目录&#xff08;示例&#xff1a;/opt/gradle&#xff09; sudo mkdir -p /opt/gr…

Rust赋能土木工程数字化

基于Rust语言在数字化领域应用 基于Rust语言在土木工程数字 以下是基于Rust语言在土木工程数字化领域的30个实用案例,涵盖结构分析、BIM、GIS、传感器数据处理等方向。案例均采用Rust高性能、安全并发的特性实现,部分结合开源库或算法。 结构分析与计算 有限元分析框架 使…

KTH5791——3D 霍尔位置传感器--鼠标滚轮专用芯片

1 产品概述 KTH5791是一款基于3D霍尔磁感应原理的鼠标滚轮专用芯片&#xff0c;主要面向鼠标滚轮的旋转的应用场景。两个 专用的正交输出使该产品可直接替代机械和光学旋转编码器的输出方式&#xff0c;使得鼠标磁滚轮的应用开发工作极简 化即兼容目前所有鼠标的滚轮输出方式。…

决策树(Decision Tree)完整解析:原理 + 数学推导 + 剪枝 + 实战

1️⃣ 什么是决策树&#xff1f;决策树&#xff08;Decision Tree&#xff09;是一种常见的监督学习方法&#xff0c;可用于分类和回归。 其基本思想是&#xff1a;通过特征条件的逐层划分&#xff0c;将数据集分割成越来越“纯净”的子集&#xff0c;直到子集中的样本几乎属于…

C语言:20250728学习(指针)

回顾/*************************************************************************> File Name: demo01.c> Author: 阮> Description: > Created Time: 2025年07月28日 星期一 09时07分52秒**********************************************************…

esp32s3文心一言/豆包(即火山引擎)大模型实现智能语音对话--流式语音识别

一、引言 在之前的帖子《Esp32S3通过文心一言大模型实现智能语音对话》中&#xff0c;我们介绍了如何使用Esp32S3微控制器与文心一言大模型实现基本的智能语音对话功能&#xff0c;但受限于语音识别技术&#xff0c;只能处理2-3秒的音频数据。为了提升用户体验&#xff0c;满足…