技术日志——第一篇

目录

开篇前言

工训竞赛是去年的下半年开始做的,很感兴趣,自己和队友们也很卖力,终于过了校赛。因为校赛用的是arduino mega2560作为主控,并且有着各种各样不规范的地方,经过老师的指点,有了写技术日志的兴趣。

一月底的时候开始学stm32,因为已经有了微机原理的基础,还有自己之前做的一些经验,学的比较快,但还是困难重重,所以这篇技术日志本身应该是从二月初开始记录,拖到了三月初,用了一个月的时间铺垫了stm32的基础,当然还有很大一部分时间是用来过年了。

为了加快开发速度,使用了stm32CubeMx生成初始代码。

stm32F4资源

直入主题,主控用的是stm32F407ZG,定时器是很重要的一部分。因此首先摆一张stm32F4系列的定时器资源图
stm32F4系列定时器资源

虽然不知道合不合理,但目前的总体布局和引脚分配如下

电机驱动A4950

电机驱动采用的是A4950,电机驱动其实有很多选型,两年前做的巡线小车采用的是L298N,后来的两轮平衡小车和北京市创新创业项目都使用的是TB6612,而这次之所以采用了A4950,其实也没有什么原因,可能是和卖电机的商家是同一商家,同时我也查了相关资料A4950有逐步替代TB6612的趋势,就选择了这款。
图片来源于商家
图片来源于商家

A4950是一个比较占资源的电机驱动,驱动一个电机正反转需要两路PWM,也就是说使用TB6612可以只用一个定时器,而A4950需要占用两个定时器,但好在stm32F4的定时器足够。这里采用TIM9~TIM14作为四个电机的PWM输出,总共8路PWM。其中TIM9和TIM12每个是两路PWM,TIM10、TIM11、TIM13、TIM14每个是一路PWM。至于为什么有一路并且有两路的PWM接口,这个目前我还不清楚了。
TIM9~TIM14挂载APB1时钟

最新版已更正为使用TIM3、TIM4定时器,且下表也对应更改完毕

引脚对应表

左前A,右前B  
AIN1    ------------   PA6
AIN2    ------------   PA7
BIN1    ------------   PB0
BIN2    ------------   PB1
VM      ------------   7.6-30V
GND     ------------   GND
VCC     ------------   5V
GND     ------------   GND

AOUT1   ------------  A电机线电源-
AOUT2   ------------  A电机线电源+
BOUT1   ------------  B电机线电源+
BOUT2   ------------  B电机线电源-

左后C,右后D  
AIN1    ------------   PD12
AIN2    ------------   PD13
BIN1    ------------   PD14
BIN2    ------------   PD15
VM      ------------   7.6-30V
GND     ------------   GND
VCC     ------------   5V
GND     ------------   GND

AOUT1   ------------  C电机线电源-
AOUT2   ------------  C电机线电源+
BOUT1   ------------  D电机线电源+
BOUT2   ------------  D电机线电源-

配置CubeMx

设置高速外部时钟HSE 选择外部时钟源
在这里插入图片描述
设置定时器
在这里插入图片描述

在 Parameter Settings 页配置预分频系数为 71,计数周期(自动加载值)]为 499,定时器溢出频率,即PWM的周期,就是 72MHz/(71+1)/(499+1) = 2kHz。
其余同理
在这里插入图片描述

设置时钟源
在这里插入图片描述

keil中添加处理函数

商家提供的程序

直接贴上保存以后使用
main.c

#include "stm32f10x.h"

#include "delay.h"
#include "moto.h"
#include "pwm.h"

 int main(void)
 {	
   SystemInit(); //配置系统时钟为72M   
	 delay_init();    //延时函数初始化

   pwm_int_TIM3(7199,0);      //初始化pwm输出 72M/((7199+1)*(0+1))=10000=10KHZ 
	 pwm_int_TIM4(7199,0);      //初始化pwm输出 72M/((7199+1)*(0+1))==10000 =10KHZ 
	
  while(1)
	{
	   control(1,10,7000);                //1为A电机正转  
//	 control(1,7000,10);                //1为A电机反转
//	 control(2,10,5000);                //2为B电机反转
//	 control(2,7000,10);                //2为B电机正转
	}
 }

pwm.c

#include "pwm.h"



/**************************************************************************
函数功能:pwm初始化TIM3
入口参数:arr:设为一个时钟频率的最大值  psc: 预分频值
返回  值:无
**************************************************************************/

