0.96寸OLED刷新率超2300FPS!!!
最近又玩起了0.96寸的OLED,是在某宝买的一块7脚的OLED,先看看图片:
就是上图的样子,它的引脚定义如下:
1
2
3
4
5
6
7
1、CS: 片选信号。
2、DC:数据或命令切换。
3、RES: 复位。
4、D1: SPI接口为SPI数据线。
5、DO: SPI接口为SPI时钟线。
6、VCC:电源正3.3V-5V。
7、GND: 电源地。
在cubemx中配置引脚定义,注意硬件SPI2的引脚,现在先验证OLED可用性,使用软件SPI模拟时序,同时也方便直接对接OLED的库,定义如下:
注意,以上的引脚定义也可用于后续硬件SPI的驱动,后续驱动硬件SPI也不需要更改引脚。
将OLED库与端口寄存器进行对接(问就是提高性能):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//-----------------OLED端口定义----------------
#define OLED_SCLK_Clr() GPIOB->BSRR = GPIO_PIN_13<<16u
#define OLED_SCLK_Set() GPIOB->BSRR = GPIO_PIN_13
#define OLED_SDIN_Clr() GPIOB->BSRR = GPIO_PIN_15<<16u
#define OLED_SDIN_Set() GPIOB->BSRR = GPIO_PIN_15
#define OLED_RST_Clr() GPIOB->BSRR = GPIO_PIN_11<<16u
#define OLED_RST_Set() GPIOB->BSRR = GPIO_PIN_11
#define OLED_DC_Clr() GPIOB->BSRR = GPIO_PIN_10<<16u
#define OLED_DC_Set() GPIOB->BSRR = GPIO_PIN_10
#define OLED_CS_Clr() GPIOB->BSRR = GPIO_PIN_12<<16u
#define OLED_CS_Set() GPIOB->BSRR = GPIO_PIN_12
软件SPI刷新率
软件SPI只需要更改oled.h中的操作定义。
在main函数中一直刷新屏幕,每刷新一次屏幕的同时进行计数,每1000ms读一次计数的数据,这就是屏幕的刷新率。
软件SP刷新率如下:
硬件SPI刷新率
接下来我们创建一个线程来专门刷新oled,将线程的优先级设置为最低,同时启用硬件SPI,cubeMX的设置如下:
同时将OLED的显存更新设置为页刷新,简单来说就一次性传输整个屏幕的数据,以下我贴官方手册的描述:
代码中只需要更改一个数据就可以设置页刷新:
同时重写OLED的刷新函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//写一个数据
void OLED_WR_Byte(u8 dat,u8 cmd)
{
u8 i;
if(cmd)
OLED_DC_Set();
else
OLED_DC_Clr();
HAL_SPI_Transmit(&hspi2, &dat, 1,1);
}
//更新显存到OLED
void OLED_Refresh(void)
{
u8 i,n;
static u8 cmd[3] = {0xb0,0x00,0x10};
//设置行起始地址 设置低列起始地址 设置高列起始地址
OLED_DC_Clr();
HAL_SPI_Transmit(&hspi2, cmd, 3,1);
//一次性传1024个字节的显存
OLED_DC_Set();
HAL_SPI_Transmit(&hspi2, OLED_GRAM, 128*8,5);
}
//清屏函数
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
for(n=0;n<128;n++)
{
OLED_GRAM[n][i]=0;//清除所有数据
}
}
}
看一下刷新率:
硬件SPI+DMA刷新率
以上硬件SPI的刷新率已经达到了恐怖的601FPS,但是这仍然不是极限!
接下来启用DMA,在cubeMX中配置:
改写发送函数,主要是将一次性发送1024个数据的更改为DMA发送:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//更新显存到OLED
void OLED_Refresh(void)
{
u8 i,n;
static u8 cmd[3] = {0xb0,0x00,0x10};
//设置行起始地址 设置低列起始地址 设置高列起始地址
OLED_DC_Clr();
HAL_SPI_Transmit(&hspi2, cmd, 3,1);
//一次性传1024个字节的显存
OLED_DC_Set();
HAL_SPI_Transmit_DMA(&hspi2, OLED_GRAM, 128*8);
// 等待DMA完成
while(hspi2.State != HAL_SPI_STATE_READY);
}
看一下刷新率:
这不是极限!!!
一般来说这个刷新率已经完全够了,而且OLED本身受到自身硬件限制,实际上远没有这么高的刷新率,现在显示的刷新率只能当作单片机1s内可以处理这么多次屏幕刷新,已经超过本身刷新率的实际意义。
但是这就是极限了吗?
极限了吗?
吗?
不,我隐约记得还有一招远古超频法!
超频!开启!
打开时钟配置代码,将晶振的的倍频直接拉到最高,代码如下:
更改此处参数,会将原本72Mhz的主频拉到128Mhz,同时将会把硬件SPI的频率拉高,此处我就不进行计算了 ,直接看刷新率:
wc,已经达到了惊人的2348FPS,只能说超频大法好啊。。。
一些优化
以上的代码仍然有优化空间,DMA发送数据的时候是一直在等待,我们需要将这段等待时间释放出来让CPU去干其他活,充分发挥CPU的性能。
此处使用信号量来进行等待,在DMA发送的时候等待信号量,在发送完成回调中释放信号量即可完成DMA传输过程中释放CPU,同时将频率也降回72Mhz,防止单片机挂了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//更新显存到OLED
void OLED_Refresh(void)
{
u8 i,n;
static u8 cmd[3] = {0xb0,0x00,0x10};
//设置行起始地址 设置低列起始地址 设置高列起始地址
OLED_DC_Clr();
HAL_SPI_Transmit(&hspi2, cmd, 3,1);
//一次性传1024个字节的显存
OLED_DC_Set();
HAL_SPI_Transmit_DMA(&hspi2, OLED_GRAM, 128*8);
// 等待DMA传输完成的同时去干其他活
rt_sem_take(&spi2_dma_sem, RT_WAITING_FOREVER);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI2)
{
// 这里可以释放信号量、互斥锁,或设置标志位
rt_sem_release(&spi2_dma_sem);
}
}
频率1251FPS,完全够用了,而且充分利用了CPU的性能,在刷新屏幕的同时还可以做其他的事情。
这次的折腾就到这里了。
一些代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
void OLED_WR_Byte(u8 dat,u8 cmd)
{
u8 i;
if(cmd)
OLED_DC_Set();
else
OLED_DC_Clr();
HAL_SPI_Transmit(&hspi2, &dat, 1,1);
}
//更新显存到OLED
void OLED_Refresh(void)
{
u8 i,n;
static u8 cmd[3] = {0xb0,0x00,0x10};
//设置行起始地址 设置低列起始地址 设置高列起始地址
OLED_DC_Clr();
HAL_SPI_Transmit(&hspi2, cmd, 3,1);
//一次性传1024个字节的显存
OLED_DC_Set();
HAL_SPI_Transmit_DMA(&hspi2, OLED_GRAM, 128*8);
// 等待DMA传输完成的同时去干其他活
rt_sem_take(&spi2_dma_sem, RT_WAITING_FOREVER);
}
//清屏函数
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
for(n=0;n<128;n++)
{
OLED_GRAM[n][i]=0;//清除所有数据
}
}
//OLED_Refresh();//更新显示
}
static void oled_refresh_callback(void *parameter)
{
static rt_tick_t now;
static int frame_count;
static rt_tick_t last_tick;
static int fps;
static char fps_str[8];
// 在右上角显示帧率
static char freq_str[16];
static rt_uint32_t freq;
while(1)
{
frame_count++;
now = rt_tick_get();
if (now - last_tick >= RT_TICK_PER_SECOND)
{
fps = frame_count;
frame_count = 0;
last_tick = now;
}
// 在右上角显示主频
// freq = SystemCoreClock / 1000000;
// rt_snprintf(freq_str, sizeof(freq_str), "%2dMHz", freq);
// OLED_ShowString(128 - 6 * 8, 12, (u8 *)freq_str, 12); // 显示CPU频率
// 在右上角显示帧率
rt_snprintf(fps_str, sizeof(fps_str), "%2dFPS", fps);
OLED_ShowString(128 - 6 * 8, 0, (u8 *)fps_str, 12); // 6像素宽字体, 5字符
OLED_Refresh();
}
}
//OLED的初始化
void OLED_Init(void)
{
// rt_pin_mode(OLED_SCLK_PIN, PIN_MODE_OUTPUT);
// rt_pin_mode(OLED_SDIN_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(OLED_RST_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(OLED_DC_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(OLED_CS_PIN, PIN_MODE_OUTPUT);
//初始化发送信号量
rt_sem_init(&spi2_dma_sem, "spi2dma", 0, RT_IPC_FLAG_FIFO);
OLED_RST_Clr();//复位
OLED_CS_Clr();//使能
rt_thread_mdelay(100);
OLED_RST_Set();
OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
OLED_WR_Byte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness
OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WR_Byte(0x00,OLED_CMD);//-not offset
OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WR_Byte(0x01,OLED_CMD);//
OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
OLED_WR_Byte(0xAF,OLED_CMD);
OLED_Clear();
OLED_ColorTurn(0);
OLED_DisplayTurn(0);
OLED_Refresh();
//创建一个线程来刷新OLED
rt_thread_t oled_thread = rt_thread_create("oled_ref",
(void (*)(void *))oled_refresh_callback,
RT_NULL,
512,
30,
1000);
if (oled_thread != RT_NULL)
{
rt_thread_startup(oled_thread);
rt_kprintf("OLED refresh thread created successfully\n");
}
else
{
rt_kprintf("Failed to create OLED refresh thread\n");
}
}
INIT_DEVICE_EXPORT(OLED_Init); //初始化OLED
void DMA1_Channel5_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma_spi2_tx);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI2)
{
// 这里可以释放信号量、互斥锁,或设置标志位
rt_sem_release(&spi2_dma_sem);
}
}
static void MX_SPI2_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel5_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_SPI2_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**SPI2 GPIO Configuration
PB13 ------> SPI2_SCK
PB15 ------> SPI2_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* SPI2 parameter configuration*/
hspi2.Instance = SPI2;
hspi2.Init.Mode = SPI_MODE_MASTER;
hspi2.Init.Direction = SPI_DIRECTION_2LINES;
hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi2.Init.NSS = SPI_NSS_SOFT;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi2.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi2) != HAL_OK)
{
while(1);
}
}
INIT_BOARD_EXPORT(MX_SPI2_Init); //初始化SPI2