Uno 驱动SPI OLED中文显示-Arduino中文社区 - Powered by Discuz! Archiver

希岩 发表于 2017-2-27 13:23

Uno 驱动SPI OLED中文显示

本帖最后由 希岩 于 2017-2-27 13:23 编辑

采用1.3寸带中文字库的OLED白屏,显示一些中文字符。
所需材料:杜邦线、Arduino UNO R3、1.3寸OLED屏(带中文芯片)
接线:代码中包含。
所显示结果如图所示。


#include <Arduino.h>
#include <avr/pgmspace.h>
   
const unsigned char table[]PROGMEM ={"清风过境,带着浓浓的秋意催开了将绽未绽的寒菊,逗笑了娃娃的笑脸,秋后,枝头缓缓飘落的秋叶铺满了整条不算宽敞的小路,偶尔三两片落叶调皮的跳着名为永不再见的舞曲,眨眼间,眉宇便染上淡淡的哀愁。\
  雨后的天总是带有微微的潮意,雨后的天空拥有着不同以往的清新的气息,独自一个人漫步在那落叶铺就的叶之路上,一路上听着那声音,一切仿佛都安静下来了,在这里,能感受到每一朵的呼吸声和水滴一滴一滴的低落声,这里的一切都静得那般不真实,哪怕不真,也不愿意打破心中这片刻的宁静,只想在这里一世长安,静观浮生。\
  轻捧着盛放的花朵,寻一僻静之地,嗅着那清浅的花香,斜依着参天古树,将手中的百花散落在古树旁,身下是枯软的草地,那种舒适的感觉不禁想让人快点进入梦乡。\
  随手拾起手边的一片残叶,发现残叶上那清晰的纹路,是如此的浑然天成,那是用怎样的笔才可以勾勒出这清晰的细枝末节,我们这个繁华的世界与这个微妙的世界到底有什么微妙的关系呢?\
  枯藤、老树、昏鸦,夕阳的余晖静静地斜映而下,那轮将隐未隐的红日,在这般温暖和安静的环境里,竟然明白了一些许久未明白的,有时,不是我们不够好,只是我们不愿意的往前看,我们只愿意在那个封闭的世界里,在原地踏步,总是在自怨自艾.\
  在逐渐的成长中是我们给自己给自己造成了过多的枷锁,终于到自己不能承受的地步,只得缩在一方天地,苟延残喘的活着.\
  我们总是在自己逼自己,总是在自己的世界蒙上一层薄纱,看不清前路的自己,总是在一圈圈的打转……\
  人,聪明吧,却又是极笨的,有时间一个聪明的人倒不如一个愚人活的自在,活的快乐无忧,我们无时无刻的都在羡慕别人,却怎么也不肯还自己一个清晰的世界和生活,固执的活着那个迷茫的世界,受尽苦楚.\
  有些人,有些事,该散就散,毕竟天下无不散的宴席,又或者说这次的分离,这次的失望与挫败,都是为了下一次最美的相逢。\
  与其苦苦的困在曾经的回忆里,倒不如抬头望望天,垂眸赏三两只锦鲤,与月常伴,画船听雨眠。\
  白光过隙,花开半夏,其实,人的一生极其短暂,与其为难自己,倒不如做一个手执书卷,阅经抚琴的闲雅之人,于落花深处,静候半夏"};






/****************************************************************************   
//!!本程序只供学习使用,未经作者许可,不得用于其它任何用途
//作    者   : 希岩
//生成日期   : 2017-2-27
//功能描述   : 字库版 OLED SPI接口演示例程(Atmega328系列)
//说    明:
               OLED驱动芯片:SSH1106
               单片机      : ATMEGA328P-PU
               晶振      : 片外16Mhz石英晶振
               工作频率    : 8Mhz
//========================以下为OLED显示所用到的接口===========================
//            GND       电源地
//            VCC       接5V或3.3v电源
//            CLK       数字引脚13 (PB5)
//            MOSI      数字引脚11 (PB3)
//            DC      数字引脚8(PB0数据/指令)
//            CS1       数字引脚10 (PB2OLED片选)
//            FSO       数字引脚12 (PB4MISO)
//            CS2       数字引脚9(PB1字库片选)            

********************************************************************************/
#include <Arduino.h>
#include <avr/pgmspace.h>
#include "chartable.c"

