一 电机测速
用我们的电机连接到单片机,方向随便给两个初始的控制就可以。

cs
uint16_t a = 900;
while (1)
{
HAL_Delay(1000);
a += 1000;
if(a > 7000){
a = 900;
}
printf("a = %d\r\n",a);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, a);
}
电机速度控制正常.接下来改完成编码器测速了。


通过逻辑分析仪观察电机编码器波形


通过两次测量我们就能看到占空比的变化情况。

修改定时器编码模式参数,这是一个减速电机,减速后的输出轴旋转一圈会增加多少呢我们还需要计算
// 1. 电机轴每转的编码器脉冲数
电机每转脉冲数 = 11 × 4 = 44 脉冲/电机转
// 2. 输出轴每转的编码器脉冲数
输出轴每转脉冲数 = 44 × 19 = 836 脉冲/输出轴转
// 3. 输出轴转速(RPM)计算公式
输出轴转速 = (脉冲数 / 采样时间) × 60 / 输出轴每转脉冲数
= total / 0.1 × 60 / 836
= total × 600 / 836
= total × 0.7177
// 简化公式:输出轴RPM = total × 0.7177
我们还需要写好我们的测速函数,因为我们用的是减速比19的一个电机,12V然后跑满的话大概是520转因为L298N会有2V左右的压差,跑满的话大概再500转左右,所以我们更改一下。对于我们STM32编码器我们选择的是边沿计数,A相和B相都会计数,之后A相有11个脉冲会记录22个数,B相也有11个也会记录22个数,加在一起就是44个。
减速后输出轴旋转一圈,磁铁旋转的圈数是19,计数值加减44。因此输出轴旋转一圈,计数值加减19*44=836。我们的速度(测得计数值)/836/0.01就是转每秒



