[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的优势。
<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>
lugl4313820 发表于 2022-4-14 22:58
哇你的帖是什么神马工具写的呀,教教我。我见过文章整理得最好的了。
spi写了三个实现方式,看来对spi的 ...
<p>斑竹都不睡觉的吗</p>
<p>markdown的使用,其实发帖打开的时候有类似使用向导的东东,使用现成的轮子就行。</p>
[GD32L233C-START 评测] 三、1.5寸单色16级灰度OLED的多种刷图驱动方式对比
<p><iframe allowfullscreen="true" frameborder="0" height="450" src="//player.bilibili.com/player.html?bvid=1Ca411e7BS&page=1" style="background:#eee;margin-bottom:10px;" width="700"></iframe><br /> </p>
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>
wo4fisher 发表于 2022-4-14 23:14
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;
然鹅,使用下边的也是可以亮的 ...
<p>早上好,朋友们!</p>
<p> 是不是屏对这个时序要求不是很严格,我记得在弄N32G45时,就是因这个地方配置不一样,拆腾了好久。</p>
<p>1.5寸单色16级灰度OLED? 头一次见到这样的OLED。学习了。</p>
<p>不错不错,刚好我也淘了一块,DMA的方式我还没试过,谢谢</p>
lugl4313820 发表于 2022-4-15 06:42
早上好,朋友们!
是不是屏对这个时序要求不是很严格,我记得在弄N32G45时,就是 ...
<p>我也很惊喜。什么时钟极性、相位,好像没有那么严格。<br/>其实过程也是很曲折的。数据出去了,屏没反应,哈哈。</p> kit7828 发表于 2022-4-15 15:06
不错不错,刚好我也淘了一块,DMA的方式我还没试过,谢谢
<p>显示效果还是挺让人意外的。比电子墨水屏刷新速度快多了。</p>
页:
[1]