之前做UI设计,每次用到特殊的字符,就要我重新做字库。 后面有几次要我贴图,可想而知,太鸡巴折腾了,做过的朋友都知道,后面就想着能不能让单片机虚拟U盘,然后图片拖进去,就直接显示出来。 PS:其实想做视频解码来着,后面懒了,先做图片解码。 折腾了一两天,最终还是实现了。 硬件组成: 1.STM32F4 2.OLED12864 3.W25Q64 软件组成: 1.DMA做SPI数据传输,基本底层驱动,就不解释了 2.W25Q64上做FATS文件系统,版本是 / b3 g, a9 x, u2 T" @; N, @% R
3.将W25Q64虚拟成U盘 蛮久以前折腾的了 4.编码解码及屏幕显示 其实解码不难,难的是数据对应,解码我大概花了两三小时,数据对应显示花了将近一天。 因为是OLED12864,只能显示8位的数据,也就没有什么颜色好说的了。 简述一下使用步骤。 首先设备上电,看到我用单片机+W25Q64模拟的U盘 
% n+ M+ Q# `2 a$ N
然后,做张BMP图片,放到U盘里面 / G ~8 ~5 ?% s+ Z. [# c/ d
电脑打开图片是这样的,图片是我随便截图来的 8 M0 p/ j3 u/ R3 | r5 L8 G/ M$ s
然后,设备重启上电,OLED上面就显示这张图片。 
$ k- i. B" o9 B5 P! l. |0 r1 j
这样就意味着我之后想贴图就可以直接贴了,不要再搞来搞去的做字库。 首先是了解BMP的组成结构:
9 g. f8 X/ U5 }5 ^3 Z1:位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
; k, @ B& N3 I3 p7 X其结构定义如下:
d+ s7 B' D( K3 z' |$ I[size=1em][size=1em]1 ) g) W! W; p% {" K5 S `
[size=1em]2
5 U8 D, k& ]& e4 A[size=1em]3 * @( W8 \8 m% n4 C7 z$ l" }+ E
[size=1em]4
2 K* {. ]" q% X[size=1em]5 2 v+ Q! c, c5 [, D
[size=1em]6
: h: Q- j% b/ C1 Q4 M1 ?# x[size=1em]7 8 j6 b( a) G) y- H, K8 S; W1 x4 l9 m
[size=1em]8 , _ U/ s3 \( r9 `- ]( W
[size=1em]9
7 ?9 ~. t3 u$ B0 _4 b1 j* ~ | [size=1em][size=1em]typedef struct tagBITMAPFILEHEADER, e1 f. P( C4 u- h9 b# F+ d4 s, D
[size=1em]{
0 N" z# v! J3 L[size=1em] WORD bfType;//位图文件的类型,必须为BM(1-2字节)
1 O/ K4 Q, c- ?[size=1em] DWORD bfSize;//位图文件的大小,以字节为单位(3-6字节,低位在前)
/ S; E3 {+ y$ V' |) V2 O3 @[size=1em] WORD bfReserved1;//位图文件保留字,必须为0(7-8字节)
1 M+ O" c* h# x$ O6 c- y0 K7 [& q[size=1em] WORD bfReserved2;//位图文件保留字,必须为0(9-10字节)
2 G5 j* L; v- P6 E6 @2 y[size=1em] DWORD bfOffBits;//位图数据的起始位置,以相对于位图(11-14字节,低位在前)
9 h3 c8 Y( B$ [, V[size=1em] //文件头的偏移量表示,以字节为单位- f' t, A4 f6 ?
[size=1em]}__attribute__((packed)) BITMAPFILEHEADER;8 H1 S5 s6 C, O2 s4 [8 u! J9 a# _
5 g9 b+ `4 c# A0 a% L6 s% X3 p |
4 g/ I. T" p: t. S/ V' E" d" E+ g3 v* ~5 ?! o% l1 e
6 w: \; q3 S& [2 z" u
2:位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
$ |( s- \5 G. J k+ gBMP位图信息头数据用于说明位图的尺寸等信息。
1 @, D: t" ~9 E' s[size=1em][size=1em]1 $ M0 `) m/ M' m
[size=1em]2
) J% e; u; n( S+ ]7 o6 L' N[size=1em]3 7 w6 M" F6 q) L; W! V1 L
[size=1em]4 / i# Y( _0 _* g: V, |2 ?
[size=1em]5
/ m/ |( L: F; K4 I* g3 Z/ |# P[size=1em]6
9 u, g I$ W* f5 h) ~3 y: ^4 S[size=1em]7
2 t! c, D, D ^( \! D[size=1em]8 6 u* V( t! E6 a
[size=1em]9 3 n1 J+ q) k6 [/ A9 `
[size=1em]10
+ Y4 j& ^* ?( Q5 D J[size=1em]11 $ R! `& Z% `- w' E. v) P/ b
[size=1em]12 ; ^* d8 Z& d; i* t# t9 \7 ~
[size=1em]13
: } |6 o0 E8 @[size=1em]14
F, A# G3 S2 f# Z G[size=1em]15 9 z0 \! y, [, x8 c$ g7 x3 D1 `
| [size=1em][size=1em]typedef struct tagBITMAPINFOHEADER{
# N7 D) x* h5 L) l. b0 u[size=1em]DWORD biSize;//本结构所占用字节数(15-18字节)
" \7 A7 x3 R% J* ~0 {% Y2 j[size=1em]LONG biWidth;//位图的宽度,以像素为单位(19-22字节)
( D* Z& f1 R1 D# y1 v5 f0 O[size=1em]LONG biHeight;//位图的高度,以像素为单位(23-26字节)
& \3 {/ I5 R' F+ `; d3 i5 l& u2 m[size=1em]WORD biPlanes;//目标设备的级别,必须为1(27-28字节)
4 \& i' Z6 O7 Q4 a9 e[size=1em]WORD biBitCount;//每个像素所需的位数,必须是1(双色),(29-30字节)9 C$ u6 j3 _2 j4 K- R( @
[size=1em]//4(16色),8(256色)16(高彩色)或24(真彩色)之一
3 z# J4 @4 W! E- h/ j[size=1em]DWORD biCompression;//位图压缩类型,必须是0(不压缩),(31-34字节)5 P y0 F0 I9 _3 B
[size=1em]//1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
& z! D4 g5 u) _+ [) S[size=1em]DWORD biSizeImage;//位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位(35-38字节)0 o t9 i! n! }' t
[size=1em]LONG biXPelsPerMeter;//位图水平分辨率,每米像素数(39-42字节)
" l9 G4 j* j7 L& k# y[size=1em]LONG biYPelsPerMeter;//位图垂直分辨率,每米像素数(43-46字节)& R' X# k0 N+ }$ F- h
[size=1em]DWORD biClrUsed;//位图实际使用的颜色表中的颜色数(47-50字节)
7 \1 h: x! I1 }% W; C[size=1em]DWORD biClrImportant;//位图显示过程中重要的颜色数(51-54字节)4 Y7 n. Q$ @' c9 C4 W. l7 {
[size=1em]}__attribute__((packed)) BITMAPINFOHEADER;: `3 ~6 l3 w, n, D& F+ D
: h5 \* g! J q
| ! M" J1 f( Y$ s( {8 v
# W: m4 N, P3 M( y. M& {
. }) S- E. A+ F2 V$ s! R' M1 l
3:调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
' A/ r: D- n+ [. c/ N# b颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:& P; l, \ d/ s, e
[size=1em][size=1em]1 - Z3 v2 j2 J9 L1 n- r; e
[size=1em]2 9 S4 A4 ^' j( O1 S' C5 N( ]8 C9 u
[size=1em]3 7 a* Y% t. Y# G+ O; Q d( N* U
[size=1em]4
! l* x1 C5 s5 ?6 q4 y1 Y( v4 W4 }[size=1em]5
! [6 c* b9 g! G' C) D[size=1em]6 2 k$ }5 O8 h* o
| [size=1em][size=1em]typedef struct tagRGBQUAD{# i, w0 h& f z! l
[size=1em]BYTE rgbBlue;//蓝色的亮度(值范围为0-255)% [# G5 a: H0 |; C7 S5 ]
[size=1em]BYTE rgbGreen;//绿色的亮度(值范围为0-255)
, u0 h* o# N: e. }9 P[size=1em]BYTE rgbRed;//红色的亮度(值范围为0-255) g- b: @6 \2 H% E9 O) D# @. L
[size=1em]BYTE rgbReserved;//保留,必须为02 {' h6 f, o3 V. ]
[size=1em]}__attribute__((packed)) RGBQUAD;
; h p$ A; v- C9 t6 ]
( ?( E1 |3 d$ J' i; m1 O* o |
" S& k* }# ]! R1 T) c0 Y( r, h1 _! z% X9 J/ n9 m0 [7 n, n
颜色表中RGBQUAD结构数据的个数有biBitCount来确定:
8 D8 l. b: `( X9 ^# N当biBitCount=1,4,8时,分别有2,16,256个表项;
! s% z( `% t' Z. A当biBitCount=24时,没有颜色表项。( G6 A Q4 R; K% ]7 d4 `
位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:0 n) t/ O) |; ]& U! c
[size=1em][size=1em]1
5 D* S, q- j$ @/ p[size=1em]2
2 `- H( l0 D( W[size=1em]3 5 y5 Y& S/ j2 B
[size=1em]4 ' W, o. ~& h4 ]$ f o8 \; B5 z
| [size=1em][size=1em]typedef struct tagBITMAPINFO{
. h) o! x& p& U9 B( Z[size=1em]BITMAPINFOHEADER bmiHeader;//位图信息头
6 v0 q3 n$ L' Y1 m' _[size=1em]RGBQUAD bmiColors[1];//颜色表: t# z+ L6 i' O: }
[size=1em]}__attribute__((packed)) BITMAPINFO;+ _6 O7 Q; \5 k- [% y- i0 d
6 q1 l! v2 e/ M | 3 e- \+ s, ?& s1 d7 s% R( Z- R3 n
8 Y3 ^ U* E8 m+ i$ }
. ?- i9 {; d* s
+ S/ D* y) m$ V2 a9 s4:位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。" a8 j! `" M: y
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:
) q) B" C. b4 u1 b1 ~1 @当biBitCount=1时,8个像素占1个字节;1 ^1 f/ S7 c! ^+ ~- w* d, F
当biBitCount=4时,2个像素占1个字节;
" s" @9 ^( R# s) \/ ?0 w当biBitCount=8时,1个像素占1个字节;2 b# F9 N* X( O: T' A
当biBitCount=24时,1个像素占3个字节,按顺序分别为B,G,R;' J2 S# u! s, |
Windows规定一个扫描行所占的字节数必须是" X ?$ J0 k: w+ o. X" _
4的倍数(即以long为单位),不足的以0填充,
( b1 Q1 v! V$ s5 a, j1 ?biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight;6 W7 ~2 J4 ~5 }! O
以上内容网上大同小异,都是一样的。 根据以上信息,首先我在程序里面做了一个结构体,存放除显示数据外的所有东西。 
4 s9 E3 u) P5 I
我不需要调色板,也就没用结构体了。 计算上面的数据,一共是62个8位数据,用来存放BMP的定义信息。 程序很简单,首先是U盘读取数据 ret = f_open(&fp, "246.bmp", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);
+ m" ~" C: j M5 |7 e ret = f_read(&fp, readbuff, picsize, &unt); //读取文件头信息 然后提取前62个数据保存到上面说的结构体 Getpicture(readbuff);, n3 u5 [, }6 u- ^6 N: `2 U
# p+ |: X' d" }- i% I- y
仿真确认数据正确性 然后是提取图片信息 
4 l0 `* ^8 B# k o
一个简单的嵌套逻辑。
0 C, N4 ]: X+ s7 {; {$ `最后是花了我将近一天的东西,数据显示对应 void Display_BMP_DOT(uint8_t bmp[64][16])7 E) x6 e8 T* Y" U7 Y9 J) O
{
4 ~/ J# ]* @8 V1 L* u unsigned char x,y,z,n,m;
4 {" v& e4 g, z; z' O% X6 T uint8_t Oled_Draw_BMP_buff[128];1 P+ g0 A9 M( x7 Y% |8 b4 N# I
z( f" x* F3 h3 X' H# S0 M) g6 j6 x/ a* L5 ]5 t: P, G2 N; e
for(m=0;m<64;m++)
1 |8 Q& b+ \* u# Y/ l {
) W" O: n! c( o8 _+ f: B for(n=0;n<16;n++)
. d* u8 {. Z4 t. m( s { - e8 E& ]9 t# o2 A
z = 0x80;- G6 M/ K* R' m4 r V9 I) ^
for(x=0;x<8;x++): [3 `" L& m+ x3 t8 D
{
& ^' x- K4 o! t, f Oled_Dot(x+8*n-1,63-m,bmp[m][n]&(z>>x));
! F7 |, ?2 t* G2 Q- a' C- P4 ~; h }
0 ?3 b- g( }4 s I+ p' M3 a; O! N/ u }, O; z- D5 j0 ~$ S6 S( f
}
* V7 F# |/ Y* K5 ]( C1 s1 {- n Display_Process(OLED_Display_Data); " V% l o' k5 n. t9 S, h' b
} 这一步的难度在 OLED12864支持的是页写,之前做驱动的时候,刷屏是从左至右从上到下,但是WINDOWS上面,图片提取是,从左至右,从下至上。
2 q' }. M. G+ K, N7 [. u/ h" E |