打印
[CW32F030系列]

【CW32F030CxTx StartKit测评】4.软件模拟SPI驱动OLED

[复制链接]
1735|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

SPI是一种高速、全双工、同步串行总线,SPI与I2C对比:

    SPI是全双工,I2C是半双工

    SPI只能有一个主机,I2C支持多主机多从机

    I2C占用更少的GPIO,更节省资源
    SPI的数据位宽更灵活,可以根据需要选择多位数数据宽度
    SPI协议没有响应机制,主机无法得知从机是否接收到所发的数据,需采用一些方法来防止数据丢帧
    SPI协议可以做到非常高的速率,每一个SCLK都可以进行数据的传输,通过引入CRC等校验方法,可以即高速传输数据,又能保持数据的准确度
    I2C通过器件地址来选择从机,从机数量的增加不会导致GPIO的增加,而SPI通过CS片选信号选择从机,每增加一个从机就要多占用一个GPIO;也可以通过加入译码器来实现多从机控制
    SPI、I2C都应用于板内器件短距离通讯

SPI总线详解可参考: SPI Block Guide V04.01.pdf (428.89 KB)

此篇主要介绍GPIO软件模拟实现SPI来驱动0.96寸OLED显示屏

硬件连接

GND ——  GND

VCC ——  3.3V

DO  ——  PA5
DI  ——  PA7
RES ——  PB1
DC  ——  PB0
CS  ——  PA4

软件代码

SPI代码:

//SPI初始化
void SPI_Initial(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    //根据GPIO组初始化GPIO时钟
    RCC_AHBPeriphClk_Enable( RCC_SPI_CS | RCC_SPI_SCK | RCC_SPI_MOSI | RCC_SPI_MISO, ENABLE);

    //GPIO_CS初始化设置
    GPIO_InitStructure.Pins = PIN_SPI_CS;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(PORT_SPI_CS, &GPIO_InitStructure);
    //GPIO_SCK初始化设置
    GPIO_InitStructure.Pins = PIN_SPI_SCK;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(PORT_SPI_SCK, &GPIO_InitStructure);
    //GPIO_MISO初始化设置
    GPIO_InitStructure.Pins = PIN_SPI_MISO;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_Init(PORT_SPI_MISO, &GPIO_InitStructure);
    //GPIO_MOSI初始化设置
    GPIO_InitStructure.Pins = PIN_SPI_MOSI;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(PORT_SPI_MOSI, &GPIO_InitStructure);

    SPI_CS_DISABLE;
    SPI_SCK_HIGH;
    SPI_MOSI_HIGH;

}


//SPI写一字节数据,TxData --- 发送的字节数据
void SPI_WriteByte(unsigned char TxData)
{
    unsigned char cnt;

    for(cnt = 0; cnt < 8; cnt++)
    {
        SPI_SCK_LOW;                                 //时钟 - 低
        Delay_Us(1);

        if(TxData & 0x80)                            //发送数据
            SPI_MOSI_HIGH;
        else
            SPI_MOSI_LOW;

        TxData <<= 1;

        Delay_Us(1);
        SPI_SCK_HIGH;                                //时钟 - 高
        Delay_Us(1);
    }
}


//SPI读一字节数据,读回来的字节数据
unsigned char SPI_ReadByte(void)
{
    unsigned char cnt;
    unsigned char RxData = 0;

    for(cnt = 0; cnt < 8; cnt++)
    {
        SPI_SCK_LOW;                                 //时钟 - 低
        Delay_Us(1);

        RxData <<= 1;

        if(SPI_MISO_READ)                            //读取数据
        {
            RxData |= 0x01;
        }

        SPI_SCK_HIGH;                                //时钟 - 高
        Delay_Us(1);
    }

    return RxData;
}

OLED代码:

//向设备写控制命令
static void OLED_Write_CMD(unsigned char cmd)
{
    #ifdef HW_I2C
    unsigned char tx_buf[BUF_SIZE] = {0x00, cmd};
    I2C_MasterSendDataToSlave(CW_I2C2, tx_buf, BUF_SIZE);
    #endif
    #ifdef SW_I2C
    I2C_Start();
    I2C_Send_Byte(0x78);
    I2C_Wait_Ack();
    I2C_Send_Byte(0x00);
    I2C_Wait_Ack();
    I2C_Send_Byte(cmd);
    I2C_Wait_Ack();
    I2C_Stop();
    #endif
    #ifdef HW_SPI
    OLED_CS_LOW;
    OLED_DC_LOW;

    while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_TXE) == RESET);

    SPI_SendData(SPI_MASTER, cmd);

    while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_RXNE) == RESET);

    SPI_ReceiveData(SPI_MASTER);
    OLED_CS_HIGH;
    #endif
    #ifdef SW_SPI
    OLED_CS_LOW;
    OLED_DC_LOW;
    SPI_WriteByte(cmd);
    OLED_CS_HIGH;
    #endif
}

