基於51單片機的簡易“視頻播放器”

来源:https://www.cnblogs.com/hejunlin1992/archive/2022/05/03/16219673.html
-Advertisement-
Play Games

痞子衡嵌入式半月刊: 第 54 期 這裡分享嵌入式領域有用有趣的項目/工具以及一些熱點新聞,農曆年分二十四節氣,希望在每個交節之日準時發佈一期。 本期刊是開源項目(GitHub: JayHeng/pzh-mcu-bi-weekly),歡迎提交 issue,投稿或推薦你知道的嵌入式那些事兒。 上期回顧 ...


介紹

本文介紹在51單片機上,使用OLED12864(SSD1306)播放視頻,並且使用蜂鳴器播放音樂。

因為是gif的原因,看著會比較卡,實際上是不會有卡頓的,實際效果可以看文末的視頻鏈接

最終的效果如下:

播放bad apple的效果:

播放數位寶貝的效果:

 

 

 

 

使用到的主要元器件如下:

  • 國產51單片機:STC15F2K60S2

  • OLED顯示屏:SSD1306,解析度為128*64

  • 無源蜂鳴器,8550三極體等

 

原理圖如下:

 

 

 

具體方案

由於視頻文件比較大(MB級別),而51單片機的flash一般都比較小(KB級別),因此把視頻文件直接存儲在單片機內部顯然是不行的。可以把視頻文件存儲在SD卡裡面,然後單片機讀取SD卡裡面的內容;或者視頻文件直接存儲在電腦上,然後電腦通過串口實時發送視頻數據給單片機,單片機實時顯示視頻畫面。文本採用後者的方案。

 

OLED12864繪圖

我們購買OLED12864(SSD1306)顯示屏時,一般賣家都會提供51單片機的示例代碼,或者網上也能找到很多相關的代碼。

使用這些代碼在整個屏幕上繪圖時,發現刷新率比較低,在11.0592M時鐘頻率的情況下,實測大概只有8.6fps。測試方法如下:

void main() 
{ 
    for(;;) 
    { 
        p27 = ~p27; 
        oled_drawbmp(pic); 
    } 
}

oled_drawbmp為賣家提供的繪圖的函數,每次屏幕刷新一次,p27 IO口翻轉一次。使用邏輯分析儀測試p27的電平變化如下,可以看到頻率約為4.3Hz,那麼屏幕的刷新率大概為8.6Hz(fps)。

 

 

因為我們的目的是使用單片機在這款顯示屏上播放視頻,而一般視頻的幀率需要大於25fps,幀率過低就會有卡頓的感覺。顯然,上面提到屏幕8.6Hz的刷新率是比較低的,因此我們需要做一些優化。

比較直觀且容易的優化方式之一,就是提高時鐘頻率,把11.0592M提高到24M或者27M的時鐘頻率。

第二個優化方法就是優化繪圖函數。

先來看看iic的開始信號和結束信號的代碼:

void iic_start() //開始iic
{
    scl = 1;
    sda = 1;
    delay_5us();
    sda = 0;
    delay_5us();
    scl = 0;
}
void iic_stop() //停止iic
{
    scl = 0;
    sda = 0;
    scl = 1;
    delay_5us();
    sda = 1;
    delay_5us();
}

可以看到,裡面有一些延時5微妙(delay_5us),其實這個不是必須的,去掉這個延時,iic同樣可以正常通信。因此,去掉這個延時,可以加快顯示屏的刷新速率。

 

其次,我們再看看賣家提供繪圖部分的函數:

/***********功能描述:顯示顯示BMP圖片128×64起始點坐標(x,y)*****************/
void oled_drawbmp(unsigned char bmp[]) //畫圖
{
    unsigned int j = 0;
    unsigned char x, y;
    for (y = 0; y < 8; y++)
    {
        oled_set_pos(0,  y);
        for (x = 0; x < 128; x++)
        {
            iic_writedata(bmp[j++]);
        }
    }
}

而iic_writedata的實現如下:

