智能小车之舵机控制

Posted 旭日初扬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智能小车之舵机控制相关的知识,希望对你有一定的参考价值。

目录

一、输出比较功能分析

二、PWM封装

三、定时器封装

四、初始化定时器与舵机配置

五、初始化配置

六、main函数

     


一、输出比较功能分析

 TIM5_PWM_Init(9999,143,TIM5);  

IM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

设置CCR寄存器
void SetJointAngle(float angle)

	angle=(u16)(50.0*angle/9.0+249.0);
	//  设置捕获比较寄存器1的值
	TIM_SetCompare1(TIM5,angle);

定义:自动重装载值ARR=9999,预分频系数psc=143

设置自动重装值,当计数器的计数值=arr发生定时器中断,且计数器重新开始计数。

CNT计数器计数值被捕获/比较寄存器获取,当CNT的值=arr时,OCxREF信号极性发生反转,当指定OCx通道的高电平为有效电平时,OCxREF=1为有效电平,OCxREF=0为无效电平,并且会产生比较中断 CCxI,相应的标志位 CCxIFSR 寄存器中)会置 位。然后 OCxREF 再经过一系列的控制之后就成为真正的输出信号 OCx/OCxN

二、PWM封装

/**********************************************************************************
                        PWM初始化
PWM 信号的频率的计算公式为:F = TIM_CLK/(ARR+1)*(PSC+1)。
其中 TIM_CLK 等于 72MHZ,ARR 即自动重装载寄存器的值。PSC 即计数器时钟的分频因子。

PWM 输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR 的值决定。

PWM 信号主要都是用来控制电机,一般的电机控制用的都是边沿对齐模式,FOC 电机一般用中心对齐模式

CNT 工作在递增模式为例,在中,ARR=8,CCR=4,CNT 从 0 开始计数,当 CNT<CCR 的值时,OCxREF为有效的高电平于此同时,比较中断寄存器 CCxIF 置位。
当CCR=<CNT<=ARR 时,OCxREF 为无效的低电平。然后 CNT 又从 0 开始计数并生成计数器上溢事件,以此循环往复。

中心对齐模式:ARR=8,CCR=4。第一阶段计数器 CNT 工作在递增模式下,从 0 开始计数,当 CNT<CCR 的值时,OCxREF 为有效的高电平,当
CCR=<CNT<<ARR 时,OCxREF 为无效的低电平。
第二阶段计数器 CNT 工作在递减模式从 ARR 的值开始递减,当 CNT>CCR 时,OCxREF 为无效的低电平,当 CCR=>CNT>=1时,OCxREF 为有效的高电平


模式  计数器 CNT 计算方式 说明 
PWM1  递增                CNT<CCR,通道 CH 为有效,否则为无效
      递减                CNT>CCR,通道 CH 为无效,否则为有效
	
PWM2  递增                CNT<CCR,通道 CH 为无效,否则为有效
      递减                CNT>CCR,通道 CH 为有效,否则为无效
	
当使用 PWM 输入模式的时候,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时器在使用 PWM 输入的时候最多只能使用两个输入通道(TIx)。
**********************************************************************************/

void TIMx_PWM_Init(uint16_t OCMode,uint16_t OCPolarity,st_u8 CHx,TIM_TypeDef* TIMx)

	// 定时器比较输出初始化结构体 TIM_OCInitTypeDef 用于输出比较模式,与 TIM_OCxInit 函数配合使用完成指定定时器输出通道初始化配置。高级控制定时器有四个定时器通道,使用时都必须单独设置。
	TIM_OCInitTypeDef  TIM_OCInitStructure; 
	
	//*--------------------输出比较结构体初始化-------------------*/
 
  TIM_OCInitStructure.TIM_OCMode = OCMode;//TIM_OCMode_PWM1;
	// 输出通道电平极性配置  它决定着定时器通道有效电平
	 TIM_OCInitStructure.TIM_OCPolarity = OCPolarity;//TIM_OCPolarity_High;
	// 脉冲值,即输出都是低电平
  TIM_OCInitStructure.TIM_Pulse = 0;  
 // 输出使能
 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	
// 互补输出使能
// TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
	


if(CHx==1)
   TIM_OC1Init(TIMx, &TIM_OCInitStructure);
	 TIM_CtrlPWMOutputs(TIMx,ENABLE);	//MOE 主输出使能	
	 TIM_OC1PreloadConfig(TIMx, TIM_OCPreload_Enable); //CH1预装载使

if(CHx==2)
   TIM_OC2Init(TIMx, &TIM_OCInitStructure);
	 TIM_CtrlPWMOutputs(TIMx,ENABLE);	//MOE 主输出使能  当使用的是通用定时器时,这句不需要
   TIM_OC2PreloadConfig(TIMx, TIM_OCPreload_Enable);

