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

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

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

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

[復制鏈接]

448

主題

448

帖子

539

積分

二級會員

Rank: 2

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

    1 M! S5 |- j( u9 ZIPS:! b$ K9 a$ E$ `8 H( G' K  o8 V. y1 Q

    , K! K7 w2 z/ A5 m' V* oCOG lcd很多人可能不知道COG LCD是什么,我覺得跟現(xiàn)在開發(fā)板銷售方向有關(guān)系,大家都出大屏,玩酷炫界面,對于更深的技術(shù),例如軟件架構(gòu)設計,都不涉及。使用單片機的產(chǎn)品,COG LCD其實占比非常大。COG是Chip On Glass的縮寫,就是驅(qū)動芯片直接綁定在玻璃上,透明的。實物像下圖:, O! ^- N% m$ y) |! e3 O. {2 m

    + [% X3 e: M+ ]- N# D- Z這種LCD通常像素不高,常用的有128X64,128X32。一般只支持黑白顯示,也有灰度屏。
    ! z. c( n3 `: c3 f接口通常是SPI,I2C。也有號稱支持8位并口的,不過基本不會用,3根IO能解決的問題,沒必要用8根吧?常用的驅(qū)動IC:STR7565。
      I) Y# c2 F& P& A0 \' m% K9 zOLED lcd買過開發(fā)板的應該基本用過。新技術(shù),大家都感覺高檔,在手環(huán)等產(chǎn)品常用。OLED目前屏幕較小,大一點的都很貴。在控制上跟COG LCD類似,區(qū)別是兩者的顯示方式不一樣。從我們程序角度來看,最大的差別就是,OLED LCD,不用控制背光。。。。。實物如下圖:
    " Z; G" I) W: [" v* K; F * s3 G% {1 g* }/ S
    常見的是SPI跟I2C接口。常見驅(qū)動IC:SSD1615。+ v2 F. m% K- R! L% @
    硬件場景接下來的討論,都基于以下硬件信息:+ D9 i& h# I/ Q3 K0 e4 C7 u# N8 m( W
    1、有一個TFT屏幕,接在硬件的FSMC接口,什么型號屏幕?不知道。
    : Y5 J$ a7 f) @0 k3 c2、有一個COG lcd,接在幾根普通IO口上,驅(qū)動IC是STR7565,128X32像素。. g3 y  S  f5 T6 K0 b8 D' v
    3、有一個COG LCD,接在硬件SPI3跟幾根IO口上,驅(qū)動IC是STR7565,128x64像素。
    9 b0 q: y' |& |3 @  W4、有一個OLED LCD,接在SPI3上,使用CS2控制片選,驅(qū)動IC是SSD1315。
    # @3 o1 C+ Y, T ; K3 a3 C' p( q. Z9 t9 o
    預備知識在進入討論之前,我們先大概說一下下面幾個概念,對于這些概念,如果你想深入了解,請GOOGLE。
    6 D5 W( d4 d" T7 q- Z) W面向?qū)ο竺嫦驅(qū)ο,是編程界的一個概念。什么叫面向?qū)ο竽?編程有兩種要素:程序(方法),數(shù)據(jù)(屬性)。例如:一個LED,我們可以點亮或者熄滅它,這叫方法。LED什么狀態(tài)?亮還是滅?這就是屬性。我們通常這樣編程:& Y3 j$ y8 L9 J
    u8 ledsta = 0;
    ' |, }9 f6 |5 E& B7 }+ Y) nvoid ledset(u8 sta), Q: J6 _! {7 Y# E
    {" I& A- }) x+ D8 V2 P1 Z- K9 `7 ~) H
    }% |  e- B5 s& Z
    這樣的編程有一個問題,假如我們有10個這樣的LED,怎么寫?這時我們可以引入面向?qū)ο缶幊,將每一個LED封裝為一個對象。可以這樣做:
    ( z; j- p9 m" ^0 R( n0 {+ u/*9 t! W3 I% k+ H6 Y
    定義一個結(jié)構(gòu)體,將LED這個對象的屬性跟方法封裝。
    * }2 q5 J  M. K2 {6 c9 C; {' d這個結(jié)構(gòu)體就是一個對象。
    ! K0 K9 Z" h# J$ j# ]! b2 ]; y! q但是這個不是一個真實的存在,而是一個對象的抽象。
    % m9 b$ y& [( e; q*/
    + s8 x4 Q* M- C: o  _& g$ H5 p$ ~4 ytypedef struct{
    # K0 N2 n# j! t* X" O2 x    u8 sta;. v) H- T* k' ^! k. X' }
        void (*setsta)(u8 sta);
    . i0 A. |% x0 N}LedObj;- ]$ N. |& t- t
    /*  聲明一個LED對象,名稱叫做LED1,并且實現(xiàn)它的方法drv_led1_setsta*/# p5 B0 ^. {; [8 R+ y' Z2 {$ u
    void drv_led1_setsta(u8 sta)
    + t- N( P( I1 x& D( {* Z{
    ; ]" g" n* O2 l4 ]7 N- F* B& H& x}  J. ], r1 _; J; k5 e
    LedObj LED1={( x- H3 ]( R# @8 m7 E: b
            .sta = 0,8 O! A  F5 |; }$ r9 }
            .setsta = drv_led1_setsta," K( N5 y$ A+ a9 A: C
        };
    * ?+ d4 O; N4 q3 G* }: _/*  聲明一個LED對象,名稱叫做LED2,并且實現(xiàn)它的方法drv_led2_setsta*/
    6 _8 K3 z3 N' C0 l# D2 Dvoid drv_led2_setsta(u8 sta)! i7 y# h- T3 H3 ^
    {
    7 C. L4 ?! y0 W7 p}
    2 i, v: d+ J4 h  mLedObj LED2={7 _' E" s) L/ U1 Q( {/ @$ V3 g% I& \
            .sta = 0,# C& i. m+ v6 X& N8 B
            .setsta = drv_led2_setsta,% R: E& y8 \' W+ G
        };/ q4 I. G5 ^3 {: W( R' M; ^
        8 ~0 n( z* q( K; R& @: l) K
    /*  操作LED的函數(shù),參數(shù)指定哪個led*/
    ! J0 x; s( Q% l; ^" B% xvoid ledset(LedObj *led, u8 sta)+ ^' W2 C$ C" u* e7 d9 B
    {& o. i$ _: W  m  t+ U4 r
        led->setsta(sta);
    $ W9 P0 _5 y! A* q6 V}
    + A  ~: G' b. D' O; q# d3 o2 h3 G是的,在C語言中,實現(xiàn)面向?qū)ο蟮氖侄尉褪墙Y(jié)構(gòu)體的使用。上面的代碼,對于API來說,就很友好了。操作所有LED,使用同一個接口,只需告訴接口哪個LED。大家想想,前面說的LCD硬件場景。4個LCD,如果不面向?qū)ο螅?strong>「顯示漢字的接口是不是要實現(xiàn)4個」?每個屏幕一個?: K( e4 l. E" l/ Q% {; ]
    驅(qū)動與設備分離如果要深入了解驅(qū)動與設備分離,請看LINUX驅(qū)動的書籍。
    5 M; a; I5 Y8 M. _什么是設備?我認為的設備就是「屬性」,就是「參數(shù)」,就是「驅(qū)動程序要用到的數(shù)據(jù)和硬件接口信息」。那么驅(qū)動就是「控制這些數(shù)據(jù)和接口的代碼過程」。/ V: H0 D  M8 o" {& D# H4 W
    通常來說,如果LCD的驅(qū)動IC相同,就用相同的驅(qū)動。有些不同的IC也可以用相同的,例如SSD1315跟STR7565,除了初始化,其他都可以用相同的驅(qū)動。例如一個COG lcd:+ s2 y: }  g  d1 ~: o; ]. V& J
    ?驅(qū)動IC是STR7565 128 * 64 像素用SPI3背光用PF5 ,命令線用PF4 ,復位腳用PF3
    7 b% L* K. q+ h- ^( E?
    上面所有的信息綜合,就是一個設備。驅(qū)動就是STR7565的驅(qū)動代碼。
    ( I6 k8 @1 g# {為什么要驅(qū)動跟設備分離,因為要解決下面問題:
    / a! Z3 B% M$ C4 D' _' n?有一個新產(chǎn)品,收銀設備。系統(tǒng)有兩個LCD,都是OLED,驅(qū)動IC相同,但是一個是128x64,另一個是128x32像素,一個叫做主顯示,收銀員用;一個叫顧顯,顧客看金額。5 Y! p: N, u1 \) l; ^/ m
    ?
    這個問題,「兩個設備用同一套程序控制」才是最好的解決辦法。驅(qū)動與設備分離的手段:8 }9 R8 ^2 c3 P: x$ _( }+ f/ d
    ?在驅(qū)動程序接口函數(shù)的參數(shù)中增加設備參數(shù),驅(qū)動用到的所有資源從設備參數(shù)傳入。, N+ T# Q, i- M
    ?
    驅(qū)動如何跟設備綁定呢?通過設備的驅(qū)動IC型號。
      b) M: k* P% P) D模塊化我認為模塊化就是將一段程序封裝,提供穩(wěn)定的接口給不同的驅(qū)動使用。不模塊化就是,在不同的驅(qū)動中都實現(xiàn)這段程序。例如字庫處理,在顯示漢字的時候,我們要找點陣,在打印機打印漢字的時候,我們也要找點陣,你覺得程序要怎么寫?把點陣處理做成一個模塊,就是模塊化。非模塊化的典型特征就是「一根線串到底,沒有任何層次感」。
    2 }0 V! u2 L, B! qLCD到底是什么前面我們說了面向?qū)ο,現(xiàn)在要對LCD進行抽象,得出一個對象,就需要知道LCD到底是什么。問自己下面幾個問題:
    6 ?/ V  n$ |0 Q
  • LCD能做什么?
  • 要LCD做什么?
  • 誰想要LCD做什么?剛剛接觸嵌入式的朋友可能不是很了解,可能會想不通。我們模擬一下LCD的功能操作數(shù)據(jù)流。APP想要在LCD上顯示 一個漢字。+ T: l3 M: p' h3 @# k
    1、首先,需要一個顯示漢字的接口,APP調(diào)用這個接口就可以顯示漢字,假設接口叫做lcd_display_hz。9 [+ \5 T- O2 x( G- G$ D+ X/ |
    2、漢字從哪來?從點陣字庫來,所以在lcd_display_hz函數(shù)內(nèi)就要調(diào)用一個叫做find_font的函數(shù)獲取點陣。
    " e1 n: s1 e: _" F7 R0 z7 s3、獲取點陣后要將點陣顯示到LCD上,那么我們調(diào)用一個ILL9341_dis的接口,將點陣刷新到驅(qū)動IC型號為ILI9341的LCD上。
    : q# H; k% _4 [# n9 i1 A4、ILI9341_dis怎么將點陣顯示上去?調(diào)用一個8080_WRITE的接口。
    ! S; Y( U$ r+ Y, `好的,這個就是大概過程,我們從這個過程去抽象LCD功能接口。漢字跟LCD對象有關(guān)嗎?無關(guān)。在LCD眼里,無論漢字還是圖片,都是一個個點。那么前面問題的答案就是:
    4 H9 c' }2 W1 Y
  • LCD可以一個點一個點顯示內(nèi)容。
  • 要LCD顯示漢字或圖片-----就是顯示一堆點
  • APP想要LCD顯示圖片或文字。結(jié)論就是:所有LCD對象的功能就是顯示點。「那么驅(qū)動只要提供顯示點的接口就可以了,顯示一個點,顯示一片點。」 抽象接口如下:( I$ A# U% L! t* c7 f* V+ n
    /*
    0 w3 D1 z7 ?. z9 x4 t    LCD驅(qū)動定義
    7 ?) o4 \4 m' l: D0 `; |2 \5 |*/
    " ^* y5 I6 u# s( f# \3 a3 x# ltypedef struct  
    + S, y+ h; x+ V$ D& a# B{" a( d1 }+ ~- S% K' e
        u16 id;
    / C! b& n3 {/ ~9 R! P    s32 (*init)(DevLcd *lcd);
    $ K8 w7 \6 W" C4 C$ P7 J' Y    s32 (*draw_point)(DevLcd *lcd, u16 x, u16 y, u16 color);( b  s* f: |2 E+ n1 h$ e
        s32 (*color_fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey, u16 color);/ O: p& }/ C  G2 a+ _
        s32 (*fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 *color);3 h' R3 _& p  R1 ]! M9 e  p* C! R3 Z
        s32 (*onoff)(DevLcd *lcd, u8 sta);
    ; y2 \, a8 o  S* I+ V    s32 (*prepare_display)(DevLcd *lcd, u16 sx, u16 ex, u16 sy, u16 ey);
    8 y: G2 ?2 o9 t; U    void (*set_dir)(DevLcd *lcd, u8 scan_dir);  ?  _' w/ @7 ?# J1 ?
        void (*backlight)(DevLcd *lcd, u8 sta);
    " N4 B7 ~6 L1 I) S}_lcd_drv;
    9 ^% m  f) |& p* T上面的接口,也就是對應的驅(qū)動,包含了一個驅(qū)動id號。5 {- c9 r1 X+ Q+ X5 o2 ~
  • id,驅(qū)動型號
  • 初始化
  • 畫點
  • 將一片區(qū)域的點顯示某種顏色
  • 將一片區(qū)域的點顯示某些顏色
  • 顯示開關(guān)
  • 準備刷新區(qū)域(主要彩屏直接DMA刷屏使用)
  • 設置掃描方向
  • 背光控制顯示字符,劃線等功能,不屬于LCD驅(qū)動。應該歸類到GUI層。
    8 `1 C; ]0 ?! R3 m0 y+ zLCD驅(qū)動框架我們設計了如下的驅(qū)動框架:
    # q( l( I1 L- N" G+ B* a. d
    5 S- Z: T+ `9 H4 g% M& z設計思路:
    8 k* F  u& U8 S0 j. E$ I$ d0 P0 l1、中間顯示驅(qū)動IC驅(qū)動程序提供統(tǒng)一接口,接口形式如前面說的_lcd_drv結(jié)構(gòu)體。( `& v9 V. A& Q
    2、各顯示IC驅(qū)動根據(jù)設備參數(shù),調(diào)用不同的接口驅(qū)動。例如TFT就用8080驅(qū)動,其他的都用SPI驅(qū)動。SPI驅(qū)動只有一份,用IO口控制的我們也做成模擬SPI。8 d$ p) R, V" r0 a
    3、LCD驅(qū)動層做LCD管理,例如完成TFT LCD的識別。并且將所有LCD接口封裝為一套接口。% P+ S5 p0 Z# v$ p  Q5 q9 b+ R
    4、簡易GUI層封裝了一些顯示函數(shù),例如劃線、字符顯示。, Z* p# O) t/ O
    5、字體點陣模塊提供點陣獲取與處理接口。
      i! d: Q/ s: ?4 h由于實際沒那么復雜,在例程中我們將GUI跟LCD驅(qū)動層放到一起。TFT LCD的兩個驅(qū)動也放到一個文件,但是邏輯是分開的。OLED除初始化,其他接口跟COG LCD基本一樣,因此這兩個驅(qū)動也放在一個文件。
    ' R2 g0 Z7 N- L6 o6 V2 {代碼分析代碼分三層:( w' X) h, U- T4 S  o) c9 m
    1、GUI和LCD驅(qū)動層 dev_lcd.c dev_lcd.h
    : w2 U+ T5 n5 [2、顯示驅(qū)動IC層 dev_str7565.c & dev_str7565.h dev_ILI9341.c & dev_ILI9341.h5 ^9 Q) `$ Z0 m- J5 s( k
    3、接口層 mcu_spi.c & mcu_spi.h stm324xg_eval_fsmc_sram.c & stm324xg_eval_fsmc_sram.h
    * j8 X! g+ ~5 ^8 Q% N$ }' o6 ?GUI和LCD層這層主要有3個功能 :
    5 }3 K/ R2 I; S3 i「1、設備管理」7 E9 N, a5 S7 z4 K0 `% e
    首先定義了一堆LCD參數(shù)結(jié)構(gòu)體,結(jié)構(gòu)體包含ID,像素。并且把這些結(jié)構(gòu)體組合到一個list數(shù)組內(nèi)。
    1 d' |4 K9 x3 N  ]5 m+ S& _" T- `/*  各種LCD的規(guī)格參數(shù)*/
    4 Z( R0 b3 j7 B# ?3 ^_lcd_pra LCD_IIL9341 ={
      Z9 E  J2 t7 \7 k4 q" c/ T5 Q        .id   = 0x9341,( b7 u3 X$ K; x3 `# Q; M( L4 H2 p; f
            .width = 240,   //LCD 寬度: }7 _+ o2 F& b% _/ k. o) C  t' H2 p
            .height = 320,  //LCD 高度! }$ j6 T, ?5 l7 T
    };
    6 f. o0 S, L7 j$ a...- A* d8 W) R2 A3 Y0 i1 Y. \. U: z
    /*各種LCD列表*/
    ! }  G% X5 N  Y_lcd_pra *LcdPraList[5]=. q. O( P, \1 I8 t0 c+ U4 V
                {+ R% f+ \# ?5 `
                    &LCD_IIL9341,      
    8 B5 O, P* {1 T; M                &LCD_IIL9325,
    " ^% ?2 A: B& Y( F/ C& H! R1 m7 K                &LCD_R61408,
    ( U! n2 Y7 R; D) R; m                &LCD_Cog12864,; d" P: S/ X$ o( I% S8 C2 o
                    &LCD_Oled12864,
    - I' W( z6 C# U  b% D% X# O            };
    ; \9 M/ m  W4 p然后定義了所有驅(qū)動list數(shù)組,數(shù)組內(nèi)容就是驅(qū)動,在對應的驅(qū)動文件內(nèi)實現(xiàn)。2 M5 u7 G! u  r: D+ i  O! V9 }
    /*  所有驅(qū)動列表$ ^) z# L& a6 I: C
        驅(qū)動列表*/
    * p) Z" K6 ?: Q3 D( x. g_lcd_drv *LcdDrvList[] = {
    ! }  L  H7 v% [) R; J; P( q: a                    &TftLcdILI9341Drv,
    7 f8 u9 \) q3 h( }                    &TftLcdILI9325Drv,# p/ ~4 c' i. C% L" d6 G+ a1 w
                        &CogLcdST7565Drv,
    9 W1 x) S2 N  }! x- @7 g& B+ m                    &OledLcdSSD1615rv," b; W# N- [, b+ R+ u9 e
    定義了設備樹,即是定義了系統(tǒng)有多少個LCD,接在哪個接口,什么驅(qū)動IC。如果是一個完整系統(tǒng),可以做成一個類似LINUX的設備樹。9 N% M8 [" L% ^: w5 y# w8 R* w/ j
    /*設備樹定義*/
    + b. v) a* F8 C, P, t( l#define DEV_LCD_C 3//系統(tǒng)存在3個LCD設備( [  D2 |# B8 L" X
    LcdObj LcdObjList[DEV_LCD_C]=2 Z6 i% O5 _7 F! \4 h2 ^
    {* ~0 i( z! o  u9 X& H# {% X8 g
        {"oledlcd", LCD_BUS_VSPI, 0X1315},
    ; P' {* v/ J4 N' S    {"coglcd", LCD_BUS_SPI,  0X7565},
    . I+ z! G4 z; v' M0 q- h' n    {"tftlcd", LCD_BUS_8080, NULL},
    ) Y& A) }0 q* @1 P};
    4 i# h+ W: I* a# U4 P' K. q「2 、接口封裝」1 U$ X. B2 d% e' u- Y
    void dev_lcd_setdir(DevLcd *obj, u8 dir, u8 scan_dir)* e  H  i0 K0 ]$ S5 T/ y0 s6 K  L
    s32 dev_lcd_init(void)
    - t6 C, i2 z; V- t7 c" UDevLcd *dev_lcd_open(char *name)
    1 S- }& Y$ O3 es32 dev_lcd_close(DevLcd *dev)5 x7 e$ f% C, S" H
    s32 dev_lcd_drawpoint(DevLcd *lcd, u16 x, u16 y, u16 color)9 H! c1 l& H/ Y
    s32 dev_lcd_prepare_display(DevLcd *lcd, u16 sx, u16 ex, u16 sy, u16 ey)
    1 q( ~3 T: p7 Bs32 dev_lcd_display_onoff(DevLcd *lcd, u8 sta)
    : V2 {$ j$ `( z$ t, v# rs32 dev_lcd_fill(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 *color). f) w  r! o! ~, s3 k0 B! a+ C
    s32 dev_lcd_color_fill(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 color)
    ; Y" A, o, |9 c- I9 Js32 dev_lcd_backlight(DevLcd *lcd, u8 sta)
    + Z/ E" m0 r0 f( }1 `大部分接口都是對驅(qū)動IC接口的二次封裝。有區(qū)別的是初始化和打開接口。初始化,就是根據(jù)前面定義的設備樹,尋找對應驅(qū)動,找到對應設備參數(shù),并完成設備初始化。打開函數(shù),根據(jù)傳入的設備名稱,查找設備,找到后返回設備句柄,后續(xù)的操作全部需要這個設備句柄。
    1 h6 E/ y5 C, \. ]/ Z% @6 G( Y( B7 o) R2 U「3 、簡易GUI層」
    - ~1 Q* n% L3 ?  g) s目前最重要就是顯示字符函數(shù)。
    0 s9 T1 E. Z$ ms32 dev_lcd_put_string(DevLcd *lcd, FontType font, int x, int y, char *s, unsigned colidx)
    3 m8 N- d; e. W# t4 U9 X* k! ]' Q' ]: n) R其他劃線畫圓的函數(shù)目前只是測試,后續(xù)會完善。/ x3 w/ ~5 v1 g, N
    驅(qū)動IC層驅(qū)動IC層分兩部分:
    ! l: l: e. k2 V2 y5 U& z- C& H9 o「1 、封裝LCD接口」
    / U8 K9 S& W/ ?4 QLCD有使用8080總線的,有使用SPI總線的,有使用VSPI總線的。這些總線的函數(shù)由單獨文件實現(xiàn)。但是,除了這些通信信號外,LCD還會有復位信號,命令數(shù)據(jù)線信號,背光信號等。我們通過函數(shù)封裝,將這些信號跟通信接口一起封裝為「LCD通信總線」, 也就是buslcd。BUS_8080在dev_ILI9341.c文件中封裝。BUS_LCD1和BUS_lcd2在dev_str7565.c 中封裝。# n3 l2 F, Z9 D# E$ f3 }
    「2 驅(qū)動實現(xiàn)」
    + R" k( R& G1 v  _/ V實現(xiàn)_lcd_drv驅(qū)動結(jié)構(gòu)體。每個驅(qū)動都實現(xiàn)一個,某些驅(qū)動可以共用函數(shù)。
    - D! L! J0 _7 z0 _/ C# p_lcd_drv CogLcdST7565Drv = {8 K3 u- _: F0 f: c
                                .id = 0X7565,
    ( F; A7 O0 T5 U: o$ M) a. P                            .init = drv_ST7565_init,8 F  H# I. z9 s3 p
                                .draw_point = drv_ST7565_drawpoint,
    3 q* S3 B3 I3 q0 o, S                            .color_fill = drv_ST7565_color_fill,) h/ t6 s* E: m9 N6 E: z5 |
                                .fill = drv_ST7565_fill,2 p8 S+ Z, R4 n' f. N
                                .onoff = drv_ST7565_display_onoff,0 {* {- V1 r/ C* W! y: K. J
                                .prepare_display = drv_ST7565_prepare_display,
    % y" W2 r2 s) W9 [$ W5 p                            .set_dir = drv_ST7565_scan_dir,6 f! C" b, D& T1 v) ]5 C8 k1 }6 \
                                .backlight = drv_ST7565_lcd_bl
    % A' Z( L0 O+ ~0 S; L2 U                            };2 N) E+ ]2 S2 ~+ ~; Y3 H6 @
    接口層8080層比較簡單,用的是官方接口。SPI接口提供下面操作函數(shù),可以操作SPI,也可以操作VSPI。8 X1 W5 V3 }" n+ r& ^) r
    extern s32 mcu_spi_init(void);5 e3 o3 U( b, h" M( {
    extern s32 mcu_spi_open(SPI_DEV dev, SPI_MODE mode, u16 pre);
    + M8 R; i+ a6 x* f+ Z" Eextern s32 mcu_spi_close(SPI_DEV dev);: v& x) ~; G8 Z; z+ o, `
    extern s32 mcu_spi_transfer(SPI_DEV dev, u8 *snd, u8 *rsv, s32 len);( T/ B! l4 N: O9 ~7 o
    extern s32 mcu_spi_cs(SPI_DEV dev, u8 sta);, u6 V- `  `0 w, s' D/ L, W& w
    至于SPI為什么這樣寫,會有一個單獨文件說明。4 O& `4 [6 N+ l" l
    總體流程前面說的幾個模塊時如何聯(lián)系在一起的呢?請看下面結(jié)構(gòu)體:# P2 L3 Z$ M: h& s6 t0 e
    /*  初始化的時候會根據(jù)設備數(shù)定義,' [) P( j: F9 J7 ^5 k1 H$ T+ N
        并且匹配驅(qū)動跟參數(shù),并初始化變量。
    & B" s+ M& s* s- g; J" w    打開的時候只是獲取了一個指針 */' d8 U' y, _" d: ~/ n
    struct _strDevLcd
    ' Y! E4 ^, P- K: a7 E{
    * N1 z6 e9 h: \/ D    s32 gd;//句柄,控制是否可以打開4 W. X7 [2 l) u7 @  t
        LcdObj   *dev;
    1 r$ V) s* w5 g    /* LCD參數(shù),固定,不可變*/5 J  ~& O+ H( h5 d5 e+ g5 x
        _lcd_pra *pra;5 Q! ]" ?9 q: m
        /* LCD驅(qū)動 */  H: b: E: o8 Z$ }
        _lcd_drv *drv;* L7 {) o; J* L, [8 n
        /*驅(qū)動需要的變量*/
    ' [- K: h5 X! N( i    u8  dir;    //橫屏還是豎屏控制:0,豎屏;1,橫屏。
    " A% E* Q2 n# g7 p    u8  scandir;//掃描方向1 l1 I+ G) Q4 `' J! s' e4 F% r5 D: u
        u16 width;  //LCD 寬度+ d- \  X! T) `; K4 h+ _
        u16 height; //LCD 高度6 R$ @% |! _% d! F$ Q
        void *pri;//私有數(shù)據(jù),黑白屏跟OLED屏在初始化的時候會開辟顯存5 A( H& V9 `5 o  v
    };. G# |, u- w. W6 j, p
    每一個設備都會有一個這樣的結(jié)構(gòu)體,這個結(jié)構(gòu)體在初始化LCD時初始化。1 b6 {9 N, H; U% R5 l/ S6 l$ n4 r1 u
  • 成員dev指向設備樹,從這個成員可以知道設備名稱,掛在哪個LCD總線,設備ID。typedef struct5 O# @+ C4 A$ a
    {; m. [! T/ l7 j- {; G3 d
        char *name;//設備名字& |$ D9 A6 o0 h% i
        LcdBusType bus;//掛在那條LCD總線上9 R- R" e/ x7 t$ c7 B$ ~
        u16 id;9 X; V4 ?5 |9 ~( |! ^0 y1 O
    }LcdObj;# K' n$ J0 c* f/ w
  • 成員pra指向LCD參數(shù),可以知道LCD的規(guī)格。typedef struct
    3 R) w7 _$ T! K/ h0 y- v{
    : |& ?/ C# I+ Z* @    u16 id;; N; J# a2 i; n9 Y" ?/ w
        u16 width;  //LCD 寬度  豎屏
    + Q5 P- h# K, \5 Z- F5 E, U: B    u16 height; //LCD 高度    豎屏3 y8 w# e$ T2 D8 ?; R
    }_lcd_pra;' o9 G5 |, ~. A- m3 q
  • 成員drv指向驅(qū)動,所有操作通過drv實現(xiàn)。typedef struct  
    5 M" b/ A9 \; T; s! Q- ]{
    & l9 H) e2 z& N$ c. O; H3 `    u16 id;
    4 G* C( }4 f+ z( U- V: y    s32 (*init)(DevLcd *lcd);3 U; r8 n) H4 H; s
        s32 (*draw_point)(DevLcd *lcd, u16 x, u16 y, u16 color);
    6 ?: t: P. I7 |$ r    s32 (*color_fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey, u16 color);
    # h6 C3 U' {. C; {    s32 (*fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 *color);3 U2 X8 m. D* l# e$ `- M
        s32 (*prepare_display)(DevLcd *lcd, u16 sx, u16 ex, u16 sy, u16 ey);
    3 F  x  z! G2 ^5 c5 a5 I5 c5 O    s32 (*onoff)(DevLcd *lcd, u8 sta);5 z2 J  v$ X2 N. P  D, f+ H
        void (*set_dir)(DevLcd *lcd, u8 scan_dir);
    6 d  i7 R- f: \  O& \    void (*backlight)(DevLcd *lcd, u8 sta);
    ' m7 W4 A$ U* t  Z( T5 O5 H+ n( a}_lcd_drv;+ x3 G% K. `" u; e, E: N
  • 成員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)體組合在一起。6 G; r# g9 e8 N' u) y
    1、初始化,根據(jù)設備樹,找到驅(qū)動跟參數(shù),然后初始化上面說的結(jié)構(gòu)體。! m) [/ }* D" W( Y$ n
    2、要使用LCD前,調(diào)用dev_lcd_open函數(shù)。打開成功就返回一個上面的結(jié)構(gòu)體指針。0 V+ i9 l; L: k
    3、顯示字符,接口找到點陣后,通過上面結(jié)構(gòu)體的drv,調(diào)用對應的驅(qū)動程序。
    - M  m( B% x+ [) w7 R$ K; m3 a4、驅(qū)動程序根據(jù)這個結(jié)構(gòu)體,決定操作哪個LCD總線,并且使用這個結(jié)構(gòu)體的變量。
    ! H( x& g5 h9 Q$ a8 J用法和好處
  • 好處1請看測試程序
    4 M; @8 H$ J- l8 {" ^( y! Ivoid dev_lcd_test(void); g1 d% i$ {' n9 u# \4 |: x  |
    {
    0 ^  v$ K, E1 B! k! t    DevLcd *LcdCog;0 p' g0 c+ I* F5 P
        DevLcd *LcdOled;
    / [2 U, I+ Z' e# _    DevLcd *LcdTft;2 j" [' i# x/ O
        /*  打開三個設備 */( q5 w* a0 Z0 B' Q! v) ?
        LcdCog = dev_lcd_open("coglcd");' T$ @( k* O9 B/ ^' d7 [. O' c
        if(LcdCog==NULL)
    " B% D& w" G$ r# B3 u        uart_printf("open cog lcd err\r
    3 Y+ d# l9 t  W- E");+ t& X+ H# L# e+ C
        LcdOled = dev_lcd_open("oledlcd");: K8 P2 ^* Y4 t4 ~$ M5 ^: i7 [
        if(LcdOled==NULL)
    # V  e0 j" o$ P/ O& I# w        uart_printf("open oled lcd err\r
    & t8 G5 N5 d9 R5 n0 U$ Y: A& K");. |* B: b, n  R. @# v# K
        LcdTft = dev_lcd_open("tftlcd");3 ]' G4 E0 B0 }9 @
        if(LcdTft==NULL)
    1 D" _: A" C4 Y. E5 i. F        uart_printf("open tft lcd err\r
    . F: K4 {: k/ ~1 A: C/ P" I");, n9 g. c; a" W4 _6 W: f
        /*打開背光*/
    5 M1 a5 i; o! d  J4 z! l+ o/ G- B    dev_lcd_backlight(LcdCog, 1);
    4 |6 @/ Z( o, L" L6 G: Q7 n    dev_lcd_backlight(LcdOled, 1);
    7 l, e5 m4 _+ f& m: T9 l- A    dev_lcd_backlight(LcdTft, 1);5 R5 J( L/ e% u  A0 k* s# m
        dev_lcd_put_string(LcdOled, FONT_SONGTI_1212, 10,1, "ABC-abc,", BLACK);
    2 v3 J3 b# N  v/ j    dev_lcd_put_string(LcdOled, FONT_SIYUAN_1616, 1, 13, "這是oled lcd", BLACK);
    " @; [. C' t8 e4 |. x& q    dev_lcd_put_string(LcdOled, FONT_SONGTI_1212, 10,30, "www.wujique.com", BLACK);
    3 T* D2 F9 K8 k    dev_lcd_put_string(LcdOled, FONT_SIYUAN_1616, 1, 47, "屋脊雀工作室", BLACK);) J& u' d* Z1 |' O+ s! I( A$ ?9 J
        dev_lcd_put_string(LcdCog, FONT_SONGTI_1212, 10,1, "ABC-abc,", BLACK);; r' P! l( B7 g$ f( L; `
        dev_lcd_put_string(LcdCog, FONT_SIYUAN_1616, 1, 13, "這是cog lcd", BLACK);
    + K& X3 E# p/ Y8 e9 {3 c% ~    dev_lcd_put_string(LcdCog, FONT_SONGTI_1212, 10,30, "www.wujique.com", BLACK);7 K9 r5 s# z: Y# }7 ]$ X: d
        dev_lcd_put_string(LcdCog, FONT_SIYUAN_1616, 1, 47, "屋脊雀工作室", BLACK);
    1 w3 i) x8 r8 Q5 q% n2 H    dev_lcd_put_string(LcdTft, FONT_SONGTI_1212, 20,30, "ABC-abc,", RED);% w& |, G. q% C+ q
        dev_lcd_put_string(LcdTft, FONT_SIYUAN_1616, 20,60, "這是tft lcd", RED);9 ~' a# M' P. g- v
        dev_lcd_put_string(LcdTft, FONT_SONGTI_1212, 20,100, "www.wujique.com", RED);' C' E* H' O7 H0 \* M
        dev_lcd_put_string(LcdTft, FONT_SIYUAN_1616, 20,150, "屋脊雀工作室", RED);
    - f! W6 ?; @6 e" G  d; D    while(1);2 J/ h6 X3 X! R" W/ I
    }
    ! ?  J4 Y+ K, o使用一個函數(shù)dev_lcd_open,可以打開3個LCD,獲取LCD設備。然后調(diào)用dev_lcd_put_string就可以在不同的LCD上顯示。其他所有的gui操作接口都只有一個。這樣的設計對于APP層來說,就很友好。顯示效果:( n- o, g1 a/ u0 Y
    6 K& f/ }8 |2 a7 D# Q$ N! q
  • 好處2現(xiàn)在的設備樹是這樣定義的
    5 X: U& _6 F5 k2 r" _6 LLcdObj LcdObjList[DEV_LCD_C]=
    + p. T" e: Z3 s( J( ?! ?2 A8 x{
    3 Z# f$ ^$ w6 v5 G% w, U2 {    {"oledlcd", LCD_BUS_VSPI, 0X1315},/ ~, s- E5 ?0 p* @3 U0 c5 L
        {"coglcd", LCD_BUS_SPI,  0X7565},
    ! P( e8 w5 d1 f3 d    {"tftlcd", LCD_BUS_8080, NULL},
    % F+ W+ N7 n- q, O4 @. v0 B8 ]};
    # C: T, j: k! ?1 A7 f某天,oled lcd要接到SPI上,只需要將設備樹數(shù)組里面的參數(shù)改一下,就可以了,當然,在一個接口上不能接兩個設備。
    0 R9 t! u8 P, ]2 t( TLcdObj LcdObjList[DEV_LCD_C]=9 @# L( O4 T  W" n" O* p
    {
    ( W0 J& ~0 [# ^7 p$ r1 B) `5 n: O    {"oledlcd", LCD_BUS_SPI, 0X1315},. _. I5 k, H, `$ L& @$ Q% b
        {"tftlcd", LCD_BUS_8080, NULL},6 v  X( A5 H" L% D
    };0 h* v" ]0 G0 q3 m
    字庫暫時不做細說,例程的字庫放在SD卡中,各位移植的時候根據(jù)需要修改。具體參考font.c。: R, e1 c, G+ T  _- l& W4 ~
    聲明代碼請按照版權(quán)協(xié)議使用。當前源碼只是一個能用的設計,完整性與健壯性尚未測試。后續(xù)會放到github,并且持續(xù)更新優(yōu)化。最新消息請關(guān)注www.wujique.com。
    $ G  E8 z* H; Z; o1 I-END-3 i% _- b# B( b2 H' N
    往期推薦:點擊圖片即可跳轉(zhuǎn)閱讀: Z5 a# e. s& y  I) q
                                                           
    9 V  v" K: b6 i                                                               
    . q# I) U* w, j; G1 k! |. E                                                                       
    ( F; ]+ O8 q1 C% C: k4 W8 l# I                                                                               
    : }) }7 ^! W1 o3 V) Y
    6 H1 t+ z9 G; t/ h* ^6 V                                                                                - h" @) z7 m% l
                                                                                            淺談為何不該入行嵌入式技術(shù)開發(fā)
    $ B) K0 @6 ^: u& \. Y8 y                                                                                6 R5 g$ w! i4 |4 x- F, z: L) a
                                                                            ( \% v- G* w' G5 c. [8 n2 {
                                                                    3 E. ?; u9 N% c& w
                                                           
    ; @" D7 X" h5 h# Y/ o  y7 a% n                                               
    9 W# |# u0 J3 V# h- I# j4 j- f) g
    . F+ F9 _- Z6 h2 l                                                       
    / ]/ z; S; {2 g2 ?5 P; C4 Y                                                               
    8 y  Q! s+ [5 ^; D, A6 G                                                                        3 H0 W0 I1 M1 C( ]
                                                                                      ]! y$ e9 p8 B$ A. v2 e3 O

      n1 @5 a9 L" n" V" H# |: @                                                                               
    + E) O: h# F+ k$ {9 m: i9 N) k: o" v                                                                                        在深圳搞嵌入式,從來沒讓人失望過!3 Y, N0 k# T' t; O+ t
                                                                                   
    7 L( Q& i. a( ~  G' T, b$ w                                                                       
    3 i  ]. w$ L, j0 [3 ?% {                                                               
    & n3 O' w: Q1 s- p  V$ ]. u                                                        8 `/ L& R- [% h$ O, L
                                                   
    , _" \1 K6 s) q; J; `4 \/ n+ |1 t6 y" O6 Z. a+ I; s( N
                                                           
    1 x5 B* ]# j3 W- ^8 b; Y0 w                                                               
    1 H% p* v" g& Z                                                                       
    8 w2 B5 `9 x0 C+ d3 `                                                                                ; _. @6 v' {1 k' _) z* }( u

    " O  r8 [4 D3 {" e! A3 D. z                                                                                - S" N% Y* e2 M# X6 F
                                                                                            蘋果iPhone16發(fā)布了,嵌入式鴻蒙,國產(chǎn)化程度有多高?& @4 ]* l0 d2 J8 d  w
                                                                                   
    ) M/ y' @0 N% j1 m                                                                        " b, l1 \) P" }. t' x: ~
                                                                   
    / f+ a  p: M# z" V3 d( L' k6 h                                                       
    + z+ w, f3 Y- j; n) R4 r5 [                                                我是老溫,一名熱愛學習的嵌入式工程師
    7 a, I! _1 z, J, K+ p: u$ ?3 @關(guān)注我,一起變得更加優(yōu)秀!
  • 回復

    使用道具 舉報

    發(fā)表回復

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

    本版積分規(guī)則

    關(guān)閉

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


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