stm32通过spi连接esp8266的hspi 开发

stm32通过spi连接esp8266的hspi 开发   

        刚刚做了stm32通过spi连接esp8266的开发,目前已经解决了遇到的大多数问题,基本可以交付使用了,写一篇文章留作记录,也可以给以后做这个的朋友做为参考。esp8266模块本身发布的时候默认里边烧写的是AT固件,虽然硬件上有spi的引脚,但是并不支持spi的通信,如果要支持spi的通信,自行修改编译esp8266的sdk,写自己需要的代码来实现。本身sdk中有相关的例程,根据例程的代码修改调试就可以实现相应的功能。

     使用spi的好处,第一 可以节省一个串口,因为stm的串口资源是比较有限的。另外spi的通讯速度要比串口快一些。

这篇文章将包含如下的一些内容:

1,stm32 spi的驱动如何开发?

2,esp8266端的驱动如何开发?

3,esp 8266 hspi 的双线协议代码如何实现?

4,tcp 数据转spi , spi数据转tcp数据,数据如何分片重组,一集如何提高性能。

对于smartconfig(自动配网)和tcp client连接server的内容,不放在本篇文章之内。

1,stm32 spi驱动开发

        我使用了stm32的标准库,并没用hal库,因为之前stm32的大部分其它的程序用的都是标准库开发的,所以没有改为hal库。对于spi协议,可以参考另外的文档,我已经将其传到了CSDN上可以到我的CSDN的资源页中下载。如果下载不到可以email 联系我,abc_123_ok at 163.com。


下面为本次开发所用到的原理图:


上图为esp8266端原理图。


上图为stm32端的原理图。

从原理图中可以看到有六根线连接,PB12连GPIO15 CS引脚,PB13连GPIO14 SCK引脚,PB14连GPIO12 为MISO引脚,PB15连GPIO13为MOSI引脚。另外还有PC6连GPIO2 为SPI双线协议的TXINT引脚,PC7连GPIO0为spi双线协议的RXINT引脚。至于双线协议后边还有所涉及。

如下的代码为stm32做为master端的初始化代码。标准的spi共有4个引脚,CS(片选 用于在多个spi设备间选择)  SCK(时钟引脚) MOSI(Master Output  Slave Input 引脚) MISO(Master Input Slave Output引脚)

void Spi2MasterInit(void)
{
	SPI_InitTypeDef SPI_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure; 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE); //设置用到的GPIO引脚的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); //设置SPI2的时钟
	//GPIO12 作为CS引脚
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//CS引脚的模式配置为推挽输出
	GPIO_Init(GPIOB,&GPIO_InitStructure); 
	//SPI的时钟引脚SCK
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;  //SCK
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//GPIO_Mode_AF_PP; 
	GPIO_Init(GPIOB,&GPIO_InitStructure); 
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; //MOSI
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//GPIO_Mode_AF_PP; 
	GPIO_Init(GPIOB,&GPIO_InitStructure); 
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 ; //MISO
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//GPIO_Mode_AF_PP;
	GPIO_Init(GPIOB,&GPIO_InitStructure); 
	//如下的一些配置要和esp8266端相匹配,需要参考8266的技术参考手册,和代码。默认的esp8266的测试程序配置的是时钟空闲低电平,第一个上升沿采样。
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //本端stm32端作为master,那么esp8266端就要作为Slave了。
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //每一次的发送 接收 都以8bit为一个单位。
	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_256; //我当前用的256分频,可以自行调节,加速spi的速率。
	SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //首字节优先
	SPI_InitStructure.SPI_CRCPolynomial = 7; 
	SPI_Init(SPI2, &SPI_InitStructure); 
	//NVIC_Configuration();
	//SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE); 
	SPI_Cmd(SPI2 , ENABLE); 

	//用于reset 8266的wifi模块
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	
	GPIO_Init(GPIOB, &GPIO_InitStructure);	
	GPIO_SetBits(GPIOB, GPIO_Pin_1);
	

}

        上边的代码主要有三个部分,第一个部分对GPIO进行初始化;对二个部分对SPI做了初始化;第三部分初始化了一个wifi模块的reset的引脚,当然也是GPIO的。

        对于GPIO的初始化,最最主要的是初始化GPIO的模式,这些模式的选择应该参考《STM32F10x系列编程手册》上的


        对于第二部分SPI的配置在注释中已经做了详细说明了,注意点就是要和Slave端的配置匹配,按如上的配置就是和esp8266的默认值相匹配的,但您还是要认真做一下检查,此时的默认配置,以后兴许会有改动的。

        如下为SPI的发送接收函数,因为SPI的硬件特性主从移位的原因(可以参考spi协议的介绍),我们发送和接收必须在一起,也就是发一个字节收一个字节,必须这样,否则不能成功的发送和接收。

