OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 5934|回复: 9

学习ARM笔记 OLED 第三季

[复制链接]

71

主题

467

帖子

0

精华

高级会员

Rank: 4

积分
800
金钱
800
注册时间
2011-11-18
在线时间
5 小时
发表于 2012-6-18 02:12:20 | 显示全部楼层 |阅读模式
这一季,我们来玩=====>> 字符~~~~~~~~~~~~

首先我们来说一下现在大多字模是怎么样来取的.在没有OLD之前,我们大多用的是LCD液晶屏.其中大名鼎鼎 1602屏,我想大多人51入门就会玩的屏了.只要我们
搞清它的显存地址就会用了.因为大多1206屏都会有字库,所以很少人用取字模的方式取字,到了后来,带中文字库的屏越来越多应用在产中后,我们越来越多的51er
们才开始玩画图模式.如用的最多的12864屏.

好了,费话不说了,我们来说说取模后,一个字符的结构 如下图:

先说一下LCD屏用的最多的16x8结构,0到7表示一个一个8位的二进制数,从上到下,7表示最高位bit7,0表示最低位bit0.


7 7 7 7 7 7 7 7
6 6 6 6 6 6 6 6
5 5 5 5 5 5 5 5
4 4 4 4 4 4 4 4
3 3 3 3 3 3 3 3
2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
range;display:inline-block;">7 7 7 7 7 7 7 7
range;display:inline-block;">6 6 6 6 6 6 6 6
range;display:inline-block;">5 5 5 5 5 5 5 5
range;display:inline-block;">4 4 4 4 4 4 4 4
range;display:inline-block;">3 3 3 3 3 3 3 3
range;display:inline-block;">2 2 2 2 2 2 2 2
range;display:inline-block;">1 1 1 1 1 1 1 1
range;display:inline-block;">0 0 0 0 0 0 0 0


看上边,显示一个字符用16个8位的二进制字来表示.绿色底的是第一个字节,橙色底的是第二个字节,蓝色底的就是第三个字节,如些类推,红色底的就是第16个字节了,如原子老大
取的字模我标上对应的着色打字累.偶不想全上色,只上4种大家明白就OK)

{0x00,range;display:inline-block;">0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,0x07,0x40,0x00,0xE4,0x00,0x1C,0x00,0x04},/*"A",33*/

比如这个A字,我标上颜色给大家看:

7  7  7  7  7  7  7  7
6  6  6  6  6  6  6  6
5  5  5  5  5  5  5  5
4  4  4  4  4  4  4  4
3  3  3  3  3  3  3  3
2  2  2  2  2  2  2  2
1  1  1  1  1  1  1  1
0  0  0  0  0  0  0  0
7  7  7  7  7  7  7  7
6  6  6  6  6  6  6  6
5  5  5  5  5  5  5  5
4  4  4  4  4  4  4  4
3  3  3  3  3  3  3  3
2  2  2  2  2  2  2  2
1  1  1  1  1  1  1  1
0  0  0  0  0  0  0  0


看!!!!!!!!!!!!!!!!   一个A字就是用八进制数里的"1"画出来的."0"代表空白.

有一个重点,如果我们取的是12X6怎么办呢??也一样,只是只有6列而已.如下:


7  7  7  7  7  7   
6  6  6  6  6  6   
5  5  5  5  5  5   
4  4  4  4  4  4   
3  3  3  3  3  3   
2  2  2  2  2  2   
1  1  1  1  1  1   
0  0  0  0  0  0   
7  7  7  7  7  7   
6  6  6  6  6  6   
5  5  5  5  5  5   
4  4  4  4  4  4   
3  3  3  3  3  3   
2  2  2  2  2  2   
1  1  1  1  1  1   
0  0  0  0  0  0 

有人问了,不是坚向不是12个bit吗?怎么有两个字节共16个bit呢? 是这样的,应为我们的寄存器只有8个位的,没有4个位的,那么用8位加4位来组是组不成的.
那怎么办呢,有办法的,我们没用4位就拿8位的来组.最下边(也就是最低3位)不用,全填上0就OK了,0是不显示的嘛.那12个字的排列也和16个字的排列一样.
如下:

如原子老大12x6的字模来做例子:

{0x00,range;display:inline-block;">0x40,0x07,0xC0,0x39,0x00,0x0F,0x00,0x01,0xC0,0x00,0x40},/*"A",33*/

7  7  7  7  7  7   
6  6  6  6  6  6   
5  5  5  5  5  5   
4  4  4  4  4  4   
3  3  3  3  3  3   
2  2  2  2  2  2   
1  1  1  1  1  1   
0  0  0  0  0  0   
7  7  7  7  7  7   
6  6  6  6  6  6   
5  5  5  5  5  5   
4  4  4  4  4  4   
3  3  3  3  3  3   
2  2  2  2  2  2   
1  1  1  1  1  1   
0  0  0  0  0  0 

黄色的部全里边填的全是0,有兴趣的同学可以安我上边的点陈画个A出来.

好了,知道结构了,写字符的代码就好写了.我写了显示的代码,如下.

//画一个12x6的字符

void asc2_1206t(u8 x,u8 y,u8 asc)
    u8 i;
for (i=0;i<12;i=i+2)
{
OLED_GRAM[x+i][y] |= asc2_1206[asc-' '];

OLED_GRAM[x+i][y-1] |= asc2_1206[asc-' '][i+1];
}
}

//画一个16x8的字符

void asc2_1608t(u8 x,u8 y,u8 asc)
    u8 i;
for (i=0;i<16;i=i+2)
{
OLED_GRAM[x+i][y] |= asc2_1608[asc-' '];

OLED_GRAM[x+i][y-1] |= asc2_1608[asc-' '][i+1];
}
}