//向设备写数据
static void OLED_Write_Date(unsigned char date)
{
    #ifdef HW_I2C
    unsigned char tx_buf[BUF_SIZE] = {0x40, date};
    I2C_MasterSendDataToSlave(CW_I2C2, tx_buf, BUF_SIZE);
    #endif
    #ifdef SW_I2C
    I2C_Start();
    I2C_Send_Byte(0x78);
    I2C_Wait_Ack();
    I2C_Send_Byte(0x40);
    I2C_Wait_Ack();
    I2C_Send_Byte(date);
    I2C_Wait_Ack();
    I2C_Stop();
    #endif
    #ifdef HW_SPI
    OLED_CS_LOW;
    OLED_DC_HIGH;

    while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_TXE) == RESET);

    SPI_SendData(SPI_MASTER, date);

    while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_RXNE) == RESET);

    SPI_ReceiveData(SPI_MASTER);
    OLED_CS_HIGH;
    #endif
    #ifdef SW_SPI
    OLED_CS_LOW;
    OLED_DC_HIGH;
    SPI_WriteByte(date);
    OLED_CS_HIGH;
    #endif
}

//坐标设置
static void OLED_Set_Pos(unsigned char x, unsigned char y)
{
    OLED_Write_CMD(0xB0 + y);
    OLED_Write_CMD(((x & 0xF0) >> 4) | 0x10);
    OLED_Write_CMD(x & 0x0F);
}

//开启OLED显示
static void OLED_Display_On(void)
{
    OLED_Write_CMD(0x8D); //SET DCDC命令
    OLED_Write_CMD(0x14); //DCDC ON
    OLED_Write_CMD(0xAF); //DISPLAY ON
}

//关闭OLED显示
static void OLED_Display_Off(void)
{
    OLED_Write_CMD(0x8D); //SET DCDC命令
    OLED_Write_CMD(0x10); //DCDC OFF
    OLED_Write_CMD(0xAE); //DISPLAY OFF
}

//OLED清屏
void OLED_Clear(void)
{
    unsigned char cnt, count;

    for(cnt = 0; cnt < 8; cnt++)
    {
        OLED_Write_CMD(0xB0 + cnt);
        OLED_Write_CMD(0x00);
        OLED_Write_CMD(0x10);

        for(count = 0; count < 128; count++)
        {
            OLED_Write_Date(0x00);
        }
    }
}

//OLED清行
void OLED_Clear_Row(unsigned char n)
{
    unsigned char count;

    OLED_Write_CMD(0xB0 + n);
    OLED_Write_CMD(0x00);
    OLED_Write_CMD(0x10);

    for(count = 0; count < 128; count++)
    {
        OLED_Write_Date(0x00);
    }
}

//OLED填满屏幕
void OLED_Fill(void)
{
    unsigned char cnt, count;

    for(cnt = 0; cnt < 8; cnt++)
    {
        OLED_Write_CMD(0xB0 + cnt); //设置页地址(0~7)
        OLED_Write_CMD(0x00); //设置显示位置—列低地址
        OLED_Write_CMD(0x10); //设置显示位置—列高地址

        for(count = 0; count < 128; count++)
        {
            OLED_Write_Date(0x01);
        }
    }
}

//指定位置显示一个字符
//x:0~127
//y:0~63
//chr:字符
//mode:0,反白显示;1,正常显示
//size:选择字体 16/12
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char chr, unsigned char size)
{
    unsigned char offset = 0, cnt = 0;

    offset = chr - ' '; //计算偏移量

    if(x > 128 - 1)
    {
        x = 0;
        y = y + 2;
    }

    if(size == 16)
    {
        OLED_Set_Pos(x, y);

        for(cnt = 0; cnt < 8; cnt++)
        {
            OLED_Write_Date(F8x16[offset * 16 + cnt]);
        }

        OLED_Set_Pos(x, y + 1);

        for(cnt = 0; cnt < 8; cnt++)
        {
            OLED_Write_Date(F8x16[offset * 16 + cnt + 8]);
        }
    }
    else
    {
        OLED_Set_Pos(x, y);

        for(cnt = 0; cnt < 6; cnt++)
        {
            OLED_Write_Date(F6x8[offset][cnt]);
        }
    }
}

