wo4fisher 发表于 2022-4-14 22:37

[GD32L233C-START 评测] 三、1.5寸单色16级灰度OLED的多种刷图驱动方式对比

##**目录 (Table of Contents)**



## 一、挖宝
隔壁老王家总是有好多新奇的小玩意儿,稍不留神就没了。
这次运气好,赶在最后一波,捡了些漏。
其中有一个是1.5寸的单色OLED 16级灰度屏,自带驱动板(spi接口),驱动是SSD1327,与中景园/waveshare类似的屏代码兼容。

## 二、硬件分析

GD32L233C START 板载GD32L233C具有两个SPI外设,经查芯片手册和START布局,最后选定SPI1相关引脚来驱动。使用三种驱动方式来体验一下刷图时的性能。软件模拟SPI、硬件SPI、硬件SPI+DMA。

硬件连接:
```
PB13--------SCL
PB15--------SDA(MOSI)
PB12--------NSS(CS)
PB9 --------RESET
PB8 --------D/C
```

## 三、软件模拟
这个是最简单的,参考中景园/waveshare的代码实现GPIO 置位/复位操作后,然后再实现**OLED_WR_Byte(...)**写字节函数就可以了。不再赘述。
```
void OLED_WR_Byte(u8 dat, u8 cmd)
{
        u8 i;
        if (cmd)
                OLED_DC_Set();
        else
                OLED_DC_Clr();
        OLED_CS_Clr();
        for (i = 0; i < 8; i++)
        {
                OLED_SCL_Clr();
                if (dat & 0x80)
                {
                        OLED_SDA_Set();
                }
                else
                {
                        OLED_SDA_Clr();
                }
                OLED_SCL_Set();
                dat <<= 1;
        }
        OLED_CS_Set();
        OLED_DC_Set();
}
```
OLED其它相关函数都基于此而来,不再赘述。

## 四、硬件SPI
SPI初始化及其它GPIO控制口初始化
```
void SPI1_Init(void)
{
    spi_parameter_struct spi_init_struct = {0};

    rcu_periph_clock_enable(RCU_GPIOB);
    rcu_periph_clock_enable(RCU_SPI1);

    /* SPI0_CLK(PB13), SPI0_MISO_IO1(PB14), SPI0_MOSI_IO0(PB15) GPIO pin configuration */
    gpio_af_set(GPIOB, GPIO_AF_6, GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);

    /* SPI1_CS(PB12) GPIO pin configuration */
    gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_12);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12);
    gpio_bit_set(GPIOB, GPIO_PIN_12);

    /* OLED_DC(PB8), OLED_RST(PB9)GPIO pin configuration */
    gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_8 | GPIO_PIN_9);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8 | GPIO_PIN_9);

    /* chip select invalid */
    gpio_bit_set(GPIOB, GPIO_PIN_12);

    /* deinitilize SPI and the parameters */
    spi_i2s_deinit(SPI1);
    spi_struct_para_init(&spi_init_struct);

    /* SPI1 parameter configuration */
    spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
    spi_init_struct.device_mode = SPI_MASTER;
    spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;
    spi_init_struct.nss = SPI_NSS_SOFT;
    spi_init_struct.prescale = SPI_PSC_2;
    spi_init_struct.endian = SPI_ENDIAN_MSB;
    spi_init(SPI1, &spi_init_struct);

    /* set crc polynomial */
    spi_crc_polynomial_set(SPI1, 7);

    /* configure SPI1 byte access to FIFO */
    spi_fifo_access_size_config(SPI1, SPI_BYTE_ACCESS);
               
    /* enable SPI1 */
    spi_enable(SPI1);
}
```
实现SPI1_ReadWriteByte(...),实现硬件SPI收发一个直接操作。
```
uint8_t SPI1_ReadWriteByte(uint8_t txData)
{
    uint8_t retry = 0;
    while (spi_i2s_flag_get(SPI1, SPI_FLAG_TBE) == RESET)
    {
      if (retry++ > 200)
            return 0;
    }
    spi_i2s_data_transmit(SPI1, txData);
    retry = 0;

    while (spi_i2s_flag_get(SPI1, SPI_FLAG_RBNE) == RESET)
    {
      if (retry++ > 200)
            return 0;
    }
    return spi_i2s_data_receive(SPI1);
}
```
硬件SPI实现OLED_WR_Byte()操作
```
void OLED_WR_Byte(u8 dat, u8 cmd)
{
        u8 i;
        if (cmd)
                OLED_DC_Set();
        else
                OLED_DC_Clr();
        OLED_CS_Clr();
        SPI1_ReadWriteByte(dat);
        OLED_CS_Set();
        OLED_DC_Set();
}
```
在发送数据阶段,SSD1327可以连续发送数据,而不需要DC、CS引脚的变化。因此,在刷图的时候,比较适合使用多字节连续发送。
```
void OLED_WR_Bytes(u8 *dat, u16 num, u8 cmd)
{
        if (cmd)
                OLED_DC_Set();
        else
                OLED_DC_Clr();
        OLED_CS_Clr();
        u16 j = 0;
        while (j < num)
        {
                SPI1_ReadWriteByte(*dat++);               
        }       
        OLED_CS_Set();
        OLED_DC_Set();
}
```
在显示图片的函数中做如下更改:
```
void OLED_DrawBMP(u8 x, u8 y, u8 width, u8 height, const u8 *BMP)
{
        u16 k, num;
        x /= 2;
        width /= 2;
        Column_Address(x, x + width - 1);
        Row_Address(y, y + height - 1);
        num = width * height;
        /******   单字节发送*****/
        //        for (k = 0; k < num; k++)
        //        {
        //                OLED_WR_Byte(BMP, OLED_DATA);
        //        }
        /******   多字节发送*****/
        OLED_WR_Bytes(BMP, num, OLED_DATA);
}
```

