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