uint8_t SPI_SendByte1(uint8_t byte)
{
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);
	SPI_I2S_SendData(SPI2,byte); //发送一个byte的数据
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);
	return SPI_I2S_ReceiveData(SPI2); //紧接着再接收一个byte的数据
}

        参考esp8266的技术手册,我们知道esp8266的hspi协议是需要有命令和地址的,所以我们再做一次封装,把命令和地址也封装在里边。

uint8_t spi_transmit(uint8_t cmd, uint8_t addr, uint8_t * buff)
{
	char i;
	GPIO_ResetBits(GPIOB,GPIO_Pin_12); //CS 拉低
	SPI_SendByte1(cmd); //首先主给从发一个命令,命令里包含发送的命令,接收的命令,有函数参数传入。
	SPI_SendByte1(addr); //在发送一个地址,第一协议规定为00,不能下其它的内容。
	//如果命令为0x02 表示为发送命令,0x03为接收命令,esp8266的接收和发送缓冲区都为32byte。
	if(0x02 == cmd) {
		for(i = 0; i < 32; i++) {
			SPI_SendByte1(buff[i]); //在函数参数中发送数据
		}
	} else if(0x03 == cmd) {
		for(i = 0; i < 32; i++) {
			buff[i]=SPI_SendByte1(0xff); //通过函数的返回值接收数据
		}
	}
	GPIO_SetBits(GPIOB,GPIO_Pin_12);  //CS 拉高
	return OK;
}

        esp8266的HSPI有双线协议和单线协议,这两个协议的目的是为了通过中断线通知Master端接收和发送缓冲区的状态,例如,Master发送32个数据给Slave,32byte的数据放在了Slave(8266)的缓冲区中了,如果Slave将数据从缓冲区中移出,这个时候表明Slave已经接收完成,缓冲区也释放了,这个时候就可以通过中断线告知Master,你可以再次发送了。另外对于Slave发送给Master的数据,Slave将数据放到缓冲区中,首先Slave会通过中断线告诉Master:“有数据你可以读了”,Master去读,读完之后也会给Slave一个中断,告诉Slave我读完了,你可以放新的数据到缓冲区中了。总结下就是 Slave可写可读的时候都要通知Master,Master读完的时候也要通知Slave。为了实现双线协议就有以下的代码了。

uint8_t spi_read_func(uint8_t * read_buff)
{
	char ESP07S_GPIO0 = 0;
	
	ESP07S_GPIO0 = GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_7);
	//printf("read:rd_rdy:%x,gpio0:%d,wr_rdy:%x\r\n",rd_rdy,ESP07S_GPIO0,wr_rdy);
	if(rd_rdy && ((ESP07S_GPIO0==0) || wr_rdy)){
		rd_rdy=0;
		spi_transmit(0x03,0,read_buff);
		return OK;
	}
	return READ_FAILED; 
}

uint8_t spi_write_func(uint8_t * write_buff)
{
	char ESP07S_GPIO2 = 0;
	
	ESP07S_GPIO2 = GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_6);
	printf("write:wr_rdy:%x,gpio2:%d,rd_rdy:%x\r\n",wr_rdy,ESP07S_GPIO2,rd_rdy);
	if(wr_rdy && ((ESP07S_GPIO2==0) || rd_rdy)){
		wr_rdy=0;
		//printf("%x,%x",write_buff[0],write_buff[1]);
		spi_transmit(0x02,0,write_buff);
		return OK;
	}
	return WRITE_FAILED;
}

