最近撿到一個十幾年前學(xué)單片機(jī)時候的入門單片機(jī):AT89S52。
我覺得我應(yīng)該做點什么,于是便去翻騰垃圾,又撿到一片DS1302;再從一個玩具上拆下一個32.768KHZ晶振,找到一片0.91寸I2C接口的OLED,還有一個ADC,TLC2543,DS12887……然后就去嘉立創(chuàng)“薅羊毛”打了一個板子開始玩起來
3D效果圖:
實物圖:
以前做什么都是本著能用就好的拿來主義,很少去深度思考人家是怎么寫的。比如這個OLED模塊,大部分都是用廠家提供的那一套,沒有思考是怎么寫的,底層是怎么實現(xiàn)的,也就是不重復(fù)造輪子。
然而我今天閑了,想要看看輪子怎么造的,最后也要仿造人家的輪子做個零件出來
1、I2C的基礎(chǔ)操作函數(shù)
首先,看了一下廠家提供的示例,比如51單片機(jī)用IO模擬I2C的基礎(chǔ)函數(shù)有:起始信號、結(jié)束信號、等待信號響應(yīng)。
//起始信號
void I2C_Start(void)
{
OLED_SDA_Set();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Clr();
IIC_delay();
OLED_SCL_Clr();
}
//結(jié)束信號
void I2C_Stop(void)
{
OLED_SDA_Clr();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Set();
}
//等待信號響應(yīng)
void I2C_WaitAck(void) //測數(shù)據(jù)信號的電平
{
OLED_SDA_Set();
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}
2、I2C的字節(jié)寫入函數(shù):寫入一個字節(jié)
//寫入一個字節(jié)
void Send_Byte(u8 dat)
{
u8 i;
for(i=0;i< 8;i++)
{
OLED_SCL_Clr();//將時鐘信號設(shè)置為低電平
if(dat&0x80)//將dat的8位從最高位依次寫入
{
OLED_SDA_Set();
}
else
{
OLED_SDA_Clr();
}
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
dat< <=1;
}
}
3、給OLED寫入指令或數(shù)據(jù)
接下來,在以上的基礎(chǔ)函數(shù)前提下可以操作OLED了,通過以上的組合可以實現(xiàn)給OLED寫入指令或數(shù)據(jù)。
//發(fā)送一個字節(jié)
//向SSD1306寫入一個字節(jié)。
//mode:數(shù)據(jù)/命令標(biāo)志 0,表示命令;1,表示數(shù)據(jù);
void OLED_WR_Byte(u8 dat,u8 mode)
{
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
if(mode){Send_Byte(0x40);}
else{Send_Byte(0x00);}
I2C_WaitAck();
Send_Byte(dat);
I2C_WaitAck();
I2C_Stop();
}
4、利用基礎(chǔ)的寫入操作可以實現(xiàn)上層次的傳送各種指令和數(shù)據(jù)給OLED的控制器SSD1306了
/*
坐標(biāo)設(shè)置,對于128*32分辨率的OLED:x從127;y從0到3
*/
void OLED_Set_Pos(u8 x, u8 y)
{
OLED_WR_Byte(0xb0+y,OLED_CMD);
OLED_WR_Byte(((x&0xf0) > >4)|0x10,OLED_CMD);
OLED_WR_Byte((x&0x0f),OLED_CMD);
}
//開啟OLED顯示
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
}
//關(guān)閉OLED顯示
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
}
//清屏函數(shù),清完屏,整個屏幕是黑色的!和沒點亮一樣!!!
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i< 4;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //設(shè)置頁地址(0~7)
OLED_WR_Byte (0x00,OLED_CMD); //設(shè)置顯示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //設(shè)置顯示位置—列高地址
for(n=0;n< 128;n++)OLED_WR_Byte(0,OLED_DATA);
} //更新顯示
}
//初始化
void OLED_Init(void)
{
OLED_WR_Byte(0xAE,OLED_CMD); /*display off*/
OLED_WR_Byte(0x00,OLED_CMD); /*set lower column address*/
OLED_WR_Byte(0x10,OLED_CMD); /*set higher column address*/
OLED_WR_Byte(0x00,OLED_CMD); /*set display start line*/
OLED_WR_Byte(0xB0,OLED_CMD); /*set page address*/
OLED_WR_Byte(0x81,OLED_CMD); /*contract control*/
OLED_WR_Byte(0xff,OLED_CMD); /*128*/
OLED_WR_Byte(0xA1,OLED_CMD); /*set segment remap*/
OLED_WR_Byte(0xA6,OLED_CMD); /*normal / reverse*/
OLED_WR_Byte(0xA8,OLED_CMD); /*multiplex ratio*/
OLED_WR_Byte(0x1F,OLED_CMD); /*duty = 1/32*/
OLED_WR_Byte(0xC8,OLED_CMD); /*Com scan direction*/
OLED_WR_Byte(0xD3,OLED_CMD); /*set display offset*/
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0xD5,OLED_CMD); /*set osc division*/
OLED_WR_Byte(0x80,OLED_CMD);
OLED_WR_Byte(0xD9,OLED_CMD); /*set pre-charge period*/
OLED_WR_Byte(0x1f,OLED_CMD);
OLED_WR_Byte(0xDA,OLED_CMD); /*set COM pins*/
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0xdb,OLED_CMD); /*set vcomh*/
OLED_WR_Byte(0x40,OLED_CMD);
OLED_WR_Byte(0x8d,OLED_CMD); /*set charge pump enable*/
OLED_WR_Byte(0x14,OLED_CMD);
OLED_Clear();
OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/
}
5、研究一下傳送進(jìn)去的數(shù)據(jù)是怎么對應(yīng)的顯示在128*32的點陣上的
如果上圖看不懂,就看下面的文字部分。
這個芯片最大支持12864,我手里用的0.91寸的只有12832,也就是只使用PAGE0~PAGE3。
屏幕的點陣橫向看作x,即x列,總數(shù)是128列,x∈[0,127];
屏幕的點陣豎向看作y,即y行,總數(shù)是032行,y∈[0,031];
而芯片寫入是按照頁寫入的,即y屬于PAGE0~PAGE3。
所以,這一點很重要。注意,下面這個廠家提供的操作函數(shù)種的y是對應(yīng)的頁的編號。
/*
坐標(biāo)設(shè)置,對于128*32分辨率的OLED:x從127;y從0到3
*/
void OLED_Set_Pos(u8 x, u8 y) ;
于是乎顯示一個1,我們要把1圖像的每一列的8BIT裝進(jìn)一個頁的數(shù)據(jù)里。如下圖所示:
這是6*8大小的點陣字符,如果從第一行寫,那么就是寫入第0頁,然后將對應(yīng)列的幾個字節(jié)按順序?qū)懭爰纯伞?/p>
例如我寫入左下角,那么對應(yīng)的就是PAGE3,然后x坐標(biāo)對應(yīng)0,1,2,3,4,5。
OLED_Set_Pos(0,3);
OLED_WR_Byte(0x00,OLED_DATA);
OLED_Set_Pos(1,3);
OLED_WR_Byte(0x00,OLED_DATA);
OLED_Set_Pos(2,3);
OLED_WR_Byte(0x42,OLED_DATA);
OLED_Set_Pos(3,3);
OLED_WR_Byte(0x7F,OLED_DATA);
OLED_Set_Pos(4,3);
OLED_WR_Byte(0x40,OLED_DATA);
OLED_Set_Pos(5,3);
OLED_WR_Byte(0x00,OLED_DATA);
顯示效果:
所以,明白了這一點就可以實現(xiàn)各種自定義的圖像了。另外,也可以使用相關(guān)的生成工具生成相關(guān)的圖像編碼。
比如我們繪制一個電池的圖標(biāo):
將11個字節(jié)數(shù)據(jù)放到一個數(shù)組,這樣我們可以用循環(huán)調(diào)用。
unsigned char temp[11]={0x42,0xFF,0x81,0xBD,0xBD,0xBD,0xBD,0xBD,0x81,0xFF,0x18};
考慮到剛才顯示1的那個位置有鼓包,我們將其向右偏移20個像素點放置,同樣放在第三頁顯示。
for(i=0;i< 11;i++)
{
OLED_Set_Pos(i+20,3);
OLED_WR_Byte(temp[i],OLED_DATA);
}
delay_ms(2000);
顯示效果如下圖所示,怎么樣,是不是很贊?現(xiàn)在你是不是學(xué)會顯示任何圖案了?
接下來,我們造一個函數(shù)實現(xiàn)一個點的顯示,參數(shù)為p(x,y)的絕對坐標(biāo)。
/*
x:0~127;y:0~31
*/
void setPixel(int x, int y)
{
unsigned char page;
unsigned char bits;
page = y / 8;
bits = y % 8;
OLED_Set_Pos(x,page);
OLED_WR_Byte(1<
利用這個函數(shù),我們可以繪制正弦曲線了。
接下來,測試51使用math.h庫函數(shù)計算正弦波圖像,用于顯示正弦波。先直接輸出一個,然后翻轉(zhuǎn)一個顯示。
//正弦波
for(i=0;i< 128;i++)
{
y=16.0+sin(i*3.1415926/32.0)*16.0;
j=(unsigned int)(y);
setPixel(i,j);
}
OLED_Clear();
//正弦波
for(i=0;i< 128;i++)
{
y=16.0-sin(i*3.1415926/32.0)*16.0;
j=(unsigned int)(y);
setPixel(i,j);
}
OLED_Clear();
請注意上面的函數(shù),因為計算過程,正弦函數(shù)出來的都是0到1之間的小數(shù),所以要用浮點型,即y為浮點型變量。參與計算的常數(shù)也要寫作浮點型,免得給優(yōu)化掉,這樣就只能出來一條線了。
同樣,如果更改周期參數(shù),即可實現(xiàn)不同周期的正弦波顯示。
for(k=8;k<=64;k=k*2)
{
for(i=0;i< 128;i++)
{
y=16.0-sin(i*3.1415926/(float)k)*16.0;
j=(unsigned int)(y);
setPixel(i,j);
}
OLED_Clear();
}
-
單片機(jī)
+關(guān)注
關(guān)注
6030文章
44489瀏覽量
631980 -
AT89S52
+關(guān)注
關(guān)注
9文章
358瀏覽量
87748 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4277瀏覽量
62323 -
oled模塊
+關(guān)注
關(guān)注
0文章
4瀏覽量
2813
發(fā)布評論請先 登錄
相關(guān)推薦
評論