OpenVINOTM,给你看得见的未来!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » boyiss的平衡车学习进程贴~(加入整参视频和加避障后的绕八字走不了8字的来看

共24条 1/3 1 2 3 跳转至

boyiss的平衡车学习进程贴~(加入整参视频和加避障后的绕八字走不了8字的来看看)

菜鸟
2015-09-22 19:42:54    评分

首先先说下情况,本来早早开始弄了,可是拖延症发作,做了一部分突然有其他事就没继续做下去了,其实也弄了大概的功能,剩下的也是完善,我就边回顾之前的内容,边继续完善吧~话说,这个平衡车也是我用STM32芯片做的第一个作品呢!




一:硬件说明

二:LED配置

三:按键配置

四:串口配置

五:电机配置

六:读编码器

七:移植DMP

八:无线双工

九:调参准备

十:调PID(整参)~~新加了视频~~

十一:开机动画

十二:体感遥控

十三:超声波避障

十四 :加避障后的绕八字分析

十五:关于CCD

十六:暂时的结束



菜鸟
2015-09-22 20:04:31    评分
2楼
首先说明一下硬件

这次做平衡车我用的底板是passoni店里的大车底板,然后自己画的控制板,现在发板挺便宜的,40~50就能发10*10cm的PCB了,还有10块,本来是设想一块控制板,另画一块作为遥控器,不过后来想到省钱(或是说懒。。)就没画了,一板两用,同时原理图方面参考了不少passoni的,控制板主要就是几部分,电压转换,控制芯片最小系统,电机驱动,MPU6050,其他一些模块可加可不加。


遥控效果可见视频:http://forum.eepw.com.cn/thread/276651/2#14

图片如图:

小车和遥控





菜鸟
2015-09-22 20:24:48    评分
3楼
然后是程序部分

我的程序是看着野火开发板的教程和passioni的代码学着做的,暂时是能驱动LED灯,MPU6050,NRF伪无线双工,按键,OLED,能读取电机的编码器输出。互联网是个好地方,有不懂的,网上搜索一下,或是来我们论坛逛逛,总会有收获,最后实在不会了,问问大家,大家往往都会热心回答。大赞!


stm32外设配置主要用3.5的库函数,简单易懂,当然有时寄存器简单时,就用寄存器,我个人觉得芯片是个工具,怎么舒服怎么用,重要的是配置完之后的程序算法,那些才更能体现程序员价值,还是个学生,也不知道对不对,随便说说,不要被我骗了哦~


暂时先不讲最基础的部分,学得还不好,就不说啦~有需要可以看看其他各位的帖子,讲得很好很详细!


菜鸟
2015-09-22 21:19:02    评分
4楼

首先是LED的驱动

LED灯原理图

由原理图可知,当PB8为高电平时,LED几乎无电流流过,则灯不亮

当PB8为低电平时,约为0V,电流从3.3V高电位流向PB8,

下面是一些具体的配置的程序:

#include "LED.h"
/*
 * 硬件连接:----------------------
 *          |   GPIOB8 - LED1     |
 *          -----------------------
 */


/*
 * 函数名: LED_GPIO_Config
 * 描述 :  LED灯引脚初始化并关闭
 * 输入 :  无 
 * 输出 :  无
 * 调用 :  外部调用 
 */
void LED_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	/*开启 GPIOB 的外设时钟*/
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);

	/*选择要控制的 GPIOB 引脚*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

	/*设置引脚模式为通用推挽输出*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

	/*设置引脚速率为 50MHz */
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

	/*调用库函数,初始化 GPIOB*/
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	/* 关闭所有 led 灯 */
	GPIO_SetBits(GPIOB, GPIO_Pin_8);
}

/*
 * 函数名: Led_Flash
 * 描述 :  LED灯改变
 * 输入 :  无 
 * 输出 :  无
 * 调用 :  外部调用 
 */
void Led_Flash(void)
{
	//LED1 取反
	GPIO_WriteBit(GPIOB, GPIO_Pin_8,(BitAction)((1-GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_8))));

}





菜鸟
2015-09-22 22:17:26    评分
5楼