## 五、硬件SPI+DMA

DMA 控制器提供了一种硬件传输方式,在外设和存储器之间或者存储器和存储器之间传输数据,而无需 CPU 的介入,从而使 CPU 可以专注在处理其他系统功能上。
这里简单对DMA做个测试,使用查询方式。
在上一节硬件SPI基础上,实现SPI请求DMA,完成数据包的发送。
DMA初始化:
```
#define SPI1_DATA_ADDRESS (&SPI_DATA(SPI1))

void dma_config(uint8_t *memAddr, uint16_t dataSize)
{
    dma_parameter_struct dma_init_struct = {0};

    rcu_periph_clock_enable(RCU_DMA);

    /* initialize DMA channel 0 */
    dma_deinit(DMA_CH0);
    dma_struct_para_init(&dma_init_struct);

    dma_init_struct.request = DMA_REQUEST_SPI1_TX;             /*!< channel input identification */
    dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;      /*!< channel data transfer direction */
    dma_init_struct.memory_addr = (uint32_t)memAddr;         /*!< memory base address */
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;   /*!< memory increasing mode */
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;      /*!< transfer data size of memory */
    dma_init_struct.number = (uint32_t)dataSize;               /*!< channel transfer number */
    dma_init_struct.periph_addr = (uint32_t)SPI1_DATA_ADDRESS; /*!< peripheral base address */
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;/*!< peripheral increasing mode */
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;/*!< transfer data size of peripheral */
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;      /*!< channel priority level */
    dma_init(DMA_CH0, &dma_init_struct);

    /* configure DMA mode */
    dma_circulation_disable(DMA_CH0);
    dma_memory_to_memory_disable(DMA_CH0);
    /* disable the DMAMUX_MUXCH0 synchronization mode */
    dmamux_synchronization_disable(DMAMUX_MUXCH0);
    /* enable DMA channel 0 transfer complete interrupt */
    // dma_interrupt_enable(DMA_CH0, DMA_INT_FTF);
    /* enable DMA channel 0 */
    dma_channel_enable(DMA_CH0);

    /* SPI DMA enable for transmission or reception */
    spi_dma_enable(SPI1, SPI_DMA_TRANSMIT);//启动发送

    while (RESET == dma_flag_get(DMA_CH0, DMA_FLAG_FTF)) //等待发送完成
      ;
    dma_flag_clear(DMA_CH0, DMA_FLAG_FTF);//清除发送完成标志
    dma_channel_disable(DMA_CH0);//关闭DMA通道
}
```
SPI+DMA实现发送多个字节
```
void OLED_WR_Bytes(u8 *dat, u16 num, u8 cmd)
{
        if (cmd)
                OLED_DC_Set();
        else
                OLED_DC_Clr();
        OLED_CS_Clr();
        dma_config(dat,num);
        OLED_CS_Set();
        OLED_DC_Set();
}
```
**OLED_DrawBMP()**函数与上节一致。
## 六、显示效果对比视频
16级灰度,显示效果还是可以的。




