日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

树莓派-光立方

發(fā)布時(shí)間:2023/12/9 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 树莓派-光立方 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

LED CUBE. (Driven by RaspberryPi and 74HC154 chip)

【驅(qū)動(dòng)程序 + 20多種特效】【C++】

一、GitHub地址

Leopard-C/LedCube

二、原理圖

原理圖(pdf)

我用立創(chuàng)EDA自己畫的,并不專業(yè),不過還是比較清晰的。

制作教程,參考視頻:BV1Ex411C718

演示視頻:

BV1Kz411B7KT

三、核心類LedCube解析(src/driver/cube.h)

程序運(yùn)行大概的流程:

類LedCube中有一個(gè)后臺線程,不停的掃描光立方。實(shí)際上,任何時(shí)刻,都只有一個(gè)LED燈被點(diǎn)亮,但是利用人眼的視覺暫留原理,只要掃描得足夠快,就能看到多個(gè)LED燈被點(diǎn)亮。

static void backgroundThread();

類中有兩個(gè)三維數(shù)組,存儲(chǔ)坐標(biāo)(z, x, y)處的LED燈的狀態(tài)。

using LedState = char; enum LED_State : char { LED_OFF = 0, LED_ON = 1 };// [z][x][y], 用于后臺掃描線程,真正表示光立方的狀態(tài) LedState leds[8][8][8]; // 緩沖區(qū),用于主線程 LedState ledsBuff[8][8][8];

類中提供的對LED燈的操作,都是對ledsBuff數(shù)組的修改,而后臺掃描線程使用的是leds數(shù)組。

只有調(diào)用update()函數(shù),將ledsBuff一次性拷貝到leds數(shù)組,才能真正改變光立方的狀態(tài)。

void LedCube::update() {mutex_.lock();memcpy(leds, ledsBuff, 512);mutex_.unlock(); }

下面介紹以下該類對外提供的接口:

2.1 setup()

初始化。

事實(shí)上,整個(gè)程序,只有一個(gè)LedCube的全局對象,定義在main函數(shù)所在的文件中,在其他地方通過extern關(guān)鍵字進(jìn)行聲明:

// main.cpp LedCube cube;// other files extern LedCube cube;

在主函數(shù)調(diào)用setup()函數(shù),用于初始化74HC154芯片、熄滅所有LED燈等。

2.2 update()

對光立方做一系列修改后,只有調(diào)用update()函數(shù),才能真正起作用。

2.3 quit()

退出函數(shù),執(zhí)行清理工作,正常退出的話,會(huì)由析構(gòu)函數(shù)調(diào)用。

非正常退出,比如捕獲到Ctrl+C發(fā)出的SIGINIT信號,應(yīng)該主動(dòng)調(diào)用該函數(shù)進(jìn)行清理,否則程序退出時(shí)可能有一些LED仍然亮著。

2.4 clear()

熄滅所有LED燈。

2.5 修改(x,y,z) 處LED燈狀態(tài)

LedState& operator()(int x, int y, int z); LedState& operator()(const Coordinate& coord);

如何使用:

LedCube cube; cube(2, 5, 7) = LED_ON; cube(6, 6, 3) = LED_OFF; Coordinate coord = { 1, 4, 5 }; cube(coord) = LED_OFF;

2.6 點(diǎn)亮某一個(gè)面(Layer)

可以是垂直于x或y或z軸的任何一個(gè)面。

(1)整個(gè)面的LED燈狀態(tài)相同

void lightLayerX(int x, LedState state); void lightLayerY(int y, LedState state); void lightLayerZ(int z, LedState state);

(2)顯示圖像

void lightLayerX(int x, const std::array<std::array<char,8>>& image); void lightLayerY(...); void lightLayerZ(...);

其中參數(shù)image是一個(gè)8x8的數(shù)組,剛好對應(yīng)光立方的一個(gè)面(8x8=64個(gè)LED燈)。

(2)顯示圖像(指定圖像在圖像庫的編碼)

如顯示數(shù)字、字母、和自定義的圖案。