rd_rdy 和wr_rdy 是两根中断线通知上来的状态,下边代码有详细的解释。

以上的两个函数实现了esp8266的hspi双线协议,具体协议的细节可以参考esp8266的技术参考手册。如果没有可以向我要。

上边提到的中断状态,由下面的代码实现。

void EXTI_WIFI_INT_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; 
	EXTI_InitTypeDef EXTI_InitStructure;

	/*开启按键GPIO口的时钟*/
	RCC_APB2PeriphClockCmd(WIFI_RX_INT_GPIO_CLK,ENABLE);
												
	/* 配置 NVIC 中断*/
	NVIC_Configuration();

	/* 选择按键用到的GPIO */	
        GPIO_InitStructure.GPIO_Pin = WIFI_RX_INT_GPIO_PIN;
        /* 配置为浮空输入 */	
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(WIFI_RX_INT_GPIO_PORT, &GPIO_InitStructure);

	/* 选择EXTI的信号源 */
        GPIO_EXTILineConfig(WIFI_RX_INT_EXTI_PORTSOURCE, WIFI_RX_INT_EXTI_PINSOURCE); 
        EXTI_InitStructure.EXTI_Line = WIFI_RX_INT_EXTI_LINE;
	
	/* EXTI为中断模式 */
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	/* 上升沿中断 */
        EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
        /* 使能中断 */	
        EXTI_InitStructure.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStructure);

	/* 选择按键用到的GPIO */	
        GPIO_InitStructure.GPIO_Pin = WIFI_TX_INT_GPIO_PIN;
        /* 配置为浮空输入 */	
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);

	/* 选择EXTI的信号源 */
        GPIO_EXTILineConfig(WIFI_TX_INT_EXTI_PORTSOURCE, WIFI_TX_INT_EXTI_PINSOURCE); 
        EXTI_InitStructure.EXTI_Line = WIFI_TX_INT_EXTI_LINE;
	
	/* EXTI为中断模式 */
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	/* 上升沿中断 */
        EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
        /* 使能中断 */	
        EXTI_InitStructure.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStructure);
}

下边代码是上边使用到的宏的定义,下边的注释部分需要格外注意,不通的GPIO引脚采用的中断不相同,需要查询手册决定用哪个。

//引脚定义
#define WIFI_RX_INT_GPIO_PORT         GPIOC
#define WIFI_RX_INT_GPIO_CLK          (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define WIFI_RX_INT_GPIO_PIN          GPIO_Pin_7
#define WIFI_RX_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOC
#define WIFI_RX_INT_EXTI_PINSOURCE    GPIO_PinSource7
#define WIFI_RX_INT_EXTI_LINE         EXTI_Line7
#define WIFI_RX_INT_EXTI_IRQ          EXTI9_5_IRQn  //此处需要查询手册,来确定用的中断。

#define WIFI_RX_IRQHandler            EXTI9_5_IRQHandler

#define WIFI_TX_INT_GPIO_PORT         GPIOC
#define WIFI_TX_INT_GPIO_CLK          (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define WIFI_TX_INT_GPIO_PIN          GPIO_Pin_6
#define WIFI_TX_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOC
#define WIFI_TX_INT_EXTI_PINSOURCE    GPIO_PinSource6
#define WIFI_TX_INT_EXTI_LINE         EXTI_Line6
#define WIFI_TX_INT_EXTI_IRQ          EXTI9_5_IRQn  //此处需要查询手册,来确定中断。

#define WIFI_TX_IRQHandler            EXTI9_5_IRQHandler


上边代码片段,通过EXTI将两根中断线配置为上升沿中断(协议要求)。