//写一串 12x6的字符

void linechar_1206t(u8 x,u8 y,u8 *pd)
{
  while((*pd)!='\0')
    {
  asc2_1206t(x,y,*pd);

  pd++;
  x=x+12;
}
}

//写一串 16x8的字符

void linechar_1608t(u8 x,u8 y,u8 *pd)
{
  while((*pd)!='\0')
    {
  asc2_1608t(x,y,*pd);

  pd++;
  x=x+16;
}
}


哈哈,是不是很简单呀.   OLED_GRAM[x+i][y] |= asc2_1608[asc-' ']; 里的==>>  asc-' '  <<==  我要说一下,原子老大搞的字库是从
空格开始的,所以你要画的字符如不减去空格号的SCSI码,就会乱的,因为空格之前还有些码,但不显示我们没有用.

好,主程式,如下:

int main(void)
{
  u8 i;
  
  Stm32_Clock_Init(9);
  delay_init(72);
  uart_init(72,9600);
  IO_Init();
  
  while (1) 
    {   
OLED_WR_Byte (0x81,OLED_CMD);
OLED_WR_Byte (0xff,OLED_CMD);


X_line(0,127,47);

X_line(0,127,0);

Y_line(0,0,47);

Y_line(127,0,47);  //以上4行是画个框,上一季留下的.

asc2_1206t(32,7,' '+i);

asc2_1608t(82,7,' '+i);

i=i+1;
if (i>=95)i=0;  //因为只有96个(从0到95)字模.

linechar_1206t(25,4,"LQQ.FOX\0");
linechar_1608t(1,2,"CHINAFOX\0");

OLED_Refresh_Gram();

delay_ms(500);
OLED_Clear();

    }
}

说明一下:

OLED_WR_Byte (0x81,OLED_CMD);
OLED_WR_Byte (0xff,OLED_CMD);

是控制亮度的,我试过,很搓,做呼吸灯不明显,所以让它点到最亮而已,OLED_WR_Byte (0xff,OLED_CMD);里的0xff是最大值,如变成0x00是最小值,也就是最不亮.

好吧,丢进板子里运行.哈哈,好玩吧.全部代码你帖出来,大家可以改来玩玩.所需要的头文件原子的例程里有.

#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "FONT.h"

#define OLED_CS PCout(9)
//#define OLED_RST  Bout(14)//在MINISTM32上直接接到了STM32的复位脚!
#define OLED_RS PCout(8)
#define OLED_WR PCout(7)   
#define OLED_RD PCout(6)

#define OLED_CMD  0 //写命令
#define OLED_DATA 1 //写数据

//PB0~7,作为数据线
#define DATAOUT(x) GPIOB->ODR=(GPIOB->ODR&0xff00)|(x&0x00FF); //输出

u8 OLED_GRAM[128][8];


