历史上的今天
返回首页

历史上的今天

今天是:2024年09月12日(星期四)

正在发生

2019年09月12日 | stm32通过spi连接esp8266的hspi 开发

2019-09-12 来源:eefocus

刚刚做了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 = 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 intrn");

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 intrn");

#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:VMshareESP8266_NONOS_SDK-2.2.0examplesperipheral_test

推荐阅读

史海拾趣

台湾致强(FORT)公司的发展小趣事

面对电子行业的快速发展和市场需求的不断变化,致强科技始终保持敏锐的市场洞察力。公司积极寻求与上下游企业的跨界合作,共同推动产业升级和技术创新。通过与知名电子厂商、科研机构及高校等建立紧密的合作关系,致强科技不断引入新技术、新工艺和新材料,为产品的升级换代提供了有力支持。同时,公司还积极拓展新业务领域,如车联网、物联网等新兴领域,为公司的未来发展开辟了更广阔的空间。

Hitachi Metals公司的发展小趣事

自1956年成立以来,Hitachi Metals便逐步在电子材料领域建立了坚实的基础。公司早期便专注于研发和生产用于电子设备的核心部件,如磁性材料。随着电子行业的蓬勃发展,日立金属迅速抓住了市场机遇,推出了高性能的Nd-Fe-B系列烧结磁石和高性能铁氧体烧结磁石,这些材料广泛应用于计算机、半导体集成电路器件及平板显示屏等高科技产品中。凭借其卓越的产品质量和稳定的市场供应,Hitachi Metals在电子材料市场上赢得了广泛的认可。

协顺电子(Finecables)公司的发展小趣事

随着公司业务的不断拓展,协顺电子逐渐意识到品质和创新的重要性。公司投入大量资金引进先进的生产设备和技术人才,同时加强内部管理和质量控制。此外,协顺电子还积极与国内外知名企业和研究机构合作,共同研发新产品和新技术。这些举措使得公司的产品质量和技术水平得到了显著提升,也赢得了客户的广泛赞誉。

CAMBION公司的发展小趣事

CAMBION公司自成立以来,始终将技术创新作为核心驱动力。在早期,公司研发团队成功开发了一款具有革命性的芯片技术,显著提升了电子设备的处理速度和能效。这一技术迅速在行业内引起关注,为CAMBION赢得了大量订单和合作伙伴。随着技术的不断完善和应用范围的扩大,CAMBION逐渐在电子行业中崭露头角。

功得(CONQUER)公司的发展小趣事

在市场竞争日益激烈的环境下,功得公司意识到仅仅依靠创新是不够的,还需要有高品质的产品来赢得客户的信任。因此,公司开始注重产品质量管理,建立了完善的质量控制体系。功得公司严格把控原材料采购、生产工艺和成品检验等环节,确保每一件产品都符合高标准的质量要求。这种对品质的执着追求,使得功得公司的产品在市场上赢得了良好的口碑,品牌知名度也逐渐提升。

Excel Cell Electronic Co Ltd公司的发展小趣事

自1981年成立以来,ECE公司始终将技术创新作为发展的核心驱动力。早期,公司专注于电子元器件的研发和生产,通过引进国际先进技术,并结合本地市场需求进行创新,成功推出了一系列具有竞争力的产品。随着技术的不断进步,ECE公司不断加大研发投入,积极探索新的应用领域,逐渐成为电子行业的领军企业。

问答坊 | AI 解惑

CDMA模块

CDMA模块基于CDMA平台的通信模块,它将通信芯片、存储芯片等集成在一块电路板上,使其具有发送通过CDMA平台收发短消息、语音通话、数据传输等功能。CDMA模块可以实现普通CDMA手机的主要通信功能,也可以说是一个“精简版”的手机。电脑、单片机、AR ...…

查看全部问答>

基于ARM的SD主控制器的设计与实现.pdf

基于ARM的SD主控制器的设计与实现.pdf…

查看全部问答>

编译uclinux内核时候遇到的问题,帮我看看吧。

由于毕业设计需要和本身兴趣,要构造ARM+uclinux,本机Linux版本:UBUNTU6 .06. 在本机上安装了www.uclinux.org上下的arm-elf-tools, 然后arm-elf-gcc-v,显示如下 Reading specs from /usr/local/lib/gcc-lib/arm-elf/2.95.3/specs gcc ver ...…

查看全部问答>

DriverWized生成的应用程序

用DS3.2的DriverWized生成一个驱动程序后. 生成的win32 console应用程序无法打开和运行.exe文件. 错误提示: Could not execute: Bad executable format (Win32 error 193) Cannot execute program 为什么我的应用程序无法运行? 另外如何直接在D ...…

查看全部问答>

有谁做过 BF532 的视频开发吗?

有项目要做,自己想从头学。…

查看全部问答>

怎样程序烧写到指定的flash地址里?

在项目中遇到一个问题:需要将一个变量放到一个指定的flash地址里,即flash的绝对地址中。我查看了IAR的complier手册,上面的例子说以下面的方式定义即可。const int bootKey @ 0x0801FFFC = 3; /* O ...…

查看全部问答>

谁来做一次9B96的使用教程?

期待EEWORLD和TI能够为我们这些菜鸟提供一些学习的方法和使用介绍!我想以后使用这个9B96但是这个入门貌似还不懂!…

查看全部问答>

稳定电源/负载电路的快速瞬态响应

吉时利的2300系列电池/充电器仿真器(快速瞬态响应电源)专用于研发和制造环境中RFIC功放、手机及其它便携式电池供电产品的功率估计。这些电池仿真电源能从变化量高达1000%(即负载电流增大10倍)的短脉冲负载电流中快速恢复并测量负载电流脉冲的 ...…

查看全部问答>

输入偏置电流消除电阻—您真的需要它们吗?

您会为了匹配您运算放大器电路的输入 DC 电阻而添加一个电阻器吗?请看下面图 1 所示电路。我们中的许多人会教条地认为添加 Rb 是一种“好方法”,并让其值等于 R1 和 R2 的并联组合。我们现在就来研究使用这种电阻器的原因,并思考它的使用是否必 ...…

查看全部问答>