二是按键的驱动


由原理图可知,类似LED灯,当按键按下时PB5直接接地,为低电平,

当按键未按下时,3.3V经10K电阻然后是PB5,为高电平

配置和LED也差不多,只是改为了输入,且无输出速度这个设置

下面是一些具体的配置的程序:

/*
 * 硬件连接:----------------------
 *          |   GPIOB5 - Key1     |
 *          -----------------------
 */
	
	
#include "key.h"


void Delay(__IO u32 nCount);

/*
 * 函数名: Key_GPIO_Config
 * 描述  : 按键IO初始化
 * 输入  : 无
 * 输出  : 无
 * 调用  : 外部调用
 */
void Key_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;//定义了一个GPIO_InitTypeDef的结构体

	/*开启按键端口(GPB5)的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	/*选择设置的引脚*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;

	// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;(输入模式无输出速度)
	/*选择输入输出模式*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉

	//根据前面设定结果进行对B组进行初始化
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

/*
 * 函数名:	Key_Scan(GPIO_TypeDef* GPIOx,u16 GPIO_Pin)
 * 描述  : 检测是否有按键按下
 * 输入  : GPIOx: x 可以是 A, B, C, D 或者 E
 *			 		GPIO_Pin:待读取的端口位
 * 输出  : KEY_OFF(没按下按键)、 KEY_ON(按下按键)
 * 调用  : 外部调用
 */
u8 Key_Scan(GPIO_TypeDef* GPIOx,u16 GPIO_Pin)
{
	/*检测是否有按键按下 */
	if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )
	{
		/*延时消抖*/
		Delay(10000);
		if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )
		{
		/*等待按键释放 */
			while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);
			return KEY_ON;

		}
		else
			return KEY_OFF;
	}
	else
		return KEY_OFF;
}
/* 
 * 函数名:delay
 * 描述  : 延时
 * 输入  :无
 * 输出  : 无	
 * 调用  : 外部调用
 */
void Delay(__IO u32 nCount)
{
	for(; nCount!=0; nCount--)
	;
}

 



菜鸟
2015-09-23 22:52:23    评分
6楼

三是串口

串口~

串口是个好东西,调试可以用LED,但很多时候我们需要查看一个数据的实时变化,或是其他情况,这时使用串口通过电脑查看就很方便了。有人可能说,PA9,PA10怎么看出串口的RX,TX,其实这些都在ST公司的资料里有说,例如datasheet,参考手册,固件库使用说明等,官方给的资料还是挺全的。


这个是在datasheet里截的图,里面有每个引脚功能的介绍,其中倒数第二列是芯片刚复位时的功能,而倒数第一列是它能复用的功能(Default alternate functions),所以我们配置这两个引脚时还要把它设置为复用的功能才能把这两个IO口当串口1的RX,TX使用。说个题外话,如果这两个引脚已经被使用了,STM32系列的芯片还有一个强大的重映射功能,可以把这个引脚的功能映射到另一个脚。使用灵活,也需要通过程序配置。


这是我在官方翻译的中文参考手册里截的图。虽然版本比英文的旧,不过一般看看也没什么问题。

通过开启重映射后可以映射到PB6,PB7,很好很强大


其中字长,停止位,奇偶校验位等可根据上位机来具体修改,配置,各参数通过查看STM32固件库使用手册搜索可以找到。


最后通过一个串口转USB的模块就可以连接电脑使用了。


上位机我找了三款,两款虚拟示波器是在论坛找的,一款匿名上位机的,上位机之后再讲。


下面是一些具体的配置的程序,其中有一部分是配合匿名上位机(2.x版)而改的:

#include 
#include "misc.h"



#define BYTE0(dwTemp)       (*(char *)(&dwTemp))
#define BYTE1(dwTemp)       (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp)       (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp)       (*((char *)(&dwTemp) + 3))

u8 TxBuffer[256];
u8 count=0; 


typedef union 
{
	unsigned char byte[4];
	float num;
}t_float2byte;


t_float2byte float2byte;