//TX_INT和RX_INT中断发生的时候会调用如下的代码。
void WIFI_TX_IRQHandler(void)
{
	//gpio2上升沿中断
	if(EXTI_GetITStatus(WIFI_TX_INT_EXTI_LINE) != RESET) 
	{
		//printf("wifi rd_rdy gpio2 int\r\n");
		rd_rdy=1;
		EXTI_ClearITPendingBit(WIFI_TX_INT_EXTI_LINE);     
	}  
	//gpio0 上升沿中断
	if(EXTI_GetITStatus(WIFI_RX_INT_EXTI_LINE) != RESET) 
	{
		//printf("wifi wr_rdy gpio0 int\r\n");
		#if TWO_INTR_LINE_PROTOCOL
		wr_rdy=1;
		#elif ONE_INTR_LINE_WITH_STATUS
		//用一个全局变量,把读到的状态返回?
		//spi_status_bitmap = spi_read_status();
		#endif
		EXTI_ClearITPendingBit(WIFI_RX_INT_EXTI_LINE);     
	}
}


2,esp8266端的驱动如何开发?

在该链接下载乐鑫的SDK,我现在的版本是2.2.0
https://github.com/espressif/ESP8266_NONOS_SDK

编译环境的搭建,参考文档esp8266_quick_start_guide_dn.pdf 进行搭建,我是用的虚拟机搭建的,没用用eclipse。

在SDK的如下的目录下,有SPI的例程代码参考,我就是根据这个例程代码做的开发。

D:\VM\share\ESP8266_NONOS_SDK-2.2.0\examples\peripheral_test


在SDK的目录下创建了一个app目录,其中目录结构为如上图片所示,其中集成了几个功能,smart config --- 可以通过手机app或者微信对wifi模块一件配网的功能,还有espconn --- 这个是对lwip的封装可以建立tcp ip的链接的接口,比较好用。hspi --- 主要是SPI的驱动。 smart config 和 espconn不是本篇文章的中断,不在这里叙述。本编文章主要讲spi的内容,下面让我们聚焦esp8266的spi驱动。

esp8266是做Slave的,所以首先是Slave的初始化。

void ICACHE_FLASH_ATTR hspi_slave_init(void)
{
	SpiAttr hSpiAttr; //SPI属性相关配置,一定要和Master端想匹配才能正常通信
	hSpiAttr.bitOrder = SpiBitOrder_MSBFirst;
	hSpiAttr.speed = 0;
	hSpiAttr.mode = SpiMode_Slave;
	hSpiAttr.subMode = SpiSubMode_0;

	// Init HSPI GPIO 将GPIO的功能选择为SPIO的功能
	WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105);
	PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, 2);//configure io to spi mode
	PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2);//configure io to spi mode
	PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2);//configure io to spi mode
	PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 2);//configure io to spi mode

	os_printf("\r\n ============= hspi init slave =============\r\n");
	SPIInit(SpiNum_HSPI, &hSpiAttr);
	
	// Set spi interrupt information.
	SpiIntInfo spiInt;
	spiInt.src = (SpiIntSrc_TransDone 
		|SpiIntSrc_WrStaDone 
		|SpiIntSrc_RdStaDone 
		|SpiIntSrc_WrBufDone 
		|SpiIntSrc_RdBufDone);
	#if 0  //spi 透传协议 单线
	spiInt.isrFunc = spi_slave_isr_sta;
	#else  //SPI 透传协议 双线
	spiInt.isrFunc = spi_slave_isr_handler; //我们使用的是双线协议,所以使用该中断函数。每次Slave收到Master数据的时候,该中断函数被调用到。
	#endif
	SPIIntCfg(SpiNum_HSPI, &spiInt);
	// SHOWSPIREG(SpiNum_HSPI);
	
	SPISlaveRecvData(SpiNum_HSPI);
	uint32_t sndData[8] = { 0 };
	sndData[0] = 0x35343332;
	sndData[1] = 0x39383736;
	sndData[2] = 0x3d3c3b3a;
	sndData[3] = 0x11103f3e;
	sndData[4] = 0x15141312;
	sndData[5] = 0x19181716;
	sndData[6] = 0x1d1c1b1a;
	sndData[7] = 0x21201f1e;

	SPISlaveSendData(SpiNum_HSPI, sndData, 8); //发送给一个测试数据出去
	WRITE_PERI_REG(SPI_RD_STATUS(SpiNum_HSPI), 0x8A);
	WRITE_PERI_REG(SPI_WR_STATUS(SpiNum_HSPI), 0x83);
	GPIO_OUTPUT_SET(2, 1); //对GPIO2的引脚操作
	GPIO_OUTPUT_SET(2, 0);
}