然后动态调试PWM时候也可以看到我们的速度也是动态的。
之后就是所有的程序。
二.所有程序
cs
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
//记录溢出数量
int n = 0;
typedef unsigned char uint8;
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void TestGearRatio(void);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
printf("Test OK!\r\n"); //ceshu
// 1.清除标志位
__HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
//2 开启TIM3中断
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
// 3.启动TIM2定时中断
HAL_TIM_Base_Start_IT(&htim2);
//开启定时器1
__HAL_TIM_MOE_ENABLE(&htim1);
//开启PWM
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
uint32_t arr = TIM1->ARR; // 直接读取寄存器,最准确!
uint32_t pulse = arr / 2; // 50%占空比
printf("PWM启动成功!\r\n");
printf("ARR: %lu, 当前占空比: %lu\r\n", arr, pulse);
// 2. 配置方向引脚检查(修改为英文注释)
printf("GPIO configuration check:\r\n");
// STM32F1使用CRL寄存器控制PIN0-7,CRH控制PIN8-15
// PA0是PIN0,所以在CRL寄存器中
printf(" PA2 mode: 0x%08lX (bit1:0 should be 0x01=output)\r\n",
(GPIOA->CRL >> 8) & 0x3);
printf(" P3 mode: 0x%08lX (bit1:0 should be 0x01=output)\r\n",
(GPIOA->CRL >> 12) & 0x3); // PA1是PIN1,偏移4位
// 3. 设置初始方向:停止 (IN1=0, IN2=0)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); // IN1 = 1
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET); // IN2 = 0
printf("电机初始状态:停止\r\n");
printf(" IN1(PA2=)=%d, IN2(PA3)=%d\r\n",
HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2),
HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3));
// // 3. �ֶ�ʹ���ж�
//// HAL_NVIC_SetPriority(TIM3_IRQn, 5, 0); // �������ȼ�
//// HAL_NVIC_EnableIRQ(TIM3_IRQn); // ʹ���ж�
//
//4 设置编码器模式
HAL_TIM_Encoder_Start_IT(&htim3, TIM_CHANNEL_ALL);
printf("NVIC初始化成功\r\n");
// uint8 sumcheck = 0;
// uint8 addcheck = 0;
// //以一个字节数据为演示,如果多个字节,需要调整下面的参数
// uint8 arr[9] = {0xab,0x00,0x00,0xf1,0x01,0x00,0xf0,0x00,0x00};
// uint8 flen = arr[4]+arr[5]*256;
// for(uint8 i=0;i<(flen+6);i++){
// sumcheck += arr[i];
// addcheck += sumcheck;
// }
// arr[7] = sumcheck;
// arr[8] = addcheck;
// for(uint8 i=0;i<(flen+8);i++){
// printf("%02x ",arr[i]);
// }
// HAL_UART_Transmit(&huart1, arr, 9, 100);
uint16_t a = 7100;
// __HAL_TIM_SET_AUTORELOAD(&htim1, 65535); // 设置周期
//__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0); // 0%占空比
//HAL_Delay(3000);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// a += 1000;
// if(a > 7000){
// a = 1900;
// }
printf("a = %d\r\n",a);
// __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, a);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, a);
HAL_Delay(3000);
// TestGearRatio();
// // 测试值
// uint32_t sr = TIM3->SR;
// uint32_t cnt = TIM3->CNT;
// uint32_t dier = TIM3->DIER;
//
// printf("CNT: %lu, SR: 0x%04lX, DIER: 0x%04lX", cnt, sr, dier);
// printf("\r\n");
// printf("n = %d", n);
//
// printf("\r\n");
//
// HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
//定时器3的中断事件8
if(htim->Instance == TIM3){
//1为下溢
uint32_t direction = __HAL_TIM_DIRECTION_STATUS(htim);
if(direction == 0){
n++;
}else{
n--;
}
// printf("ARR direction = %d\r\n",direction);
}
//定时器2得中断事件
if(htim->Instance == TIM2){
//printf("TIM2\r\n");
uint16_t x = TIM3->CNT;
uint32_t total = 0;
//判断正反
if(n >= 0){
total = x + n*65535;
}else{
total = (65535-x)-(n+1)*65535;
}
// send_to_anonymous(0xF2, speed_rpm); // 0xF2表示速度数据 修改了836
printf("speed%d = %lf\r\n",n>=0?1:-1, total*1.0/0.1/4/888*60);
TIM3->CNT = 0;
n = 0;
}
}
//void send_to_anonymous(uint8_t func_id, int16_t data_value)
//{
// uint8_t frame[10]; // 3+1+2+2+2 = 10字节
// uint8_t sumcheck = 0, addcheck = 0;
//
// // 构建帧
// frame[0] = 0xAB; // 帧头
// frame[1] = 0x00;
// frame[2] = 0x00;
// frame[3] = func_id; // 功能ID(告诉上位机这是什么数据)
// frame[4] = 0x02; // 数据长度低字节 = 2
// frame[5] = 0x00; // 数据长度高字节
//
// // 数据值(小端格式)
// frame[6] = data_value & 0xFF; // 低字节
// frame[7] = (data_value >> 8) & 0xFF; // 高字节
//
// // 计算校验
// for(int i = 0; i < 8; i++) {
// sumcheck += frame[i];
// addcheck += sumcheck;
// }
//
// frame[8] = sumcheck;
// frame[9] = addcheck;
//
// // 发送
// HAL_UART_Transmit(&huart1, frame, 10, 100);
//}
//void TestGearRatio(void) {
// printf("=== 减速比测试 ===\r\n");
// printf("请手动将输出轴转动10圈...\r\n");
// printf("测试开始,您有15秒时间转动。\r\n");
//
// // 禁用定时器2中断,防止干扰
// __HAL_TIM_DISABLE_IT(&htim2, TIM_IT_UPDATE);
//
// // 记录起始计数
// uint32_t start_cnt = TIM3->CNT;
// int start_n = n;
//
// printf("3秒后开始...\r\n");
// HAL_Delay(3000);
// printf("开始转动!请转动输出轴10圈。\r\n");
//
// // 等待15秒,让用户转动
// for(int i = 15; i > 0; i--) {
// printf("剩余时间: %d秒\r\n", i);
// HAL_Delay(1000);
// }
//
// printf("时间到!\r\n");
//
// // 记录结束计数
// uint32_t end_cnt = TIM3->CNT;
// int end_n = n;
//
// // 计算总脉冲数
// int32_t start_total = start_cnt + start_n * 65536;
// int32_t end_total = end_cnt + end_n * 65536;
// int32_t pulse_count = end_total - start_total;
//
// // 计算减速比
// // 转动10圈输出轴,每圈44个脉冲(11线×4倍频)
// float gear_ratio = (float)pulse_count / (10.0f * 44.0f);
//
// printf("========== 测试结果 ==========\r\n");
// printf("脉冲数变化: %ld\r\n", pulse_count);
// printf("实际减速比: %.2f:1\r\n", gear_ratio);
// printf("============================\r\n");
//
// if(pulse_count == 0) {
// printf("警告:未检测到转动!\r\n");
// }
// else if(gear_ratio < 1.0f) {
// printf("注意:减速比小于1,可能测试有误。\r\n");
// }
//
// // 重置计数器
// TIM3->CNT = 0;
// n = 0;
//
// printf("测试完成!\r\n");
//}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */