本章概述思維導圖:
SPI通信協議
SPI通信協議介紹
SPI通訊:高速的、串行通訊、全雙工、同步、總線協議;(通過片選信號選中設備);
注:SPI通訊通過片選信號選中設備,串口通訊通過端口號選中設備;
SPI通訊一般至少配置4根線:SCK時鐘線,MOSI主機輸出從機輸入、MISO主機輸入從機輸出、CS片選;
SPI中根據時鐘極性和時鐘相位的不同,可以分為4種模式;
CPOL時鐘極性:可以確定時鐘線空閑時的電平狀態;
CPHA時鐘相位:可以確定第一個時鐘沿是發送數據還是采樣數據的
尋址方式:當主設備要和多個從機進行通信時,主設備需要先向對應從設備的片選線上發送使能信號(高電平或者低電平,根據從機而定)表示選中該從設備;
SPI總線在進行數據傳送時,先傳送高位,后傳送低位;數據線為高電平表示邏輯‘1’,低電平表示邏輯‘0’,一字節傳送完成后無需應答即可開始下一字節的傳送;SPI總線采用同步方式工作,時鐘線在上升沿或下降沿時發送器向數據線上發送數據,在緊接著的下降沿或上升沿接收器從數據線上讀取數據,完成一位數據傳送,八個時鐘周期即可完成一個字節數據的傳送;
極性和相位:
PI總線有4種不同的工作模式,取決于時鐘極性CPOL和時鐘相位CPHL這兩個因素;
時鐘極性CPOL表示時鐘線SCLK空閑時的狀態;
????????CPOL=0時,空閑時時鐘線SCLK為低電平;
????????CPOL=1時,空閑時時鐘線SCLK為高電平;
時鐘相位CPHL表示采樣時刻;
????????CPHL=0時,每個周期的第一個時鐘沿采樣數據;
????????CPHL=1時,每個周期的第二個時鐘沿采樣數據;
極性和相位設置
對于一個特定的從設備來說,一般在出廠時就會將其設計為某種特定的工作模式,我們在使用該設備就必須保證主設備的工作模式和該從設備保持一致否則是無法進行通信的,所以一般我們需要對主設備的CPOL極性和CPHA進行配置;
SPI四種工作模式:
注意,在此SPI傳輸數據采用的時軟件模擬時序,跟此前串口采用的是硬件時序控制(寄存器控制)是不一樣的;
軟件模擬時序:軟件模擬時序是通過編程代碼(如循環、延時函數、定時器中斷等)來控制信號的時序關系。開發者需要手動編寫代碼來生成特定的時序波形(如SPI、I2C、PWM等),通常依賴CPU的指令執行時間或定時器來控制信號的切換。
硬件時序控制:硬件時序控制是通過直接操作單片機的硬件寄存器(如定時器、PWM模塊、SPI/I2C控制器等)來生成時序信號。單片機通常內置了專用的硬件模塊,開發者通過配置這些模塊的寄存器來實現特定的時序功能。
在以后編程開發中如何選擇:
選擇軟件模擬時序:
1. 需要快速實現簡單功能。
2.硬件平臺沒有專用硬件模塊。
3.對時序精度要求不高。
選擇硬件時序控制(寄存器操作):
1.需要高速、精確的時序控制。
2.需要節省CPU資源。
3.硬件平臺提供專用硬件模塊。
SPI模式1(軟件模擬時序)
模式1:時鐘極性CPOL=0;時鐘相位CPHA=0;時鐘線SCLK空閑時為低電平,數據線在第一個時鐘沿(上升沿)采樣數據;
代碼示例:
/*SPI模式1軟件模擬時序形參:dat-->發送的數據
*/
u8 SPI_WRbyte(u8 dat)
{SCLK=0;//時鐘線拉低u8 data_rx=0;//讀取數據for(u8 i=0;i<8;i++){SCLK=0;//開始發送數據if(dar & 0x80) MOSI=1;//主機輸出從機輸入else MOSI=0;//主機輸入從機輸出SCLK=1;//一位數據發送完成dat<<=1;data_rx<<=1;//接收數據一定要先左移1位,因為先賦值為1的話,循環8次;最前面的1就會丟失if(MISO) data_rx|=0x01;}SCLK=0;//時鐘線空閑電平為低電平return data_rx;
}
SPI模式2(軟件模擬時序)
模式2:時鐘極性CPOL=0;時鐘相位CPHA=1;時鐘線SCLK空閑時為低電平,數據線在第二個時鐘沿(下降沿)采樣數據;
代碼示例:
/*SPI模式2軟件模擬時序形參:dat-->發送的數據
*/
u8 SPI2_WRbyte(u8 dat)
{SCLK=0;//時鐘線空閑電平為低電平u8 data_rx=0;//讀取數據for(u8 i=0;i<8;i++){SCLK=1;//開始發送數據if(dat & 0x80) MOSI=1;//主機輸出,從機輸入else MOSI=0;//主機輸出,從機輸入SCLK=0;//一位數據發送完成dat<<=1;data_rx<<=1;if(MISO) data_rx|=0x01; }SCLK=0;//時鐘線空閑電平為低電平return data_rx;
}
SPI模式3(軟件模擬時序)
模式3:時鐘極性CPOL=1;時鐘相位CPHA=0;時鐘線SCLK空閑時為高電平,數據線在第一個時鐘沿(下降沿)采樣數據;
代碼示例:
/*SPI模式3軟件模擬時序形參:dat-->發送的數據
*/
u8 SPI3_WRbyte(u8 dat)
{SCLK=1;//時鐘線空閑電平為高電平u8 data_rx=0;//讀取數據for(u8 i=0;i<8;i++){SCLK=1;//開始發送數據if(dat & 0x80) MOSI=1;//主機輸出,從機輸入else MOSI=0;//主機輸出從機輸入SCLK=0;//一位數據發送完成dat<<=1;data_rx<<=1;if(MISO) data_rx|=0x01;}SCLK=1;//時鐘線空閑電平為高電平return data_rx;
}
SPI模式4(軟件模擬時序)
模式4:時鐘極性CPOL=1;時鐘相位CPHA=1;時鐘線SCLK空閑時為高電平,數據線在第二個時鐘沿(上升沿)采樣數據
代碼示例:
/*SPI模式3軟件模擬時序形參:dat-->發送的數據
*/
u8 SPI4_WRbyte(u8 dat)
{SCLK=1;//時鐘線空閑電平為高電平u8 data_rx=0;//讀取數據for(u8 i=0;i<8;i++){SCLK=0;//開始發送數據if(dat & 0x80) MOSI=1;//主機輸出,從機輸入else MISO=0;//主機輸出從機輸入SCLK=1;//一位數據發送完成dat<<=1;data_rx<<=1;if(MISO) data_rx|=0x01;}SCLk=1;//時鐘線空閑電平為高電平return data_rx;
}
OLED顯示屏
OLED顯示屏介紹
屏幕類型:OLED(自發光顯示屏)
屏幕接口:串行接口(只有一根數據線)、并行通訊(多條數據線);
接口協議:IIC或SPI(4線/3線模式);
屏幕尺寸:0.96寸,分辨率:128(列)*64(行);單色屏幕;
屏幕顏色:單色屏幕、16位(bit)屏幕(RGB565)、26位屏幕;
當前開發板:OLED屏幕,串行通訊方式,通訊接口:SPI
電路原理圖:
根據原理圖,查看對應引腳
OLED1腳(GND)——接地(低電平)
OLED2腳(VCC)——接3.3V高電壓(高電平)
OLED3腳(D0)——PB14時鐘線(SCK)
OLED4腳(D1)——PB13數據線(MOSI主機輸出從機輸入)
OLED5腳(RES)——PB12復位線(低電平復位)
OLED6腳(DC)——PB1數據命令選擇腳(低電平0:發送命令;高電平1:發送數據)
OLED7腳(CS)——PA7片選信號線(低電平選中)
OLED顯示屏引腳配置
配置步驟:
1.開時鐘
2.配置GPIO引腳模式
3.上拉下拉
4.將每個引腳進行宏定義,方便操作;
代碼示例:
//宏定義
#define OLED_CS(x) if(x==1){GPIOA->ODR|=1<<7;}\else if(x==0){GPIOA->BRR|=1<<7;}
#define OLED_DC(x) if(x==1){GPIOB->ODR|=1<<1;}\else if(x==0){GPIOB->BRR|=1<<1;}
#define OLED_RES(x) if(x==1){GPIOB->ODR|=1<<12;}\else if(x==0){GPIOB->BRR|=1<<12;}
#define OLED_MOSI(x) if(x==1){GPIOB->ODR|=1<<13;}\else if(x==0){GPIOB->BRR|=1<<13;}
#define OLED_SCLK(x) if(x==1){GPIOB->ODR|=1<<14;}\else if(x==0){GPIOB->BRR|=1<<14;} #include"OLED.h"
/*OLED顯示屏引腳配置函數1腳(GND)——接地(低電平)2腳(VCC)——接3.3V高電壓(高電平)3腳(D0)——PB14時鐘線(SCK)4腳(D1)——PB13數據線(MOSI主機輸出從機輸入)5腳(RES)——PB12復位線(低電平復位)6腳(DC)——PB1數據命令選擇腳(低電平0:發送命令;高電平1:發送數據)7腳(CS)——PA7片選信號線(低電平選中)
*/
void OLED_GPIO_Init(void)
{//開時鐘RCC->APB2ENR|=1<<2;//開啟PA時鐘RCC->APB2ENR|=1<<3;//開啟PB時鐘//配置GPIO引腳模式GPIOB->CRL&=0xffffff0f;GPIOB->CRL|=0x00000030;//PB1GPIOB->CRH&=0xf000ffff;GPIOB->CRH|=0x03330000;//PB12,13,14;GPIOA->CRL&=0x0fffffff;GPIOA->CRL|=0x30000000;//PA7//上拉下拉GPIOA->ODR|=1<<7;//取消選中設備
}
OLED屏幕實現發送1字節數據函數(軟件模擬時序)
分析OLED顯示屏時序接口:
CS片選信號是低電平選中設備
SCLK時鐘線空閑時既可以為高電平也可以為低電平,上升沿為讀取數據,下降沿為發送數據。推斷出時鐘極性既可以為1也可以為0。
當時鐘極性為0時,第一個時鐘沿(上升沿)為采樣讀取數據,相位為0;為SPI工作模式1;
當時鐘極性為1時,第二個時鐘沿(上升沿)為采樣讀取數據,相位為1;為SPI工作模式4;
實現發送一字節數據函數(軟件模擬時序)代碼示例:
/*OLED屏幕SPI模式1發送一字節函數形參: dat-->發送的數據flag-->DC數據命令選擇標志位
當時鐘極性為0時,第一個時鐘沿(上升沿)為采樣讀取數據,相位為0;為SPI工作模式1;
*/
void OLED_Wbyte(u8 dat,u8 flag)
{OLED_CS(0);//片選線低電平選中設備OLED_SCLK(0);//時鐘線空閑電平為低電平OLED_DC(flag);//數據命令選擇線選擇發送命令還是數據for(u8 i=0;i<8;i++){OLED_SCLK(0);//開始發送時間if(dat & 0x80){OLED_MOSI(1);}else OLED_MOSI(0);OLED_SCLK(1);//數據發送完成dat<<=1;}OLED_SCLK(0);//時鐘線空閑電平為低電平OLED_CS(1);//取消選中設備
}
清屏函數
清屏本質:將屏幕緩沖區(顯存)全部填充為0(黑色)或0xFF(白色);
根據SSD1306驅動芯片OLED屏中文參考手冊(第7頁)
通過命令B0h到B7h,設置目標顯示位置的頁起始地址。
通過命令00h到0Fh設置指針的下起始列地址。
通過命令10h到1Fh設置指針的上起始列地址。
尋址方式:頁面尋址,把68行分成了8頁,列不變,依舊時128列。
在頁面尋址下,在顯示RAW讀/寫之后,列地址指針會自動增加1,行地址不會;
代碼示例:
/*OLED屏幕清屏函數形參:dat->清屏的數據
*/
#define OLED_cmd 0//寫入命令
#define OLED_dat 1//寫入數據
void OLED_clear(u8 dat)
{for(u8 i=0;i<8;i++){OLED_Wbyte(0xb0+i,OLED_cmd);//頁起始地址OLED_Wbyte(0x00,OLED_cmd);//下列低起始地址OLED_Wbyte(0x10,OLED_cmd);//上列高起始地址for(u8 j=0;j<128;j++){OLED_Wbyte(dat,OLED_dat);}}
}
OLED屏幕初始化函數
OLED屏幕代碼序列廠家都會提供好的,我們只用復制粘貼在修改即可(這里將清屏函數寫入0xff是起全部點亮效果用來測試OLED屏幕初始化是否有用。正常寫入0,即可);
代碼示例:
/*OLED顯示屏初始化函數
*/
void OLED_Init(void)
{OLED_GPIO_Init();//OLED引腳配置函數OLED_RES(1);//復位Delay_MS(100);OLED_RES(0);Delay_MS(100);OLED_RES(1);//復位OLED_Wbyte(0xAE,OLED_cmd); /*display off*/ OLED_Wbyte(0x00,OLED_cmd); /*set lower column address*/OLED_Wbyte(0x10,OLED_cmd); /*set higher column address*/OLED_Wbyte(0x40,OLED_cmd); /*set display start line*/ OLED_Wbyte(0xB0,OLED_cmd); /*set page address*/ OLED_Wbyte(0x81,OLED_cmd); /*contract control*/ OLED_Wbyte(0xCF,OLED_cmd); /*128*/ OLED_Wbyte(0xA1,OLED_cmd); /*set segment remap*/ OLED_Wbyte(0xA6,OLED_cmd); /*normal / reverse*/ OLED_Wbyte(0xA8,OLED_cmd); /*multiplex ratio*/ OLED_Wbyte(0x3F,OLED_cmd); /*duty = 1/64*/ OLED_Wbyte(0xC8,OLED_cmd); /*Com scan direction*/ OLED_Wbyte(0xD3,OLED_cmd); /*set display offset*/ OLED_Wbyte(0x00,OLED_cmd);OLED_Wbyte(0xD5,OLED_cmd); /*set osc division*/ OLED_Wbyte(0x80,OLED_cmd);OLED_Wbyte(0xD9,OLED_cmd); /*set pre-charge period*/ OLED_Wbyte(0Xf1,OLED_cmd);OLED_Wbyte(0xDA,OLED_cmd); /*set COM pins*/ OLED_Wbyte(0x12,OLED_cmd);OLED_Wbyte(0xdb,OLED_cmd); /*set vcomh*/ OLED_Wbyte(0x30,OLED_cmd);OLED_Wbyte(0x8d,OLED_cmd); /*set charge pump enable*/ OLED_Wbyte(0x14,OLED_cmd);OLED_Wbyte(0xAF,OLED_cmd); /*display ON*/OLED_clear(0xff);//清屏函數寫0xff全部點亮
}
主函數代碼示例:
#include "stdio.h"
#include "OLED.h"
int main()
{OLED_Init();//顯示屏初始化函數printf("各模塊初始化完成\n");while(1){}
}
代碼運行效果:
設置光標函數
x--列? ? ? ? 0列-127列
y--行? ? ? ? 0行-7行(頁面尋址),每8行為一個頁面
代碼示例:
/*
設置光標
形參:x --列0~127y --行0~7(頁面尋址),每8行為一個頁面
*/
void OLED_setpos(u8 x,u8 y)
{OLED_Wbyte(0xb0+(y&0x7),OLED_cmd);//頁地址選擇OLED_Wbyte(0x00|(x&0x0f),OLED_cmd);//設置列低4位OLED_Wbyte(0x10|((x>>4)&0xf),OLED_cmd);//設置列高4位
}
漢字顯示函數
/*顯示漢字*/
//形參:size--漢字的大小,y--光標設置的行,X--關閉設置的列
void OLED_DisplayFont(u8 x,u8 y,u8 size,const u8 *font)
{u8 i=0,j=0;for(i=0;i<size/8;i++)//漢字需要占用的頁面數量{OLED_setpos(x,y+i);for(j=0;j<size;j++)//要顯示的列數{OLED_Wbyte(font[i*size+j],OLED_dat);} }
}
顯示函數首先要在取模軟件取模(取模軟件設置):
取模代碼:
const u8 font1[][16*16/8]=
{{0x00,0xFC,0x04,0x04,0xFC,0x20,0x10,0x4C,0x4B,0x48,0x48,0x48,0xC8,0x08,0x08,0x00,0x00,0x0F,0x04,0x04,0x0F,0x00,0x30,0x48,0x44,0x42,0x42,0x41,0x40,0x40,0x70,0x00},/*"吃",0*/{0x02,0x02,0xE2,0x22,0x22,0xFE,0x22,0x22,0x22,0xFE,0x22,0x22,0xE2,0x02,0x02,0x00,0x00,0x00,0xFF,0x48,0x44,0x43,0x40,0x40,0x40,0x43,0x44,0x44,0xFF,0x00,0x00,0x00},/*"西",1*/{0x00,0x00,0x00,0xFC,0x04,0x04,0xFC,0x04,0x02,0x02,0xFE,0x03,0x02,0x00,0x00,0x00,0x80,0x60,0x18,0x07,0x00,0x00,0x7F,0x20,0x14,0x08,0x31,0x0E,0x30,0x40,0x80,0x00},/*"瓜",2*/{0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",3*/
};
主函數代碼示例:
#include "OLED.h"int main()
{OLED_Init();//顯示屏初始化函數while(1){OLED_DisplayFont(16+16,3,16,font1[0]);OLED_DisplayFont(16+16*2,3,16,font1[1]);OLED_DisplayFont(16+16*3,3,16,font1[2]);OLED_DisplayFont(16+16*4,3,16,font1[3]);}
}
代碼運行結果:
制作不易!喜歡的小伙伴給個小贊贊!喜歡我的小伙伴點個關注!有不懂的地方和需要的資源隨時問我喲!