之前做UI设计,每次用到特殊的字符,就要我重新做字库。 后面有几次要我贴图,可想而知,太鸡巴折腾了,做过的朋友都知道,后面就想着能不能让单片机虚拟U盘,然后图片拖进去,就直接显示出来。 PS:其实想做视频解码来着,后面懒了,先做图片解码。 折腾了一两天,最终还是实现了。 硬件组成: 1.STM32F4 2.OLED12864 3.W25Q64 软件组成: 1.DMA做SPI数据传输,基本底层驱动,就不解释了 2.W25Q64上做FATS文件系统,版本是 3 Q8 C. [% X& k$ _ U! H
3.将W25Q64虚拟成U盘 蛮久以前折腾的了 4.编码解码及屏幕显示 其实解码不难,难的是数据对应,解码我大概花了两三小时,数据对应显示花了将近一天。 因为是OLED12864,只能显示8位的数据,也就没有什么颜色好说的了。 简述一下使用步骤。 首先设备上电,看到我用单片机+W25Q64模拟的U盘 4 L' w# I) e( Z3 I N$ @: f
然后,做张BMP图片,放到U盘里面 
& ?- ^4 w; }" e' I" R" N+ e
电脑打开图片是这样的,图片是我随便截图来的 
( x% d8 R! ^& y( p: U1 c
然后,设备重启上电,OLED上面就显示这张图片。 8 F6 i! r6 z+ T! R- ?" n
这样就意味着我之后想贴图就可以直接贴了,不要再搞来搞去的做字库。 首先是了解BMP的组成结构: 5 b( w1 }% r9 s( V) }1 r
1:位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;: n7 I- M3 [# Q* V) F/ c Z$ M! X
其结构定义如下:
+ M5 E( _8 H0 l- O[size=1em][size=1em]1
. A% v# A# J$ F d$ a[size=1em]2 - i, r8 U0 E6 K2 D/ y1 g
[size=1em]3
: T; L' U( U6 U4 G4 H[size=1em]4 7 }/ V4 }0 D+ k' g
[size=1em]5 9 _0 z F8 d9 a* ?
[size=1em]6 $ u3 k1 t6 r: M3 [) |
[size=1em]7
- P' x* D+ q- P[size=1em]8
, m/ m, G& G) i. H[size=1em]9
s4 b$ b) ?1 V% d | [size=1em][size=1em]typedef struct tagBITMAPFILEHEADER) p- _- w- O1 _! b5 V
[size=1em]{ v' M+ k4 _4 |7 i6 I( m1 B# c& K
[size=1em] WORD bfType;//位图文件的类型,必须为BM(1-2字节)
2 Z$ _9 {$ G. \+ Q& k. [& }[size=1em] DWORD bfSize;//位图文件的大小,以字节为单位(3-6字节,低位在前)5 {. ]# ?* h3 Q9 a ?
[size=1em] WORD bfReserved1;//位图文件保留字,必须为0(7-8字节)" C! z- g) ?( S. {& d1 t
[size=1em] WORD bfReserved2;//位图文件保留字,必须为0(9-10字节)
R4 P( ^5 {6 I0 d& e( B9 E[size=1em] DWORD bfOffBits;//位图数据的起始位置,以相对于位图(11-14字节,低位在前)9 ~6 q% ]* p) S3 e8 a2 a3 x L
[size=1em] //文件头的偏移量表示,以字节为单位- P; R2 J$ e; N% Z: D- x" w
[size=1em]}__attribute__((packed)) BITMAPFILEHEADER;
, B* U5 [" N" Q9 v" ^2 G+ e+ R% y O5 H: C5 g
|
6 N: Z1 N& ^) d2 _, O7 S
H; ]& O& Q W* f/ [: |
B, H: C* y( }4 W/ y2:位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息; j/ G" p0 w+ G
BMP位图信息头数据用于说明位图的尺寸等信息。! y; J. L! n# j" p3 z6 l& d8 h
[size=1em][size=1em]1 ' `. z" H( h# P0 q: m$ ?* J+ p
[size=1em]2 9 j2 u4 \# `6 b# S; Y$ f" h
[size=1em]3 - g0 s/ [# U6 r! S
[size=1em]4 # ^6 x4 } F1 c1 B. u
[size=1em]5 2 D5 W; R. ]# w9 ^' E3 d
[size=1em]6
& U* l% J% p# r/ h8 v6 P7 ?[size=1em]7
" M; W1 }3 I* }) g$ U[size=1em]8 $ s4 S3 A# c! w0 J
[size=1em]9 0 [) u) N) m3 Q( s
[size=1em]10
8 B- G# q _7 y; p% g+ `3 h3 T[size=1em]11
0 \1 d5 A$ L$ S- F[size=1em]12
* b7 F: Z1 z7 m6 G$ O[size=1em]13
/ I6 [+ Y n+ |6 J+ q3 d[size=1em]14 ) v. p+ Y! q! d# \
[size=1em]15 + P) g6 ]/ L0 n7 Q; y5 @
| [size=1em][size=1em]typedef struct tagBITMAPINFOHEADER{
+ ~+ p5 v9 L" z' W- B2 ?8 |; ^[size=1em]DWORD biSize;//本结构所占用字节数(15-18字节)0 ^: Q: U$ Q- g6 V
[size=1em]LONG biWidth;//位图的宽度,以像素为单位(19-22字节), {8 l2 F" B, p7 P& w, a
[size=1em]LONG biHeight;//位图的高度,以像素为单位(23-26字节)7 q3 C' d* Y1 g1 o
[size=1em]WORD biPlanes;//目标设备的级别,必须为1(27-28字节)
5 ~2 p- @) O" I- r: \ x[size=1em]WORD biBitCount;//每个像素所需的位数,必须是1(双色),(29-30字节)% J% z, J% }* N- L4 _# F% Q8 X
[size=1em]//4(16色),8(256色)16(高彩色)或24(真彩色)之一! E% ?/ l3 g2 Z4 F
[size=1em]DWORD biCompression;//位图压缩类型,必须是0(不压缩),(31-34字节)
! D* k- q. T2 S6 B0 p" |, K! E[size=1em]//1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一2 T0 J1 c3 i) e8 b
[size=1em]DWORD biSizeImage;//位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位(35-38字节)' s; m& I% Z9 ]1 h( d
[size=1em]LONG biXPelsPerMeter;//位图水平分辨率,每米像素数(39-42字节)
: b7 X0 F! m6 q- y/ ?5 c! d! F# g: W[size=1em]LONG biYPelsPerMeter;//位图垂直分辨率,每米像素数(43-46字节)
; m" ~" W3 ~1 C; u4 e7 B6 N[size=1em]DWORD biClrUsed;//位图实际使用的颜色表中的颜色数(47-50字节)- ^7 Q. H9 b( j; L# g; @
[size=1em]DWORD biClrImportant;//位图显示过程中重要的颜色数(51-54字节). K; S9 Y$ `) l, h9 ^
[size=1em]}__attribute__((packed)) BITMAPINFOHEADER;
; C! K4 Y7 L; Q& @: x5 ]/ X0 q1 | g
. t) \' s2 l" _# S/ z | ; L7 @* L* F9 j/ P3 ]; ?$ d
5 k6 H0 C( [6 F) ~ F
7 Q- X& ?, i7 N* }8 y. g
3:调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
" ^4 q: `& v$ k+ v- k. [4 }颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:
* o" y' x+ C; C0 T. I[size=1em][size=1em]1
# c J7 l$ v; I( k i) u[size=1em]2
2 M2 r7 J8 s1 y" N. w: n[size=1em]3
) ?" J% n" ]4 I# L* I1 `% H7 E4 C[size=1em]4
" B- |- a* Q( c1 k; b/ `7 u4 b[size=1em]5
, G) H, N" t) N0 j1 u! Y% P[size=1em]6
+ X5 y3 D7 H! S | [size=1em][size=1em]typedef struct tagRGBQUAD{
& F1 E: I9 I! r4 j- O[size=1em]BYTE rgbBlue;//蓝色的亮度(值范围为0-255)8 M, ~* q- d' n7 ^. R1 t$ @* C
[size=1em]BYTE rgbGreen;//绿色的亮度(值范围为0-255)/ `$ V/ ]% x9 {
[size=1em]BYTE rgbRed;//红色的亮度(值范围为0-255)
) P" z. E Q2 O9 J! I( {[size=1em]BYTE rgbReserved;//保留,必须为07 V* a8 `0 T5 |+ s9 \! u
[size=1em]}__attribute__((packed)) RGBQUAD;
" u# }! Y. a" q/ Y% `
% I6 Y3 I7 m E5 X$ V | + y- Y! l6 K" r! v3 B
) g( {/ \5 D$ o6 l8 e- b, K/ M颜色表中RGBQUAD结构数据的个数有biBitCount来确定:
1 ~8 ?$ e0 d n4 ]& k+ H当biBitCount=1,4,8时,分别有2,16,256个表项;
/ [1 E8 f# B7 A' @! ^6 v当biBitCount=24时,没有颜色表项。4 @. p* x4 Q& r8 e2 j
位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:
( ?2 L4 z0 w+ |- `[size=1em][size=1em]1 ! L3 J" w; g ?9 _9 y1 I" @
[size=1em]2
) l: A# \7 E) i4 c6 U[size=1em]3
: j9 r( r4 F% Z$ H- {& F[size=1em]4
@. t8 C+ }4 M! g- x, P | [size=1em][size=1em]typedef struct tagBITMAPINFO{; d( n* s; Y0 n
[size=1em]BITMAPINFOHEADER bmiHeader;//位图信息头- l; u! w, W1 [$ Q$ M4 N" y% x" j
[size=1em]RGBQUAD bmiColors[1];//颜色表7 c$ u9 @% X2 x9 [: d' {# P
[size=1em]}__attribute__((packed)) BITMAPINFO;' g1 O9 e) b' t( I
( ]9 K; G; a- D) D% k: R- W: \
|
& t, \0 I/ r) O) k% S, m0 @( G5 H& j8 r, y
( I7 q7 J7 ]& U) E5 b, r3 U1 a
: i* a1 {& N1 O7 v( L8 P @) x4:位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。
0 `" e( r! E2 h$ {: K位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:6 T; P) ^7 l6 ]' F7 f
当biBitCount=1时,8个像素占1个字节;4 y3 x3 C$ G+ v4 b9 L: M
当biBitCount=4时,2个像素占1个字节;& S: l7 P/ L+ n7 ^- \
当biBitCount=8时,1个像素占1个字节;
/ D* E! ~/ `0 m& q当biBitCount=24时,1个像素占3个字节,按顺序分别为B,G,R;
6 `- i( Y Y. O' mWindows规定一个扫描行所占的字节数必须是7 X* q% g5 M+ b$ W6 I c5 F+ H) k
4的倍数(即以long为单位),不足的以0填充,
A6 y3 M: c$ k- H* zbiSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight;' M$ O" j" G$ m: N9 [
以上内容网上大同小异,都是一样的。 根据以上信息,首先我在程序里面做了一个结构体,存放除显示数据外的所有东西。 
) n0 r0 }( B# {3 Q7 L1 J7 ]. H& w" V
我不需要调色板,也就没用结构体了。 计算上面的数据,一共是62个8位数据,用来存放BMP的定义信息。 程序很简单,首先是U盘读取数据 ret = f_open(&fp, "246.bmp", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);
% E/ z. H [. A( L1 v! K ret = f_read(&fp, readbuff, picsize, &unt); //读取文件头信息 然后提取前62个数据保存到上面说的结构体 Getpicture(readbuff);2 T$ p: D4 b& z! v9 g
' P' n. }- C* k1 y5 A) n$ ?
仿真确认数据正确性 然后是提取图片信息 T9 O$ d- S7 q* T% y& Z! v7 t- \
一个简单的嵌套逻辑。 ' v$ S6 w; h) @% t/ C& X$ y
最后是花了我将近一天的东西,数据显示对应 void Display_BMP_DOT(uint8_t bmp[64][16])7 [8 t ]% E$ i/ `, K
{$ e7 ~: b( o2 j: a9 a5 m: |
unsigned char x,y,z,n,m;
' V1 Y5 f" j+ A uint8_t Oled_Draw_BMP_buff[128];
; g; s7 M, m1 m; I
/ B. N" z+ C. Z7 m
: n4 W* k- j' M7 x for(m=0;m<64;m++)
+ c9 F" `8 \' n; n {' d! W0 H. [2 h, S
for(n=0;n<16;n++)
4 u) B9 D- Q4 Y$ u) e C9 ~2 b! g {
3 m+ \9 o( c# y7 R z = 0x80;
5 F7 h' f l T0 E+ ^5 E D+ `% v+ @; C for(x=0;x<8;x++)4 S s1 }* u' g
{ # w4 y* @2 e! m, ]
Oled_Dot(x+8*n-1,63-m,bmp[m][n]&(z>>x));
9 y; q7 P& t7 P6 ^9 s/ ~ }' q c; R: D# D- y* b' N/ T
}
- ?' I, P5 r6 W* H: E% w& Y }% m8 B8 Q# ~/ W2 ^8 Q1 n
Display_Process(OLED_Display_Data);
; G0 v, Z8 r, Z+ E& | } 这一步的难度在 OLED12864支持的是页写,之前做驱动的时候,刷屏是从左至右从上到下,但是WINDOWS上面,图片提取是,从左至右,从下至上。 . t# [3 c9 ]- ]
|