/*
 * 函数名:USART1_NVIC_Configuration
 * 描述  :调用了(NVIC_PriorityGroupConfig,NVIC_Init)函数
 *				设置了中断优先级的分组,配置嵌套向量中断控制器NVIC
 *				开启了usart的串口中断
 * 输入  :无
 * 输出  :无
 * 调用  :内部调用
 */
static void USART1_NVIC_Configuration(void)
{
	NVIC_InitTypeDef NVIC_InitStructure; 
	/* Configure the NVIC Preemption Priority Bits */  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	/* Enable the USARTy Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;	 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}


/*
 * 函数名:Uart1_Init
 * 描述  :			 USART1 GPIO 配置,工作模式配置。
 * 输入  :无
 * 输出  : 无
 * 调用  :外部调用
 */
void Uart1_Init(u32 BaudRate)
{
	USART_InitTypeDef USART_InitStructure;
	USART_ClockInitTypeDef USART_ClockInitStruct;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	
	
	//配置PA9作为USART1 Tx
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOA , &GPIO_InitStructure);
	//配置PA10作为USART1 Rx
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA , &GPIO_InitStructure);
	
	//配置USART1
	
	USART_InitStructure.USART_BaudRate = BaudRate;       			//波特率可以通过地面站配置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b; 	//8位数据
	USART_InitStructure.USART_StopBits = USART_StopBits_1;			//在帧结尾传输1个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;    			//禁用奇偶校验
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制失能
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;  //发送、接收使能
	//配置USART1时钟
	USART_ClockInitStruct.USART_Clock = USART_Clock_Disable;		 //时钟低电平活动
	USART_ClockInitStruct.USART_CPOL = USART_CPOL_Low;  			//SLCK引脚上时钟输出的极性->低电平
	USART_ClockInitStruct.USART_CPHA = USART_CPHA_2Edge;  			//时钟第二个边沿进行数据捕获
	USART_ClockInitStruct.USART_LastBit = USART_LastBit_Disable; //最后一位数据的时钟脉冲不从SCLK输出
	
	USART_Init(USART1, &USART_InitStructure);
	USART_ClockInit(USART1, &USART_ClockInitStruct);
	USART1_NVIC_Configuration();

	//使能USART1接收中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	//使能USART1
	USART_Cmd(USART1, ENABLE); 
}



void Uart1_Send_Buf(u8 *buf,u8 len)		//发送buf,长度len,返回字节和sum
{
	while(len)
	{
		Uart1_Put_Char(*buf);
		buf++;
		len--;
	}
}


/*
 * 函数名:fputc
 * 描述  :调用了(USART_SendData,USART_GetFlagStatus)函数
 *				 重定向c库函数printf到USART1(printf最终调用了fputc来发送)
 * 输入  :无
 * 输出  :无
 * 调用  ;被printf调用
 */
int fputc(int ch, FILE *f)
{
	/* 将Printf内容发往串口 */
	USART_SendData(USART1, (unsigned char) ch);//发送printf处理好的数据
//	while (!(USART1->SR & USART_FLAG_TXE));
	while( USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET);//检测发送是否完成	
	return (ch);
}

/**************************三种数据类型的实现函数********************************/
uint8_t Uart1_Put_Char(unsigned char DataToSend)
{
	TxBuffer[count++] = DataToSend;
  if(!(USART1->CR1 & USART_CR1_TXEIE))
		USART_ITConfig(USART1, USART_IT_TXE, ENABLE); 
	return DataToSend;
}
uint8_t Uart1_Put_Int16(uint16_t DataToSend)
{
	uint8_t sum = 0;
	TxBuffer[count++] = BYTE1(DataToSend);
	TxBuffer[count++] = BYTE0(DataToSend);
	if(!(USART1->CR1 & USART_CR1_TXEIE))
		USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
	sum += BYTE1(DataToSend);
	sum += BYTE0(DataToSend);
	return sum;
}
uint8_t Uart1_Put_Float(float DataToSend)
{
	uint8_t sum = 0;
	float2byte.num=DataToSend;
	TxBuffer[count++] = float2byte.byte[3];  
	TxBuffer[count++] = float2byte.byte[2];  
	TxBuffer[count++] = float2byte.byte[1];  
	TxBuffer[count++] = float2byte.byte[0];  
	if(!(USART1->CR1 & USART_CR1_TXEIE))
		USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
	sum += BYTE3(DataToSend);
	sum += BYTE2(DataToSend);
	sum += BYTE1(DataToSend);
	sum += BYTE0(DataToSend);
	return sum;	
}
void Uart1_Put_String(unsigned char *Str)
{
	//判断Str指向的数据是否有效.
	while(*Str)
	{
		//是否是回车字符 如果是,则发送相应的回车 0x0d 0x0a
		if(*Str=='\r')Uart1_Put_Char(0x0d);
			else if(*Str=='\n')Uart1_Put_Char(0x0a);
				else Uart1_Put_Char(*Str);
		//指针++ 指向下一个字节.
		Str++;
	}
}