unsigned int oled_pow(unsigned char m, unsigned char n)
{
    unsigned int result = 1;

    while(n--)
    {
        result *= m;
    }

    return result;
}

//指定位置显示一个数字
//x,y:起点坐标
//num:数值(0~4294967295)
//len:数字的位数
//size:字体大小
//mode:模式        0,填充模式;1,叠加模式
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len, unsigned char size)
{
    unsigned char cnt, temp;
    unsigned char show = 0;

    for(cnt = 0; cnt < len; cnt++)
    {
        temp = (num / oled_pow(10, len - cnt - 1)) % 10;

        if(show == 0 && cnt < (len - 1))
        {
            if(temp == 0)
            {
                OLED_ShowChar(x + (size / 2) * cnt, y, ' ', size);
                continue;
            }
            else
            {
                show = 1;
            }
        }

        OLED_ShowChar(x + (size / 2) * cnt, y, temp + '0', size);
    }
}

//指定位置显示字符串
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *chr, unsigned char size)
{
    unsigned char cnt = 0;

    while(chr[cnt] != '\0')
    {
        OLED_ShowChar(x, y, chr[cnt], size);
        x += 8;

        if(x > 120)
        {
            x = 0;
            y += 2;
        }

        cnt++;
    }
}

//显示汉字
void OLED_ShowCHinese(unsigned char x, unsigned char y, unsigned char no)
{
    unsigned char cnt, addr = 0;

    OLED_Set_Pos(x, y);

    for(cnt = 0; cnt < 16; cnt++)
    {
        OLED_Write_Date(Hzk[2 * no][cnt]);
        addr++;
    }

    OLED_Set_Pos(x, y + 1);

    for(cnt = 0; cnt < 16; cnt++)
    {
        OLED_Write_Date(Hzk[2 * no + 1][cnt]);
        addr++;
    }
}

//显示图片
/*
        @brief                        显示图片
        @param                        x0:起始列地址
                                        y0:起始页地址
                                        x1:终止列地址
                                        y1:终止页地址
                                        BMP[]:存放图片代码的数组
        @retval                        无
*/
void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[])
{
    unsigned int j = 0; //定义变量
    unsigned char x, y; //定义变量

    if(y1 % 8 == 0)
    {
        y = y1 / 8; //判断终止页是否为8的整数倍
    }
    else
    {
        y = y1 / 8 + 1;
    }

    for(y = y0; y < y1; y++) //从起始页开始,画到终止页
    {
        OLED_Set_Pos(x0, y); //在页的起始列开始画

        for(x = x0; x < x1; x++) //画x1 - x0 列
        {
            OLED_Write_Date(BMP[j++]); //画图片的点
        }
    }
}

//显示动图
/*
        @brief                        显示动图
        @param                        x0:起始列地址
                                y0:起始页地址
                                x1:终止列地址
                                y1:终止页地址
                                k: 帧个数
                                m: 单帧数组大小
                                BMP[][m]:存放动图代码的数组
        @retval                        无
*/
void OLED_DrawGIF(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char k, int m, const unsigned char GIF[][m])
{
    unsigned int j = 0; //定义变量
    unsigned char x, y, i; //定义变量

    if(y1 % 8 == 0)
    {
        y = y1 / 8; //判断终止页是否为8的整数倍
    }
    else
    {
        y = y1 / 8 + 1;
    }

    for (i = 0; i < k; i++) //从第一帧开始画
    {
        j = 0;

        for(y = y0; y < y1; y++) //从起始页开始,画到终止页
        {
            OLED_Set_Pos(x0, y); //在页的起始列开始画

            for(x = x0; x < x1; x++) //画x1 - x0 列
            {
                OLED_Write_Date(GIF[i][j++]); //画图片的点
            }
        }

        //Delay_Ms(80);
    }
}


#if defined SW_SPI || defined HW_SPI
//SPI驱动方式DC、RES引脚初始化
void OLED_SPI_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    //根据GPIO组初始化GPIO时钟
    RCC_AHBPeriphClk_Enable( RCC_OLED_DC | RCC_OLED_RES, ENABLE);
    //GPIO_DC初始化设置
    GPIO_InitStructure.Pins = PIN_OLED_DC;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(PORT_OLED_DC, &GPIO_InitStructure);
    //GPIO_RES初始化设置
    GPIO_InitStructure.Pins = PIN_OLED_RES;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.IT = GPIO_IT_NONE;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(PORT_OLED_RES, &GPIO_InitStructure);

    OLED_RES_LOW;
    Delay_Ms(200);
    OLED_RES_HIGH;
}
#endif


