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