STM32_iC-MHM磁编码器使用
时间:2022-11-19 20:00:00
直接看代码到最后第六章,前面是流水账笔记
1、前言
讲道理,iC-MHM这是一个模型,我误以为硬件组的同事很懒,因为这个模型没有任何数字,看起来不像芯片模型。 结果我用这个型号搜索,真的搜索到了。iC-Haus Homepage - product: iC-MHM (ichaus.de)
拿到老规矩datasheet,先看功能介绍,特点等。然后拉通
根据原理图, STM32主芯片与iC-MHM芯片通信采用 SPI因此,主要关注通信接口SPI接口。
看这样的目录挺吓人的。其实还好。让我们先看看。SPI接口相关内容
和iC-MHM用SPI其他人写得很清楚通信操作的规则。
前黄色OP7 - OP0,就是OPcode意思,操作码。蓝色背面ADR7 - ADR0.我不知道这个缩写是什么,但是上表 下图显然意味着你(STM32主芯片 Master)不仅要先发一个操作码,还要再发一个 数据码(我暂时称之为数据码)。
正如前一篇文章所说,每个需要配置的寄存器都必须不可避免地再次阅读和配置。现在,事实上,我们必须阅读每个操作代码。
2、iC-MHM SPI通信操作码 详解
以下是对每个操作码的总结简要描述
0xB0 Activate,激活
那这两个位 (RACTIVE和PACTIVE)数据字节在哪里? 也给出了数据手册,如下图所示
我暂时可以理解, 我可以这样做
我发送 0xB0 0x先关闭激活两个字节。
我再发送0xB0 0x打开激活两个字节。 这样呢,能够实现一次类似于软复位的功能。
或者不操作, 默认情况活状态
或直接发送0xB0 0x03字节,我管你默认是什么状态,反正我激活一次。
另一个值得注意的地方
大意是:这两个位置,不能读,只能写。
0xA6 Position Read 位置读取命令
最短3个字节,最常见的7个字节。 这个多圈意味着它会帮你记录你转了多少圈,但是对于我们使用的场景,我们不会转一圈以上, 所以我们用singleturn Position数据应该是可以的。但既然都看了,就可以了解了。
上面是多圈, 以下是单圈配置
单圈位置(角度)总是用两个字节传输。 如果由参数 RESO_ST 插值器的分辨率小于 16 位置,单圈位置值在单圈位置字段中左对齐,未使用 LSB 设置为零。
例如,如果 RESO_ST= 6.单圈位置(角度)为 16 位字段中的 10 位值。 在这种情况下,位 15:6 包括单圈位置和位置 5:0 为零。 有关 RESO_ST 更多信息请参考第一条 21 页的 INTERPOLATOR。
一开始就看到这个编码器的分辨率是14位。 所以它Note里面不推荐 > 14。 然后>12需要使用到
AVGFILT,是平均滤波器。 AVGFILT不能为0。
啥意思? 鱼和熊掌不能兼得, 不可能分辨率高,反应快。 我觉得拿个中间值吧。RESO_ST 设置为 2,即14位, AVGFILT设置为1,1.2us延迟。 如果没有,则设置为2,2.3us延迟,其实是可以接受的。
刚刚发散,现在收回,继续我们Position Read,解释这个命令。 分析了多圈圈数和单圈角度,还有一个error位 warning位
说白了,如果这两个位置是0,说明这个芯片有问题, 假如都是1,那就没问题了。 暂时不跳30页和21页, 感觉指针满天飞
Register Access
不推荐用 Register Read(Single)和Register Write(Single)。 目前,我认为我们应该关注它 0x70后地址。
0x8A Register Read (Continuous) 寄存器读取(连续)
第一段:寄存器读取(连续)命令 (0x8A) 从指定地址开始的一个或多个 RAM 连续块读取地址的数据。
第二段:主机在 MOSI 发送读取寄存器操作码 (0x8A),跟随要读取的地址块 (ADR) 起始地址。 iC-MHM 立即输出 MISO 操作的操作码和地址,然后是地址 ADR (DATA1) 寄存器中的数据。只要 NCS 从下一个寄存器(地址)保持有效(低) ADR 1)数据将被输出(DATA2)。 只要 NCS 保持低电平,后续寄存器的数据将继续输出。
第三段:简而言之,第一个命令讲的,要激活“寄存器通道”,否则会报错。
0xCF Register Write(Continuous) 寄存器写入(连续)
和连续读取一样, 只要你的电影选择没有被提升,你就可以根据你指定的地址将字节连续写入寄存器。
0x9C Read Status 读取状态寄存器 指令
这个指令跟 【寄存器读取(连续)】指令非常相似,只要你的电影选线没有降低,你就会一直阅读。 您不需要指定状态寄存器的地址。如果你直接使用这个命令,他就会知道你在读状态寄存器。 但他最多应该读4个地址的数据,0x70-0x73。 另外,最后一段还是说要激活寄存器操作的通道。
0xD9 Write Instruction 写入命令寄存器 指令
这个指令跟 【寄存器写入(连续)】指令非常相似,只要你的选线没有拉低,就会一直写。 您不需要指定命令寄存器的地址。如果你直接使用这个命令,他就会知道你在写命令寄存器。 地址是从0x74开始的, 同样,最后一段还是说要激活寄存器操作的通道。 注意:你不需要写0的起始地址x74哦,你发送的第一个数据是0x74数据,第二个数据是0x75的数据。这很关键。
0x97 Register Read(Single) 寄存器读取(单次)
意思是第一个字节发送 0x97,第二个字节发送 指定地址,第三个字节发送0x00,
接收第一个字节 0x97 第二个字节接收 指定地址,第三个字节接收您想要的相应地址的数据。
看一下MAP吧,
0xD2 Register Write(Single) 寄存器写入(单次)
意思是第一个字节发送 0xD二、发送第二个字节 指定地址,第三个字节发送您想要写入地址的数据,
接收第一个字节 0xD2 第二个字节接收 指定地址,第三个字节接收您刚刚写入的数据。
0xAD Read Register Status/Data 读取寄存器状态/数据
这个指令有点迷人。
其实大概意思是: 你在最后一个操作 例如,寄存器的单读或单写命令 如果你跟着这个命令,你就会得到上一个命令执行的状态, 比如你的操作码错了, 你操作的地址错了啊,这些都可以给你爆出来。 它返回的第二个字节。 第三个字节是你读或写的字节,完全一样。 主要作用还是用于判断上一个命令执行状态。
3.如何写代码?
如何编写代码,或者根据第二步整理的操作码,整理
0xB0 Activate:
SPI 发送0xB0 0x03或0x83两个字节,我管你默认是什么状态,反正我激活了两个通道。
0CF Register Write(Continuous):
CS 拉低
SPI发送 0xCF + 0x00 + 字节1 + 字节2,四个字节
SPI接收 0xCF + 0x00 + 字节1 + 字节2(无用的)
CS拉高
字节1 对应RAM 地址是 0x00
字节2 对应RAM 地址是0x01
根据第二章的总结理解,总结关注三个配置点
RESO_MT = 0 多圈就算了,因为我们不转圈。
RESO_ST = 2 单圈位置14位 配套的 AVGFILT设置成1(1.2us延迟) 或者 设置成2(2.3us延迟)
地址:0x01的数据配置成什么已经可以确定了。
0 010 0 000 即 0x20
2 0
地址: 0x00的还不确定,应该还有HYS DIR TLF这些东西我都不知道是什么? 需要去看看。
HYS 磁滞(磁铁旋转角度), 这个东西到底是啥意思,我还在真不懂,先设置成0吧,// TODO
DIR是方向意思, 0是逆时针,1是顺时针。 这个也设置成0吧,这个问题不大。
我们的AVGFILT ≠ 0,所以TLF 得≥ 3。 那就设置成3吧。 暂时不纠这么细了。
总结一下 HYS 0 DIR 0 TLF 3 AVGFILT 2
1 0 0 0 1 1 1 0 即 0x8E
8 E
也就是说地址: 0x00的字节是0x8E
最终总结
CS 拉低
SPI发送 0xCF + 0x00 + 字节1 + 字节2,四个字节
SPI接收 0xCF + 0x00 + 字节1 + 字节2(无用的)
CS拉高
字节1 对应RAM 地址是 0x00 :0x8E
字节2 对应RAM 地址是0x01 :0x20
0xA6 Position Read
SPI直接发送0xA6 + 0x00 + 0x00 + 0x00 四个字节
SPI接收到 0xA6 + Multiturn Position(0字节) + singleturn Position(2字节) + (Err and warning) 一个字节
先就这三个命令就可以了,其它的暂时先不管。
4、STM32CubeMx开始配置
4.1 NSS Signal Type
SPI需要配置的东西,已经框出来了。 NSS是片选的选项,我们的原理图的是用一个GPIO引脚来做片选的(3线SPI),而不是用指定的那个NSS引脚之间直接连的(4线SPI)。 所以这里应该选择Software,即我们在发送消息的时候,需要自己去把片选线电平拉低。 下面看看其它的配置
4.2 Data Size & First Bit
芯片的Datasheet上讲了,SPI 支持Mode 0和Mode 3, 后面还有两个信息,byte by byte 即 Data Size 选 8Bits。
MSB first,即 First Bit 选 MSB first。
4.3 Clock Polarity(CPOL) & Clock Phase(CPHA)
上面谈到了芯片SPI通信支持Mode 0和Mode 3, 那看看 SPI的4中Mode分别是长什么样的。
也就是说,配置的时候配成: (CPOL = LOW && CPHA = 1 Edge) || (CPOL = High && CPHA = 2Edge)
我暂时选后者。
4.4 Prescaler(for Baud Rate)
另外,只剩时钟频率了,时钟频率这个东西,建议在芯片手册上搜索 System Clock这个关键字
最小 11.5Mhz 典型 14MHz 最大16MHz
所以我最终配置成 4分频, 10.5MBits/s
测试 :基于上面的配置
#define iCMHM_spi_cs_A_Enable HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET)
#define iCMHM_spi_cs_A_Disable HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_SET)
iCMHM_spi_cs_A_Enable;
Tx[0] = 0xB0;
Tx[1] = 0x83;
Rx[0] = 0x00;
Rx[1] = 0x00;
HAL_SPI_TransmitReceive(&hspi2,Tx,Rx,2,100);
iCMHM_spi_cs_A_Disable;
向芯片发送 B0 83,接收到 B0 20。 这个配置是没有问题的。
但是,如果把分频设置2, 21MBits/s , 接收的数据就是错误的。 也就是你的分频器Prescaler只能 >= 4
5、测试配置按照第3章所诉的配置是否能够成功
实验发现,激活 + 只配置00和01地址的方式,无法正常的读取传感器数据,
还是需要从0x00 - 0x13地址,把所有的寄存器都配置一遍。
Two thousand years later!
这件事情告诉我们,一定要把买芯片的时候,最好选那种软件支持做得好的,最好有参考配置例程的。
Two thousand years later!
6、不倔强了,参考前辈的代码,给出最终结果
我只能说这个芯片比那个3轴陀螺+3轴加速度计的芯片还难搞。这件事情告诉我们买东西的时候一定要拿参考程序,正经看datasheet配置出来,我感觉没有十天半个月搞不定,这回拉长开发周期。
摆结果吧,不多说了。
#define icMHM_cs_A_enable HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET)
#define icMHM_cs_A_disable HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET)
#define icMHM_OP_Active 0xB0
#define icMHM_OP_Register_Write_Continuous 0xCF
#define icMHM_OP_Register_Read_Continuous 0x8A
#define icMHM_OP_Write_Instruction 0xD9
#define icMHM_OP_Position_Read 0xA6
void iCMHM_config(void){
unsigned char ActivateOpcodeTx[2] = {icMHM_OP_Active,0x03};
unsigned char WriteContinuousOpcodeTx[2] = {icMHM_OP_Register_Write_Continuous,0x00};
unsigned char ReadContinuousOpcodeTx[2] = {icMHM_OP_Register_Read_Continuous,0x00};
unsigned char WriteInstructionOpcodeTx[4] = {icMHM_OP_Write_Instruction,0x01,0x07,0xC0}; // Reset
unsigned char configTx[20] = {0x8E, 0x20, 0x00, 0x28, 0x80, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C};
unsigned char Rxtemp[24] = {0x00};
int iCRCPoly = 0x11D; // CRC-Polynomial 100011101
uint8_t ucDataStream = 0;
uint8_t ucCRC;
// A
icMHM_cs_A_enable;
HAL_SPI_TransmitReceive(&hspi3,ActivateOpcodeTx,Rxtemp,2,3);
icMHM_cs_A_disable;
HAL_Delay(4);
// Calculate Config-CRC //
ucCRC = 2; // startvalue !!!
for (int iReg = 0 ; iReg<12; iReg ++) {
ucDataStream = configTx[iReg];
for (int i =0; i <=7; i ++) {
if ( (ucCRC & 0x80) != (ucDataStream & 0x80))
ucCRC = (ucCRC << 1 ) ^ iCRCPoly ;
else
ucCRC = (ucCRC << 1 ) ;
ucDataStream = ucDataStream << 1 ;
}
}
configTx[12] = ucCRC;
// Calculate Offset-CRC //
ucCRC = 2; // startvalue !!!
for (int iReg = 13 ; iReg<19; iReg ++) {
ucDataStream = configTx[iReg];
for (int i =0; i <=7; i ++) {
if ( (ucCRC & 0x80) != (ucDataStream & 0x80))
ucCRC = (ucCRC << 1 ) ^ iCRCPoly ;
else
ucCRC = (ucCRC << 1 ) ;
ucDataStream = ucDataStream << 1 ;
}
}
configTx[19] = ucCRC;
icMHM_cs_A_enable;
HAL_SPI_TransmitReceive(&hspi3,WriteContinuousOpcodeTx,Rxtemp,2,3);
HAL_SPI_TransmitReceive(&hspi3,configTx,Rxtemp,20,3);
icMHM_cs_A_disable;
HAL_Delay(4);
icMHM_cs_A_enable;
HAL_SPI_TransmitReceive(&hspi3,ReadContinuousOpcodeTx,Rxtemp,2,3);
HAL_SPI_Receive(&hspi3,Rxtemp,20,10);
icMHM_cs_A_disable;
HAL_Delay(4);
icMHM_cs_A_enable;
HAL_SPI_TransmitReceive(&hspi3,WriteInstructionOpcodeTx,Rxtemp,2,3);
icMHM_cs_A_disable;
HAL_Delay(4);
}
void iCMHM_position_read(void){
unsigned char positionTx[4] = {icMHM_OP_Position_Read,0x00,0x00,0x00};
unsigned char Rxtemp[4] ={0x00};
unsigned short HighByte, LowByte, u14_position;
icMHM_cs_A_enable;
HAL_SPI_TransmitReceive(&hspi3,positionTx,Rxtemp,4,10);
icMHM_cs_A_disable;
LowByte = Rxtemp[2];
HighByte = Rxtemp[1];
u14_position = HighByte << 6 | LowByte >> 2;
fAencoder = (float)u14_position * 0.02197265625f; // 360 / 16384 = 0.02197265625
}