基于STM32电机驱动学习笔记

一、电机介绍

(一)电机简介

电机,即电动机(Motor),也称之为马达,是把电能转换成机械能的一种设备。

(二)电机分类

① 按照电源进行分类:直流电动机、交流电动机;
② 按用途区分进行分类:驱动用电动机、控制用电动机
③ 按运转速度分类 :高速电动机、低速电动机、恒速电动机、调速电动机

(三)各种电机的简单介绍

(1)直流有刷电机

直流有刷电机(Brushed DC Motor)是内含电刷装置将直流电能转换成机械能的电动机。
特点

  1. 结构简单、操控方便、成本低廉等特点
  2. 电刷和换向器之间有摩擦,导致换向时产生电火花易产生电磁干扰,故障多,维护工作量大,寿命短
  3. 主要应用场景:各类电动玩具,电动自行车,印刷机械等等
    原理
    在这里插入图片描述
    左手定则(死去的记忆偷袭我)
    中间镂空的位置是为了保持转子换向的

(2)直流无刷电机

直流无刷电机(BLDC)是指无电刷和换向器(或集电环)的电机,又称无换向器电机。
特点
1、没有了碳刷结构,干扰小,噪音低,运转流畅,高速,而且寿命更长
2、控制较为复杂,可以使用方波或者正弦波换相
3、主要应用场景:四轴飞行器,汽车,工业工控,发动机等等
外转式-四轴飞行器
在这里插入图片描述
内转式-工业控制
在这里插入图片描述

(3)步进电机

步进电机是一种把电脉冲信号转换为角位移或线位移的电动机。
特点
1、控制简单,低速扭矩大、速度取决脉冲频率、角位移取决脉冲个数等特点
2、存在空载启动频率,不可高于该频率否则就会丢步甚至是堵转。
3、主要应用场景:3D打印机,绘图仪,数控机床等等

在这里插入图片描述

(4)伺服电机

伺服电机(servo motor)可以理解为绝对服从控制信号指挥的电机
特点
1、可使控制速度,位置精度非常准确,效率高,寿命长
2、驱动器可设置电机工作在转速、转矩、位置等模式
3、价格昂贵,一套至少都是大几k以上;控制较复杂。
4、主要应用场景:自动化生产线,机器人,自动化工业设备等等
在这里插入图片描述

(5)舵机

舵机(Servo)实际上可以看作一个伺服电机,主要由直流电机、减速齿轮组、角度传感器、控制电路构成
特点
1、一般而言旋转角度范围在0-180°
2、闭环系统,可以反馈转动的角度信息
3、通过控制PWM脉冲占空比大小,指定输出轴的旋转角度
4、主要应用场景:飞机的舵面,机器人关节等等
在这里插入图片描述
在这里插入图片描述

二、 直流有刷电机入门

(一)直流有刷电机简介

直流有刷电机(BDC)是一种内含电刷装置的将直流电能转换成机械能的电动机。在允许的范围之内,供电即可工作,只需要调整电压,即可调整电机的转速
拥有良好的调速性能

(二)直流有刷电机结构

直流有刷电机结构包含:定子、转子、电刷和换向器
定子:产生固定的磁场
转子:由一个或多个绕组构成,通电后在磁场中受力运动
电刷:将外部电流输入到转子绕组上
换向器:改变绕组中电流的流向
在这里插入图片描述

优点缺点
驱动简单、操控方便、成本低廉寿命短、可靠性差、换向火花易产生电磁干扰

(三)直流有刷电机应用场景

直流有刷电机常被应用于电动玩具、砂轮机、电风扇、低端电动自行车等方面
对于扭矩有要求的场所我们会加上减速齿轮,以增大扭矩
电机的输出功率是一定的,电机的转速跟扭矩成反比,通过加装减速齿轮,起到降低速度,提高扭矩的作用。

(四)直流有刷电机参数

参数解释
额定电压电机正常工作的电压
额定电流也叫负载电流,电机带负载正常工作时的电流。电机不带负载的电流叫空载电流。如果电机的负载太大导致电机堵转,这时的电流叫做堵转电流。通过分析电机的电流就可以知道电机的工作是否正常。
额定转速也叫负载转速,单位是 r/min,也常用 RPM 表示
额定扭矩电机额定电流下输出力的大小,单位常用 kg · cm
减速比电机原始转速和经过减速器之后转速的比值,表示为N:1

注意:不要使用过大负载,防止堵转造成电机过热

(五)直流有刷电机原理

1.左手定则

左手四指和大拇指伸直,使其相互垂直。磁场方向垂直穿过掌心,四指指向电流方向,大拇指所指的方向即所受安培力方向。
在这里插入图片描述
嗯嗯学费了

2.工作原理分析

在这里插入图片描述