void	pwm_int_TIM3(u16 arr,u16 psc)
{
	 GPIO_InitTypeDef GPIO_InitStructure;                 //定义结构体GPIO_InitStructure
	 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;       //定义结构TIM_TimeBaseStructure
	 TIM_OCInitTypeDef  TIM_OCInitStructure;               //定义结构TIM_OCInitStructure
	
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  //使能定时器3时钟
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PA端口时钟

	 GPIO_InitStructure.GPIO_Pin =    GPIO_Pin_6 | GPIO_Pin_7;          //PA7
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //IO口速度
	 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     	//复用模式输出
	 GPIO_Init(GPIOA, &GPIO_InitStructure);              //GBIOA初始化  
	
	 TIM_TimeBaseStructure.TIM_Period = arr;    //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 
   TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频
   TIM_TimeBaseStructure.TIM_ClockDivision = 0;  //设置时钟分割:TDTS = Tck_tim
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式

   TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

	 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;   //PWMTIM脉冲宽度调制模式1
 	 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //设置TIM输出比较极性为高
   TIM_OCInitStructure.TIM_OutputState=	TIM_OutputState_Enable;   //比较输出使能
	 
	 TIM_OC1Init(TIM3, &TIM_OCInitStructure);  //根据TIM_OCInitStructure中指定的参数初始化外设TIM3

   TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);	    //使能预装载寄存器

   TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据TIM_OCInitStructure中指定的参数初始化外设TIM3

   TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);	    //使能预装载寄存器
	 
	 TIM_ARRPreloadConfig(TIM3, ENABLE);      //使能自动装载允许位
	 
	 TIM_Cmd(TIM3, ENABLE);   //启动定时器TIM3
 }

 
 /**************************************************************************
函数功能:pwm初始化TIM4
入口参数:arr:设为一个时钟频率的最大值  psc: 预分频值
返回  值:无
**************************************************************************/
 

 void	pwm_int_TIM4(u16 arr,u16 psc)
{
	 GPIO_InitTypeDef GPIO_InitStructure;                 //定义结构体GPIO_InitStructure
	 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;       //定义结构TIM_TimeBaseStructure
	 TIM_OCInitTypeDef  TIM_OCInitStructure;               //定义结构TIM_OCInitStructure
	 
	 
	  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);  //使能定时器4时钟
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能PB端口时钟

	 GPIO_InitStructure.GPIO_Pin =   GPIO_Pin_6 | GPIO_Pin_7;          //PB7
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //IO口速度
	 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     	//复用模式输出
	 GPIO_Init(GPIOB, &GPIO_InitStructure);              //GBIOB初始化  
	
	 TIM_TimeBaseStructure.TIM_Period = arr;    //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 
   TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频
   TIM_TimeBaseStructure.TIM_ClockDivision = 0;  //设置时钟分割:TDTS = Tck_tim
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式

   TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

	 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;   //PWMTIM脉冲宽度调制模式1
 	 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //设置TIM输出比较极性为高
   TIM_OCInitStructure.TIM_OutputState=	TIM_OutputState_Enable;   //比较输出使能
	 
	 TIM_OC1Init(TIM4, &TIM_OCInitStructure);  //根据TIM_OCInitStructure中指定的参数初始化外设TIM4

   TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);	    //使能预装载寄存器

   TIM_OC2Init(TIM4, &TIM_OCInitStructure);  //根据TIM_OCInitStructure中指定的参数初始化外设TIM4

   TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);	    //使能预装载寄存器
	
	 TIM_ARRPreloadConfig(TIM4, ENABLE);      //使能自动装载允许位

	 TIM_Cmd(TIM4, ENABLE);   //启动定时器TIM4
	 
	 

}

motor.c

#include "moto.h"


/**************************************************************************
函数功能:电机的正反转
入口参数:
	  moto:电机  1=电机A,0=电机B
	  pwm1:IN1的PWM的CRR寄存器赋值占空比 就是pwm1/7200
		pwm2:IN2的PWM的CRR寄存器赋值占空比 就是pwm2/7200
返回  值: 无
   a=pwm1-pwm2
  |a|  的大小决定转速度
   a   的符号决定转速
**************************************************************************/

void control(int moto, int pwm1, int pwm2)
{
	if(moto==1)
	{
	 TIM_SetCompare1(TIM3,pwm1);   //设置TIM3通道1的占空比  pwm1/7200
	 TIM_SetCompare2(TIM3,pwm2);   //设置TIM3通道2的占空比  pwm2/7200
	}
	
	if(moto==2)
	{
	 TIM_SetCompare1(TIM4,pwm1);   //设置TIM3通道1的占空比  pwm1/7200
	 TIM_SetCompare2(TIM4,pwm2);   //设置TIM3通道2的占空比  pwm2/7200
	}

}

编码器

编码器是我们经常使用的电机编码器自带的
在这里插入图片描述
一个编码器有A相和B相两相,因此要使用四个定时器来连接编码器,这里采用TIM2~TIM5

新版本已经更新为外部中断形式的编码器,没有使用定时器,下面的引脚对应表目前也是最新的

引脚对应表

A电机  
A相    ------------   PE1

B电机  
A相    ------------   PE2

C电机  
A相    ------------   PE3

D电机  
A相    ------------   PE0

配置CubeMx

SYS: DEBUG选择 SW模式
在这里插入图片描述
TIM2~TIM5 配置成定时器模式 Combined Channels: Encoder Mode
在这里插入图片描述

