目录
适用场景:STM32H723VGT6+ Keil MDK-ARM
协议类型:SSI (Synchronous Serial Interface)
编码器规格:23位 绝对值编码器
一、什么是SSI协议?
1.协议简介
SSI (Synchronous Serial Interface) 是一种同步串行通信协议,广泛用于绝对值编码器与控制器之间的数据传输。它只需要3根线:
CLK(时钟)------由主机单片机发出
DATA(数据)------由编码器(从机)返回
GND(地线)------共地
补充:单片机(SSI主机)它是发出时钟信号,接收数据信号。而绝对值编码器(SSI从机)是接收时钟,同时根据发送数据。
2.通信时序

关键时序参数:
| 参数 | 数值 | 说明 |
|---|---|---|
| f =1/T 时钟频率 | 0.1 ~ 10 MHz | 常用 2~6.25 MHz |
| T 时钟周期 | 160 ns (@6.25MHz) | T = 1/f |
| n 数据位数 | 23 bit | 取决于编码器规格 |
| Tc 帧间隔 | > T | 两帧之间的最小间隔 |
| Td 数据更新 | < T/2 | 上升沿后数据必须在 T/2 内准备好 |
通信流程
1.空闲状态:CLK为高电平,DATA为高阻或高电平。
2.帧起始:CLK变为低电平,编码器在DATA线上输出最高位(MSB)。
3.数据传输:每个 CLK 下降沿,主机锁存一位数据;每个 CLK 上升沿,编码器更新下一位。
4.帧结束:最后一位传输完后,CLK 保持低电平 T 时间,然后回到高电平空闲状态。
5.帧间隔:必须 > T,等待编码器准备下一帧。
二、硬件连接
1.引脚分配
| 功能 | 引脚 | 模式 | 连接到 |
|---|---|---|---|
| SSI_CLK1 (SCK) | PA5 | 推挽输出 | 外部编码器时钟线 |
| SSI_DATA1 (DATA) | PA6 | 上拉输入 | 外部编码器数据线 |
2.连接示意图
cpp
STM32H723VGT6 SSI 绝对值编码器
┌──────────────┐ ┌────────────────┐
│ │ │ │
│ PA5 (CLK) ├───────────►│ CLK │
│ │ │ │
│ PA6 (DATA) │◄───────────┤ DATA │
│ │ │ │
│ GND ├───────────┤ GND │
│ │ │ │
│ (PA7 MOSI) │ 未使用 │ │
│ (PB10 SCK) │ 内部测试 │ (SPI2 从机) │
└──────────────┘ └────────────────┘
三、STM32CUBEMAX的配置
1.配置管脚PA5和PA6。使用管脚模拟时钟时序,因为23位不是标准的24位的。不能使用SPI硬件模拟。(我这个版本是发现硬件SPI行不通,此款产品是23位使用软件。)

2.硬件SPI配置,图片我也配上,建议软件SPI和硬件SPI二选一就可以,硬件通信速度快一些。也可以配置SPI的DMA。

3.时钟配置

4.然后配置调试管脚------用于下载代码

5.编写文件存放位置。------生成文件

四、代码框架
1.完整数据流
cpp
┌─────────────────────────────────────────────────────────┐
│ 主循环 (每100ms) │
│ │
│ 1. SSI_Master_ReadPosition() │
│ ├── PA5(SCK) 产生 23 个时钟脉冲 │
│ ├── PA6(DATA) 在下降沿采样 23 次 │
│ └── 返回 23 位位置值 │
│ │
│ 2. 使用位置值 (控制/显示/计算) │
│ │ │
│ │
│ 3. HAL_Delay(100) 等待 100ms │
└─────────────────────────────────────────────────────────┘
2.主机初始化,可以插入到主函数while循环前。
cpp
/*
* 功 能: SSI_Master_Init - 初始化SSI主机驱动
* 参 数: 无
* 返回值: 无
* 说 明:
* 1. 将PA5/PA6从SPI功能切换为普通GPIO模式
* 2. PA5配置为输出(SCK时钟线), PA6配置为输入
* 3. 启动TIM2定时器用于时序控制和帧间隔测量
* 需在 MX_GPIO_Init() 和 MX_TIM2_Init() 之后调用
*/
void SSI_Master_Init(void)
{
/* 重新配置GPIO为SSI专用模式 */
ssi_gpio_init();
/* 启动TIM2自由运行计数器(用于微秒级时序控制) */
HAL_TIM_Base_Start(&htim2);
/* 清空内部收发缓冲区(安全起见) */
for (uint8_t i = 0; i < SSI_MASTER_BUF_SIZE_MAX; i++)
{
master_tx_buf[i] = 0x00;
master_rx_buf[i] = 0x00;
}
master_data_length = 0;
master_state = SSI_MASTER_STATE_IDLE;
}
3.主机读取数据的代码------这个在while循环,循环里面还需要加延时HAL_Delay(100);需要使用一个变量存储这个函数读取到的数据。这个变量调试查看。

