STM32平衡车开发计划2-电机编码器测速

一 电机测速

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

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 */
相关推荐
黑客思维者3 小时前
机器学习010:监督学习【回归算法】(Lasso回归)-- 用“魔法剪刀”找到真正重要的信息
人工智能·学习·机器学习·回归·监督学习·回归算法·lasso
zhangrelay3 小时前
新旧交替-传统模式被逐步抛弃……(节选)
学习
deng-c-f3 小时前
Linux C/C++ 学习日记(55):原子操作(四):实现无锁队列
学习
黄昏单车3 小时前
golang语言基础到进阶学习笔记
笔记·golang·go
小韩博3 小时前
小迪安全 · 第 39 课学习笔记
笔记·学习·安全·网络安全
非凡ghost3 小时前
Topaz Video(人工智能视频增强软件)
人工智能·windows·学习·音视频·软件需求
西西学代码4 小时前
《Flutter实战笔记》
笔记
重生之我在番茄自学网安拯救世界4 小时前
网络安全中级阶段学习笔记(十):upload靶场实战(17关以及问题解决)
笔记·学习·网络安全·文件上传漏洞·图片木马
宵时待雨5 小时前
C语言笔记归纳20:文件操作
c语言·开发语言·笔记·算法