S、N:定子磁极
A、B:电刷
C、D:换向器
线圈:转子绕组
磁场方向:N极到S极
换向器的作用是,当转子转过一定角度时,对于转子而言,电流反向,此时转子的受力依旧保持不变。
在这里插入图片描述

反转原理:改变电流方向

3.测速原理

测量转速可以采用编码器计数的原理。磁电编码器由磁盘和霍尔传感器组成。磁盘是由一个个交替的S极和N极组成,利用霍尔传感器的霍尔效应(即霍尔传感器对S极和N极感知的变化),可以生成脉冲信号。
在这里插入图片描述

二、IIC总线

(一)IIC

1.IIC总线协议介绍

IIC:Inter Integrated Circuit,集成电路总线,是一种同步 串行 半双工通信总线。

2.IIC总线结构图(拓扑图)

在这里插入图片描述
① 由时钟线SCL和数据线SDA组成,并且都接上拉电阻,确保总线空闲状态为高电平
② 总线支持多设备连接,允许多主机存在,每个设备都有一个唯一的地址
③ 连接到总线上的数目受总线的最大电容400pf限制
④ 数据传输速率:标准模式100k bit/s 快速模式400k bit/s 高速模式3.4Mbit/s

3.IIC协议

在这里插入图片描述
起始信号(S):当SCL为高电平时,SDA从高电平变为低电平
停止信号§:当SCL为高电平时,SDA从低电平变为高电平
应答信号:上拉电阻影响下SDA默认为高,而从机拉低SDA就是确认收到数据即ACK,否则NACK
数据先发送高位
数据以字节(8bit)传输
数据在SCL高电平稳定

八位数据发送完成释放SDA,主机把SDA数据线的控制权交给从机,
在这里插入图片描述
Start and Stop

void iic_start(void)
{ /* SCL为高电平期间, SDA从高电平往低电平跳变*/
    IIC_SDA ( 1 );	
	IIC_SCL ( 1 );
    iic_delay( );
 	IIC_SDA ( 0 );	
 	iic_delay( );
    IIC_SCL ( 0 );	
 	iic_delay( );  /* 钳住总线, 准备发送/接收数据 */
}
void iic_stop(void)
{ /* SCL为高电平期间, SDA从低电平往高电平跳变*/
    	IIC_SDA ( 0 );	
	iic_delay( );
 	IIC_SCL ( 1 );	
 	iic_delay( );
    	IIC_SDA ( 1 ); 	/* 发送总线停止信号*/
 	iic_delay( );
}

主机还需要干的事情

  • 检测应答信号
  • 发送应答信号