void iic_writedata(unsigned char iic_data) //寫數據
{
        iic_start();
    write_byte(0x78);
    write_byte(0x40);
    write_byte(iic_data);
    iic_stop();
}

可以看到,每寫一次圖像數據bmp[j],都會有一次iic開始與結束動作,也都會先發送兩個控制指令(0x78, 0x40),這其實沒有必要,優化後的函數如下:

// 快速繪製圖像
void oled_drawbmp_fast(unsigned char BMP[])
{
    unsigned int j = 0;
    unsigned char x, y;
    for (y = 0; y < 8; y++)
    {
        oled_set_pos(0, y);
        iic_start();
        write_byte(0x78);
        write_byte(0x40);
        for (x = 0; x < 128; x++)
        {
            write_byte(BMP[j++]);
        }
        iic_stop();
    }
}

可以看到,上面的函數減少了啟動iic、結束iic,減少了寫控制命令(0x78, 0x40)。使用跟之前同樣的測試方法,經過上述的優化,最終屏幕的刷新率如下:

 

 

上述的結果為,使用27M時鐘頻率,加上上面提到的幾點優化,可以看出最終屏幕的刷新率約為34.5*2=69Hz(fps),這已經滿足我們播放視頻所需的屏幕刷新率了。

 

另外,再提一點,其實還可以進一步優化,使得屏幕刷新率達到100fps以上,測試結果如下。在這裡賣個關子,感興趣可以去B站看下,視頻地址為:51單片機播放視頻-原理介紹

 

 

視頻轉碼成十六進位格式

單片機播放視頻,我們需要將視頻轉碼為單片機可以讀取的十六進位數據。

首先我們需要將視頻分解為一幀一幀的圖像,然後可以用如下的取模軟體獲得圖像的十六進位字模。

 

 

但是,由於視頻的幀數比較多,我們一幀一幀手動的使用取模軟體獲取字模,顯然是一個比較累的活。因此,我們可以寫個python代碼,批量生成每幀畫面的十六進位數據。python代碼如下:

import cv2

def bit2num(pixcels):
   output_val = 0
   for i, pix in enumerate(pixcels):
      if pix > 128: # 白色
         output_val += pow(2, i)
   return output_val

def main():
   video_path = 'BadApple.flv'
   cap = cv2.VideoCapture(video_path) # 打開視頻
   cnt = 0
   fout = open('bad_apple_data.txt', 'w') # 
   while True:
      ret, frame = cap.read()       # 一幀一幀的讀取
      if not ret:
         break
      cnt += 1
      frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 轉換為灰度的畫面
      frame = cv2.resize(frame, (128, 64))  # 圖像尺寸調整到128*64大小
      convert_val = []
      for row in range(0, 8):   # page0 ~ page7
         for col in range(0, 128): # seg0 ~ seg127
            cur_data = frame[row*8: row*8+8, col] # 取出對應的8個像素點
            convert_val.append(str(bit2num(cur_data))) # 轉換成8位的數據
      
      fout.write("%s\n"%(','.join(convert_val)))

      # cv2.imshow("capture", frame)  #顯示畫面
      # if cv2.waitKey(30) & 0xff == ord('q'): #按q退出
      #    break

main()

代碼比較簡單,其中會調用OpenCV的庫,用來讀取視頻以及視頻畫面對應的像素值。

 

串口發送

視頻數據準備好之後,我們需要把視頻數據通過串口發送給單片機,單片機接收到完整的一幀數據(一幀畫面)之後,就可以開始顯示畫面。我們同樣可以寫個python代碼來將視頻數據發送給單片機,代碼如下:

import serial  # 導入串口相關的庫
from time import sleep

def get_bmp_data():
    filepath = 'bad_apple_data.txt'
    f = open(filepath)
    bmp_data = []
    for line in f:
        val = line.strip().split(',')
        if len(val) == 0:
            continue
        bmp_data.append([int(x) for x in val])
    f.close()
    return bmp_data

