这个裸板驱动的流程:
1、配置与OLED连接的引脚为输入或输出引脚,并设置有关引脚的默认或初始状态。
2、根据spi时序,实现主机SPISendByte()函数,以便配置OLED或向其发送数据时调用。
3、实现OLED 操作集,包括选择芯片,设置数据/命令模式,地址模式等函数。
4、实现OLED的初始化函数 OLEDInit()和功能函数 OLEDPutChar(int page, int col, char c)和OLEDPrint(int page, int col, char *str);
5、调用SPIInit(); OLEDInit();OLEDPrint()测试;
SPI时序图:
注:SPI时序图还细分有几种格式,但对本例没有什么区别。SPIMODE:由CPOL CPHA组合决定,其中CPOL决定SPICLK的初始值,CPHA表示时钟相位(也就是时序格式的LSB,MSB格式)
1、把GPIO配置为对应的输入或者输出。
OLED与芯片的连接:
OLED引脚 芯片引脚 描述 功能
OLED_CSn GPF1 输出引脚 选中芯片,低电平选中
OLED_DC GPG4 输出引脚 OLED模式,0:命令模式;1:数据模式
SPICLK GPG7 输出引脚 锁存数据时钟,上升沿锁存数据
SPIMOSI GPG6 输出引脚 SPI主机数据输出引脚,OLED数据输入引脚
初始化函数:SPI_GPIO_Init()
把上述引脚配置为输出,并把GPF1配置为1,没选中OLED:
static void SPI_GPIO_Init(void)
{
/* GPFCON[2:3]=01, OLED_CSn(GPF1) as output */
GPFCON &= ~(3<<(1*2));
GPFCON |= (1<<(1*2));
/* GPF1=1, 没选中,低电平选中 */
GPFDAT |= (1<<1);
/* GPG4 OLED_DC output
* GPG6 SPIMOSI output
* GPG7 SPICLK output
*/
GPGCON &= ~( (3<<(4*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGCON |= ( (1<<(4*2)) | (1<<(6*2)) | (1<<(7*2)));
}
2、实现主机SPISendByte()函数
static void SPI_Set_CLK(char val)
{
if (val)
GPGDAT |= (1<<7);
else
GPGDAT &= ~(1<<7);
}
static void SPI_Set_DO(char val)
{
if (val)
GPGDAT |= (1<<6);
else
GPGDAT &= ~(1<<6);
}
void SPISendByte(unsigned char val)
{
int i;
for (i = 0; i < 8; i++)
{
SPI_Set_CLK(0);
SPI_Set_DO(val & 0x80);
SPI_Set_CLK(1);
val <<= 1;
}
}
3、OLED操作集:
包括:
1)数据/命令模式选择:OLED_Set_DC()
2)从机选中:OLED_Set_CS()
3)写命令:OLEDWriteCmd()
4)写数据:OLEDWriteDat()
5)设置OLED显存地址模式:OLEDSetPageAddrMode(),这里选择页模式
6)页地址模式下的设置显示位置函数:OLEDSetPos()
static void OLED_Set_DC(char val)//模式,命令模式或数据模式
{
if (val)
GPGDAT |= (1<<4);
else
GPGDAT &= ~(1<<4);
}
static void OLED_Set_CS(char val)
{
if (val)
GPFDAT |= (1<<1);
else
GPFDAT &= ~(1<<1);
}
static void OLEDWriteCmd(unsigned char cmd)
{
OLED_Set_DC(0); /* command */
OLED_Set_CS(0); /* select OLED */
SPISendByte(cmd);
OLED_Set_CS(1); /* de-select OLED */
OLED_Set_DC(1); /* */ //默认状态吧,数据模式
}
static void OLEDWriteDat(unsigned char dat)
{
OLED_Set_DC(1); /* data */
OLED_Set_CS(0); /* select OLED */
SPISendByte(dat);
OLED_Set_CS(1); /* de-select OLED */
OLED_Set_DC(1); /* */
}
static void OLEDSetPageAddrMode(void)
{
OLEDWriteCmd(0x20); //设置地址模式命令0x20
/* 选择地址模式
A[1:0] = 00b, Horizontal Addressing Mode
A[1:0] = 01b, Vertical Addressing Mode
A[1:0] = 10b, Page Addressing Mode (RESET)
A[1:0] = 11b, Invalid
*/
OLEDWriteCmd(0x02);
}
static void OLEDSetPos(int page, int col)
{
OLEDWriteCmd(0xB0 + page); /* 设置页地址 page address */
//列地址分两步设置,先设置低4位,再设置高四位
OLEDWriteCmd(col & 0xf); /* Lower Column Start Address */
OLEDWriteCmd(0x10 + (col >> 4)); /* Lower Higher Start Address */
}
4、利用OLED操作集实现:
1)清屏函数:OLEDClear()
2)初始化函数:OLEDInit()
3)显示字符函数:OLEDPutChar()
4)显示字符串函数:OLEDPrint()
这些函数是供外部使用的函数。
void OLEDClear(void)
{
int page, i;
for (page = 0; page < 8; page ++)
{
OLEDSetPos(page, 0);
for (i = 0; i < 128; i++)
OLEDWriteDat(0);
}
}
void OLEDInit(void)
{
/* 向OLED发命令以初始化 */
OLEDWriteCmd(0xAE); /*display off*/
OLEDWriteCmd(0x00); /*set lower column address*/
OLEDWriteCmd(0x10); /*set higher column address*/
OLEDWriteCmd(0x40); /*set display start line*/
OLEDWriteCmd(0xB0); /*set page address*/
OLEDWriteCmd(0x81); /*contract control*/
OLEDWriteCmd(0x66); /*128*/
OLEDWriteCmd(0xA1); /*set segment remap*/
OLEDWriteCmd(0xA6); /*normal / reverse*/
OLEDWriteCmd(0xA8); /*multiplex ratio*/
OLEDWriteCmd(0x3F); /*duty = 1/64*/
OLEDWriteCmd(0xC8); /*Com scan direction*/
OLEDWriteCmd(0xD3); /*set display offset*/
OLEDWriteCmd(0x00);
OLEDWriteCmd(0xD5); /*set osc division*/
OLEDWriteCmd(0x80);
OLEDWriteCmd(0xD9); /*set pre-charge period*/
OLEDWriteCmd(0x1f);
OLEDWriteCmd(0xDA); /*set COM pins*/
OLEDWriteCmd(0x12);
OLEDWriteCmd(0xdb); /*set vcomh*/
OLEDWriteCmd(0x30);
OLEDWriteCmd(0x8d); /*set charge pump enable*/
OLEDWriteCmd(0x14);
OLEDSetPageAddrMode();//页地址模式下使用OLED(显存)
OLEDClear();
OLEDWriteCmd(0xAF); /*display ON*/
}
//实现OLED显示字符和字符串函数:
/* page: 0-7
* col : 0-127
* 字符: 8x16象素,8列16行
*/
void OLEDPutChar(int page, int col, char c)
{
int i = 0;
/* 得到字模 */
const unsigned char *dots = oled_asc2_8x16[c - ' '];
//字模为8*16,跨2页,分两次发送
/* 发给OLED */
OLEDSetPos(page, col);
/* 发出8字节数据 */
for (i = 0; i < 8; i++)
OLEDWriteDat(dots[i]);
OLEDSetPos(page+1, col);
/* 发出8字节数据 */
for (i = 0; i < 8; i++)
OLEDWriteDat(dots[i+8]);
}
/* page: 0-7
* col : 0-127
* 字符: 8x16象素,8列16行
*/
void OLEDPrint(int page, int col, char *str)
{
int i = 0;
while (str[i])
{
OLEDPutChar(page, col, str[i]);
col += 8;
if (col > 127)
{
col = 0;
page += 2;
}
i++;
}
}
5、测试,不说了。
OLED相关知识:
一个spi外设,对应一条独立的选中信号线,共用的是数据I/O线,时钟线。
oled显存与像素对应问题:
百问网提供的oled是128*64,这就是像素,每行128个,每列64个。
那么 1 byte 8位对应的是行中的8像素还是列中的8像素?显存地址增加,对应的像素在屏幕上是怎么变化?
答案是:1 byte对应的是一列,下 1 byte 对应的下一列,同样的8行。
示意如下:
显存:
行数 byte1 byte2 ... byte128
0 0 0
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5
6 6 6
7 7 7
......
byte(128*(56/8)+1) ...
56 0
1
2
3
4
5
6
63 7
oled的几种地址模式:
1、页地址模式:每8行作为一页,就是共8页127列,每一列里实际包含了8像素。
见图:
本例中使用页地址模式。
2、水平地址模式和垂直地址模式请看手册。
字符点阵与显存像素位置是怎么对应?这个OLED的字模点阵式纵向取模,不是平时的横向取模。
纵向取模示意:
字模点阵: bit 0 1 2 3 4 5 6 7
对应像素点阵:0
1
2
3
4
5
6
7
横向取模示意:
字模点阵: bit 0 1 2 3 4 5 6 7
对应像素点阵:0 1 2 3 4 5 6 7