uint8_t iic_wait_ack (void) /* return 1:fail 0:succeed*/
{	
	IIC_SDA (1);  /* 主机释放SDA线 ,把主动权交给从机*/
	iic_delay( );
	IIC_SCL (1);  /* 从机返回ACK*/ 	
 	iic_delay( );
	if ( IIC_READ_SDA ) /* SCL高电平读取SDA状态*/ 
	{
		iic_stop();	    /* SDA高电平表示从机nack */ 
		return 1;
	}
	IIC_SCL(0);	 /* SCL低电平表示结束ACK检查 */ 
 	iic_delay( );
	return 0;
void iic_ack(void)//继续向从机要数据
{ 
    IIC_SCL (0);	
	iic_delay( );
 	IIC_SDA (0);  /* 数据线为低电平,表示应答 */
 	iic_delay( );
    IIC_SCL (1); 	
 	iic_delay( );
}
void iic_nack(void)
{ 
    IIC_SCL (0);	
	iic_delay( );
 	IIC_SDA (1);  /* 数据线为高电平,表示非应答 */
 	iic_delay( );
    IIC_SCL (1); 	
 	iic_delay( );
}

发送1字节数据

void iic_send_byte(uint8_t data)
{
	for (uint8_t t = 0; t < 8; t++)
	{	/* 高位先发 */
		IIC_SDA((data & 0x80) >> 7);
 		iic_delay( );
 		IIC_SCL ( 1 );	
 		iic_delay( );
 		IIC_SCL ( 0 );
		data <<= 1; /* 左移1位, 用于下一次发送 */
	}
	IIC_SDA ( 1 ); 	/* 发送完成,主机释放SDA线 */ 
}

读取1字节数据

uint8_t iic_read_byte (uint8_t ack) /* 1:ack 0:nack*/
{ 
	uint8_t receive = 0 ;
	for (uint8_t t = 0; t < 8; t++)
	{	/* 高位先输出,先收到的数据位要左移 */ 
		receive <<= 1;		
		IIC_SCL ( 1 );	
 		iic_delay( );
		if ( IIC_READ_SDA ) receive++;
 		IIC_SCL ( 0 );
		 iic_delay( );
	}
	if ( !ack ) iic_nack();
	else iic_ack();
	return receive; 	
}

(二)AT24C02介绍

EEPROM是一种掉电后数据不丢失的储存器,常用来存储一些配置信息,在系统重新上电时就可以加载。AT24C02是一个2K bit(256byte)的EEPROM存储器,使用IIC通信方式。
在这里插入图片描述
A0/1/2 : 设备地址决定引脚
WP : 写保护引脚(接0就是关闭写保护)
SCL : 时钟线
SDA : 数据线
AT24C02通讯地址
设备地址不包括读写位
通讯地址有包括读写位
在这里插入图片描述

(三)AT24C02读写时序

AT24C02支持的读写操作
写操作
AT24C02支持字节写模式和页写模式。
字节写模式就是一个地址一个数据进行写入。
页写模式就是连续写入数据。只需要写一个地址,连续写入数据时地址会自增,但存在页的限制,超出一页时,超出数据覆盖原先写入的数据。但读会自动翻页。
读操作
AT24C02支持当前地址读模式,随机地址读模式和顺序读模式。
当前读模式是基于上一次读/写操作的最后位置继续读出数据。
随机地址读模式是指定地址读出数据。
随机地址读模式是指定地址读出数据。
数据有效性
IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

1.AT24C02写时序

在这里插入图片描述
起始信号
地址和方向
应答信号从机发送应答,主机等待应答
内存地址
应答信号从机发送应答,主机等待应答
数据内容
应答信号从机发送应答,主机等待应答
停止信号
注意:EEPROM比较慢,必须等到10ms后再写下一个字节

2.AT24C02读时序

在这里插入图片描述
起始信号
地址和方向
应答信号从机发送应答,主机等待应答
内存地址
应答信号从机发送应答,主机等待应答
(以上步骤的目的是告诉从机主机要操作的内存地址)
起始信号
地址和方向就是最后一位读写位要置1
应答信号从机发送应答,主机等待应答
数据内容
应答信号
停止信号

(三)AT24C02驱动步骤

硬件和软件IIC对比

IIC用法速度稳定性管脚
硬件IIC比较复杂较稳定需使用特定管脚
软件IIC操作过程比较清晰较慢稳定任意管脚,比较灵活

IIC配置步骤
1、使能SCL和SDA对应时钟__HAL_RCC_GPIOB_CLK_ENABLE()

2、设置GPIO工作模式SDA开漏/SCL推挽输出模式,使用HAL_GPIO_Init初始化

3、编写基本信号 起始信号 停止信号 应答信号
主机:send ack send nack
从机:wait ack

4、编写读和写函数
iic_read_byte
iic_send_byte
注意:发送完成,主机释放SDA

为什么IIC总线SDA建议用开漏模式?
IIC的SDA脚即要作为输出,又要作为输入,用开漏输出模式,很好实现输出输入共用,避免IO模式频繁切换带来的麻烦。
输出时:主机(MCU)输出0,可以拉低信号,来实现低电平发送,主机输出1(实际不起作用),由外部上拉电阻上拉,实现高电平发送。
输入时:主机(MCU)设置输出1状态,此时因为MCU无法输出1,相当于释放了SDA脚,此时外部器件可以主动拉低SDA脚/释放SDA脚(同样由上拉电阻提供“输出1的功能”),实现SDA脚的高低电平变化。
由于开漏输出模式下,MCU还是可以读取IDR状态寄存器,来获取引脚高低电平,因此MCU读取IDR,即可获得SDA脚的高低电平状态,从而实现输入检测。

AT24C02
1、初始化IIC接口
2、编写写入/读取一个字节数据函数 遵循时序流程编写

3、编写连续读和连续写函数(在2的基础上进行实现

(四)编程实战

1. cube

RCC
在这里插入图片描述

** 时钟树**
在这里插入图片描述

IIC
在这里插入图片描述
Master  features  主模式特性
I2C Speed Mode: IIC模式设置 快速模式和标准模式。实际上也就是速率的选择。
I2C Clock Speed:I2C传输速率,默认为100KHz
Slave  features  从模式特性
Clock No Stretch Mode: 时钟没有扩展模式
IIC时钟拉伸(Clock stretching)
clock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行.clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟.
Primary Address Length selection: 从设备地址长度 设置从设备的地址是7bit还是10bit 大部分为7bit
-Dual Address Acknowledged: 双地址确认
Primary slave address:  从设备初始地址

USART
在这里插入图片描述
我们需要将AT24C02中存储的数据发送到上位机上

keil

需要用到的函数

 HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
 HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

*hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c2
DevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0
MemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1
MemAddSize: 从机寄存器地址字节长度 8位或16位
写入数据的字节类型 8位还是16位
I2C_MEMADD_SIZE_8BIT
I2C_MEMADD_SIZE_16BIT
抑或是用之前串口使用的printf函数也可以

三、MPU

(一)MPU6050简介

MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
加速度计只需要测量出力的大小就能通过F=ma计算出加速度
(测力计)
角速度只需要积分就能算出角度
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(二)MPU6050参数

  • 16位ADC采集传感器的模拟信号,量化范围:-32768~32767
  • 加速度计满量程选择:±2、±4、±8、±16(g)
  • 陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
    (满量程就是相当于测量范围和分辨率的权衡)
  • 可配置的数字低通滤波器
  • 可配置的时钟源
  • 可配置的采样分频
  • I2C从机地址:1101000(AD0=0)1101001(AD0=1)(二进制)
    如果要将二进制的地址改成十六进制的地址
    一种方法是将设备地址的前三位和后四位分开。
    另一种方法是将设备地址左移一位(融入读写位)

(三)硬件电路

在这里插入图片描述

引脚功能
VCC、GND电源
SCL、SDA I2C通信引脚
XCL、XDA主机I2C通信引脚
AD0从机地址最低位
INT中断信号输出
左下角的六个排针中大多数都是最小系统板固定死了,就是那个XDA和XCL可以为以后外接磁力计和气压计和气压计。MPU6050可以直接读取扩展芯片的数据,在MPU6050芯片中会有DMP单元可有进行数据融合和姿态解算。
左上角是LDO供电模块能输出稳定的3.3v电压给芯片端供电

(四)MPU6050框图

![在这里插入图片描述](https://img-blog.csdnimg.cn/82b278c03eba46359554d322e7882d0c.png
左上角是外部时钟的输出引脚和输入引脚(但其实大多数情况是用内部时钟的)
左边是Selftest就是mpu6050自带的一个自测模块,使能时会向传感器施加一个外力,使能时又会得到一个数据,两个数据相减如果在对应芯片手册的允许范围之内,就是正常的e
灰色部分是三轴加速计和三轴陀螺仪外加一个温度传感器(本质上都是可变电阻,通过分压后输出模拟电压)
温度传感器下边的那个东西是电荷泵升压
右侧引脚输出部分依次是:中断寄存器、FIFO先入先出寄存器(对数据流进行缓存)、配置继勋奇、传感器寄存器、数值运动春初器
最右侧是GPIO接口跟用于与扩展设备进行通讯的接口
serial Interface Bypass Mux是接口旁路选择器,就是一个开关,拨到上面MPU6050和他的拓展设备就都是STM32的从机,如果拨到下边STM32是MPU6050的主机,MPU6050是其他拓展设备的主机,下边是供电
实际上我们只需要一个IIC使能、读取姿态传感器的数据并反馈到OLED/串口上即可

实际的配置可参考小小白的博客

添加链接描述

亲测有效

四、直流电机驱动实验

(一)直流电机驱动基础实验

笔者的直流电机是跟着正点原子学的,由于所使用的单片机和驱动板与正点原子的板子并不相同(我的板子是STM32F103C8T6和TB661FNG),在跟着他们学电机的过程中会加入自己的理解和与他们开源的代码进行本土化适配的想法。再说一次,正点原子——我的超人。

1.PWM初始化

PWM互补输出和死区控制的代码与之前在高级定时器所学内容一致。

2.motor初始化

输入引脚输入引脚输出引脚输出引脚
INSDHOLO
-000
0101
1110
void dcmotor_init(void)
{
    SHUTDOWN_GPIO_CLK_ENABLE();     /* PF口时钟使能 */
    GPIO_InitTypeDef gpio_init_struct;
    /* SD引脚和IN引脚共同是控制输出引脚输出的正负 */
    /* 当SD引脚被设置为0时电机停止输出 */
    /* SD引脚设置,设置为推挽输出 */
    gpio_init_struct.Pin = SHUTDOWN1_Pin|SHUTDOWN2_Pin;
    /*初始化的两个引脚都是SD引脚*/
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
    gpio_init_struct.Pull = GPIO_NOPULL;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(SHUTDOWN1_GPIO_Port, &gpio_init_struct);
    HAL_GPIO_WritePin(GPIOF, SHUTDOWN1_Pin|SHUTDOWN2_Pin, GPIO_PIN_RESET);      /* SD拉低,关闭输出 */
    
    dcmotor_stop();                 /* 停止电机 */
    dcmotor_dir(0);                 /* 设置正转 */
    dcmotor_speed(0);               /* 速度设置为0 */
    dcmotor_start();                /* 开启电机 */
}

对于我的TB661FNG就应该是

AIN1AIN2状态
00停止
10正转
01反转
BIN1BIN2状态
00停止
10正转
01反转
在这里插入图片描述
原理图
我的TB661FNG可以同时驱动两个到电机,两个电机驱动的H桥就是分别对应的AIN1、AIN2和BIN1、BIN2。所以我这边初始化要将所有的输入引脚置0.
    HAL_GPIO_CLK_ENABLE();
    GPIO_InitTypeDef gpio_init_struct;
    gpio_init_struct.Pin = AIN1|AIN2;
    /*初始化的两个引脚都是SD引脚*/
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
    gpio_init_struct.Pull = GPIO_NOPULL;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(SHUTDOWN1_GPIO_Port, &gpio_init_struct);
    HAL_GPIO_WritePin(GPIOF, AIN1|AIN2, GPIO_PIN_RESET);      /* SD拉低,关闭输出 */

3.motorstart

void dcmotor_start(void)
{
    ENABLE_MOTOR;                                                      
     /* 拉高SD引脚,开启电机 */
}
#define ENABLE_MOTOR  HAL_GPIO_WritePin(SHUTDOWN1_GPIO_Port,SHUTDOWN1_Pin,GPIO_PIN_SET)
/*视频里正点原子的motor启动函数是还有一行定时器使能的代码*/
/*程序源码中被弄到定时器初始化那里了*/

4.motorstop

void dcmotor_stop(void)
{
     DISABLE_MOTOR                                                      
     /* 拉低SD引脚,关闭电机 */
}
#define DISABLE_MOTOR   HAL_GPIO_WritePin(SHUTDOWN1_GPIO_Port,SHUTDOWN1_Pin,GPIO_PIN_RESET)

5.motordirection

/**
 * @brief       电机旋转方向设置
 * @param       para:方向 0正转,1反转
 * @note        以电机正面,顺时针方向旋转为正转
 * @retval      无
 */
void dcmotor_dir(uint8_t para)
{
    HAL_TIM_PWM_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);   /* 关闭主通道输出 */
    HAL_TIMEx_PWMN_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);/* 关闭互补通道输出 */
    if (para == 0)                /* 正转 */
    {
        HAL_TIM_PWM_Start(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);/* 开启主通道输出 */
    } 
    else if (para == 1)           /* 反转 */
    {
        HAL_TIMEx_PWMN_Start(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);/* 开启互补通道输出 */
    }
}

我感觉我可以不用互补通道就能实现正反转的切换

AIN1AIN2状态
00停止
10正转
01反转
这个玩意他本身就是一个H桥
只需要用HAL_GPIO_WritePin(GPIOx,GPIO_PIN_x,1/0);就可以控制正转反转

6.motorsetspeed

通过改变比较值就能达到限速的目的

void dcmotor_speed(uint16_t para)
{
        __HAL_TIM_SetCompare(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1, para);
}

五、编码器

(一)編碼器入門

1.编码器简介

编码器:一种将直线位移、角位移数据转换为脉冲信号、二进制编码的设备。
常用于测量物体运动的位置、角度或者速度

2.编码器分类

在这里插入图片描述
编码器可以按照检测原理、编码类型进行分类
1.光电+绝对式
2.光电+增量式
3.磁电+绝对式
4.磁电+增量式

3.编码器原理

磁电+增量式:利用霍尔效应,将位移转换成计数脉冲,用脉冲个数计算位移速度

4.编码器参数

分辨率:编码器可以测量的最小距离。 对于增量式编码器,分辨率即转轴每旋转一圈所输出的脉冲数(PPR)
精度:编码器输出的信号数据与实际位置之间的误差,常用 角分 ′ 、角秒 ″ 表示
最大响应频率:编码器每秒能输出的最大脉冲数,单位Hz,也称为PPS
最大转速:指编码器机械系统所能承受的最高转速

(二)STM32編碼器接口

1.编码器接口简介

STM32定时器的编码器接口模式就相当于带有方向选择的外部时钟
当编码器电机正转的时候,输出的PWM波是以A的上升沿为初相,编码器电机反转的时候是以B的上升沿为初相。当然我的电机貌似也是两个霍尔传感器的相位差是90度。
编码器正转(此时为向上计数模式)

在这里插入图片描述
编码器反转(此时为递减计数模式)
在这里插入图片描述

2.编码器接口框图

在这里插入图片描述
两个PWM波只能是从定时器的通道一和通道二进行输入。进过滤波器和边沿检测器后输入TI1FP1,然后接到编码器接口,编码器接口再接到下方的时基模块。由此将编码器输出的脉冲信号转化为计数值吗。

3.编码器接口计数原理

首先需要控制寄存器:TIMx 从模式控制寄存器 (TIMx_SMCR),控制位 2:0 SMS:从模式选择

SMC位从模式倍频
001计数器根据 TI1FP1 电平,仅在 TI2FP2 边沿递增/递减计数二倍
010计数器根据 TI2FP2 电平,仅在 TI1FP1 边沿递增/递减计数二倍
011计数器在 TI1FP1 和 TI2FP2 的边沿都计数,计数的方向取决于另外一个信号的电平四倍
例如001:计数器根据 TI1FP1 电平,仅在 TI2FP2 边沿递增/递减计数

在这里插入图片描述
正转时的PWM波
在这里插入图片描述
由于正转时的在A相高电平期间B相都是上升沿,在A相低电平期间B相都是低电平,所以就都是递增计数模式。反转的时候,在A相高电平期间B相都是下降沿,在A相低电平的时候B相都是上升沿,所以都是递减计数模式。
在这里插入图片描述
010模式也是一致的
010:计数器根据 TI2FP2 电平,仅在 TI1FP1 边沿递增/递减计数
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
道理我是懂得但是我不知道为什么是这样的

4.计数溢出处理

在这里插入图片描述
在这里插入图片描述
当前总计数值 = 计数器当前值 + 溢出次数 * 65536
当计数模式是递增计数模式的时候,如果计数值中断就在重复计数寄存器里+1,这样就能算出真实值。单计数模式是递减计数模式的时候,计数器TIMx_CNT的复位值是0,由于向下递减计数器马上会发生溢出中断,此时溢出次数减1.再用第二次的值减去第一次的值就能算出变化量。由于再递减计数模式的时候,第二次的值减去第一次的值算出来是负数,此时说明计数器反转了。通过变化量的正负就能算出正转/反转了。
在TIMx控制寄存器(TIMx_CR1)
在这里插入图片描述
位4 DIR写0是计数器递增计数写1是计数器递减计数,这样我们才能在计数溢出时,读取到溢出方向,后续才能计算总的计数变化量

(三)编码器测速

1.编码器接口HAL库函数

函数主要功能
HAL_TIM_Encoder_Init()初始化定时器基础参数及编码器接口
HAL_TIM_Encoder_Start()开启编码器接口通道
HAL_TIM_PeriodElapsedCallback()定时器更新中断回调函数
__HAL_TIM_IS_TIM_COUNTING_DOWN()读取DIR位,判断计数方向
关键结构体介绍(编码器初始化结构体)
typedef struct 
{ 
    uint32_t EncoderMode;		/* 编码器模式(设置在A/B/AB检测,这也决定是2/2/4倍频) */ 
    uint32_t IC1Polarity;       /* 输入极(反相/不反相)性(边沿检测器)*/ 
    uint32_t IC1Selection;		/* 输入通道选择 */ 
    uint32_t IC1Prescaler;		/* 时钟分频因子 */ 
    uint32_t IC1Filter;     	/* 滤波器 */ 
    uint32_t IC2Polarity; 		
    uint32_t IC2Selection; 		
    uint32_t IC2Prescaler; 		
    uint32_t IC2Filter; 
} TIM_Encoder_InitTypeDef;

2.cube

笔者使用的主控是STM32F407ZGT6,电机驱动模块为AT8236
RCC
在这里插入图片描述
选择外部高速晶振
SYS
在这里插入图片描述
TIM
定时器1
在这里插入图片描述

  1. 将定时器一配置为编码器模式
  2. 通道x计数模式并不是如它英文Rising Edge一样的上升沿计数的意思,而是计数模式的意思,这里算是cubeMX的小问题。
    他的真实含义是相对信号的电平计数模式。
    什么是相对信号?其实就是CHA相对CHB的的电平或者CHB相对CHA的电平。
    Rising Edge模式下CH1通道是遇到虚线出数值发生一次变化,左边为每次遇到虚线加一,右边为每次遇到虚线减一。即一个周期编码器计数两次。(Falling Edge模式加减相反)
    知道Rising Edge什么意思了,CHB的就略过了,可以直接看下图理解。
    TI1是CHA,TI2是CHB
  3. 定时器的分频系数为0,ARR为65535.这是因为定时器的编码器模式相当于外部脉冲计数模式:给一个脉冲信号计数值就+1。
  4. 编码器模式设置为Encode Mode T1 and T2这是两个通道的上升沿和下降沿都进行检测。这是相当于给编码器4倍频。
    定时器2
    在这里插入图片描述
    这里我将定时器2配置为PWM输出模式。分频系数为167,自动重装载值为499.意味着此时PWM波输出的频率为2KHz。
    定时器7
    在这里插入图片描述
    定时器7就是一个普通的用于计数的基本是定时器。
    为了给后续的pid打框架
    APB2:TIM11 TIM10 TIM9 TIM8 TIM1
    APB1:TIM14 TIM13 TIM12 TIM7 TIM6 TIM5 TIM4 TIM3 TIM2

3.Keil

首先需要跑通下面这些简单的代码才能继续走

HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);//打开定时器的encoder模式
Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim1);//可以获得当前电机的转向
enc1 = (__HAL_TIM_GET_COUNTER(&htim1));//获取encoder编码器的计数值