//数据类型宏定义
typedef unsigned char       uint08;
typedef signed   char       sint08;
typedef unsigned int      uint16;
typedef signed   int      sint16;
typedef unsigned long       uint32;
typedef const unsigned char cuint08;



#define CS1LPORTB&=0xfb      //CS1低
#define CS1HPORTB|=0x04      //CS1高
#define CS2LPORTB&=0xfd      //CS2低
#define CS2HPORTB|=0x02      //CS2高
#define DCL() PORTB&=0xfe;   //DC=0 PB0=0
#define DCH() PORTB|=0x01;   //DC=1 PB0=1

//-----------------------------------------------------------------------------
//初始化时钟-------------------------------------------------------------------
void Init_Clock(void)
{
//OSCCAL = 0x96;               //时钟矫正至8.0Mhz
CLKPR= 0x80;                   //时钟分频器使能CLKPR_CLKPCE
CLKPR= 0x01;                   //系统时钟2分频,p39(0>1;1>2;...8>256)
}

//-----------------------------------------------------------------------
void Init_Port(void)
{ DDRB   = (1<<DDB2)|(1<<DDB1)|(1<<DDB0);            //PB2 PB1作为片选输出
// DDRB|= 0x01;                           //DC输出
PORTB= 0xff;
// PORTB= 0xff;
}
//初始化定时器------------------------------------------------------------
void Init_Timer0(void)
{ PRR =0b01001001;         //TC1,2抑制,adc抑制,page34,功耗抑制寄存器
TCCR0A= 0x00;          //正常模式
TCCR0B= 0x04;          //预分频器256分频,每次计数32us,page91
}
void SPI_High_Rate(void)
{
//最高操作速率不能高于25Mbps
SPCR &= 0x5C;   //BR=busclk/(SPPR *SPR )=8M/4=2M ,page147
   //SPCR = (1<<SPE)|(1<<MSTR);            
}
//SPI主机初始化,低速模式125k==========================================================
void SPI_Init_M0(void)
{                                          //SPI模式0
DDRB|= (1<<DDB3)|(1<<DDB5);            //设置MOSI 和SCK 为输出,其他为输入
SPCR = 0b01010001;                     //使能SPI 主机模式,时钟模式为下降沿,结束沿采样,设置时钟速率为fck/16
}
//SPI主机初始化,低速模式125k==========================================================
void SPI_Init_M3(void)
{                                           //SPI模式3
DDRB|= (1<<DDB3)|(1<<DDB5);               //设置MOSI 和SCK 为输出,其他为输入
SPCR = 0b01011001;                        //使能SPI 主机模式,时钟模式为下降沿,结束沿采样,设置时钟速率为fck/16
}

//======================================================================
//延时函数
void delayms(uint16 u16count)
{uint16 count,i;
for(i=0;i<u16count;i++)
{count=TCNT0;                           //读取计数器0
   while((TCNT0-count)<32) asm("wdr");      //每次约1ms
}
}

//SPI数据传输,采用轮询=========================================================
uint08 SPI_Tran_Byte(uint08 data)
{
SPDR = data;                              //启动数据传输
while(!(SPSR & (1<<SPIF)));               //等待传输结束
return SPDR;                               //接收字符
}

//写指令到oled模块==============================================================
void tran_cmd_oled(uint08 data)   
{
DCL();
_NOP();
(void)SPI_Tran_Byte(data);
}

//写数据到oled模块==============================================================
void tran_dat_oled(uint08 data)   
{
DCH();
_NOP();      
(void)SPI_Tran_Byte(data);
}