cpp
/*
* 功 能: SSI_Master_ReadPosition - 使用GPIO位带方式读取SSI编码器绝对位置
* 参 数: 无
* 返回值: uint32_t - 23位编码器位置值(低23位有效, 高位为0)
*
* 说 明:
* 通过GPIO直接操作模拟SSI协议时序:
*
* 时序(23个时钟脉冲, MSB优先):
* Clock 1: 下降沿采样编码器输出位P22(MSB), 上升沿编码器更新下一位P22
* Clock 2: 下降沿采样编码器输出位P21, 上升沿编码器更新下一位P21
* ...
* Clock 23: 下降沿采样编码器输出位P0(LSB), 上升沿编码器更新下一位P0
*
* 采样策略:
* SCK下降沿后等待T/2再采样(数据在下降沿后已稳定)
* SCK上升沿前完成采样(避免读取到更新中的数据)
*
* 帧间隔:
* 帧结束SCK保持低电平 > T (确保编码器识别帧结束)
*
* 时序参数:
* TIM2@272MHz, 1tick3.68ns
* T/2 = 80ns = 22 ticks
* T = 160ns = 44 ticks
*
* 注意:
* - 此函数为阻塞方式, 执行时间约 4μs
* - 如需数据位取反(某些编码器输出电平与协议相反)
* 可取消注释 SSI_DATA_INVERT 宏
*/
uint32_t SSI_Master_ReadPosition(void)
{
uint32_t position = 0;
int32_t i;
/*
* 数据位取反开关:
* 某些编码器输出的数据电平与标准SSI协议相反(低电平=1),
* 取消下行宏定义即可使能取反功能, 恢复正逻辑数据
*/
//#define SSI_DATA_INVERT
/*===== 开始传输: SCK由空闲高电平切换到低电平 =====*/
/*
* 循环23个时钟周期, MSB优先输出
*
* 每个时钟周期(T):
* 1. SCK低电平(时钟开始) -> 等待T/2(数据建立时间)
* 2. 等待 T/2 -> 采样数据
* 3. SCK高电平(时钟结束) -> 编码器更新下一位数据
* 4. 等待 T/2 -> 完成半个时钟周期(数据保持时间 < T/2)
*/
/* 第22位至第1位(bit22~bit1): 标准周期T, 每个位采样一次 */
for (i = SSI_MASTER_DATA_BITS - 1; i > 0; i--)
{
/* 时钟开始: 拉低SCK */
SSI_SCK_LOW();
/* 等待 T/2: 数据建立时间 (编码器需 < T/2 建立数据) */
delay_tim2_ticks(SSI_MASTER_HALF_CLOCK_TICKS);
/* 采样数据 (下降沿采样) */
if (SSI_DATA_READ())
{
position |= (1UL << i);
}
/* 时钟结束: 拉高SCK */
SSI_SCK_HIGH();
/* 等待 T/2 */
delay_tim2_ticks(SSI_MASTER_HALF_CLOCK_TICKS);
}
/*===== 最后一位(bit 0) + 帧结束 =====*/
/*
* 最后一位采样后只需要T/2等待,
* 再保持T/2低电平即满足帧结束T时间,
* 然后SCK回到高电平空闲状态
*/
SSI_SCK_LOW();
/* 等待 T/2: 数据建立时间 */
delay_tim2_ticks(SSI_MASTER_HALF_CLOCK_TICKS);
/* 采样最后一位(bit 0) */
if (SSI_DATA_READ())
{
position |= 1UL;
}
/* 帧结束: 再等待 T/2, 累计低电平时间 = T */
delay_tim2_ticks(SSI_MASTER_HALF_CLOCK_TICKS);
/* SCK回到高电平(空闲状态) */
SSI_SCK_HIGH();
/*===== 帧间隔: 等待 > T =====*/
delay_tim2_ticks(SSI_MASTER_FULL_CLOCK_TICKS);
/* 可选: 数据位取反(某些编码器输出电平反转) */
#ifdef SSI_DATA_INVERT
position ^= ((1UL << SSI_MASTER_DATA_BITS) - 1);
#endif
/* 返回23位有效数据, 高位清零 */
return position & ((1UL << SSI_MASTER_DATA_BITS) - 1);
}
4.头文件的宏定义
cpp
/*============================================================================
* SSI主机配置参数
*============================================================================*/
/* SSI主机数据缓冲区最大长度(字节) - 保留用于兼容 */
#define SSI_MASTER_BUF_SIZE_MAX 8
/*
* SSI编码器数据位宽 - 23位绝对位置编码器
*/
#define SSI_MASTER_DATA_BITS 23
/* SSI协议: 总时钟数 = 数据位数 (无触发位, 23个时钟精确对应23位数据) */
#define SSI_MASTER_TOTAL_BITS SSI_MASTER_DATA_BITS /* 23个时钟 */
#define SSI_MASTER_RX_BYTES ((SSI_MASTER_TOTAL_BITS + 7) / 8) /* 3字节 */
/*
* SSI时钟频率 - 根据编码器规格 2~10MHz, 默认6.25MHz
* 由SPI1分频得到: SPI1CLK=12.5MHz, BaudRatePrescaler=2 → 6.25MHz
* TIM2自由运行在272MHz, 用于精确控制位带时序
*/
#define SSI_MASTER_CLOCK_HZ 6250000UL /* SSI时钟频率 6.25MHz */
#define SSI_MASTER_CLOCK_PERIOD_NS 160 /* T = 160ns */
/* TIM2计数器时钟频率(Hz) - 用于微秒级延时 */
#define SSI_MASTER_TIM2_CLOCK_HZ 272000000UL
/* T/2对应的TIM2计数值: 80ns / (1/272MHz) ≈ 22 ticks */
#define SSI_MASTER_HALF_CLOCK_TICKS 22
/* T对应的TIM2计数值 */
#define SSI_MASTER_FULL_CLOCK_TICKS 44
/* SSI帧间隔时间(编码器要求 > T = 160ns, 取10μs保证可靠) */
#define SSI_MASTER_FRAME_GAP_US 10
/* 根据TIM2时钟频率计算指定微秒数对应的计数器增量 */
#define SSI_MASTER_TIM2_TICKS_US(us) ((us) * (SSI_MASTER_TIM2_CLOCK_HZ / 1000000UL))
/*
* GPIO位带操作宏 - 直接寄存器访问, 确保微秒级时序精度
* SSI_CLK1 = PA5, SSI_DATA1 = PA6
*/
#define SSI_SCK_PORT GPIOA
#define SSI_SCK_PIN GPIO_PIN_5
#define SSI_DATA_PORT GPIOA
#define SSI_DATA_PIN GPIO_PIN_6
#define SSI_SCK_HIGH() (SSI_SCK_PORT->BSRR = SSI_SCK_PIN)
#define SSI_SCK_LOW() (SSI_SCK_PORT->BSRR = (uint32_t)(SSI_SCK_PIN << 16U))
#define SSI_DATA_READ() ((SSI_DATA_PORT->IDR & SSI_DATA_PIN) != 0)
愿学者在学习的路上不迷路~
以上仅仅属于本人学习心得,可供学习参考,禁止商用~