SSI 绝对值编码器通信 - 单片机TM32Cubemax配置教程

目录

一、什么是SSI协议?

1.协议简介

2.通信时序

二、硬件连接

三、STM32CUBEMAX的配置

四、代码框架


适用场景: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)

愿学者在学习的路上不迷路~

以上仅仅属于本人学习心得,可供学习参考,禁止商用~

相关推荐
科芯创展1 小时前
1.5A,5.5VIN,单灯,XZ4059A/D,4.2V/4.34V
单片机
江哥工作室2 小时前
几分钱的 LM358 采集心电
单片机·lm358·心电·小信号
迷茫不知归路2 小时前
简单认识一种外设硬件:温度传感器ds18b20
嵌入式硬件
z20348315202 小时前
CLion开发STM32——文件管理
stm32·单片机·嵌入式硬件·clion
weixin_452077643 小时前
STM32 烧录程序后上电不工作问题集合
stm32·单片机·嵌入式硬件
姜小二3 小时前
QAxObject实现读写excel
嵌入式硬件·excel
沸速存储3 小时前
内存技术的未来:DDR6、CAMM2与CXL将如何改变计算架构
科技·嵌入式硬件·架构·计算机外设·电脑
LCG元3 小时前
STM32多传感器数据采集系统设计:I2C+ADC+UART+低功耗工程实战
stm32·单片机·嵌入式硬件
国科安芯4 小时前
MCU芯片AS32X601如何使用 DMA 单次模式搬运 ADC 多通道采样数据?
单片机·嵌入式硬件