以上的代码是Slave的初始化,代码相关部分做了一定的注释。上边的函数已经注册了中断,接收中断可以在中断处理函数中处理,相当已经有了接收功能,我们还要另外还需要写一个发送函数,来发送spi数据。

//从slave 往master上写数据
uint8_t ICACHE_FLASH_ATTR hspi_write(uint8_t * data)
{
	uint32_t sndData[8] = { 0 };
	//uint8_t data[32] = {0};
	uint8_t idx = 0;
	
	 if (1 == GPIO_INPUT_GET(2)) { //上次发的数据已经被Master读出了吗?如果没读,还不能发送。
		 os_printf("gpio2 == 1\r\n");
		 return 1; //Err NO. 1 表示缓存中数据还没被读出。
	 }
	
	//os_memcpy(data,write_data,32);
	//将字节数据转为4字节的数据,因为寄存器是4byte的。
	while(idx<8) {
		sndData[idx] <<= 0; sndData[idx]|= (data[(idx<<2)+3]&0x000000ff);
		sndData[idx] <<= 8; sndData[idx]|= (data[(idx<<2)+2]&0x000000ff);
		sndData[idx] <<= 8; sndData[idx]|= (data[(idx<<2)+1]&0x000000ff);
		sndData[idx] <<= 8; sndData[idx]|= (data[(idx<<2)+0]&0x000000ff);
		
		os_printf("%x ",sndData[idx]);
		idx++;
	}
	
	os_printf("\r\n");
	SPISlaveSendData(SpiNum_HSPI, sndData, 8);
	WRITE_PERI_REG(SPI_RD_STATUS(SpiNum_HSPI), 0x8A); //这两句是更新状态寄存器
	WRITE_PERI_REG(SPI_WR_STATUS(SpiNum_HSPI), 0x83);
	
	GPIO_OUTPUT_SET(2, 1);
	
	return 0; //成功标志。
}

下边是中断程序代码:

uint8_t tcp_data[1588] = {0};
u16 rcv_len = 0;
void spi_slave_isr_handler( void *para )
{
	uint32		regvalue, calvalue;
	static uint8	state = 0;
	uint32		recv_data, send_data;
	
	//BIT4 表示SPI中断
	if ( READ_PERI_REG( 0x3ff00020 ) & BIT4 )
	{
		/* following 3 lines is to clear isr signal */
		CLEAR_PERI_REG_MASK( SPI_SLAVE( SPI ), 0x3ff );
	}else if ( READ_PERI_REG( 0x3ff00020 ) & BIT7 ) /* bit7 is for hspi isr, */
	{
		int i; //debug
		//记录中断类型
		regvalue = READ_PERI_REG( SPI_SLAVE( HSPI ) );
		
		//os_printf("spi_slave_isr_handler SPI_SLAVE[0x%08x]\n\r", regvalue);
		
		//关闭spi中断使能
		CLEAR_PERI_REG_MASK( SPI_SLAVE( HSPI ),
				     SPI_TRANS_DONE_EN |
				     SPI_SLV_WR_STA_DONE_EN |
				     SPI_SLV_RD_STA_DONE_EN |
				     SPI_SLV_WR_BUF_DONE_EN |
				     SPI_SLV_RD_BUF_DONE_EN );
		//将SPI从机恢复到可通信状态,准备下一次通信
		SET_PERI_REG_MASK( SPI_SLAVE( HSPI ), SPI_SYNC_RESET );
		//清楚中断标志
		CLEAR_PERI_REG_MASK( SPI_SLAVE( HSPI ),
				     SPI_TRANS_DONE |
				     SPI_SLV_WR_STA_DONE |
				     SPI_SLV_RD_STA_DONE |
				     SPI_SLV_WR_BUF_DONE |
				     SPI_SLV_RD_BUF_DONE );
		//打开spi中断
		SET_PERI_REG_MASK( SPI_SLAVE( HSPI ),
				   SPI_TRANS_DONE_EN |
				   SPI_SLV_WR_STA_DONE_EN |
				   SPI_SLV_RD_STA_DONE_EN |
				   SPI_SLV_WR_BUF_DONE_EN |
				   SPI_SLV_RD_BUF_DONE_EN );
		//主机写入,从机接收处理程序
		if ( regvalue & SPI_SLV_WR_BUF_DONE )
		{
			os_printf("SPI_SLV_WR_BUF_DONE\r\n");
			GPIO_OUTPUT_SET( 0, 0 ); //Slave 收到数据将中断线拉低
			idx = 0;
			
			memset(spi_data,0,32);
			while ( idx < 8 )
			{
				recv_data = READ_PERI_REG( SPI_W0( HSPI ) + (idx << 2) );
				spi_data[idx << 2]		= recv_data & 0xff;
				spi_data[(idx << 2) + 1]	= (recv_data >> 8) & 0xff;
				spi_data[(idx << 2) + 2]	= (recv_data >> 16) & 0xff;
				spi_data[(idx << 2) + 3]	= (recv_data >> 24) & 0xff;
				idx++;
			}
			
			os_printf("rx:");
			for(i=0;i<32;i++) {
				os_printf("0x%x ",spi_data[i]);
			}
			os_printf("\r\n");
			//0x01表示为tcp数据, //数据的分片,将在后边做详细讲解
			if(0x01 == spi_data[0]) {
				//数组1里存放的是报文总长度,3为子报文的长度。
				//总长度和分片累加长度相等时证明报文接收完了,可以向外发送了。
				if((rcv_len + spi_data[3])== spi_data[1]){
					// 发送数据出去
					os_memcpy(&tcp_data[rcv_len],&spi_data[4],spi_data[3]);
					at_espconn_demo_response(tcp_data,spi_data[1]);
					rcv_len = 0;
				} else {
					os_memcpy(&tcp_data[rcv_len],&spi_data[4],spi_data[3]);
					rcv_len += spi_data[3];
					os_printf("rcv_len=%d\r\n",rcv_len);
				}
			//表示设置tcp服务器的地址
			} else if(0x02 == spi_data[1]) {
			}
			
			/* add system_os_post here */
			//system_os_post(USER_TASK_PRIO_0,	MOSI,	‘a’);
			GPIO_OUTPUT_SET( 0, 1 ); //Slave处理完收到的数据 将中断再次拉高,通知Master 可以再次写了。
		}
		//主机读取,从机发送处理程序
		if ( regvalue & SPI_SLV_RD_BUF_DONE )
		{
			os_printf("SPI_SLV_RD_BUF_DONE\r\n");
			/* it is necessary to call GPIO_OUTPUT_SET(2, 1), when new data is preped in SPI_W8-15 and needs to be sended. */
			GPIO_OUTPUT_SET( 2, 0 );//Master读完将中断线恢复为低电平。
			/*
			 * add system_os_post here
			 * system_os_post(USER_TASK_PRIO_1,WR_RD,regvalue);
			 */
		}
	}else if ( READ_PERI_REG( 0x3ff00020 ) & BIT9 ) /* bit7 is for i2s isr, */

	{
	}
}

中断程序代码,各种中断状态进入,并且对中断进行处理。各个中断部分已经做了相应的注释了。

以下代码是对spi的驱动函数的调用。