在主函数中写初始化定时器与中断

  HAL_TIM_Base_Start_IT(&htim7);//打开定时器中断
  HAL_TIM_Base_Start_IT(&htim1);//打开定时器中断
  HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//使能PWM输出
  __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 0);//设置PWM比较值	
	HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);      //开启编码器定时器
  __HAL_TIM_ENABLE_IT(&htim1,TIM_IT_UPDATE);         	 //开启编码器定时器更新中断,防溢出处理

中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	unsigned char Direction;
	unsigned int enc1;
 	/*判断当进入定时器7中断时*/
	if(htim->Instance == TIM7)
	{
//		CNT++;
//		Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim1);//可以获得当前电机的转向
//		enc1 = (__HAL_TIM_GET_COUNTER(&htim1));//获取encoder编码器的计数值
//		if(CNT%100==0)
//		{
//			printf("Direction=%d,enc1=%d\r\n",Direction,enc1);	
//			CNT=0;
//		}
		Encode_now = get_encode();
		printf("%lf\r\n",speed);
		var=Encode_now-Encode_old;
		Encode_old=Encode_now;
		speed=(double)var*10/1320;
	}
	if(htim->Instance == TIM1)
	{
		if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim1))   /* 判断CR1的DIR位 */
        {
            encode_count--;                                      /* DIR位为1,也就是递减计数 */
        }
        else
        {
            encode_count++;                                      /* DIR位为0,也就是递增计数 */
        }	
	}
}

