历史上的今天
今天是:2024年11月25日(星期一)
2021年11月25日 | STM8S驱动OLED12864
2021-11-25 来源:eefocus

这种OLED的液晶屏似乎很受电子爱好者的欢迎,原因大概是:
①这种屏小巧玲珑,同样是128*64点阵的液晶屏,这种屏的体积比以前那种LCD12864小了四分之一,作为DIY手表的屏幕刚刚好。
②功耗低,由于OLED是有机发光材料制作成的,没有使用背光,所以功耗比使用LED作为背光的液晶屏,功耗要低很多。所以这块液晶屏适合用于手表之类的手持设备。
③可以选择的接口很多。接口和使用的液晶屏驱动芯片有关,大多数OLED12864使用的驱动芯片是SSD1306,这款驱动芯片提供给MCU的接口有5种,如下图。本文使用I2C接口来驱动OLED,主要是因为I2C占用MCU的IO少,当然用SPI接口驱动OLED的也很多。
使用I2C接口时,D0脚作为I2C的SCLK时钟信号线,D1,D2作为SDA数据信号线。D/C#引脚做为地址设定引脚,该引脚接地,OLED器件地址为0x78,该引脚上拉,OLED器件地址为0x7A。
当使用I2C时,SCLK和SDA引脚要加上拉电阻,关于上拉电阻要注意:
①如果I2C的速度很低,使用10K的上拉电阻是可以的,这个低速大概是100KHz
②如果I2C的速度很高,要达到300KHz左右,这个上拉电阻的阻值应该为4.7K
在淘宝上买的OLED模块,有的直接焊接了10K的上拉电阻,这样对于需要I2C高速通信的用户来说,很不方便,不查看电路还以为是程序出了问题,很浪费时间。今天我就遇到了这个问题,查找了好长时间,手头上又没4.7K的电阻,只好又并了个10K的电阻。
上图是SSD1306的显存分布,y轴范围是0~7,分为8个页,x轴坐标是0~127.