if(CHx==3)
   TIM_OC3Init(TIMx, &TIM_OCInitStructure);
	 TIM_CtrlPWMOutputs(TIMx,ENABLE);	//MOE 主输出使能
   TIM_OC3PreloadConfig(TIMx, TIM_OCPreload_Enable);

if(CHx==4)
   TIM_OC4Init(TIMx, &TIM_OCInitStructure);
	 TIM_CtrlPWMOutputs(TIMx,ENABLE);	//MOE 主输出使能
   TIM_OC4PreloadConfig(TIMx, TIM_OCPreload_Enable);


  //使能ARR、MOE及定时器 
 TIM_ARRPreloadConfig(TIMx, ENABLE); //使能TIMx在ARR上的预装载寄存器   占空比
TIM_Cmd(TIMx, ENABLE);

三、定时器封装


/******************************************************************************
                                 定时器初始化函数

STM32总共有8个定时器,分别是2个高级定时器(TIM1、TIM8),
4个通用定时器(TIM2、TIM3、TIM4、TIM5)和2个基本定时器(TIM5、TIM6)

定时器时钟经过psc分频后得到驱动计数器计数的计数器时钟(CK_CNT)
CK_CNT=TIMxCLK/(PSC+1).

计数器 CNT 是一个 16 位的计数器,只能往上计数,最大计数值为 65535。当计数达
到自动重装载寄存器的时候产生更新事件,并清零从头开始计数.

自动重装载寄存器 ARR 是一个 16 位的寄存器,这里面装着计数器能计数的最大数
值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断.

定时器的定时时间等于计数器的中断周期乘以中断的次数。

计数器在 CK_CNT 的驱动下,计一个数的时间则是 CK_CLK 的倒数,等于:1/(TIMxCLK/(PSC+1))
产生一次中断的时间则等于:1/(CK_CLK * ARR)
中断服务程序中设置一个变量TIME,记录中断次数,则定时时间:1/CK_CLK * (ARR+1)*time
*******************************************************************************/
void TIMx_Init(st_u32 RCC_APB1Periph,st_u16 per,st_u16 psc,st_u16 clk_div,st_u16 Count_mode,TIM_TypeDef* TIMx)

	/*uint32_t RCC_APB1Periph = RCC_APB1Periph_TIM2|RCC_APB1Periph_TIM3|RCC_APB1Periph_TIM4
	                          |RCC_APB1Periph_TIM5|RCC_APB1Periph_TIM6|RCC_APB1Periph_TIM7;*/
	//  定时器结构体
  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	
	//使能定时器时钟 TIM5
  RCC_APB1PeriphClockCmd(RCC_APB1Periph, ENABLE);
	
  //  设置自动重装载寄存器周期的值	 计数到5000为500ms
  //  计数一次的时间1/(TIMxCLK/(PSC+1))=1/1000
	//  定时器周期,实际就是设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为 0 至 65535。

	TIM_TimeBaseInitStructure.TIM_Period = per;
	
	//  设置用来作为TIMx时钟频率除数的预分频值  10Khz的计数频率  
	//  定时器预分频器设置,时钟源经该预分频器才是定时器时钟,它设定
  //  TIMx_PSC 寄存器的值。可设置范围为 0 至 65535,实现 1 至 65536 分频。
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
	
	//  时钟分割    设置时钟分割:TDTS = Tck_tim
	//  时钟分频,设置定时器时钟 CK_INT 频率与数字滤波器采样时钟
  //  频率分频比,基本定时器没有此功能,不用设置。
	TIM_TimeBaseInitStructure.TIM_ClockDivision =clk_div ; 
	
	//  计数模式  TIM向上计数模式
	//  可是在为向上计数(TIM_CounterMode_Up)、向下计数(TIM_CounterMode_Down)以及三种中心对齐模式。TIM_CounterMode_CenterAligned1   TIM_CounterMode_CenterAligned2 TIM_CounterMode_CenterAligne3
  //  基本定时器只能是向上计数,即 TIMx_CNT 只能从 0 开始递增,并且无需初始化。
	TIM_TimeBaseInitStructure.TIM_CounterMode = Count_mode;  
	
	TIM_TimeBaseInit(TIMx, &TIM_TimeBaseInitStructure);
	//TIM_Cmd(TIMx, ENABLE);

四、初始化定时器与舵机配置


#include "public.h"