测速的代码其实很简单

int get_encode(void)
{
    return (int32_t)__HAL_TIM_GET_COUNTER(&htim1) + encode_count * 500;       /* 当前计数值+之前累计编码器的值=总的编码器值 */
}

在收取到编码器脉冲次数得基础上/一圈脉冲数/倍频数/采样周期即可
解释一下中断函数回调函数,中断回调函数的溢出之后,读取Direction的值判断电机正转/反转(其实在电机开始转动输出PWM波就可以判断正转和反转,这是由于正转和反转的相位是不一样的)。

(四)编码器较为精准的测速

1.冒泡排序

挺突然的 冒泡排序

int  i , j , temp;
int  data[ 10 ] = { 1 , 5 , 4 , 3 , 9 , 10 , 15 , 14 , 6 , 0 } ;
                
for ( i = 10 ; i >= 1 ; i-- ) 				/* 冒泡排序 */ 
{
        for ( j = 0 ; j < ( i – 1 ) ; j++ ) 
        {
                if ( data[ j ] > data[ j + 1] )    	/* 数值大小比较 */
                { 
                        temp = data[ j ] ; 			/* 数值换位 */
 		      data[ j ] = data[ j + 1 ] ;
 		      data[ j + 1 ] = temp ;
                 }
         }
}