void lightLayerX(int x, int imageCode, Direction viewDirection, Angle rotate); void lightLayerY(...) void lightLayerZ(...)
  • imageCode:圖像編碼,在src/utility/image_lib.cpp中可以找到,即std::map的鍵。
  • viewDirection:從哪個(gè)方向觀察這個(gè)圖像,如X_ASCEND表示沿著x軸正向的方向觀察該圖像。
  • rotate:旋轉(zhuǎn),支持:
    • ANGLE_0:不旋轉(zhuǎn)
    • ANGLE_90:順時(shí)針旋轉(zhuǎn)90度
    • ANGLE_180:順時(shí)針旋轉(zhuǎn)180度
    • ANGLE_270:順時(shí)針旋轉(zhuǎn)270度

也就是說,在任何一個(gè)垂直于x或y或z軸的面上,都可以有 2×4=82 \times 4 = 82×4=8 種方式顯示一個(gè)圖案。

  • 2種視角:沿著軸的正向還是負(fù)向
  • 4種旋轉(zhuǎn)角度:0、90、180、270
// file: src/utility/image_lib.cpp std::map<int, std::array<std::array<char, 8>, 8>> ImageLib::table = {{ '0', util::toBinary({ 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C }) },{ '1', util::toBinary({ 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C }) },{ '2', util::toBinary({ 0x1C, 0x22, 0x02, 0x02, 0x1C, 0x20, 0x20, 0x3E }) },// ...{ '9', util::toBinary({ 0x1C, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x22, 0x1C }) },{ 'A', util::toBinary({ 0x00, 0x1C, 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22 }) },{ 'B', util::toBinary({ 0x00, 0x3C, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x3C }) },{ 'C', util::toBinary({ 0x00, 0x1C, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1C }) },// ...{ 'Z', util::toBinary({ 0x00, 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3E }) },// 自定義的圖案// 直徑為3的圓{ Image_Circle_Solid_3, util::toBinary({ 0x00, 0x18, 0x3C, 0x7E, 0x7E, 0x3C, 0x18, 0x00 }) },// 8x8的實(shí)心矩形(8x8=64個(gè)LED燈全部點(diǎn)亮){ Image_Fill , util::toBinary({ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) }, };

2.7 點(diǎn)亮一行或一列

(1)一行或一列全部點(diǎn)亮,或者全部熄滅

void lightRowXY(int x, int y, LedState); void lightRowYZ(int y, int z, LedState); void lightRowXZ(int x, int z, LedState);

(2)分別指定一行或一列8個(gè)LED燈的狀態(tài)

void lightRowXY(int x, int y, const std::array<LedState,8>& states); void lightRowYZ(...); void lightRowXZ(...);// 例如下面一行代碼,將點(diǎn)亮 x==5 && y==7 那一列的LED燈,隔一個(gè)亮一個(gè) // LED_ON==1,表示點(diǎn)亮 // LED_OFF==0, 表示熄滅 lightXY(5, 7, { 1, 0, 1, 0, 1, 0, 1, 0 });

2.8 點(diǎn)亮/熄滅一條空間直線

void lightLine(const Coordinate& start, const Coordinate& end, LedState state);
  • start:線的起點(diǎn) (x1, y1, z1)
  • end:線的終點(diǎn)(x2, y2, z2)

該函數(shù)實(shí)際上調(diào)用了src/utility/utils.h中的getLine3D函數(shù)。

使用的是 Bresenham生成線 算法。

void getLine3D(const Coordinate& start, const Coordinate& end, std::vector<Coordinate>& line);

給定線段的起點(diǎn)和終點(diǎn),該函數(shù)會(huì)返回這條線段上的所有點(diǎn)(整數(shù)坐標(biāo))。

獲取到所有點(diǎn)后,設(shè)置些點(diǎn)處的LED燈的狀態(tài)即可。

2.9 繪制正方形 / 矩形

void lightSquare(const Coordinate& A, const Coordinate& B, FillType fillType);
  • AB:矩形的對角線
  • fillType:填充類型
    • FILL_SOLID:實(shí)心
    • FILL_SURFACE:實(shí)心
    • FILL_EDGE:邊界(無填充)

2.10 繪制立方體 / 長方體

