之前介绍过I2C协议的模拟,本次介绍另一个常用的协议SPI,使用普通GPIO模拟SPI主机。由于正常使用中SPI频率都比较高,模拟SPI的频率比较低,并不常用。通常在SPI读一些传感器数据,而又没有硬件SPI可用时,可以通过普通GPIO模拟SPI。
模拟SPI时序要比I2C简单,根据时钟收发数据即可,不过SPI分很多种模式(CPOL,CPHA),LSB在前还是MSB在前等。
1. 模拟SPI主机代码
这里使用的是Stm32L4的开发板,基于HAL库实现了GPIO模拟SPI主机,可以支持模拟多个SPI接口,不同接口可以有不同的SCK频率。Demo中在80MHz主频下,粗略设置了200K和500KHz SCK频率,需要精确频率可以通过定时器获得。
头文件为:
#ifndef __SPI_SIMU_H
#define __SPI_SIMU_H
#ifdef __cplusplus
extern "C" {
#endif /*_cplusplus*/
#include "stm32l4xx_hal.h"
typedef struct _SPI_PortDef
{
uint32_t SPI_Timing;
uint8_t CPOL;
uint8_t CPHA;
uint8_t BitOrder;
GPIO_TypeDef* NSS_GPIOx;
uint32_t NSS_Pin;
GPIO_TypeDef* SCK_GPIOx;
uint32_t SCK_Pin;
GPIO_TypeDef* MISO_GPIOx;
uint32_t MISO_Pin;
GPIO_TypeDef* MOSI_GPIOx;
uint32_t MOSI_Pin;
// internal
void (*Sync)(struct _SPI_PortDef*, uint8_t, uint8_t*);
GPIO_PinState idle;
GPIO_PinState active;
} SPI_PortDef;
#define SPI_TIMING_200K 30
#define SPI_TIMING_500K 4
#define SPI_BYTE_TIMEOUT 10u
#define SPI_PIN_MODE GPIO_MODE_OUTPUT_PP
#define SPI_MSB_FIRST 0
#define SPI_LSB_FIRST 1
#define ENTER_CRITICAL()
#define EXIT_CRITICAL()
void SPI_SimInit(SPI_PortDef* port);
void SPI_SimNSS(SPI_PortDef* port, uint8_t state);
HAL_StatusTypeDef SPI_SimWrite(SPI_PortDef* port,
uint8_t *data, uint32_t len, uint32_t Timeout);
HAL_StatusTypeDef SPI_SimRead(SPI_PortDef* port,
uint8_t *data, uint32_t len, uint32_t Timeout);
HAL_StatusTypeDef SPI_SimSync(SPI_PortDef* port,
uint8_t *indata, uint8_t *outdata,
uint32_t len, uint32_t Timeout);
#ifdef __cplusplus
}
#endif /*_cplusplus*/
#endif /* __SPI_SIMU_H */
实现文件为:
#include "spi_sim.h"
static void SysCtlDelay(uint32_t ulCount)
{
__asm(" subs r0, #1\n"
" bne.n SysCtlDelay\n"
" bx lr");
}
static void _SPI_DelayUs(uint32_t us)
{
SysCtlDelay(us*2);
}
void _SPI_RCC_ENABLE(GPIO_TypeDef* GPIOx)
{
if (GPIOx == GPIOA)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
} else if (GPIOx == GPIOB) {
__HAL_RCC_GPIOB_CLK_ENABLE();
} else if (GPIOx == GPIOC) {
__HAL_RCC_GPIOC_CLK_ENABLE();
}
#if defined(GPIOD)
else if (GPIOx == GPIOD) {
__HAL_RCC_GPIOD_CLK_ENABLE();
}
#endif
#if defined(GPIOE)
else if (GPIOx == GPIOE) {
__HAL_RCC_GPIOE_CLK_ENABLE();
}
#endif
#if defined(GPIOF)
else if (GPIOx == GPIOF) {
__HAL_RCC_GPIOF_CLK_ENABLE();
}
#endif
#if defined(GPIOG)
else if (GPIOx == GPIOG) {
__HAL_RCC_GPIOG_CLK_ENABLE();
}
#endif
}
void _SPI_PinOutPut(GPIO_TypeDef *GPIOx, uint32_t Pin)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = SPI_PIN_MODE;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Pin = Pin;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
void _SPI_PinInPut(GPIO_TypeDef *GPIOx, uint32_t Pin)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Pin = Pin;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
/* CPOL=0 & CPOL=1, CPHA=0 */
void SPI_SimSyncByteM0(SPI_PortDef* port, uint8_t chr, uint8_t* out)
{
uint8_t i;
uint8_t tmp = 0;
ENTER_CRITICAL();
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
if (port->BitOrder == SPI_MSB_FIRST)
{
for (i=0;i<8u;i++)
{
if (chr & 0x80)
{
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_RESET);
}
_SPI_DelayUs(port->SPI_Timing>>1);
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->active);
tmp <<= 1;
if (HAL_GPIO_ReadPin(port->MISO_GPIOx, port->MISO_Pin) == GPIO_PIN_SET)
{
tmp |= 0x01;
}
_SPI_DelayUs(port->SPI_Timing>>1);
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
chr <<= 1u;
}
} else {
for (i=0;i<8u;i++)
{
if (chr & 0x01)
{
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_RESET);
}
_SPI_DelayUs(port->SPI_Timing>>1);
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->active);
tmp >>= 1;
if (HAL_GPIO_ReadPin(port->MISO_GPIOx, port->MISO_Pin) == GPIO_PIN_SET)
{
tmp |= 0x80;
}
_SPI_DelayUs(port->SPI_Timing>>1);
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
chr >>= 1u;
}
}
*out = tmp;
EXIT_CRITICAL();
}
/* CPOL=0 & CPOL=1, CPHA=1 */
void SPI_SimSyncByteM1(SPI_PortDef* port, uint8_t chr, uint8_t* out)
{
uint8_t i;
uint8_t tmp = 0;
ENTER_CRITICAL();
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
if (port->BitOrder == SPI_MSB_FIRST)
{
for (i=0;i<8u;i++)
{
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->active);
if (chr & 0x80)
{
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_RESET);
}
_SPI_DelayUs(port->SPI_Timing>>1);
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
tmp <<= 1;
if (HAL_GPIO_ReadPin(port->MISO_GPIOx, port->MISO_Pin) == GPIO_PIN_SET)
{
tmp |= 0x01;
}
_SPI_DelayUs(port->SPI_Timing>>1);
chr <<= 1u;
}
} else {
for (i=0;i<8u;i++)
{
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->active);
if (chr & 0x01)
{
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_RESET);
}
_SPI_DelayUs(port->SPI_Timing>>1);
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
tmp >>= 1;
if (HAL_GPIO_ReadPin(port->MISO_GPIOx, port->MISO_Pin) == GPIO_PIN_SET)
{
tmp |= 0x80;
}
_SPI_DelayUs(port->SPI_Timing>>1);
chr >>= 1u;
}
}
*out = tmp;
EXIT_CRITICAL();
}
void SPI_SimInit(SPI_PortDef* port)
{
_SPI_RCC_ENABLE(port->NSS_GPIOx);
_SPI_RCC_ENABLE(port->SCK_GPIOx);
_SPI_RCC_ENABLE(port->MISO_GPIOx);
_SPI_RCC_ENABLE(port->MOSI_GPIOx);
HAL_GPIO_WritePin(port->NSS_GPIOx, port->NSS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(port->MISO_GPIOx, port->MISO_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
if (port->CPOL == 1){
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, GPIO_PIN_RESET);
}
_SPI_PinOutPut(port->NSS_GPIOx, port->NSS_Pin);
_SPI_PinInPut(port->MISO_GPIOx, port->MISO_Pin);
_SPI_PinOutPut(port->MOSI_GPIOx, port->MOSI_Pin);
_SPI_PinOutPut(port->SCK_GPIOx, port->SCK_Pin);
if ((port->CPOL == 0) && (port->CPHA == 0))
{
port->Sync = SPI_SimSyncByteM0;
port->idle = GPIO_PIN_RESET;
port->active = GPIO_PIN_SET;
} else if ((port->CPOL == 0) && (port->CPHA == 1)) {
port->Sync = SPI_SimSyncByteM1;
port->idle = GPIO_PIN_RESET;
port->active = GPIO_PIN_SET;
} else if ((port->CPOL == 1) && (port->CPHA == 0)) {
port->Sync = SPI_SimSyncByteM0;
port->idle = GPIO_PIN_SET;
port->active = GPIO_PIN_RESET;
} else if ((port->CPOL == 1) && (port->CPHA == 1)) {
port->Sync = SPI_SimSyncByteM1;
port->idle = GPIO_PIN_SET;
port->active = GPIO_PIN_RESET;
} else {
port->Sync = SPI_SimSyncByteM0;
port->idle = GPIO_PIN_RESET;
port->active = GPIO_PIN_SET;
}
}
void SPI_SimNSS(SPI_PortDef* port, uint8_t state)
{
if (state) {
HAL_GPIO_WritePin(port->NSS_GPIOx, port->NSS_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(port->NSS_GPIOx, port->NSS_Pin, GPIO_PIN_RESET);
}
}
HAL_StatusTypeDef SPI_WaitTill(SPI_PortDef* port, uint32_t start, uint32_t Timeout)
{
if ((HAL_GetTick()-start) > Timeout)
{
return HAL_TIMEOUT;
}
return HAL_OK;
}
HAL_StatusTypeDef SPI_SimWrite(SPI_PortDef* port,
uint8_t *data, uint32_t len, uint32_t Timeout)
{
uint8_t tmp;
uint32_t i;
uint32_t start;
start = HAL_GetTick();
for (i=0;i<len;i++)
{
port->Sync(port, data[i], &tmp);
if (SPI_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
return HAL_OK;
}
HAL_StatusTypeDef SPI_SimRead(SPI_PortDef* port,
uint8_t *data, uint32_t len, uint32_t Timeout)
{
uint8_t tmp = 0;
uint32_t i;
uint32_t start;
start = HAL_GetTick();
for (i=0;i<len;i++)
{
port->Sync(port, tmp, data + i);
if (SPI_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
return HAL_OK;
}
HAL_StatusTypeDef SPI_SimSync(SPI_PortDef* port,
uint8_t *indata, uint8_t *outdata,
uint32_t len, uint32_t Timeout)
{
uint32_t i;
uint32_t start;
start = HAL_GetTick();
for (i=0;i<len;i++)
{
port->Sync(port, indata[i], outdata + i);
if (SPI_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
return HAL_OK;
}
2.测试代码
这里直接通过逻辑分析仪分析数据正确性。测试代码为:
uint8_t data[5];
uint8_t data_out[5];
SPI_PortDef spiport;
spiport.SPI_Timing = SPI_TIMING_500K;
spiport.CPOL = 0; //0,1
spiport.CPHA = 0; //0,1
spiport.BitOrder = SPI_MSB_FIRST; //SPI_MSB_FIRST
spiport.NSS_GPIOx = GPIOB;
spiport.NSS_Pin = GPIO_PIN_12;
spiport.SCK_GPIOx = GPIOB;
spiport.SCK_Pin = GPIO_PIN_13;
spiport.MISO_GPIOx = GPIOB;
spiport.MISO_Pin = GPIO_PIN_14;
spiport.MOSI_GPIOx = GPIOB;
spiport.MOSI_Pin = GPIO_PIN_15;
for (uint8_t i=0;i<sizeof(data);i++)
{
data[i] = i;
data_out[i] = 0;
}
SPI_SimInit(&spiport);
SPI_SimNSS(&spiport, 0);
SPI_SimSync(&spiport, data, data_out, sizeof(data), 50);
SPI_SimNSS(&spiport, 1);
通过逻辑分析仪抓到的数据可以看到SPI的时序是正确的:
CPOL=0, CPHA=0, SPI_MSB_FIRST

CPOL=0, CPHA=1, SPI_MSB_FIRST

CPOL=1, CPHA=0, SPI_MSB_FIRST

CPOL=1, CPHA=1, SPI_MSB_FIRST

3.总结
通过GPIO模拟SPI主机灵活方便,可以同时模拟多个SPI端口,缺点是速度都比较慢,只适用一些特殊的情况,如同时测试多个SPI slave设备。