通过两个嵌套的循环,每次循环两两交换位置,把最大的数交换到后面去
在电机中的运用:减去最大值和最小值 剩下的值取平均数就能达到滤波的效果

2.一阶低通滤波

滤掉高频的部分(为了稳定性减少响应速度)
Y(n) = q * X(n) + (1-q) * Y(n-1)
Y(n)是本次滤波输出值
Y(n-1)是上次滤波输出值
X(n)本次采样值
q:滤波系数(0~1)
在这里插入图片描述
q越大,响应越快,但曲线不平滑
q越小,曲线越平滑,但响应越慢

(五)自动控制系统

1.自动控制系统简介

自动控制系统:用自动控制装置,对关键参数进行自动控制,使它在受到外界干扰而偏离正常状态时,能够被自动地调节回到目标范围内。

2.开环控制系统

开环控制:输出只受系统输入控制,没有反馈回路,控制精度和抗干扰能力差
例如:电风扇风力控制系统:
目标风力->控制电路->电机->扇叶转速->风力

3.闭环控制系统

闭环控制:引入反馈回路,利用输出和输入值的偏差对系统进行控制,避免偏离预定目标
大棚温控系统:
在这里插入图片描述

4.电机速度闭环控制

直流有刷电机速度闭环控制系统:
在这里插入图片描述