上图是对OLED写入地址的设定.
SSD1306也有一些控制命令,详情请查看SSD1306的数据手册。
由于我的STM8L-DISCOVERY的芯片烧坏掉了,所以没办法再继续用STM8L来写程序了。去淘宝买了块STM8S103F3的最小系统板,使用STM8L-DISCOVERY上的SWIM进行仿真调试。
第一次遇到,超过2000字不能发表的情况,大概是程序太长了。我把程序上传到GitHub了,刚刚摸索着使用这个国外的网站进行代码托管,由于是全英文的网站,还不是很会用。
/*硬件连接*/
// PB4--SCL PB5--SDA
/****************************************************************************************
*开发环境:IAR for stm8 v6.5.3
*硬件平台:STM8S103F3
*功能说明:通过硬件I2C等待的方法,
*作 者:茗风
****************************************************************************************/
#include"iostm8s103f3.h"
#include"stdbool.h"
#include"stdint.h"
/******************************************************************************************************
* 名 称: uint8_t I2C_ReadOneByteDataFromSlave(uint8_t address)
* 功 能:从I2C从设备中读取一字节的数据
* 入口参数:address:读取数据的寄存器地址
* 出口参数:返回一个从I2C从设备指定地址读到的数据
* 说 明:
* 范 例:无
******************************************************************************************************/
uint8_t I2C_ReadOneByteDataFromSlave(uint8_t slave_address,uint8_t address)
{
volatile uint8_t t;
//----------I2C起始信号--------------
I2C_CR2_START=1;//产生一个起始条件
while(!(I2C_SR1_SB==1));//读SR1寄存器,清除SB标志位
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
//-------发送写I2C从器件地址---------
I2C_DR=slave_address;//发送从设备地址
while(!(I2C_SR1_ADDR==1));//读SR1寄存器,清除ADDR标志位
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
if(I2C_SR3_TRA==0)return 1;//读SR3寄存器,清除ADDR标志位
// 0: Data bytes received
// 1: Data bytes transmitted
//-----写I2C从器件寄存器地址--------
I2C_DR=address;
while(!(I2C_SR1_BTF==1));//等待地址发送完成
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
//--------I2C重复起始信号-----------
I2C_CR2_START=1;//重复产生一个起始条件
while(!(I2C_SR1_SB==1));//读SR1寄存器,清除SB标志位
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
//-------发送读I2C从器件地址---------
I2C_DR=0xD1;//发送从设备地址
while(!(I2C_SR1_ADDR==1));//读SR1寄存器,清除ADDR标志位
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
if(I2C_SR3_TRA==1)return 1;//读SR3寄存器,清除ADDR标志位
//-------------停止信号-------------
I2C_CR2_ACK=0;//ACK位控制着ACK信号,此位为0可以产生一个NOACK信号
I2C_CR2_STOP=1;
//-------------等待接收到数据-------------
while(!(I2C_SR1_RXNE==1));//等待地址发送完成
//-------------读取数据-------------
t=I2C_DR;
return t;
}
/******************************************************************************************************
* 名 称:void I2C_WriteOneByteDataToSlave(uint8_t address,uint8_t dat)
* 功 能:写入一字节的数据到I2C设备中
* 入口参数:address:写入的数据存储地址 dat:待写入的数据
* 出口参数:无
* 说 明: 通过MSTM8L硬件写入I2C设备一个字节的数据
* 范 例:无
******************************************************************************************************/
uint8_t I2C_WriteOneByteDataToSlave(uint8_t slave_address,uint8_t address,uint8_t dat)
{
volatile uint8_t t;
I2C_CR2_ACK=1;
//----------I2C起始信号--------------
I2C_CR2_START=1;//产生一个起始条件
while(!(I2C_SR1_SB==1));
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
I2C_DR=slave_address;
//--------写I2C从器件地址-----------
while(!(I2C_SR1_ADDR==1));
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
if(I2C_SR3_TRA==0)return 1;//读SR3寄存器,清除ADDR标志位
//-----写I2C从器件寄存器地址--------
while(!(I2C_SR1_TXE==1));
I2C_DR=address;
//-------写I2C数据到寄存器中--------
while(!(I2C_SR1_TXE==1));
I2C_DR=dat;
while(!(I2C_SR1_TXE==1));
while(!(I2C_SR1_BTF==1));
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
//-------------停止信号-------------
I2C_CR2_STOP=1;
return 0;
}
/******************************************************************************************************
* 功 能:从I2C从设备读取多个字节数据
* 入口函数:
* 出口函数:
* 说 明:
* 范 例:
* 日 期:
******************************************************************************************************/
uint8_t I2C_ReadMultiBytesFromSlave(uint8_t slave_address,uint8_t address,uint8_t *rxbuf,uint8_t len)
{
volatile uint8_t i=0;
if(len==0)return 1;//如果写入字节长度为0退出
I2C_CR2_ACK=1;
//----------I2C起始信号--------------
I2C_CR2_START=1;//产生一个起始条件
while(!(I2C_SR1_SB==1));//读SR1寄存器,清除SB标志位
//-------发送写I2C从器件地址---------
I2C_DR=slave_address;//发送从设备地址
while(!(I2C_SR1_ADDR==1));//读SR1寄存器,清除ADDR标志位
if(I2C_SR3_TRA==0)return 1;//读SR3寄存器,清除ADDR标志位
// 0: Data bytes received
// 1: Data bytes transmitted
//-----写I2C从器件寄存器地址--------
I2C_DR=address;
while(!(I2C_SR1_BTF==1));//等待地址发送完成
//--------I2C重复起始信号-----------
I2C_CR2_START=1;//重复产生一个起始条件
while(!(I2C_SR1_SB==1));//读SR1寄存器,清除SB标志位
//-------发送读I2C从器件地址---------
I2C_DR=0xD1;//发送从设备地址
while(!(I2C_SR1_ADDR==1));//读SR1寄存器,清除ADDR标志位
if(I2C_SR3_TRA==1)return 1;//读SR3寄存器,清除ADDR标志位
//-------------读取数据-------------
if(len>1)
{
for( i=len;i>1;i-- )
{
while(!(I2C_SR1_RXNE==1));//等待I2C_DR接收到数
*rxbuf++ = I2C_DR;
}
}
//-------------停止信号-------------
I2C_CR2_ACK=0;//ACK位控制着ACK信号,此位为0可以产生一个NOACK信号
I2C_CR2_STOP=1;
while(!(I2C_SR1_RXNE==1));//等待I2C_DR接收到数
*rxbuf++ = I2C_DR;
return 0;
}
/******************************************************************************************************
* 名 称:IIC_init()
* 功 能:初始化I2C,系统主频位8MHz,I2C通信速度333KHz
* 入口 参数:无
* 出口 参数:无
* 说 明:PB4--SCL PB5--SDA
* 范 例:无
******************************************************************************************************/
void I2C_Init(void)
{
//----打开IIC外设时钟----
I2C_CR1_PE=0;
I2C_CR2_ACK=1;
//----I2C输入时钟频率选择----
I2C_FREQR_FREQ=0x08;//8MHz
/* The allowed range is between 1 MHz and 16 MHz
000000: not allowed
000001: 1 MHz
000010: 2 MHz
...
010000: 16 MHz */
//----配置时钟控制寄存器----
I2C_CCRH=0;
I2C_CCRH_F_S=1; //Fast mode I2C
I2C_CCRH_DUTY=0;
/* If DUTY = 0:
Period(I2C) = 3* CCR * tMASTER
thigh = CCR * tMASTER
tlow = 2 * CCR * tMASTER*/
I2C_CCRL=7; //SCL高电平时间配置
//I2C的SCK时钟设置为400KHz,则SCK周期为2.5us 2.5us/0.125/3=7
//因为I2C_FREQR_FREQ=0x08,即I2C输入时钟频率为8M,周期为0.125us
//CCR=7时,SCK的低电平时间为2*tlow=2*7*0.125us=1.75us,SCk高电平时间为thigh=7*0.125us=0.875us
//所以CCR=7时,SCK输出频率为380KHz
//----配置上升时间寄存器----
I2C_TRISER_TRISE=5;//in standard mode, the maximum allowed SCL rise time is 1000 ns.
//1 us / 0.125 us = 8
//+1
I2C_CR1_PE=1;//
}
#define Write_CMD_TO_OLED12864(byte) I2C_WriteOneByteDataToSlave(0x78,0x00,byte)
#define Write_DAT_TO_OLED12864(byte) I2C_WriteOneByteDataToSlave(0x78,0x40,byte)
/************************************6*8的点阵************************************/
const uint8_t ASCII_6X8[92][6] = //水平寻址
{
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // sp
{ 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 }, // !
{ 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 }, // "
{ 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // #
{ 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // $
{ 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 }, // %
{ 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 }, // &
{ 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 }, // '
{ 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 }, // (
{ 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 }, // )
{ 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 }, // *
{ 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
{ 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 }, // ,
{ 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 }, // -
{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // .
{ 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 }, // /
{ 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0
{ 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1
{ 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2
{ 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3
{ 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4
{ 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5
{ 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6
{ 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7
{ 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
{ 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9
{ 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 }, // :
史海拾趣
|
对于不同的单片机开发程序差别不大,你要是在89c51的程序语言上花费太多,而感觉在dsp上的程序语言不能学懂的话,我向你的精力花错了方向了。如果你学习了数据结构上的程序描述语言,就发现所有的程序结构都可以用一种标准语言来描述,而不同的 ...… 查看全部问答> |
|
在s3c2410下程序是一定要下到0x30200000这里么?我自己修改了一个ucos的程序,下到芯片,但是指定的地址是从0开始,结果跑错了,然后就一直不能再下载其他程序了,请问应该怎么修正这个错误。 原因可能是:我查了下,下载程序时总是 ...… 查看全部问答> |
|
我想利用FILEMON来开发一个实时监控程序,想在驱动中使用loadlibray函数来加载DLL,但WINXPDDK总是报winbase.h文件出错,好像是和ntddk.h有重复的宏定义。请大家帮帮忙!以下是错误报告: 1>g:\\winddk\\inc\\crt\\winbase.h(293) : error C2061: s ...… 查看全部问答> |
|
我在用PB定制OS时候,想把映像模拟出来,但是每当运行时候都出现 The specified CE boot image could not be loaded. Your virt ...… 查看全部问答> |
|
使用片式磁珠和片式电感的原因:是使用片式磁珠还是片式电感主 要还在于应用。在谐振电路中需要使用片式电感。而需要消除不需要的EMI噪声时,使用片式磁珠是最佳的选择。 1。磁珠的单位是欧姆,而不是亨特,这一点要特别注意。因为磁珠的单位是按 ...… 查看全部问答> |