//OLED模块初始化================================================================
void Initial_oled(void)
{
CS1L;               //CS1低
CS2H;               //CS2高
   
delayms(20);      
tran_cmd_oled(0xAE); //显示关
tran_cmd_oled(0x20); //设置内存寻址模式
tran_cmd_oled(0x10); //00,水平寻址模式;01,垂直寻址模式;10,页寻址模式(RESET);11,无效
tran_cmd_oled(0xb0); //设置页寻址模式的页起始地址,0-7
tran_cmd_oled(0xc8); //设置COM口输出扫描方向
tran_cmd_oled(0x02); //设置y的低地址
tran_cmd_oled(0x10); //设置y的高地址
tran_cmd_oled(0x40); //设置开始行地址
tran_cmd_oled(0x81); //设置对比控制寄存器
tran_cmd_oled(0x7f);
tran_cmd_oled(0xa1); //设置段 0 - 127
tran_cmd_oled(0xa6); //设置正常显示
tran_cmd_oled(0xa8); //设置多路复用率(1 -64)
tran_cmd_oled(0x3F); //
tran_cmd_oled(0xa4); //0xa4,输出跟踪存储器内容;0xa5,输出忽略存储器内容
tran_cmd_oled(0xd3); //设置显示偏移
tran_cmd_oled(0x00); //不偏移
tran_cmd_oled(0xd5); //设置显示时钟分频系数/晶振频率
tran_cmd_oled(0xf0); //设置分频系数
tran_cmd_oled(0xd9); //设置预充电周期
tran_cmd_oled(0x22); //
tran_cmd_oled(0xda); //设置端口硬件配置
tran_cmd_oled(0x12);
tran_cmd_oled(0xdb); //设置vcomh
tran_cmd_oled(0x20); //0x20,0.77xVcc
tran_cmd_oled(0x8d); //DC-DC使能
tran_cmd_oled(0x14); //
tran_cmd_oled(0xaf); //-打开Oled面板
CS1H;                  
}

//设置行和列================================================================
void Oled_Position(uint08 x,uint08 y)
{
tran_cmd_oled(0xb0+x);                  //设置行
tran_cmd_oled(((y&0xf0) >> 4)|0x10);    //设置列地址的高4位
tran_cmd_oled((y&0x0f)|0x00);         //设置列地址的低4位
}

//全屏填充=================================================================
void Fill_screen(uint08 dat)
{
uint08 i,j;
CS2H;
CS1L;            
for(i=0;i<8;i++)
{
    tran_cmd_oled(0xb0+i);
    tran_cmd_oled(0x02);
    tran_cmd_oled(0x10);
    for(j=0;j<128;j++)
   tran_dat_oled(dat);      //全部写dat
   
}
    // Oled_Position(0,1);               //初始化位置
CS1H;
}

/*从相关地址(addrHigh:地址高字节,addrMid:地址中字节,addrLow:地址低字节)中连续读出DataLen个字节的数据到 pBuff的地址*/
/*连续读取*/
void Get_bytes_from_ROM(uint08 addrHigh,uint08 addrMid,uint08 addrLow,uint08 *pBuff,uint08 DataLen )
{
uint08 i;
CS2L;                        //字库芯片片选
CS1H;
(void)SPI_Tran_Byte(0x03);   //提取数据命令
(void)SPI_Tran_Byte(addrHigh);
(void)SPI_Tran_Byte(addrMid);
(void)SPI_Tran_Byte(addrLow);
for(i=0;i<DataLen;i++)
      *(pBuff++) =SPI_Tran_Byte(0xff);
CS2H;

}
//显示16x16点阵图像、汉字、生僻字或16x16点阵的其他图标
//x表示行,y表示列列变化不连续
void Dis_grap_16x16(uint08 x,uint08 y,uint08 *dp)
{
uint08 i,j;
CS1L;
for(j=0;j<2;j++,x++)
{
    Oled_Position(x,y+1);
    for(i=0;i<16;i++,dp++)
    tran_dat_oled(*dp);         //写数据到LCD,每写完一个8位的数据后列地址自动加1
}
CS1H;
}
/*显示8x16点阵图像、ASCII, 或8x16点阵的自造字符、其他图标*/
void Dis_grap_8x16(uint08 x,uint08 y,uint08 *dp)
{
uint08 i,j;
CS1L;                  
for(j=0;j<2;j++,x++)
{
   Oled_Position(x,y+1);
   for(i=0;i<8;i++,dp++)
    tran_dat_oled(*dp);         /*写数据到LCD,每写完一个8位的数据后列地址自动加1*/
   
}
CS1H;
}

/*****************************************************************
功能:显示GB2312汉字字符
x行,取值范围:0,2,4,6
y列,取值范围:1—128
ch[] 需要输出的字符串
StrAdd 需要输出的字符地址
返回值 0 完成,非0 页满
*******************************************************************/