(六)PID

1.PID算法简介

PID 是 Proportional(比例)、Integral(积分)、Differential(微分)的首字母缩写,它是一种结合比例、积分和微分三个环节于一体的闭环控制算法。
在这里插入图片描述
本质:根据输入的偏差值,按照比例、积分、微分的函数关系进行运算,运算结果用以控制输出。

2.PID各环节作用

(1)比例环节

成比例地反应控制系统的偏差信号,即输出𝑢与输入偏差𝑒成正比,可以用来减小系统的偏差。
𝑢 = 𝐾_p∙𝑒
𝑢:输出
𝑒:偏差值
𝐾_p :比例系数
在这里插入图片描述
1 、𝐾_p越大,系统响应越快,越快达到目标值。
2 、𝐾_p过大会使系统产生较大的超调和振荡,导致系统的稳定性变差。
3 、仅有比例环节无法消除静态误差。
静态误差
系统控制过程趋于稳定时,目标值与实测值之间的偏差。
产生静差原因:
输出𝑢被外部影响抵消
消除静差方法:引入积分环节

(2)积分环节

对输入偏差𝑒进行积分,只要存在偏差,积分环节就会不断起作用,主要用于消除静态误差。
𝑢 = 𝐾_p ∙ 𝑒 + 𝐾_𝑖 ∙ ∑𝑒
𝑢 :输出
𝑒 :偏差值
𝐾_p :比例系数
𝐾_𝑖 :积分系数
在这里插入图片描述
1、 𝐾_𝑖越大,消除静态误差的时间越短,越快达到目标值。
2、 𝐾_𝑖过大会使系统产生较大的超调和振荡,导致系统的稳定性变差。
3、对于惯性较大的系统,积分环节动态响应较差,容易产生超调、振荡。

