GPIO模拟SPI驱动OLED

这个裸板驱动的流程:
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时序图:

GPIO模拟SPI驱动OLED_第1张图片

注: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像素。
见图:

GPIO模拟SPI驱动OLED_第2张图片

本例中使用页地址模式。
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 

你可能感兴趣的:(裸板,Linux驱动,SPI,OLED,GPIO,SPI裸板,SPIMOSI)