556|1

41

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

【极海APM32M3514电机通用评估板】模拟I2C读取AS5600 [复制链接]

 

1. 背景

相较于无感FOC,有感FOC可以直接读取电机当前角度,而无需位置观测器。不仅实现容易,而且减少计算量。

评估版1.0的传感器接口只有霍尔编码器,而市面上很多编码器是I2C接口,或者SPI接口,无法通用。我手上有的电机编码器是AS5600型号,而开发板着实没有预留gpio口供使用,因此产生了将霍尔传感器接口改成普通的GPIO口的想法,使用模拟I2C驱动并读取电机旋转角度。

 

2.硬件电路改动

使用HA、HB分别作为SDA和SCL,将R7和R8两个电阻替换成0欧姆电阻即可。

 

3.AS5600编码器

◆AS5600与两极磁铁配对,可以输出12位分辨率的磁性旋转位置,支持IIC通信,还可以输出模拟电压和PWM信号。这里我是用I2C读取方式,分别对0x0C和0x0D两个寄存器进行读取。

在实物上,需要在电机旋转轴上吸附一个磁铁(旋转轴自带径向磁铁更好),as5600编码器放在磁铁正下方。

 

4.代码编写。这里创建两个源文件,分别对应了模拟I2C驱动和AS5600驱动

  • #include "dev_include.h"
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_Delay
  • * 功能说明: I2C总线位延迟,最快400KHz
  • * 形 参:无
  • * 返 回 值: 无
  • *********************************************************************************************************
  • */
  • static void i2c_Delay(void)
  • {
  • uint8_t i;
  • /* 
  • 可用逻辑分析仪测量I2C通讯时的频率
  • 工作条件:CPU主频168MHz ,MDK编译环境,1级优化
  • 经测试,循环次数为20~250时都能通讯正常
  • */
  • for (i = 0; i < 40; i++);
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_Start
  • * 功能说明: CPU发起I2C总线启动信号
  • * 形 参:无
  • * 返 回 值: 无
  • *********************************************************************************************************
  • */
  • void i2c_Start(void)
  • {
  • /* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
  • DEVICE_I2C_SDA_1();
  • DEVICE_I2C_SCL_1();
  • i2c_Delay();
  • DEVICE_I2C_SDA_0();
  • i2c_Delay();
  • DEVICE_I2C_SCL_0();
  • i2c_Delay();
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_Start
  • * 功能说明: CPU发起I2C总线停止信号
  • * 形 参:无
  • * 返 回 值: 无
  • *********************************************************************************************************
  • */
  • void i2c_Stop(void)
  • {
  • /* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
  • DEVICE_I2C_SDA_0();
  • DEVICE_I2C_SCL_1();
  • i2c_Delay();
  • DEVICE_I2C_SDA_1();
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_SendByte
  • * 功能说明: CPU向I2C总线设备发送8bit数据
  • * 形 参:_ucByte : 等待发送的字节
  • * 返 回 值: 无
  • *********************************************************************************************************
  • */
  • void i2c_SendByte(uint8_t _ucByte)
  • {
  • uint8_t i;
  • /* 先发送字节的高位bit7 */
  • for (i = 0; i < 8; i++)
  • {
  • if (_ucByte & 0x80)
  • {
  • DEVICE_I2C_SDA_1();
  • }
  • else
  • {
  • DEVICE_I2C_SDA_0();
  • }
  • i2c_Delay();
  • DEVICE_I2C_SCL_1();
  • i2c_Delay();
  • DEVICE_I2C_SCL_0();
  • if (i == 7)
  • {
  • DEVICE_I2C_SDA_1(); // 释放总线
  • }
  • _ucByte <<= 1; /* 左移一个bit */
  • i2c_Delay();
  • }
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: ee_WriteBytes
  • * 功能说明: 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
  • * 形 参:_usAddress : 起始地址
  • * _usSize : 数据长度,单位为字节
  • * _pWriteBuf : 存放读到的数据的缓冲区指针
  • * 返 回 值: 0 表示失败,1表示成功
  • *********************************************************************************************************
  • */
  • uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
  • {
  • return 0;
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_ReadByte
  • * 功能说明: CPU从I2C总线设备读取8bit数据
  • * 形 参:无
  • * 返 回 值: 读到的数据
  • *********************************************************************************************************
  • */
  • uint8_t i2c_ReadByte(unsigned char ack)
  • {
  • uint8_t i;
  • uint8_t value;
  • /* 读到第1个bit为数据的bit7 */
  • value = 0;
  • for (i = 0; i < 8; i++)
  • {
  • value <<= 1;
  • DEVICE_I2C_SCL_1();
  • i2c_Delay();
  • if (DEVICE_I2C_SDA_READ())
  • {
  • value++;
  • }
  • DEVICE_I2C_SCL_0();
  • i2c_Delay();
  • }
  • if (!ack)
  • i2c_NAck();//·¢?ínACK
  • else
  • i2c_Ack(); //·¢?íACK
  • return value;
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_WaitAck
  • * 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
  • * 形 参:无
  • * 返 回 值: 返回0表示正确应答,1表示无器件响应
  • *********************************************************************************************************
  • */
  • uint8_t i2c_WaitAck(void)
  • {
  • uint8_t re;
  • DEVICE_I2C_SDA_1(); /* CPU释放SDA总线 */
  • i2c_Delay();
  • DEVICE_I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
  • i2c_Delay();
  • if (DEVICE_I2C_SDA_READ()) /* CPU读取SDA口线状态 */
  • {
  • re = 1;
  • }
  • else
  • {
  • re = 0;
  • }
  • DEVICE_I2C_SCL_0();
  • i2c_Delay();
  • return re;
  • }
  • //这两个函数没有使用过
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_Ack
  • * 功能说明: CPU产生一个ACK信号
  • * 形 参:无
  • * 返 回 值: 无
  • *********************************************************************************************************
  • */
  • void i2c_Ack(void)
  • {
  • DEVICE_I2C_SDA_0(); /* CPU驱动SDA = 0 */
  • i2c_Delay();
  • DEVICE_I2C_SCL_1(); /* CPU产生1个时钟 */
  • i2c_Delay();
  • DEVICE_I2C_SCL_0();
  • i2c_Delay();
  • DEVICE_I2C_SDA_1(); /* CPU释放SDA总线 */
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_NAck
  • * 功能说明: CPU产生1个NACK信号
  • * 形 参:无
  • * 返 回 值: 无
  • *********************************************************************************************************
  • */
  • void i2c_NAck(void)
  • {
  • DEVICE_I2C_SDA_1(); /* CPU驱动SDA = 1 */
  • i2c_Delay();
  • DEVICE_I2C_SCL_1(); /* CPU产生1个时钟 */
  • i2c_Delay();
  • DEVICE_I2C_SCL_0();
  • i2c_Delay();
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_CfgGpio
  • * 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
  • * 形 参:无
  • * 返 回 值: 无
  • *********************************************************************************************************
  • */
  • void i2c_CfgGpio(void)
  • {
  • GPIO_Config_T GPIO_InitStructure;
  • RCM_EnableAHBPeriphClock(DEVICE_I2C_GPIO_CLK); /* 打开GPIO时钟 */
  • GPIO_InitStructure.pin = DEVICE_I2C_SCL_PIN | DEVICE_I2C_SDA_PIN;
  • GPIO_InitStructure.speed = GPIO_SPEED_50MHz;
  • GPIO_InitStructure.mode = GPIO_MODE_OUT;
  • GPIO_InitStructure.outtype = GPIO_OUT_TYPE_OD; /* 开漏输出 */
  • GPIO_InitStructure.pupd = GPIO_PUPD_NO; //无上拉
  • GPIO_Config(DEVICE_I2C_GPIO_PORT, &GPIO_InitStructure);
  • i2c_Stop();
  • }
  • /*
  • *********************************************************************************************************
  • * 函 数 名: i2c_CheckDevice
  • * 功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
  • * 形 参:_Address:设备的I2C总线地址
  • * 返 回 值: 返回值 0 表示正确, 返回1表示未探测到
  • *********************************************************************************************************
  • */
  • uint8_t i2c_CheckDevice(uint8_t _Address)
  • {
  • uint8_t ucAck;
  • i2c_CfgGpio(); /* 配置GPIO */
  • i2c_Start(); /* 发送启动信号 */
  • /* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
  • i2c_SendByte(_Address | DEVICE_I2C_WR);
  • ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
  • i2c_Stop(); /* 发送停止信号 */
  • return ucAck;
  • }
  • #ifndef _BSP_I2C_GPIO_H
  • #define _BSP_I2C_GPIO_H
  • #include "apm32m35xx.h"
  • typedef uint8_t u8;
  • typedef uint16_t u16;
  • #define DEVICE_I2C_WR 0 /* 写控制bit */
  • #define DEVICE_I2C_RD 1 /* 读控制bit */
  • /* 定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
  • #define DEVICE_I2C_GPIO_PORT GPIOA /* GPIO端口 */
  • #define DEVICE_I2C_GPIO_CLK RCM_AHB_PERIPH_GPIOA /* GPIO端口时钟 */
  • #define DEVICE_I2C_SCL_PIN GPIO_PIN_1 /* 连接到SCL时钟线到编码器HB */
  • #define DEVICE_I2C_SDA_PIN GPIO_PIN_3 /* 连接到SDA数据线到编码器HA */
  • /* 定义读写SCL和SDA的宏 */
  • #define DEVICE_I2C_SCL_1() GPIO_SetBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SCL_PIN) /* SCL = 1 */
  • #define DEVICE_I2C_SCL_0() GPIO_ClearBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SCL_PIN) /* SCL = 0 */
  • #define DEVICE_I2C_SDA_1() GPIO_SetBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SDA_PIN) /* SDA = 1 */
  • #define DEVICE_I2C_SDA_0() GPIO_ClearBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SDA_PIN) /* SDA = 0 */
  • #define DEVICE_I2C_SDA_READ() GPIO_ReadInputBit(DEVICE_I2C_GPIO_PORT, DEVICE_I2C_SDA_PIN) /* 读SDA口线状态 */
  • void i2c_Start(void);
  • void i2c_Stop(void);
  • void i2c_SendByte(uint8_t _ucByte);
  • uint8_t i2c_WaitAck(void);
  • uint8_t i2c_ReadByte(unsigned char);
  • void i2c_Ack(void);
  • void i2c_NAck(void);
  • void i2c_CfgGpio(void);
  • uint8_t i2c_CheckDevice(uint8_t _Address);
  • uint32_t I2C_ByteWrite(u8* pBuffer, u8 WriteAddr);//发送一字节
  • uint32_t I2C_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead);
  • #endif
  • #include "dev_include.h"
  • AS5600_ENCODER_Def as5600_encoder;
  • int RAW_Angle_Hi = 0x0c;
  • int RAW_Angle_Lo = 0x0d;
  • // 旋转的总圈数*2pi
  • float full_rotation_offset=0; // full rotation tracking;
  • long angle_data_prev=0; //上一时刻的角度(读出来的,两字节)
  • float angle_prev; //换算出的角度
  • unsigned long velocity_calc_timestamp; //速度计算的时刻
  • long cpr; //量程
  • // 返 回 值: 返回1表示错误
  • u8 AS5600_ReadOneByte(u16 ReadAddr)
  • {
  • u8 temp=1;
  • i2c_Start();
  • i2c_SendByte((AS5600_ADDRESS<<1)|0x00); //·¢ËÍдÃüÁî
  • i2c_WaitAck();
  • i2c_SendByte(ReadAddr); //·¢Ë͵ØÖ·
  • i2c_WaitAck();
  • i2c_Start();
  • i2c_SendByte((AS5600_ADDRESS<<1)|0x01); //½øÈë½ÓÊÕģʽ
  • i2c_WaitAck();
  • temp=i2c_ReadByte(0);
  • i2c_Stop();
  • return temp;
  • }
  • //写一字节 AS5600¸这个函数没咋用
  • void AS5600_WriteOneByte(u16 WriteAddr,u8 WriteData)
  • {
  • i2c_Start();
  • i2c_SendByte((0X36<<1)|0x00); //·¢ËÍдÃüÁî
  • i2c_WaitAck();
  • i2c_SendByte(WriteAddr); //·¢Ë͵ØÖ·
  • i2c_WaitAck();
  • i2c_Start();
  • i2c_SendByte(WriteData); //·¢ËÍÊý¾Ý
  • i2c_WaitAck();
  • i2c_Stop();//²úÉúÒ»¸öÍ£Ö¹Ìõ¼þ
  • //Delay_ms(10); 我没有实现这个 会有影响吗?
  • }
  • //readTwoBytes(int in_adr_hi, int in_adr_lo)这段代码是一个函数,其目的是从I2C设备(在代码中的变量名为AS5600_Address)中读取两个字节数据,并将其合并成一个16位的无符号整数返回。
  • //具体来说,函数接受两个整型参数in_adr_hi和in_adr_lo,它们用于指定需要读取的两个字节数据的地址。函数中首先通过Wire库开始I2C传输,向设备写入in_adr_lo和in_adr_hi分别作为数据地址,然后读取相应的字节数据。
  • //在每个Wire.requestFrom()调用之后,通过一个while循环等待数据接收完毕。然后读取接收到的低字节和高字节,并使用位运算将它们合并成一个16位的无符号整数。
  • //最后,返回合并后的整数。如果读取过程中出现错误或者函数没有成功读取到数据,则函数返回-1。
  • unsigned short readTwoBytes(int in_adr_hi, int in_adr_lo)
  • {
  • unsigned short retVal = 0;
  • u8 low = AS5600_ReadOneByte(in_adr_lo);
  • u8 high = AS5600_ReadOneByte(in_adr_hi);
  • retVal = (high << 8) | low;
  • return retVal;
  • }
  • unsigned short getRawAngle()
  • {
  • return readTwoBytes(RAW_Angle_Hi, RAW_Angle_Lo);
  • }
  • //初始化 磁编码器
  • void MagneticSensor_Init()
  • {
  • i2c_CfgGpio();
  • cpr=AS5600_CPR;
  • angle_data_prev = getRawAngle();
  • full_rotation_offset = 0;
  • velocity_calc_timestamp=0;
  • }
  • float getAngle()
  • {
  • long val = getRawAngle();
  • return ( val / (float)cpr) * _2PI;
  • }
  • float getFullAngle()
  • {
  • long val = getRawAngle();
  • long d_angle = val - angle_data_prev;
  • //计算旋转的总圈数
  • //通过判断角度变化是否大于80%的一圈 来判断是否发生了溢出,
  • //如果发生了,则将full_rotation_offset增加1(如果d_angle小于0)或减少1(如果d_angle大于0)。
  • if(abs(d_angle) > (0.8*cpr) ) full_rotation_offset += ( d_angle > 0 ) ? -_2PI : _2PI;
  • angle_data_prev = val;
  • return (full_rotation_offset + ( val / (float)cpr) * _2PI);
  • }
  • //获取编码器的机械角度,真实的物理角度
  • //获取电角度角度,FOC电角度
  • void Get_Encoder_Angles(void)
  • {
  • as5600_encoder.angle = getAngle() * (180.0 / PI);
  • as5600_encoder.angle_rad = getAngle();
  • // as5600_encoder.angle_rad_offset = 6.045f; //手动找到的零点,后续通过函数调用获得
  • //as5600_encoder.angle_rad_offset = 0.91426f; //手动找到的零点,后续通过函数调用获得
  • if(as5600_encoder.angle_rad>=as5600_encoder.angle_rad_offset) //减去零点偏置
  • {
  • as5600_encoder.angle_rad = as5600_encoder.angle_rad - as5600_encoder.angle_rad_offset;
  • }
  • else
  • {
  • as5600_encoder.angle_rad = 2*PI - as5600_encoder.angle_rad_offset + as5600_encoder.angle_rad;
  • }
  • as5600_encoder.electronic_angle = as5600_encoder.angle_rad*MOTOR_POLE;
  • }
  • //轴速度计算
  • // Shaft velocity calculation
  • float getVelocity()
  • {
  • unsigned long now_us;
  • float Ts, angle_c, vel;
  • // calculate sample time
  • now_us = SysTick->VAL; //_micros();
  • if(now_us<velocity_calc_timestamp)Ts = (float)(velocity_calc_timestamp - now_us)/9*1e-6f;
  • else
  • Ts = (float)(0xFFFFFF - now_us + velocity_calc_timestamp)/9*1e-6f;
  • // quick fix for strange cases (micros overflow)
  • if(Ts == 0.0f || Ts > 0.5f) Ts = 1e-3f;
  • // current angle
  • angle_c = getFullAngle();
  • // velocity calculation
  • vel = (angle_c - angle_prev)/Ts;
  • // save variables for future pass
  • angle_prev = angle_c;
  • velocity_calc_timestamp = now_us;
  • return vel;
  • }
  • #ifndef __AS5600_H
  • #define __AS5600_H
  • #include "stm32f4xx.h"
  • #include "user_config.h"
  • #include "./i2c/software_i2c/bsp_i2c_gpio.h"
  • //#include <math.h> //求浮点数的绝对值
  • #include <stdlib.h>
  • #define AS5600_CPR 4096 // as5600的满量程
  • //0x36是地址,但是要左移一位,因为最后一位是读写位,1是读,0是写
  • #define AS5600_ADDRESS 0x36
  • typedef struct
  • {
  • float angle; //0~360
  • float angle_rad; //弧度制
  • float electronic_angle;
  • float angle_rad_offset;
  • } AS5600_ENCODER_Def;
  • extern AS5600_ENCODER_Def as5600_encoder;
  • float getAngle(void); //返回弧度值 0-2Pi
  • float getFullAngle(void);//总弧度
  • float getVelocity(void);
  • void MagneticSensor_Init(void);
  • void Get_Encoder_Angles(void);
  • #define _2PI 6.28318530718f
  • #define PI 3.14159265f
  • #endif

5. 代码讲解

初始化定义。这里使用了编码器的HA和HB两个引脚,注意HA在硬件电路上是PA3引脚。我一开始配置时直接copy例程里的PA0引脚,调试很久才发现问题。

 

 

GPIO口初始化配置。配置成无上拉的开漏输出。

 

读一字节函数。 输入参数为寄存器地址,返回参数为对应的值。

 

读AS5600的编码器结果函数。分别传入高和低寄存器地址,对内容进行读取,并返回。返回结果是0-4096对应0-360°

 

6.主函数编写,当读取的角度大于180°时,led灯点亮。反之熄灭

  • IO_Init();
  • MagneticSensor_Init();
  • while(1){
  • // 将弧度转换为度
  • ang = getAngle()* (180.0 / 3.14158f);
  • if(ang > 180)
  • LED_Fault_ON();
  • else
  • LED_Fault_OFF();
  • for(i=0;i<1000;i++);
  • }

7. 效果展示

鎾斁鍣ㄥ姞杞藉け璐�: 鏈娴嬪埌Flash Player锛岃鍒�瀹夎
5f79114885356724acad327fd9064c55

 

查看本帖全部内容,请登录或者注册

最新回复

nmg
我先看的你视频,还想着,咋还手拨呢   详情 回复 发表于 2024-12-10 16:44
点赞 关注
 
 

回复
举报

5269

帖子

236

TA的资源

管理员

沙发
 

我先看的你视频,还想着,咋还手拨呢

加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条
Microchip 直播|利用motorBench开发套件高效开发电机磁场定向控制方案 报名中!
直播主题:利用motorBench开发套件高效开发电机磁场定向控制方案
直播时间:2025年3月25日(星期二)上午10:30-11:30
快来报名!

查看 »

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表