首先,您的循环永远不会结束,并且会无限地递增j,因此,在您填充一次屏幕后,您会继续在屏幕高度之外循环;尽管您的库does bounds checking,但在j 溢出并回到零之前,在没有实际做有用工作的情况下继续循环肯定不是对CPU 的有效使用。
此外,有符号溢出在 C++ 中是未定义的行为,因此从技术上讲,您的理由是不稳定的(我最初认为 Arduino 总是使用 -fwrapv 编译,这保证了有符号整数溢出 but apparently I was mistaken 的环绕)。
鉴于您正在使用的库将整个帧缓冲区保存在内存中并在 refresh 调用时将其全部发送,因此在每个像素处重新发送它没有多大意义 - 特别是因为帧传输可能正在进行成为这个循环中最慢的部分。所以,你可以把它移出循环。
将其放在一起(加上缓存宽度和高度并使用random 的更简单的重载),您可以将其更改为:
void testdrawnoise() {
int w = display.width(), h = display.height();
for (int j=0; j<h; ++j) {
for (int i=0; i<w; ++i) {
display.drawPixel(i, j, random(2));
}
}
display.refresh();
}
(如果您在 AVR Arduinos 上的屏幕尺寸小于 256,您可能通过将所有这些 int 更改为 byte 来获得一些好处,但不要相信我的话)
请注意,这只会执行一次,您可以将其放入您的 loop() 函数或无限循环中以使其不断生成随机模式。
这是您可以使用提供的界面执行的操作;现在,进入无证领域,我们可以走得更快。
如上所述,您使用的库将整个帧缓冲区保存在内存中,以每字节 8 位打包(如预期)在一个名为 sharpmem_buffer 的全局变量中,初始化为 with a malloc of the obvious size。
还应注意,当您在代码中请求一个随机位时,PRNG 会生成一个完整的 31 位随机数并只取低位。为什么要浪费所有其他完美的随机位?
同时,当您调用drawPixel 时,库对内存中的相应字节执行一系列布尔运算,以设置您要求的位,而不会触及其余位。相当愚蠢,因为无论如何你都会用 random 覆盖其他的。
所以,把这两个事实放在一起,我们可以做这样的事情:
void testdrawnoise() {
// access the buffer defined in another .cpp
extern byte *sharpmem_buffer;
byte *ptr = sharpmem_buffer; // pointer to current position
// end position
byte *end = ptr + display.width()*display.height()/8;
for (; ptr!=end; ++ptr) {
// store a full byte of random
*ptr = random(256);
}
display.refresh();
}
其中减去refresh() 时间,应该至少比以前的版本快 8 倍(我实际上期望更多,因为不仅循环的核心执行 1/第 8 次迭代,但也更简单 - 除了 random 之外没有函数调用,没有分支,没有对内存的布尔运算)。
在 AVR Arduinos 上,唯一可以进一步优化的点可能是 RNG——我们仍然只使用 31 位中的 8 位(如果它们实际上是 31 位?像往常一样,Arduino 文档在提供有用的技术信息方面很糟糕) RNG,所以我们可能会从单个 RNG 调用中生成 3 个字节的随机数,或者如果我们切换到不会与符号位混淆的手动 LCG 的话,我们可以生成 4 个字节。在 ARM Arduinos 上,在最后一种情况下,我们甚至可以通过在内存中执行完整的 32 位存储而不是写入单个字节来获得一些好处。
但是,这些进一步的优化 (1) 编写起来很乏味(如果您必须处理像素数不是 24/32 倍数的屏幕)和 (2) 可能不是特别有利可图,因为大多数无论如何,大部分时间都将花在通过 SPI 传输上。无论如何都值得一提,因为它们可能在其他没有传输瓶颈来减慢速度的情况下很有用。
鉴于 OP 的 MCU 实际上是 Cortex M0(因此,是 32 位 ARM),因此值得尝试使用完整的 32 位 PRNG 和 32 位存储使其更快。
如上所述,内置random返回一个有符号值,它提供的范围并不完全清楚;出于这个原因,我们必须推出自己的 PRNG,保证提供 32 位完整的随机性。
xorshift 是一个体面且非常快速的 PRNG,它提供 32 个随机位和最小状态;我们将直接使用 Wikipedia 中的 xorshift32,因为我们并不真正需要改进的“*”或“+”版本(我们也不真正关心更大的对应物提供更大的周期)。
struct XorShift32 {
uint32_t state = 0x12345678;
uint32_t next() {
uint32_t x = state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
state = x;
return x;
}
};
XorShift32 xorShift;
现在我们可以重写testdrawnoise():
void testdrawnoise() {
int size = display.width()*display.height();
// access the buffer defined in another .cpp
extern byte *sharpmem_buffer;
/*
we can access the framebuffer as if it was an array of 32-bit words;
this is fine, since it was alloc-ed with malloc, which guarantees memory
aligned for the most restrictive built-in type, and the library only
uses it with byte pointers, so there should be no strict aliasing problem
*/
uint32_t *ptr = (uint32_t *)sharpmem_buffer;
/*
notice that the division is an integer division, which truncates; so, we
are filling the framebuffer up the the last multiple of 4 bytes; with
"strange" sizes we may be leaving out up to 3 bytes (see later)
*/
uint32_t *end = ptr + size/32;
for (; ptr!=end; ++ptr) {
// store a full byte of random
*ptr = xorShift.next();
}
// now to fill the possibly missing last three bytes
// pick it up where we left it
byte *final_ptr = (byte *)end;
byte *final_end = sharpmem_buffer + size/8;
// generate 32 random bits; it's ok, we'll need at most 24
uint32_t r = xorShift.next();
for(; final_ptr!=final_end; ++final_ptr) {
// take the lower 8 bits
*final_ptr = r;
// throw away the bits we used, get in the upper ones
r = r>>8;
}
display.refresh();
}