admin 发表于 2023-5-22 23:26:47

一切都要从我捡到一个51单片机说起

一切都要从我捡到一个51单片机说起


深度再学习驱动OLED最近捡到一个十几年前学单片机时候的入门单片机: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 ONOLED_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 OFFOLED_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的点阵上的

如果上图看不懂,就看下面的文字部分。
这个芯片最大支持128*64,我手里用的0.91寸的只有128*32,也就是只使用PAGE0~PAGE3。
屏幕的点阵横向看作x,即x列,总数是128列,x∈;
屏幕的点阵竖向看作y,即y行,总数是032行,y∈;
而芯片写入是按照页写入的,即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={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,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<<bits,OLED_DATA);}
利用这个函数,我们可以绘制正弦曲线了。
接下来,测试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();}

页: [1]
查看完整版本: 一切都要从我捡到一个51单片机说起