菜鸟
2015-10-29 12:04:36    评分
7楼

好久没发帖了,最近比较忙,而且自己画的板有不少小的问题,调试下来,还是浪费不少时间,几乎没怎么写程序,论坛提供一个好的硬件给大家是省了很多时间,期间我也时不时看了看大神的贴,大家都好厉害,有用matLab建模生成代码的,有把知识讲的简单通俗易懂的,我还要多学习!现在开始继续发帖~


菜鸟
2015-10-29 17:43:43    评分
8楼

电机的驱动

TB6612FNG引脚图

平衡车上使用的芯片是TB6612FNG,性能强大,以下是网上找到的简介

TB6612FNG是东芝半导体公司生产的一款直流电机驱动器件,它具有大电流MOSFET-H桥结构,双通道电路输出,可同时驱动2个电机。TB6612FNG每通道输出最高1.2 A的连续驱动电流,启动峰值电流达2A/3.2 A(连续脉冲/单脉冲);4种电机控制模式:正转/反转/制动/停止;PWM支持频率高达100 kHz;待机状态;片内低压检测电路与热停机保护电路;工作温度:-20~85℃;SSOP24小型贴片封装。

根据芯片手册上的控制图我们可以知道如何让电机正反转和停止或者看我在淘宝找到的

===========================================


STBY口接单片机的IO口清零电机全部停止,置1通过AIN1 AIN2,BIN1,BIN2 来控制正反转 VM  接12V以内电源 VCC 接5V电源 GND 就不多说了啊

驱动1路

PWMA      接单片机的PWM口

真值表:

AIN1 0      0      1

AIN2 0      1      0

停止 正传   反转

A01

AO2   接电机1的两个脚

驱动2路

PWMB     接单片机的PWM口

真值表:

BIN1 0      0      1

BIN2 0      1      0

停止 正传   反转

B01

BO2   接电机2的两个脚

==============================================


然后是它的内部电路

一路的输出是有四个MOS管搭成的H桥和4个关断时用到的续流二极管。

下面是具体的驱动的程序。配置引脚,然后使用定时器里的PWM功能,具体看我程序注释

/*
 * 硬件连接:-----------------------------------------------------
 *          |   	A电机:IN1:GPIOB14,IN2:15,PWM》GPIOB1通道4 |		 
 *          |  		B电机:IN1:GPIOB13,IN2: 12,PWM》GPIOB0      |  
 *          -----------------------------------------------------
 */
	
#include "PWM.h"
int MotorA,MotorB;
int Balance_Pwm=0,Velocity_Pwm=0,Turn_Pwm=0;



/*
 * 函数名:PWM_Direction_GPIO_Config
 * 描述  :配置控制电机正反转的I/O
 * 输入  :无
 * 输出  :无
 * 调用  :内部调用
 */
static void PWM_Direction_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	/*开启 GPIOB 的外设时钟*/
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);

	/*选择要控制的 GPIOC 引脚*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15
	;

	/*设置引脚模式为通用推挽输出*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

	/*设置引脚速率为 50MHz */
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

	/*调用库函数,初始化 GPIOB*/
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	
	//开始时没开电机
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);
	GPIO_ResetBits(GPIOB,GPIO_Pin_13);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_14);
	GPIO_ResetBits(GPIOB,GPIO_Pin_15);

}
/*
 * 函数名:TIM3_GPIO_Config
 * 描述  :配置TIM3复用输出PWM时用到的I/O
 * 输入  :无
 * 输出  :无
 * 调用  :内部调用
 */