void lightCube(const Coordinate& A, const Coordinate& B, FillType fillType);
  • AB:長方體的對角線
  • fillType:填充類型
    • FILL_SOLID:實(shí)心
    • FILL_SURFACE:只填充面(不填充內(nèi)部)
    • FILL_EDGE:只有邊界(面和內(nèi)部均無填充)

2.11 復(fù)制 / 移動(dòng)一個(gè)面

void copyLayerX(int xFrom, int xEnd, bool clearXFrom = false); void copyLayerY(...); void copyLayerZ(...);
  • xFrom:面的原始位置,即面x=xFrom
  • xEnd:面的目標(biāo)位置,即面x=xEnd
  • clearXFrom:是否清空原來的面
    • true:移動(dòng)
    • false:復(fù)制

2.12 setLoopCount(int count)

void setLoopCount(int count) {this->loopCount = count; }

達(dá)到的效果是:控制燈的明暗程度。

這里假設(shè)有兩個(gè)閾值, $ 0 < C1 < C2 < +\infty$

  • 當(dāng)count < C1時(shí),count越小,LED燈越
  • 當(dāng)count > C2時(shí),count越大,LED燈越
  • 當(dāng)C1 < count < C2時(shí),LED比較亮,且亮度變化不大,肉眼無法辨別。

這里的C1,C2很難確定,而且影響亮度的因素比較多。

但是經(jīng)過測試,C1≈100,C2≈200C1 \approx 100,C2 \approx 200C1100C2200

這里的count實(shí)際上影響的是每個(gè)LED燈點(diǎn)亮的時(shí)間。因?yàn)槿魏我粋€(gè)時(shí)間都只有一個(gè)LED燈被點(diǎn)亮,后臺線程在不斷掃描整個(gè)光立方,即循環(huán)512次,逐一判斷每個(gè)LED燈是否需要點(diǎn)亮。

每個(gè)LED燈被點(diǎn)亮后都會(huì)暫停一段時(shí)間(很短),然后熄滅該LED燈,去點(diǎn)亮下一個(gè)需要被點(diǎn)亮的LED燈。

這里的暫停一段時(shí)間是通過空語句循環(huán)實(shí)現(xiàn)的

// 這里的loopCount,就是通過setLoopCount(int count)設(shè)置的 for (int i = 0; i < loopCount; ++i) {// ; }

在樹莓派上,根據(jù)測算,一次空語句循環(huán)需要5~6ns,默認(rèn)的loopCount=150,也就是相當(dāng)于暫停800ns。

loopCount越大或越小都會(huì)導(dǎo)致LED偏暗,而且過大時(shí)還會(huì)有其他副作用,如下:

  • loopCount越小:每個(gè)LED燈被點(diǎn)亮的時(shí)間越短,看起來越暗。但是經(jīng)過測試,loopCount在100~200之間LED燈的亮度變化不大,小于100,甚至說小于50才會(huì)觀察到變暗。在loopCount在5左右時(shí),LED基本完全不亮。
  • loopCount越大,每個(gè)LED燈被點(diǎn)亮的時(shí)間越長,但是,相應(yīng)的,對光立方進(jìn)行一次掃描耗時(shí)也越長,這就導(dǎo)致每個(gè)LED燈兩次被點(diǎn)亮之間的間隔變長,即不供電的時(shí)間變長,這也會(huì)導(dǎo)致LED燈看起來偏暗。
  • loopCount越大,還有一個(gè)副作用,就是LED燈的亮度和當(dāng)前光立方中被點(diǎn)亮的LED燈數(shù)量有關(guān)。被點(diǎn)亮的LED燈越多,掃描一次光立方的時(shí)間越長(只有在被點(diǎn)亮的LED燈處會(huì)執(zhí)行暫停程序,如果某個(gè)LED燈為熄滅狀態(tài),直接跳過),再加之每次“暫停”的時(shí)間很長,因此出現(xiàn)的一個(gè)現(xiàn)象就是,被點(diǎn)亮的LED燈少時(shí),LED燈特別亮,被點(diǎn)亮的LED燈多時(shí),LED燈特別暗,對比十分明顯。

