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
英文地址,請注明出處:http://en.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;滿足…