static void TIM3_GPIO_Config(void) 
{
  GPIO_InitTypeDef GPIO_InitStructure;

	/* 开启TIM3的时钟 */
	//PCLK1经过2倍频后作为TIM3的时钟源等于72MHz
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 

  /* 开启GPIOA和GPIOB的时钟 */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); 

  /*设置GPIOA定时器3通道1,2为复用推挽输出 */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		    // 复用推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /*设置GPIOB定时器3通道3,4为复用推挽输出 */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_0 | GPIO_Pin_1;

  GPIO_Init(GPIOB, &GPIO_InitStructure);
}

/*
 * 函数名:TIM3_Mode_Config
 * 描述  :配置TIM3输出的PWM信号的模式,如周期、极性、占空比
 * 输入  :无
 * 输出  :无
 * 调用  :内部调用
 */
static void TIM3_Mode_Config(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

	/* PWM信号电平跳变值 */
//	u16 CCR1_Val = 3600;        
//	u16 CCR2_Val = 3600;
	u16 CCR3_Val = 3600;//50%
	u16 CCR4_Val = 3600;

/* -----------------------------------------------------------------------
    TIM3 Configuration: generate 4 PWM signals with 4 different duty cycles:
    TIM3CLK = 72 MHz, Prescaler = 0x0, TIM3 counter clock = 72 MHz
    TIM3 ARR Register = 999 => TIM3 Frequency = TIM3 counter clock/(ARR + 1)
    TIM3 Frequency = 72 KHz.
    TIM3 Channel1 duty cycle = (TIM3_CCR1/ TIM3_ARR)* 100 = 50%(3600/7200*100=50)
    TIM3 Channel2 duty cycle = (TIM3_CCR2/ TIM3_ARR)* 100 = 37.5%
    TIM3 Channel3 duty cycle = (TIM3_CCR3/ TIM3_ARR)* 100 = 25%
    TIM3 Channel4 duty cycle = (TIM3_CCR4/ TIM3_ARR)* 100 = 12.5%
  ----------------------------------------------------------------------- */
	
	//1MHz=1k*1k,频率为10k=72M/7200;

  /* 配置时间基数 */		 
  TIM_TimeBaseStructure.TIM_Period = 7199;       //当定时器从0计数到7199,即为7200次,为一个定时周期
  TIM_TimeBaseStructure.TIM_Prescaler = 0;	    //设置预分频:不预分频,即为72MHz
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;	//设置时钟分频系数:不分频
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数模式

  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);





  /* 对4路输出通道(OC>>OutputChannel)进行配置 */
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;	    //配置OutputChannel的模式为为PWM模式1
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  //当定时器计数值小于CCR1_Val时为高电平


//  TIM_OCInitStructure.TIM_Pulse = CCR1_Val;	   //设置跳变值,当计数器计数到这个值时,电平发生跳变
//  TIM_OC1Init(TIM3, &TIM_OCInitStructure);	 //使能通道1(根据 TIM_OCInitStruct 中指定的参数初始化外设 TIMx)
//  TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);//使能TIMx 在 CCR1 上的预装载寄存器
// 
// /* 通道2 */
//  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//  TIM_OCInitStructure.TIM_Pulse = CCR2_Val;	  //设置通道2的电平跳变值,输出另外一个占空比的PWM
//  TIM_OC2Init(TIM3, &TIM_OCInitStructure);	  //使能通道2
//  TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
  
  /* 通道3*/
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_Pulse = CCR3_Val;	//设置通道3的电平跳变值,输出另外一个占空比的PWM
  TIM_OC3Init(TIM3, &TIM_OCInitStructure);	 //使能通道3
  TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);

  /* 通道4*/
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_Pulse = CCR4_Val;	//设置通道4的电平跳变值,输出另外一个占空比的PWM
  TIM_OC4Init(TIM3, &TIM_OCInitStructure);	//使能通道4
  TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);


  TIM_ARRPreloadConfig(TIM3, ENABLE);			 // 使能TIM3重载寄存器ARR

  /* 开启TIM3 */
  TIM_Cmd(TIM3, ENABLE);                   //使能定时器3	
}