TIM2~TIM5 定时器分频配置: 这里分频数要注意一下,Prescaler 直接给0 ,Counter Period给65535,下面的Encoder Mode 如果是TI1的话就是只计数上升沿的脉冲,如果是TI2 andTI2 就是上下沿都计,脉冲是前一个的两倍
在这里插入图片描述

TIM Encoder GPIO上拉模式配置,两个定时器四个引脚,全部改成 Pull-Up,即上拉模式,主要用于没有外部上拉的编码器读取时,可以确定引脚电平,防止出错(但实际上应该是自带上拉的)
在这里插入图片描述
这里的GPIO mode理论上来讲应该设置为Input Mode模式,但是我还不清楚为什么设置不了,先不考虑这个问题了,近期不使用编码器,在后面解决。

keil中添加处理函数

打开生成的文件,在main.c中初始化的时候开启编码器计数

HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);

在循环中调用 __HAL_TIM_IS_TIM_COUNTING_DOWN 可以获得当前电机的转向 0为正、1为负

DirectionA = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim3);
DirectionB = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim4);

在循环中调用 __HAL_TIM_GET_COUNTER 获取计数器的计数值,即编码器的脉冲数

CaptureNumberA=__HAL_TIM_GET_COUNTER(&htim3);
CaptureNumberB=__HAL_TIM_GET_COUNTER(&htim4);

个人感觉编码器的问题很大,在电机和陀螺仪的实际程序写好后调试编码器。

HWT101陀螺仪

HWT101可以使用串口和IIC两种模式连接,这里采用了串口中断的方式连接
在这里插入图片描述
波特率为115200

引脚对应表

USART1与程序线连接,也用USART1来查看数据
用USART2与HWT101连接

RX    ------------   PD5
TX    ------------   PD6

配置CubeMx

这里把USART1~3都配置完,留USART3作备用,可能与Jetson Nano通讯,也可能有其他用
在这里插入图片描述
记得开启串口中断

keil中添加处理函数

HAL库发送数据到串口1上的函数举例

uint8_t UART_BUF[12] = “you press A!”;
HAL_UART_Transmit(&huart1,UART_BUF,12,0xffff);

定义三个变量

uint8_t RxByte;
uint8_t RxBuff[256];
uint16_t Rx_Count;

开启串口1和串口2的中断

MX_USART1_UART_Init();
MX_USART2_UART_Init();

串口2接收1个字节的数据存入RxByte,并调用回调函数

HAL_UART_Receive_IT(&huart2,&RxByte,1);

回调函数HAL_UART_RxCpltCallback是一个软函数,重定义后会覆盖掉原来定义过的内容

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  UNUSED(huart);
  RxBuff[Rx_Count++]=RxByte;
	
	if(RxByte==0x55)
	{
		while(HAL_UART_Transmit_IT(&huart1,RxBuff,Rx_Count)==HAL_OK);
		Rx_Count=0;
	}
	if(Rx_Count>=254)
	{
		Rx_Count=0;
	}
	while(HAL_UART_Receive_IT(&huart2,&RxByte,1)==HAL_OK);
}

这个函数没有经过优化,只是已经测试完成可以使用,效果为将HWT101返回的数据全部输出到串口里,在串口调试器里显示出来,数据处理程序后续写。

通讯协议与数据处理

在这里插入图片描述

在这里插入图片描述
详细解算示例:
http://www.openedv.com/forum.php?mod=viewthread&tid=79352&page=1&extra=#pi d450195.

HWT101的硬件Z轴归零

在这里插入图片描述

总结

这篇文章相当于是把前两天的工作做一个总结记录,没有进行实际的操作。输出PWM波用示波器测了确实是PWM波,但没有接电机测试,不知道是否真的可以转,而HWT101的部分也确实返回了数据,但数据处理并没有做,也许也是一个难点。因此接下来的计划是:

  • 完成HWT101的数据处理并得到正确的数据
  • 完成电机的测试
  • 完成编码器的测试

先完成以上三点吧,毕竟课设还需要时间,再进行下一步计划

因为自己比较嫌麻烦,以前做项目都是用的Arduino,编程、接线都很方便,也不需要初始化什么,arduino的开源库也非常好用。但想了想还是觉得想要真正学好嵌入式开发,并且应用,就得走出自己的舒适区,不能懒,因此才开始学stm32,期间也受到了很多同学和老师的帮助,自己才进步的这么快。

主要使用到的资料
stm32CubeMx串口通讯配置: https://zhuanlan.zhihu.com/p/344527023.
stm32CubeMx编码器配置: https://zhuanlan.zhihu.com/p/94382263.
stm32CubeMxPWM配置: https://mp.weixin.qq.com/s?src=11&timestamp=1614657101&ver=2921&signature=t4vFmuuwuFbT3-u0NbWqfvi1FgsrN4Rq8SH-bnvx70C-R6uPCRY8rbs8-bgbEUZyLeOx0zxXeUyOgzO5kBYDyNQHf07471QcXKlQ3MN4wJmIoxY3o-y8ma6N0pO1du&new=1.
除此之外还有非常多的资料,但是太杂了,无法一一列举出来了。

技术日志的第一篇完。

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值