電子產(chǎn)業(yè)一站式賦能平臺

PCB聯(lián)盟網(wǎng)

搜索
查看: 66|回復: 0
收起左側(cè)

用模塊化和面向?qū)ο蟮姆绞,編寫單片機LCD驅(qū)動程序

[復制鏈接]

449

主題

449

帖子

582

積分

二級會員

Rank: 2

積分
582
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2024-9-19 17:50:00 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
我是老溫,一名熱愛學習的嵌入式工程師5 C9 i( N$ ~, n- P
關(guān)注我,一起變得更加優(yōu)秀!% |$ j+ z2 K' h0 Z
# K! m. M- @: N% ~3 V
來源 | 屋脊雀2 L$ C- h# o! T1 A4 m3 W1 a# M% `
網(wǎng)絡上配套STM32開發(fā)板有很多LCD例程,主要是TFT LCD跟OLED的。從這些例程,大家都能學會如何點亮一個LCD。但這代碼都有下面這些問題:
, x3 _' N* T( i( V
  • 分層不清晰,通俗講就是模塊化太差。
  • 接口亂。只要接口不亂,分層就會好很多了。
  • 可移植性差。
  • 通用性差。為什么這樣說呢?如果你已經(jīng)了解了LCD的操作,請思考如下情景:
    4 N2 j1 k4 |! _* O5 H- [1、代碼空間不夠,只能保留9341的驅(qū)動,其他LCD驅(qū)動全部刪除。能一鍵(一個宏定義)刪除嗎?刪除后要改多少地方才能編譯通過?3 X- ^8 ]' V7 m- \* ?
    2、有一個新產(chǎn)品,收銀設備。系統(tǒng)有兩個LCD,都是OLED,驅(qū)動IC相同,但是一個是128x64,另一個是128x32像素,一個叫做主顯示,收銀員用;一個叫顧顯,顧客看金額。怎么辦?這些例程代碼要怎么改才能支持兩個屏幕?全部代碼復制粘貼然后改函數(shù)名稱?這樣確實能完成任務,只不過程序從此就進入惡性循環(huán)了。
    $ U0 I+ J3 x6 j; D: z+ T: k6 ]' v2 K5 c3、一個OLED,原來接在這些IO,后來改到別的IO,容易改嗎?
    ) q7 F* a# a2 L5 j9 v4、原來只是支持中文,現(xiàn)在要賣到南美,要支持多米尼加語言,好改嗎?' N2 A+ E3 l; ~3 I0 k* B
    LCD種類概述在討論怎么寫LCD驅(qū)動之前,我們先大概了解一下嵌入式常用LCD。概述一些跟驅(qū)動架構(gòu)設計有關(guān)的概念,在此不對原理和細節(jié)做深入討論,會有專門文章介紹,或者參考網(wǎng)絡文檔。
    5 Z/ F1 |1 t# M* z* C9 Z8 kTFT lcdTFT LCD,也就是我們常說的彩屏。通常像素較高,例如常見的2.8寸,320X240像素。4.0寸的,像素800X400。這些屏通常使用并口,也就是8080或6800接口(STM32 的FSMC接口);或者是RGB接口,STM32F429等芯片支持。其他例如手機上使用的有MIPI接口。
      S% ^. A4 k: i$ o總之,接口種類很多。也有一些支持SPI接口的。除非是比較小的屏幕,否則不建議使用SPI接口,速度慢,刷屏閃屏。玩STM32常用的TFT lcd屏幕驅(qū)動IC通常有:ILI9341/ILI9325等。
    / v/ ~) Q" B" e, M$ w6 s2 ntft lcd:% E* |" p7 i  Q- R# J2 R+ Z* Q4 o& q

    ( X8 j' D* Z! D" \, UIPS:, N& i$ R& p  d, Q0 w* X
    , t; [6 ~9 [. H
    COG lcd很多人可能不知道COG LCD是什么,我覺得跟現(xiàn)在開發(fā)板銷售方向有關(guān)系,大家都出大屏,玩酷炫界面,對于更深的技術(shù),例如軟件架構(gòu)設計,都不涉及。使用單片機的產(chǎn)品,COG LCD其實占比非常大。COG是Chip On Glass的縮寫,就是驅(qū)動芯片直接綁定在玻璃上,透明的。實物像下圖:
    : Z0 U; S' f% X8 a/ k 8 ?" D1 o/ K7 y/ q/ r# w2 b
    這種LCD通常像素不高,常用的有128X64,128X32。一般只支持黑白顯示,也有灰度屏。+ ?! p4 r2 Y1 D  X  d+ D
    接口通常是SPI,I2C。也有號稱支持8位并口的,不過基本不會用,3根IO能解決的問題,沒必要用8根吧?常用的驅(qū)動IC:STR7565。+ A. K# U* ~$ Z$ x; C. h' N: _  L
    OLED lcd買過開發(fā)板的應該基本用過。新技術(shù),大家都感覺高檔,在手環(huán)等產(chǎn)品常用。OLED目前屏幕較小,大一點的都很貴。在控制上跟COG LCD類似,區(qū)別是兩者的顯示方式不一樣。從我們程序角度來看,最大的差別就是,OLED LCD,不用控制背光。。。。。實物如下圖:
    . e' @# U" J3 l
    ) z* F, F! J- v7 \) A& }& a常見的是SPI跟I2C接口。常見驅(qū)動IC:SSD1615。& F- N; R8 ~2 h) X2 ~3 A
    硬件場景接下來的討論,都基于以下硬件信息:
    - m; c! ]3 R, x( D1 m1、有一個TFT屏幕,接在硬件的FSMC接口,什么型號屏幕?不知道。
    % m  x" ~6 y+ Z, X2、有一個COG lcd,接在幾根普通IO口上,驅(qū)動IC是STR7565,128X32像素。
    , X! _- w" |% }5 Q3、有一個COG LCD,接在硬件SPI3跟幾根IO口上,驅(qū)動IC是STR7565,128x64像素。
    ( e2 L2 ~$ H$ V% X8 \" A: f% J, u4、有一個OLED LCD,接在SPI3上,使用CS2控制片選,驅(qū)動IC是SSD1315。, ]! q7 r- m6 e9 U6 f0 _! u
    . V2 x! _& ~, y9 R+ z: w
    預備知識在進入討論之前,我們先大概說一下下面幾個概念,對于這些概念,如果你想深入了解,請GOOGLE。# E$ @  A0 P! {/ S; {, B
    面向?qū)ο竺嫦驅(qū)ο螅蔷幊探绲囊粋概念。什么叫面向?qū)ο竽兀烤幊逃袃煞N要素:程序(方法),數(shù)據(jù)(屬性)。例如:一個LED,我們可以點亮或者熄滅它,這叫方法。LED什么狀態(tài)?亮還是滅?這就是屬性。我們通常這樣編程:
    # V( p+ F5 _' mu8 ledsta = 0;0 s( H" j% C+ A  N
    void ledset(u8 sta)
    0 x, L2 C" ~9 v/ K- Z/ b; M/ I6 G{
    0 k" o0 M6 h: j5 T" [4 v}
    3 ^+ Q# }+ }# ]6 Z這樣的編程有一個問題,假如我們有10個這樣的LED,怎么寫?這時我們可以引入面向?qū)ο缶幊,將每一個LED封裝為一個對象?梢赃@樣做:
    $ Z) I& D+ x4 `7 J" {( y2 F) j# R/*% U4 Q: ~( O8 E& w$ @! J
    定義一個結(jié)構(gòu)體,將LED這個對象的屬性跟方法封裝。* M5 w& O7 f$ y, e* N# k) @1 O
    這個結(jié)構(gòu)體就是一個對象。8 b1 P" U" h7 g; p
    但是這個不是一個真實的存在,而是一個對象的抽象。
    - n% O5 I; X# p/ a1 K" e*/. g0 F# s1 @) _  V& N8 d
    typedef struct{$ ?) A8 C! M- ?. U$ G6 p
        u8 sta;
    ( Q& n5 a  r0 g+ K! m0 ?& y    void (*setsta)(u8 sta);$ g* ]2 b- t' J+ }$ o
    }LedObj;
    & Z1 L+ Y( R% [9 p' W/*  聲明一個LED對象,名稱叫做LED1,并且實現(xiàn)它的方法drv_led1_setsta*/7 W5 U2 c4 \+ X# u$ k
    void drv_led1_setsta(u8 sta)
    ; t/ ^" e+ ~: p4 h+ a: O; D4 P{' A, i0 f6 B. |- N
    }+ \' P& o' f, l
    LedObj LED1={
      _/ M2 n6 g( ~$ M" B        .sta = 0,3 ~: e" Q# O" ?- {' n/ C
            .setsta = drv_led1_setsta,
    * Q* Z, y) [0 ]; j+ |' C    };; G% L2 M* |! K: k% T
    /*  聲明一個LED對象,名稱叫做LED2,并且實現(xiàn)它的方法drv_led2_setsta*/* G5 W" d( ?5 A; M: A7 J3 H
    void drv_led2_setsta(u8 sta)8 j& B: ]5 r+ @" e& \0 ^/ y
    {# ?9 K/ D* u3 O; V8 n# a+ D- `
    }; V2 d: W7 M5 O) ?) E3 n; T/ A
    LedObj LED2={
    4 l% l$ O* q) [8 m( a4 y: V+ F        .sta = 0,' j: T9 H0 C3 u8 O4 [. C  Q
            .setsta = drv_led2_setsta,
    5 f: @; E2 B+ a3 q6 e1 o# Z    };0 e3 Q3 W- Q6 F# W0 }- W4 ~! a
        - L8 n; g% B  K! k% q
    /*  操作LED的函數(shù),參數(shù)指定哪個led*/
    " G6 n% e, P) Uvoid ledset(LedObj *led, u8 sta)
    ; A9 N. m* v5 S) s. h9 E/ R4 p3 E{
    4 z0 L8 X$ \4 k6 G    led->setsta(sta);
    + O7 M5 [& `" \! |+ C}6 ?# Z4 T: h8 n. Z
    是的,在C語言中,實現(xiàn)面向?qū)ο蟮氖侄尉褪墙Y(jié)構(gòu)體的使用。上面的代碼,對于API來說,就很友好了。操作所有LED,使用同一個接口,只需告訴接口哪個LED。大家想想,前面說的LCD硬件場景。4個LCD,如果不面向?qū)ο螅?strong>「顯示漢字的接口是不是要實現(xiàn)4個」?每個屏幕一個?$ }. {- E+ c! S, A+ j3 Y
    驅(qū)動與設備分離如果要深入了解驅(qū)動與設備分離,請看LINUX驅(qū)動的書籍。
    # E9 `  P7 Y$ U( L9 O5 _什么是設備?我認為的設備就是「屬性」,就是「參數(shù)」,就是「驅(qū)動程序要用到的數(shù)據(jù)和硬件接口信息」。那么驅(qū)動就是「控制這些數(shù)據(jù)和接口的代碼過程」。7 d  t' [  D) {7 e8 t6 v" D& i/ S
    通常來說,如果LCD的驅(qū)動IC相同,就用相同的驅(qū)動。有些不同的IC也可以用相同的,例如SSD1315跟STR7565,除了初始化,其他都可以用相同的驅(qū)動。例如一個COG lcd:! Y9 d& m2 V8 j2 J* j) n  x! T  D
    ?驅(qū)動IC是STR7565 128 * 64 像素用SPI3背光用PF5 ,命令線用PF4 ,復位腳用PF3- ^. O5 @/ O; t+ N
    ?
    上面所有的信息綜合,就是一個設備。驅(qū)動就是STR7565的驅(qū)動代碼。
    $ m/ f2 T3 `) a9 R6 Y6 b為什么要驅(qū)動跟設備分離,因為要解決下面問題:
    " d# P6 ~. e  h9 d1 q0 N?有一個新產(chǎn)品,收銀設備。系統(tǒng)有兩個LCD,都是OLED,驅(qū)動IC相同,但是一個是128x64,另一個是128x32像素,一個叫做主顯示,收銀員用;一個叫顧顯,顧客看金額。" e9 L0 N* c8 o, n1 E- W
    ?
    這個問題,「兩個設備用同一套程序控制」才是最好的解決辦法。驅(qū)動與設備分離的手段:
    6 _7 W5 _  F8 `6 n?在驅(qū)動程序接口函數(shù)的參數(shù)中增加設備參數(shù),驅(qū)動用到的所有資源從設備參數(shù)傳入。
    ( M- ^- c7 y5 b$ s* O; F0 Q* T3 c?
    驅(qū)動如何跟設備綁定呢?通過設備的驅(qū)動IC型號。
    , z  U# `7 F( ]模塊化我認為模塊化就是將一段程序封裝,提供穩(wěn)定的接口給不同的驅(qū)動使用。不模塊化就是,在不同的驅(qū)動中都實現(xiàn)這段程序。例如字庫處理,在顯示漢字的時候,我們要找點陣,在打印機打印漢字的時候,我們也要找點陣,你覺得程序要怎么寫?把點陣處理做成一個模塊,就是模塊化。非模塊化的典型特征就是「一根線串到底,沒有任何層次感」
    * o+ L( G" G* v: C6 w$ O- ?8 q4 yLCD到底是什么前面我們說了面向?qū)ο,現(xiàn)在要對LCD進行抽象,得出一個對象,就需要知道LCD到底是什么。問自己下面幾個問題:( O" h" o+ q+ E
  • LCD能做什么?
  • 要LCD做什么?
  • 誰想要LCD做什么?剛剛接觸嵌入式的朋友可能不是很了解,可能會想不通。我們模擬一下LCD的功能操作數(shù)據(jù)流。APP想要在LCD上顯示 一個漢字。
    ( l3 }2 C. a6 N! D' a8 s- g9 G1、首先,需要一個顯示漢字的接口,APP調(diào)用這個接口就可以顯示漢字,假設接口叫做lcd_display_hz。: e8 v9 p- o, e
    2、漢字從哪來?從點陣字庫來,所以在lcd_display_hz函數(shù)內(nèi)就要調(diào)用一個叫做find_font的函數(shù)獲取點陣。
    ) ?4 J9 \0 G% O3、獲取點陣后要將點陣顯示到LCD上,那么我們調(diào)用一個ILL9341_dis的接口,將點陣刷新到驅(qū)動IC型號為ILI9341的LCD上。
    # X" M: g, |4 V+ D/ n1 J- T! A+ {4、ILI9341_dis怎么將點陣顯示上去?調(diào)用一個8080_WRITE的接口。
    2 [0 M1 y. L9 Z9 l+ x9 F" H好的,這個就是大概過程,我們從這個過程去抽象LCD功能接口。漢字跟LCD對象有關(guān)嗎?無關(guān)。在LCD眼里,無論漢字還是圖片,都是一個個點。那么前面問題的答案就是:) ~! i* Z, _4 k% A( N+ s
  • LCD可以一個點一個點顯示內(nèi)容。
  • 要LCD顯示漢字或圖片-----就是顯示一堆點
  • APP想要LCD顯示圖片或文字。結(jié)論就是:所有LCD對象的功能就是顯示點。「那么驅(qū)動只要提供顯示點的接口就可以了,顯示一個點,顯示一片點! 抽象接口如下:) B# F  a) y# k3 ^4 K
    /*& t" c, {7 u3 `! }! i/ _
        LCD驅(qū)動定義. |, y$ x5 W$ b# {
    */# R1 g/ f5 l& p% g; w# n3 ^
    typedef struct  # U+ f7 k8 X' S0 G& Z& k. k
    {
    7 c- z$ d( `& C. X6 t  I    u16 id;+ q1 l% t' S8 d  v5 @' f
        s32 (*init)(DevLcd *lcd);
    , [  G& e2 v- F/ [7 G* l+ v% \    s32 (*draw_point)(DevLcd *lcd, u16 x, u16 y, u16 color);
    8 S  A9 o6 G" p    s32 (*color_fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey, u16 color);
    , j( [+ p- V5 Y/ P, K5 Q    s32 (*fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 *color);
    , M# i3 |* x+ v1 Z. p    s32 (*onoff)(DevLcd *lcd, u8 sta);4 K4 F. y$ \+ m- ~3 i- P3 {
        s32 (*prepare_display)(DevLcd *lcd, u16 sx, u16 ex, u16 sy, u16 ey);" }  [0 \$ n, B( @' I% |
        void (*set_dir)(DevLcd *lcd, u8 scan_dir);
    # D$ y9 p& f4 C# r  @& _( f0 ?    void (*backlight)(DevLcd *lcd, u8 sta);
    ; e" ~6 C9 I' Y}_lcd_drv;' N. d; K1 r7 r. a; z
    上面的接口,也就是對應的驅(qū)動,包含了一個驅(qū)動id號。
    $ F% N0 F' Y1 X! @/ Y
  • id,驅(qū)動型號
  • 初始化
  • 畫點
  • 將一片區(qū)域的點顯示某種顏色
  • 將一片區(qū)域的點顯示某些顏色
  • 顯示開關(guān)
  • 準備刷新區(qū)域(主要彩屏直接DMA刷屏使用)
  • 設置掃描方向
  • 背光控制顯示字符,劃線等功能,不屬于LCD驅(qū)動。應該歸類到GUI層。: r" Z0 @# _6 o5 i( o. p' o- o
    LCD驅(qū)動框架我們設計了如下的驅(qū)動框架:
    ; l  A$ H: n4 b" k5 l3 o$ k2 A$ h
      {0 w/ L- }- H( k6 E設計思路:
    - m  c/ M$ T' E" b9 }- g1、中間顯示驅(qū)動IC驅(qū)動程序提供統(tǒng)一接口,接口形式如前面說的_lcd_drv結(jié)構(gòu)體。3 b. ^! X& H7 L  {8 T$ p/ t" [
    2、各顯示IC驅(qū)動根據(jù)設備參數(shù),調(diào)用不同的接口驅(qū)動。例如TFT就用8080驅(qū)動,其他的都用SPI驅(qū)動。SPI驅(qū)動只有一份,用IO口控制的我們也做成模擬SPI。
    : o* T9 B! d1 \* S! N3、LCD驅(qū)動層做LCD管理,例如完成TFT LCD的識別。并且將所有LCD接口封裝為一套接口。. A) k% g3 Z3 Z# v( k
    4、簡易GUI層封裝了一些顯示函數(shù),例如劃線、字符顯示。* j- n+ t% \4 I: u4 c
    5、字體點陣模塊提供點陣獲取與處理接口。
    6 L' k$ e- j4 B2 m/ O由于實際沒那么復雜,在例程中我們將GUI跟LCD驅(qū)動層放到一起。TFT LCD的兩個驅(qū)動也放到一個文件,但是邏輯是分開的。OLED除初始化,其他接口跟COG LCD基本一樣,因此這兩個驅(qū)動也放在一個文件。
    . V2 k# t8 X2 p# ?* T2 `4 ~4 t代碼分析代碼分三層:/ L! S1 ~6 ^6 A/ ^) g
    1、GUI和LCD驅(qū)動層 dev_lcd.c dev_lcd.h) e+ r3 q0 Z% h
    2、顯示驅(qū)動IC層 dev_str7565.c & dev_str7565.h dev_ILI9341.c & dev_ILI9341.h
    $ |5 J! B0 y& q7 |8 L0 R2 v/ f3、接口層 mcu_spi.c & mcu_spi.h stm324xg_eval_fsmc_sram.c & stm324xg_eval_fsmc_sram.h
    % ~$ r3 c( [' E: S6 c! LGUI和LCD層這層主要有3個功能 :* \5 F6 a' H+ N6 q) @6 n/ S
    「1、設備管理」: T5 {% e3 B+ _
    首先定義了一堆LCD參數(shù)結(jié)構(gòu)體,結(jié)構(gòu)體包含ID,像素。并且把這些結(jié)構(gòu)體組合到一個list數(shù)組內(nèi)。
    " u3 I, g! H6 d1 c  |/*  各種LCD的規(guī)格參數(shù)*/
    ; S% |* w  F. R. i0 z_lcd_pra LCD_IIL9341 ={
    8 v4 \7 R7 M8 E/ K& f0 }# _0 D        .id   = 0x9341,
    2 _- z; m) O# g9 D+ {, t        .width = 240,   //LCD 寬度
    * W$ J" m' L  _+ ~: `: _" j" [" C0 M        .height = 320,  //LCD 高度0 R$ b+ |$ x8 N9 ^' ~
    };% ]. }; N9 |$ b% E" u& P
    ...
    4 ?2 U1 K4 \( n; ^: c, B/*各種LCD列表*/9 A" _. e, b' M8 t# d( ?& [
    _lcd_pra *LcdPraList[5]=
    $ F7 `% w# Z' d1 ~! O            {
    8 t/ g4 d- w- Y' ~9 i1 U                &LCD_IIL9341,       5 }" B# P4 [- T; r
                    &LCD_IIL9325,/ f/ C. O  p8 U; j: |& I
                    &LCD_R61408,
    2 p9 A* }4 W# L0 H0 \                &LCD_Cog12864,$ O, N5 ~9 b- W, F4 x: P
                    &LCD_Oled12864,
    6 C# S, s+ P' k0 S8 Z            };( k8 v/ Q( s$ P
    然后定義了所有驅(qū)動list數(shù)組,數(shù)組內(nèi)容就是驅(qū)動,在對應的驅(qū)動文件內(nèi)實現(xiàn)。+ e' E) l, y: B7 j1 G7 Q
    /*  所有驅(qū)動列表5 `+ {& n; U1 {% c* i4 B
        驅(qū)動列表*/
    2 _& E1 K: P3 S& M( F. G0 z6 f_lcd_drv *LcdDrvList[] = {3 }$ ], D/ `" [- K) _% m
                        &TftLcdILI9341Drv,
    * o6 C3 `: u: H1 c                    &TftLcdILI9325Drv,0 Q9 W; X) ^! g$ c  T
                        &CogLcdST7565Drv,
    ) J' k- N' ~; X. X! B* {6 f                    &OledLcdSSD1615rv,
    ( r! E. c6 m9 W5 |8 v定義了設備樹,即是定義了系統(tǒng)有多少個LCD,接在哪個接口,什么驅(qū)動IC。如果是一個完整系統(tǒng),可以做成一個類似LINUX的設備樹。
    4 I2 r) N" _0 t* e- f, e) D1 L/*設備樹定義*/
    ( L" |+ S9 i6 h4 h( L#define DEV_LCD_C 3//系統(tǒng)存在3個LCD設備
    4 v! I& f8 C0 w+ V2 {% C0 DLcdObj LcdObjList[DEV_LCD_C]=+ _2 D% u2 a4 x2 n; e: \
    {
    2 L' O5 R" [1 ~+ W# V    {"oledlcd", LCD_BUS_VSPI, 0X1315},
    5 H. J' R8 G) M    {"coglcd", LCD_BUS_SPI,  0X7565},
    0 ~' G- m' U! J8 Y$ s5 G9 {8 \. C    {"tftlcd", LCD_BUS_8080, NULL},
    8 }' }+ _5 [, D# m3 P};0 [5 z6 E. O7 z* V6 Y8 B
    「2 、接口封裝」
    ' }5 c& k& ~% F$ K5 [4 a# W; hvoid dev_lcd_setdir(DevLcd *obj, u8 dir, u8 scan_dir)  v3 W) u% t" ~5 p  U3 w
    s32 dev_lcd_init(void)
    # C& t( M- o9 v1 J6 e6 r* LDevLcd *dev_lcd_open(char *name)
    4 R7 D3 n/ Y* y6 u& R* Ws32 dev_lcd_close(DevLcd *dev)
    3 n3 S6 P3 x* T& Q, @6 i4 gs32 dev_lcd_drawpoint(DevLcd *lcd, u16 x, u16 y, u16 color)
    " U7 i1 v. T! A* ?, \6 s& qs32 dev_lcd_prepare_display(DevLcd *lcd, u16 sx, u16 ex, u16 sy, u16 ey)
    3 J" w1 Z; c" ?+ D" c$ ^' Qs32 dev_lcd_display_onoff(DevLcd *lcd, u8 sta)
    9 c8 j# `; Z8 O0 B  g! es32 dev_lcd_fill(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 *color)
    : H3 T) d3 v3 l# L# o9 F/ ?( H9 f5 r! ps32 dev_lcd_color_fill(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 color)
    6 O, v+ g4 w$ J$ Ds32 dev_lcd_backlight(DevLcd *lcd, u8 sta)
    * W# v/ r2 I8 \' t. A7 w$ N大部分接口都是對驅(qū)動IC接口的二次封裝。有區(qū)別的是初始化和打開接口。初始化,就是根據(jù)前面定義的設備樹,尋找對應驅(qū)動,找到對應設備參數(shù),并完成設備初始化。打開函數(shù),根據(jù)傳入的設備名稱,查找設備,找到后返回設備句柄,后續(xù)的操作全部需要這個設備句柄。+ y5 y! j# l  x1 x
    「3 、簡易GUI層」5 X& i8 s3 k$ B% P" F1 T! M
    目前最重要就是顯示字符函數(shù)。2 ^6 [! _0 k5 \7 o; M
    s32 dev_lcd_put_string(DevLcd *lcd, FontType font, int x, int y, char *s, unsigned colidx): {- a5 C( V4 P+ A3 S+ u" U; A
    其他劃線畫圓的函數(shù)目前只是測試,后續(xù)會完善。
    9 H/ h* `6 C  {* K* X1 K驅(qū)動IC層驅(qū)動IC層分兩部分:- {4 |3 I/ d1 f$ ^# ?5 U4 U; H
    「1 、封裝LCD接口」( \, D$ B* p, r! p$ j
    LCD有使用8080總線的,有使用SPI總線的,有使用VSPI總線的。這些總線的函數(shù)由單獨文件實現(xiàn)。但是,除了這些通信信號外,LCD還會有復位信號,命令數(shù)據(jù)線信號,背光信號等。我們通過函數(shù)封裝,將這些信號跟通信接口一起封裝為「LCD通信總線」, 也就是buslcd。BUS_8080在dev_ILI9341.c文件中封裝。BUS_LCD1和BUS_lcd2在dev_str7565.c 中封裝。0 i! ?" P7 K: _  e1 w5 `
    「2 驅(qū)動實現(xiàn)」
    " s( r: w: |4 ~9 q% l9 q實現(xiàn)_lcd_drv驅(qū)動結(jié)構(gòu)體。每個驅(qū)動都實現(xiàn)一個,某些驅(qū)動可以共用函數(shù)。5 K0 Z% _& a( z, ~/ R
    _lcd_drv CogLcdST7565Drv = {, t: C3 f4 m6 J
                                .id = 0X7565,
    . }# v6 x) F! I                            .init = drv_ST7565_init,
    $ `5 k+ F* Y& z# r6 x- Q                            .draw_point = drv_ST7565_drawpoint,: u7 x$ N0 Q& G, B5 `: h* p5 D/ i
                                .color_fill = drv_ST7565_color_fill,' x4 }  p" W+ ]' Z
                                .fill = drv_ST7565_fill,
    5 ~0 H) v- F# j0 F  Z& t- `                            .onoff = drv_ST7565_display_onoff,
    % a$ B$ c. p8 r4 N% b                            .prepare_display = drv_ST7565_prepare_display,+ t# z: D  {  A1 q1 b
                                .set_dir = drv_ST7565_scan_dir,3 F7 F5 }# D' p4 M+ ?
                                .backlight = drv_ST7565_lcd_bl
    0 d8 _9 ?% }7 `4 A                            };
    $ L* k, S7 k+ j" a  g4 R接口層8080層比較簡單,用的是官方接口。SPI接口提供下面操作函數(shù),可以操作SPI,也可以操作VSPI。
    7 E  H3 D$ a0 d* D  nextern s32 mcu_spi_init(void);! o4 |( p0 q; A7 L8 `+ F
    extern s32 mcu_spi_open(SPI_DEV dev, SPI_MODE mode, u16 pre);- K* M7 x3 b: e2 p( s
    extern s32 mcu_spi_close(SPI_DEV dev);
    # ?! X" s/ f+ t; B4 n8 g7 _0 `extern s32 mcu_spi_transfer(SPI_DEV dev, u8 *snd, u8 *rsv, s32 len);8 X7 l0 G3 z* |0 u
    extern s32 mcu_spi_cs(SPI_DEV dev, u8 sta);& R1 D$ i2 Q$ b: U
    至于SPI為什么這樣寫,會有一個單獨文件說明。
    2 ]& ?( i  r6 ~* d總體流程前面說的幾個模塊時如何聯(lián)系在一起的呢?請看下面結(jié)構(gòu)體:! Z; u9 u( B# r; A
    /*  初始化的時候會根據(jù)設備數(shù)定義,
    * \9 p; G& s! }, i5 X. C    并且匹配驅(qū)動跟參數(shù),并初始化變量。
    2 y7 Y4 w) u8 T: D    打開的時候只是獲取了一個指針 */( }2 h+ ^) M1 [; U( E% ^3 _
    struct _strDevLcd
    : K9 _/ Y+ Y5 j+ t: c{" _0 u2 K$ C5 w
        s32 gd;//句柄,控制是否可以打開
    5 x$ _* [' r8 G8 w9 n6 l/ I    LcdObj   *dev;
      P2 W( y7 g' \/ g    /* LCD參數(shù),固定,不可變*/$ A' j9 X$ l  j0 z' W+ e! O6 N) j
        _lcd_pra *pra;6 x" r( n& E, ?* I4 H2 G! h( f
        /* LCD驅(qū)動 */
    7 T  m, o8 c3 d1 j6 W0 @' @+ @* J    _lcd_drv *drv;7 d. F4 R0 _, Q) F0 d1 Y7 S; @
        /*驅(qū)動需要的變量*/
    . j9 l5 F' r8 Y* f& C9 K9 [    u8  dir;    //橫屏還是豎屏控制:0,豎屏;1,橫屏。6 v; @% B) M, \) y6 s8 _
        u8  scandir;//掃描方向
    * N# P, I- H2 ~3 H% n3 g    u16 width;  //LCD 寬度- l! n, f( J' b/ W2 r; R0 \
        u16 height; //LCD 高度: s' F" ~! d2 @; m" u' ?- Z* u
        void *pri;//私有數(shù)據(jù),黑白屏跟OLED屏在初始化的時候會開辟顯存7 m0 X4 @5 N  R' S; U
    };/ \9 I9 Q. g/ E. O# `4 j
    每一個設備都會有一個這樣的結(jié)構(gòu)體,這個結(jié)構(gòu)體在初始化LCD時初始化。$ [7 G. U# |: m8 E; w
  • 成員dev指向設備樹,從這個成員可以知道設備名稱,掛在哪個LCD總線,設備ID。typedef struct: m5 c2 x  P* d7 ]% Z
    {+ x3 ?; D0 t9 G! P% ~* [
        char *name;//設備名字% |  a) ~0 j' m' N+ G
        LcdBusType bus;//掛在那條LCD總線上
    . E8 b) k* H, \& f2 p    u16 id;3 T0 ~& j6 k4 [6 b% z* T: c6 C0 c
    }LcdObj;. k! U' T7 I5 J! l
  • 成員pra指向LCD參數(shù),可以知道LCD的規(guī)格。typedef struct1 N) j1 W6 g7 H/ M" U& h" f) A# r
    {# {. x! i( ^- R
        u16 id;+ P. u- O# {$ D# H5 [* ?1 T3 K
        u16 width;  //LCD 寬度  豎屏( A% y4 t0 V2 a* Q8 I  t
        u16 height; //LCD 高度    豎屏
    4 e5 j1 k% w' |" K" {: e}_lcd_pra;
    7 F7 a& P! p1 x* J
  • 成員drv指向驅(qū)動,所有操作通過drv實現(xiàn)。typedef struct  % ?; J: W+ ^/ r: d  w
    {
    : I* O9 p' g/ j$ G/ b    u16 id;( o6 t3 \$ W, I) z7 O
        s32 (*init)(DevLcd *lcd);
    ( x$ k' ?4 w% D. ?) p$ e4 s4 W    s32 (*draw_point)(DevLcd *lcd, u16 x, u16 y, u16 color);3 V( S# U% L4 `( X
        s32 (*color_fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey, u16 color);; o, N5 \8 G6 R7 ?* {3 e
        s32 (*fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 *color);) e% ~' p; v8 q6 _; Z; F
        s32 (*prepare_display)(DevLcd *lcd, u16 sx, u16 ex, u16 sy, u16 ey);
    0 Z: {' a+ c2 ?6 r    s32 (*onoff)(DevLcd *lcd, u8 sta);
    8 R+ c. r" e. R1 g9 `0 J    void (*set_dir)(DevLcd *lcd, u8 scan_dir);
    9 t+ F+ H$ l! S- d' @5 f1 a' U9 x4 H    void (*backlight)(DevLcd *lcd, u8 sta);
    % t' e; G" F. g2 C% b7 D}_lcd_drv;) p* e3 y" m4 {) ^' P
  • 成員dir、scandir、 width、 height是驅(qū)動要使用的通用變量。因為每個LCD都有一個結(jié)構(gòu)體,一套驅(qū)動程序就能控制多個設備而互不干擾。
  • 成員pri是一個私有指針,某些驅(qū)動可能需要有些比較特殊的變量,就全部用這個指針記錄,通常這個指針指向一個結(jié)構(gòu)體,結(jié)構(gòu)體由驅(qū)動定義,并且在設備初始化時申請變量空間。目前主要用于COG LCD跟OLED LCD顯示緩存。整個LCD驅(qū)動,就通過這個結(jié)構(gòu)體組合在一起。
    8 Q! r. }& v& B# v: s4 z: [1、初始化,根據(jù)設備樹,找到驅(qū)動跟參數(shù),然后初始化上面說的結(jié)構(gòu)體。4 j4 F( W! @" B' Y& G
    2、要使用LCD前,調(diào)用dev_lcd_open函數(shù)。打開成功就返回一個上面的結(jié)構(gòu)體指針。
    6 [8 ]) z" D4 S3、顯示字符,接口找到點陣后,通過上面結(jié)構(gòu)體的drv,調(diào)用對應的驅(qū)動程序。/ x3 i2 d, I- P( _! s; K
    4、驅(qū)動程序根據(jù)這個結(jié)構(gòu)體,決定操作哪個LCD總線,并且使用這個結(jié)構(gòu)體的變量。
    / B1 D, E: X: |& Y8 G$ G( B用法和好處
  • 好處1請看測試程序* y1 C, Z4 X+ f0 n' ?, i2 n3 ]
    void dev_lcd_test(void)0 M6 }' @9 N6 O) ?* s
    {" u9 s8 `- d- `
        DevLcd *LcdCog;
    ' l3 Q  O, n# O! j4 d5 j) x    DevLcd *LcdOled;* k5 m! W- n) P4 R' T2 b
        DevLcd *LcdTft;
    & e$ P. ~1 G& ^    /*  打開三個設備 */
    # u2 J, y& \$ c0 N2 q1 n5 u    LcdCog = dev_lcd_open("coglcd");
    2 R5 W4 A, y$ B    if(LcdCog==NULL)
    2 I: S, b- \1 x        uart_printf("open cog lcd err\r
    8 \# M& y7 v3 h+ t( n! ?");3 s3 Z% Z4 r+ X
        LcdOled = dev_lcd_open("oledlcd");4 `' o  S& w/ D# M  L! Z  N+ n9 C
        if(LcdOled==NULL)
    * P5 }* R7 f1 C; t  @        uart_printf("open oled lcd err\r
    % O4 D2 ?- i& z");" N: a/ w4 b, h/ ?
        LcdTft = dev_lcd_open("tftlcd");4 F" N8 p2 T; G/ h6 N+ \5 S; c. X
        if(LcdTft==NULL)
    ! b( W8 }) M1 d: e( o% y7 x; T, G        uart_printf("open tft lcd err\r& {6 b0 z0 r. a5 O2 t  j
    ");) Y5 j& U/ s8 Q1 @% |, Q
        /*打開背光*/# d- ~' {1 R  a) b2 _# v
        dev_lcd_backlight(LcdCog, 1);
    6 m4 P( V6 |7 y& `    dev_lcd_backlight(LcdOled, 1);) z3 o* q8 m5 c- c& E- _( o
        dev_lcd_backlight(LcdTft, 1);
    $ E6 W: l% Y$ d- R  K! q# z  t( s7 V    dev_lcd_put_string(LcdOled, FONT_SONGTI_1212, 10,1, "ABC-abc,", BLACK);
    6 y) L4 ]& T1 V( ~; x1 ~    dev_lcd_put_string(LcdOled, FONT_SIYUAN_1616, 1, 13, "這是oled lcd", BLACK);: M( ]9 s, U6 s8 h
        dev_lcd_put_string(LcdOled, FONT_SONGTI_1212, 10,30, "www.wujique.com", BLACK);! G- @7 F5 r6 K1 U7 C8 |
        dev_lcd_put_string(LcdOled, FONT_SIYUAN_1616, 1, 47, "屋脊雀工作室", BLACK);, @' R7 R5 p1 F9 j0 q* ]" P
        dev_lcd_put_string(LcdCog, FONT_SONGTI_1212, 10,1, "ABC-abc,", BLACK);
    9 I1 y5 U4 q1 @( K! Z. U& l    dev_lcd_put_string(LcdCog, FONT_SIYUAN_1616, 1, 13, "這是cog lcd", BLACK);4 l# l) m1 Q) Q; k* s7 E$ q
        dev_lcd_put_string(LcdCog, FONT_SONGTI_1212, 10,30, "www.wujique.com", BLACK);
    ' `% i" J- o5 E' \. |3 f1 i    dev_lcd_put_string(LcdCog, FONT_SIYUAN_1616, 1, 47, "屋脊雀工作室", BLACK);
    ( ]- W' D; ?3 i4 {1 T0 K7 O3 L    dev_lcd_put_string(LcdTft, FONT_SONGTI_1212, 20,30, "ABC-abc,", RED);
    ! S. y' p, Q& @7 u; V! U3 R. F    dev_lcd_put_string(LcdTft, FONT_SIYUAN_1616, 20,60, "這是tft lcd", RED);4 \$ @( Y3 v+ [! f
        dev_lcd_put_string(LcdTft, FONT_SONGTI_1212, 20,100, "www.wujique.com", RED);: p, z" {, c9 h- @, Y2 Y
        dev_lcd_put_string(LcdTft, FONT_SIYUAN_1616, 20,150, "屋脊雀工作室", RED);
    - P- \* f" C; a* |: P. \3 t    while(1);* m' U9 t) G* R+ M- K; E
    }# C( ]8 B, I: j- P/ S. s3 ]
    使用一個函數(shù)dev_lcd_open,可以打開3個LCD,獲取LCD設備。然后調(diào)用dev_lcd_put_string就可以在不同的LCD上顯示。其他所有的gui操作接口都只有一個。這樣的設計對于APP層來說,就很友好。顯示效果:: W, q$ t/ L2 E( `) f

    % i8 J8 f$ \: @! {
  • 好處2現(xiàn)在的設備樹是這樣定義的) x5 o; E) ^' [! y/ w, u& X
    LcdObj LcdObjList[DEV_LCD_C]=
    4 u3 @0 D2 v+ v/ P0 Z9 r4 V{
    ' u6 F; ]3 U$ k0 O4 e4 O    {"oledlcd", LCD_BUS_VSPI, 0X1315},
    - P7 L8 N! n0 j. m4 {. b: b    {"coglcd", LCD_BUS_SPI,  0X7565},! b! }) I6 [1 j# @
        {"tftlcd", LCD_BUS_8080, NULL},
      d9 F$ U* M8 A/ W- I5 ?$ }};# o; g6 v+ s7 A4 Z* e( r" |
    某天,oled lcd要接到SPI上,只需要將設備樹數(shù)組里面的參數(shù)改一下,就可以了,當然,在一個接口上不能接兩個設備。* {4 _5 s" [! }6 n% L* c3 F: M/ j7 m
    LcdObj LcdObjList[DEV_LCD_C]=
    6 l  G2 f! G5 K/ C/ S+ J% f; {2 p, v8 n{
    ' U& u* u8 L7 e$ R3 I0 G    {"oledlcd", LCD_BUS_SPI, 0X1315},  N0 F5 ~3 l+ @; f  m7 k
        {"tftlcd", LCD_BUS_8080, NULL},
    2 @& L2 U6 R. S! Q};- V& [' c6 m+ |' f
    字庫暫時不做細說,例程的字庫放在SD卡中,各位移植的時候根據(jù)需要修改。具體參考font.c。$ }& Z1 d; f1 Q$ b) M9 p
    聲明代碼請按照版權(quán)協(xié)議使用。當前源碼只是一個能用的設計,完整性與健壯性尚未測試。后續(xù)會放到github,并且持續(xù)更新優(yōu)化。最新消息請關(guān)注www.wujique.com。
    + O5 v5 t6 {5 D% L-END-
    # a5 e: O2 }/ }5 `/ B往期推薦:點擊圖片即可跳轉(zhuǎn)閱讀7 G% ^4 H+ F; Q& f
                                                           
    ' p8 S$ w, _7 n                                                                % S6 Q: I: G  ?- S$ s
                                                                            . n, e( J# `0 G8 R! I3 S0 z
                                                                                    # ?4 ~* x0 F9 K9 a* {
    & l' p* \- T% ^! S2 H
                                                                                    6 B$ |* r6 C9 O" @8 J# i8 h/ E
                                                                                            淺談為何不該入行嵌入式技術(shù)開發(fā)5 q( M3 K9 g& V% i) a; X
                                                                                   
    1 g) j  h; [% V% z; h# e: j                                                                        4 C' K% S5 w/ t
                                                                   
    ( y) N* g' A  k                                                       
    * l8 I2 d7 u, U* [8 o: W- V* u/ X                                               
    , t& p9 X) @/ ]& x  ^4 S9 Y3 N9 |& |7 o: f$ [; F5 C* X  m
                                                            . P9 [) Z/ {& i" F+ C, a6 I0 n
                                                                   
    9 b; O; U5 o/ }                                                                        , H" k) \1 S$ V0 }: E9 p# T# K
                                                                                    7 a* l& Z& N0 b) H* b
    3 ]9 |  C; H# u! g" U* x  {  l
                                                                                   
    . k, U& J# l% r                                                                                        在深圳搞嵌入式,從來沒讓人失望過!. U1 [  H$ c1 B; t) |
                                                                                    1 I' A' h5 @2 ^6 L% s& F. c
                                                                           
    ( r. w3 O! R4 l* o) ]                                                                ( C5 D2 [9 i# j7 j! M
                                                           
    : j. D& v/ D0 w1 R9 O6 O- b                                               
    & r/ a+ o4 E& a
    4 n5 |) D& ~1 G# A6 Z$ F% m                                                       
    ( d; ]. H, p0 ?* U* J( d; U                                                               
    $ r+ I, s5 |- ~% l  \8 Y                                                                       
    9 \' R; p/ q7 Y+ ]' D2 ]                                                                               
    : O/ }% ^5 @6 ]  r ) a6 Z+ r# t* E" K
                                                                                   
    2 Y/ e7 b" G0 J: y  N1 s                                                                                        蘋果iPhone16發(fā)布了,嵌入式鴻蒙,國產(chǎn)化程度有多高?0 f8 a6 h+ {% c3 F
                                                                                   
    1 K  w* J! }% p, L, k                                                                       
    * z/ K- A5 p& V$ |% J# Q                                                               
    # \6 m) m. t" b                                                       
    ; F6 l2 q) c' f" `) [                                                我是老溫,一名熱愛學習的嵌入式工程師
    " W) n( Y& T8 o$ ]; e關(guān)注我,一起變得更加優(yōu)秀!
  • 回復

    使用道具 舉報

    發(fā)表回復

    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規(guī)則

    關(guān)閉

    站長推薦上一條 /1 下一條


    聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表