/*
 * 函数名:TIM3_PWM_Init
 * 描述  :TIM3 输出PWM信号初始化,只要调用这个函数
 *         TIM3的1,2通道就会有PWM信号输出
 *			同时配置了控制方向的4个IO
 * 输入  :无
 * 输出  :无
 * 调用  :外部调用
 */
void TIM3_PWM_Init(void)
{
	TIM3_GPIO_Config();
	TIM3_Mode_Config();	
	PWM_Direction_GPIO_Config();
}
/*
 * 函数名:abs
 * 描述  :调用获取PWM绝对值
 * 输入  :无
 * 输出  :无
 * 调用  :内部调用
 */
static int abs(int a)
{
	int abs;
	abs=a;
	if(a<0) { abs=a*(-1); } return abs; } /* * 函数名:SET_PWM * 描述 :调用此函数,可设置电机PWM及正反转 * 输入 :无 * 输出 :无 * 调用 :外部调用 */ void Set_Pwm(int motorA,int motorB) { if(motorA<0)//则电机A小于0时反转 { GPIO_SetBits (GPIOB,GPIO_Pin_15);//AIN2 GPIO_ResetBits(GPIOB,GPIO_Pin_14);//AIN1 } else { GPIO_SetBits (GPIOB,GPIO_Pin_14); GPIO_ResetBits(GPIOB,GPIO_Pin_15); } TIM3->CCR4=abs(motorA);	
	
	if(motorB<0)//若设电机B小于0正转 //电机正反转设置 { GPIO_SetBits (GPIOB,GPIO_Pin_12);//BIN2 GPIO_ResetBits(GPIOB,GPIO_Pin_13);//BIN1 } else { GPIO_SetBits (GPIOB,GPIO_Pin_13); GPIO_ResetBits(GPIOB,GPIO_Pin_12); } TIM3->CCR3=abs(motorB);					//PWM占空比设置(通道3)
}
/*
 * 函数名:limit_PWM
 * 描述  :为电机限幅,PWM定时器设定重装值为7199,所以满幅是7199
 * 输入  :无
 * 输出  :无
 * 调用  :外部调用
 */
int limit_PWM(int pwm)
{
	int Amplitude=7000;    //===PWM满幅是7199 限制在6800
	if(pwmAmplitude) 	   
		pwm=Amplitude;	
	return pwm;	
}
/*
 * 函数名:Abnormal
 * 描述  :使用此函数可检查状态是否正常,不正常关电机并返回状态1
 * 输入  :无
 * 输出  :状态不正常返回1,正常返回0
 * 调用  :外部调用
 */
int Abnormal(void)
{	
	if((Angle_Balance<-50)||(Angle_Balance>50))//===倾角大于40度关闭电机
	{	                  					//===可增加主板温度过高时关闭电机
		GPIO_ResetBits(GPIOB,GPIO_Pin_12);
		GPIO_ResetBits(GPIOB,GPIO_Pin_13);	
		GPIO_ResetBits(GPIOB,GPIO_Pin_14);
		GPIO_ResetBits(GPIOB,GPIO_Pin_15);
		Set_Pwm(0,0);
		return 1;
	}
	return 0;			
}






菜鸟
2015-10-29 18:14:13    评分
9楼
定时器编码器模式


上面黑色的圆加上一个霍尔传感器就是测电机转动的重要传感器了。

关于A相B相,看我网上找的资料:


A相B相的先后可用于判断正反转,而次数可用于大致测定转的度数。


然后再看看M3的配置说明:



本来想用库函数写的,不过试了一次没成功,之后有机会再试吧~

/*
 * 硬件连接: -----------------------------
 *          |   GPIOA0,1 ->编码器1输入	|
 *          |   GPIOB6,7 ->编码器2输入	|
 *           ----------------------------
 */