void OLED_WR_Byte(u8 dat,u8 cmd)
{
DATAOUT(dat);     
  OLED_RS=cmd;
OLED_CS=0;    
OLED_WR=0;  
OLED_WR=1;
OLED_CS=1;   
OLED_RS=1;  

void OLED_Refresh_Gram(void)
{
u8 i,n;     
for(i=0;i<8;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(OLED_GRAM[n],OLED_DATA); 
}   
}

void OLED_Clear(void)  
{  
u8 i,n;  
for(i=0;i<8;i++)for(n=0;n<128;n++)OLED_GRAM[n]=0X00;  
OLED_Refresh_Gram();//更新显示
}


void IO_Init(void)

{

RCC->APB2ENR|=1<<3;    //使能PORTB时钟 
RCC->APB2ENR|=1<<4;    //使能PORTC时钟 

  JTAG_Set(SWD_ENABLE);
GPIOB->CRL=0X33333333;
GPIOB->ODR|=0XFFFF;       
 
  GPIOC->CRH&=0XFFFFFF00;
GPIOC->CRL&=0X00FFFFFF;
  GPIOC->CRH|=0X00000033;
GPIOC->CRL|=0X33000000;
GPIOC->ODR|=0X03C0;

OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率
OLED_WR_Byte(80,OLED_CMD);   //[3:0],分频因子;[7:4],震荡频率
OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数
OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64) 
OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移
OLED_WR_Byte(0X00,OLED_CMD); //默认为0

OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数.
    
OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置
OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭
OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式
OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127;
OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置
OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置
 
OLED_WR_Byte(0x81,OLED_CMD); //对比度设置
OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)
OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期
OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率
OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;

OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示         
OLED_WR_Byte(0xAF,OLED_CMD); //开启显示  
OLED_Clear();

}

//画点函数
void dot(u8 x,u8 y)
{
u8 Y_page,Y_data,temp;
Y_page = y/8;  //将Y座标值变在页的的数值

temp = y%8;    //所在点的位置

Y_data = 1<<temp; //得到这个点在这页里的Y坐标

    //OLED_GRAM[128][8];
OLED_GRAM[x][Y_page] |= Y_data;

}
//画横线函数
void X_line(u8 x0,u8 x1,u8 y)   // x0为从左到右的起始点x1为结束点 注意x1一定要比x0大 否则会出错 可以在代码里做成不论大小,但复杂会影响大家的学习热情
{
u8 i;

for (i=x0;i<x1;i++)
{
  dot(i,y);
}
}

//画竖线函数
void Y_line(u8 x,u8 y0,u8 y1)   // 原理同画横线
{
u8 i;

for (i=y0;i<y1;i++)
{
  dot(x,i);
}
}

//画一个12x6的字符
void asc2_1206t(u8 x,u8 y,u8 asc)
    u8 i;
for (i=0;i<12;i=i+2)
{
OLED_GRAM[x+i][y] |= asc2_1206[asc-' '];

OLED_GRAM[x+i][y-1] |= asc2_1206[asc-' '][i+1];
}
}


//画一个16x8的字符
void asc2_1608t(u8 x,u8 y,u8 asc)
    u8 i;
for (i=0;i<16;i=i+2)
{
OLED_GRAM[x+i][y] |= asc2_1608[asc-' '];

OLED_GRAM[x+i][y-1] |= asc2_1608[asc-' '][i+1];
}
}

//写一串 12x6的字符

void linechar_1206t(u8 x,u8 y,u8 *pd)
{
  while((*pd)!='\0')
    {
  asc2_1206t(x,y,*pd);

  pd++;
  x=x+12;
}
}


//写一串 16x8的字符

void linechar_1608t(u8 x,u8 y,u8 *pd)
{
  while((*pd)!='\0')
    {
  asc2_1608t(x,y,*pd);

  pd++;
  x=x+16;
}
}

int main(void)
{
  u8 i;
  
  Stm32_Clock_Init(9);
  delay_init(72);
  uart_init(72,9600);
  IO_Init();
  
  while (1) 
    {   
OLED_WR_Byte (0x81,OLED_CMD);
OLED_WR_Byte (0xff,OLED_CMD);


X_line(0,127,47);

X_line(0,127,0);

Y_line(0,0,47);

Y_line(127,0,47);

asc2_1206t(32,7,' '+i);

asc2_1608t(82,7,' '+i);

i=i+1;
if (i>=95)i=0;  //因为只有96个(从0到95)字模.

linechar_1206t(25,4,"LQQ.FOX\0");
linechar_1608t(1,2,"CHINAFOX\0");

OLED_Refresh_Gram();

delay_ms(500);
OLED_Clear();

    }
}

