本篇文章是针对ADI公司ADRV902x系列射频器件中SPI(Serial Peripherial Interface)控制接口,参考设计中相关API函数的学习总结。注意相关知识是基于通用SPI之上,并且针对ADRV902x器件的。每个SPI寄存器都是8位的位宽。
SPI总线包括四个信号:
C S ‾ \overline{CS} CS是低电平有效片选信号,作为baseband processor对transceiver的使能信号,当 C S ‾ \overline{CS} CS为高电平时,transceiver忽略clock和data信号。在传输中强制拉高 C S ‾ \overline{CS} CS电平,会使整个或者部分传输失败。transceiver SPI_EN pin的输入。
S C L K SCLK SCLK是串行接口参考时钟信号,由baseband processor提供,频率在10 MHz ~ 25 MHz之间,当 C S ‾ \overline{CS} CS为低电平时有效。transceiver SPI_CLK pin的输入。
当SPI配置为4-wire bus(4线)模式时,会使用两个data信号:SDIO和SDO。SDIO是baseband processor data的输出和transceiver的输入,使用的是 SPI_DIO pin。与之相反的,SDO是transceiver data的输入和baseband processor的输入,使用的是SPI_DO pin。当SPI配置为3-wire bus(3线)模式时,SDIO信号用作双工模式,不使用SDO。
ADRV902x器件SPI的传输协议包括了Phase1和Phase2,Phase1是control cycle,主要是向transceiver写入一些控制字,比如说接下来的数据是读操作还是写操作,也传输相应的寄存器地址。
Phase2是data field transfer cycle,顾名思义就是传输8-bit SPI寄存器的值。
Phase1 instruction format(16-bit):
R / W ‾ R/\overline{W} R/W:bit 15,logic high表示为读操作,logic low表示写操作。
D 14 : D 0 D14:D0 D14:D0:SPI寄存器的起始地址,如果地址无效,在写操作中,写入的比特会被忽略,在读操作中,会返回全0。
在ADI的参考设计中(https://www.analog.com/en/design-center/landing-pages/001/transceiver-evaluation-software.html)提供了SPI相关的API函数。
关于SPI的配置可以调用adi_adrv9025_SpiCfgSet()。这个函数同样会在adi_adrv9025_Initialize()和 adi_adrv9025_PreMcsInit_v2()这些顶层的初始化函数中被调用。
下面是adi_adrv9025_SpiCfgSet()在参考设计中的函数原型:
/**
* \brief Sets the ADRV9025 device SPI settings (3wire/4wire, msbFirst, etc).
*
* This function will use the settings in the passed spi structure parameter
* to set SPI stream mode, address auto increment direction, msbFirst/lsbfirst,
* and 3wire/4wire mode for the ADRV9025 SPI controller. The ADRV9025 device
* always uses SPI MODE 0 (CPHA=0, CPOL=0) and a 16-bit instruction word.
*
* \pre This function is a helper function and does not need to be called
* directly by the user.
*
* \dep_begin
* \dep{device->common.devHalInfo}
* \dep{init->spiSettings->MSBFirst}
* \dep{init->spiSettings->enSpiStreaming}
* \dep{init->spiSettings->autoIncAddrUp}
* \dep{init->spiSettings->fourWireMode}
* \dep{init->spiSettings->cmosPadDrvStrength}
* \dep_end
*
* \param device Structure pointer to ADRV9025 device data structure
* \param spi Pointer to ADRV9025 SPI controller settings - not platform hardware SPI settings
*
* \retval ADI_COMMON_ACT_WARN_RESET_LOG Recovery action for log reset
* \retval ADI_COMMON_ACT_ERR_RESET_INTERFACE Recovery action for SPI reset required
* \retval ADI_COMMON_ACT_NO_ACTION Function completed successfully, no action required
*/
int32_t adi_adrv9025_SpiCfgSet(adi_adrv9025_Device_t* device,
adi_adrv9025_SpiSettings_t* spi);
从中可以看出关于SPI的相关参数的配置是通过adi_adrv9025_SpiSettings_t这个结构体实现。
/**
* \brief Data structure to hold SPI settings for all system device types
*/
typedef struct adi_adrv9025_SpiSettings
{
uint8_t msbFirst; /*!< 1 = MSB First, 0 = LSB First Bit order for SPI transaction */
uint8_t enSpiStreaming; /*!< Not Recommended - most registers in ADRV9025 API are not consecutive */
uint8_t autoIncAddrUp; /*!< For SPI Streaming, set address increment direction. 1= next addr = addr+1, 0:addr = addr-1 */
uint8_t fourWireMode; /*!< 1: Use 4-wire SPI, 0: 3-wire SPI (SDIO pin is bidirectional). NOTE: ADI's FPGA platform always uses 4-wire mode */
adi_adrv9025_CmosPadDrvStr_e cmosPadDrvStrength; /*!< Drive strength of CMOS pads when used as outputs (SDIO, SDO, GP_INTERRUPT, GPIO 1, GPIO 0) */
} adi_adrv9025_SpiSettings_t;
下面的表格是对结构体中参数的介绍:
SPI的读写在上层可以调用参考设计中的adi_adrv9025_SpiByteWrite()和adi_adrv9025_SpiByteRead()这两个API。
/**
* \brief writes a byte of data to the part.
*
* \dep_begin
* \dep{device->halInfo}
* \dep_end
*
* \param device Pointer to the ADRV9025 device data structure.
* \param addr the address of the register to write to.
* \param data the value to write to the register.
*
* \retval ADRV9025_ACT_WARN_RESET_LOG Recovery action for log reset
* \retval ADRV9025_ACT_ERR_CHECK_PARAM Recovery action for bad parameter check
* \retval ADRV9025_ACT_ERR_RESET_SPI Recovery action for SPI reset required
* \retval ADRV9025_ACT_NO_ACTION Function completed successfully, no action required
*
*/
int32_t adi_adrv9025_SpiByteWrite(adi_adrv9025_Device_t* device,
uint16_t addr,
uint8_t data);
/**
* \brief reads a byte of data from the part.
*
* \dep_begin
* \dep{device->halInfo}
* \dep_end
*
* \param device Pointer to the ADRV9025 device data structure.
* \param addr the address of the register to read from.
* \param readData a pointer to a location to write the register data to.
*
* \retval ADRV9025_ACT_WARN_RESET_LOG Recovery action for log reset
* \retval ADRV9025_ACT_ERR_CHECK_PARAM Recovery action for bad parameter check
* \retval ADRV9025_ACT_ERR_RESET_SPI Recovery action for SPI reset required
* \retval ADRV9025_ACT_NO_ACTION Function completed successfully, no action required
*
*/
int32_t adi_adrv9025_SpiByteRead(adi_adrv9025_Device_t* device,
uint16_t addr,
uint8_t* readData);
在这两个函数中底层的实现都会调用HAL(Hardware Abstraction Layer)接口中的API函数,其实它是两个函数指针:
/* SPI Interface */
int32_t (*adi_hal_SpiWrite)(void* devHalCfg,
const uint8_t txData[],
uint32_t numTxBytes) = NULL;
int32_t (*adi_hal_SpiRead)(void* devHalCfg,
const uint8_t txData[],
uint8_t rxData[],
uint32_t numRxBytes) = NULL;
在定义平台platform时会定义这两个函数指针的具体实现,如平台为ADS9,就用ADS9平台的SPI读写方法,具体参考adi_platform.c文件:
/**
* \brief Platform setup
*
* \param devHalInfo void pointer to be casted to the HAL config structure
* \param platform Platform to be assigning the function pointers
*
* \return
*/
int32_t adi_hal_PlatformSetup(void* devHalInfo,
adi_hal_Platforms_e platform)
{
UNUSED_PARA(devHalInfo);
adi_hal_Err_e error = ADI_HAL_OK;
switch (platform)
{
case ADI_ADS9_PLATFORM:
adi_hal_HwOpen = ads9_HwOpen;
adi_hal_HwClose = ads9_HwClose;
adi_hal_HwReset = ads9_HwReset;
adi_hal_DevHalCfgCreate = ads9_DevHalCfgCreate;
adi_hal_DevHalCfgFree = ads9_DevHalCfgFree;
adi_hal_HwVerify = ads9_HwVerify;
adi_hal_SpiInit = ads9_SpiInit; /* TODO: remove? called by HwOpen() */
adi_hal_SpiWrite = ads9_SpiWrite_v2; //ads9_SpiWrite;
adi_hal_SpiRead = ads9_SpiRead_v2;
adi_hal_CustomSpiStreamWrite = NULL;
adi_hal_CustomSpiStreamRead = NULL;
adi_hal_LogFileOpen = ads9_LogFileOpen;
adi_hal_LogLevelSet = ads9_LogLevelSet;
adi_hal_LogLevelGet = ads9_LogLevelGet;
adi_hal_LogWrite = ads9_LogWrite;
adi_hal_LogFileClose = ads9_LogFileClose;
adi_hal_Wait_us = ads9_TimerWait_us;
adi_hal_Wait_ms = ads9_TimerWait_ms;
/* only required to support the ADI FPGA*/
adi_hal_BbicRegisterRead = ads9_BbicRegisterRead;
adi_hal_BbicRegisterWrite = ads9_BbicRegisterWrite;
adi_hal_BbicRegistersRead = ads9_BbicRegistersRead;
adi_hal_BbicRegistersWrite = ads9_BbicRegistersWrite;
break;
case ADI_ADS8_PLATFORM:
adi_hal_HwOpen = ads8_HwOpen;
adi_hal_HwClose = ads8_HwClose;
adi_hal_HwReset = ads8_HwReset;
adi_hal_DevHalCfgCreate = ads8_DevHalCfgCreate;
adi_hal_DevHalCfgFree = ads8_DevHalCfgFree;
adi_hal_HwVerify = ads8_HwVerify;
adi_hal_SpiInit = ads8_SpiInit; /* TODO: remove? called by HwOpen() */
adi_hal_SpiWrite = ads8_SpiWrite_v2;
adi_hal_SpiRead = ads8_SpiRead_v2;
adi_hal_CustomSpiStreamWrite = NULL;
adi_hal_CustomSpiStreamRead = NULL;
adi_hal_LogFileOpen = ads8_LogFileOpen;
adi_hal_LogLevelSet = ads8_LogLevelSet;
adi_hal_LogLevelGet = ads8_LogLevelGet;
adi_hal_LogWrite = ads8_LogWrite;
adi_hal_LogFileClose = ads8_LogFileClose;
adi_hal_Wait_us = ads8_TimerWait_us;
adi_hal_Wait_ms = ads8_TimerWait_ms;
/* only required to support the ADI FPGA*/
adi_hal_BbicRegisterRead = ads8_BbicRegisterRead;
adi_hal_BbicRegisterWrite = ads8_BbicRegisterWrite;
adi_hal_BbicRegistersRead = ads8_BbicRegistersRead;
adi_hal_BbicRegistersWrite = ads8_BbicRegistersWrite;
break;
case ADI_UNKNOWN_PLATFORM:
adi_hal_HwOpen = NULL;
adi_hal_HwClose = NULL;
adi_hal_HwReset = NULL;
adi_hal_DevHalCfgCreate = NULL;
adi_hal_DevHalCfgFree = NULL;
adi_hal_HwVerify = NULL;
adi_hal_SpiInit = NULL; /* TODO: remove? called by HwOpen() */
adi_hal_SpiWrite = NULL;
adi_hal_SpiRead = NULL;
adi_hal_CustomSpiStreamWrite = NULL;
adi_hal_CustomSpiStreamRead = NULL;
adi_hal_LogFileOpen = NULL;
adi_hal_LogLevelSet = NULL;
adi_hal_LogLevelGet = NULL;
adi_hal_LogWrite = ads8_LogWrite;
adi_hal_LogFileClose = NULL;
adi_hal_Wait_us = NULL;
adi_hal_Wait_ms = NULL;
/* only required to support the ADI FPGA*/
adi_hal_BbicRegisterRead = ads8_BbicRegisterRead;
adi_hal_BbicRegisterWrite = ads8_BbicRegisterWrite;
adi_hal_BbicRegistersRead = ads8_BbicRegistersRead;
adi_hal_BbicRegistersWrite = ads8_BbicRegistersWrite;
break;
default:
error = ADI_HAL_GEN_SW;
break;
}
return error;
}
当然这里就可以设计用户自定义的底层SPI读写实现方法。
transcevier也提供了primary SPI的接口,API函数配置基本一样,名称不同,例如adi_adrv9025_Spi2CfgSet(adi_adrv9025_Device_t *device, uint8_t spi2Enable):
/**
* \brief Enables/Disables SPI2 on ADRV9025
* \brief Not currently implemented
*
* The ADRV9025 device can enable a second SPI port on the low voltage GPIO[3:0]
* pins. This SPI port allows read/write access to a limited set of
* TxAttenuation and Rx gain index registers.
*
* The SPI2 uses the same configuration that is programmed for SPI. This
* includes LSB/MSB first, 4 wire mode, streaming, and address increment.
*
* One unique feature about the TxAtten control is that the SPI register value
* can be set and does not update to the transmitter until a GPIO pin is
* toggled (See spi2TxAttenGpioSel function parameter). The GPIO pin is level
* sensitive, selecting the TxAtten programmed in either the
* tx(1/2)_attenuation_s1 or tx(1/2)_attenuation_s2 bitfields of the second SPI
* registers. The GPIO pin used to switch between the two TxAtten settings is
* user selectable using the ENUM in the function parameter.
*
* For readback of TxAttenuation SPI registers, first write the desired register
* to force the value to be updated, before reading it back across SPI.
*
* \dep_begin
* \dep{device->common.devHalInfo}
* \dep_end
*
* \param device Pointer to the ADRV9025 data structure
* \param spi2Enable Enable(=1)/Disable(=0) SPI2 protocol on ADRV9025
*
* \retval ADI_ADRV9025_WARN_RESET_LOG Recovery action for log reset
* \retval ADI_ADRV9025_ERR_CHECK_PARAM Recovery action for bad parameter check
* \retval ADI_ADRV9025_ERR_RESET_SPI Recovery action for SPI reset required
* \retval ADI_ADRV9025_NO_ACTION Function completed successfully, no action required
*/
int32_t adi_adrv9025_Spi2CfgSet(adi_adrv9025_Device_t* device,
uint8_t spi2Enable);