#include "Encoder.h"

int Encoder_Left,Encoder_Right;

/*
 * 函数名:Encoder1_Init
 * 描述  :Encoder1初始化
 * 输入  :无
 * 输出  :无
 * 调用  :内部调用
 */
static void Encoder1_Init(void) 
{
	RCC->APB1ENR|=1<<0;					//TIM2时钟使能
	RCC->APB2ENR|=1<<2;					//使能PORTA时钟
	GPIOA->CRL&=0XFFFFFF00;				//GPA0 PA1
	GPIOA->CRL|=0X00000044;				//浮空输入
	/* 把定时器初始化为编码器模式 */ 
	TIM2->PSC = 0x0;//预分频器
	TIM2->ARR = ENCODER_TIM_PERIOD-1;	//设定计数器自动重装值 
	TIM2->CCMR1 |= 1<<0;         		 //输入模式,输入通道1FP1映射到TI1上
	TIM2->CCMR1 |= 1<<8;         		 //输入模式,输入通道2映射到TI2上
	TIM2->CCER |= 0<<1;          		 //IC1不反向(上升沿捕捉)
	TIM2->CCER |= 0<<5;          		 //IC2不反向
	/*
	//001:编码器模式1 – 根据TI1FP1的电平,计数器在TI2FP2的边沿向上/下计数。
	//010:编码器模式2 – 根据TI2FP2的电平,计数器在TI1FP1的边沿向上/下计数。
	//011:编码器模式3 – 根据另一个信号的输入电平,计数器在TI1FP1和TI2FP2的边沿向上/下计数。(正转反转都能测)	
	*/
	TIM2->SMCR |= 3<<0;	   //SMS='011' A相B相输入在上升和下降沿有效(一次变化4次计数)
	
	TIM2->CR1 |= 0x01;    //CEN=1,使能定时器
	
	TIM2 -> CNT=0;
}


/*
 * 函数名:Encoder2_Init
 * 描述  :Encoder2初始化
 * 输入  :无
 * 输出  :无
 * 调用  :内部调用
 */
static void Encoder2_Init(void) 
{
	RCC->APB1ENR|=1<<2;    			//TIM4时钟使能
	RCC->APB2ENR|=1<<3;    			//使能PORTB时钟
	GPIOB->CRL&=0X00FFFFFF;			//GPB6 PB7
	GPIOB->CRL|=0X44000000;			//浮空输入
	/* 把定时器初始化为编码器模式 */ 
	TIM4->PSC = 0x0;//预分频器
	TIM4->ARR = ENCODER_TIM_PERIOD-1;//设定计数器自动重装值 
	TIM4->CCMR1 |= 1<<0;          //输入模式,IC1FP1映射到TI1上
	TIM4->CCMR1 |= 1<<8;          //输入模式,IC2FP2映射到TI2上
	TIM4->CCER |= 0<<1;           //IC1不反向
	TIM4->CCER |= 0<<5;           //IC2不反向
	TIM4->SMCR |= 3<<0;	          //SMS='011' 所有的输入均在上升沿和下降沿有效
	TIM4->CR1 |= 0x01;			  //CEN=1,使能定时器	
	TIM4 -> CNT=0;
}

/*
 * 函数名:Encoder_Init
 * 描述  :编码器初始化
 * 输入  :无
 * 输出  :无
 * 调用  :外部调用
 */
void Encoder_Init(void) 
{	
	#if UltrasonicWave_ON==1
	#else
	Encoder1_Init();
	#endif
	Encoder2_Init();
}

/*
 * 函数名:Read_Encoder
 * 描述  :Encoder2初始化
 * 输入  :EncoderX可为1或2
 * 输出  :-32768~+32767,为0则可能是输入有错
 * 调用  :外部调用
 */
int Read_Encoder(u8 EncoderX) 
{
	int Encoder_Return;
	switch(EncoderX)
	{
		case 1:  Encoder_Return= (short)TIM2 -> CNT;  TIM2 -> CNT=0;break;//可以表示-32768~+32767
		case 2:  Encoder_Return= (short)TIM4 -> CNT;  TIM4 -> CNT=0;break;	
		default: Encoder_Return=0;
	}
	return Encoder_Return;
}

 