好了,OLED我们可以毕业了,今天太晚老婆睡觉了,打开电铬铁味不好闻,就搞不成SPI模式了.反正下下季我们要学NRF24L01,到时再玩SPI吧 .
明天我们开搞更大的屏  TFT屏~~~~~~~~~~~~~~~~~~~~~~~~~~~
我的工作就是天天在玩
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

71

主题

467

帖子

0

精华

高级会员

Rank: 4

积分
800
金钱
800
注册时间
2011-11-18
在线时间
5 小时
 楼主| 发表于 2012-6-18 02:20:47 | 显示全部楼层
PS:
上一季有同学说要解释一下初始化里的命令,没用的,厂家要求这样做的.里边更深一层的东东我们很少用.其实平时也直的没用.我们还是不要花时间去搞他了.好象反相显示,说来也真的很少用.

原子老大说搞个灰阶出来玩玩.这个屏反差太大,一个小点很明显.做出来较果很坑爹的.那个显示亮度控制是全屏的.不能一个个点控制,成本问题,厂家不鸟我们.SO.只能作罢.
我的工作就是天天在玩
回复 支持 反对

使用道具 举报

46

主题

329

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1544
金钱
1544
注册时间
2012-4-9
在线时间
80 小时
发表于 2012-6-18 08:30:07 | 显示全部楼层
好贴~支持!
STM32
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165536
金钱
165536
注册时间
2010-12-1
在线时间
2117 小时
发表于 2012-6-18 09:04:20 | 显示全部楼层
回复【2楼】chinafox:
---------------------------------
应该是可以实现的.你针对每个点设置一个灰度数组(假设支持5个灰度值).然后用定时器刷新,比如刷新频率为50Hz,你就设定定时器每20ms刷新一次,那么你就根据灰阶数组里面的值,让每个点点亮的时间有差异,比如5,那么就是全亮;4,那么就是4/5的时间全亮.如此,1就是只有1/5的时间全亮,这样,就可以实现灰度了.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

71

主题

467

帖子

0

精华

高级会员

Rank: 4

积分
800
金钱
800
注册时间
2011-11-18
在线时间
5 小时
 楼主| 发表于 2012-6-18 12:21:34 | 显示全部楼层
那也是用定时刷新的方法搞,我们到玩定时器再玩。
我的工作就是天天在玩
回复 支持 反对

使用道具 举报

0

主题

15

帖子

0

精华

新手上路

积分
37
金钱
37
注册时间
2012-9-20
在线时间
0 小时
发表于 2013-1-28 10:42:11 | 显示全部楼层
回复【5楼】chinafox:
---------------------------------
void asc2_1206t(u8 x,u8 y,u8 asc)
楼主  这个函数可以解释下么?循环里的数据怎么列是减一 不应该是减八的么
回复 支持 反对

使用道具 举报

3

主题

26

帖子

0

精华

初级会员

Rank: 2

积分
58
金钱
58
注册时间
2012-12-23
在线时间
0 小时
发表于 2013-3-5 22:44:07 | 显示全部楼层
回复【6楼】442609298:
---------------------------------
楼主 这个OLED如何显示汉字字符串呢?
回复 支持 反对

使用道具 举报

83

主题

349

帖子

1

精华

高级会员

Rank: 4

积分
908
金钱
908
注册时间
2012-8-10
在线时间
13 小时
发表于 2013-3-26 16:24:17 | 显示全部楼层
 受益匪浅,膜拜
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
21
金钱
21
注册时间
2012-12-20
在线时间
0 小时
发表于 2013-4-12 15:45:35 | 显示全部楼层
//画一个16x8的字符
void asc2_1608t(u8 x,u8 y,u8 asc)


x.y 还必须是按照字节的大小,而不是按照像素的大小 ?


沉沉浮浮
回复 支持 反对

使用道具 举报

10

主题

232

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2288
金钱
2288
注册时间
2012-8-24
在线时间
247 小时
发表于 2017-11-28 15:47:21 | 显示全部楼层
楼主,你好,我想问一下这个程序中的变量每次都+2吗?我从上面取模看来好像是+1呀?楼主能否说一下,谢谢
//画一个16x8的字符
void asc2_1608t(u8 x,u8 y,u8 asc)
{
    u8 i;
for (i=0;i<16;i=i+2)
{
OLED_GRAM[x+i][y] |= asc2_1608[asc-' '];

OLED_GRAM[x+i][y-1] |= asc2_1608[asc-' '][i+1];
}
}
单片机技术交流请加127034610
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-9 11:59

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表