這里之所以使用空語句循環(huán)來執(zhí)行延時(shí)(“暫?!?#xff09;,是因?yàn)橹挥羞@樣才能做到納秒級延時(shí)(雖然并不精確)。

如果使用sleep()、usleep()、nanosleep()、,尤其是nanosleep(),雖然函數(shù)的目的時(shí)暫停納秒級的時(shí)間,但是其暫停時(shí)間都在微秒以上(在樹莓派上50微秒)。

包括C++11提供的,std::this_thread::sleep_for(std::chrono::nanoseconeds(xxx));

也就是說,即使我寫的程序是 sleep_for(nanoseconds(1))之類的,想要暫停1ns,實(shí)際上也會(huì)暫停50微秒,也就是這個(gè)參數(shù)在0~50000之間,程序全都會(huì)暫停50微秒左右。這可就太可怕了,如果需要同時(shí)點(diǎn)亮256個(gè)LED燈,那每次掃描的時(shí)間將是50us×256=12800us=12.8ms50us \times 256 = 12800us = 12.8ms50us×256=12800us=12.8ms,這個(gè)時(shí)間已經(jīng)太長了,一個(gè)發(fā)光的LED燈,經(jīng)過這個(gè)時(shí)間基本已經(jīng)很暗或者熄滅了。

剛寫程序時(shí)一直困擾在這里,每次點(diǎn)亮的LED燈變多時(shí),LED燈都會(huì)特別暗,1個(gè)LED燈時(shí)特別刺眼,200個(gè)LED燈就已經(jīng)明顯變暗了。本來都想放棄了呢,后來,逐一判斷到底是哪一條語句這么耗時(shí),一開始以為是 digitalWrite函數(shù)的原因或者74HC154芯片反應(yīng)慢之類的,后來才定位到sleep_for(nanoseconds(100))這個(gè)延時(shí)語句上。然后就去網(wǎng)上搜了一下,了解到精確的納秒級暫停目前很難實(shí)現(xiàn)的,因?yàn)閳?zhí)行到暫停語句會(huì)牽扯到中斷、時(shí)間片切換,還有內(nèi)核調(diào)用(要從用戶空間切換到內(nèi)核再返回)(大概是這些吧,我不是專業(yè)的。。。),反正意思就是,你想暫停幾納秒、幾十幾百納秒,做不到!!!

