AT89S52单片机入门

描述

最近捡到一个十几年前学单片机时候的入门单片机:AT89S52。

我觉得我应该做点什么,于是便去翻腾垃圾,又捡到一片DS1302;再从一个玩具上拆下一个32.768KHZ晶振,找到一片0.91寸I2C接口的OLED,还有一个ADC,TLC2543,DS12887……然后就去嘉立创“薅羊毛”打了一个板子开始玩起来

3D效果图:

单片机

实物图:

单片机

以前做什么都是本着能用就好的拿来主义,很少去深度思考人家是怎么写的。比如这个OLED模块,大部分都是用厂家提供的那一套,没有思考是怎么写的,底层是怎么实现的,也就是不重复造轮子。

然而我今天闲了,想要看看轮子怎么造的,最后也要仿造人家的轮子做个零件出来

1、I2C的基础操作函数

首先,看了一下厂家提供的示例,比如51单片机用IO模拟I2C的基础函数有:起始信号、结束信号、等待信号响应。

//起始信号
void I2C_Start(void)
{
OLED_SDA_Set();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Clr();
IIC_delay();
OLED_SCL_Clr();


}


//结束信号
void I2C_Stop(void)
{
OLED_SDA_Clr();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Set();
}


//等待信号响应
void I2C_WaitAck(void) //测数据信号的电平
{
OLED_SDA_Set();
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}

2、I2C的字节写入函数:写入一个字节

//写入一个字节
void Send_Byte(u8 dat)
{
u8 i;
for(i=0;i< 8;i++)
{
OLED_SCL_Clr();//将时钟信号设置为低电平
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写入指令或数据

接下来,在以上的基础函数前提下可以操作OLED了,通过以上的组合可以实现给OLED写入指令或数据。

//发送一个字节
//向SSD1306写入一个字节。
//mode:数据/命令标志 0,表示命令;1,表示数据;
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、利用基础的写入操作可以实现上层次的传送各种指令和数据给OLED的控制器SSD1306了

/*
坐标设置,对于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
}
//关闭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
}
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i< 4;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
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、研究一下传送进去的数据是怎么对应的显示在128*32的点阵上的

单片机

如果上图看不懂,就看下面的文字部分。

这个芯片最大支持12864,我手里用的0.91寸的只有12832,也就是只使用PAGE0~PAGE3。

屏幕的点阵横向看作x,即x列,总数是128列,x∈[0,127];

屏幕的点阵竖向看作y,即y行,总数是032行,y∈[0,031];

而芯片写入是按照页写入的,即y属于PAGE0~PAGE3。

所以,这一点很重要。注意,下面这个厂家提供的操作函数种的y是对应的页的编号。

/*
坐标设置,对于128*32分辨率的OLED:x从127;y从0到3
*/


void OLED_Set_Pos(u8 x, u8 y) ;

于是乎显示一个1,我们要把1图像的每一列的8BIT装进一个页的数据里。如下图所示:

单片机

这是6*8大小的点阵字符,如果从第一行写,那么就是写入第0页,然后将对应列的几个字节按顺序写入即可。

例如我写入左下角,那么对应的就是PAGE3,然后x坐标对应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);

显示效果:

单片机

所以,明白了这一点就可以实现各种自定义的图像了。另外,也可以使用相关的生成工具生成相关的图像编码。

比如我们绘制一个电池的图标:

单片机

将11个字节数据放到一个数组,这样我们可以用循环调用。

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

显示效果如下图所示,怎么样,是不是很赞?现在你是不是学会显示任何图案了?

单片机

接下来,我们造一个函数实现一个点的显示,参数为p(x,y)的绝对坐标。

/*
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<

利用这个函数,我们可以绘制正弦曲线了。

接下来,测试51使用math.h库函数计算正弦波图像,用于显示正弦波。先直接输出一个,然后翻转一个显示。

//正弦波
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();

请注意上面的函数,因为计算过程,正弦函数出来的都是0到1之间的小数,所以要用浮点型,即y为浮点型变量。参与计算的常数也要写作浮点型,免得给优化掉,这样就只能出来一条线了。

同样,如果更改周期参数,即可实现不同周期的正弦波显示。

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();
}
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分