def main():
    com = serial.Serial('com10', 345600, timeout=10) # 設置埠號,波特率,超時時間
    if not com.isOpen():  # 判斷埠是否打開成功
        raise "埠打開失敗"

    bmp_data = get_bmp_data() # 讀取剛剛生成的TXT文件
    for frame in bmp_data: # 一幀一幀的發送數據
        ret = com.write(bytes(frame)) # 將數據轉換成二進位後發送
        sleep(0.03) # 延時適當時間

main()

 

代碼比較簡單,其中會調用串口相關的serial庫,然後每次迴圈發送一幀數據,直至全部發送完成。

 

小結

最後,總結一下 在51單片機播放視頻的大致流程:

  1. 視頻解碼成一幀幀的圖像,然後再轉碼成顯示屏可以顯示的十六進位格式(這一步可以提前完成)

  2. 電腦通過串口把十六進位格式的視頻數據發送給單片機

  3. 單片機接收到完整的一幀數據(一幅圖像)後,顯示屏開始顯示畫面

  4. 同時,蜂鳴器播放音樂(可選)

 

註:

想進一步瞭解細節的朋友,可以去B站看下我上傳的視頻,裡面的介紹更加詳細一點,也有最終的效果演示。

51單片機播放視頻-原理介紹

51單片機之超級簡易視頻播放器

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 所謂軟體過程模型就是一種開發策略,這種策略針對軟體工程的各個階段提供了一套範形,使工程的進展達到預期的目的。對一個軟體的開發無論其大小,我們都需要選擇一個合適的軟體過程模型,這種選擇基於項目和應用的性質、採用的方法、需要的控制,以及要交付的產品的特點。一個錯誤模型的選擇,將迷失我們的開發方向。 ...
  • package com.oop.demo03;public class Pet { String name; int age; public void shout(){ System.out.println("叫了一聲"); }} package com.oop.demo03;import com. ...
  • 模板(不深挖哦,是最最淺的) 基本概念 模板就是建立通用的模具,大大提高復用性(類型參數化) 模板不能直接使用,它是一個框架 模板的通用不是萬能的 函數模板 C++另一種編程思想為泛型編程,主要是利用模板技術 語法 template<typename T> //聲明模板<typename 數據類型名 ...
  • 一、前言&背景 1、項目原因需要在windows系統搭建jenkins打包部署java項目(旋了一瓶二鍋頭也沒想明白為什麼要用windows部署項目) 2、這篇文章包含打包後創建tag用於版本回滾、通過SSH推送到遠程win10部署操作 3、本次用的是打jar包方式 4、既然分給我了就硬著頭皮上吧, ...
  • 選擇排序 非穩定版本與穩定版本 排序過程中選擇一個比較大(大到小排序)的數,然後把它放到數組中指定的位置;這時候可以直接與數組中指定位置交換數據,但是可能會導致同值的數據的順序發生改變,這就是所謂的“不穩定”。可以通過下圖來理解所謂的“穩定”和“非穩定”。 不穩定排序演算法按數字排序時,會打亂原本同值 ...
  • Springboot 整合 MyBatisPlus[詳細過程] 提要 這裡已經將Springboot環境創建好 這裡只是整合MyBatis過程 引入Maven依賴 添加MyBatisPlus啟動依賴,添加mysql-connector-java依賴 <!-- mybatis-plus --> <de ...
  • 程式計數器、虛擬機棧、本地方法棧三個區域隨著線程的創建而創建、執行完成銷毀,棧中的棧幀隨著放大的進入和退出執行入棧與出棧,每個棧幀分配多少記憶體基本上是在類結構確定下來時已知,因此這幾個區域的記憶體分配與回收都具備確定性。Java堆中存放的所有對象的實例,只有在程式運行期間我們才會知道會創建哪些對象,這 ...
  • 微軟商店下載的python不能修改config的解決方法 找到圖中文件的位置 C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.9_3.9.3312.0_x64__qbz5n2kfra8p0\\pip.ini 右鍵屬性 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...