void PWM_Init(uint16_t arr,uint16_t psc)

	#if 0
	/*************************引脚初始化***************************************/
	 //  PA0  DJ  TIM5_CH1  
   GPIOInit(DJ_PORT,DJ_PIN,GPIO_Mode_AF_PP,DJ_RCC);
	
	 //  PB6  DJ2 TIM4_CH1
	 //  GPIOInit(DJ2_PORT,DJ2_PIN,GPIO_Mode_AF_PP,DJ2_RCC);
	 //  PA6  DJ3  TIM3_CH1
	 //  GPIOInit(DJ3_PORT,DJ3_PIN,GPIO_Mode_AF_PP,DJ3_RCC);
	
	/**************************定时器配置***************************************/
	
	 TIMx_Init(RCC_APB1Periph_TIM5,arr, psc,0,TIM_CounterMode_Up,TIM5);
	
	// TIMx_Init(RCC_APB1Periph_TIM4,arr, psc,0,TIM_CounterMode_Up,TIM4);
	
	// TIMx_Init(RCC_APB1Periph_TIM3,arr, psc,0,TIM_CounterMode_Up,TIM3);
	
	/***************************PWM输出比较引脚**********************************/
	 
	 TIMx_PWM_Init(TIM_OCMode_PWM1,TIM_OCPolarity_High,CH1,TIM5);
	 TIMx_PWM_Init(TIM_OCMode_PWM1,TIM_OCPolarity_High,CH2,TIM5);
	 //TIMx_PWM_Init(TIM_OCMode_PWM1,TIM_OCPolarity_High,1,TIM4);
	 //TIMx_PWM_Init(TIM_OCMode_PWM1,TIM_OCPolarity_High,1,TIM3);
	 
	 //TIMx_PWM_Init(TIM_OCMode_PWM1,TIM_OCPolarity_High,2,TIM5);
	 //TIMx_PWM_Init(TIM_OCMode_PWM1,TIM_OCPolarity_High,2,TIM4);
	 //TIMx_PWM_Init(TIM_OCMode_PWM1,TIM_OCPolarity_High,2,TIM3);
	 #else 
	 	//定义初始化结构体
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  TIM_OCInitTypeDef TIM_OCInitStructure;
	
	//使能定时器时钟 TIM5
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
	//使能GPIOA外设时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	//初始化GPIO
	//设置服用输出功能TIM5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM5 PA0
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//初始化时基
	//设置在下一个更新事件装入活动的自动重装载寄存器周期值50HZ
	 TIM_TimeBaseStructure.TIM_Period = arr;
	//设置用来作为TIMx时钟频率除数的预分频值 不分频
	 TIM_TimeBaseStructure.TIM_Prescaler =psc;
	//设置时钟分割:TDTS = Tck_tim
	 TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	 //TIM向上计数模式
	 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	 //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	  TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); 
		 //输出模式配置
	 //选择定时器模式:TIM脉冲宽度调制模式1
	 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	 //比较输出使能
	 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	 TIM_OCInitStructure.TIM_Pulse = 0;//设置待装入捕获比较寄存器的脉冲值
	 //输出极性:TIM输出比较级性高
	 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	 //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
	 TIM_OC1Init(TIM5, &TIM_OCInitStructure);
	 
	 TIM_CtrlPWMOutputs(TIM5,ENABLE);	//MOE 主输出使能	
	 
	 TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Enable); //CH1预装载使能
	
	TIM_OC2Init(TIM5, &TIM_OCInitStructure);
	 
	 //使能ARR、MOE及定时器
	 TIM_ARRPreloadConfig(TIM5, ENABLE); //使能TIMx在ARR上的预装载寄存器
   TIM_Cmd(TIM5, ENABLE); //使能TIM4
	 #endif




//  设置CCR寄存器的值
	void SetJointAngle(st_u8 ID, float angle)  
  
    switch(ID)  
      
        case 0:                                      //-90°~90°     
            //	设置TIMx Capture Compare1寄存器值
            angle=(u16)(50.0*angle/9.0+249.0);
	          TIM_SetCompare1(TIM5,angle);				
            break;  
                                                    //0°~180°  
        case 1:  
            angle=(u16)(4.175*angle+409.25);  
            TIM_SetCompare2(TIM3,angle);            
            break;  
  
  
        case 2:                                    //-150°~0°  
            angle=-angle;  
            angle=(u16)(4.175*angle+480.0);  
            TIM_SetCompare1(TIM4,angle);  
            break;  
  
        case 3:  
            angle=-180-angle;  
            angle=-angle;  
            angle=(u16)(4.175*angle+315.0);  
          
  
            TIM_SetCompare2(TIM4,angle);  
            break;  
                                              //-90°~90°  
        case 4:  
            angle=90.0+angle;  
            angle=(u16)(249.0+50.0*angle/9.0);  
            TIM_SetCompare3(TIM4,angle);              
            break;   
  
  
        default: break;  
          
  

五、初始化配置

/*
 * File      : board.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2017-07-24     Tanek        the first version
 */
#include <rthw.h>
#include <rtthread.h>
#include "board.h"


