W800 HSPI是做为SPI SLAVE设备来使用的,最高支持50M的时钟,与主设备的连接如下图所示:CS、CLK、MOSI、MISO四根线很容易理解,INT对于MCU端找一个GPIO设置为input,平时为高电平,当从设备有数据需要主动上报时,会变成低电平,直到MCU发送查询中断状态寄存器命令后,才会恢复高电平。
1、W800端收发数据
W800端的代码比较简单,可以参考wm_slave_spi_demo.c,type设置为HSPI_INTERFACE_SPI按如下代码依次调用接口即可完成初始化。
static void HspiInit(int type)
{
if(type == HSPI_INTERFACE_SPI)
{
wm_hspi_gpio_config(0);
}
else if (type == HSPI_INTERFACE_SDIO)
{
wm_sdio_slave_config(0);
}
else
{
printf("do not support interface\n");
return;
}
tls_slave_spi_init();
tls_set_high_speed_interface_type(type);
tls_set_hspi_user_mode(1);
tls_hspi_rx_data_callback_register(HspiRxDataCb);
tls_hspi_rx_cmd_callback_register(HspiRxCmdCb);
}
当收到数据时,底层驱动会调用static s16 HspiRxDataCb(char buf)回调函数,buff里面就是收到的数据的内容。当收到命令时,底层驱动会调用static s16 HspiRxCmdCb(char buf)回调函数,buff里面是收到的命令内容。这里的数据和命令并没有区别,可以都认为是通信内容,只不过是MCU端调用了不同的接口发下来的,比如也可以不使用命令接口,都用数据接口发送也是可以的,那这样就只会调用数据接收回调函数。当W800需要发送数据时,可以调用int tls_hspi_tx_data(char *txbuf, int len)接口。
2、MCU端收发数据
2.1SPI初始化
void SPI1Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
memset(gsSPIRxDesc, 0, sizeof(gsSPIRxDesc));
//GPIOA Periph clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //Configure SPI2 pins: SCK, MOSI
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //Configure SPI2 pins: MISO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //Configure PB2 pin: TP_CS pin
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_SetBits(GPIOA, GPIO_Pin_5);
GPIO_SetBits(GPIOA, GPIO_Pin_7);
// SPI1 Config
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE); // SPI1 enable
SPIINTInit();
}
2.2接收数据时序
INT需要设置为GPIO INPUT、下降沿触发模式。当从设备有数据需要上报时,会先拉低INT,此时MCU端会收到GPIO中断,就知道需要读数据了。
static void SPIRxData(void)
{
INT16U temp = 0;
INT16U i;
WM_SPI_RX_DESC* rxdesc;
INT8U tempdata;
SPINSS(0);
SPIReadWriteByte(SPI_REG_INT_STTS); //查询SPI_INT_HOST_STTS
temp |= SPIReadWriteByte(0xff); //读寄存器,字节序为小端
temp |= SPIReadWriteByte(0xff) << 8;
SPINSS(1);
if((temp != 0xffff) && (temp & 0x01)) //数据或命令已准备好
{
SPINSS(0);
SPIReadWriteByte(SPI_REG_RX_DAT_LEN); //查询RX_DAT_LEN
temp |= SPIReadWriteByte(0xff);
temp |= SPIReadWriteByte(0xff) << 8;
SPINSS(1);
if(temp > 0)
{
if(temp % 4)
{
temp = ((temp + 3) / 4) << 2;
}
rxdesc = SPIGetRxBuff(temp);
if(rxdesc)
{
SPINSS(0);
SPIReadWriteByte(SPI_CMD_RX_DATA); //读数据命令
for(i = 0; i < temp; i++)
{
*(rxdesc->buff+ i) = SPIReadWriteByte(0xff);
// SPI_PRINT("[%d]=[%x]\r\n", i, *(rxdesc->buff + i));
}
SPINSS(1);
AppSendMsg(MSG_SPI, (INT32U)rxdesc);
}
else
{
SPINSS(0);
SPIReadWriteByte(SPI_CMD_RX_DATA); //读数据命令
for(i = 0; i < temp; i++)
{
tempdata = SPIReadWriteByte(0xff);
// SPI_PRINT("[%d]=[%x]\r\n", i, *(rxdesc->buff + i));
}
SPINSS(1);
printf("SPIRXData no buf\r\n");
}
}
else
{
// printf("SPIRXData data len = %04X\r\n", temp);
}
}
else
{
// printf("SPIRXData SPI_REG_INT_STTS = %04X\r\n", temp);
}
}
这里面有三个命令,1、查询中断状态命令,是通过spi读W800的0x06寄存器,然后接收返回的16bit回复,回复的16bit数据中,只有bit0有效,bit0=1表示从设备准备好发送数据,bit0=0表示从设备没有准备好数据。2、查询数据长度,是通过spi读W800的0x02寄存器,然后接收返回的16bit回复,代表主设备需要读取的数据长度,如果不是4的整数倍,需要按4的整数倍读取,因为W800的SPI使用DMA按字发送的。3、读数据命令,是通过spi读取W800的0x10寄存器,然后接收返回的数据,长度在第二条命令里已经知道。注意前两条命令返回的16bit结果都是小端模式。
2.3发送数据时序
发送数据时有两条命令,1、查询从设备的接收buff是否可用,是通过spi读W800的0x03寄存器,然后接收返回的16bit回复,回复的16bit数据中,只有bit1和bit0有效,bit1=1表示W800的cmd buff可用,bit1=0表示W800的cmd buff不可用,bit0=1表示W800的数据buff可用,bit1=0表示W800数据buff不可用。2、发送数据或命令,是通过spi发送0x91(命令)或者0x90(数据),后面跟要发送的数据内容,不够四字节的,需要补齐。可以看到,发送命令或者数据,只是下发的寄存器地址不同,对应到W800从设备接收调用的回调函数不同,跟内容并无关系。使用者可以自行选择。
INT8U SPITxCmd(INT8U *TXBuf, INT16U CmdLen)
{
INT8U temp = 0;
INT16U i;
INT32U retry = 0;
if(NULL == TXBuf)
{
SPI_PRINT("SPITxCmd buff == NULL\r\n");
return 0;
}
SPINSS(0);
while((temp != 0xffff) && (0 == (temp & 0x02)))
{
retry++;
SPIReadWriteByte(SPI_REG_TX_BUFF_AVAIL); //查询TX_BUFF_AVAIL
temp |= SPIReadWriteByte(0xff); //读寄存器,字节序为小端
temp |= SPIReadWriteByte(0xff) << 8;
OSTimeDly(1);
if(retry > SPI_TIMEOUT)
{
SPI_PRINT("SPI_CMD_TIMEOUT\r\n");
return 0;
}
}
SPINSS(1);
if(CmdLen > 0)
{
if(CmdLen % 4)
{
CmdLen = ((CmdLen + 3) / 4) << 2;
}
// SPI_PRINT("TX_BUFF_AVAIL = %d, cmdlen=%d\r\n", temp, CmdLen);
SPINSS(0);
SPIReadWriteByte(SPI_CMD_TX_CMD); //写发送命令命令
for(i = 0; i < CmdLen; i ++)
{
SPIReadWriteByte(*(TXBuf + i));
}
SPINSS(1);
}
return 1;
}
INT8U SPITxData(INT8U *TXBuf, INT16U DataLen)
{
u16 temp = 0;
u16 i;
u16 retry=0;
if(NULL == TXBuf)
{
return 0;
}
SPINSS(0);
while((temp != 0xffff) && (0 == (temp & 0x01)))
{
retry ++;
SPIReadWriteByte(SPI_REG_TX_BUFF_AVAIL); //查询TX_BUFF_AVAIL
temp |= SPIReadWriteByte(0xff); //读寄存器,字节序为小端
temp |= SPIReadWriteByte(0xff) << 8;
// OSTimeDly(1);
if(retry > SPI_TIMEOUT)
{
SPI_PRINT(" TX_BUFF_AVAIL SPI_TIMEOUT \r\n");
return 0;
}
}
SPINSS(1);
if(DataLen > 0)
{
if(DataLen % 4)
{
DataLen = ((DataLen + 3) / 4) << 2;
}
SPINSS(0);
SPIReadWriteByte(SPI_CMD_TX_DATA); //写发送数据命令
for(i = 0; i < DataLen; i ++)
{
SPIReadWriteByte(*(TXBuf + i));
}
SPINSS(1);
}
return 1;
}
3、另外需要注意的是主设备和从设备之间的连线需要串一个阻值为100的电阻,对于时钟频率高得情况下会有帮助,避免出错。
4、STM32做HOST驱动程序实现参考如下:
spi.rar