最后在读数据时强制转换为了short型,其实是看到@passoni 的解答,效果是一样的


以下是他的解读,很好很清晰:

在MiniBlalance.c文件的149-160行中if(Encoder_L>32768)中的32768是什么含义呢?还有后面的6500又如何理解?
是这样的,编码器和电机的PWM没有任何关系。当然,编码器输出的是方波信号。当我们把STM32的某个定时器初始化为编码器接口后,每检测到AB相中任意一相的跳变沿,TIMX -> CNT就会+1或者-1。

OK,基于上面的基础,我们怎么测速呢?就是单位时间内测得的脉冲数,所以我们会在每5ms读取一次TIMX -> CNT的值,并清零。这也就是书本上讲的M法测速的原理。

接下来,我们去TIMER.H文件里面找到如下的宏定义

#define ENCODER_TIM_PERIOD (u16)(65000)

#define COUNTER_RESET   (u16)0

计数的时候,定时器是这么工作的,上电后,TIMX -> CNT的值是COUNTER_RESET  ,也就是0,当我们正转的时候,TIMX -> CNT向上计数,反转的时候向下计数,小于零后就从ENCODER_TIM_PERIOD也就是65000向下计数。

关于ENCODER_TIM_PERIOD的取值,因为STM32F103的定时器是16位的,最大可以取65536,而F4系列中有4个32位的定时器,就可以取得更大。

这样,我们在5MS的定时器中断函数里面做如下处理

if(Encoder_L>32768)  Encoder_Left=Encoder_L-65000; else  Encoder_Left=Encoder_L;

就可以通过INT型的Encoder_Left变量的正负号得到车轮的正反转。

有理解不当的,请同学们批评指正

==============================================



菜鸟
2015-10-29 21:44:31    评分
10楼

MPU6050之DMP


==================DMP是什么====================

MPU6050 自带了数字运动处理器, 即 DMP(Digital Motion Processing),

并且InvenSense提供了一个 MPU6050 的嵌入式运动驱动库,

结合 MPU6050 的 DMP, 可以将我们的原始数据,直接转换成四元数输出。

然后通过下面函数得到所需角度,小车只用到pitch角

pitch=asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3; //俯仰角

roll=atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3; //横滚角

yaw=atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) * 57.3; //航向角




============那这3个角度又是什么呢==========

  • roll的意思是翻滚,中文中飞机的翻滚是什么,就是绕着机身所在的那个轴。
  • yaw:是偏航的意思,如果要改变航向,飞机必定是绕着重力方向为轴。
  • pitch:有倾斜、坠落的意思。飞机在坠落时,必定会一头栽下去,以翅膀所在的直线为轴。
pitch():将物体绕X轴旋转(localRotationX) yaw():将物体绕Y轴旋转(localRotationY) roll():将物体绕Z轴旋转(localRotationZ) 如果有一个人站在(0,0,0)点,面向X轴正向,头顶向上方向为Y轴正向,右手方向为Z轴正向,那么旋转角度和方向的计算方法如下:

Yaw是围绕Y轴旋转,站在(0,0,0)点的人脚下是XOZ平面,以正角度为参数是向左转,以负角度为参数是向右转。
Pitch是围绕X轴旋转,站在(0,0,0)点的人脚下是XOY平面,以正角度为参数是向右倒,以负角度为参数是向左倒。

Roll是围绕Z轴旋转,站在(0,0,0)点的人脚下是YOZ平面,以正角度为参数是向后倒,以负角度为参数是向前倒。

===================================================

不过这些都是需要根据模块具体安装在物体的什么位置来确定的,因此之后调试时需注意,以免方向弄错了。

至于如何移植,可参照@passoni 的帖子,讲的比我好多啦~http://forum.eepw.com.cn/thread/274797/1

注意模块没连接好不能通信的话,就会在DMP_Init()函数中

if(temp[0]!=0x68)NVIC_SystemReset();

这句使M3不断重置哦!


共24条 1/3 1 2 3 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]