uint16 Dis_GB2312_Str(uint08 x,uint08 y,cuint08 ch2[] PROGMEM,uint16 StrAdd)
{ uint32 fontadd=0;                     //32位数据地址
uint08 addrHigh,addrMid,addrLow ;
uint08 fontbuf;
uint08 CharNow,CharNext;

CharNow =pgm_read_byte_near(table+StrAdd);
CharNext=pgm_read_byte_near(table+StrAdd+1);

while(CharNow)                     //数据值不为0
{
    if((CharNow>175)&&(CharNow<248)&&(CharNext>160))
    {         
//国标简体(GB2312)汉字在晶联讯字库IC中的地址由以下公式来计算:
//Address = ((MSB - 0xB0) * 94 + (LSB - 0xA1)+ 846)*32+ BaseAdd;BaseAdd=0
//由于担心8位单片机有乘法溢出问题,所以分三部取地址
      fontadd = (CharNow-176)*94;
      fontadd += (CharNext-161)+846;
      fontadd = fontadd*32;
      
      addrHigh =(fontadd&0xff0000)>>16;//地址的高8位,共24位
      addrMid =(fontadd&0xff00)>>8;      //地址的中8位,共24位
      addrLow =fontadd&0xff;            //地址的低8位,共24位
      
      Get_bytes_from_ROM(addrHigh,addrMid,addrLow,fontbuf,32 );//取32个字节的数据,存到"fontbuf"
      Dis_grap_16x16(x,y,fontbuf);                           //显示汉字到OLED上,y为页地址,x为列地址,fontbuf[]为数据
      StrAdd+=2;                        //地址加2
      if(y<=112) y+=16;
      else if(x<=4) {x+=2;y=1;}         //到头了换行
          else return StrAdd;         //返回当前地址
    }
    else if((CharNow>160)&&(CharNow<164)&&(CharNext>160))
    {         
   //Address = ((MSB - 0xa1) * 94 + (LSB - 0xA1))*32+ BaseAdd;BaseAdd=0
      fontadd = (CharNow- 0xa1)*94;
      fontadd += (CharNext-0xa1);
      fontadd = fontadd*32;
      
      addrHigh = (fontadd&0xff0000)>>16;
      addrMid = (fontadd&0xff00)>>8;   
      addrLow = fontadd&0xff;   
            
      Get_bytes_from_ROM(addrHigh,addrMid,addrLow,fontbuf,32 );
      Dis_grap_16x16(x,y,fontbuf);
      StrAdd+=2;
      if(y<=112) y+=16;
      else if(x<=4) {x+=2;y=1;}   //到头了换行
         else return StrAdd;       //返回当前地址
    }
    else if((CharNow>31)&&(CharNow<127))
    {         
      fontadd = CharNow- 0x20;
      fontadd = fontadd*16;
      fontadd = fontadd+0x3cf80;      
      addrHigh = (fontadd&0xff0000)>>16;
      addrMid = (fontadd&0xff00)>>8;
      addrLow = fontadd&0xff;

      Get_bytes_from_ROM(addrHigh,addrMid,addrLow,fontbuf,16 );
      Dis_grap_8x16(x,y,fontbuf);
            StrAdd++;
      if(y<=120) y+=8;
      else if(x<=4) {x+=2;y=1;}      //到头了换行
          else return StrAdd;          //返回当前地址
    }
   CharNow =pgm_read_byte_near(table+StrAdd);            //读取下个数据
   CharNext=pgm_read_byte_near(table+StrAdd+1);

}
return 0;

}
/*
uint16 Dis_GB2312_Str(uint08 x,uint08 y,uint08 ch[],uint16 StrAdd)
{   uint32 fontadd=0;                     //32位数据地址
uint08 addrHigh,addrMid,addrLow ;
uint08 fontbuf;
      
while(ch)                     //数据值不为0
{
    if((ch>175)&&(ch<248)&&(ch>160))
    {         
                                              //国标简体(GB2312)汉字在晶联讯字库IC中的地址由以下公式来计算:
                                              //Address = ((MSB - 0xB0) * 94 + (LSB - 0xA1)+ 846)*32+ BaseAdd;BaseAdd=0
                                              //由于担心8位单片机有乘法溢出问题,所以分三部取地址
      fontadd = (ch-176)*94;
      fontadd += (ch-161)+846;
      fontadd = fontadd*32;
      
      addrHigh =(fontadd&0xff0000)>>16;//地址的高8位,共24位
      addrMid =(fontadd&0xff00)>>8;      //地址的中8位,共24位
      addrLow =fontadd&0xff;            //地址的低8位,共24位
      
      Get_bytes_from_ROM(addrHigh,addrMid,addrLow,fontbuf,32 );//取32个字节的数据,存到"fontbuf"
      Dis_grap_16x16(x,y,fontbuf);                           //显示汉字到OLED上,y为页地址,x为列地址,fontbuf[]为数据
      StrAdd+=2;                        //地址加2
      if(y<=112) y+=16;
      else if(x<=4) {x+=2;y=1;}         //到头了换行
          else return StrAdd;         //返回当前地址
    }
    else if((ch>160)&&(ch<164)&&(ch>160))
    {         
                                             //Address = ((MSB - 0xa1) * 94 + (LSB - 0xA1))*32+ BaseAdd;BaseAdd=0
      fontadd = (ch- 0xa1)*94;
      fontadd += (ch-0xa1);
      fontadd = fontadd*32;
      
      addrHigh = (fontadd&0xff0000)>>16;
      addrMid = (fontadd&0xff00)>>8;   
      addrLow = fontadd&0xff;   
            
      Get_bytes_from_ROM(addrHigh,addrMid,addrLow,fontbuf,32 );
      Dis_grap_16x16(x,y,fontbuf);
      StrAdd+=2;
      if(y<=112) y+=16;
      else if(x<=4) {x+=2;y=1;}   //到头了换行
          else return StrAdd;       //返回当前地址
    }
    else if((ch>31)&&(ch<127))
    {         
      fontadd = ch- 0x20;
      fontadd = fontadd*16;
      fontadd = fontadd+0x3cf80;      
      addrHigh = (fontadd&0xff0000)>>16;
      addrMid = (fontadd&0xff00)>>8;
      addrLow = fontadd&0xff;

      Get_bytes_from_ROM(addrHigh,addrMid,addrLow,fontbuf,16 );
      Dis_grap_8x16(x,y,fontbuf);
            StrAdd++;
      if(y<=120) y+=8;
      else if(x<=4) {x+=2;y=1;}      //到头了换行
          else return StrAdd;          //返回当前地址
    }

}
return 0;

}

*/
uint08 mychar;
uint16 add=0;               //位置

