SPI简介
SPI 的物理线支持 三种类型 :
片选线 (NCS / CS / SS),某些数据手册会将 片选引脚 称为 NSS
/NCS
/ SEL
(Sel ect) / CS
(C hip S elect) / STE
(S laver T ransmit E nable)。
时钟线 (CLK / SCK)
数据线 (DA 或 MOSI 和 MISO )(此处的 DA 特指单根支持半双工的数据线)。
需要注意的是,SPI的通信线有太多的名称,需要自行对照芯片手册进行查询,避免混淆。
SPI 通过对硬件资源的使用修改(禁用片选、禁 / 复用数据线)可以达到 全双工 或 半双工 通信的目的。标准SPI为 四线SPI (CS / CLK / MOSI / MISO);最低支持2根数据引脚(仅保留 CLK / DA,其中DA为分时复用数据线 )来达到半双工通信的目的,可以增加一 / 多根 CS 来达到拓展SPI从设备片选的目的;也可以利用四根独立的半双工数据线,来达到 标准四线SPI 四倍通信能力 的 六线SPI(一般被称为 Quad-IO SPI
)。
SPI分类
SPI通信按GPIO 数量可以分类为 2~6线 SPI,包含单工、半双工、全双工三总。
两线SPI
单工/半双工
CLK / DA 或 CLK / MOSI 或 CLK / MISO
三线SPI
单工/半双工/全双工
CS / CLK / DA 或 CLK / MOSI / MISO
四线SPI
全双工
CS / CLK / MOSI / MISO
六线SPI
全双工
CS / CLK / DA1 / DA2 / DA3 / DA4
例如,据STM32L476RG芯片手册《RM0351 》第1450页所示,SPI外设可以被配置为以下三种模式,即 三线全双工 ,两线半双工 和 两线单工 。
Full-duplex synchronous transfers on three lines
Half-duplex synchronous transfer on two lines (with bidirectional data line)
Simplex synchronous transfers on two lines (with unidirectional data line)
HALF DUPLEX SPI
半双工通信可以是 两线SPI 或 三线SPI,后面着重说明两者异同。
PIN DEFINITION
NCS : SPI控制读写使能信号;使用时需要被拉低 ,否则SDIO会处在 HIGH-Z 态,而SCLK信号也会被无视。也可以被用于在通信错误发生时重置SPI 通信。两线SPI不需要这根线,但个别芯片可能需要这个引脚接地(不能悬空),具体看芯片手册。
SDIO : SPI的数据读写端口;半双工读写。只有在“从被控设备读出数据”的情况下,SDIO
才会由被控制设备控制。
SCLK : SPI接口时钟;总是由主控制器生成和控制。
TRANSMISSION PROTOCOL
跟其他通信类似,SPI 的 SDIO 需要在 时钟低电平时 进行电平跳变。
半双工SPI读写操作都包含两个字(byte),第一个字包含1 bit的 数据方向(或称 控制位) 和 7 bit的 地址 ,第二个字包含 数据 。
关于数据方向/控制位
通常定义是,当需要写入数据时,控制位写 1
。当需要读出数据时,控制位写 0
。
但不一定所有支持半双工SPI的通信控制位都是在MSB,有可能是在 LSB 。
如,下方右侧 SL8541E 的安卓平台主控,支持半双工SPI,但是其控制位就在地址字符的LSB上。
image-20220419235450360
写操作
主控制器在SCLK下降沿时改变 SDIO的电平,被控设备在SCLK上升沿时读取SDIO的电平。
image-20220118140526017
读取操作
SDIO会在SCLK下降沿时修改,主控制器需要在SCLK的上升沿时读取SDIO的电平信息。
注意 :发送完地址字 之后需要延长半个周期的时钟低电平。
image-20220118141033005
DEBUG RECORDS
下面这个时序图错误的坑是真的大,调了好就才折腾明白,这不能是 Hi-Z
态。
时序图错误
内部调试时发现,SDIO在读取数据前后必须是 输出模式 ,最好 强制拉高 ,而不是 Hi-Z
态。
image-20220419210753902
读写单个字符
以下这两个函数中的 for()
使用了 MSB 发送数据,最好不要自作聪明将 i
的数据类型改为 uint8_t
或其他非负型数据,以下这种循环写法会让程序陷入死循环的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 static void write_byte (uint8_t data) { for (int i = 7 ; i>= 0 ; i--) { if ( (data >> i) & 0x01 ) { write_high_level(); } else { write_low_level(); } } } static uint8_t read_byte (void ) { uint8_t data = 0 ; for (int i = 7 ; i>= 0 ; i--) { SCLK_SET_LOW; delay_nop(LEVEL_DELAY_SLICES); SCLK_SET_HIGH; delay_nop(LEVEL_DELAY_SLICES); data |= HAL_GPIO_ReadPin(SPI_DATA_GPIO_Port, SPI_DATA_Pin) << i; } return data; }
SOURCE CODE
下方是适用于 STM32 平台的 GPIO-模拟 半双工SPI 代码,利用 空指令进行延时控制,实现半双工(三线/两线)SPI 的通信。
需要适配以下几个功能:
控制精细度在 \([10^{-9}s:10^{-6}s]\) 之间的 精准延时 (如果做不到,通信速率就上不去 1MHz
)。
控制引脚电平的宏定义。
设置数据接口输出 和 设置数据接口输入 的代码。
拉高电平 和 拉低电平 的代码。
对照着 正确的时序图 撸一遍 读单字符 和 写单字符 的代码。
对照着 正确的时序图 撸一遍 写寄存器 和 读寄存器 的代码。
注意 :都是按顺序来完成的,所有后面的功能都需要依托于前面的功能来实现。而且,需要用示波器(有条件)进行测量以校准通信速率,并使用正确的时序图进行编码(如果是错误的,浪费时间,尽快跟厂家确认)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <stdbool.h> #include "spi_access.h" #include "stm32l4xx_hal.h" #include "log.h" #include "main.h" #define SPI_DATA_Pin GPIO_PIN_5 #define SPI_DATA_GPIO_Port GPIOC #define SPI_CLK_Pin GPIO_PIN_6 #define SPI_CLK_GPIO_Port GPIOC #define SPI_CS_Pin GPIO_PIN_8 #define SPI_CS_GPIO_Port GPIOC #ifndef HIGH_LEVEL #define HIGH_LEVEL GPIO_PIN_SET #endif #ifndef LOW_LEVEL #define LOW_LEVEL GPIO_PIN_RESET #endif #define SDIO_SET_HIGH {HAL_GPIO_WritePin(SPI_DATA_GPIO_Port,SPI_DATA_Pin,HIGH_LEVEL);} #define SDIO_SET_LOW {HAL_GPIO_WritePin(SPI_DATA_GPIO_Port,SPI_DATA_Pin,LOW_LEVEL);} #define SDIO_SET_IN {HAL_GPIO_ReadPin(SPI_DATA_GPIO_Port, SPI_DATA_Pin);} #define SCLK_SET_HIGH {HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, HIGH_LEVEL);} #define SCLK_SET_LOW {HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, LOW_LEVEL);} #define SNCS_SET_HIGH {HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, HIGH_LEVEL);} #define SNCS_SET_LOW {HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, LOW_LEVEL);} #define LEVEL_DELAY_SLICES 80 #define MAX_WRITE_TIMES 10 typedef struct { int handle; } spi_dev; static bool two_wired_spi; static void delay_nop (volatile uint32_t times) { for (uint32_t i = 0 ; i < times; i++) { __NOP(); } } static void delay_us (volatile uint32_t times) { for (uint32_t i = 0 ; i < times; i++) { __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); } } static void delay_ms (uint16_t times) { for (uint16_t i = 0 ; i < times; i++) { delay_us(1000 ); } } static void sdio_input_mode (void ) { GPIO_InitTypeDef GPIO_InitStruct = {0 }; GPIO_InitStruct.Pin = SPI_DATA_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(SPI_DATA_GPIO_Port, &GPIO_InitStruct); } static void sdio_output_mode (void ) { GPIO_InitTypeDef GPIO_InitStruct = {0 }; GPIO_InitStruct.Pin = SPI_DATA_Pin ; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(SPI_DATA_GPIO_Port, &GPIO_InitStruct); } static void write_high_level (void ) { SCLK_SET_LOW; SDIO_SET_HIGH; delay_nop(LEVEL_DELAY_SLICES); SCLK_SET_HIGH; delay_nop(LEVEL_DELAY_SLICES); } static void write_low_level (void ) { SCLK_SET_LOW; SDIO_SET_LOW; delay_nop(LEVEL_DELAY_SLICES); SCLK_SET_HIGH; delay_nop(LEVEL_DELAY_SLICES); } static void write_byte (uint8_t data) { for (int i = 7 ; i>= 0 ; i--) { if ( (data >> i) & 0x01 ) { write_high_level(); } else { write_low_level(); } } } static uint8_t read_byte (void ) { uint8_t data = 0 ; for (int i = 7 ; i>= 0 ; i--) { SCLK_SET_LOW; data |= HAL_GPIO_ReadPin(SPI_DATA_GPIO_Port, SPI_DATA_Pin) << i; delay_nop(LEVEL_DELAY_SLICES); SCLK_SET_HIGH; delay_nop(LEVEL_DELAY_SLICES); } return data; } void *write_sensor_init (bool switch_two_wired_spi) { if (switch_two_wired_spi) { two_wired_spi = true ; } else { two_wired_spi = false ; } spi_dev *instance = NULL ; instance = malloc (sizeof (spi_dev)); memset (instance, 0 , sizeof (spi_dev)); if (two_wired_spi) { SNCS_SET_LOW; SCLK_SET_HIGH; delay_ms(5 ); SCLK_SET_LOW; delay_ms(1 ); SCLK_SET_HIGH; delay_ms(5 ); } else { sdio_input_mode(); SCLK_SET_HIGH; delay_nop(LEVEL_DELAY_SLICES); SCLK_SET_LOW; delay_nop(LEVEL_DELAY_SLICES); SCLK_SET_HIGH; delay_nop(LEVEL_DELAY_SLICES); } instance->handle = 1 ; return (void *)instance; } void write_sensor_deinit (void * handle) { if (handle != NULL ) { free (handle); } else { LOGE("ERROR: NULL spi handle." ); } } void spi_resynchronized () { if (two_wired_spi) { SCLK_SET_HIGH; SDIO_SET_HIGH; delay_ms(2 ); SCLK_SET_LOW; delay_us(1 ); SCLK_SET_HIGH; delay_ms(2 ); SDIO_SET_IN; delay_ms(1 ); } else { return ; } } int write_register (uint8_t regAddr, uint8_t regData) { regAddr |= 0x80 ; for (uint8_t write_times = 0 ; write_times < MAX_WRITE_TIMES; write_times++) { sdio_output_mode(); SDIO_SET_HIGH; SCLK_SET_HIGH; delay_us(1 ); if (!two_wired_spi) { SNCS_SET_LOW; } delay_nop(LEVEL_DELAY_SLICES); write_byte(regAddr); write_byte(regData); if (!two_wired_spi) { SNCS_SET_HIGH; sdio_input_mode(); delay_nop(LEVEL_DELAY_SLICES); } else { delay_us(500 ); } if (read_register(regAddr) == regData) return 0 ; } LOGE("ERROR: Write register failed." ); return -1 ; } uint8_t read_register (uint8_t regAddr) { uint8_t data = 0 ; regAddr &= 0x7F ; sdio_output_mode(); if (two_wired_spi) delay_us(10 ); SDIO_SET_HIGH; SCLK_SET_HIGH; if (two_wired_spi) delay_us(100 ); if (!two_wired_spi) { SNCS_SET_LOW; } delay_nop(LEVEL_DELAY_SLICES); write_byte(regAddr); SCLK_SET_LOW; sdio_input_mode(); if (two_wired_spi) delay_us(5 ); else delay_nop(LEVEL_DELAY_SLICES); data = read_byte(); SCLK_SET_HIGH; sdio_output_mode(); delay_nop(LEVEL_DELAY_SLICES); if (two_wired_spi) delay_us(40 ); else SNCS_SET_HIGH; return data; }
REFERENCE
[【硬件通信协议】5. 实例解析非标准SPI(三线SPI)_sishuihuahua的博客-CSDN博客_三线spi](https://blog.csdn.net/sishuihuahua/article/details/105047926)