void ICACHE_FLASH_ATTR
user_init(void)
{
	os_printf("SDK version:%s\n", system_get_sdk_version());
	smartconfig_set_type(SC_TYPE_ESPTOUCH); //SC_TYPE_ESPTOUCH,SC_TYPE_AIRKISS,SC_TYPE_ESPTOUCH_AIRKISS
	//根据保存在flash里的配置连接AP
	if(1) {
		int a = 100;
		wifi_connect_ap();
		gpio_init(); //初始化spi用到的gpio
		hspi_slave_init(); //初始化spi slave
		//spi_test_init();
		//test slave wrtie to master.
		//test_hspi_write(); 
		myespconn_init();
	//配网
	}else {
		wifi_set_opmode(STATION_MODE);
		smartconfig_start(smartconfig_done);
	}

}
以上stm32 和esp8266两端的spi代码已经准备齐了,可以进行调试,最后达到spi正常通信了。



3,esp 8266 hspi 的双线协议代码如何实现?

双线协议的协议,请参考esp8266的技术参考手册,在这里不在详细叙述了。

双线协议的代码介绍已经涵盖在第2节和第3节中了,在这里不在做详细的介绍了。


4,spi透传tcp数据分片与重组

spi的缓冲区发送和接收都是32,也就是一次发送接收只能最多32个字节,但是tcp数据有可能是比较长长于32个字节。我们需要将长的超过32byte的tcp数据分片,一片通过spi发送过去,spi的另一端接收到之后再一片一片将数据重新组装起来,下边就是这一部分代码的实现。

首先看我所设计的分片协议:



stm32端将报文分成片,一片一片的发出:

uint8_t snd_tcp_data(uint8_t * data, uint8_t len)
{
	uint8_t spi_data[32] = {0};
	uint8_t * p = NULL;
	uint8_t seq = 0;
	
	spi_data[0] = 1;  //1 报文类型为tcp数据。
	spi_data[1] = len; //第二个byte存放数据的总长度。
	p = data;
	seq = 0;
	
	while(len){
		uint8_t rt = 0;
		if( len <= 28) {
			spi_data[2] = seq;  //报文序列号
			spi_data[3] = len;  //如和总长度相同,就是不分片报文。
			memcpy(&spi_data[4],p,len);
			rt=spi_write_func(spi_data);
			//0 代表送成功。
			if(rt == 0) {
				len = 0;
				seq++;
			}
		} else {
			spi_data[2] = seq;
			spi_data[3] = 28;  //分片报文,每一片的长度。
			memcpy(&spi_data[4],p,28);
			rt = spi_write_func(spi_data);
			if(rt == 0){ //0 代表发送成功
				p += 28;
				len -= 28;
				seq++;
			}
		}
		memset(&spi_data[2],'\0',30);
		//seq++;
	}
	//函数执行成功。
	return 0;
}

esp8266收到数据,将数据重新组装:

		if ( regvalue & SPI_SLV_WR_BUF_DONE )
		{
			os_printf("SPI_SLV_WR_BUF_DONE\r\n");
			GPIO_OUTPUT_SET( 0, 0 );
			idx = 0;
			
			memset(spi_data,0,32);
			while ( idx < 8 )
			{
				recv_data = READ_PERI_REG( SPI_W0( HSPI ) + (idx << 2) );
				spi_data[idx << 2]		= recv_data & 0xff;
				spi_data[(idx << 2) + 1]	= (recv_data >> 8) & 0xff;
				spi_data[(idx << 2) + 2]	= (recv_data >> 16) & 0xff;
				spi_data[(idx << 2) + 3]	= (recv_data >> 24) & 0xff;
				idx++;
			}
			
			os_printf("rx:");
			for(i=0;i<32;i++) {
				os_printf("0x%x ",spi_data[i]);
			}
			os_printf("\r\n");
			//0x01表示为tcp数据,
			if(0x01 == spi_data[0]) {
				//数组1里存放的是报文总长度,3为子报文的长度。
				//总长度和分片累加长度相等时证明报文接收完了,可以向外发送了。
				if((rcv_len + spi_data[3])== spi_data[1]){
					// 发送数据出去
					os_memcpy(&tcp_data[rcv_len],&spi_data[4],spi_data[3]);
					at_espconn_demo_response(tcp_data,spi_data[1]);
					rcv_len = 0;
				} else {
					os_memcpy(&tcp_data[rcv_len],&spi_data[4],spi_data[3]);
					rcv_len += spi_data[3];
					os_printf("rcv_len=%d\r\n",rcv_len);
				}
			//表示设置tcp服务器的地址
			} else if(0x02 == spi_data[1]) {
			}
			
			/* add system_os_post here */
			//system_os_post(USER_TASK_PRIO_0,	MOSI,	‘a’);
			GPIO_OUTPUT_SET( 0, 1 );
		}