void setup() {

asm("cli");                                             //中断禁用
Init_Clock();
Init_Port();
Init_Timer0();
SPI_Init_M0();                                          //SPI初始化时钟格式3
Initial_oled();
//asm("sei");                                           //中断使能

CS2H;
CS1L;
asm("nop");
Fill_screen(0xff);                                    //全屏填充
delayms(500);
Fill_screen(0);                                       //清屏
delayms(100);
//Serial.begin(38400);


}

void loop() {
   asm("wdr");
    add=0;
    do
    {
      
   
    add=Dis_GB2312_Str(0,1,table,add);         //在第1行,第1列,显示表中的数据
    delayms(2000);
    Fill_screen(0);                                    //清屏
    //Serial.println(add);
    } while(add);


}


希岩 发表于 2017-2-27 13:26

需要注意的是,代码的FLASH存储的数据是包含在另一个文件中的,编译时不要用Arduino IDE打开,保留其ANSI编码形式

jackten 发表于 2017-2-27 15:25

谢谢分享                        

Qunicy 发表于 2017-3-6 10:29

楼主这个是中景园电子买的吗?

希岩 发表于 2017-3-7 21:55

Qunicy 发表于 2017-3-6 10:29
楼主这个是中景园电子买的吗?

是的呢----

派灃 发表于 2017-6-1 10:54

楼主代码注释很全 谢谢分享

希岩 发表于 2017-6-10 15:16

派灃 发表于 2017-6-1 10:54
楼主代码注释很全 谢谢分享

不客气

caihongpei 发表于 2017-7-26 17:02

想问一下楼主,汉字是用字模提取的点阵来表示的吗?还是直接把你写的汉字保存成.c文件?
页: [1]
查看完整版本: Uno 驱动SPI OLED中文显示