可以看一下以下兩個(gè)網(wǎng)頁

  • https://frenchfries.net/paul/dfly/nanosleep.html
  • https://stackoverflow.com/questions/18071664/stdthis-threadsleep-for-and-nanoseconds
  • 四、特效

    Effect基類,其他特效類都繼承自該類,需要重寫以下兩個(gè)虛函數(shù)

    // 特效是如何顯示的 virtual void show();// 從一個(gè)文件流(文件指針fp當(dāng)前位置,可能并非文件頭)解析特效的參數(shù) virtual bool readFromFP(FILE* fp);

    每個(gè)特效基本上都有一個(gè)Event類,用于描述一組特效參數(shù)。

    下面以src/effect/layer_scan.h為例

    LayerScanEffect類實(shí)現(xiàn)的特效是:按照某一個(gè)圖案逐層(沿x軸或y軸或z軸)掃描光立方。

    // 關(guān)于Event部分的代碼 class LayerScanEffect : public Effect { public: struct Event {Event(Direction view, Direction scan, Angle r, int together, int interval1, int interval2);Direction viewDirection;Direction scanDirection;Angle rotate;int together;int interval1;int interval2;};void setEvents(const std::vector<Event>& events) {events_ = events;}protected:std::vector<Event> events_; };

    這里Event類的成員變量的意思是:

    • viewDirection:視角,就是你注視該圖案的方向,沿哪個(gè)軸的哪個(gè)方向(X_ASCEND、X_DESCNED、Y_ASCEND、Y_DESCEND、Z_ASCEND、Z_DESCEND)
    • scanDirection:掃描的方向(圖案移動(dòng)的方向)
    • rotate:圖案的旋轉(zhuǎn)角度
    • together:一次移動(dòng)多少層
    • interval1:每次移動(dòng)的時(shí)間間隔(單位毫秒)
    • interval2:掃描結(jié)束后暫停的時(shí)間(單位毫秒)

    (PS. 基本上每個(gè)特效都至少有interval,interval2兩個(gè)參數(shù),事實(shí)上,大多數(shù)特效都有四五個(gè)甚至更多參數(shù),通過不同參數(shù)的組合,即一個(gè)Event對象,可以顯示出不一樣的效果,雖然是同一類特效)

    四、EML文件

    為了更方便的創(chuàng)造出不同參數(shù)的(同一大類)特效,我創(chuàng)造了一種新的文本文件類型EML,Effect Markup Language。每個(gè)特效類都支持從eml文件讀取參數(shù)。

    下面看一個(gè)簡單的eml例子:

    <##>------------------------------- Count Down --------------------------------<LayerScan> <IMAGESCODE><####> imageCode<CODE> 5<CODE> 4<CODE> 3<CODE> 2<CODE> 1 <END_IMAGESCODE> <EVENTS><#####> viewDirection scanDirection rotate together interavl1 interval2<EVENT> X_DESCEND X_ASCEND ANGLE_0 1 125 125 <END_EVENTS> <END><##>------------------------------- Drop Line --------------------------------<DropLine> <IMAGESCODE><CODE> IMAGE_FILL <END_IMAGESCODE> <EVENTS><#####> viewDirection dropDirection lineParallel rotate together interval1 interval2<EVENT> X_ASCEND X_ASCEND PARALLEL_Y ANGLE_0 3 30 30<EVENT> X_ASCEND X_DESCEND PARALLEL_Y ANGLE_0 3 30 30<EVENT> X_ASCEND X_ASCEND PARALLEL_Z ANGLE_0 3 30 30<EVENT> X_ASCEND X_DESCEND PARALLEL_Z ANGLE_0 3 30 30<EVENT> Z_ASCEND Z_ASCEND PARALLEL_X ANGLE_0 3 30 30<EVENT> Z_ASCEND Z_DESCEND PARALLEL_X ANGLE_0 3 30 30<EVENT> Z_ASCEND Z_ASCEND PARALLEL_Y ANGLE_0 3 30 30<EVENT> Z_ASCEND Z_DESCEND PARALLEL_Y ANGLE_0 3 30 30 <END_EVENTS> <END><END><END>
  • 以<#開頭的行是注釋行,忽略,也就是說, <#,<#>,<##>,<####>等開頭的都是注釋。
  • <COMMENT>和<END_COMMENT>行之間的所有行都是注釋,忽略。
  • 不區(qū)分大小寫
  • <EVENTS> 和 <END_EVENTS>之間是一系列 <EVENT>
  • 以<EVENT>開頭的是一組特效參數(shù)(注意有個(gè)空格)
  • <IMAGESCODE>和END_IMAGESCODE>之間是一系列<CODE>
  • 以<CODE>開頭的是一個(gè)圖案的代碼,如Letter_A或者A都是表示字母A的圖案,IMAGE_FILL表示8x8完全填充的正方形,NUM_0或者0表示數(shù)字0的圖案,以及其他自定義的圖案代碼。
  • <EML>表示在此處插入其他eml文件。
  • <Script>表示在此處插入script文件(也是自己定義的一種文件類型,屬于腳本語言,一行表示一條語句,每條語句的功能就是調(diào)用LedCube類中的相應(yīng)的函數(shù))。
  • <END>表示這一種特效結(jié)束。
  • <END><END>表示文件結(jié)束,忽略之后的所有內(nèi)容
  • 解析eml文件的容錯(cuò)能力比較低,只會(huì)簡單地進(jìn)行語法檢查,應(yīng)該保證傳入的eml文件沒有語法錯(cuò)誤。

    六、展示(圖片)

    (光立方做的比較丑,emmm,關(guān)鍵是特效代碼嘛!)

    (第一次使用錫焊,足足用了兩卷焊錫,一開始經(jīng)常焊不上,掉的錫得有1/3,后來慢慢掌握了技巧。孰能生巧,第一次使用錫焊就焊了1000多個(gè)焊點(diǎn),學(xué)會(huì)了錫焊,哈哈哈!)



    七、展示(視頻)

    BV1Kz411B7KT

    【光立方】【樹莓派】特效展示,20+種

    END

    leopard.c@outlook.com

    總結(jié)

    以上是生活随笔為你收集整理的树莓派-光立方的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。