历史上的今天
返回首页

历史上的今天

今天是: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 },   // :

推荐阅读

史海拾趣

Electronic Concepts Inc公司的发展小趣事

随着市场的竞争加剧,ECI意识到只有不断创新才能在行业中立足。在总经理张女士的领导下,公司投入大量资源进行研发,成功推出了一系列具有竞争力的新产品。其中,一款集成了先进算法的智能传感器,因其高精度和低功耗特性,在市场上大受欢迎。这一创新不仅提升了ECI的品牌形象,也为公司带来了丰厚的利润。

Advanced Electronic Packaging公司的发展小趣事

随着国内市场的逐渐饱和,Advanced Electronic Packaging公司开始将目光投向海外市场。公司积极参与国际电子展会和技术交流活动,展示其先进的封装技术和优质产品。同时,公司还加大了对海外市场的宣传力度,通过线上线下相结合的方式提升品牌知名度和影响力。在不懈的努力下,公司的产品和服务逐渐赢得了国际市场的认可和青睐,海外市场份额逐年攀升。

AIM公司的发展小趣事

AIM公司自创立以来,便以其前瞻性的视野和独特的技术理念,在电子行业中崭露头角。创始团队凭借对市场的敏锐洞察和对技术的深刻理解,将公司定位为提供创新解决方案的引领者。在创立初期,AIM便投入大量资源进行技术研发,逐渐在行业中建立了自己的技术优势和品牌形象。

Abbatron公司的发展小趣事

在电子行业的激烈竞争中,Abbatron公司以其创新的技术赢得了市场的认可。某年,公司研发团队成功开发出了一款新型的高效能芯片,这款芯片不仅性能卓越,而且功耗极低,引起了业界的广泛关注。通过这一技术突破,Abbatron公司在市场上占据了有利地位,并逐渐成为了行业内的佼佼者。

ETL semiconductor公司的发展小趣事

英特尔(Intel)的成立与发展与微处理器的发明密不可分。在20世纪70年代初,英特尔的工程师们开始研发一种能够将计算机的中央处理器(CPU)集成到单一芯片上的技术。经过数年的努力,他们成功推出了世界上第一款微处理器——Intel 4004。这一创新不仅改变了计算机行业,也为英特尔的崛起奠定了坚实基础。

DBS Microwave Inc公司的发展小趣事

DBS Microwave Inc公司起源于XXXX年,当时由几位志同道合的电子工程师创立。他们共同意识到,随着无线通信技术的飞速发展,微波器件的市场需求将会急剧增长。于是,他们凭借丰富的专业知识和对市场趋势的敏锐洞察,决定专注于研发和生产高质量的微波射频器件。起初,公司面临着资金紧张、市场竞争激烈等诸多挑战,但创始人团队凭借坚韧不拔的精神和对技术的执着追求,逐步在市场上崭露头角。

问答坊 | AI 解惑

现在学习单片机是在学习它的控制理论

对于不同的单片机开发程序差别不大,你要是在89c51的程序语言上花费太多,而感觉在dsp上的程序语言不能学懂的话,我向你的精力花错了方向了。如果你学习了数据结构上的程序描述语言,就发现所有的程序结构都可以用一种标准语言来描述,而不同的 ...…

查看全部问答>

安规仪的检测及校准

安规仪器的检验及校准方法…

查看全部问答>

服务器通过GPRS上外网是否可行

一台机器A通过GPRS上外网,公网上的机器B在知道机器A的IP地址的情况下,是否能够与机器A建立TCP连接?…

查看全部问答>

s3c2410的问题

在s3c2410下程序是一定要下到0x30200000这里么?我自己修改了一个ucos的程序,下到芯片,但是指定的地址是从0开始,结果跑错了,然后就一直不能再下载其他程序了,请问应该怎么修正这个错误。    原因可能是:我查了下,下载程序时总是 ...…

查看全部问答>

如何在驱动中加载DLL

我想利用FILEMON来开发一个实时监控程序,想在驱动中使用loadlibray函数来加载DLL,但WINXPDDK总是报winbase.h文件出错,好像是和ntddk.h有重复的宏定义。请大家帮帮忙!以下是错误报告: 1>g:\\winddk\\inc\\crt\\winbase.h(293) : error C2061: s ...…

查看全部问答>

PB内核定制中出现的问题

我在用PB定制OS时候,想把映像模拟出来,但是每当运行时候都出现 The   specified   CE   boot   image   could   not   be   loaded. Your   virt ...…

查看全部问答>

VS2005中调试wince程序时,为什么进不去断点。

vs2005中开发win32程序很正常,但是下载到目标机中调试wince程序时,就是进不去断点…

查看全部问答>

什么叫地址对准?

所谓地址对准的意义是,比如32位双字地址的最低两位是00,这样可以从32位存储器数据总线一次读出。 请解释上面那句话是什么意思,不明白,什么叫地址对准…

查看全部问答>

高亮度LED寿命的致命问题散热

replyreload += \',\' + 702864;Timson,如果您要查看本帖隐藏内容请回复…

查看全部问答>

PCB电路设计中磁珠的选用

使用片式磁珠和片式电感的原因:是使用片式磁珠还是片式电感主 要还在于应用。在谐振电路中需要使用片式电感。而需要消除不需要的EMI噪声时,使用片式磁珠是最佳的选择。 1。磁珠的单位是欧姆,而不是亨特,这一点要特别注意。因为磁珠的单位是按 ...…

查看全部问答>