中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當前位置: 首頁 > news >正文

網(wǎng)站解析什么意思建網(wǎng)站需要多少錢和什么條件

網(wǎng)站解析什么意思,建網(wǎng)站需要多少錢和什么條件,沈陽百度廣告,河北精品網(wǎng)站建設(shè)目錄 OLED設(shè)備層驅(qū)動開發(fā) 如何抽象一個OLED 完成OLED的功能 初始化OLED 清空屏幕 刷新屏幕與光標設(shè)置1 刷新屏幕與光標設(shè)置2 刷新屏幕與光標設(shè)置3 繪制一個點 反色 區(qū)域化操作 區(qū)域置位 區(qū)域反色 區(qū)域更新 區(qū)域清空 測試我們的抽象 整理一下,我們應(yīng)…

目錄

OLED設(shè)備層驅(qū)動開發(fā)

如何抽象一個OLED

完成OLED的功能

初始化OLED

清空屏幕

刷新屏幕與光標設(shè)置1

刷新屏幕與光標設(shè)置2

刷新屏幕與光標設(shè)置3

繪制一個點

反色

區(qū)域化操作

區(qū)域置位

區(qū)域反色

區(qū)域更新

區(qū)域清空

測試我們的抽象

整理一下,我們應(yīng)該如何使用?


在上一篇博客:從0開始使用面對對象C語言搭建一個基于OLED的圖形顯示框架2-CSDN博客中,我們完成了協(xié)議層的抽象,現(xiàn)在讓我們更近一步,完成對設(shè)備層的抽象。

OLED設(shè)備層驅(qū)動開發(fā)

現(xiàn)在,我們終于來到了最難的設(shè)備層驅(qū)動開發(fā)。在這里,我們抽象出來了一個叫做OLED_Device的東西,我們終于可以關(guān)心的是一塊OLED,他可以被打開,被設(shè)置,被關(guān)閉,可以繪制點,可以繪制面,可以清空,可以反色等等。(畫畫不是這個層次該干的事情,要知道,繪制一個圖形需要從這個設(shè)備可以被繪制開始,也就是他可以畫點,畫面開始!)

所以,離我在這篇總覽中從0開始使用面對對象C語言搭建一個基于OLED的圖形顯示框架-CSDN博客提到的繪制一個多級菜單還是有一些遙遠的。飯一口口吃,事情一步步做,這急不得,一著急反而會把我們精心維護的抽象破壞掉。

代碼在MCU_Libs/OLED/library/OLED at main · Charliechen114514/MCU_Libs (github.com),兩個文件夾都有所涉及,所以本篇的代碼量會非常巨大。請各位看官合理安排。

如何抽象一個OLED

協(xié)議層上,我們抽象了一個IIC協(xié)議?,F(xiàn)在在設(shè)備層上,我們將進一步抽象一個OLED。上面筆者提到了,一個OLED可以被開啟,關(guān)閉,畫點畫面,反色等等操作,他能干!他如何干是我們馬上要做的事情?,F(xiàn)在,我們需要一個OLED句柄。這個OLED句柄代表了背后使用的通信協(xié)議和它自身相關(guān)的屬性信息,而不必要外泄到其他模塊上去。所以,封裝一個這樣的抽象變得很有必要。

OLED的品種很多,分法也很多,筆者順其自然,打算封裝一個這樣的結(jié)構(gòu)體

typedef struct __OLED_Handle_Type{/* driver types announced the way we explain the handle */OLED_Driver_Type ? ? ?  stored_handle_type;/* handle data types here */OLED_Handle_Private ? ? private_handle;
}OLED_Handle;

讓我來解釋一下:首先,我們的OLED品種很多,程序如何知道你的OLED如何被解釋呢?stored_handle_type標識的類型來決定采取何種行動解釋。。。什么呢?解釋我們的private_handle。

typedef enum {OLED_SOFT_IIC_DRIVER_TYPE,OLED_HARD_IIC_DRIVER_TYPE,OLED_SOFT_SPI_DRIVER_TYPE,OLED_HARD_SPI_DRIVER_TYPE
}OLED_Driver_Type;
?
/* ?to abstract the private handle base this is to isolate the dependencies ofthe real implementations
*/
typedef void* OLED_Handle_Private;

也就是說,筆者按照采取的協(xié)議進行抽象,將OLED本身的信息屬性差異封裝到文件內(nèi)部去,作為使用不同的片子,只需要使用編譯宏編譯不同的文件就好了?,F(xiàn)在,OLED_Handle就是我們的OLED,拿到這個結(jié)構(gòu)體,我們就掌握了整個OLED。所以,整個OLED結(jié)構(gòu)體必然可以做到如下的事情

