刚刚做了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:%xrn",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:%xrn",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
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』