使用STM32硬件SPI接口驱动OLED显示(SSD1305)

  • STM32
  • 显示屏
  • OLED
  • SSD1305
  • 原创
  • jn1989
  • LV5工程师
  • |      2017-08-14 15:52:52
  • 浏览量 2838
  • 回复:0
最近有个项目需要用STM32F103C8做个显示板,和主板通过串口通讯,同时完成按键扫描、OLED显示和电灯功能。同时要求带GB2312字库。于是开始设计硬件,原理图就不贴了,总之就是:SP1口连接OLED和FLASH,其中OLED的控制器是SSD1305,FLASH型号是W25Q16。其他点灯啊、按键啊之类的就不说了,主要说说SSD1305的驱动。 首先是初始化系统,我使用了STM32CubeMX,设置如下图: 时钟配置如下图: 重点是SPI口配置如下: 速度配置为9M是因为SSD1305的SPI模式要求时钟周期不小于250ns,也就是频率不大于4M,但经过测试9M的速度也是没问题的。由于和W25Q16共用SPI口,为了减少读取SPI时间因此采用了高速方式。 先来基础的宏定义、写命令和写数据函数(我由于用了STM32CubeMX用的HAL库):
#define OLED_CS_E HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET)//使能OLED
然后是显示区域设置函数:
/*******************************************

 工作窗口设置;

 a,b 列窗口始末; (点为单位) c,d 页窗口始末;

 (8 点为单位)

 *******************************************/

void SET_AW(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {

	wr_com(0x21);

	wr_com(a + 4);

	wr_com(b + 4);

	wr_com(0x22);

	wr_com(c);

	wr_com(d);

}
然后是清屏函数:
//132*64 整个 RAM 区清屏;

void all_screen(uint8_t b) {

	uint8_t i, j;

	wr_com(0x21);

	wr_com(0);

	wr_com(131);

	wr_com(0x22);

	wr_com(0);

	wr_com(7);

	for (j = 0; j < 8; j++)

		for (i = 0; i < 132; i++)

			wr_data(b);

}

//128*64 显示界面的操作,b=0清屏;b=55H 隔行;b=FFH 全屏;

void fill(uint8_t b) {

	uint8_t i, j;

	SET_AW(0, 127, 0, 3);

	for (j = 0; j < 4; j++)

		for (i = 0; i < 128; i++)

			wr_data(b);

}
本次使用的是清达光电的HGS128322 模块,其初始化方式如下,各位可根据自己的模块调节初始化时候命令内容,具体内容查看附件的SSD1306数据手册(带中文注释)
/*******************************************

 初始化函数;

 *******************************************/

void spioled_init(void) {

	HAL_Delay(50);

	Spi_Oled_rest();

// 设置列首地址低半字节;

	wr_com(0x00);

// 设置列地址的高半字节;

	wr_com(0x10);

// 设置存储区的地址模式;

	wr_com(0x20);

	wr_com(0x00); /* 选水平地址模式 */

// 设置列窗口;

	wr_com(0x21);

	wr_com(0x00);

	wr_com(0x7f); /* 128 宽度的窗口 */

// 设置页窗口;

	wr_com(0x22);

	wr_com(0x00);

	wr_com(0x07); /* 8 页的窗口 */

// 设置显示起始行;

	wr_com(0x40); /* 0*/

// 设置 bank0 对比度;

	wr_com(0x81);

	wr_com(0x80); /* 共 256 级 */

// 设置彩色亮度;

	wr_com(0x82);

	wr_com(0x80); /* 共 256 级 */

// 设置 SEG 可逆;

	wr_com(0xA1); /* 改变 SEG 与地址

	 的对应关系 */

// 设置全显;

	wr_com(0xA4); /* 不全显 */

// 设置反显;

	wr_com(0xA6); /* 不反显 */

// 设置显示行;

	wr_com(0xA8);

	wr_com(0x1f); //32行

	/* 64 行, 改变的是可显示的 row*/

// Vcc 来源;

	wr_com(0xAD);

	wr_com(0x8E); /* 8E=外部 */

// 设置页首地址;

	wr_com(0xB0); /* 第 0 页 */

// 设置 COM 扫描方向;

	wr_com(0xC8); /* COM 反向 */

// 设置显示分支;

	wr_com(0xD3);

	wr_com(0x00); /* 0 */

// 设置 D 和 Fosc;

	wr_com(0xD5);

	wr_com(0xD1); /* F=D D=2 */

// 彩色与省电模式选择;

	wr_com(0xD8);

	wr_com(0); /* 48 为彩色, 5 为省电 */

// 设置 P1 P2;

	wr_com(0xD9);

	wr_com(0x22); /* P1=2 P2=2 */

//设置 COM 的硬件接法;

	wr_com(0xDA);

	wr_com(0x12);

//设置 Vcomh;

	wr_com(0xDB);

	wr_com(0x00);

	all_screen(0x00);

// 显示开关;

	wr_com(0xAF);

	/* AF=ON, AE=Sleep Mode, AC=Dim */

	fill(0xff); //清屏准备显示

}
开头加了个延时是为了系统稳定,去掉也没影响。 重点来了,由于这次的模组采用从上到下,从左到右,低字节在前高字节在后方式描点,因此字库取模需要设置为这个方式(软件分别是hzdotreaderv3、PCtoLCD2002和ts3): 这是不同软件的设置方式,可是我一点一点试出来的啊,厂家你给我出来,为什么不写清楚啊(好吧,英文我实在看不懂的过) 然后,重点来了!从W25Q16里面取字模然后显示! 先来看看字库是如何存储的,之前用hzdotreaderv3生成的16*16字库用UG打开看看 嗯……………………看不出来,再生个带注释的看看吧 通过对比可以看到,纯二进制文件就是按照GB2312字符内码顺序从A1A1开始存储的,每个16*16的字符占32个字节。OK这就好说了,下面就是从W25Q16里面取字模的程序:
void show_HZ_16(uint8_t x, uint8_t y,uint8_t *HZ_word)

{

	uint32_t HZ_addr;

	uint8_t HZ_code;

        uint8_t HZ_data;

	HZ_code=*HZ_word;	//内码高8位

	HZ_code=*(HZ_word+1);//内码第8位

	HZ_addr=(HZ_code-0xA1)*94+(HZ_code-0xa1);

        W25QXX_Read(HZ_data,HZ_addr*32,32);//从FLASH中读取字模,读取第HZ_addr个汉字的字模,读取32个字节存储到HZ_data.

	word_1616_1(x,y,HZ_data,0);

}
字符串显示函数考多次调用上面这个函数实现:
/*显示字符串自动换行

*参数:x起始x坐标;y起始y行数,*ASC_str字符串首地址指针,len长度

*注意:1、x值为像素位置,y值为行号,每行8个像素

*注意:2、每个汉字长度为2

*/

void show_str_16(uint8_t x, uint8_t y,uint8_t *ASC_str,uint16_t len)

{

  uint16_t i=0;

  uint8_t shouw_y=y;

  uint8_t shouw_x=x;

  

  for(i=0;i=128)

    {

      shouw_y=y+2;

      shouw_x=shouw_x-128;

    }

    if(*(ASC_str+i)==0x20)      //如果是空格

    {

      //不处理

    }

    else if(*(ASC_str+i)<0x7f)  //如果是ASCII字符

    {

      show_ASC_16(shouw_x,shouw_y,(ASC_str+i));

    }

    else if(*(ASC_str+i)>0xa0)  //如果是GB2312字符

    {

      show_HZ_16(shouw_x,shouw_y,(ASC_str+i));

      i++;

    }

  }

}
这里面我判断的如果说ASCII字符就调用汉字显示部分了,毕竟8*16就能显示一个ASCII字符,占用一个16*16位置太浪费了,ASCII字符显示函数如下
void show_ASC_16(uint8_t x, uint8_t y,uint8_t *ASC_word)