(3)微分环节

反应偏差量的变化趋势,根据偏差的变化量提前作出相应控制,减小超调,克服振荡。
𝑢_𝑘= 𝐾_p ∙ 𝑒_𝑘 + 𝐾_𝑖 ∙ ∑_(𝑗=0)^𝑘𝑒_𝑗 + 𝐾_𝑑 ∙ ( 𝑒_𝑘–𝑒_(𝑘−1))
𝑢_𝑘 :第k次输出
𝑒_𝑘 :第k次偏差值
𝑒_(𝑘−1) :第k-1次偏差值
𝐾_p :比例系数
𝐾_𝑖 :积分系数
𝐾_𝑑 :微分系数
1、𝐾_d或者变化趋势越大,微分环节作用越强,对超调和振荡的抑制越强。
2、 𝐾_d过大会引起系统的不稳定,容易引入高频噪声。

3.PID离散公式

位置式PID公式:
𝑢_𝑘= 𝐾_p ∙ 𝑒_𝑘 + 𝐾_𝑖 ∙ ∑_(𝑗=0)^𝑘𝑒_j + 𝐾_𝑑 ∙( 𝑒_𝑘–𝑒_(𝑘−1))
1、 𝑢_𝑘直接对应对象的输出(位置),如果计算出现异常,对系统影响很大。
2、全量计算,要对偏差 𝑒 进行累加,计算量大。
3、在不带积分部件的对象中可以得到很好效果,例如电液伺服阀、温控设备等。
增量式PID公式推导:

  • 𝑢_𝑘= 𝐾_p ∙ 𝑒_𝑘 + 𝐾_𝑖 ∙ ∑_(𝑗=0)^𝑘𝑒_j + 𝐾_𝑑 ∙( 𝑒_𝑘–𝑒_(𝑘−1))
    第一步:把 𝒌 = 𝒌 −𝟏 代入公式①中,得:
  • 𝑢_(𝑘−1)= 𝐾_p ∙ 𝑒_(𝑘−1) + 𝐾_𝑖 ∙ ∑_(𝑗=0)^(𝑘−1)𝑒_𝑗 + 𝐾_𝑑 ∙( 𝑒_(𝑘−1)–𝑒_(𝑘−2))
    第二步:把公式① − 公式②,得:
    ∆𝑢_𝑘 = 𝐾_p ∙ (𝑒_𝑘 – 𝑒_(𝑘−1))+ 𝐾_𝑖 ∙ 𝑒_𝑘+ 𝐾_𝑑 ∙(𝑒_𝑘–2∙𝑒_(𝑘−1) + 𝑒_(𝑘−2))
    增量式PID公式
    1、增量式PID计算的是相对上一次输出的增量,即𝑢_𝑘= 𝑢_(𝑘−1)+ ∆𝑢_𝑘 。
    2、增量只与近3次的偏差有关,计算出现异常对系统工作影响较小。
    3、计算量少,实时性相对较好。
    PID离散公式对比
    位置式
    1、全量计算,计算量大
    2、计算出现异常,对系统影响大
    3、适用于不带积分部件的对象
    增量式
    1、增量计算,计算量少
    2、计算出现异常,对系统影响较小
    3、适用于本身带积分部件的对象
  • 18
    点赞
  • 164
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
非常感谢您的提问,以下是一个简单的用C语言编写的STM32驱动伺服电机的程序: ``` #include "stm32f10x.h" void TIM_Configuration(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 20000 - 1; TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 1500; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); } int main(void) { TIM_Configuration(); while (1) { } } ``` 这个程序使用了STM32的定时器2和GPIOA的第0个引脚来控制伺服电机。在程序中,我们首先初始化了定时器2和GPIOA的第0个引脚,然后设置了定时器2的PWM输出模式和输出占空比,最后启动了定时器2。在主循环中,我们什么也没有做,因为我们只需要让定时器2一直运行就可以了。 希望这个程序能够帮助到您!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值