esp8266把数据分片发给stm32:

static int8_t ICACHE_FLASH_ATTR hspi_snd_tcp_data(char * data, uint16_t total_len,uint8_t frag_len,uint8_t seq_no)
{
	uint8_t spi_data[32] = {0};
	uint8_t rt = 0;
	
	if(frag_len > 28){
		os_printf("Err:spi data len > 30\r\n");
		return 1;
	}
	
	spi_data[0] = 1;
	spi_data[1] = total_len;
	spi_data[2] = seq_no;
	spi_data[3] = frag_len;
	
	os_memcpy(&spi_data[4],data,frag_len);
	
	rt=hspi_write(spi_data);
	
	//上一次缓冲区的数据还没处理完。
	if(rt == 1){
		return 2;
	}
	
	return 0;
}

// notify at module that espconn has received data
static void ICACHE_FLASH_ATTR
at_espconn_demo_recv(void *arg, char *pusrdata, unsigned short len)
{
	char *p = pusrdata;
	uint8_t rt = 0,seq_no = 1;
	uint16_t total_len = len;
	
	if(len > 0) {
		os_printf("rcv data len %d\r\n",len);
		//发往stm32端(spi master)
		while(len){
			if(len <= 28) { //协议占掉了4个直接了,32-4=28
				//hspi_write(p,len);
				rt = hspi_snd_tcp_data(p,total_len,len ,seq_no);
				if(rt == 0)
					len = 0;
			} else {
				//hspi_write(p,30);
				rt = hspi_snd_tcp_data(p,total_len,28,seq_no);
				if(rt == 0) {
					len -= 28;
					p += 28;
					seq_no++;
				}
			}
			//怕主端长时间没接收,要喂一下狗。
			system_soft_wdt_feed();
		}
	}
	//at_fake_uart_rx(pusrdata,len);
}

stm32收到数据把数据进行组装:

//data 参数必须是分配好的空间,例如生成为数组。
//len 返回接收数据的长度。
uint8_t rcv_tcp_data(uint8_t * data, uint16_t * len) 
{
	uint8_t temp[32]={0},rt=1,*p = data;
	uint16_t total_len=0,frag_len=0,cmd_type=0;
	
	*len = 0;
	while(1){
		uint8_t count = 0;
		while(1){ //等待下一片报文,最多等待50ms
			rt=spi_read_func(temp);
			delay_ms(1);
			count++;
			//if(count == 50) return 1; //返回1表示read函数出错的。
			if((count == 50) || (rt == 0))
				break;
		}
		//printf("count %d \r\n",count);
		if(rt != 0) {
			//printf("read err.\r\n");
			return 1; //返回1表示read函数出错的。
		}	
		
		cmd_type = temp[0];
		
		//是tcp数据。将分片的数据往tcp_data里copy组装,知道最后一个分片。
		if(cmd_type == 1){
			uint8_t * tcp_data;
			total_len = temp[1];
			frag_len = temp[3];			
			tcp_data = &temp[4];
			*len += frag_len;  
			if(total_len == *len) { //数据和总长度相等了,证明是分片数据的最后一片了。
				memcpy(p,tcp_data,frag_len); 
				return 0; //组装完成返回
			} else {
				memcpy(p,tcp_data,frag_len); //组装每一片
				p += frag_len;
			}
			memset(temp,0,32); //清空以备下次使用
		} else {
			return 2; //返回2 表示收到的数据为非tcp数据。
		}
	}
}




已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页