#ifdef __CC_ARM
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE 1024
//从内部SRAM里面分配一部分静态内存来作为rtt的堆空间,这里配置为4KB
static uint32_t rt_heap[RT_HEAP_SIZE];
RT_WEAK void *rt_heap_begin_get(void)

    return rt_heap;


RT_WEAK void *rt_heap_end_get(void)

    return rt_heap + RT_HEAP_SIZE;

#endif
#endif

extern uint8_t OSRunning;



/**
 * This function will initial your board.
 */
void rt_hw_board_init()

	SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
	SysTick_Init(72);
	LED_Init();
	uart_init(115200);
	/*
	PWM频率=72/(arr+1)(psc+1)=50Hz  T=1/F=0.02s=20ms=20 000us   每秒50次
	计数10000  用20ms  计数一次0.002ms
	
	舵机参数:
工作电压:4.8V-6V
位置等级:1024级
脉冲控制精度为2us
	
//  设置高电平的持续时间
TIM_SetCompare1(TIM5,angle);
软件
0.5ms-------------0度;  2.5%        计数250次  249(arr+1)  250/10000=2.5%  0.5ms=20ms/10000次*angle
1.0ms------------45度;  5.0%        计数500次  499
1.5ms------------90度;  7.5%        计数750次  749
2.0ms-----------135度;  10.0%       计数2000次
2.5ms-----------180度;  12.5%       计数2500次

调节占空比   angle/9999+1
	
	
	*/
	//TIM2_Init(5000,7199);
	//TIM4_PWM_Init(7199,0);
	PWM_Init(9999,143);
	

	OSRunning=1;
	
    /* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif
    
#if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE)
	rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
    
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif


void SysTick_Handler(void)

	/* enter interrupt */
	rt_interrupt_enter();
	
	/* 更新时基 */
	rt_tick_increase();

	/* leave interrupt */
	rt_interrupt_leave();


//重映射串口1到rt_kprintf
void rt_hw_console_output(const char *str)

	/* 进入临界段 */
	rt_enter_critical();
	/* 直到字符串结束 */
	while(*str!='\\0')
	
		if(*str=='\\n')
		
			USART1->DR = (u8)  '\\r'; 
			while((USART1->SR&0X40)==0);
		
		USART1->DR =*str++;
		while((USART1->SR&0X40)==0);	
	
	/* 退出临界段 */
	rt_exit_critical();

六、main函数

#define ROOT
#include "public.h"
/*************************其他控制量******************************/


#if 0
void ST_MCU(void)

  #ifdef TIM
	  RCC_PCLK1Config(RCC_HCLK_Div4);  //  HCLK/4 = 72/4
    RCC_PCLK2Config(RCC_HCLK_Div4);
    TIM2Init();
	#else
    TIM4_Init(1000,36000-1);  //定时500ms
	#endif

#endif
/*************************定义线程控制块******************************/
static rt_thread_t led1_thread=RT_NULL;
static rt_thread_t test_thread=RT_NULL;


/*************************线程主体函数******************************/
static void led1_thread_entry(void*parameter);
static void test_thread_entry(void*parameter);



 int main(void)
 	
	 #ifdef LED_DEBUG
	 //  创建led线程
	 led1_thread = rt_thread_create("led",
	                                 led1_thread_entry,
	                                 RT_NULL,
	                                  512,
	                                  3,
	                                  20);
	 
	 //  启动线程,开启调度
	 if(led1_thread!=RT_NULL)
		 rt_thread_startup(led1_thread);
    else
		return -1;
	 #endif
		
  test_thread = rt_thread_create("test",test_thread_entry,RT_NULL,512,3,30);
		
   //  启动线程,开启调度
	 if(test_thread!=RT_NULL)
		 rt_thread_startup(test_thread);
    else
		return -1;
 

  //LED1线程
static void led1_thread_entry(void* parameter)
	
    while(1)
    
        LED1=~LED1;
        rt_thread_delay(500);   /* 延时200个tick */
			  LED0=~LED0;
			  rt_thread_delay(500);   /* 延时200个tick */
		    
    



static void test_thread_entry(void*parameter)

 
     // ultrasonic_test();	
	while(1)
		
  SetJointAngle(0,90);
	delay_ms(300);
	//  TIM_SetCompare1(TIM5,angle);	
	SetJointAngle(0,5);
	delay_ms(300);
		
	SetJointAngle(0,175);
	delay_ms(300);
	




     

以上是关于智能小车之舵机控制的主要内容,如果未能解决你的问题,请参考以下文章

四足机器人——舵机控制

关于智能小车基于STM32和传感器码盘的测速

舵机及转向控制

树莓派控制mg995舵机如何控制

STM32之智能小车,手把手从0到1,模块化编程

毕业设计之 - 题目:基于stm32的WiFi监控小车