刷图视频:

**//我是占位符//**
## 七、总结
三种驱动方式,软件模拟最容易实现,但是最慢,而且占用CPU执行时间。硬件spi在批量输出时和使用DMA时间上区别不大,但是DMA的优势是在批量输出的过程中,不占用cpu执行时间,而这些时间,CPU就可以去执行别的任务。这个才是DMA最大的优势所在,在本例程中任务简单,因此并未体现出DMA的优势。






lugl4313820 发表于 2022-4-14 22:58

<p>哇你的帖是什么神马工具写的呀,教教我。我见过文章整理得最好的了。</p>

<p>spi写了三个实现方式,看来对spi的外设使用有火候呀!给你点赞赞。</p>

<pre>
<code>spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;
spi_init_struct.nss = SPI_NSS_SOFT;
spi_init_struct.prescale = SPI_PSC_2;</code></pre>

<p>在配置spi时这里最容易出现问题,这里备注一下屏的要求最好了。</p>

<p>感谢,如果有空,在这里分享一下你的makdown工具使用经验。</p>

wo4fisher 发表于 2022-4-14 23:09

lugl4313820 发表于 2022-4-14 22:58
哇你的帖是什么神马工具写的呀,教教我。我见过文章整理得最好的了。

spi写了三个实现方式,看来对spi的 ...

<p>斑竹都不睡觉的吗</p>

<p>markdown的使用,其实发帖打开的时候有类似使用向导的东东,使用现成的轮子就行。</p>

wo4fisher 发表于 2022-4-14 23:12

[GD32L233C-START 评测] 三、1.5寸单色16级灰度OLED的多种刷图驱动方式对比

<p><iframe allowfullscreen="true" frameborder="0" height="450" src="//player.bilibili.com/player.html?bvid=1Ca411e7BS&amp;page=1" style="background:#eee;margin-bottom:10px;" width="700"></iframe><br />
&nbsp;</p>

wo4fisher 发表于 2022-4-14 23:14

lugl4313820 发表于 2022-4-14 22:58
哇你的帖是什么神马工具写的呀,教教我。我见过文章整理得最好的了。

spi写了三个实现方式,看来对spi的 ...

<ul>
        <li>spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;</li>
        <li>然鹅,使用下边的也是可以亮的哦</li>
        <li>spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;</li>
        <li>神奇不神奇</li>
</ul>

lugl4313820 发表于 2022-4-15 06:42

wo4fisher 发表于 2022-4-14 23:14
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;
        然鹅,使用下边的也是可以亮的 ...

<p>早上好,朋友们!</p>

<p>&nbsp; &nbsp; &nbsp;是不是屏对这个时序要求不是很严格,我记得在弄N32G45时,就是因这个地方配置不一样,拆腾了好久。</p>

tagetage 发表于 2022-4-15 10:07

<p>1.5寸单色16级灰度OLED? 头一次见到这样的OLED。学习了。</p>

kit7828 发表于 2022-4-15 15:06

<p>不错不错,刚好我也淘了一块,DMA的方式我还没试过,谢谢</p>

wo4fisher 发表于 2022-4-15 19:44

lugl4313820 发表于 2022-4-15 06:42
早上好,朋友们!

&nbsp; &nbsp; &nbsp;是不是屏对这个时序要求不是很严格,我记得在弄N32G45时,就是 ...

<p>我也很惊喜。什么时钟极性、相位,好像没有那么严格。<br/>其实过程也是很曲折的。数据出去了,屏没反应,哈哈。</p>

wo4fisher 发表于 2022-4-15 19:49

kit7828 发表于 2022-4-15 15:06
不错不错,刚好我也淘了一块,DMA的方式我还没试过,谢谢

<p>显示效果还是挺让人意外的。比电子墨水屏刷新速度快多了。</p>
页: [1]
查看完整版本: [GD32L233C-START 评测] 三、1.5寸单色16级灰度OLED的多种刷图驱动方式对比