当前位置: 首页 > news >正文

STM32 HAL 180°舵机控制 PWM/中断方法

舵机硬件接线

具体电源看购买的商品详情页,不同商家与型号可能有差异

舵机工作原理

舵机控制需要一个20MS左右的时基脉冲,该脉冲的
高电平部分一般为0.5MS-2.5MS范围内的角度控制脉冲部分,
对应的控制关系为(设置ARR为20000-1):
0.5MS-------0度-----占空比0.5%--500
1.0MS-----45度-----占空比1.0%--1000
1.5MS------90度----占空比1.5%--1500
2.0MS-- -135度-----占空比2.0%--2000
2.5MS- --180度-----占空比2.5%--2500

舵机从上面看是逆时针转动的,

这两个例程烧录后现象都是舵机逆时针角度增加,可增加90°左右

1.直接PWM控制

cubemx配置

使用TIM2 CHANNEL1(黄色线接PA0),PSC=72-1,ARR=20000-1,记得配置时钟为72MHz

完整代码

//servo.h #ifndef __SERVO_H #define __SERVO_H #include "stm32f1xx_hal.h" //定义一个结构体用于管理舵机的角度 struct SERVO{ uint16_t angel; uint16_t max_angel; uint16_t min_angel; }; void Servo_Init(void); void Servo_Set_Angel(uint16_t angel); void Servo_Up(uint8_t unit_angel); void Servo_Down(uint8_t unit_angel); #endif
//servo.c #include "servo.h" extern TIM_HandleTypeDef htim2; //根据需要设置初始角度,最大角度,最小角度 struct SERVO servo ={90,180,0}; //初始化 void Servo_Init(void) { //HAL_TIM_Base_Start(&htim2); HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); //上电后舵机角度为设置的初始角度 Servo_Set_Angel(servo.angel); } //封装舵机控制函数 void Servo_Set_Angel(uint16_t angel) { uint16_t pwm = (2000/180)*angel+500; __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,pwm); } //舵机角度增加单位角度函数 void Servo_Up(uint8_t unit_angel) { //更新角度 servo.angel += unit_angel; //限幅 if(servo.angel >= servo.max_angel) { servo.angel = servo.max_angel; } //设置角度 Servo_Set_Angel(servo.angel); } //舵机角度减小单位角度函数 void Servo_Down(uint8_t unit_angel) { //更新角度 servo.angel += unit_angel; //限幅 if(servo.angel <= servo.min_angel) { servo.angel = servo.min_angel; } //设置角度 Servo_Set_Angel(servo.angel); }
//main.c /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2026 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 "servo.h" #include "encoder.h" #include "uart.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define UNIT_ANGEL 5 /* 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 */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* 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_TIM2_Init(); MX_TIM3_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ Servo_Init(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { Servo_Up(UNIT_ANGEL); HAL_Delay(1000); /* 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_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; 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_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* 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 */

2.定时中断方法

cubemx配置

信号线随便选一个引脚为输出模式即可,需要一个TIM做定时器模拟20ms周期的pwm信号,

这里选择PB10为控制引脚,TIM1做10us定时,PSC=72-1,ARR=10-1

T = (PSC-1) * (ARR-1) / 时钟频率

记得开启中断

PB10默认配置即可,速度那里也可以选择中速

完整代码

//servo.h #ifndef __SERVO_H #define __SERVO_H #include "stm32f1xx_hal.h" //使用宏定义,使代码更简洁 #define Servo_H HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_SET) #define Servo_L HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET) //定义管理舵机pwm的结构体 struct SERVO{ uint16_t pwm; uint16_t max_pwm; uint16_t min_pwm; }; void Servo_Init(void); void Servo_Set_PWM(uint32_t pwm); void Servo_Up(uint8_t unit_pwm); void Servo_Down(uint8_t unit_pwm); #endif
//servo.c #include "servo.h" extern TIM_HandleTypeDef htim1; //根据需要设置 初始值,最大值,最小值 struct SERVO servo ={150,250,50}; //计数值 使用volatile保护 static volatile uint64_t servo_cnt; //初始化 开启时钟 设置低电平 计数器置0 void Servo_Init(void) { HAL_TIM_Base_Start_IT(&htim1); Servo_Set_PWM(servo.pwm); Servo_L; servo_cnt = 0; } //封装舵机控制函数 //其实这个函数可以不封装的,直接在Servo_Up和Servo_Down中限幅也可以的 void Servo_Set_PWM(uint32_t pwm) { //限幅 if(pwm >= servo.max_pwm ) { servo.pwm = servo.max_pwm ; } else if(pwm < servo.min_pwm ) { servo.pwm = servo.min_pwm ; } //最后这个分支很重要,笔者开始忘记写这个,debug发现servo.pwm一直停留在155,找了好久原因 呜呜呜 else { servo.pwm = servo.pwm ; } } //舵机角度增加单位pwm函数 void Servo_Up(uint8_t unit_pwm) { servo.pwm += unit_pwm; Servo_Set_PWM(servo.pwm); } //舵机角度减小单位pwm函数 void Servo_Down(uint8_t unit_pwm) { servo.pwm -= unit_pwm; Servo_Set_PWM(servo.pwm); } //使用高电平占比时间模拟pwm void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == &htim1) { //每10us进入中断一次,计数值+1 servo_cnt++; //模拟pwm,未到达计数值时为高电平,到达后为低电平 if(servo_cnt <= servo.pwm) { Servo_H; } else if(servo_cnt > servo.pwm) { Servo_L; } //计数到2000 即20ms周期清零计数值 if(servo_cnt >= 2000) { servo_cnt = 0; } } }

