HAL STM32 SPI/ABZ/PWM方式读取MT6816磁编码器数据
- 📚MT6816相关资料(来自商家的相关资料):
资料:https://pan.baidu.com/s/1CAbdLBRi2dmL4D7cFve1XA?pwd=8888
提取码:8888
- 📍驱动代码编写,可信源参考:
https://github.com/simplefoc/Arduino-FOC-drivers/blob/master/src/encoders/mt6816/MT6816.cpp
https://github.com/unlir/XDrive/blob/master/Firmware_APP/Base_Drivers/mt6816.c
📗MT6816所有型号和功能描述差异表
- 从上图型号描述上看,差异在于ABZ模式下,径向磁铁旋转一圈,正交编码(A、B线)输出的脉冲数( ABZ输出分辨率)差异,对于Z线来讲,径向磁铁旋转一圈,都只产生一个脉冲信号,该脉冲信号的脉宽有所不同,读取角度的精度还是一样的。
- 如果选择SPI作为通讯,选择基础款:
MT6816CT-STD-R
就可以了。- 如果ABZ模式下使用,推荐
MT6816CT-ACD/ACE
相比于AKD/AKH
,一圈内,分辨率多了24个脉冲信号,正交编码信号相对变窄了一些。- 具体的其他型号的用途和场景使用,没有研究不做讨论。
🛠输出模式配置说明
MT6816可以输出ABZ、UVW和PWM信号,另外还可以通过4线或3线SPI接口读取14位的绝对角度寄存器。其中ABZ、UVW和SPI接口是互相复用I/O引脚的。SPI接口和ABZ/UVW之间是通过HVPP引脚进行配置的,当HVPP接高电平VDD时,相关I/O管脚切换至SPI模式;当HVPP接地时,芯片相关I/O切换至ABZ或UVW模式。ABZ和UVW模式的切换,由芯片内部相关寄存器控制。4线SPI和3线SPI也是通过芯片内部寄存器进行切换控制的,MT6816出厂默认配置为4线SPI.
- 👉ABZ、UVW和PWM信号读取数据和SPI方式读取数据区别在于芯片的第2引脚,如果接VCC就是SPI方式,如果接GND就是ABZ、UVW和PWM方式读取。
- 🔧ABZ, UVW 和PWM模式参考电路:
🍁MT6816 SPI参考电路
- 🌿SPI方式参考电路:
- ⚡在手册中,没有提到
mosi
上拉,在实际测试过程中,如果外部没有配置上拉或者mcu驱动引脚(mosi)内部没有开启上拉,则读取不到数据,这一点需要注意,这个坑,排查了很久才发现。✨推荐在硬件电路设计时,在mosi引脚(芯片第5引脚)外部添加一个4.7K的上拉电阻。
- 👉对于较老的STM32F103系列,在配置软件上没有该配置选项,需要使用外部上拉电阻。
🧬MT6816 SPI时序
MT6816的SPI使用模式3(CPOL=1, CPHA=1)传输数据。如图-16所示,数据传输开始于CSN的下降沿,结束于CSN的上升沿,MT6816在时钟上升沿采样数据。
- 时序图:
📄4线SPI协议
- 🌿4线SPI协议时序
MT6816的CSN下降沿激活SPI通信,CSN的上升沿结束SPI通信。SCK时钟信号由上位机发送给MT6816,在非通信状态下,请保持SCK为高电平;MOSI (上位机输出、MT6816输入) 和MISO (上位机输入、MT6816输出) 是SPI接口的两路数据信号, 数据都是在时钟信号SCK的下降沿发生改变,所以推荐使用SCK时钟信号的上升沿对数据进行采样.
- 比特 0: 读写标志位,。低电平为写操作,此时数据DI7~DI0写入芯片;高电平为读操作,此时从芯
片读出数据DO7~DO0 。- 比特 1-7: 地址A6~A0。 寄存器操作地址。
- 比特 8-15: 数据 DI7~DI0 (写模式)。会被写入芯片的数据 (MSB优先)。
- 比特 8-15: 数据DO7~DO0 (读模式)。 从芯片读出的数据(MSB优先)。
📑3线SPI协议
- 3线SPI时序
CSN的下降沿激活SPI通信,CSN的上升沿结束SPI通信。SCK时钟信号由上位机发送给MT6816,在非通信状态下,请保持SCK为高电平。SDA是数据输入输出信号数据,是在时钟信号SCK的下降沿发生改变,所以推荐使用SCK时钟信号的上升沿对数据进行采样 。
- Bit 0: 读写标志位,。低电平为写操作,此时数据DI7~DI0写入芯片;高电平为读操作,此时从芯
片读出数据DO7~DO0 。- Bit 1-7: 地址A6~A0。 寄存器操作地址。
- Bit 8-15: 数据 D7~D0 (写模式)。会被写入芯片的数据 (MSB优先)。
- Bit 8-15: 数据D7~D0 (读模式)。 从芯片读出的数据(MSB优先)。
📗SPI读取角度数据(4线SPI)
- 🌿4线SPI读取角度数据
- 📐角度数据寄存器
- 🌿3线SPI读角度时序:
📝SPI/ABZ/PWM驱动代码
- ✨本驱动代码从
https://github.com/unlir/XDrive
项目中,移植有关MT6816内容,作为单独驱动读取MT6816磁编码器数据使用。
- 🌿mt6816.c
/******
************************************************************************
******
** @project : XDrive_Step
** @brief : Stepper motor with multi-function interface and closed loop function.
** @brief : 具有多功能接口和闭环功能的步进电机
** @author : unlir (知不知啊)
** @contacts: QQ.1354077136
******
** @address : https://github.com/unlir/XDrive
******
************************************************************************
******
** {Stepper motor with multi-function interface and closed loop function.}
** Copyright (c) {2020} {unlir(知不知啊)}
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
******
************************************************************************
******/
#include "mt6816.h"
//GPIO输出
#define MT6816_SPI_CS_H() (MT6816_SPI_CS_GPIO_Port -> BSRR = MT6816_SPI_CS_Pin)
#define MT6816_SPI_CS_L() (MT6816_SPI_CS_GPIO_Port -> BRR = MT6816_SPI_CS_Pin)
#if (MT6816_Mode == MT6816_Mode_PWM)
/****************************** MT6816_PWM ******************************/
/****************************** MT6816_PWM ******************************/
/****************************** MT6816_PWM ******************************/
MT6816_PWM_Signal_Typedef mt6816_pwm;
/**
* @brief TIM_MT6816_PWM初始化
* @param NULL
* @retval NULL
**/
void REIN_TIM_MT6816_PWM_Init(void)
{
/* GPIO初始化 */
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*GPIO Ports Clock Enable*/
MT6816_PWM_GPIO_CLK_ENABLE(); //启用PWM端口时钟
/*Configure GPIO pin*/
GPIO_InitStruct.Pin = MT6816_PWM_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP; //禁用上下拉
HAL_GPIO_Init(MT6816_PWM_GPIO_Port, &GPIO_InitStruct);
/* 定时器初始化 */
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
MT6816_PWM_TIM_CLK_ENABLE(); //启用TIM时钟
MT6816_PWM_Get_HTIM.Instance = MT6816_PWM_Get_TIM;
MT6816_PWM_Get_HTIM.Init.Prescaler = (9 - 1); //预分频:9 采样频率 72M/9 = 8M 分辨精度125ns
MT6816_PWM_Get_HTIM.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数
MT6816_PWM_Get_HTIM.Init.Period = (65536 - 1); //采样宽度 65536 * 125ns = 8.192ms 频率122.07Hz (满足MT6816两种PWM频率输出)
MT6816_PWM_Get_HTIM.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; //不分频
MT6816_PWM_Get_HTIM.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; //禁用自动重新加载
if (HAL_TIM_Base_Init(&MT6816_PWM_Get_HTIM) != HAL_OK) {
Error_Handler();
}
if (HAL_TIM_IC_Init(&MT6816_PWM_Get_HTIM) != HAL_OK) {
Error_Handler();
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; //从模式:复位模式
sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; //从模式触发信号:TI1FP1
sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING; //从模式触发极性:上升沿触发
sSlaveConfig.TriggerFilter = 0; //禁用滤波器
if (HAL_TIM_SlaveConfigSynchro(&MT6816_PWM_Get_HTIM, &sSlaveConfig) != HAL_OK) {
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; //主模式:复位
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; //禁用主机模式
if (HAL_TIMEx_MasterConfigSynchronization(&MT6816_PWM_Get_HTIM, &sMasterConfig) != HAL_OK) {
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; //上升沿捕获
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; //TI1FP1
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; //不分频
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&MT6816_PWM_Get_HTIM, &sConfigIC, TIM_CHANNEL_1) != HAL_OK) {
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; //下降沿捕获
sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; //TI1FP2
if (HAL_TIM_IC_ConfigChannel(&MT6816_PWM_Get_HTIM, &sConfigIC, TIM_CHANNEL_2) != HAL_OK) {
Error_Handler();
}
/*interrupt init*/
HAL_NVIC_EnableIRQ(MT6816_PWM_Get_IRQn); //使能定时中断
/*begin work*/
HAL_TIM_Base_Stop(&MT6816_PWM_Get_HTIM); //停用TIM
HAL_TIM_OC_Start_IT(&MT6816_PWM_Get_HTIM, TIM_CHANNEL_1); //启动CH捕获比较中断
HAL_TIM_OC_Start_IT(&MT6816_PWM_Get_HTIM, TIM_CHANNEL_2); //启动CH捕获比较中断
HAL_TIM_Base_Start_IT(&MT6816_PWM_Get_HTIM); //启动TIM中断模式
}
/**
* @brief MT6816_PWM采集初始化
* @param NULL
* @retval NULL
**/
void REIN_MT6816_PWM_Signal_Init(void)
{
//采集数据
mt6816_pwm.h_width = 0;
mt6816_pwm.period = 0;
mt6816_pwm.count_rising = 0;
mt6816_pwm.count_falling = 0;
mt6816_pwm.count_update = 0;
mt6816_pwm.whole_h_flag = false;
mt6816_pwm.whole_l_flag = false;
mt6816_pwm.ready_once = false;
mt6816_pwm.ready_again = false;
//输出数据
mt6816_pwm.valid_width = 0;
//配置外设
REIN_TIM_MT6816_PWM_Init();
}
/**
* @brief MT6816_PWM采集中断回调
* @param NULL
* @retval NULL
**/
void REIN_MT6816_PWM_TIM_Callback(void)
{
//定时器捕获中断通道1 (上升沿中断) (必须在更新中断前执行)
if (__HAL_TIM_GET_FLAG(&MT6816_PWM_Get_HTIM, TIM_FLAG_CC1) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(&MT6816_PWM_Get_HTIM, TIM_IT_CC1) != RESET)
{
__HAL_TIM_CLEAR_IT(&MT6816_PWM_Get_HTIM, TIM_IT_CC1);
MT6816_PWM_Get_HTIM.Channel = HAL_TIM_ACTIVE_CHANNEL_1;
//采集上升沿数据
mt6816_pwm.period = HAL_TIM_ReadCapturedValue(&MT6816_PWM_Get_HTIM, TIM_CHANNEL_1) + 1; //获取PWM周期
mt6816_pwm.count_rising = mt6816_pwm.count_update; //更新计数器镜像
}
}
//定时器捕获中断通道2 (下降沿中断)
if (__HAL_TIM_GET_FLAG(&MT6816_PWM_Get_HTIM, TIM_FLAG_CC2) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(&MT6816_PWM_Get_HTIM, TIM_IT_CC2) != RESET)
{
__HAL_TIM_CLEAR_IT(&MT6816_PWM_Get_HTIM, TIM_IT_CC2);
MT6816_PWM_Get_HTIM.Channel = HAL_TIM_ACTIVE_CHANNEL_2;
//采集下降沿数据
mt6816_pwm.h_width = HAL_TIM_ReadCapturedValue(&MT6816_PWM_Get_HTIM, TIM_CHANNEL_2) + 1; //获取PWM高宽度
mt6816_pwm.count_falling = mt6816_pwm.count_update; //更新计数器镜像
}
}
//定时器更新中断 (更新事件中断) (由更新事件触发 | 溢出事件触发)
if (__HAL_TIM_GET_FLAG(&MT6816_PWM_Get_HTIM, TIM_FLAG_UPDATE) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(&MT6816_PWM_Get_HTIM, TIM_IT_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_IT(&MT6816_PWM_Get_HTIM, TIM_IT_UPDATE);
//单次PWM全高全低检测
if ( (mt6816_pwm.count_update != mt6816_pwm.count_rising) //(上升沿计数器镜像,更新计数器不相等)
&& (mt6816_pwm.count_update != mt6816_pwm.count_falling) //(下降沿计数器镜像,更新计数器不相等)
) {
//读取PWM电平用于判定全高或全低
if (MT6816_PWM_GPIO_Port -> IDR & MT6816_PWM_Pin) {
mt6816_pwm.whole_h_flag = true; //置位100%标志
mt6816_pwm.whole_l_flag = false; //清位0%标志
}
else {
mt6816_pwm.whole_h_flag = false; //清位100%标志
mt6816_pwm.whole_l_flag = true; //置位0%标志
}
}
else {
mt6816_pwm.count_update ++;
mt6816_pwm.whole_h_flag = false; //清位100%标志
mt6816_pwm.whole_l_flag = false; //清位0%标志
}
//单次PWM有效性确认
if (0) {}
else if ((mt6816_pwm.whole_h_flag)) mt6816_pwm.ready_once = false; //100%_PWM无效
else if ((mt6816_pwm.whole_l_flag)) mt6816_pwm.ready_once = false; //0%_PWM无效
//脉冲超长检测(无需检测)
//脉冲超短检测(无需检测)
else mt6816_pwm.ready_once = true;
//可靠的PWM有效性确认
if (mt6816_pwm.ready_once) {
if (mt6816_pwm.ready_again) {
mt6816_pwm.valid_width = mt6816_pwm.h_width;
}
else {
mt6816_pwm.valid_width = 0;
mt6816_pwm.ready_again = true;
}
}
else {
mt6816_pwm.valid_width = 0;
mt6816_pwm.ready_again = false;
}
}
}
}
/**
* @brief MT6816_PWM获取角度数据
* @param NULL
* @retval NULL
**/
void REIN_MT6816_PWM_Get_AngleData(void)
{
//输出数据
int32_t out = (mt6816_pwm.valid_width << 1) - 64;
if (out < 0)
mt6816_pwm.angle = 0;
else if (out > 16383)
mt6816_pwm.angle = 16383;
else
mt6816_pwm.angle = out;
}
#elif (MT6816_Mode == MT6816_Mode_ABZ)
/****************************** MT6816_ABZ ******************************/
/**
* @brief TIM_MT6816_ABZ初始化
* @param NULL
* @retval NULL
**/
void REIN_TIM_MT6816_ABZ_Init(void)
{
/* GPIO初始化 */
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*GPIO Ports Clock Enable*/
MT6816_ABZ_A_GPIO_CLK_ENABLE(); //启用ABZ_A端口时钟
MT6816_ABZ_B_GPIO_CLK_ENABLE(); //启用ABZ_B端口时钟
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = MT6816_ABZ_A_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //输入模式
GPIO_InitStruct.Pull = GPIO_NOPULL; //禁用上下拉
HAL_GPIO_Init(MT6816_ABZ_A_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = MT6816_ABZ_B_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //输入模式
GPIO_InitStruct.Pull = GPIO_NOPULL; //禁用上下拉
HAL_GPIO_Init(MT6816_ABZ_B_GPIO_Port, &GPIO_InitStruct);
/* TIM初始化 */
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
__HAL_RCC_TIM1_CLK_ENABLE();
MT6816_ABZ_Get_HTIM.Instance = MT6816_ABZ_Get_TIM;
MT6816_ABZ_Get_HTIM.Init.Prescaler = 0; //无预分频
MT6816_ABZ_Get_HTIM.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数
MT6816_ABZ_Get_HTIM.Init.Period = (4096 - 1); //12位周期
MT6816_ABZ_Get_HTIM.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; //不分频
MT6816_ABZ_Get_HTIM.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; //禁用自动重新加载
sConfig.EncoderMode = TIM_ENCODERMODE_TI12; //双边沿四倍频采样
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING; //上升沿计数
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; //TI1FP1
sConfig.IC1Prescaler = TIM_ICPSC_DIV1; //不分频
sConfig.IC1Filter = 0; //禁用滤波器
sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING; //下降沿计数
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI; //TF2FP2
sConfig.IC2Prescaler = TIM_ICPSC_DIV1; //不分频
sConfig.IC2Filter = 0; //禁用滤波器
if (HAL_TIM_Encoder_Init(&MT6816_ABZ_Get_HTIM, &sConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; //主机模式:复位
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; //禁用主机模式
if (HAL_TIMEx_MasterConfigSynchronization(&MT6816_ABZ_Get_HTIM, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/*begin work*/
HAL_TIM_Base_Stop(&MT6816_ABZ_Get_HTIM);
HAL_TIM_Encoder_Start(&MT6816_ABZ_Get_HTIM, TIM_CHANNEL_ALL);
}
/****************************** MT6816_ABZ GPIO初始化******************************/
/**
* @brief GPIO初始化(MT6816_ABZ)
* @param NULL
* @retval NULL
*/
void REIN_GPIO_MT6816_ABZ_Init(void)
{
#if (defined SENSOR_HVPP_CLK_ENABLE) || (defined MT6816_ABZ_Z_GPIO_CLK_ENABLE)
GPIO_InitTypeDef GPIO_InitStruct = {0};
#endif
#ifdef SENSOR_HVPP_CLK_ENABLE
/* GPIO Ports Clock Enable */
SENSOR_HVPP_CLK_ENABLE(); //启用MT6816_HVPP端口时钟
/*Configure GPIO pin Output Level*/
SENSOR_HVPP_GPIO_Port -> BRR = SENSOR_HVPP_Pin; //MT6816_HVPP引脚输出低电平,启动PWM+ABZ模式
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = SENSOR_HVPP_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; //禁用上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; //低速
HAL_GPIO_Init(SENSOR_HVPP_GPIO_Port, &GPIO_InitStruct);
#endif
#ifdef MT6816_ABZ_Z_GPIO_CLK_ENABLE
/* GPIO Ports Clock Enable */
MT6816_ABZ_Z_GPIO_CLK_ENABLE(); //启用MT6816_ABZ_Z端口时钟
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = MT6816_ABZ_Z_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; //上升沿触发中断
GPIO_InitStruct.Pull = GPIO_NOPULL; //禁用上下拉
HAL_GPIO_Init(MT6816_ABZ_Z_GPIO_Port, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_EnableIRQ(MT6816_ABZ_Z_EXTI_IRQn); //启用MT6816_Z中断
#endif
}
/****************************** MT6816_ABZ ******************************/
MT6816_ABZ_Signal_Typedef mt6816_abz;
/**
* @brief MT6816_ABZ采集初始化
* @param NULL
* @retval NULL
**/
void REIN_MT6816_ABZ_Signal_Init(void)
{
//采集数据
mt6816_abz.sample_data = 0;
//输出数据
mt6816_abz.angle = 0;
//配置外设
REIN_TIM_MT6816_ABZ_Init();
REIN_GPIO_MT6816_ABZ_Init();
}
/**
* @brief MT6816_ABZ采集Z脉冲中断回调
* @param NULL
* @retval NULL
**/
void REIN_MT6816_ABZ_ZPulse_Callback(void)
{
//清除定时器计数值
__HAL_TIM_SET_COUNTER(&MT6816_ABZ_Get_HTIM, 0);
}
/**
* @brief MT6816_ABZ获取角度数据
* @param NULL
* @retval NULL
**/
void REIN_MT6816_ABZ_Get_AngleData(void)
{
//采集数据
mt6816_abz.sample_data = __HAL_TIM_GET_COUNTER(&MT6816_ABZ_Get_HTIM) & 0x0FFF; //读取定时器计数值
//输出数据
mt6816_abz.angle = ((mt6816_abz.sample_data + 1) << 2) - 1; //获得数值0~16383
}
#elif (MT6816_Mode == MT6816_Mode_SPI)
/****************************** MT6816_SPI ******************************/
/****************************** MT6816_SPI GPIO初始化******************************/
/**
* @brief GPIO初始化(MT6816_SPI)
* @param NULL
* @retval NULL
*/
void REIN_GPIO_MT6816_SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
#ifdef SENSOR_HVPP_CLK_ENABLE
/* GPIO Ports Clock Enable */
SENSOR_HVPP_CLK_ENABLE(); //启用MT6816_HVPP端口时钟
/*Configure GPIO pin Output Level*/
SENSOR_HVPP_GPIO_Port -> BSRR = SENSOR_HVPP_Pin; //MT6816_HVPP引脚输出高电平,启动PWM+SPI模式
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = SENSOR_HVPP_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; //禁用上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; //低速
HAL_GPIO_Init(SENSOR_HVPP_GPIO_Port, &GPIO_InitStruct);
#endif
/* GPIO Ports Clock Enable */
MT6816_SPI_CS_GPIO_CLK_ENABLE(); //启用MT6816_SPI_CS端口时钟
/*Configure GPIO pin Output Level*/
MT6816_SPI_CS_GPIO_Port -> BSRR = MT6816_SPI_CS_Pin; //CS引脚启动输出高电平
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = MT6816_SPI_CS_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; //禁用上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; //低速
HAL_GPIO_Init(MT6816_SPI_CS_GPIO_Port, &GPIO_InitStruct);
}
/****************************** MT6816_SPI 接口初始化******************************/
/**
* @brief SPI初始化(MT6816)
* @param NULL
* @retval NULL
**/
void REIN_SPI_MT6816_SPI_Init(void)
{
/* AFIO初始化 */
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*GPIO Ports Clock Enable*/
MT6816_SPI_CLK_GPIO_CLK_ENABLE(); //启用CLK端口时钟
MT6816_SPI_MOSI_GPIO_CLK_ENABLE(); //启用MOSI端口时钟
MT6816_SPI_MISO_GPIO_CLK_ENABLE(); //启用MISO端口时钟
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = MT6816_SPI_CLK_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; //复用推挽
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(MT6816_SPI_CLK_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = MT6816_SPI_MOSI_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; //复用推挽
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(MT6816_SPI_MOSI_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins*/
GPIO_InitStruct.Pin = MT6816_SPI_MISO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //输入
GPIO_InitStruct.Pull = GPIO_NOPULL; //禁用上下拉
HAL_GPIO_Init(MT6816_SPI_MISO_GPIO_Port, &GPIO_InitStruct);
/*Configure AFIO*/
#ifdef MT6816_SPI_AFIO_REMAP
MT6816_SPI_AFIO_REMAP //启用备用的SPI_AFIO映射
#endif
/* SPI初始化 */
MT6816_SPI_SPI_CLK_ENABLE(); //启用SPI时钟
MT6816_SPI_Get_HSPI.Instance = MT6816_SPI_Get_SPI;
MT6816_SPI_Get_HSPI.Init.Mode = SPI_MODE_MASTER; //主机模式
MT6816_SPI_Get_HSPI.Init.Direction = SPI_DIRECTION_2LINES; //双向传输
MT6816_SPI_Get_HSPI.Init.DataSize = SPI_DATASIZE_16BIT; //数据位宽:16
MT6816_SPI_Get_HSPI.Init.CLKPolarity = SPI_POLARITY_HIGH; //CLK空闲时高电平
MT6816_SPI_Get_HSPI.Init.CLKPhase = SPI_PHASE_2EDGE; //第二边沿采样
MT6816_SPI_Get_HSPI.Init.NSS = SPI_NSS_SOFT; //软件NSS
MT6816_SPI_Get_HSPI.Init.BaudRatePrescaler = MT6816_SPI_Prescaler; //输入预设分频
MT6816_SPI_Get_HSPI.Init.FirstBit = SPI_FIRSTBIT_MSB; //MSB传输
MT6816_SPI_Get_HSPI.Init.TIMode = SPI_TIMODE_DISABLE; //禁用中断模式
MT6816_SPI_Get_HSPI.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; //禁用CRC校验
MT6816_SPI_Get_HSPI.Init.CRCPolynomial = 10; //CRC校验多项式
if (HAL_SPI_Init(&MT6816_SPI_Get_HSPI) != HAL_OK)
{
Error_Handler();
}
}
MT6816_SPI_Signal_Typedef mt6816_spi;
/**
* @brief MT6816_SPI采集初始化
* @param NULL
* @retval NULL
**/
void REIN_MT6816_SPI_Signal_Init(void)
{
//采集数据
mt6816_spi.sample_data = 0;
//输出数据
mt6816_spi.angle = 0;
//配置外设
REIN_GPIO_MT6816_SPI_Init();
REIN_SPI_MT6816_SPI_Init();
}
/**
* @brief MT6816_SPI采集获取角度数据
* @param NULL
* @retval NULL
**/
void RINE_MT6816_SPI_Get_AngleData(void)
{
uint16_t data_t[2];
uint16_t data_r[2];
uint8_t h_count;
data_t[0] = (0x80 | 0x03) << 8;
data_t[1] = (0x80 | 0x04) << 8;
for (uint8_t i = 0; i < 3; i++) {
//读取SPI数据
MT6816_SPI_CS_L();
HAL_SPI_TransmitReceive(&MT6816_SPI_Get_HSPI, (uint8_t*)&data_t[0], (uint8_t*)&data_r[0], 1, HAL_MAX_DELAY);
MT6816_SPI_CS_H();
MT6816_SPI_CS_L();
HAL_SPI_TransmitReceive(&MT6816_SPI_Get_HSPI, (uint8_t*)&data_t[1], (uint8_t*)&data_r[1], 1, HAL_MAX_DELAY);
MT6816_SPI_CS_H();
mt6816_spi.sample_data = ((data_r[0] & 0x00FF) << 8) | (data_r[1] & 0x00FF);
//奇偶校验
h_count = 0;
for (uint8_t j = 0; j < 16; j++) {
if (mt6816_spi.sample_data & (0x0001 << j))
h_count++;
}
if (h_count & 0x01) {
mt6816_spi.pc_flag = false;
}
else {
mt6816_spi.pc_flag = true;
break;
}
}
if (mt6816_spi.pc_flag) {
mt6816_spi.angle = mt6816_spi.sample_data >> 2;
mt6816_spi.no_mag_flag = (bool)(mt6816_spi.sample_data & (0x0001 << 1));
}
}
#endif
/****************************** MT6816 ******************************/
/****************************** MT6816 ******************************/
/****************************** MT6816 ******************************/
MT6816_Typedef mt6816;
/**
* @brief MT6816初始化
* @param NULL
* @retval NULL
**/
void REIN_MT6816_Init(void)
{
#ifndef MT6816_Mode
#error "NO define MT6816_Mode"
#endif
#if (MT6816_Mode == MT6816_Mode_PWM)
REIN_MT6816_PWM_Signal_Init(); //初始化PWM接口
#elif (MT6816_Mode == MT6816_Mode_SPI)
REIN_MT6816_SPI_Signal_Init(); //初始化SPI接口
#elif (MT6816_Mode == MT6816_Mode_ABZ)
REIN_MT6816_ABZ_Signal_Init(); //初始化ABZ接口
#else
#error "MT6816_Mode Invalid !!!"
#endif
//初始化阶段获取一次角度数据(过滤错误数据)(暂未查明复位后第一次读取数据丢失的原因)
REIN_MT6816_Get_AngleData();
}
/**
* @brief MT6816获取角度数据
* @param NULL
* @retval NULL
**/
void REIN_MT6816_Get_AngleData(void)
{
#ifndef MT6816_Mode
#error "NO define MT6816_Mode"
#endif
#if (MT6816_Mode == MT6816_Mode_PWM)
REIN_MT6816_PWM_Get_AngleData(); //MT6816_PWM获取角度数据
mt6816.angle_data = mt6816_pwm.angle;
mt6816.angle = 360*mt6816.angle_data/16384.00f; //转换后的角度0 - 360
#elif (MT6816_Mode == MT6816_Mode_SPI)
RINE_MT6816_SPI_Get_AngleData();
mt6816.angle_data = mt6816_spi.angle;
mt6816.angle = 360*mt6816.angle_data/16384.00f; //转换后的角度0 - 360
#elif (MT6816_Mode == MT6816_Mode_ABZ)
REIN_MT6816_ABZ_Get_AngleData(); //MT6816_ABZ获取角度数据
#else
#error "MT6816_Mode Invalid !!!"
#endif
}
#if (MT6816_Mode == MT6816_Mode_PWM)
/**
* @brief TIM3_IRQHandler
**/
void TIM3_IRQHandler(void)
{
//TIM3的中断标志清除由PWM处理函数识别并清除
REIN_MT6816_PWM_TIM_Callback(); //MT6816_PWM采集中断回调
}
#endif
#if (MT6816_Mode == MT6816_Mode_ABZ)
/**
* @brief EXTI15_10_IRQHandler
**/
void EXTI15_10_IRQHandler(void)
{
if (__HAL_GPIO_EXTI_GET_IT(MT6816_ABZ_Z_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(MT6816_ABZ_Z_Pin);
REIN_MT6816_ABZ_ZPulse_Callback(); //编码器器Z脉冲中断
}
}
#endif
- 🌿mt6816.h
/******
************************************************************************
******
** @project : XDrive_Step
** @brief : Stepper motor with multi-function interface and closed loop function.
** @brief : 具有多功能接口和闭环功能的步进电机
** @author : unlir (知不知啊)
** @contacts: QQ.1354077136
******
** @address : https://github.com/unlir/XDrive
******
************************************************************************
******
** {Stepper motor with multi-function interface and closed loop function.}
** Copyright (c) {2020} {unlir(知不知啊)}
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
******
************************************************************************
******/
#ifndef MT6816_H
#define MT6816_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
//引用端口定义
/********** MT6816 **********/
//MT6816模式控制(GPIO)
#define SENSOR_HVPP_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() //PA7
#define SENSOR_HVPP_GPIO_Port (GPIOA)
#define SENSOR_HVPP_Pin (GPIO_PIN_7)
//MT6816_PWM采集(AFIO & TIM)
#define MT6816_PWM_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() //PA6
#define MT6816_PWM_GPIO_Port (GPIOA)
#define MT6816_PWM_Pin (GPIO_PIN_6)
#define MT6816_PWM_TIM_CLK_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE() //TIM3
#define MT6816_PWM_Get_TIM (TIM3)
#define MT6816_PWM_Get_HTIM (htim3)
#define MT6816_PWM_Get_IRQn (TIM3_IRQn) //TIM3中断
//MT6816_ABZ采集(GPIO)
#define MT6816_ABZ_Z_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() //PA10
#define MT6816_ABZ_Z_GPIO_Port (GPIOA)
#define MT6816_ABZ_Z_Pin (GPIO_PIN_10)
#define MT6816_ABZ_Z_EXTI_IRQn (EXTI15_10_IRQn) //EXTI15_10中断
//MT6816_ABZ采集(AFIO & TIM)
#define MT6816_ABZ_A_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() //PA8
#define MT6816_ABZ_A_GPIO_Port (GPIOA)
#define MT6816_ABZ_A_Pin (GPIO_PIN_8)
#define MT6816_ABZ_B_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() //PA9
#define MT6816_ABZ_B_GPIO_Port (GPIOA)
#define MT6816_ABZ_B_Pin (GPIO_PIN_9)
#define MT6816_ABZ_TIM_CLK_ENABLE() __HAL_RCC_TIM1_CLK_ENABLE() //TIM1
#define MT6816_ABZ_Get_TIM (TIM1)
#define MT6816_ABZ_Get_HTIM (htim1)
//MT6816_SPI采集(GPIO)
//#define MT6816_SPI_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() //PB12
//#define MT6816_SPI_CS_GPIO_Port (GPIOB)
//#define MT6816_SPI_CS_Pin (GPIO_PIN_12)
//MT6816_SPI采集(AFIO & SPI)
//#define MT6816_SPI_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() //PB13
//#define MT6816_SPI_CLK_GPIO_Port (GPIOB)
//#define MT6816_SPI_CLK_Pin (GPIO_PIN_13)
//#define MT6816_SPI_MISO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() //PB14
//#define MT6816_SPI_MISO_GPIO_Port (GPIOB)
//#define MT6816_SPI_MISO_Pin (GPIO_PIN_14)
//#define MT6816_SPI_MOSI_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() //PB15
//#define MT6816_SPI_MOSI_GPIO_Port (GPIOB)
//#define MT6816_SPI_MOSI_Pin (GPIO_PIN_15)
//#define MT6816_SPI_SPI_CLK_ENABLE() __HAL_RCC_SPI2_CLK_ENABLE(); //SPI2
//#define MT6816_SPI_Get_SPI (SPI2)
//#define MT6816_SPI_Get_HSPI (hspi2)
//#define MT6816_SPI_AFIO_REMAP __HAL_AFIO_REMAP_SPI2_ENABLE(); //SPI2_AFIO端口重新映射
/********** MT6816 **********/
//MT6816_SPI采集(GPIO)
#define MT6816_SPI_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() //PA15
#define MT6816_SPI_CS_GPIO_Port (GPIOA)
#define MT6816_SPI_CS_Pin (GPIO_PIN_15)
//MT6816_SPI采集(AFIO & SPI)
#define MT6816_SPI_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() //PB3
#define MT6816_SPI_CLK_GPIO_Port (GPIOB)
#define MT6816_SPI_CLK_Pin (GPIO_PIN_3)
#define MT6816_SPI_MISO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() //PB4
#define MT6816_SPI_MISO_GPIO_Port (GPIOB)
#define MT6816_SPI_MISO_Pin (GPIO_PIN_4)
#define MT6816_SPI_MOSI_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() //PB5
#define MT6816_SPI_MOSI_GPIO_Port (GPIOB)
#define MT6816_SPI_MOSI_Pin (GPIO_PIN_5)
#define MT6816_SPI_SPI_CLK_ENABLE() __HAL_RCC_SPI1_CLK_ENABLE(); //SPI1
#define MT6816_SPI_Get_SPI (SPI1)
#define MT6816_SPI_Get_HSPI (hspi1)
#define MT6816_SPI_AFIO_REMAP __HAL_AFIO_REMAP_SPI1_ENABLE(); //SPI1_AFIO端口重新映射
#define MT6816_SPI_Prescaler (SPI_BAUDRATEPRESCALER_8)
//MT6816工作模式定义
#define MT6816_Mode_PWM (0x01) //MT6816工作在PWM模式 (支持该模式的硬件版本:XDrive_REIN_Basic_H1_0)
#define MT6816_Mode_ABZ (0x02) //MT6816工作在ABZ模式 (支持该模式的硬件版本:XDrive_REIN_Basic_H1_0)
#define MT6816_Mode_SPI (0x03) //MT6816工作在SPI模式 (支持该模式的硬件版本:XDrive_REIN_Basic_H1_0 / XDrive_REIN_Basic_H1_1)
//MT6816工作模式配置
#define MT6816_Mode MT6816_Mode_PWM
#if (MT6816_Mode == MT6816_Mode_PWM)
#include "tim.h"
/********** MT6816_PWM **********/
/********** MT6816_PWM **********/
/********** MT6816_PWM **********/
typedef struct {
//采集数据(PWM)
uint16_t h_width; //PWM高宽度
uint16_t period; //PWM周期
uint8_t count_rising; //PWM上升沿计数器镜像
uint8_t count_falling; //PWM下降沿计数器镜像
uint8_t count_update; //PWM更新计数器
bool whole_h_flag; //PWM全高标志
bool whole_l_flag; //PWM全低标志
bool ready_once; //PWM就绪标志(完成一次PWM采集后置位)
bool ready_again; //PWM就绪标志(完成二次PWM采集后置位)
uint16_t valid_width; //PWM有效宽度
//输出数据(PWM)
uint16_t angle;
} MT6816_PWM_Signal_Typedef;
//MT6816_PWM
void REIN_MT6816_PWM_Signal_Init(void); //MT6816_PWM采集初始化
void REIN_MT6816_PWM_TIM_Callback(void); //MT6816_PWM采集中断回调
void REIN_MT6816_PWM_Get_AngleData(void); //MT6816_PWM获取角度数据
#elif (MT6816_Mode == MT6816_Mode_ABZ)
/********** MT6816_ABZ **********/
/********** MT6816_ABZ **********/
/********** MT6816_ABZ **********/
typedef struct {
//采集数据(ABZ)
uint16_t sample_data; //ABZ读取到的数据
//输出数据(ABZ)
uint16_t angle; //ABZ输出的角度
} MT6816_ABZ_Signal_Typedef;
//MT6816_ABZ
void REIN_MT6816_ABZ_Signal_Init(void); //MT6816_ABZ采集初始化
void REIN_MT6816_ABZ_ZPulse_Callback(void); //MT6816_ABZ采集Z脉冲中断回调
void REIN_MT6816_ABZ_Get_AngleData(void); //MT6816_ABZ获取角度数据
#elif (MT6816_Mode == MT6816_Mode_SPI)
#include "spi.h"
/********** MT6816_SPI **********/
/********** MT6816_SPI **********/
/********** MT6816_SPI **********/
typedef struct {
//采集数据
uint16_t sample_data; //SPI读取到的数据
//输出数据
uint16_t angle; //SPI输出的角度
bool no_mag_flag; //磁铁数据有效标志
bool pc_flag; //奇偶校验位
} MT6816_SPI_Signal_Typedef;
//MT6816_SPI
void REIN_MT6816_SPI_Signal_Init(void); //MT6816_SPI采集初始化
void RINE_MT6816_SPI_Get_AngleData(void); //MT6816_SPI采集获取角度数据
#endif
/********** MT6816 **********/
/********** MT6816 **********/
/********** MT6816 **********/
typedef struct {
uint16_t angle_data; //角度数据
float angle; //换算陈0 - 360 角度数据
// bool rectify_valid; //校准数据有效标志
} MT6816_Typedef;
extern MT6816_Typedef mt6816;
//MT6816
void REIN_MT6816_Init(void); //MT6816初始化
void REIN_MT6816_Get_AngleData(void); //MT6816获取角度数据
#ifdef __cplusplus
}
#endif
#endif
⛳驱动代码使用说明
- 🔖驱动代码基于
STM32F103
,SPI/ABZ/PWM 3方式读取MT6816磁编码器数据做过实测验证。 - 🌿SPI方式默认采用的是SPI1。
- 🌿ABZ方式需要用到TIM1定时器。
- 🌿PWM方式需要利用到TIM3定时器。
🔧STM32CubeMX
工程配置说明:
- 针对使用不同通讯方式:
SPI方式:使能spi1接口即可。(驱动代码中已经包含了对SPI1的初始化代码,其他方式相同,主要是为例生成驱动文件,用来包含
spi.h
、tim.h
头文件)
ABZ模式:使能定时器TIM1既可。
PWM方式:使能定时器TIM3即可。
🌟生成的驱动代码,默认初始化的函数用不到,全部注释掉:(驱动代码中已经包含了相关初始化代码)
📑调用方式:
REIN_MT6816_Init();//初始化MT6816
#if (MT6816_Mode == MT6816_Mode_PWM)
HAL_NVIC_SetPriority(MT6816_PWM_Get_IRQn, 1, 0); //定时器捕获(MT6816_PWM_PWM)获取MT6816_PWM两个边沿计数值
#endif
#if (MT6816_Mode == MT6816_Mode_ABZ)
HAL_NVIC_SetPriority(MT6816_ABZ_Z_EXTI_IRQn, 1, 0); //外部中断(MT6816_ABZ_Z脉冲) 矫正MT6816_ABZ信号
#endif
while(1) {
REIN_MT6816_Get_AngleData(); //MT6816获取角度数据
printf("angle_data:%d,angle:%.2f\r\n", mt6816.angle_data, mt6816.angle);
HAL_Delay(1000);
}
- 🔖以下资源性链接不公开,为避免文章和资源被爬取和盗用,不再对外公布相关资源链接,如果按照上面提供的驱动代码无法独立驱动起来,而你是真正需要这份源码作为参考,用于个人学习研究,可以私信作者,其具体文章和相关资源。