第47章 QR-Decoder-OV5640二維碼識別
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx 中文參考手冊》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。
關於開發板配套的OV5640攝像頭參數可查閱《ov5640datasheet》配套資料獲知。
STM32F4芯片具有浮點運算單元,適合對圖像信息使用DSP進行基本的圖像處理,其處理速度比傳統的8、16位機快得多,而且它還具有與攝像頭通訊的專用DCMI接口,所以使用它驅動攝像頭采集圖像信息並進行基本的加工處理非常適合。本章講解如何使用二維碼識別庫進行二維碼的識別。
47.1 二維碼簡介
二維碼,又稱二維條碼或二維條形碼,二維條碼是用某種特定的幾何圖形按一定規律在平面(二維方向上)分布的黑白相間的圖形記錄數據符號信息的;在代碼編制上巧妙地利用構成計算機內部邏輯基礎的"0"、"1"比特流的概念,使用若干個與二進制相對應的幾何形體來表示文字數值信息,通過圖象輸入設備或光電掃描設備自動識讀以實現信息自動處理:它具有條碼技術的一些共性:每種碼制有其特定的字符集;每個字符占有一定的寬度;具有一定的校驗功能等。同時還具有對不同行的信息自動識別功能、及處理圖形旋轉變化等特點。二維條碼/二維碼能夠在橫向和縱向兩個方位同時表達信息,因此能在很小的面積內表達大量的信息。
47.2 二維條形碼類型
47.2.1 矩陣式二維條碼
矩陣式二維條碼(2D MATRIX BAR CODE)又稱:棋盤式二維條碼。有代表性的矩陣式二維條碼有:QR Code 、Data Matrix、Maxi Code、Code one 等,目前最流行的是QR CODE。見圖 471。
圖 471 矩陣式二維碼
47.2.2 行排列式二維條碼
行排列式二維條碼(2D STACKED BAR CODE)又稱:堆積式二維條碼或層排式二維條碼,其編碼原理是建立在一維條碼基礎之上,按需要堆積成二行或多行。有代表性的行排式二維條碼有:PDF417、CODE49、CODE 16K等。見圖 472。
圖 472 行排列式二維條碼
47.3 二維條形碼的優點
1. 可靠性強,條形碼的讀取准確率遠遠超過人工記錄,平均每15000個字符才會出現一個錯誤。
2. 效率高,條形碼的讀取速度很快,相當於每秒40個字符。
3. 成本低,與其它自動化識別技術相比較,條形碼技術僅僅需要一小張貼紙和相對構造簡單的光學掃描儀,成本相當低廉。
4. 易於制作,條形碼制作:條形碼的編寫很簡單,制作也僅僅需要印刷,被稱作為"可印刷的計算機語言"。
5. 構造簡單,條形碼識別設備的構造簡單,使用方便。
6. 靈活實用,條形碼符號可以手工鍵盤輸入,也可以和有關設備組成識別系統實現自動化識別,還可和其他控制設備聯系起來實現整個系統的自動化管理。
7. 高密度,二維條碼通過利用垂直方向的堆積來提高條碼的信息密度,而且采用高密度圖形表示,因此不需事先建立數據庫,真正實現了用條碼對信息的直接描述。
8. 糾錯功能,二維條形碼不僅能防止錯誤,而且能糾正錯誤,即使條形碼部分損壞,也能將正確的信息還原出來。
9. 多語言形式、可表示圖像,二維條碼具有字節表示模式,即提供了一種表示字節流的機制。不論何種語言文字它們在計算機中存儲時以機內碼的形式表現,而內部碼都是字節碼,可識別多種語言文字的條碼。
10. 具有加密機制,可以先用一定的加密算法將信息加密,再用二維條碼表示。在識別二維條碼時,再加以一定的解密算法,便可以恢復所表示的信息。
47.4 QR二維碼的編碼及識別
47.4.1 QR碼基本結構
QR碼基本結構,見圖 473。
1. 位置探測圖形、位置探測圖形分隔符、定位圖形:用於對二維碼的定位,對每個QR碼來說,位置都是固定存在的,只是大小規格會有所差異。
2. 校正圖形:規格確定,校正圖形的數量和位置也就確定了。
3. 格式信息:表示改二維碼的糾錯級別,分為L、M、Q、H。
4. 版本信息:即二維碼的規格,QR碼符號共有40種規格的矩陣(一般為黑白色),從21x21(版本1),到177x177(版本40),每一版本符號比前一版本 每邊增加4個模塊。
5. 數據和糾錯碼字:實際保存的二維碼信息,和糾錯碼字(用於修正二維碼損壞帶來的錯誤)。
圖 473 QR碼基本結構
47.4.2 QR碼編碼過程
1. 數據分析:確定編碼的字符類型,按相應的字符集轉換成符號字符; 選擇糾錯等級,在規格一定的條件下,糾錯等級越高其真實數據的容量越小。
2. 數據編碼:將數據字符轉換為位流,每8位一個碼字,整體構成一個數據的碼字序列。其實知道這個數據碼字序列就知道了二維碼的數據內容。見表 471和表 472。
表 471 QR碼數據容量
QR碼數據容量 |
|
數字 |
最多7,089字符 |
字母 |
最多4,296字符 |
二進制數(8 bit) |
最多2,593字節 |
日本漢字/片假名 |
最多1,817字符(采用Shift JIS) |
中文漢字 |
最多984字符(采用UTF-8) |
中文漢字 |
最多1,800字符(采用BIG5) |
表 472 QR數據模式指示符
模式 |
指示符 |
ECI |
0111 |
數字 |
0001 |
字母數字 |
0010 |
8位字節 |
0100 |
日本漢字 |
1000 |
中國漢字 |
1101 |
結構鏈接 |
0011 |
FNC1 |
0101(第一位置) 1001(第二位置) |
終止符(信息結尾) |
0000 |
3. 編碼過程:數據可以按照一種模式進行編碼,以便進行更高效的解碼,例如:對數據:01234567編碼(版本1-H)。
a) 分組:012 345 67
b) 轉成二進制:
012 → 0000001100
345 → 0101011001
67 → 1000011
c) 轉成序列:0000001100 0101011001 1000011
d) 字符數轉成二進制:8 → 0000001000
e) 加入模式指示符:
0001:0001 0000001000 0000001100 0101011001 1000011
對於字母、中文、日文等只是分組的方式、模式等內容有所區別。基本方法是一致的。
4. 糾錯編碼:按需要將上面的碼字序列分塊,並根據糾錯等級和分塊的碼字,產生糾錯碼字,並把糾錯碼字加入到數據碼字序列后面,成為一個新的序列。
錯誤修正容量, L水平有7%的字碼可被修正; M水平有15%的字碼可被修正;Q水平有25%的字碼可被修正;H水平有30%的字碼可被修正。
二維碼規格和糾錯等級確定的情況下,其實它所能容納的碼字總數和糾錯碼字數也就確定了,比如:版本10,糾錯等級時H時,總共能容納346個碼字,其中224個糾錯碼字。
就是說二維碼區域中大約1/3的碼字時冗余的。對於這224個糾錯碼字,它能夠糾正112個替代錯誤(如黑白顛倒)或者224個據讀錯誤(無法讀到或者無法譯碼),這樣糾錯容量為:112/346=32.4%。
5. 構造最終數據信息:在規格確定的條件下,將上面產生的序列按次序放如分塊中,按規定把數據分塊,然后對每一塊進行計算,得出相應的糾錯碼字區塊,把糾錯碼字區塊按順序構成一個序列,添加到原先的數據碼字序列后面。
例如:D1, D12, D23, D35, D2, D13, D24, D36, ... D11, D22, D33, D45, D34, D46, E1, E23,E45, E67, E2, E24, E46, E68,...
6. 構造矩陣:將探測圖形、分隔符、定位圖形、校正圖形和碼字模塊放入矩陣中。把上面的完整序列填充到相應規格的二維碼矩陣的區域中,見圖 474 構造矩陣。
圖 474 構造矩陣
7. 掩摸:將掩摸圖形用於符號的編碼區域,使得二維碼圖形中的深色和淺色(黑色和白色)區域能夠比率最優的分布。見圖 474 構造矩陣。
8. 格式和版本信息:生成格式和版本信息放入相應區域內。版本7-40都包含了版本信息,沒有版本信息的全為0。二維碼上兩個位置包含了版本信息,它們是冗余的。版本信息共18位,6X3的矩陣,其中6位是數據位,如版本號8,數據位的信息時 001000,后面的12位是糾錯位。
47.4.3 QR碼識別過程
通過圖像的采集設備(激光掃描器、面陣CCD、數碼相機等成像設備),我們得到含有條碼的圖像,此后主要經過條碼定位(預處理,定位,角度糾正和特征值提取)、分割和解碼三個步驟實現條碼的識別。
1. 條碼的定位就是找到條碼符號的圖像區域,對有明顯條碼特征的區域進行定位。然后根據不同條碼的定位圖形結構特征對不同的條碼符號進行下一步的處理。
2. 實現條碼的定位,采用以下步驟:
a) 利用點運算的閾值理論將采集到的圖象變為二值圖像, 即對圖像進行二值化處理;
b) 得到二值化圖像后,對其進行膨脹運算;
c) 對膨脹后的圖象進行邊緣檢測得到條碼區域的輪廓;
下圖 475是經過上述處理后得到的一系列圖像。
圖 475 圖像處理
3. 對圖像進行二值化處理,按下式進行
其中,f(x,y)是點(x,y)處像素的灰度值,T為閾值(自適應門限)。找到條碼區域后,我們還要進一步區分到底是哪種矩陣式條碼。下面圖形是幾種常見的矩陣式條碼:
a) 位於左上角、左下角、右上角的三個定位圖形
b) 位於符號中央的三個等間距同心圓環(或稱公牛眼定位圖形)
c) 位於左邊和下邊的兩條垂直的實線段
圖 476 圖像處理
4. 條碼的分割
邊緣檢測后條碼區域的邊界不是很完整,所以需要進一步的修正邊界,然后分割出一個完整的條碼區域。首先采用區域增長的方法對符號進行分割,以此修正條碼邊界。其基本思想是從符號內的一個小區域(種子)開始,通過區域增長來修正條碼邊界,把符號內的所有點都包括在這個邊界內。然后通過凸殼計算准確分割出整個符號。之后區域增長和凸殼計算交替進行,通常對那些密度比較大的條碼重復兩次就足夠了,而對於那些模塊組合比較稀疏的條碼至少要重復四次。
5. 譯碼
得到一幅標准的條碼圖像后,對該符號進行網格采樣,對網格每一個交點上的圖像像素取樣,並根據閾值確定是深色塊還是淺色塊。構造一個位圖,用二進制的"1"表示深色像素, "0"表示淺色像素,從而得到條碼的原始二進制序列值,然后對這些數據進行糾錯和譯碼,最后根據條碼的邏輯編碼規則把這些原始的數據位流轉換成數據碼字,即將碼字圖像符號換成ASCII碼字符串。
47.5 QR-Decoder-OV564攝像頭實驗
本小節講解如何使用QR-Code庫在DCMI—OV5640攝像頭實驗基礎上進行二維碼解碼的過程,建議學習之前先把DCMI—OV5640攝像頭實驗弄明白。
學習本小節內容時,請打開配套的"QR-Decoder-OV5640"工程配合閱讀。由於硬件設計方面跟DCMI—OV5640攝像頭實驗的是一樣的,這里不再重復。下面直接介紹如何使用QR-Code庫進行二維碼識別。OV5640識別二維碼的過程包括以下幾個重要部分:圖像采集,液晶驅動,圖像處理,數據解碼,串口打印輸出結果。見圖 477。
圖 477 OV5640識別二維碼過程
47.5.1 QR-Code解碼庫特點
QR-Code解碼庫是秉火專門針對STM32F429移植的一個的條碼解碼庫,因為其結構復雜,移植過程繁瑣,所以打包為一個解碼庫,提供接口方便用戶直接調用,提高開發的效率。其主要特點如下:
條碼種類: 支持常用QR-Code、EAN、UPC
掃描速度: 400 毫秒
掃描英文: 250 個字符
掃描中文: 90中文字符,UTF-8編碼格式(需上位機支持)
多碼掃描: 支持多個二維碼同時解碼,同時輸出結果
47.5.2 軟件設計
1. 編程要點
根據OV5640識別二維碼的過程,軟件設計可以根據以下幾個模塊分別進行:
(1) 圖像采集,通過STM32F429的DCMI接口驅動OV5640,采集適合液晶屏分辨率的圖像。OV5640支持自動對焦功能,因此很容易采集到高清度的圖像。
(2) 液晶驅動,通過STM32F429的LTDC接口驅動液晶屏,使用外部SDRAM作為液晶屏的顯存,通過DMA2D來刷屏;同時LTDC支持雙層疊加顯示,可以在液晶屏上實現半透明的掃描窗並且支持繪制掃描線的動畫效果。
(3) 圖像處理,使用外部SDRAM作為緩存為圖像處理提供足夠的空間,通過調用QR-Code解碼庫的get_image函數獲取一幀圖像。通過圖像處理將圖像的數據流轉變為一個二進制的碼流再進行數據解碼。
(4) 數據解碼,直接通過QR_decoder函數來解碼。返回值為解碼的條碼個數。並將解碼結果保存到decoded_buf的二維數組當中。
(5) 串口發送,根據解碼結果的個數及decoded_buf二維數組的數據,通過串口發送到電腦上位機。
2. 代碼分析
QR-Code解碼庫相關宏定義
我們把QR-Code解碼庫相關的配置都以宏的形式定義到"qr_decoder_user.h"文件中,其中包括數據緩沖基地址、掃描窗大小、掃描框線條大小、解碼結果二維數組、掃描二維碼的函數,見代碼清單 242。
代碼清單 471 QR-Code解碼庫配置相關的宏
1 #ifndef __QR_DECODER_USER_H
2 #define __QR_DECODER_USER_H
3
4 #include "qr_decoder.h"
5 #include <stdio.h>
6
7 // 開辟SDRAM的3M字節作為數據緩存,這里使用顯存以外的空間,
8 // 0xD0800000-0x300000 = 0xD0500000
9 #define QR_FRAME_BUFFER ((uint32_t)0xD0500000)
10
11 /*掃描窗口參數*/
12 #define Frame_width ((uint16_t)320)//掃描窗口邊長(正方形)
13
14 /*掃描框線條參數*/
15 #define Frame_line_length ((uint16_t)30) //掃描框線條長度
16 #define Frame_line_size ((uint16_t)3) //掃描框線條寬度
17
18 #define QR_SYMBOL_NUM 5 //識別二維碼的最大個數
19 #define QR_SYMBOL_SIZE 512 //每組二維碼的的最大容量
20
21 //解碼數據封裝為二維數組decoded_buf,格式為:
22 // (第一組:解碼類型長度(8bit)+解碼類型名稱+解碼數據長度(16bit,高位在前低位在后)+ 解碼數據)
23
24 // (第二組:解碼類型長度(8bit)+解碼類型名稱+解碼數據長度(16bit,高位在前低位在后)+ 解碼數據)
25
26 // 。。。
27 //以此類推
28 extern char decoded_buf[QR_SYMBOL_NUM][QR_SYMBOL_SIZE];
29
30 //解碼函數,返回值為識別條碼的個數
31 char QR_decoder(void);
32
33 //獲取一幀圖像
34 void get_image(uint32_t src_addr,uint16_t img_width,uint16_t img_height);
35
36 #endif /* __QR_DECODER_USER_H */
以上代碼首先定義一個3M字節的空間用作解碼庫的數據的緩沖,只需要定義SDRAM的空閑空間的基地址;然后定義掃描二維碼的窗口及框體大小,范圍由100~480(圖像不能太小,否則圖像很難識別);定義decoded_buf[QR_SYMBOL_NUM][QR_SYMBOL_SIZE]二維數組存放解碼的結果,存放解碼的最大個數由QR_SYMBOL_NUM決定,存放解碼的最大數據量由QR_SYMBOL_SIZE決定,沒有特殊要求就不需要做變動;存放數據的格式介紹如下表 473。
表 473 二維數組數據格式
數組 |
十六進制 |
字符 |
含義 |
decoded_buf[0][0] |
0x07 |
第一組解碼類型名字的長度 |
|
decoded_buf[0][1] |
0x51 |
Q |
第一組解碼類型名字:QR-Code |
decoded_buf[0][2] |
0x52 |
R |
|
decoded_buf[0][3] |
0x2d |
- |
|
decoded_buf[0][4] |
0x43 |
C |
|
decoded_buf[0][5] |
0x6f |
o |
|
decoded_buf[0][6] |
0x64 |
d |
|
decoded_buf[0][7] |
0x65 |
e |
|
decoded_buf[0][8] |
0x00 |
第一組解碼數據長度的高八位 |
|
decoded_buf[0][9] |
0x15 |
第一組解碼數據長度的低八位 |
|
decoded_buf[0][10] |
0x68 |
h |
第一組解碼數據:http://www.firebbs.cn |
decoded_buf[0][11] |
0x74 |
t |
|
decoded_buf[0][12] |
0x74 |
t |
|
decoded_buf[0][13] |
0x70 |
p |
|
decoded_buf[0][14] |
0x3a |
: |
|
decoded_buf[0][15] |
0x2f |
/ |
|
decoded_buf[0][16] |
0x2f |
/ |
|
decoded_buf[0][17] |
0x77 |
w |
|
decoded_buf[0][18] |
0x77 |
w |
|
decoded_buf[0][19] |
0x77 |
w |
|
decoded_buf[0][20] |
0x2e |
. |
|
decoded_buf[0][21] |
0x66 |
f |
|
decoded_buf[0][22] |
0x69 |
i |
|
decoded_buf[0][23] |
0x72 |
r |
|
decoded_buf[0][24] |
0x65 |
e |
|
decoded_buf[0][25] |
0x62 |
b |
|
decoded_buf[0][26] |
0x62 |
b |
|
decoded_buf[0][27] |
0x73 |
s |
|
decoded_buf[0][28] |
0x2e |
. |
|
decoded_buf[0][29] |
0x63 |
c |
|
decoded_buf[0][30] |
0x6e |
n |
|
decoded_buf[1][0] |
0x07 |
第二組解碼類型名字的長度 |
|
decoded_buf[1][1] |
0x51 |
Q |
第二組解碼類型名字:QR-Code |
decoded_buf[1][2] |
0x52 |
R |
|
decoded_buf[1][3] |
0x2d |
- |
|
decoded_buf[1][4] |
0x43 |
C |
|
decoded_buf[1][5] |
0x6f |
o |
|
decoded_buf[1][6] |
0x64 |
d |
|
decoded_buf[1][7] |
0x65 |
e |
|
decoded_buf[1][8] |
0x00 |
第二組解碼數據長度的高八位 |
|
decoded_buf[1][9] |
0x03 |
第二組解碼數據長度的低八位 |
|
decoded_buf[1][10] |
0x31 |
1 |
第二組解碼數據:123 |
decoded_buf[1][11] |
0x32 |
2 |
|
decoded_buf[1][12] |
0x33 |
3 |
|
decoded_buf[2][0] |
第三組解碼類型名字的長度 |
||
… |
… |
… |
… |
QR_decoder為解碼函數,用戶可以直接調用這個函數,返回值為解碼成功的個數。get_image函數為獲取圖片的函數,通過指定存放圖片的首地址,圖片的分辨率來獲取圖片。
圖像采集
我們需要通過OV5640攝像頭采集的圖像數據傳遞到解碼庫解碼,在幀中斷提取一幀圖片用來解碼,見代碼清單 243。
代碼清單 472 DCMI的中斷響應函數(stm32f4xx.it)
1 //使用幀中斷重置line_num,可防止有時掉數據的時候DMA傳送行數出現偏移
2 void DCMI_IRQHandler(void)
3 {
4 /*判斷幀中斷標志位是否被置位*/
5 if ( DCMI_GetITStatus (DCMI_IT_FRAME) == SET ) {
6 /*傳輸完一幀,計數復位*/
7 line_num=0;
8 /*停止采集*/
9 DCMI_CaptureCmd(DISABLE);
10 /*獲取一幀圖片,FSMC_LCD_ADDRESS為存放圖片的首地址*/
11 /*LCD_PIXEL_WIDTH為圖片寬度,LCD_PIXEL_HEIGHT為圖片高度*/
12 get_image(FSMC_LCD_ADDRESS,LCD_PIXEL_WIDTH,LCD_PIXEL_HEIGHT);
13 /*繪制掃描窗口里邊的掃描線,放在這里主要是避免屏幕閃爍*/
14 LCD_Line_Scan_ARGB8888();
15 /*重新開始采集*/
16 DCMI_CaptureCmd(ENABLE);
17 /*清除幀中斷標志位*/
18 DCMI_ClearITPendingBit(DCMI_IT_FRAME);
19 }
20
21 }
在DCMI中斷函數中增加獲取圖片函數,先停止攝像頭的采集,然后通過get_image 函數獲取一幀圖片,這個函數傳遞的第一個參數FSMC_LCD_ADDRESS是圖片存放的首地址,第二個參數LCD_PIXEL_WIDTH為圖片寬度,第三個參數是LCD_PIXEL_HEIGHT為圖片高度,圖片通過這個函數傳遞給解碼函數進行解碼,主函數將介紹如何調用解碼函數。
通過LCD_Line_Scan_ARGB8888函數來繪制掃描線,繪制完后再啟動攝像頭的采集。LCD_Line_Scan_ARGB8888函數放在這個位置解決了當同時操作液晶的前景層和背景層時閃爍的問題。
液晶驅動
F429的LTDC支持雙層疊加顯示功能,具體可以參考我們LTDC部分章節的詳細介紹。現在主要介紹如何繪制掃描窗口。我們定義背景層為顯示攝像頭圖像層,前景層為掃描框顯示層,代碼清單 455。
代碼清單 473 配置DMA數據傳輸(bsp_ov5640.c文件)
1 /*掃描窗口參數*/
2 #define Frame_width ((uint16_t)320)//掃描窗口邊長(正方形)
3
4 /*掃描框線條參數*/
5 #define Frame_line_length ((uint16_t)30) //掃描框線條長度
6 #define Frame_line_size ((uint16_t)3) //掃描框線條寬度
7
8 //指定掃描窗口里邊掃描線的初始位置
9 int pos=(LCD_PIXEL_HEIGHT-Frame_width)/2+5*Frame_line_size;
10 /**
11 * @brief 清屏
12 * @param Color: 清屏顏色
13 * @retval None
14 */
15 void LCD_Clear_ARGB8888(uint32_t Color)
16 {
17 DMA2D_InitTypeDef DMA2D_InitStruct;
18
19 uint16_t Alpha_Value=0,Red_Value = 0, Green_Value = 0, Blue_Value = 0;
20
21 Alpha_Value = (0xFF000000&Color)>>24;
22 Red_Value = (0x00FF0000 & Color) >> 16;
23 Blue_Value = 0x000000FF & Color;
24 Green_Value = (0x0000FF00 & Color) >> 8;
25
26 /* configure DMA2D */
27 DMA2D_DeInit();
28 DMA2D_InitStruct.DMA2D_Mode = DMA2D_R2M;
29 DMA2D_InitStruct.DMA2D_CMode = DMA2D_ARGB8888;
30 DMA2D_InitStruct.DMA2D_OutputGreen = Green_Value;
31 DMA2D_InitStruct.DMA2D_OutputBlue = Blue_Value;
32 DMA2D_InitStruct.DMA2D_OutputRed = Red_Value;
33 DMA2D_InitStruct.DMA2D_OutputAlpha = Alpha_Value; //設置透明度
34 DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CurrentFrameBuffer;
35 DMA2D_InitStruct.DMA2D_OutputOffset = 0;
36 DMA2D_InitStruct.DMA2D_NumberOfLine = LCD_PIXEL_HEIGHT;
37 DMA2D_InitStruct.DMA2D_PixelPerLine = LCD_PIXEL_WIDTH;
38 DMA2D_Init(&DMA2D_InitStruct);
39
40 /* Start Transfer */
41 DMA2D_StartTransfer();
42
43 /* Wait for CTC Flag activation */
44 while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET) {
45 }
46 }
47 /**
48 * @brief 繪制一條線條
49 * @param Xpos: 起點X軸坐標,范圍0 到800
50 * @param Ypos: 起點Y軸坐標,范圍0 到480
51 * @param Length: 線條長度
52 * @param Line_width: 線條寬度
53 * @param Direction: 線條方向(水平或者垂直).
54 * @retval None
55 */
56 void LCD_DrawLine_ARGB8888(
57 uint16_t Xpos,
58 uint16_t Ypos,
59 uint16_t Length,
60 uint8_t Line_width,
61 uint8_t Direction)
62 {
63 DMA2D_InitTypeDef DMA2D_InitStruct;
64
65 uint32_t Xaddress = 0;
66 uint16_t Alpha_Value=0,Red_Value = 0, Green_Value = 0, Blue_Value = 0;
67 //提取各通道的顏色值
68 Alpha_Value = (0xFF000000&CurrentTextColor_ARGB8888)>>24;
69 Red_Value = (0x00FF0000 & CurrentTextColor_ARGB8888) >> 16;
70 Blue_Value = 0x000000FF & CurrentTextColor_ARGB8888;
71 Green_Value = (0x0000FF00 & CurrentTextColor_ARGB8888) >> 8;
72 //指定繪制的首地址
73 Xaddress = CurrentFrameBuffer + 4*(LCD_PIXEL_WIDTH*Ypos + Xpos);
74
75 //配置 DMA2D
76 DMA2D_DeInit();
77 DMA2D_InitStruct.DMA2D_Mode = DMA2D_R2M;
78 DMA2D_InitStruct.DMA2D_CMode = DMA2D_ARGB8888;
79 DMA2D_InitStruct.DMA2D_OutputGreen = Green_Value;
80 DMA2D_InitStruct.DMA2D_OutputBlue = Blue_Value;
81 DMA2D_InitStruct.DMA2D_OutputRed = Red_Value;
82 DMA2D_InitStruct.DMA2D_OutputAlpha = Alpha_Value;
83 DMA2D_InitStruct.DMA2D_OutputMemoryAdd = Xaddress;
84 //水平方向
85 if (Direction == LCD_DIR_HORIZONTAL) {
86 DMA2D_InitStruct.DMA2D_OutputOffset = LCD_PIXEL_WIDTH-Length;
87 DMA2D_InitStruct.DMA2D_NumberOfLine = Line_width;
88 DMA2D_InitStruct.DMA2D_PixelPerLine = Length;
89 } else { //垂直方向
90 DMA2D_InitStruct.DMA2D_OutputOffset = LCD_PIXEL_WIDTH - Line_width;
91 DMA2D_InitStruct.DMA2D_NumberOfLine = Length;
92 DMA2D_InitStruct.DMA2D_PixelPerLine = Line_width;
93 }
94
95 DMA2D_Init(&DMA2D_InitStruct);
96 // 開始傳輸
97 DMA2D_StartTransfer();
98 //等待傳輸完成
99 while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET) {
100 }
101
102 }
103 /**
104 * @brief 繪制一個矩形線條框.
105 * @param Xpos: X起始位置, 范圍 0 —— 800
106 * @param Ypos: Y起始位置,范圍 0 —— 480
107 * @param Width: 顯示線條的寬度度, 范圍 0 —— 800
108 * @param Height:顯示線條的高度, 范圍 0 —— 480
109 * @param Line_width:顯示線條的寬度
110 * @retval None
111 */
112 void LCD_DrawRect_ARGB8888(
113 uint16_t Xpos,
114 uint16_t Ypos,
115 uint16_t Width,
116 uint16_t Height,
117 uint8_t Line_width)
118 {
119 //繪制水平方向的線條
120 LCD_DrawLine_ARGB8888(Xpos, Ypos, Width,Line_width ,LCD_DIR_HORIZONTAL);
121 LCD_DrawLine_ARGB8888(Xpos, (Ypos+ Height), Width+Line_width,
122 Line_width ,LCD_DIR_HORIZONTAL);
123
124 //繪制垂直方向的線條
125 LCD_DrawLine_ARGB8888(Xpos, Ypos, Height, Line_width ,LCD_DIR_VERTICAL);
126 LCD_DrawLine_ARGB8888((Xpos + Width), Ypos, Height, Line_width ,LCD_DIR_VERTICAL);
127 }
128
129
130 /**
131 * @brief 在顯示區域中心繪制一個矩形.
132 * @param Width: 顯示圖像的寬度, 范圍 0 —— 800
133 * @param Height:顯示圖像的高度, 范圍 0 —— 480
134 * @retval None
135 */
136 void LCD_DrawFullRect_ARGB8888(uint16_t Width, uint16_t Height)
137 {
138 DMA2D_InitTypeDef DMA2D_InitStruct;
139
140 uint32_t Xaddress = 0;
141 uint16_t Alpha_Value=0,Red_Value = 0, Green_Value = 0, Blue_Value = 0;
142
143 //提取各通道的顏色值
144 Alpha_Value = (0xFF000000&CurrentTextColor_ARGB8888)>>24;
145 Red_Value = (0x00FF0000 & CurrentTextColor_ARGB8888) >> 16;
146 Blue_Value = 0x000000FF & CurrentTextColor_ARGB8888;
147 Green_Value = (0x0000FF00 & CurrentTextColor_ARGB8888) >> 8;
148
149 //指定繪制的首地址
150 Xaddress = CurrentFrameBuffer +
151 4*(LCD_PIXEL_WIDTH*(LCD_PIXEL_HEIGHT-Height)/2 +
152 (LCD_PIXEL_WIDTH-Width)/2);
153
154 //配置 DMA2D
155 DMA2D_DeInit();
156 DMA2D_InitStruct.DMA2D_Mode = DMA2D_R2M;
157 DMA2D_InitStruct.DMA2D_CMode = DMA2D_ARGB8888;
158 DMA2D_InitStruct.DMA2D_OutputGreen = Green_Value;
159 DMA2D_InitStruct.DMA2D_OutputBlue = Blue_Value;
160 DMA2D_InitStruct.DMA2D_OutputRed = Red_Value;
161 DMA2D_InitStruct.DMA2D_OutputAlpha = Alpha_Value;
162 DMA2D_InitStruct.DMA2D_OutputMemoryAdd = Xaddress;
163 DMA2D_InitStruct.DMA2D_OutputOffset = (LCD_PIXEL_WIDTH - Width);
164 DMA2D_InitStruct.DMA2D_NumberOfLine = Height;
165 DMA2D_InitStruct.DMA2D_PixelPerLine = Width;
166 DMA2D_Init(&DMA2D_InitStruct);
167
168 //開始傳輸
169 DMA2D_StartTransfer();
170
171 //等待傳輸完成
172 while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET) {
173 }
174
175 LCD_SetTextColor(CurrentTextColor);
176 }
177
178 /**
179 * @brief 繪制一個掃描窗口.
180 * @param Width: 正方形的邊長.
181 * @param Length:邊框的長度.
182 * @param size: 邊框的線寬.
183 * @param color:掃描框的顏色.
184 * @retval None
185 */
186 void LCD_View_Finder_ARGB8888(
187 uint16_t Width,
188 uint16_t Length,
189 uint16_t size ,
190 uint32_t color)
191 {
192 //設置當前顏色
193 LCD_SetTextColor_ARGB8888(color);
194 //繪制矩形框
195 LCD_DrawRect_ARGB8888((LCD_PIXEL_WIDTH-Width)/2,
196 (LCD_PIXEL_HEIGHT-Width)/2,Width,Width-size,size);
197 //設置當前顏色為透明
198 LCD_SetTextColor_ARGB8888(TRANSPARENCY_ARGB8888);
199 //繪制線條
200 LCD_DrawLine_ARGB8888((LCD_PIXEL_WIDTH-Width)/2+Length,
201 (LCD_PIXEL_HEIGHT-Width)/2,Width-2*Length,size, LCD_DIR_HORIZONTAL);
202
203 LCD_DrawLine_ARGB8888((LCD_PIXEL_WIDTH-Width)/2+Length,
204 (LCD_PIXEL_HEIGHT+Width)/2-size,Width-2*Length,size, LCD_DIR_HORIZONTAL);
205
206 LCD_DrawLine_ARGB8888((LCD_PIXEL_WIDTH-Width)/2,
207 (LCD_PIXEL_HEIGHT-Width)/2+Length,Width-2*Length,size, LCD_DIR_VERTICAL);
208
209 LCD_DrawLine_ARGB8888((LCD_PIXEL_WIDTH+Width)/2,
210 (LCD_PIXEL_HEIGHT-Width)/2+Length, Width-2*Length,size, LCD_DIR_VERTICAL);
211
212 }
213
214 /**
215 * @brief 在掃描框里循環顯示掃描線條.
216 * @param None
217 * @retval None
218 */
219 void LCD_Line_Scan_ARGB8888(void)
220 {
221 //切換為前景層
222 LCD_SetLayer(LCD_FOREGROUND_LAYER);
223 //設置圖形顏色為透明
224 LCD_SetTextColor_ARGB8888(TRANSPARENCY_ARGB8888);
225 //畫一條透明顏色的線條,即清除上一次繪制的線條
226 LCD_DrawLine_ARGB8888((LCD_PIXEL_WIDTH-Frame_width+8*Frame_line_size)/2,pos,
227 Frame_width-8*Frame_line_size,Frame_line_size, LCD_DIR_HORIZONTAL);
228 //改變線條位置
229 pos=pos+Frame_line_size;
230 //判斷線條是否越界
231 if (pos>=((LCD_PIXEL_HEIGHT+Frame_width)/2-5*Frame_line_size)) {
232 pos = (LCD_PIXEL_HEIGHT-Frame_width)/2+5*Frame_line_size;
233 }
234 //設置圖形顏色為紅色
235 LCD_SetTextColor_ARGB8888(0xD0FF0000);
236 //繪制一條線線條
237 LCD_DrawLine_ARGB8888((LCD_PIXEL_WIDTH-Frame_width+8*Frame_line_size)/2,pos,
238 Frame_width-8*Frame_line_size,Frame_line_size, LCD_DIR_HORIZONTAL);
239 }
通過宏定義Frame_width為掃描窗口的寬度,定義Frame_line_length為掃描線的長度,
Frame_line_size的線條的寬度。
LCD_Clear_ARGB8888為清屏函數,配置DMA2D模式為R2M,意思是寄存器到存儲器,顏色為ARGB8888模式,Alpha_Value為透明度的設置參數,范圍是0~255,0為全透明,255為不透明,半透明取中間值127即可。
LCD_DrawLine_ARGB8888為繪制線條函數,通過配置DMA2D來繪制,跟RGB565模式類似,主要注意每個像素的大小為4個字節。
LCD_DrawRect_ARGB8888為繪制矩形框函數,實際上就是畫線,畫四條線條組成一個矩形。
LCD_View_Finder_ARGB8888是繪制掃描框的函數,首先是繪制一個矩形,然后將各個邊上的線的中間部分刷一遍透明色,就成了掃描框。
LCD_Line_Scan_ARGB8888為掃描線條函數,目的是為了在掃描窗口里邊的從上往下畫線形成掃描線的效果。需要注意的是每次畫線之前先清掉上一次畫的線條。
圖像處理
圖像處理部分已經封裝到解碼庫里邊,並預留了與之相關的接口,通過宏定義QR_FRAME_BUFFER確保圖像處理的數據緩沖區有3M字節的空間。同時攝像頭需要采集到圖像並傳遞到解碼庫即可。其他圖像數據的處理全部在解碼庫里邊完成。
數據解碼
數據解碼部分已經封裝到解碼庫里邊,並預留了與之相關的接口,通過調用QR_decoder解碼函數對經過圖像處理的數據進行解碼,返回解碼成功的條碼個數。並將解碼結果存進decoded_buf二維數組。
串口發送結果
接下來需要配置USART1的工作模式,我們通過編寫Debug_USART_Config函數完成該功能,見代碼清單 244。
代碼清單 474 配置串口中斷發送模式(bsp_debug_usart.c文件)
1 #include "./usart/bsp_debug_usart.h"
2
3 unsigned int uart_data_len = 0; //串口待發送數據長度
4 unsigned int uart_data_index = 0; //串口已發送數據個數
5 unsigned char uart_send_state= 0; //串口狀態,1表示正在發送,0表示空閑
6 unsigned char uart_tx_buf[UART_MAX_BUF_SIZE] = {0};//串口發送數據緩沖區
7
8 /**
9 * @brief DEBUG_USART GPIO 配置,工作模式配置。115200 8-N-1
10 * @param 無
11 * @retval 無
12 */
13 void Debug_USART_Config(void)
14 {
15 GPIO_InitTypeDef GPIO_InitStructure;
16 USART_InitTypeDef USART_InitStructure;
17 NVIC_InitTypeDef NVIC_InitStructure;
18
19 RCC_AHB1PeriphClockCmd( DEBUG_USART_RX_GPIO_CLK|DEBUG_USART_TX_GPIO_CLK, ENABLE);
20
21 /* 使能 UART 時鍾 */
22 RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);
23
24 /* 連接 PXx 到 USARTx_Tx*/
25 GPIO_PinAFConfig(DEBUG_USART_RX_GPIO_PORT,DEBUG_USART_RX_SOURCE, DEBUG_USART_RX_AF);
26
27 /* 連接 PXx 到 USARTx__Rx*/
28 GPIO_PinAFConfig(DEBUG_USART_TX_GPIO_PORT,DEBUG_USART_TX_SOURCE,DEBUG_USART_TX_AF);
29
30 /* 配置Tx引腳為復用功能 */
31 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
32 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
33 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
34
35 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN ;
36 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
37 GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
38
39 /* 配置Rx引腳為復用功能 */
40 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
41 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
42 GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
43
44 /* 配置串DEBUG_USART 模式 */
45 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
46 USART_InitStructure.USART_WordLength = USART_WordLength_8b;
47 USART_InitStructure.USART_StopBits = USART_StopBits_1;
48 USART_InitStructure.USART_Parity = USART_Parity_No ;
49 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
50 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
51 USART_Init(DEBUG_USART, &USART_InitStructure);
52 USART_Cmd(DEBUG_USART, ENABLE);
53
54 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
55
56 //配置USART1中斷優先級
57 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
58 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
59 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
60 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
61 NVIC_Init(&NVIC_InitStructure);
62
63 }
64
65 /**
66 * @brief 獲取串口發送狀態
67 * @param 無
68 * @retval 1表示正在發送,0表示空閑
69 */
70 uint8_t get_send_sta()
71 {
72 if (uart_send_state)
73 return 1;
74 return 0;
75 }
76 /**
77 * @brief 將數據寫入USART1發送緩沖區
78 * @param dat數據指針,len數據長度
79 * @retval 0表示寫入成功,1表示寫入失敗
80 */
81 uint8_t uart_send_buf(unsigned char *dat, unsigned int len)
82 {
83 unsigned char addr = 0;
84
85 if (uart_send_state)
86 return 1;
87
88 uart_data_len = len;
89 uart_data_index = 0;
90 uart_send_state = 1;
91
92 for (; len > 0; len--)
93 uart_tx_buf[addr++] = *(dat++);
94
95 USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
96
97 return 0;
98 }
99 /**
100 * @brief USART1發送中斷響應函數
101 * @param
102 * @retval
103 */
104 void USART1_IRQ(void)
105 {
106 //發送中斷
107 if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) {
108 if (uart_data_index < uart_data_len) {
109 USART_SendData(USART1, uart_tx_buf[uart_data_index++]);
110 } else {
111 uart_send_state = 0;
112 USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
113 }
114
115 USART_ClearITPendingBit(USART1, USART_IT_TXE);
116 }
117 }
串口的IO的配置跟之前的串口實驗是一樣的,這里說一下串口中斷優先級的配置,首先要聲明NVIC_InitStructure中斷向量初始化的結構體,然后依次填入串口1的中斷通道USART1_IRQn,串口1的中斷搶占式優先級0,響應優先級0,並使能USART1中斷通道,最后初始化這個結構體即可完串口中斷優先級的配置。定義全局變量uart_send_state為串口發送狀態的標志,通過get_send_sta函數獲取當前的串口發送狀態。uart_send_buf函數將待發送的數據寫入待發送緩沖區,然后使能串口1發送中斷,開始發送數據。USART1_IRQ函數是串口1的中斷響應函數的回調函數,當發送數據的緩沖區非空就一直會進入中斷發送數據,直到發送完畢,才將串口發送狀態的標志清零,等待發送數據。
使用TIM2定時器延時
掃描二維碼的時候我們需要用到蜂鳴器作為提示,蜂鳴器是有源的,給電就響掉電就不響,我們通過定時器2的計時來為蜂鳴器響的持續時間延時,TIM2的初始化,見代碼清單 475。
代碼清單 475 使用TIM2定時器延時
1 /**
2 * @brief TIM2產生10ms時基初始化函數
3 * @param
4 * @param
5 * @note
6 */
7 void Time2_init()
8 {
9 NVIC_InitTypeDef NVIC_InitStructure;
10 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
11 TIM_OCInitTypeDef TIM_OCInitStructure;
12
13 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
14 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
15 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
16 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
17 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
18 NVIC_Init(&NVIC_InitStructure);
19
20 TIM_TimeBaseStructure.TIM_Period = 10000; //10000us=10ms
21 TIM_TimeBaseStructure.TIM_Prescaler = 90-1;
22 TIM_TimeBaseStructure.TIM_ClockDivision = 0;
23 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
24 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
25
26 //中斷使能
27 TIM_ITConfig(TIM2, TIM_IT_Update , ENABLE);
28 TIM_Cmd(TIM2, ENABLE);
29 }
30 /**
31 * @brief TIM4_IRQHandler:10ms時基中斷函數
32 * @param
33 * @param
34 * @note
35 */
36 void Time2_IRQ()
37 {
38 static u32 BeepTime=8;
39
40 if (beep_on_flag) {
41 BEEP_ON;
42 if ((--BeepTime) == 0) {
43 BeepTime=8;
44 beep_on_flag =0;
45 BEEP_OFF;
46 }
47 }
48 }
Time2_init函數直接初始化定時器2,分頻系數為90,計數周期為10000,總線頻率為90MHz,中斷周期為90MHz/90*10000=10000us=10ms,每10ms進入TIM2_IRQHandler中斷一次, TIM2_IRQHandler中斷函數調用Time2_IRQ函數,設定延時8*10ms=80ms初始值,如果解碼成功,beep_on_flag被置為1,蜂鳴器通過宏BEEP_ON來觸發響聲,BeepTime開始倒計時,直到BeepTime為0,重新設定延時初始值80ms,復位蜂鳴器狀態標志位,關閉蜂鳴器。蜂鳴器的使用可以參考蜂鳴器的相關章節介紹。
main函數
最后我們來編寫main函數,利用前面講解的函數,掃描二維碼並輸出結果,見代碼清單 2414。
代碼清單 476 main函數
1 /**
2 * @brief 主函數
3 * @param 無
4 * @retval 無
5 */
6 int main(void)
7 {
8 char qr_type_len=0;
9 short qr_data_len=0;
10 char qr_type_buf[10];
11 char qr_data_buf[512];
12 int addr=0;
13 int i=0,j=0;
14 char qr_num=0;
15 /*攝像頭與RGB
16 LED燈共用引腳,不要同時使用LED和攝像頭*/
17
18 Debug_USART_Config();
19
20 /* 配置SysTick 為10us中斷一次,
21 時間到后觸發定時中斷,
22 *進入stm32fxx_it.
23 c文件的SysTick_Handler處理,通過數中斷次數計時
24 */
25 SysTick_Init();
26
27 BEEP_GPIO_Config();
28 /*初始化液晶屏*/
29 LCD_Init();
30 LCD_LayerInit();
31 LTDC_Cmd(ENABLE);
32
33 /*把背景層刷黑色*/
34 LCD_SetLayer(LCD_BACKGROUND_LAYER);
35 LCD_SetTransparency(0xFF);
36 LCD_Clear(LCD_COLOR_BLACK);
37
38 /*初始化后默認使用前景層*/
39 LCD_SetLayer(LCD_FOREGROUND_LAYER);
40 /*默認設置不透明 ,該函數參數為不透明度,范圍
41 0-0xff ,0為全透明,0xff為不透明*/
42 LCD_SetTransparency(0xFF);
43 LCD_Clear_ARGB8888(LCD_COLOR_BLACK_ARGB8888);
44 //繪制透明框
45 LCD_SetTextColor_ARGB8888(TRANSPARENCY_ARGB8888);
46 LCD_DrawFullRect_ARGB8888(Frame_width,Frame_width);
47 //繪制掃描框
48 LCD_View_Finder_ARGB8888(Frame_width,Frame_line_length,
49 Frame_line_size,LCD_COLOR_GREEN_ARGB8888);
50
51 CAMERA_DEBUG("STM32F429 二維碼解碼例程");
52
53
54 /* 初始化攝像頭GPIO及IIC */
55 OV5640_HW_Init();
56
57 /* 讀取攝像頭芯片ID,確定攝像頭正常連接 */
58 OV5640_ReadID(&OV5640_Camera_ID);
59
60 if (OV5640_Camera_ID.PIDH == 0x56) {
61 // sprintf((char*)dispBuf, " OV5640 攝像頭,ID:0x%x",
62 OV5640_Camera_ID.PIDH);
63 // LCD_DisplayStringLine_EN_CH(LINE(0),(uint8_t*)dispBuf);
64 CAMERA_DEBUG("%x %x",OV5640_Camera_ID.PIDH ,OV5640_Camera_ID.
65 PIDL);
66
67 } else {
68 LCD_SetTextColor(LCD_COLOR_RED);
69 LCD_DisplayStringLine_EN_CH(LINE(0),(uint8_t*) "
70 沒有檢測到OV5640,請重新檢查
71 查連接。");
72 CAMERA_DEBUG("沒有檢測到OV5640攝像頭,請重新檢查連
73 接。");
74
75 while (1);
76 }
77
78
79 OV5640_Init();
80
81 OV5640_RGB565Config();
82 OV5640_AUTO_FOCUS();
83
84 //使能DCMI采集數據
85 DCMI_Cmd(ENABLE);
86 DCMI_CaptureCmd(ENABLE);
87
88 Time2_init();
89
90 /*DMA直接傳輸攝像頭數據到LCD屏幕顯示*/
91 while (1) {
92 //二維碼識別,返回識別條碼的個數
93 qr_num = QR_decoder();
94
95 if (qr_num) {
96 //識別成功,蜂鳴器響標志
97 beep_on_flag =1;
98
99 //解碼的數據是按照識別條碼的個數封裝好的
100 二維數組,這些數據需要
101 //根據識別條碼的個數,按組解包並通過串口
102 發送到上位機串口終端
103 for (i=0; i < qr_num; i++) {
104 qr_type_len = qr_result_buf[i][addr++];
105 //獲取解碼類型長度
106
107 for (j=0; j < qr_type_len; j++)
108 qr_type_buf[j]=qr_result_buf[i][addr++];
109 //獲取解碼類型名稱
110
111 qr_data_len = qr_result_buf[i][addr++]<<8;
112 //獲取解碼數據長度高8位
113 qr_data_len |= qr_result_buf[i][addr++];
114 //獲取解碼數據長度低8位
115
116 for (j=0; j < qr_data_len; j++)
117 qr_data_buf[j]=qr_result_buf[i][addr++];
118 //獲取解碼數據
119
120 uart_send_buf((unsigned char *)qr_type_buf,
121 qr_type_len);//串口發送解碼類型
122 while (get_send_sta()); //等待串口發送完畢
123 uart_send_buf((unsigned char *)":", 1);
124 //串口發送分隔符
125 while (get_send_sta()); //等待串口發送完畢
126 uart_send_buf((unsigned char *)qr_data_buf,
127 qr_data_len);//串口發送解碼數據
128 while (get_send_sta()); //等待串口發送完畢
129 uart_send_buf((unsigned char *)"\r\n", 2);
130 //串口發送分隔符
131 while (get_send_sta()); //等待串口發送完畢
132 addr =0;//清零
133 }
134
135 }
136
137 }
138
139 }
在main函數中,首先初始化了串口,然后初始化系統滴答定時器,再初始化液晶屏,注意它是把攝像頭使用的液晶層初始化成RGB565格式。第二層為半透明的掃描窗口,先是通過LCD_Clear_ARGB8888函數整屏填充一個半透明的矩形,然后通過LCD_DrawFullRect_ARGB8888函數在液晶的中心位置再畫一個全透明的矩形,這樣就顯示一個掃描窗口,再用LCD_View_Finder_ARGB8888函數將二維碼的掃描框畫出來。掃描框的大小和顏色都是可以通過宏定義來定義。
攝像頭控制部分,首先調用了OV5640_HW_Init函數初始化DCMI及I2C,然后調用OV5640_ReadID函數檢測攝像頭與實驗板是否正常連接,若連接正常則調用OV5640_Init函數初始化DCMI的工作模式及配置DMA,再調用OV5640_RGB565Config函數向OV5640寫入寄存器配置,再調用OV5640_AUTO_FOCUS函數初始化OV5640自動對焦功能,最后,一定要記住調用庫函數DCMI_Cmd及DCMI_CaptureCmd函數使能DCMI開始捕獲數據,這樣才能正常開始工作。
使用蜂鳴器時需要初始化定時器2,用作解碼成功時蜂鳴器動作持續的延時。
大循環里邊直接調用QR_decoder函數來對二維碼數據進行解碼,返回值為解碼成功的條碼個數,通過二維數組保存解碼結果。然后將解碼結果拆包,發送解碼類型和解碼的數據。掃描中文二維碼的時候特別注意上位機一定要支持UTF-8編碼,否則輸出結果會亂碼。
最后特別注意,這個解碼庫消耗的堆棧比較大,我們需要調大堆棧的大小保證程序能正常穩定運行。
3. 下載驗證
把OV5640接到實驗板的攝像頭接口中,用USB線連接開發板,編譯程序下載到實驗板,並上電復位,打開串口終端助手,液晶屏會顯示攝像頭掃描框,對准二維碼掃描即可把掃描結果發送到串口終端。
47.6 每課一問
1. 為什么液晶屏掃描框里的循環掃描的線條一定要放在場中斷里邊進行?
2. 嘗試多個二維碼放在一起掃描,觀察實驗現象。
3. 嘗試修改例程中的Frame_width,Frame_line_length和Frame_line_size變量,觀察實驗現象。