{

  uint16_t i;

  uint8_t ASC_addr;

  uint8_t ASC_data;

  ASC_addr=*ASC_word-0x21;

  for(i=0;i<16;i++)

    ASC_data=ASC_16;

  

  word_1616_2(x,y,ASC_data);

}
这里的ASC_16数组是我自己生成的ASCII字符字模,存在片上FLASH里,比较STM32F103C8有64kB的片上FLASH呢,不用白不用。 最后测试程序:
void spi_oled_test()

{

  static uint8_t zf_show_data={0xa1,0xa1};

  uint8_t i;

  if(Oled_Delay_time!=0)return;

  uint8_t show_data="显示任意字符";

  fill(0x00);

  show_str_16(0,0,show_data,sizeof(show_data)-1);

  for(i=0;i<8;i++)

  {

    show_str_16(i*16,2,zf_show_data,2);

    zf_show_data++;

    if(zf_show_data>=0xff)

    {

      zf_show_data++;

      zf_show_data=0xa1;

      if(zf_show_data>0xf7)

      {

        zf_show_data=0xa1;

        zf_show_data=0xa1;

      }

    }

  }

  HAL_Delay(1000);

}
效果如下是第一行显示:显示任意字符,第二行则把字库中所有字符轮流显示,每次8个,可以看到GB2312包含常用的全角符号、英文字母、希腊字母和汉字(据说是六千多个)。
  • 0
  • 收藏
  • 举报
  • 分享
我来回复

登录后可评论,请 登录注册

所有回答 数量:0
x
收藏成功!点击 我的收藏 查看收藏的全部帖子