好的,我已经完整阅读了该论坛帖子。下面我来为您做一个详细的分析和解答。
关于 MT6816 角度传感器通过 STC 单片机 SPI 读取数据的问题分析
帖子核心问题回顾
楼主 wlhua* 使用 STC 单片机(从代码看是 STC32G 或 STC8H 系列)的硬件 SPI 读取 MT6816 角度数据,返回始终为 0。改用软件模拟 SPI 后可以正常读取数据(弱磁标识有效),说明硬件 SPI 配置存在问题。
问题分析
从楼主贴出的代码来看,硬件 SPI 配置存在以下几个关键疑点:
1. GPIO 模式配置问题
SPI_Pin_Cfg.Mode = GPIO_OUT_PP; // MOSI(P1.3)、SCK(P1.5)、CS(P1.6) 设为推挽输出 ✓
SPI_Pin_Cfg.Pin = GPIO_Pin_3 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Inilize_Ret = GPIO_Inilize(GPIO_P1, &SPI_Pin_Cfg);
SPI_Pin_Cfg.Mode = GPIO_HighZ; // MISO(P1.4) 设为高阻输入 ✓
SPI_Pin_Cfg.Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4;
GPIO_Inilize_Ret = GPIO_Inilize(GPIO_P1, &SPI_Pin_Cfg);
这里 P1.0、P1.1 也被设为了高阻,但代码中并未使用这两个引脚做 SPI,这本身不是问题,但需注意 P1.0/P1.1 如果被其他外设复用可能会受影响。
2. SPI 模式配置——最可能的问题所在
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // CPOL=1
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // CPHA=1 → 模式3 ✓
**CPOL=1, CPHA=1 确实是 SPI 模式3**,这个配置本身是对的。但问题可能出在:
MT6816 的数据手册中,读操作时序要求:
- 先发送命令字节(高位为 1 表示读,低 7 位为寄存器地址)
- 然后接收 2 个字节的数据(16 位角度值)
- 片选 CS 在传输期间保持低电平
3. 硬件 SPI 读取时序不匹配——核心问题
楼主读取函数的代码(帖子中未完整显示,但从上下文推断):
u8 MT6816_Read_Reg(u8 RegisterAddr)
{
u8 ReadCommand = 0x00;
u8 ReceiveData = 0x00;
bit EnableAllINT_Flag;
ReadCommand = RegisterAddr | 0x80; // 读命令
// ... 后续代码未完整显示
}
硬件 SPI 读 MT6816 的典型问题:
MT6816 的读时序是:主机发送命令字节的同时,从机返回数据。但很多硬件 SPI 在发送完最后一个字节后,需要**额外再发送一个 dummy 字节(0x00)**才能把从机的最后一个字节时钟出来。
对于 MT6816 这类 16 位数据输出的传感器,正确的硬件 SPI 读取流程应该是:
CS = 0;
SPI_Write(ReadCmd); // 发送读命令(8位)
SPI_Write(0x00); // 发送 dummy 字节,同时接收高8位数据
SPI_Write(0x00); // 发送 dummy 字节,同时接收低8位数据
CS = 1;
而楼主的代码可能只发送了一个命令字节就试图读取,导致数据为 0。
解决方案
方案一:修正硬件 SPI 读取函数(推荐)
// 读取 MT6816 16位角度数据(硬件SPI模式3)
u16 MT6816_ReadAngle(void)
{
u8 i;
u16 angle = 0;
u8 rx_h, rx_l;
SPI_MT6816_CSN = 0; // 片选拉低
NOP40(); // 等待tCSS
SPI_ReadWriteByte(0x83); // 发送读命令(读角度寄存器0x03)
rx_h = SPI_ReadWriteByte(0x00); // 发送dummy,接收高8位
rx_l = SPI_ReadWriteByte(0x00); // 发送dummy,接收低8位
SPI_MT6816_CSN = 1; // 片选拉高
NOP40(); // 等待tCSH
angle = ((u16)rx_h << 8) | rx_l; // 合并16位数据
return angle;
}
// 硬件SPI读写一个字节
u8 SPI_ReadWriteByte(u8 dat)
{
SPI_DATA = dat; // 写入发送数据
while((SPI_STAT & 0x80) == 0); // 等待SPIF置位
SPI_STAT = 0x80; // 清除SPIF标志
return SPI_DATA; // 返回接收数据
}
方案二:使用软件模拟 SPI(已验证可行)
参考帖子中 社区* 提供的代码,软件 SPI 已经验证可以正常读取:
// 软件SPI发送8位数据
void SPI_SEND_U8_DATA(u8 dat)
{
u8 i;
for(i = 0; i < 8; i++)
{
if(dat & 0x80) SPI_MOSI = 1;
else SPI_MOSI = 0;
SPI_CLK = 0; // 下降沿
dat <<= 1;
NOP10();
SPI_CLK = 1; // 上升沿
NOP2();
}
}
// 软件SPI接收n位数据
u8 read_angle_8(u8 n)
{
u8 i;
u8 dat = 0;
for(i = 0; i < n; i++)
{
SPI_MOSI = 0;
SPI_CLK = 0; // 下降沿
NOP10();
dat <<= 1;
SPI_CLK = 1; // 上升沿
NOP2();
if(SPI_MISO) dat++;
}
return dat;
}
// 读取MT6825角度(兼容MT6816)
u16 read_6825_angle(void)
{
u8 l_data, h_data, low_data;
SPI_CS = 0; // 启动SPI
SPI_SEND_U8_DATA(0x83); // 发送读命令
h_data = read_angle_8(8); // 接收高8位
l_data = read_angle_8(8); // 接收低8位
low_data = read_angle_8(2); // 接收低2位(MT6825特有)
SPI_CS = 1;
l_data = (l_data & 0xfc) | low_data;
return (((u16)h_data << 8) | l_data);
}
注意:MT6816 是 14 位分辨率,角度数据为 16 位(高 14 位有效,低 2 位为状态位),而 MT6825 是 18 位分辨率。如果使用 MT6816,读取 2 个字节即可,不需要再读额外的 2 位。
调试建议
- 用示波器/逻辑分析仪抓波形:对比硬件 SPI 和软件 SPI 的时序,看 CS、SCK、MOSI、MISO 的波形差异,这是最直接的排查手段。
- 降低 SPI 速度:楼主代码中
SPI_Speed_8(时钟分频 8),可以尝试改为 SPI_Speed_16 或 SPI_Speed_64,排除速度过快导致的问题。
- 检查 MT6816 的 CS 片选时序:MT6816 对 CS 的建立时间(tCSS)和保持时间(tCSH)有要求,硬件 SPI 如果 CS 控制不当可能导致通信失败。
- 确认 MISO 引脚配置:MISO 必须设为高阻输入(楼主已配置为
GPIO_HighZ,正确)。
- 检查硬件连接:确认 MT6816 的 VDD、GND、CS、SCK、MOSI、MISO 接线正确,上拉电阻是否合适。
总结
| 问题 |
原因 |
解决方案 |
| 硬件 SPI 读回 0 |
时序不匹配,缺少 dummy 字节 |
发送命令后补发 dummy 字节以时钟出数据 |
| 软件 SPI 可读 |
时序完全可控 |
可作为临时方案或参考时序 |
| 数据是否正确待验证 |
需确认寄存器地址和位定义 |
参考 MT6816 数据手册核对 |
建议:先用软件 SPI 调通协议、确认数据正确后,再对照软件 SPI 的时序用逻辑分析仪抓波形,然后调整硬件 SPI 配置使其时序一致。如果硬件 SPI 实在调不通,软件 SPI 完全够用,且移植性更好。
🔍 搜索 "https://www.stcaimcu.com/thread-24254-1-1.html"
⏱️ 查找用时 3.5s | 总回答用时 24.6s