1# SPI 2 3## 概述 4 5### 功能简介 6 7SPI指串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线。SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信。 8 9SPI接口定义了操作SPI设备的通用方法集合,包括: 10 - SPI设备句柄获取和释放。 11 - SPI读写:从SPI设备读取或写入指定长度数据。 12 - SPI自定义传输:通过消息传输结构体执行任意读写组合过程。 13 - SPI设备配置:获取和设置SPI设备属性。 14 15### 运作机制 16 17在HDF框架中,SPI的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多可能增加内存占用。 18 19SPI以主从方式工作,通常有一个主设备和一个或者多个从设备。主设备和从设备之间一般用4根线相连,它们分别是: 20 - SCLK:时钟信号,由主设备产生; 21 - MOSI:主设备数据输出,从设备数据输入; 22 - MISO:主设备数据输入,从设备数据输出; 23 - CS:片选,从设备使能信号,由主设备控制。 24 25一个主设备和两个从设备的连接示意图如下所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。 26 27**图1** SPI主从设备连接示意图 28 29![image](figures/SPI主从设备连接示意图.png "SPI主从设备连接示意图") 30 31- SPI通信通常由主设备发起,通过以下步骤完成一次通信: 32 1. 通过CS选中要通信的从设备,在任意时刻,一个主设备上最多只能有一个从设备被选中。 33 2. 通过SCLK给选中的从设备提供时钟信号。 34 3. 基于SCLK时钟信号,主设备数据通过MOSI发送给从设备,同时通过MISO接收从设备发送的数据,完成通信。 35 36- 根据SCLK时钟信号的CPOL(Clock Polarity,时钟极性)和CPHA(Clock Phase,时钟相位)的不同组合,SPI有以下四种工作模式: 37 - CPOL=0,CPHA=0 时钟信号idle状态为低电平,第一个时钟边沿采样数据。 38 - CPOL=0,CPHA=1 时钟信号idle状态为低电平,第二个时钟边沿采样数据。 39 - CPOL=1,CPHA=0 时钟信号idle状态为高电平,第一个时钟边沿采样数据。 40 - CPOL=1,CPHA=1 时钟信号idle状态为高电平,第二个时钟边沿采样数据。 41 42### 约束与限制 43 44SPI模块当前只支持主机模式,不支持从机模式。 45 46## 使用指导 47 48### 场景介绍 49 50SPI通常用于与闪存、实时时钟、传感器以及模数/数模转换器等支持SPI协议的设备进行通信。 51 52### 接口说明 53 54SPI模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/spi_if.h。 55 56**表1** SPI驱动API接口功能介绍 57 58| 接口名 | 接口描述 | 59| -------- | -------- | 60| DevHandle SpiOpen(const struct SpiDevInfo \*info) | 获取SPI设备句柄 | 61| void SpiClose(DevHandle handle) | 释放SPI设备句柄 | 62| int32_t SpiRead(DevHandle handle, uint8_t \*buf, uint32_t len) | 读取指定长度的数据 | 63| int32_t SpiWrite(DevHandle handle, uint8_t \*buf, uint32_t len) | 写入指定长度的数据 | 64| int32_t SpiTransfer(DevHandle handle, struct SpiMsg \*msgs, uint32_t count) | SPI数据传输接口 | 65| int32_t SpiSetCfg(DevHandle handle, struct SpiCfg \*cfg) | 根据指定参数,配置SPI设备 | 66| int32_t SpiGetCfg(DevHandle handle, struct SpiCfg \*cfg) | 获取SPI设备配置参数 | 67 68### 使用流程 69 70使用SPI的一般流程如下图所示。 71 72 **图2** SPI使用流程图 73 74 ![image](figures/SPI使用流程图.png "SPI使用流程图") 75 76#### 获取SPI设备句柄 77 78在使用SPI进行通信时,首先要调用SpiOpen获取SPI设备句柄,该函数会返回指定总线号和片选号的SPI设备句柄。 79 80```c 81DevHandle SpiOpen(const struct SpiDevInfo *info); 82``` 83 84 **表2** SpiOpen参数和返回值描述 85 86| **参数** | **参数描述** | 87| -------- | -------- | 88| info | SPI设备描述符 | 89| **返回值** | **返回值描述** | 90| NULL | 获取SPI设备句柄失败 | 91| 设备句柄 | 对应的SPI设备句柄 | 92 93假设系统中的SPI设备总线号为0,片选号为0,获取该SPI设备句柄的示例如下: 94 95```c 96struct SpiDevInfo spiDevinfo; /* SPI设备描述符 */ 97DevHandle spiHandle = NULL; /* SPI设备句柄 */ 98spiDevinfo.busNum = 0; /* SPI设备总线号 */ 99spiDevinfo.csNum = 0; /* SPI设备片选号 */ 100 101/* 获取SPI设备句柄 */ 102spiHandle = SpiOpen(&spiDevinfo); 103if (spiHandle == NULL) { 104 HDF_LOGE("SpiOpen: failed\n"); 105 return; 106} 107``` 108 109#### 获取SPI设备属性 110 111在获取到SPI设备句柄之后,需要配置SPI设备属性。配置SPI设备属性之前,可以先获取SPI设备属性,获取SPI设备属性的函数如下所示: 112 113```c 114int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg); 115``` 116 117 **表3** SpiGetCfg参数和返回值描述 118 119| **参数** | **参数描述** | 120| -------- | -------- | 121| handle | SPI设备句柄 | 122| cfg | SPI设备配置参数 | 123| **返回值** | **返回值描述** | 124| 0 | 获取配置成功 | 125| 负数 | 获取配置失败 | 126 127```c 128int32_t ret; 129struct SpiCfg cfg = {0}; /* SPI配置信息*/ 130ret = SpiGetCfg(spiHandle, &cfg); /* 获取SPI设备属性 */ 131if (ret != 0) { 132 HDF_LOGE("SpiGetCfg: failed, ret %d\n", ret); 133} 134``` 135 136#### 配置SPI设备属性 137 138在获取到SPI设备句柄之后,需要配置SPI设备属性,配置SPI设备属性的函数如下所示: 139 140```c 141int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg); 142``` 143 144 **表4** SpiSetCfg参数和返回值描述 145 146| **参数** | **参数描述** | 147| -------- | -------- | 148| handle | SPI设备句柄 | 149| cfg | SPI设备配置参数 | 150| **返回值** | **返回值描述** | 151| 0 | 配置成功 | 152| 负数 | 配置失败 | 153 154```c 155int32_t ret; 156struct SpiCfg cfg = {0}; /* SPI配置信息*/ 157cfg.mode = SPI_MODE_LOOP; /* 以回环模式进行通信 */ 158cfg.transferMode = PAL_SPI_POLLING_TRANSFER; /* 以轮询的方式进行通信 */ 159cfg.maxSpeedHz = 115200; /* 最大传输频率 */ 160cfg.bitsPerWord = 8; /* 读写位宽为8比特 */ 161ret = SpiSetCfg(spiHandle, &cfg); /* 配置SPI设备属性 */ 162if (ret != 0) { 163 HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret); 164} 165``` 166 167#### 进行SPI通信 168 169- 向SPI设备写入指定长度的数据 170 171 如果只向SPI设备写一次数据,则可以通过以下函数完成: 172 173 ```c 174 int32_t SpiWrite(DevHandle handle, uint8_t *buf, uint32_t len); 175 ``` 176 177 **表5** SpiWrite参数和返回值描述 178 179 | **参数** | **参数描述** | 180 | -------- | -------- | 181 | handle | SPI设备句柄 | 182 | buf | 待写入数据的指针 | 183 | len | 待写入的数据长度 | 184 | **返回值** | **返回值描述** | 185 | 0 | 写入成功 | 186 | 负数 | 写入失败 | 187 188 ```c 189 int32_t ret; 190 uint8_t wbuff[4] = {0x12, 0x34, 0x56, 0x78}; 191 /* 向SPI设备写入指定长度的数据 */ 192 ret = SpiWrite(spiHandle, wbuff, 4); 193 if (ret != 0) { 194 HDF_LOGE("SpiWrite: failed, ret %d\n", ret); 195 } 196 ``` 197 198- 从SPI设备读取指定长度的数据 199 200 如果只读取一次数据,则可以通过以下函数完成: 201 202 ```c 203 int32_t SpiRead(DevHandle handle, uint8_t *buf, uint32_t len); 204 ``` 205 206 **表6** SpiRead参数和返回值描述 207 208 | **参数** | **参数描述** | 209 | -------- | -------- | 210 | handle | SPI设备句柄 | 211 | buf | 待读取数据的指针 | 212 | len | 待读取的数据长度 | 213 | **返回值** | **返回值描述** | 214 | 0 | 读取成功 | 215 | 负数 | 读取失败 | 216 217 ```c 218 int32_t ret; 219 uint8_t rbuff[4] = {0}; 220 /* 从SPI设备读取指定长度的数据 */ 221 ret = SpiRead(spiHandle, rbuff, 4); 222 if (ret != 0) { 223 HDF_LOGE("SpiRead: failed, ret %d\n", ret); 224 } 225 ``` 226 227- 自定义传输 228 229 如果需要发起一次自定义传输,则可以通过以下函数完成: 230 231 ```c 232 int32_t SpiTransfer(DevHandle handle, struct SpiMsg *msgs, uint32_t count); 233 ``` 234 235 **表7** SpiTransfer参数和返回值描述 236 237 | **参数** | **参数描述** | 238 | -------- | -------- | 239 | handle | SPI设备句柄 | 240 | msgs | 待传输数据的数组 | 241 | count | msgs数组长度 | 242 | **返回值** | **返回值描述** | 243 | 0 | 执行成功 | 244 | 负数 | 执行失败 | 245 246 ```c 247 int32_t ret; 248 uint8_t wbuff[1] = {0x12}; 249 uint8_t rbuff[1] = {0}; 250 struct SpiMsg msg; /* 自定义传输的消息 */ 251 msg.wbuf = wbuff; /* 写入的数据 */ 252 msg.rbuf = rbuff; /* 读取的数据 */ 253 msg.len = 1; /* 读取、写入数据的长度都是1 */ 254 msg.csChange = 1; /* 进行下一次传输前关闭片选 */ 255 msg.delayUs = 0; /* 进行下一次传输前不进行延时 */ 256 msg.speed = 115200; /* 本次传输的速度 */ 257 /* 进行一次自定义传输,传输的msg个数为1 */ 258 ret = SpiTransfer(spiHandle, &msg, 1); 259 if (ret != 0) { 260 HDF_LOGE("SpiTransfer: failed, ret %d\n", ret); 261 } 262 ``` 263 264#### 销毁SPI设备句柄 265 266SPI通信完成之后,需要销毁SPI设备句柄,销毁SPI设备句柄的函数如下所示: 267 268```c 269void SpiClose(DevHandle handle); 270``` 271 272该函数会释放掉申请的资源。 273 274 **表8** SpiClose参数描述 275 276| **参数** | **参数描述** | 277| -------- | -------- | 278| handle | SPI设备句柄 | 279 280```c 281SpiClose(spiHandle); /* 销毁SPI设备句柄 */ 282``` 283 284### 使用实例 285 286本例拟对Hi3516DV300开发板上SPI设备进行操作。 287 288SPI设备完整的使用示例如下所示,首先获取SPI设备句柄,然后配置SPI设备属性,接着调用读写接口进行数据传输,最后销毁SPI设备句柄。 289 290```c 291#include "hdf_log.h" 292#include "spi_if.h" 293 294void SpiTestSample(void) 295{ 296 int32_t ret; 297 struct SpiCfg cfg; /* SPI配置信息 */ 298 struct SpiDevInfo spiDevinfo; /* SPI设备描述符 */ 299 DevHandle spiHandle = NULL; /* SPI设备句柄 */ 300 struct SpiMsg msg; /* 自定义传输的消息 */ 301 uint8_t rbuff[4] = { 0 }; 302 uint8_t wbuff[4] = { 0x12, 0x34, 0x56, 0x78 }; 303 uint8_t wbuff2[4] = { 0xa1, 0xb2, 0xc3, 0xd4 }; 304 305 spiDevinfo.busNum = 0; /* SPI设备总线号 */ 306 spiDevinfo.csNum = 0; /* SPI设备片选号 */ 307 spiHandle = SpiOpen(&spiDevinfo); /* 根据spiDevinfo获取SPI设备句柄 */ 308 if (spiHandle == NULL) { 309 HDF_LOGE("SpiOpen: failed\n"); 310 return; 311 } 312 /* 获取SPI设备属性 */ 313 ret = SpiGetCfg(spiHandle, &cfg); 314 if (ret != 0) { 315 HDF_LOGE("SpiGetCfg: failed, ret %d\n", ret); 316 goto err; 317 } 318 cfg.maxSpeedHz = 115200; /* 将最大时钟频率改为115200 */ 319 cfg.bitsPerWord = 8; /* 传输位宽改为8比特 */ 320 /* 配置SPI设备属性 */ 321 ret = SpiSetCfg(spiHandle, &cfg); 322 if (ret != 0) { 323 HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret); 324 goto err; 325 } 326 /* 向SPI设备写入指定长度的数据 */ 327 ret = SpiWrite(spiHandle, wbuff, 4); 328 if (ret != 0) { 329 HDF_LOGE("SpiWrite: failed, ret %d\n", ret); 330 goto err; 331 } 332 /* 从SPI设备读取指定长度的数据 */ 333 ret = SpiRead(spiHandle, rbuff, 4); 334 if (ret != 0) { 335 HDF_LOGE("SpiRead: failed, ret %d\n", ret); 336 goto err; 337 } 338 msg.wbuf = wbuff2; /* 写入的数据 */ 339 msg.rbuf = rbuff; /* 读取的数据 */ 340 msg.len = 4; /* 读取写入数据的长度为4 */ 341 msg.csChange = 1; /* 进行下一次传输前关闭片选 */ 342 msg.delayUs = 0; /* 进行下一次传输前不进行延时 */ 343 msg.speed = 115200; /* 本次传输的速度 */ 344 /* 进行一次自定义传输,传输的msg个数为1 */ 345 ret = SpiTransfer(spiHandle, &msg, 1); 346 if (ret != 0) { 347 HDF_LOGE("SpiTransfer: failed, ret %d\n", ret); 348 goto err; 349 } 350err: 351 /* 销毁SPI设备句柄 */ 352 SpiClose(spiHandle); 353} 354``` 355