剛剛做了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
上一篇:stm32的SPI設置步驟,SPI配置參數
下一篇:stm32 各頭文件或C文件功能
推薦閱讀
史海拾趣