//OLED初始化
void OLED_Init(void)
{
    #ifdef SW_I2C
    I2C_Init();
    #endif
    #ifdef HW_I2C
    I2C_Init();
    #endif
    #ifdef SW_SPI
    SPI_Initial();
    OLED_SPI_GPIO_Init();
    #endif
    #ifdef HW_SPI
    SPI_Initial();
    OLED_SPI_GPIO_Init();
    #endif
    Delay_Ms(200);
    OLED_Write_CMD(0xAE); //display off
    OLED_Write_CMD(0x00); //set low column address
    OLED_Write_CMD(0x10); //set high column address
    OLED_Write_CMD(0x40); //set start line address
    OLED_Write_CMD(0xB0); //set page address
    OLED_Write_CMD(0x81); //contract control
    OLED_Write_CMD(0xFF); //128
    OLED_Write_CMD(0xA1); //set segment remap
    OLED_Write_CMD(0xA6); //normal / reverse
    OLED_Write_CMD(0xA8); //set multiplex ratio(1 to 64)
    OLED_Write_CMD(0x3F); //1/32 duty
    OLED_Write_CMD(0xC8); //Com scan direction
    OLED_Write_CMD(0xD3); //set display offset
    OLED_Write_CMD(0x00); //
    OLED_Write_CMD(0xD5); //set osc division
    OLED_Write_CMD(0x80); //
    OLED_Write_CMD(0xD8); //set area color mode off
    OLED_Write_CMD(0x05); //
    OLED_Write_CMD(0xD9); //Set Pre-Charge Period
    OLED_Write_CMD(0xF1); //
    OLED_Write_CMD(0xDA); //set com pin configuartion
    OLED_Write_CMD(0x12); //
    OLED_Write_CMD(0xDB); //set Vcomh
    OLED_Write_CMD(0x30); //
    OLED_Write_CMD(0x8D); //set charge pump enable
    OLED_Write_CMD(0x14); //
    OLED_Write_CMD(0xAF); //turn on oled panel
}

运行测试

测试代码

CW32F030CxTx_SW_SPI_OLED.zip (261.28 KB)





使用特权

评论回复
沙发
duo点| | 2022-8-8 09:30 | 只看该作者
屏幕上的几个图标是啥意思啊

使用特权

评论回复
板凳
zhouminjie|  楼主 | 2022-8-8 16:39 | 只看该作者
duo点 发表于 2022-8-8 09:30
屏幕上的几个图标是啥意思啊

几个车标

使用特权

评论回复
地板
weifeng90| | 2022-8-8 21:14 | 只看该作者
软件模拟效率不高啊

使用特权

评论回复
5
zhouminjie|  楼主 | 2022-8-8 23:36 | 只看该作者
weifeng90 发表于 2022-8-8 21:14
软件模拟效率不高啊

速度?

使用特权

评论回复
6
updownq| | 2022-8-16 20:30 | 只看该作者
模拟spi可以。  

使用特权

评论回复
7
hearstnorman323| | 2022-8-20 16:25 | 只看该作者
有硬件iic或者spi驱动的吗   

使用特权

评论回复
8
zhouminjie|  楼主 | 2022-8-22 23:26 | 只看该作者
hearstnorman323 发表于 2022-8-20 16:25
有硬件iic或者spi驱动的吗

有的,硬件SPI https://bbs.21ic.com/icview-3244756-1-1.html;硬件I2C https://bbs.21ic.com/icview-3244752-1-1.html

使用特权

评论回复
9
wwk1996| | 2022-8-23 06:31 | 只看该作者
OLED的驱动芯片是?

使用特权

评论回复
10
zhouminjie|  楼主 | 2022-8-31 21:04 | 只看该作者
wwk1996 发表于 2022-8-23 06:31
OLED的驱动芯片是?

内部芯片是SSD1306

使用特权

评论回复
11
abotomson| | 2022-9-8 09:02 | 只看该作者
这个模拟spi的速度快吗   

使用特权

评论回复
12
bartonalfred| | 2022-9-8 15:42 | 只看该作者
CW32F030CxTx 价格可以吗   

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

32

主题

136

帖子

3

粉丝