写在最后:

中断的方法是在定时器资源不充足时的弥补方法,但值得一提的是,频繁的中断会影响别的外设的工作运行。

综合下来舵机控制,其实直接使用PWM控制是比较好的选择,但是中断的方法也可以参考学习一下。

舵机要注意转向,特别是机械设计机械臂时,还有注意安装舵机(指机械臂等)前一定要上电调!!!笔者开始参加比赛,当时机械队友设计的机械臂为连杆码跺结构,没有注意转向,我作为电控也没有意识到要先上电调整,于是当时一上电控制机械臂直接断裂。

还有我提供的代码参考没有直接放在main.c的,封装一下比较好。这里之所以定义结构体,是因为我觉得你们学习舵机可能是想控制机械臂,这时可以使用这个结构体控制各个轴系和爪子,至于如何实现完整的机械臂控制,你们可以积极尝试,我提供一个截图参考思路

http://www.jsqmd.com/news/484874/

相关文章:

  • 西恩士 专利赋能清洁度检测设备 高端制造品质把控优选品牌 - 技术权威说
  • JavaScript基础课程九、JavaScript DOM 操作(修改内容/样式/属性)
  • RMBG-2.0开源大模型部署案例:私有化部署至企业内网,保障图像数据安全
  • 服饰教学可视化利器:Nano-Banana软萌拆拆屋在服装课中的应用
  • 2026六大城市名表维修“二次损伤”调查报告:从摆幅衰减到防水失效,你的保养费花对了吗? - 时光修表匠
  • ollama部署QwQ-32B效果对比:长文本摘要、跨文档推理与逻辑一致性测试
  • WAN2.2文生视频镜像GPU算力高效利用:单卡A10并发生成3路1080p视频实测
  • CLIP-GmP-ViT-L-14图文匹配工具部署案例:政务公开图解材料语义合规性初筛
  • Qwen3-Reranker-0.6B开源镜像实战:ModelScope国内加速下载+离线部署方案
  • 未来演进预测:对话式图像编辑技术的发展趋势
  • 开源可部署CLAP音频分类应用:无需代码基础,通过Web界面完成专业级零样本语音理解
  • SmallThinker-3B-Preview实战教程:构建个人AI草稿引擎(支持Markdown输出)
  • AI读脸术实战案例:智能零售客流属性统计系统搭建
  • Qwen-Turbo-BF16效果惊艳:体积雾+霓虹反射+雨滴地面物理渲染实测
  • CogVideoX-2b性能实测:显存优化后GPU利用率提升分析
  • YOLO12实战案例:安防监控中实时人车检测的低成本GPU算力方案
  • FRCRN在游戏语音场景落地:玩家实时语音通信降噪SDK封装实践
  • Qwen3-ASR-0.6B部署教程(RTX3060实测):2GB显存跑通52语种自动识别
  • Leather Dress Collection一文详解:12LoRA模型在不同采样器(DPM++、Euler a)下的表现对比
  • mT5分类增强版中文-baseWebUI定制:添加历史记录本地存储与JSON导出功能
  • 实时口罩检测-通用效果惊艳案例:口罩类型识别(医用/布艺/N95)
  • SiameseUIE快速部署:Jupyter Notebook中交互式调用SiameseUIE
  • chandra多语言OCR实践:中英日韩文档批量处理方案
  • Hunyuan模型部署最佳实践:config.json关键字段说明
  • Qwen2.5-VL-7B-Instruct部署避坑指南:显存不足报错、端口冲突、环境依赖解决
  • GLM-4.7-Flash部署案例:高校科研助手——论文摘要润色与查重辅助
  • Z-Image Turbo智能助手:自动补全提示词的实用技巧
  • lite-avatar形象库开源价值:150+可商用2D数字人资产,MIT协议免费使用
  • EagleEye实战教程:构建带时间戳与地理位置元数据的检测结果数据库
  • cv_resnet101_face-detection_cvpr22papermogface效果展示:眼镜反光/头发遮挡场景识别