#ifndef OLED_BASE_DRIVER_H
#define OLED_BASE_DRIVER_H
?
#include "oled_config.h"
?
typedef struct __OLED_Handle_Type{/* driver types announced the way we explain the handle */OLED_Driver_Type ? ? ?  stored_handle_type;/* handle data types here */OLED_Handle_Private ? ? private_handle;
}OLED_Handle;
?
/*oled_init_hardiic_handle registers the hardiic commnications
handle: Pointer to an OLED_Handle structure that represents the handle for the OLED display, used for managing and controlling the OLED device.programmers should pass a blank one!
?
config: Pointer to an OLED_HARD_IIC_Private_Config structure that contains the configuration settings for initializing the hardware interface, typically related to the I2C communication parameters for the OLED display.
*/
// 按照硬件IIC進行初始化
void oled_init_hardiic_handle(OLED_Handle* handle, OLED_HARD_IIC_Private_Config* config);
?
/*oled_init_hardiic_handle registers the hardiic commnications
handle: Pointer to an OLED_Handle structure that represents the handle for the OLED display, used for managing and controlling the OLED device.programmers should pass a blank one!
?
config: Pointer to an OLED_SOFT_IIC_Private_Config structure that contains the configuration settings for initializing the hardware interface, typically related to the I2C communication parameters for the OLED display.
*/
// 按照軟件IIC進行初始化
void oled_init_softiic_handle(OLED_Handle* handle,OLED_SOFT_IIC_Private_Config* config
);
?
/* 可以清空 */
void oled_helper_clear_frame(OLED_Handle* handle);
void oled_helper_clear_area(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
?
/* 需要刷新,這里采用了緩存機制 */
void oled_helper_update(OLED_Handle* handle);
void oled_helper_update_area(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
?
/* 可以反色 */
void oled_helper_reverse(OLED_Handle* handle);
void oled_helper_reversearea(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
?
/* 可以繪制 */
void oled_helper_setpixel(OLED_Handle* handle, uint16_t x, uint16_t y);
void oled_helper_draw_area(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t* sources);
?
/* 自身的屬性接口,是我們之后要用的 */
uint8_t ? ? oled_support_rgb(OLED_Handle* handle);
uint16_t ?  oled_width(OLED_Handle* handle);
uint16_t ?  oled_height(OLED_Handle* handle);
?
#endif

說完了接口,下面就是實現(xiàn)了。

完成OLED的功能

初始化OLED

整個事情我們終于開始翻開我們的OLED手冊了。我們的OLED需要一定的初始化。讓我們看看江科大代碼是如何進行OLED的初始化。

void OLED_Init(void)
{uint32_t i, j;for (i = 0; i < 1000; i++)          //上電延時{for (j = 0; j < 1000; j++);}OLED_I2C_Init();            //端口初始化OLED_WriteCommand(0xAE);    //關(guān)閉顯示OLED_WriteCommand(0xD5);    //設(shè)置顯示時鐘分頻比/振蕩器頻率OLED_WriteCommand(0x80);OLED_WriteCommand(0xA8);    //設(shè)置多路復(fù)用率OLED_WriteCommand(0x3F);OLED_WriteCommand(0xD3);    //設(shè)置顯示偏移OLED_WriteCommand(0x00);OLED_WriteCommand(0x40);    //設(shè)置顯示開始行OLED_WriteCommand(0xA1);    //設(shè)置左右方向,0xA1正常 0xA0左右反置OLED_WriteCommand(0xC8);    //設(shè)置上下方向,0xC8正常 0xC0上下反置OLED_WriteCommand(0xDA);    //設(shè)置COM引腳硬件配置OLED_WriteCommand(0x12);OLED_WriteCommand(0x81);    //設(shè)置對比度控制OLED_WriteCommand(0xCF);OLED_WriteCommand(0xD9);    //設(shè)置預(yù)充電周期OLED_WriteCommand(0xF1);OLED_WriteCommand(0xDB);    //設(shè)置VCOMH取消選擇級別OLED_WriteCommand(0x30);OLED_WriteCommand(0xA4);    //設(shè)置整個顯示打開/關(guān)閉OLED_WriteCommand(0xA6);    //設(shè)置正常/倒轉(zhuǎn)顯示OLED_WriteCommand(0x8D);    //設(shè)置充電泵OLED_WriteCommand(0x14);OLED_WriteCommand(0xAF);    //開啟顯示OLED_Clear();               //OLED清屏
}

好長一大串,麻了,代碼真的不好看。我們?yōu)槭裁床皇褂脭?shù)組進行初始化呢?

uint8_t oled_init_commands[] = {0xAE,  // Turn off OLED panel0xFD, 0x12,  // Set display clock divide ratio/oscillator frequency0xD5,  // Set display clock divide ratio0xA0,  // Set multiplex ratio0xA8,  // Set multiplex ratio (1 to 64)0x3F,  // 1/64 duty0xD3,  // Set display offset0x00,  // No offset0x40,  // Set start line address0xA1,  // Set SEG/Column mapping (0xA0 for reverse, 0xA1 for normal)0xC8,  // Set COM/Row scan direction (0xC0 for reverse, 0xC8 for normal)0xDA,  // Set COM pins hardware configuration0x12,  // COM pins configuration0x81,  // Set contrast control register0xBF,  // Set SEG output current brightness0xD9,  // Set pre-charge period0x25,  // Set pre-charge as 15 clocks & discharge as 1 clock0xDB,  // Set VCOMH0x34,  // Set VCOM deselect level0xA4,  // Disable entire display on0xA6,  // Disable inverse display on0xAF ? // Turn on the display
};
#define CMD_TABLE_SZ ( (sizeof(oled_init_commands)) / sizeof(oled_init_commands[0]) )

現(xiàn)在,我們只需要按部就班的按照順序發(fā)送我們的指令。以hardiic的初始化為例子

void oled_init_hardiic_handle(OLED_Handle* handle, OLED_HARD_IIC_Private_Config* config)
{// 傳遞使用的協(xié)議句柄, 以及告知我們的句柄類型 handle->private_handle = config;handle->stored_handle_type = OLED_HARD_IIC_DRIVER_TYPE;// 按部就班的發(fā)送命令表for(uint8_t i = 0; i < CMD_TABLE_SZ; i++)// 這里我們協(xié)議的send_command就發(fā)力了, 現(xiàn)在我們完全不關(guān)心他是如何發(fā)送命令的config->operation.command_sender(config, oled_init_commands[i]);// 把frame清空掉oled_helper_clear_frame(handle);// 把我們的frame commit上去oled_helper_update(handle);
}

這里我們還剩下最后兩行代碼沒解釋,為什么是oled_helper_clear_frame和update要分離開來呢?我們知道,頻繁的刷新OLED屏幕非常占用我們的單片機內(nèi)核,也不利于我們合并繪制操作。比如說,我想繪制兩個圓,為什么不畫完一起更新上去呢?比起來畫一個點更新一下,這個操作顯然更合理。所以,為了完成這樣的技術(shù),我們需要一個Buffer緩沖區(qū)。

uint8_t OLED_GRAM[OLED_HEIGHT][OLED_WIDTH];

他就承擔了我們的緩存區(qū)。多大呢?這個事情跟OLED的種類有關(guān)系,一些OLED的大小是128 x 64,另一些是144 x 64,無論如何,我們需要根據(jù)chip的種類,來選擇我們的OLED的大小,更加嚴肅的說,是OLED的屬性和它的功能。

所以,這就是為什么筆者在MCU_Libs/OLED/library/OLED/Driver/oled_config.h at main · Charliechen114514/MCU_Libs (github.com)文件中,引入了這樣的控制宏

#ifndef SSD1306_H
#define SSD1306_H
?
/* hardware level defines */
#define PORT_SCL ?  GPIOB
#define PORT_SDA ?  GPIOB
#define PIN_SCL ? ? GPIO_PIN_8
#define PIN_SDA ? ? GPIO_PIN_9
?
#define OLED_ENABLE_GPIO_SCL_CLK() __HAL_RCC_GPIOB_CLK_ENABLE()
#define OLED_ENABLE_GPIO_SDA_CLK() __HAL_RCC_GPIOB_CLK_ENABLE()
?
#define OLED_WIDTH  (128)
#define OLED_HEIGHT (8)
?
#define POINT_X_MAX ? ? (OLED_WIDTH)
#define POINT_Y_MAX ? ? (OLED_HEIGHT * 8)
?
#endif

這個文件是ssd1306.h,這個文件專門承載了關(guān)于SSD1306配置的一切?,F(xiàn)在,我們將OLED的配置系統(tǒng)建立起來了,當我們的chip是SSD1306的時候,只需要定義SSD1306的宏

#ifndef OLED_CONFIG_H
#define OLED_CONFIG_H
?
...
?
/* oled chips selections */
?
#ifdef SSD1306
?
#include "configs/ssd1306.h"
?
#elif SSD1309
#include "configs/ssd1309.h"
#else
#error "Unknown chips, please select in compile time using define!"
#endif
?
#endif

現(xiàn)在,我們的configure就完整了,我們只需要依賴config文件就能知道OLED自身的全部信息。如果你有IDE,現(xiàn)在就可以看到,當我們定義了SSD1306的時候,我們的OLED_GRAM自動調(diào)整為OLED_GRAM[8][128]的數(shù)組,另一放面,如果我們使用了SSD1309,我們自動會更新為OLED_GRAM[8][144],此事在ssd1309.h中亦有記載

清空屏幕

顯然,我們有一些人對C庫并不太了解,memset函數(shù)負責將一塊內(nèi)存設(shè)置為給定的值。一般而言,編譯器實現(xiàn)將會使用獨有的硬件加速優(yōu)化,使用上,絕對比手動設(shè)置值只快不慢。

軟件工程的一大原則:復(fù)用!能不自己手搓就不自己手搓,編譯器提供了就優(yōu)先使用編譯器提供的

void oled_helper_clear_frame(OLED_Handle* handle)
{memset(OLED_GRAM, 0, sizeof(OLED_GRAM));
}
刷新屏幕與光標設(shè)置1

設(shè)置涂寫光標,就像我們使用Windows的繪圖軟件一樣,鼠標在哪里,左鍵嗯下就從那里開始繪制,我們的set_cursor函數(shù)就是干設(shè)置鼠標在哪里的工作。查詢手冊,我們可以這樣書寫(筆者是直接參考了江科大的實現(xiàn))

/*set operating cursor
*/
void __pvt_oled_set_cursor(OLED_Handle* handle, const uint8_t y,const uint8_t x)
{ ? // 筆者提示:下面這一行是修正ssd1309的,ssd1306并不需要 + 2!// 也就是說,SSD1306的OLED不需要下面這一行,但是SSD1309需要,這一點可以去我的github倉庫上看的// 更加的明白  const uint8_t new_x = x + 2;OLED_Operations op_table;__on_fetch_oled_table(handle, &op_table);op_table.command_sender(handle->private_handle, 0xB0 | y);op_table.command_sender(handle->private_handle,0x10 | ((new_x & 0xF0) >> 4));   //設(shè)置X位置高4位op_table.command_sender(handle->private_handle,0x00 | (new_x & 0x0F));          //設(shè)置X位置低4位
}
刷新屏幕與光標設(shè)置2

不對,這個代碼沒有看懂!其一原因是我沒有給出__on_fetch_oled_table是什么。

static void __on_fetch_oled_table(const OLED_Handle* handle, OLED_Operations* blank_operations)
{switch (handle->stored_handle_type){case OLED_HARD_IIC_DRIVER_TYPE:{OLED_HARD_IIC_Private_Config* config = (OLED_HARD_IIC_Private_Config*)(handle->private_handle);blank_operations->command_sender = config->operation.command_sender;blank_operations->data_sender = config->operation.data_sender;}break;case OLED_SOFT_IIC_DRIVER_TYPE:{OLED_SOFT_IIC_Private_Config* config = (OLED_SOFT_IIC_Private_Config*)(handle->private_handle);blank_operations->command_sender = config->operation.command_sender;blank_operations->data_sender = config->operation.data_sender;}break;... // ommited spi seletctions}break;default:break;}
}

這是干什么呢?答案是:根據(jù)OLED的類型,選擇我們的操作句柄。這是因為C語言沒法自動識別void*的原貌是如何的,我們必須將C++中的虛表選擇手動的完成

題外話:接觸過C++的朋友都知道繼承這個操作,實際上,這里就是一種繼承。無論是何種IIC操作,都是IIC操作。他都必須遵守可以發(fā)送字節(jié)的接口操作,現(xiàn)在的問題是:他到底是哪樣的IIC?需要執(zhí)行的是哪樣IIC的操作呢?所以,__on_fetch_oled_table就是把正確的操作函數(shù)根據(jù)OLED的類型給篩選出來。也就是C++中的虛表選擇操作

/*set operating cursor
*/
void __pvt_oled_set_cursor(OLED_Handle* handle, const uint8_t y,const uint8_t x)
{ ? const uint8_t new_x = x + 2;OLED_Operations op_table;__on_fetch_oled_table(handle, &op_table);op_table.command_sender(handle->private_handle, 0xB0 | y);op_table.command_sender(handle->private_handle,0x10 | ((new_x & 0xF0) >> 4));   //設(shè)置X位置高4位op_table.command_sender(handle->private_handle,0x00 | (new_x & 0x0F));          //設(shè)置X位置低4位
}

現(xiàn)在回到上面的代碼,我們將正確的操作句柄選擇出來之后,可以發(fā)送設(shè)置“鼠標”的指令了。

復(fù)習一下位操作的基本組成

  • &是一種萃取操作,任何數(shù)&0就是0,&1則是本身,說明可以通過對應(yīng)&1保留對應(yīng)位,&0抹除對應(yīng)位

  • |是一種賦值操作,任何數(shù)&1就是1,|0是本身,所以|可以起到對應(yīng)位置1的操作。

所以,保留高4位只需要 & 0xF0(0b11110000),保留低四位只需要&0x0F就好了(0b00001111)

刷新屏幕與光標設(shè)置3

現(xiàn)在讓我們看看刷新屏幕是怎么做的

void oled_helper_update(OLED_Handle* handle)
{OLED_Operations op_table;__on_fetch_oled_table(handle, &op_table);for (uint8_t j = 0; j < OLED_HEIGHT; j ++){/*設(shè)置光標位置為每一頁的第一列*/__pvt_oled_set_cursor(handle, j, 0);/*連續(xù)寫入128個數(shù)據(jù),將顯存數(shù)組的數(shù)據(jù)寫入到OLED硬件*/// 有趣的是,這里筆者埋下了一個伏筆,我為什么沒寫OLED_WIDTH呢?盡管在SSD1306這樣做是正確的// 但那也是偶然,筆者在移植SSD1309的時候就發(fā)現(xiàn)了這樣的不一致性,導致OLED死機.// 筆者提示: OLED長寬和可繪制區(qū)域的大小不一致性op_table.data_sender(handle->private_handle, OLED_GRAM[j], 128);}
}

刷新整個屏幕就是將鼠標設(shè)置到開頭,然后直接向后面寫入128個數(shù)據(jù)結(jié)束我們的事情,這比一個個寫要快得多!

繪制一個點

實際上,就是將對應(yīng)的數(shù)組的位置放上1就好了,這需要牽扯到的是OLED獨特的顯示方式。

OLED自身分有頁這個概念,一個頁8個像素,由傳遞的比特控制。舉個例子,我想顯示的是第一個像素亮起來,就需要在一個字節(jié)的第一個比特置1余下置0,這就是為什么OLED_HEIGHT的大小不是64而是8,也就意味著setpixel函數(shù)不是簡單的

OLED[height][width] = val

而實需要進行一個復(fù)雜的計算。我們分析一下,給定一個Y的值。它落在的頁就是 Y / 8。比如說,Y為5的時候落在第0頁的第六個比特上,Y為9的時候落在第一個頁的第一個第二個比特上(注意我們的Y從0開始計算),我們設(shè)置的位置也就是:OLED_GRAM[y / 8][x],設(shè)置的值就是Y給定的比特是0x01 << (y % 8)

void oled_helper_setpixel(OLED_Handle* handle, uint16_t x, uint16_t y)
{// current unused(void)handle;if( 0 <= x && x <= POINT_X_MAX &&0 <= y && y <= POINT_Y_MAX)OLED_GRAM[y / 8][x] |= 0x01 << (y % 8);
}

(void)T是一種常見的放置maybe_unused的寫法,現(xiàn)代編譯器支持[[maybe_unused]]的指示符,表達的是這個參數(shù)可能不被用到,編譯器不需要為此警告我,這在復(fù)用中很常見,一些接口的參數(shù)可能不被使用,這樣的可讀性會比傳遞空更加的好讀,為了遵循ISO C,筆者沒有采取,保證任何編譯器都可以正確的理解我們的意圖。

反色

反色就很簡單了。只需要異或即可,首先,當給定的比特是0的時候,我們異或1,得到的就是相異的比較,所以結(jié)果是1:即0變成了1。我們給定的比特是1的時候,我們還是異或1,得到了相同的結(jié)果,所以結(jié)果是0,即1變成了0,這樣不就實現(xiàn)了一個像素的反轉(zhuǎn)嗎!

void oled_helper_reverse(OLED_Handle* handle)
{for(uint8_t i = 0; i < OLED_HEIGHT; i++){for(uint8_t j = 0; j < OLED_WIDTH; j++){OLED_GRAM[i][j] ^= 0xFF;}}
}

能使用memset嗎?為什么?所以memset是在什么情況下能使用呢?

我都這樣問了,那顯然不能,因為設(shè)置的值跟每一個字節(jié)的內(nèi)存強相關(guān),memset的值必須跟內(nèi)存的值沒有關(guān)系。

區(qū)域化操作

我們還有區(qū)域化操作沒有實現(xiàn)?;镜牟襟E是

思考需要的參數(shù):需要知道對

  • 哪個OLED:OLED_Handle* handle,

  • 起頭在哪里:uint16_t x, uint16_t y,

  • 長寬如何:uint16_t width, uint16_t height

  • 對于置位,則需要一個連續(xù)的數(shù)組進行置位,它的大小就是描述了區(qū)域矩形的大小

我們先來看置位函數(shù)

區(qū)域置位
void oled_helper_draw_area(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t* sources)
{// 確保繪制區(qū)域的起點坐標在有效范圍內(nèi),如果超出最大顯示坐標則直接返回if(x > POINT_X_MAX)  return;if(y > POINT_Y_MAX)  return;
?// 在設(shè)置圖像前,先清空繪制區(qū)域oled_helper_clear_area(handle, x, y, width, height); 
?// 遍歷繪制區(qū)域的高度,以8像素為單位劃分區(qū)域for(uint16_t j = 0; j < (height - 1) / 8 + 1; j++){for(uint16_t i = 0; i < width; i++){// 如果繪制超出屏幕寬度,則跳出循環(huán)if(x + i > OLED_WIDTH) { break; }// 如果繪制超出屏幕高度,則直接返回if(y / 8 + j > OLED_HEIGHT - 1) { return; }
?// 將sources中的數(shù)據(jù)按位移方式寫入OLED顯存GRAM// 當前行顯示,低8位數(shù)據(jù)左移與顯存當前內(nèi)容進行按位或OLED_GRAM[y / 8 + j][x + i] |= sources[j * width + i] << (y % 8);
?// 如果繪制數(shù)據(jù)跨頁(8像素一頁),處理下一頁的數(shù)據(jù)寫入if(y / 8 + j + 1 > OLED_HEIGHT - 1) { continue; }
?// 將高8位數(shù)據(jù)右移后寫入下一頁顯存OLED_GRAM[y / 8 + j + 1][x + i] |= sources[j * width + i] >> (8 - y % 8);}}
}
我們正
常來講,傳遞的會是一個二維數(shù)組,C語言對于二維數(shù)組的處理是連續(xù)的。也就是說。對于一個被聲明為OLED[WIDTH][HEIGHT]的數(shù)組,訪問OLED[i][j]本質(zhì)上等價于OLED + i * WIDTH + j,這個事情如果還是不能理解可以查照專門的博客進行學習。筆者默認在這里看我寫的東西已經(jīng)不會被這樣基礎(chǔ)的知識所困擾了。所以,我們的所作的就是將出于低頁的內(nèi)容拷貝到底頁上

OLED_GRAM[y / 8 + j][x + i]:這是顯存二維數(shù)組的索引訪問。

  • y / 8 + j 計算出當前數(shù)據(jù)位于哪個頁(OLED通常按8個像素一頁分塊存儲),通過整除將 y 坐標映射到顯存頁。

  • x + i 表示橫向的列位置。

sources[j * width + i]:這是源圖像數(shù)據(jù)數(shù)組的索引訪問。

  • j * width + i 計算當前像素在 sources 數(shù)據(jù)中的位置偏移。

<< (y % 8):將當前像素數(shù)據(jù)向左移動 (y % 8) 位,以確保源數(shù)據(jù)對齊到目標位置。

  • y % 8 獲取繪制的起點在當前頁中的垂直偏移。

|=:按位或運算符,將偏移后的數(shù)據(jù)合并到 OLED_GRAM 中現(xiàn)有內(nèi)容。

如果 y = 5,那么 y % 8 = 5,表示當前像素從第5位開始繪制。例如:

  • 如果 sources[j * width + i] 的值是 0b11000000,經(jīng)過 << 5 位移后變?yōu)?0b00000110,再與 OLED_GRAM 的原有數(shù)據(jù)合并,從而只影響目標位置上的兩個像素。

先試一下分析OLED_GRAM[y / 8 + j + 1][x + i] |= sources[j * width + i] >> (8 - y % 8);,筆者的分析如下

  1. OLED_GRAM[y / 8 + j + 1][x + i]

    • 這是下一頁顯存中的對應(yīng)位置。

    • y / 8 + j + 1 表示當前繪制位置的下一頁。

    • x + i 仍為當前列位置。

  2. sources[j * width + i]

    • 源圖像數(shù)據(jù)中當前像素的數(shù)據(jù)。

    • j * width + i 計算出當前像素在源數(shù)據(jù)中的位置。

  3. >> (8 - y % 8)

    • 將數(shù)據(jù)右移 (8 - y % 8) 位,將超出當前頁的高位部分對齊到下一頁。

    • 8 - y % 8 計算需要移入下一頁的位數(shù)。

  4. |=

    • 按位或,將偏移后的數(shù)據(jù)合并到下一頁顯存中,以保留已有內(nèi)容。

假設(shè) y = 5,那么 8 - y % 8 = 3。如果 sources[j * width + i]0b10110000,右移 3 位得到 0b00010110,這部分數(shù)據(jù)寫入下一頁顯存。

區(qū)域反色
void oled_helper_reversearea(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{// 確認起點坐標是否超出有效范圍if(x > POINT_X_MAX)  return;if(y > POINT_Y_MAX)  return;
?// 確保繪制區(qū)域不會超出最大范圍,如果超出則調(diào)整寬度和高度if(x + width > POINT_X_MAX) ? ? width = POINT_X_MAX - x;if(y + height > POINT_Y_MAX) ?  height = POINT_Y_MAX - y;
?// 遍歷高度范圍中的每個像素行for(uint8_t i = y; i < y + height; i++){for(uint8_t j = x; j < x + width; j++){// 反轉(zhuǎn)顯存GRAM中的指定像素位(按位異或)OLED_GRAM[i / 8][j] ^= (0x01 << (i % 8));}}
}
區(qū)域更新
void oled_helper_update_area(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{// 檢查起點坐標是否超出有效范圍if(x > POINT_X_MAX)  return;if(y > POINT_Y_MAX)  return;
?// 確認繪制區(qū)域不超出最大范圍if(x + width > POINT_X_MAX) ? ? width = POINT_X_MAX - x;if(y + height > POINT_Y_MAX) ?  height = POINT_Y_MAX - y;
?// 定義OLED操作表變量OLED_Operations op_table;// 獲取對應(yīng)的操作函數(shù)表__on_fetch_oled_table(handle, &op_table);
?// 遍歷繪制區(qū)域中的每個頁(8像素一頁)for(uint8_t i = y / 8; i < (y + height - 1) / 8 + 1; i++){// 設(shè)置光標到指定頁及列的位置__pvt_oled_set_cursor(handle, i, x);// 從顯存中讀取指定頁和列的數(shù)據(jù),通過data_sender發(fā)送到OLED硬件op_table.data_sender(handle, &OLED_GRAM[i][x], width); ? ? ? ?}
}

也就是將光標對應(yīng)到位置上刷新width個數(shù)據(jù),完事!

區(qū)域清空
void oled_helper_clear_area(OLED_Handle* handle, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{// 檢查起點坐標是否超出有效范圍if(x > POINT_X_MAX)  return;if(y > POINT_Y_MAX)  return;
?// 確保繪制區(qū)域不超出最大范圍if(x + width > POINT_X_MAX) ? ? width = POINT_X_MAX - x;if(y + height > POINT_Y_MAX) ?  height = POINT_Y_MAX - y;
?// 遍歷高度范圍內(nèi)的所有像素for(uint8_t i = y; i < y + height; i++){for(uint8_t j = x; j < x + width; j++){// 清除顯存中的指定像素位(按位與非操作)OLED_GRAM[i / 8][j] &= ~(0x01 << (i % 8));}}
}
 
  1. OLED_GRAM[i / 8][j]

    • 訪問顯存緩沖區(qū)中指定位置的字節(jié)。

    • i / 8 確定當前像素所在的頁,因為 OLED 每頁存儲 8 個垂直像素。

    • j 為水平方向的列位置。

  2. 0x01 << (i % 8)

    • 生成一個掩碼,將 0x01 左移 (i % 8) 位。

    • i % 8 計算出在當前頁中的垂直位偏移。

  3. ~(0x01 << (i % 8))

    • 對掩碼取反,生成一個用于清零的掩碼。例如,如果 i % 8 == 2,則 0x01 << 20b00000100,取反后得到 0b11111011

  4. &=

    • 按位與運算,將顯存當前位置對應(yīng)的像素清零,而其他位保持不變。

假設(shè) i = 10j = 5

  • i / 8 = 1 表示訪問第 2 頁(頁索引為 1);

  • i % 8 = 2 表示需要清除該頁第 3 位的像素;

  • 0x01 << 2 = 0b00000100,取反得到 0b11111011

  • OLED_GRAM[1][5] &= 0b11111011 會將第 3 位清零,其余位保持不變。

測試我們的抽象

現(xiàn)在,我們終于可以開始測試我們的抽象了。完成了既可以使用軟件IIC,又可以使用硬件IIC進行通信的OLED抽象,我們當然迫不及待的想要測試一下我們的功能是否完善。筆者這里剎住車,耐下性子聽幾句話。

首先,測試不是一番風順的,我們按照我們的期望對著接口寫出了功能代碼,基本上不會一番風順的得到自己想要的結(jié)果,往往需要我們進行調(diào)試,找到其中的問題,修正然后繼續(xù)測試。

整理一下,我們應(yīng)該如何使用?

首先回顧接口。我們需要指定一個協(xié)議按照我們期望的方式進行通信。在上一篇博客中,我們做完了協(xié)議層次的抽象,在這里,我們只需要老老實實的注冊接口就好了。

指引:如果你忘記了我們上一篇博客在做什么的話,請參考從0開始使用面對對象C語言搭建一個基于OLED的圖形顯示框架2-CSDN博客!

筆者建議,新建一個Test文件夾,書寫一個文件叫:oled_test_hard_iic.coled_test_soft_iic.c測試我們的設(shè)備層和協(xié)議層是正確工作的。筆者這里以測試硬件IIC的代碼為例子。

新建一個CubeMX工程,只需要簡單的配置一下IIC就好了(筆者選擇的是Fast Mode,為了方便以后測試我們的組件刷新),之后,只需要

#include "OLED/Driver/hard_iic/hard_iic.h"
#include "Test/OLED_TEST/oled_test.h"
#include "i2c.h"
/* configs should be in persist way */
OLED_HARD_IIC_Private_Config config;
?
void user_init_hard_iic_oled_handle(OLED_Handle* handle)
{bind_hardiic_handle(&config, &hi2c1, 0x78, HAL_MAX_DELAY);oled_init_hardiic_handle(handle, &config);
}

bind_hardiic_handle注冊了使用硬件IIC通信的協(xié)議實體,我們將一個空白的config,注冊了配置好的iic的HAL庫句柄,提供了IIC地址和最大可接受的延遲時間

oled_init_hardiic_handle則是進一步的從協(xié)議層飛躍到設(shè)備層,完成一個OLED設(shè)備的注冊,即,我們注冊了一個使用硬件IIC通信的OLED。現(xiàn)在,我們就可以直接拿這個OLED進行繪點了。

void test_set_pixel_line(OLED_Handle* handle, uint8_t xoffset, uint8_t y_offset)
{for(uint8_t i = 0; i < 20; i++)oled_helper_setpixel(handle,xoffset * i, y_offset * i);oled_helper_update(handle);
}
?
void test_oled_iic_functionalities()
{OLED_Handle handle;// 注冊了一個使用硬件IIC通信的OLEDuser_init_hard_iic_oled_handle(&handle);// 繪制一個test_set_pixel_line(&handle, 1, 2);HAL_Delay(1000);test_clear(&handle);test_set_pixel_line(&handle, 2, 1);HAL_Delay(1000);test_clear(&handle);
}

這個測試并不全面,自己可以做修改。效果就是在導言當中的視頻開始的兩條直線所示。

筆者的OLED設(shè)備層的代碼已經(jīng)全部開源到MCU_Libs/OLED/library/OLED at main · Charliechen114514/MCU_Libs (github.com),感興趣的朋友可以進一步研究。

目錄導覽

總覽

協(xié)議層封裝

OLED設(shè)備封裝

繪圖設(shè)備抽象

基礎(chǔ)圖形庫封裝

基礎(chǔ)組件實現(xiàn)

動態(tài)菜單組件實現(xiàn)

http://www.risenshineclean.com/news/40999.html

相關(guān)文章:

  • 網(wǎng)站名怎么寫整站優(yōu)化報價
  • 做機械的網(wǎng)站網(wǎng)站人多怎么優(yōu)化
  • 做汽車團購的網(wǎng)站建設(shè)直通車推廣計劃方案
  • 網(wǎng)頁制作與網(wǎng)站開發(fā)用的軟件友情鏈接的作用大不大
  • 湖北網(wǎng)站建設(shè)公司今天重大新聞事件
  • 會議網(wǎng)站建設(shè)方案模板搜一搜站長工具
  • 企業(yè)做網(wǎng)站和宣傳冊的作用bt磁力在線種子搜索神器
  • 如何接做網(wǎng)站編程的生意成長電影在線觀看免費
  • 網(wǎng)站后臺管理系統(tǒng)使用百度seo如何快速排名
  • 臨沂網(wǎng)站建設(shè)培訓班seo工具優(yōu)化軟件
  • 織夢通用seo網(wǎng)站模板百度客服電話24小時
  • 企業(yè)網(wǎng)站建設(shè)市場報價技術(shù)培訓機構(gòu)排名前十
  • 做百度推廣需要網(wǎng)站嗎快手推廣網(wǎng)站
  • 初中畢業(yè)學網(wǎng)站開發(fā)工程師銷售成功案例分享
  • b站停止轉(zhuǎn)播404直播入口微信加精準客源軟件
  • 用模板網(wǎng)站做h5宣傳頁多少錢跨境電商哪個平臺比較好
  • 便宜網(wǎng)站設(shè)計杭州網(wǎng)站建設(shè)
  • 二級建造師證書查詢官方網(wǎng)站全球搜索引擎網(wǎng)站
  • cn域名注冊廣州seo網(wǎng)站
  • 寶塔網(wǎng)站301重定向怎么做網(wǎng)站關(guān)鍵詞如何快速上首頁
  • 佛山企業(yè)網(wǎng)站搭建公司百度行發(fā)代理商
  • 用什么軟件做網(wǎng)站圖片百度平臺推廣聯(lián)系方式
  • 網(wǎng)站欄目結(jié)構(gòu)浙江專業(yè)網(wǎng)站seo
  • 自己做網(wǎng)站處理圖片用什么軟件下載寧德市人力資源和社會保障局
  • 東莞廣告公司電話百度關(guān)鍵詞優(yōu)化大師
  • 汕尾商城網(wǎng)站建設(shè)溫州網(wǎng)站建設(shè)優(yōu)化
  • 響應(yīng)式網(wǎng)站建設(shè)必推全網(wǎng)天下seo網(wǎng)站推廣方法
  • 有沒有在家做的手工活網(wǎng)站網(wǎng)絡(luò)推廣公司專業(yè)網(wǎng)絡(luò)
  • 網(wǎng)站推廣工作職責博客網(wǎng)站登錄
  • 有贊小程序開發(fā)平臺seo優(yōu)化個人博客