一、硬件準(zhǔn)備
硬件部分主要包括:
- 龍芯2K500先鋒板
- 0.96寸OLED屏
OLED屏幕參數(shù):
- 驅(qū)動(dòng)芯片:SSD1306
- 分辨率:128x64
- 接口:I2C
開發(fā)板選擇使用I2C1,和OLED屏接線參考下圖:
開發(fā)板和OLED小屏的連接關(guān)系為:
-
3號(hào)針(I2C1_SCL)連接到OLED屏的SCL腳
-
4號(hào)針(I2C1_SDA)連接到OLED屏的SDA腳
-
23號(hào)針(GND)連接到OLED屏的GND腳
-
24號(hào)針(P3V3)連接到OLED屏的VCC腳
二、背景知識(shí)
開始之前,先簡(jiǎn)單介紹一些背景知識(shí)。
2.1 Linux內(nèi)核I2C驅(qū)動(dòng)配置
龍芯2K0500內(nèi)核默認(rèn)已經(jīng)打開了I2C驅(qū)動(dòng),啟動(dòng)后使用如下命令可以看到:
ls /dev/i2c-*
(左右移動(dòng)查看全部?jī)?nèi)容)
已經(jīng)有i2c設(shè)備了。
2.2 Linux用戶空間I2C API
參考這個(gè)文檔:https://www.kernel.org/doc/html/latest/i2c/dev-interface.html
用戶空間使用I2C,首先需要包含頭文件:
(左右移動(dòng)查看全部?jī)?nèi)容)
然后,打開設(shè)備文件:
int file;
int adapter_nr = 2; /* probably dynamically determined */
char filename[20];
snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
file = open(filename, O_RDWR);
if (file < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
(左右移動(dòng)查看全部?jī)?nèi)容)
打開設(shè)備之后,需要指定需要通信的從設(shè)備地址:
int addr = 0x40; /* The I2C address */
if (ioctl(file, I2C_SLAVE, addr) < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
(左右移動(dòng)查看全部?jī)?nèi)容)
好了,接下來就可以進(jìn)行I2C通信了:
/*
* Using I2C Write, equivalent of
* i2c_smbus_write_word_data(file, reg, 0x6543)
*/
buf[0] = reg;
buf[1] = 0x43;
buf[2] = 0x65;
if (write(file, buf, 3) != 3) {
/* ERROR HANDLING: I2C transaction failed */
}
/* Using I2C Read, equivalent of i2c_smbus_read_byte(file) */
if (read(file, buf, 1) != 1) {
/* ERROR HANDLING: I2C transaction failed */
} else {
/* buf[0] contains the read byte */
}
(左右移動(dòng)查看全部?jī)?nèi)容)
以上幾個(gè)代碼段,都來自于kernel.org的文檔。
三、移植SSD1306驅(qū)動(dòng)庫(kù)
3.1 選擇SSD1306驅(qū)動(dòng)庫(kù)
之前移植過的一個(gè)STM32的SSD1306驅(qū)動(dòng)庫(kù),原始開源項(xiàng)目鏈接:https://github.com/afiskon/stm32-ssd1306
移植后的開源項(xiàng)目連接:https://gitee.com/hihopeorg/harmonyos-ssd1306
這個(gè)移植版本主要修改包括:
-
適配了OpenHarmony 1.0的WIFI_IOT硬件接口;
-
添加了一個(gè)用于繪制矩形位圖的接口,可用于繪制漢字;
-
優(yōu)化了I2C全屏刷新速率;
這里使用移植版本作為基礎(chǔ)。
3.2 移植SSD1306驅(qū)動(dòng)庫(kù)
主要修改點(diǎn)包括:
-
初始化函數(shù)ssd1306_Reset中,添加打開I2C設(shè)備的代碼;
-
發(fā)送數(shù)據(jù)函數(shù)ssd1306_SendData中,修改為使用I2C用戶空間接口的代碼;
-
延時(shí)函數(shù)HAL_Delay,修改為使用ulseep實(shí)現(xiàn);
-
計(jì)時(shí)函數(shù)HAL_GetTick,修改為使用gettimeofday實(shí)現(xiàn);
-
添加了關(guān)閉函數(shù)ssd1306_Finish,用于關(guān)閉初始化時(shí)打開的I2C設(shè)備;
修改之后,這幾個(gè)函數(shù)的主要代碼為:
static int g_i2c = -1;
static uint64_t g_start_ms = 0;
void ssd1306_Reset(void)
{
char path[128] = {0};
snprintf(path, sizeof(path), "/dev/i2c-%d", SSD1306_DEV_NO);
g_i2c = open(path, O_RDWR);
if (g_i2c < 0) {
printf("open %s failed, %s!
", path, strerror(errno));
}
if (ioctl(g_i2c, I2C_SLAVE, SSD1306_DEV_ADDR) < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
printf("ioctl %s I2C_SLAVE failed, %s!
", path, strerror(errno));
exit(1);
}
struct timeval start_tv = {0};
if (gettimeofday(&start_tv, NULL) != 0) {
printf("gettimeofday failed!
");
}
g_start_ms = TV2MS(start_tv);
}
void ssd1306_Finish(void)
{
if (g_i2c >= 0) {
close(g_i2c);
}
}
void HAL_Delay(uint32_t ms)
{
usleep(ms * 1000);
}
uint32_t HAL_GetTick(void)
{
struct timeval now_tv = {0};
if (gettimeofday(&now_tv, NULL) != 0) {
printf("gettimeofday failed!
");
}
return TV2MS(now_tv) - g_start_ms;
}
uint32_t HAL_GetTickFreq(void)
{
return 1000;
}
static uint32_t ssd1306_SendData(uint8_t* data, size_t size)
{
struct i2c_msg msg = {0};
msg.addr = SSD1306_DEV_ADDR;
msg.buf = data;
msg.len = size;
if (g_i2c >= 0) {
struct i2c_rdwr_ioctl_data data = {0};
data.msgs = &msg;
data.nmsgs = 1;
return ioctl(g_i2c, I2C_RDWR, &data) < 0 ? -1 : 0;
}
return -1;
}
(左右移動(dòng)查看全部?jī)?nèi)容)
3.3 添加CMake構(gòu)建規(guī)則文件
接下來添加CMake構(gòu)建規(guī)則CMakeLists.txt文件,分別到ssd1306目錄和examples目錄。
ssd1306目錄的CMakeLists.txt用于編譯驅(qū)動(dòng)庫(kù),內(nèi)容為:
set(sources
ssd1306.c
ssd1306_fonts.c
)
add_library(ssd1306 STATIC ${sources})
include_directories(.)
(左右移動(dòng)查看全部?jī)?nèi)容)
3.4 移植SSD1306測(cè)試程序
之前移植版的測(cè)試程序適配的是OpenHarmony 1.0,這里也需要修改,主要修改點(diǎn):
-
ssd1306_demo.c文件中,移除和OpenHarmony相關(guān)的代碼;
-
添加main函數(shù)作為入口;
examples目錄的CMakeLists.txt用于編譯測(cè)試程序,內(nèi)容為:
set(sources
ssd1306_demo.c
ssd1306_tests.c
)
add_executable(oled_test ${sources})
add_definitions(-DUSE_MAIN)
target_link_libraries(oled_test ssd1306)
target_link_libraries(oled_test m)
include_directories(../ssd1306)
(左右移動(dòng)查看全部?jī)?nèi)容)
3.5 LoongArch CMake構(gòu)建參數(shù)
頂層的CMakeLists.txt文件內(nèi)容如下:
cmake_minimum_required(VERSION 3.21.0) # 網(wǎng)上看到消息稱這個(gè)版本的cmake才支持loongarch
set(CMAKE_SYSTEM_PROCESSOR loongarch)
set(CMAKE_C_COMPILER loongarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER loongarch64-linux-gnu-g++)
set(CMAKE_C_FLAGS -Wall)
project(ssd1306_oled)
add_subdirectory(ssd1306)
add_subdirectory(examples)
(左右移動(dòng)查看全部?jī)?nèi)容)
由于這里我添加了CMAKE_SYSTEM_PROCESSOR、CMAKE_C_COMPILER、CMAKE_CXX_COMPILER三個(gè)參數(shù),因此可以直接編譯出LoongArch的可執(zhí)行程序了。
如果不在CMakeLists.txt文件中指定這幾個(gè)參數(shù),通過命令行參數(shù)指定也是可以的:
cmake -B build -DCMAKE_SYSTEM_PROCESSOR=loongarch -DCMAKE_C_COMPILER=loongarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=loongarch64-linux-gnu-g++
(左右移動(dòng)查看全部?jī)?nèi)容)
3.6 編譯、運(yùn)行SSD1306測(cè)試程序
完成以上步驟后,就可以編譯SSD1306測(cè)試程序了。編譯:
# 生成 Makefile ,build 為生成的目錄
cmake -B build
# 編譯 ,或者 cd build && make 也可以
cmake --build build
(左右移動(dòng)查看全部?jī)?nèi)容)
編譯完成后,build/examples目錄下生成了oled_test二進(jìn)制文件,將其拷貝到開發(fā)板上。運(yùn)行測(cè)試程序:
./oeld_test
(左右移動(dòng)查看全部?jī)?nèi)容)
不出意外的話,就可以看到OLED上正常顯示各種測(cè)試畫面了:
四、實(shí)現(xiàn)SSD1306播放視頻
4.1 準(zhǔn)備視頻文件
首先需要準(zhǔn)備一個(gè)視頻文件,例如,我這里找的是蔡徐坤的“雞你太美”視頻;
4.2 轉(zhuǎn)換視頻格式
前面測(cè)試發(fā)現(xiàn)最大幀率接近 8 fps,接下來需要使用ffmpeg將視頻轉(zhuǎn)換為幀率 8 fps。轉(zhuǎn)換命令為:
ffmpeg -i input.mp4 -r 10 output.mp4
(左右移動(dòng)查看全部?jī)?nèi)容)
之后再使用Python腳本將視頻轉(zhuǎn)換為原始幀的二進(jìn)制文件:
./video2bin.py output.mp4 out.bin
(左右移動(dòng)查看全部?jī)?nèi)容)
這里的bin文件包含若干個(gè)連續(xù)的原始幀數(shù)據(jù),每個(gè)原始幀占用1KB(128x64/8=1024);
完整的視頻轉(zhuǎn)換python腳本,
#!/usr/bin/env python3
import sys
import cv2 as cv
TARGET_WIDTH = 128
TARGET_HEIGHT = 64
PIXEL_PER_BYTE = 8
WIDTH_BYTES = int(TARGET_WIDTH/PIXEL_PER_BYTE)
PIXEL_THRESHOLD = 128.0
# 將多個(gè)灰度像素打包到一個(gè)整數(shù)中
def pack_pixels(pixels, threshold):
value = 0
for gray in pixels:
bit = 1 if gray >= threshold else 0 # 二值化
value = (value << 1) + bit # 多個(gè)二值化像素值拼接為一個(gè)字節(jié)值
return value
frameCount = 0
def resize_and_binarize_image(frame, width, height, threshold):
data = []
frame = cv2.resize(frame, (width, height)) # 縮放
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) # 轉(zhuǎn)為灰度圖
_, binary = cv2.threshold(frame, threshold, 255, cv2.THRESH_BINARY) # 二值化
for r in range(height):
for b in range(int(width / PIXEL_PER_BYTE)):
colStart = b * PIXEL_PER_BYTE
pixels = frame[r, colStart: colStart + PIXEL_PER_BYTE]
byte = pack_pixels(pixels, threshold)
data.append(byte)
return bytes(data)
def convert_frame_to_bytes(frame):
return resize_and_binarize_image(frame, TARGET_WIDTH, TARGET_HEIGHT, PIXEL_THRESHOLD)
def convert_video_to_bin(videoFile, binFile):
cap = cv.VideoCapture(videoFile)
frameCount = cap.get(cv.CAP_PROP_FRAME_COUNT)
print('frame count:', frameCount)
print('frame width:', cap.get(cv.CAP_PROP_FRAME_WIDTH))
print('frame height:', cap.get(cv.CAP_PROP_FRAME_HEIGHT))
lastPercent = 0
with open(binFile, 'wb+') as f:
while True:
retval, frame = cap.read()
if not retval:
print('video done!')
break
bitmap = convert_frame_to_bytes(frame)
f.write(bitmap)
pos = cap.get(cv.CAP_PROP_POS_FRAMES)
percent = pos / frameCount * 100
if percent - lastPercent >= 1:
lastPercent = percent
sys.stdout.write('=')
sys.stdout.flush()
print('convert all frames done!')
cap.release()
def main():
if len(sys.argv) < 3:
print("Usage: {} videoFile binFile
".format(sys.argv[0]))
exit(-1)
try:
videoFile = sys.argv[1]
binFile = sys.argv[2]
convert_video_to_bin(videoFile, binFile)
except Exception as e:
print('exception raised:', e)
if __name__ == "__main__":
main()
(左右移動(dòng)查看全部?jī)?nèi)容)
4.3 實(shí)現(xiàn)視頻播放
在examples目錄下,添加ssd1306_play.cpp文件,代碼如下:
int play(char* video_bin)
{
std::unique_ptrdecltype,>(&fclose)> fptr{fopen(video_bin, "rb"), fclose};
uint32_t count = 0;
uint8_t frame[SSD1306_BUFFER_SIZE] = {0};
ssd1306_Init();
uint32_t beg = HAL_GetTick();
for(;;) {
size_t nbytes = fread(frame, 1, sizeof(frame), fptr.get());
if (ferror(fptr.get())) {
printf("Error: %s
", strerror(errno));
return -1;
}
if (feof(fptr.get())) {
break;
}
ssd1306_Fill(Black);
ssd1306_DrawBitmap(frame, sizeof(frame));
ssd1306_UpdateScreen();
count++;
}
uint32_t end = HAL_GetTick();
ssd1306_Fill(Black);
ssd1306_UpdateScreen();
ssd1306_Finish();
float cost = (end - beg) / (float) HAL_GetTickFreq();
printf("Total frames : %d
", count);
printf("Total time(s): %.3f
", cost);
printf("Average FPS : %.3f
", count / cost);
return 0;
}
int main(int argc, char* argv[])
{
if (argc <= 1) {
printf("Usage: %s video.bin
", argv[0]);
return 1;
}
return play(argv[1]);
}
(左右移動(dòng)查看全部?jī)?nèi)容)
這段代碼實(shí)現(xiàn)了播放原始視頻二進(jìn)制文件;
4.4 添加構(gòu)建規(guī)則
examples目錄的CMakeLists.txt中添加:
add_executable(oled_play ssd1306_play.cpp)
target_link_libraries(oled_play ssd1306)
include_directories(../ssd1306)
(左右移動(dòng)查看全部?jī)?nèi)容)
4.5 播放視頻文件
完成以上操作后,重新編譯,再次運(yùn)行:
./oled_play ikun.bin
(左右移動(dòng)查看全部?jī)?nèi)容)
效果如下:
五、源碼倉(cāng)庫(kù)
本文所有代碼均已在碼云開源,鏈接為:https://gitee.com/swxu/linux-ssd1306
六、參考鏈接
-
Implementing I2C device drivers in userspace — The Linux Kernel documentation:https://www.kernel.org/doc/html/latest/i2c/dev-interface.html
-
一個(gè)STM32 SSD1306驅(qū)動(dòng)庫(kù):https://github.com/afiskon/stm32-ssd1306
-
【只因太美】用龍芯2K0500驅(qū)動(dòng)小屏放視頻:https://www.bilibili.com/video/BV1Gv4y1i7nW/
更多熱點(diǎn)文章閱讀
- 基于 OpenHarmony 的智能電磁炮
- 【我的2022】堅(jiān)果:我和OpenHarmony的這一年
- OpenHarmony小型系統(tǒng)兼容性測(cè)試指南
- 玩轉(zhuǎn)OpenHarmony社交場(chǎng)景:即時(shí)通訊平臺(tái)
-
龍芯 2K500 開發(fā)板 Linux環(huán)境基礎(chǔ)調(diào)教和使用
提示:本文由電子發(fā)燒友社區(qū)發(fā)布,轉(zhuǎn)載請(qǐng)注明以上來源。如需社區(qū)合作及入群交流,請(qǐng)?zhí)砑游⑿臙EFans0806,或者發(fā)郵箱liuyong@huaqiu.com。
原文標(biāo)題:龍芯2K500先鋒板試用體驗(yàn),驅(qū)動(dòng)OLED小屏播放視頻
文章出處:【微信公眾號(hào):電子發(fā)燒友開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
電子發(fā)燒友
+關(guān)注
關(guān)注
33文章
552瀏覽量
32977 -
開源社區(qū)
+關(guān)注
關(guān)注
0文章
94瀏覽量
419
原文標(biāo)題:龍芯2K500先鋒板試用體驗(yàn),驅(qū)動(dòng)OLED小屏播放視頻
文章出處:【微信號(hào):HarmonyOS_Community,微信公眾號(hào):電子發(fā)燒友開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論