趁著幫老師代上嵌入式實驗課的機會,又重新熟悉了一遍stm32的通信協議:串口協議、SPI協議、I2C協議、RS485協議。大概半年前,是過了一遍的,但也只停留於讀了遍代碼,跑了下例程,最近又過了一遍(自己仔細的看了一遍,老師還給我們講了一遍,自己又講了一遍),然後還寫了一遍軟體模擬SPI、軟體模擬I2C的代碼,才徹底的懂了個皮毛 ,:)。稍微總結下吧,總結的不好,都是自己的理解,僅供參考,主要說軟體模擬SPI、I2C,硬體SPI和硬體I2C就不說了。
串口協議
串口協議沒什麼可說的,現在常用的串口協議,是基於以前的RS232的協議,因為RS232的引腳太多而改進過來的。
物理層只用三根引腳:TXD、RXD、GND(最好接,不然有點影響),然後TXD接RXD、RXD接TXD、GND接GND(這裡我第一次接錯過的,所以寫出來),通過有兩個收發引腳就能看出,串口協議是支持全雙工的;
協議層的話,就是起始信號(1個0表示)+數據包(5、6、7、8位可選)+校驗位(無、0、1、奇、偶可選)+停止信號(0.5、1、1.5、2個1表示)。
串口協議,很常見,多用於列印調試信息,也比較簡單;
RS485協議
RS485協議,協議層未改,只是在串口協議的物理層做了修改,外接了一個物理收發器,然後在通信雙方的兩條A、B線加了電阻,通過測A、B線的電壓差來傳送高低電平信號,所以485通信協議的特點就是抗幹擾性強、傳輸距離遠,可以組網。由於是通過A線、B線的電壓差傳輸高低電平信號,所以是半雙工的,同一時刻只能發送或者接收。物理連接方式:A線接A線,B線接B線。
如何理解半雙工、全雙工,我看到網上一個很好的例子可以幫助理解。就是說,把半雙工通信比作是對講機通信,全雙工就是手機通信,對講機同一時刻只能說或者是聽,而手機是可以同時說、聽的。
前面兩個協議比較簡單,而SPI協議、I2C協議稍微麻煩點,主要說一下。
SPI協議
SPI協議,多應用於ADC、DA、LCD等設備與MCU間,要求通信速率較高的場合。一般需要4根信號線,分別是MOSI、MISO、SCK、CS線。但是!敲黑板!有可能可以只用3根,當你通過SPI和DA設備通信的時候,MISO線就可以不要了,然後我們老師更誇張,說如果你只連一個DA設備的話,那麼CS線也可以不要,這裡我有點不敢苟同,因為畢竟片選線是控制通訊開始結束的,但這種思想是可取的了,規則是死的,人是活的,要活學活用,這裡當時聽到老師講這點的時候還是有點震撼的。扯的有點遠了。。。但其實,這裡要說的軟體模擬。
還是回到正題,接著說軟體模擬SPI,當MCU的SPI外設不夠用的時候,我們就會用GPIO去模擬SPI的方式,去和支持SPI的從設備通信。下面直接貼代碼了,代碼已經調通的了。我做的實驗是用F429IGT6通過軟體模擬SPI讀寫一款W25Q128的FLASH晶片,模擬的是模式3(CPOL=1,CPHA=1),有兩點原因:①模式3的SCK空閒電平為高電平,由高電平向低電平翻轉快,而且容易;②模式3在偶數邊沿採樣,防止第一個信號沒採到。
首先,是GPIO的初始化,CS引腳、MOSI引腳、MISO引腳、SCK引腳。除了MISO引腳配成輸入模式,其餘三個引腳都配成輸出模式(推輓輸出)。
void SPI_FLASH_Init(void)
{
/*定義一個GPIO_InitTypeDef類型的(基本IO)結構體*/
GPIO_InitTypeDef GPIO_InitStructure;
/***** 使能 GPIO 時鐘 *****/
/* 使能 FLASH_SPI引腳的GPIO時鐘< SPI_CS; SPI_MOSI; SPI_MISO; SPI_SCK > ( F口 )*/
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOF, ENABLE);
/* < 配置 SPI_FLASH_SPI 引腳: SCK; MISO; MOSI > */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_9;
/* 設置引腳模式為 SPI_5 復用功能*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
/* 設置引腳速率為50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 設置引腳的輸出類型為推輓輸出*/
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
/* 設置引腳為無上拉 下拉模式*/
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
/* 調用庫函數,使用上面配置的GPIO_InitStructure初始化GPIO_F*/
GPIO_Init(GPIOF, &GPIO_InitStructure);
/* < 配置 SPI_FLASH_SPI 引腳: CS > */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIOF->BSRRL = GPIO_Pin_6;//拉高CS線
GPIO_SetBits(GPIOF, GPIO_Pin_7);//拉高時鐘線,模擬模式3
}
接下來,就是基本的發送、接收函數了。延時是用Symtick來延時,為了延時精確,符合W25Q128晶片數據手冊的時序,理論上應該也可以用軟體延時(未嘗試)。
//軟體模擬SPI寫(發送)
void Soft_SPI_Write(uint8_t a)
{
uint8_t cnt;
for(cnt=0;cnt<8;cnt++)
{
GPIO_ResetBits(GPIOF, GPIO_Pin_7);//拉低CLK
Delay_us(10);//這個延時時間任意,但要大於晶片數據手冊上的(納秒級的)
if(a &0x80)
{
GPIO_SetBits(GPIOF, GPIO_Pin_9);
}
else
{
GPIO_ResetBits(GPIOF, GPIO_Pin_9);
}
a <<= 1;
Delay_us(10);
GPIO_SetBits(GPIOF, GPIO_Pin_7);//拉高CLK
Delay_us(10);
}
}
//軟體模擬SPI讀(接收)
uint8_t Soft_SPI_Read(void)
{
uint8_t cnt;
uint8_t Rxdata = 0;
for(cnt=0;cnt<8;cnt++)
{
GPIO_ResetBits(GPIOF, GPIO_Pin_7);//拉低CLK
Delay_us(10);
Rxdata <<= 1;
if(GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_8))
{
Rxdata |= 0x01;
}
GPIO_SetBits(GPIOF, GPIO_Pin_7);//拉高CLK
Delay_us(10);
}
return Rxdata;
}
然後是,W25Q128晶片的等待準備好的函數。通過讀取該晶片的BUSY位是否為1(繁忙)實現,這款FLAH 是這樣的,你要根據你要執行的操作找到要發送的指令是哪個,以及對應的時序,按照它的時序來,注意時序線上的時間。
void Soft_WaitFlahToBeReady(void)
{
u8 FLASH_Status = 0x01;
GPIOF->BSRRH = GPIO_Pin_6; //**CS低**
Soft_SPI_Write(0x05);
do
{
Soft_SPI_Write(0xFF);
FLASH_Status = Soft_SPI_Read();
}
while((FLASH_Status & 0x01) == 1);
printf("rn 繁忙狀態位為: 0x%Xrn", FLASH_Status);
GPIOF->BSRRL = GPIO_Pin_6; //**CS高**
}
再是,FLASH 的扇區擦除函數了,所有的FLASH每次寫入前都要進行擦除。
void Soft_Flash_SectorErase(u32 EraseAddr)
{
GPIOF->BSRRH = GPIO_Pin_6; //**CS低**
Soft_SPI_Write(0x06);//寫使能指令
GPIOF->BSRRL = GPIO_Pin_6; //**CS高**
GPIOF->BSRRH = GPIO_Pin_6; //**CS低**
Soft_SPI_Write(0x20);//扇區擦除指令
Soft_SPI_Write((EraseAddr & 0xFF0000) >> 16);
Soft_SPI_Write((EraseAddr & 0xFF00) >> 8);
Soft_SPI_Write((EraseAddr & 0xFF));
GPIOF->BSRRL = GPIO_Pin_6; //**CS高**
Soft_WaitFlahToBeReady();
}
再就是,頁寫入函數了,這款FLASH晶片支持三種寫入方式,單字節寫入、頁寫入(<256Bytes)、多字節寫入(基於頁寫入)。顯然,頁寫入方式比單字節寫入快,這裡我只做了頁寫入的方式,用於驗證是否成功,多字節寫入的方式可以在此方式上拓展,秉火的例程上有,可以參考。
void Soft_SPIFlashPageWrite(u8* Pbuffer,u32 Writeaddr,u16 NumberByteToWrite)
{
GPIOF->BSRRH = GPIO_Pin_6; //**CS低**
Soft_SPI_Write(0x06);//寫使能指令
GPIOF->BSRRL = GPIO_Pin_6; //**CS高**
GPIOF->BSRRH = GPIO_Pin_6; //**CS低**
Soft_SPI_Write(0x02);//寫頁寫入指令
Soft_SPI_Write((Writeaddr & 0xFF0000) >> 16);
Soft_SPI_Write((Writeaddr & 0xFF00) >> 8);
Soft_SPI_Write((Writeaddr & 0xFF));
if(NumberByteToWrite > 256)
{
NumberByteToWrite = 256;
printf("寫入的字節數大於256");
}
while(NumberByteToWrite--)
{
Soft_SPI_Write(*Pbuffer);
Pbuffer++;
}
GPIOF->BSRRL = GPIO_Pin_6; //**CS高**
Soft_WaitFlahToBeReady();
}
最後就是讀FLASH函數了,該晶片支持多字節一直讀。
void Soft_SPIFlashRead(u8* Pbuffer,u32 ReadAddr,u16 NumberByteToRead)
{
GPIOF->BSRRH = GPIO_Pin_6; //**CS低**
Delay_us(10);
Soft_SPI_Write(0x03);//寫讀使能指令
Soft_SPI_Write((ReadAddr & 0xFF0000) >> 16);
Soft_SPI_Write((ReadAddr & 0xFF00) >> 8);
Soft_SPI_Write((ReadAddr & 0xFF));
while(NumberByteToRead--)
{
//Soft_SPI_Write(0xFF);//這裡一開始是加了的,因為SPI是全雙工的,
*Pbuffer = Soft_SPI_Read();//但是加了,讀就有問題,然後仔細看了時序圖,發現其實不加也可以
Pbuffer++;
}
GPIOF->BSRRL = GPIO_Pin_6; //**CS高**
}
關鍵字:stm32 通信協議 軟體模擬SPI 軟體模擬I2C 編輯:什麼魚 引用地址:http://news.eeworld.com.cn/mcu/ic473289.html