送个小纪念品吧

思路

既然是一周年,首先要体现出自己的用心,同时为了在这段过程中学到一些东西,一定要挑战自己没有尝试过的领域,最后就是功能和外观要足够好看,技术足够硬。

综合考虑决定用STM32H7写一个相机。芯片选型的话,希望电路板好看一些成本低一些和板子小一点,h7系列的板子一般厂家做的都非常的古朴,所以我选择反客或者WeACT,因为我做相机需要摄像头,像素希望高一些,综合考虑决定反客的stm32h750vbt6。

环境

因为H7系列的内部flash仅有128kb,对于我们存储照片肯定是不够的,所以使用QSPI外的外部flash,同时内部flash映射到外部flash之后就可以正常运行代码了,但遗憾的是,经过我不屑尝试,终于将32的内核锁死了…..

所以我决定使用MDK-ARM,然后花费一些时间安装KeilV5,vscode安装Keil插件,然后就可以愉快的编译和烧录了。

简单配置一下环境之后,我们就可以开始写代码了,KeilV5外观伤眼,这边用vscode更舒服。

4

开始

首先查看一下原理图,根据对应接口开启功能。显示器选择320×240像素的,相对来讲尺寸比较合适。

1

观察发现,这块开发板配备了QSPI的外置flash,所以就需要用到bootloader算法和外部烧录功能/

2

最后查看DCMI和JPEG硬件编码功能,这样我们就算完成了第一步!

打开CubeMx,生成FatFs、USB_Fs的代码,添加进Keil内(CubeMX打开对应功能即可,因为生成的代码都会修改)

接下来编写USB读卡功能,在USB初始化阶段进行映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : usbd_storage_if.c
* @version : v1.0_Cube
* @brief : Memory management layer.
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "usbd_storage_if.h"

/* USER CODE BEGIN INCLUDE */
#include "fatfs.h"
/* USER CODE END INCLUDE */

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY
* @brief Usb device.
* @{
*/

/** @defgroup USBD_STORAGE
* @brief Usb mass storage device module
* @{
*/

/** @defgroup USBD_STORAGE_Private_TypesDefinitions
* @brief Private types.
* @{
*/

/* USER CODE BEGIN PRIVATE_TYPES */

/* USER CODE END PRIVATE_TYPES */

/**
* @}
*/

/** @defgroup USBD_STORAGE_Private_Defines
* @brief Private defines.
* @{
*/

#define STORAGE_LUN_NBR 1
#define STORAGE_BLK_NBR 0x10000
#define STORAGE_BLK_SIZ 0x200

/* USER CODE BEGIN PRIVATE_DEFINES */

/* USER CODE END PRIVATE_DEFINES */

/**
* @}
*/

/** @defgroup USBD_STORAGE_Private_Macros
* @brief Private macros.
* @{
*/

/* USER CODE BEGIN PRIVATE_MACRO */

/* USER CODE END PRIVATE_MACRO */

/**
* @}
*/

/** @defgroup USBD_STORAGE_Private_Variables
* @brief Private variables.
* @{
*/

/* USER CODE BEGIN INQUIRY_DATA_FS */
/** USB Mass storage Standard Inquiry Data. */
const int8_t STORAGE_Inquirydata_FS[] = {/* 36 */

/* LUN 0 */
0x00, 0x80, 0x02, 0x02, (STANDARD_INQUIRY_DATA_LEN - 5), 0x00, 0x00, 0x00, 'S',
'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer : 8 bytes */
'P', 'r', 'o', 'd', 'u', 'c', 't', ' ', /* Product : 16 Bytes */
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '0', '.', '0', '1' /* Version : 4 Bytes */
};
/* USER CODE END INQUIRY_DATA_FS */

/* USER CODE BEGIN PRIVATE_VARIABLES */
uint8_t pdrv_type;
/* USER CODE END PRIVATE_VARIABLES */

/**
* @}
*/

/** @defgroup USBD_STORAGE_Exported_Variables
* @brief Public variables.
* @{
*/

extern USBD_HandleTypeDef hUsbDeviceFS;

/* USER CODE BEGIN EXPORTED_VARIABLES */

/* USER CODE END EXPORTED_VARIABLES */

/**
* @}
*/

/** @defgroup USBD_STORAGE_Private_FunctionPrototypes
* @brief Private functions declaration.
* @{
*/

static int8_t STORAGE_Init_FS(uint8_t lun);
static int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size);
static int8_t STORAGE_IsReady_FS(uint8_t lun);
static int8_t STORAGE_IsWriteProtected_FS(uint8_t lun);
static int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_GetMaxLun_FS(void);

/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */

/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */

/**
* @}
*/

USBD_StorageTypeDef USBD_Storage_Interface_fops_FS =
{
STORAGE_Init_FS,
STORAGE_GetCapacity_FS,
STORAGE_IsReady_FS,
STORAGE_IsWriteProtected_FS,
STORAGE_Read_FS,
STORAGE_Write_FS,
STORAGE_GetMaxLun_FS,
(int8_t *)STORAGE_Inquirydata_FS
};

/* Private functions ---------------------------------------------------------*/
/**
* @brief Initializes the storage unit (medium) over USB FS IP
* @param lun: Logical unit number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_Init_FS(uint8_t lun)
{
/* USER CODE BEGIN 2 */
UNUSED(lun);
pdrv_type = retSD;

printf("STORAGE_Init_FS\r\n");
// sd卡已经在主函数中初始化
// 这边仅把sd映射到usb
return (USBD_OK);
/* USER CODE END 2 */
}

/**
* @brief Returns the medium capacity.
* @param lun: Logical unit number.
* @param block_num: Number of total block number.
* @param block_size: Block size.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
/* USER CODE BEGIN 3 */
int8_t ret = -1;
ret = disk_ioctl(pdrv_type, GET_SECTOR_COUNT, (void*) block_num);
if (ret < 0) {
return ret;
}
*block_num = *block_num - 1;
ret = disk_ioctl(pdrv_type, GET_SECTOR_SIZE, (void*) block_size);
return ret;
/* USER CODE END 3 */
}

/**
* @brief Checks whether the medium is ready.
* @param lun: Logical unit number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_IsReady_FS(uint8_t lun)
{
/* USER CODE BEGIN 4 */
int8_t ret = -1;
UNUSED(lun);
ret = disk_status(pdrv_type);
return ret;
/* USER CODE END 4 */
}

/**
* @brief Checks whether the medium is write protected.
* @param lun: Logical unit number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
/* USER CODE BEGIN 5 */
UNUSED(lun);

return (USBD_OK);
/* USER CODE END 5 */
}

/**
* @brief Reads data from the medium.
* @param lun: Logical unit number.
* @param buf: data buffer.
* @param blk_addr: Logical block address.
* @param blk_len: Blocks number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
/* USER CODE BEGIN 6 */
int8_t ret = -1;
UNUSED(lun);
// HAL_GPIO_WritePin(LED_PC1_GPIO_Port, LED_PC1_Pin, 0);
ret = disk_read(pdrv_type, (BYTE*) buf, (DWORD) blk_addr, (UINT) blk_len);
// HAL_GPIO_WritePin(LED_PC1_GPIO_Port, LED_PC1_Pin, 1);
return ret;
/* USER CODE END 6 */
}

/**
* @brief Writes data into the medium.
* @param lun: Logical unit number.
* @param buf: data buffer.
* @param blk_addr: Logical block address.
* @param blk_len: Blocks number.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
/* USER CODE BEGIN 7 */
int8_t ret = -1;
UNUSED(lun);
// HAL_GPIO_WritePin(LED_PC1_GPIO_Port, LED_PC1_Pin, 0);
ret = disk_write(pdrv_type, (BYTE*) buf, (DWORD) blk_addr, (UINT) blk_len);
// HAL_GPIO_WritePin(LED_PC1_GPIO_Port, LED_PC1_Pin, 1);
return ret;
/* USER CODE END 7 */
}

/**
* @brief Returns the Max Supported LUNs.
* @param None
* @retval Lun(s) number.
*/
int8_t STORAGE_GetMaxLun_FS(void)
{
/* USER CODE BEGIN 8 */
return (STORAGE_LUN_NBR - 1);
/* USER CODE END 8 */
}

/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */

/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */

/**
* @}
*/

/**
* @}
*/

然后写一下LCD的显示功能,因为HAL库的SPI发送方式也就是HAL_SPI_Transmit是连续发送,这边需要断点发送,所以spi.c和spi.h都不能要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
 /***
************************************************************************************************************************************************************************************************
* @file lcd_spi_200.c
* @version V1.0
* @date 2022-3-8
* @author 反客科技、Minloha
* @brief SPI驱动显示屏,屏幕控制器 ST7789
**********************************************************************************************************************************************************************************************
>>>>> 重要说明:
*
* 1.屏幕配置为16位RGB565格式
* 2.SPI通信速度为60M,清屏 LCD_Clear() 所需时间为18ms,约为55.5帧
*
>>>>> 其他说明:
*
* 1. 中文字库使用的是小字库,即用到了对应的汉字再去取模,用户可以根据需求自行增添或删减
* 2. 各个函数的功能和使用可以参考函数的说明
*
*********************************************************************************************************************************************************************************************FANKE*****
***/

#include "lcd_spi_200.h"

SPI_HandleTypeDef hspi4; // SPI_HandleTypeDef 结构体变量

#define LCD_SPI hspi4 // SPI局部宏,方便修改和移植

static pFONT *LCD_AsciiFonts; // 英文字体,ASCII字符集
static pFONT *LCD_CHFonts; // 中文字体(同时也包含英文字体)

// 因为这类SPI的屏幕,每次更新显示时,需要先配置坐标区域、再写显存,
// 在显示字符时,如果是一个个点去写坐标写显存,会非常慢,
// 因此开辟一片缓冲区,先将需要显示的数据写进缓冲区,最后再批量写入显存。
// 用户可以根据实际情况去修改此处缓冲区的大小,
// 例如,用户需要显示32*32的汉字时,需要的大小为 32*32*2 = 2048 字节(每个像素点占2字节)
uint16_t LCD_Buff[1024]; // LCD缓冲区,16位宽(每个像素点占2字节)

struct //LCD相关参数结构体
{
uint32_t Color; // LCD当前画笔颜色
uint32_t BackColor; // 背景色
uint8_t ShowNum_Mode; // 数字显示模式
uint8_t Direction; // 显示方向
uint16_t Width; // 屏幕像素长度
uint16_t Height; // 屏幕像素宽度
uint8_t X_Offset; // X坐标偏移,用于设置屏幕控制器的显存写入方式
uint8_t Y_Offset; // Y坐标偏移,用于设置屏幕控制器的显存写入方式
}LCD;

// 该函数修改于HAL的SPI库函数,专为 LCD_Clear() 清屏函数修改,
// 目的是为了SPI传输数据不限数据长度的写入
HAL_StatusTypeDef LCD_SPI_Transmit(SPI_HandleTypeDef *hspi, uint16_t pData, uint32_t Size);
HAL_StatusTypeDef LCD_SPI_TransmitBuffer (SPI_HandleTypeDef *hspi, uint16_t *pData, uint32_t Size);

/****************************************************************************************************************************************
* 函 数 名: HAL_SPI_MspInit
* 入口参数: hspi - SPI_HandleTypeDef定义的变量,即表示定义的 SPI 句柄
* 函数功能: 初始化 SPI 引脚
****************************************************************************************************************************************/

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hspi->Instance==SPI4)
{
__HAL_RCC_SPI4_CLK_ENABLE(); // 使能SPI4时钟

__HAL_RCC_GPIOE_CLK_ENABLE(); // 使能 SPI4 GPIO

GPIO_LDC_Backlight_CLK_ENABLE; // 使能 背光 引脚时钟
GPIO_LDC_DC_CLK_ENABLE; // 使能 数据指令选择 引脚时钟

/******************************************************
PE11 ------> SPI4_NSS,使用硬件片选
PE12 ------> SPI4_SCK
PE14 ------> SPI4_MOSI

PD15 ------> 背光 引脚
PE15 ------> 数据指令选择 引脚
*******************************************************/

// 初始化 SCK、MOSI以及片选引脚,使用硬件 SPI 片选
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_14; // SCK、MOSI以及片选引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // 最高速度等级
GPIO_InitStruct.Alternate = GPIO_AF5_SPI4; // 复用到SPI4,复用线1
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

// 初始化 背光 引脚
GPIO_InitStruct.Pin = LCD_Backlight_PIN; // 背光 引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 速度等级低
HAL_GPIO_Init(LCD_Backlight_PORT, &GPIO_InitStruct); // 初始化

// 初始化 数据指令选择 引脚
GPIO_InitStruct.Pin = LCD_DC_PIN; // 数据指令选择 引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 速度等级低
HAL_GPIO_Init(LCD_DC_PORT, &GPIO_InitStruct); // 初始化
}
}

/****************************************************************************************************************************************
* 函 数 名: MX_SPI4_Init
* 函数功能: 初始化SPI配置
* 说 明:使用硬件片选
****************************************************************************************************************************************/


void MX_SPI4_Init(void)
{
LCD_SPI.Instance = SPI4; // 使用SPI4
LCD_SPI.Init.Mode = SPI_MODE_MASTER; // 主机模式
LCD_SPI.Init.Direction = SPI_DIRECTION_1LINE; // 单线
LCD_SPI.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据宽度
LCD_SPI.Init.CLKPolarity = SPI_POLARITY_LOW; // CLK空闲时保持低电平
LCD_SPI.Init.CLKPhase = SPI_PHASE_1EDGE; // 数据在CLK第一个边沿有效
LCD_SPI.Init.NSS = SPI_NSS_HARD_OUTPUT; // 使用硬件片选

// SPI的内核时钟设置为120M,再经过2分频得到 60M 的SCK驱动时钟
LCD_SPI.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;

LCD_SPI.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位在先
LCD_SPI.Init.TIMode = SPI_TIMODE_DISABLE; // 禁止TI模式
LCD_SPI.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁止CRC
LCD_SPI.Init.CRCPolynomial = 0x0; // CRC校验项,这里用不到
LCD_SPI.Init.NSSPMode = SPI_NSS_PULSE_ENABLE; // 使用片选脉冲模式
LCD_SPI.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; // 片选低电平有效
LCD_SPI.Init.FifoThreshold = SPI_FIFO_THRESHOLD_02DATA; // FIFO阈值
LCD_SPI.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; // 发送端CRC初始化模式,这里用不到
LCD_SPI.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; // 接收端CRC初始化模式,这里用不到
LCD_SPI.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; // 额外延迟周期为0
LCD_SPI.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE; // 主机模式下,两个数据帧之间的延迟周期
LCD_SPI.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE; // 禁止自动接收管理
LCD_SPI.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE; // 主机模式下,禁止SPI保持当前引脚状态
LCD_SPI.Init.IOSwap = SPI_IO_SWAP_DISABLE; // 不交换MOSI和MISO

HAL_SPI_Init(&LCD_SPI);
}

/****************************************************************************************************************************************
* 函 数 名: LCD_WriteCommand
*
* 入口参数: lcd_command - 需要写入的控制指令
*
* 函数功能: 用于向屏幕控制器写入指令
*
****************************************************************************************************************************************/

void LCD_WriteCommand(uint8_t lcd_command)
{
LCD_DC_Command; // 数据指令选择 引脚输出低电平,代表本次传输 指令

HAL_SPI_Transmit(&LCD_SPI, &lcd_command, 1, 1000); // 启动SPI传输
}

/****************************************************************************************************************************************
* 函 数 名: LCD_WriteData_8bit
*
* 入口参数: lcd_data - 需要写入的数据,8位
*
* 函数功能: 写入8位数据
*
****************************************************************************************************************************************/

void LCD_WriteData_8bit(uint8_t lcd_data)
{
LCD_DC_Data; // 数据指令选择 引脚输出高电平,代表本次传输 数据

HAL_SPI_Transmit(&LCD_SPI, &lcd_data, 1, 1000) ; // 启动SPI传输
}

/****************************************************************************************************************************************
* 函 数 名: LCD_WriteData_16bit
*
* 入口参数: lcd_data - 需要写入的数据,16位
*
* 函数功能: 写入16位数据
*
****************************************************************************************************************************************/

void LCD_WriteData_16bit(uint16_t lcd_data)
{
uint8_t lcd_data_buff[2]; // 数据发送区
LCD_DC_Data; // 数据指令选择 引脚输出高电平,代表本次传输 数据

lcd_data_buff[0] = lcd_data>>8; // 将数据拆分
lcd_data_buff[1] = lcd_data;

HAL_SPI_Transmit(&LCD_SPI, lcd_data_buff, 2, 1000) ; // 启动SPI传输
}

/****************************************************************************************************************************************
* 函 数 名: LCD_WriteBuff
*
* 入口参数: DataBuff - 数据区,DataSize - 数据长度
*
* 函数功能: 批量写入数据到屏幕
*
****************************************************************************************************************************************/

void LCD_WriteBuff(uint16_t *DataBuff, uint16_t DataSize)
{
LCD_DC_Data; // 数据指令选择 引脚输出高电平,代表本次传输 数据

// 修改为16位数据宽度,写入数据更加效率,不需要拆分
LCD_SPI.Init.DataSize = SPI_DATASIZE_16BIT; // 16位数据宽度
HAL_SPI_Init(&LCD_SPI);

HAL_SPI_Transmit(&LCD_SPI, (uint8_t *)DataBuff, DataSize, 1000) ; // 启动SPI传输

// 改回8位数据宽度,因为指令和部分数据都是按照8位传输的
LCD_SPI.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据宽度
HAL_SPI_Init(&LCD_SPI);
}

/****************************************************************************************************************************************
* 函 数 名: SPI_LCD_Init
*
* 函数功能: 初始化SPI以及屏幕控制器的各种参数
*
****************************************************************************************************************************************/

void SPI_LCD_Init(void)
{
MX_SPI4_Init(); // 初始化SPI和控制引脚

HAL_Delay(10); // 屏幕刚完成复位时(包括上电复位),需要等待至少5ms才能发送指令

LCD_WriteCommand(0x36); // 显存访问控制 指令,用于设置访问显存的方式
LCD_WriteData_8bit(0x00); // 配置成 从上到下、从左到右,RGB像素格式

LCD_WriteCommand(0x3A); // 接口像素格式 指令,用于设置使用 12位、16位还是18位色
LCD_WriteData_8bit(0x05); // 此处配置成 16位 像素格式

// 接下来很多都是电压设置指令,直接使用厂家给设定值
LCD_WriteCommand(0xB2);
LCD_WriteData_8bit(0x0C);
LCD_WriteData_8bit(0x0C);
LCD_WriteData_8bit(0x00);
LCD_WriteData_8bit(0x33);
LCD_WriteData_8bit(0x33);

LCD_WriteCommand(0xB7); // 栅极电压设置指令
LCD_WriteData_8bit(0x35); // VGH = 13.26V,VGL = -10.43V

LCD_WriteCommand(0xBB); // 公共电压设置指令
LCD_WriteData_8bit(0x19); // VCOM = 1.35V

LCD_WriteCommand(0xC0);
LCD_WriteData_8bit(0x2C);

LCD_WriteCommand(0xC2); // VDV 和 VRH 来源设置
LCD_WriteData_8bit(0x01); // VDV 和 VRH 由用户自由配置

LCD_WriteCommand(0xC3); // VRH电压 设置指令
LCD_WriteData_8bit(0x12); // VRH电压 = 4.6+( vcom+vcom offset+vdv)

LCD_WriteCommand(0xC4); // VDV电压 设置指令
LCD_WriteData_8bit(0x20); // VDV电压 = 0v

LCD_WriteCommand(0xC6); // 正常模式的帧率控制指令
LCD_WriteData_8bit(0x0F); // 设置屏幕控制器的刷新帧率为60帧

LCD_WriteCommand(0xD0); // 电源控制指令
LCD_WriteData_8bit(0xA4); // 无效数据,固定写入0xA4
LCD_WriteData_8bit(0xA1); // AVDD = 6.8V ,AVDD = -4.8V ,VDS = 2.3V

LCD_WriteCommand(0xE0); // 正极电压伽马值设定
LCD_WriteData_8bit(0xD0);
LCD_WriteData_8bit(0x04);
LCD_WriteData_8bit(0x0D);
LCD_WriteData_8bit(0x11);
LCD_WriteData_8bit(0x13);
LCD_WriteData_8bit(0x2B);
LCD_WriteData_8bit(0x3F);
LCD_WriteData_8bit(0x54);
LCD_WriteData_8bit(0x4C);
LCD_WriteData_8bit(0x18);
LCD_WriteData_8bit(0x0D);
LCD_WriteData_8bit(0x0B);
LCD_WriteData_8bit(0x1F);
LCD_WriteData_8bit(0x23);

LCD_WriteCommand(0xE1); // 负极电压伽马值设定
LCD_WriteData_8bit(0xD0);
LCD_WriteData_8bit(0x04);
LCD_WriteData_8bit(0x0C);
LCD_WriteData_8bit(0x11);
LCD_WriteData_8bit(0x13);
LCD_WriteData_8bit(0x2C);
LCD_WriteData_8bit(0x3F);
LCD_WriteData_8bit(0x44);
LCD_WriteData_8bit(0x51);
LCD_WriteData_8bit(0x2F);
LCD_WriteData_8bit(0x1F);
LCD_WriteData_8bit(0x1F);
LCD_WriteData_8bit(0x20);
LCD_WriteData_8bit(0x23);

LCD_WriteCommand(0x21); // 打开反显,因为面板是常黑型,操作需要反过来

// 退出休眠指令,LCD控制器在刚上电、复位时,会自动进入休眠模式 ,因此操作屏幕之前,需要退出休眠
LCD_WriteCommand(0x11); // 退出休眠 指令
HAL_Delay(120); // 需要等待120ms,让电源电压和时钟电路稳定下来

// 打开显示指令,LCD控制器在刚上电、复位时,会自动关闭显示
LCD_WriteCommand(0x29); // 打开显示

// 以下进行一些驱动的默认设置
LCD_SetDirection(Direction_V); // 设置显示方向
LCD_SetBackColor(LCD_BLACK); // 设置背景色
LCD_SetColor(LCD_WHITE); // 设置画笔色
LCD_Clear(); // 清屏

LCD_SetAsciiFont(&ASCII_Font24); // 设置默认字体
LCD_ShowNumMode(Fill_Zero); // 设置变量显示模式,多余位填充空格还是填充0

// 全部设置完毕之后,打开背光
LCD_Backlight_ON; // 引脚输出高电平点亮背光
}

/****************************************************************************************************************************************
* 函 数 名: LCD_SetAddress
*
* 入口参数: x1 - 起始水平坐标 y1 - 起始垂直坐标
* x2 - 终点水平坐标 y2 - 终点垂直坐标
*
* 函数功能: 设置需要显示的坐标区域
*****************************************************************************************************************************************/

void LCD_SetAddress(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2)
{
LCD_WriteCommand(0x2a); // 列地址设置,即X坐标
LCD_WriteData_16bit(x1+LCD.X_Offset);
LCD_WriteData_16bit(x2+LCD.X_Offset);

LCD_WriteCommand(0x2b); // 行地址设置,即Y坐标
LCD_WriteData_16bit(y1+LCD.Y_Offset);
LCD_WriteData_16bit(y2+LCD.Y_Offset);

LCD_WriteCommand(0x2c); // 开始写入显存,即要显示的颜色数据
}

/****************************************************************************************************************************************
* 函 数 名: LCD_SetColor
*
* 入口参数: Color - 要显示的颜色,示例:0x0000FF 表示蓝色
*
* 函数功能: 此函数用于设置画笔的颜色,例如显示字符、画点画线、绘图的颜色
*
* 说 明: 1. 为了方便用户使用自定义颜色,入口参数 Color 使用24位 RGB888的颜色格式,用户无需关心颜色格式的转换
* 2. 24位的颜色中,从高位到低位分别对应 R、G、B 3个颜色通道
*
*****************************************************************************************************************************************/

void LCD_SetColor(uint32_t Color)
{
uint16_t Red_Value = 0, Green_Value = 0, Blue_Value = 0; //各个颜色通道的值

Red_Value = (uint16_t)((Color&0x00F80000)>>8); // 转换成 16位 的RGB565颜色
Green_Value = (uint16_t)((Color&0x0000FC00)>>5);
Blue_Value = (uint16_t)((Color&0x000000F8)>>3);

LCD.Color = (uint16_t)(Red_Value | Green_Value | Blue_Value); // 将颜色写入全局LCD参数
}

/****************************************************************************************************************************************
* 函 数 名: LCD_SetBackColor
*
* 入口参数: Color - 要显示的颜色,示例:0x0000FF 表示蓝色
*
* 函数功能: 设置背景色,此函数用于清屏以及显示字符的背景色
*
* 说 明: 1. 为了方便用户使用自定义颜色,入口参数 Color 使用24位 RGB888的颜色格式,用户无需关心颜色格式的转换
* 2. 24位的颜色中,从高位到低位分别对应 R、G、B 3个颜色通道
*
*****************************************************************************************************************************************/

void LCD_SetBackColor(uint32_t Color)
{
uint16_t Red_Value = 0, Green_Value = 0, Blue_Value = 0; //各个颜色通道的值

Red_Value = (uint16_t)((Color&0x00F80000)>>8); // 转换成 16位 的RGB565颜色
Green_Value = (uint16_t)((Color&0x0000FC00)>>5);
Blue_Value = (uint16_t)((Color&0x000000F8)>>3);

LCD.BackColor = (uint16_t)(Red_Value | Green_Value | Blue_Value); // 将颜色写入全局LCD参数
}

/****************************************************************************************************************************************
* 函 数 名: LCD_SetDirection
*
* 入口参数: direction - 要显示的方向
*
* 函数功能: 设置要显示的方向
*
* 说 明: 1. 可输入参数 Direction_H 、Direction_V 、Direction_H_Flip 、Direction_V_Flip
* 2. 使用示例 LCD_DisplayDirection(Direction_H) ,即设置屏幕横屏显示
*
*****************************************************************************************************************************************/

void LCD_SetDirection(uint8_t direction)
{
LCD.Direction = direction; // 写入全局LCD参数

if( direction == Direction_H ) // 横屏显示
{
LCD_WriteCommand(0x36); // 显存访问控制 指令,用于设置访问显存的方式
LCD_WriteData_8bit(0x70); // 横屏显示
LCD.X_Offset = 0; // 设置控制器坐标偏移量
LCD.Y_Offset = 0;
LCD.Width = LCD_Height; // 重新赋值长、宽
LCD.Height = LCD_Width;
}
else if( direction == Direction_V )
{
LCD_WriteCommand(0x36); // 显存访问控制 指令,用于设置访问显存的方式
LCD_WriteData_8bit(0x00); // 垂直显示
LCD.X_Offset = 0; // 设置控制器坐标偏移量
LCD.Y_Offset = 0;
LCD.Width = LCD_Width; // 重新赋值长、宽
LCD.Height = LCD_Height;
}
else if( direction == Direction_H_Flip )
{
LCD_WriteCommand(0x36); // 显存访问控制 指令,用于设置访问显存的方式
LCD_WriteData_8bit(0xA0); // 横屏显示,并上下翻转,RGB像素格式
LCD.X_Offset = 0; // 设置控制器坐标偏移量
LCD.Y_Offset = 0;
LCD.Width = LCD_Height; // 重新赋值长、宽
LCD.Height = LCD_Width;
}
else if( direction == Direction_V_Flip )
{
LCD_WriteCommand(0x36); // 显存访问控制 指令,用于设置访问显存的方式
LCD_WriteData_8bit(0xC0); // 垂直显示 ,并上下翻转,RGB像素格式
LCD.X_Offset = 0; // 设置控制器坐标偏移量
LCD.Y_Offset = 0;
LCD.Width = LCD_Width; // 重新赋值长、宽
LCD.Height = LCD_Height;
}
}

/****************************************************************************************************************************************
* 函 数 名: LCD_SetAsciiFont
*
* 入口参数: *fonts - 要设置的ASCII字体
*
* 函数功能: 设置ASCII字体,可选择使用 3216/2412/2010/1608/1206 五种大小的字体
*
* 说 明: 1. 使用示例 LCD_SetAsciiFont(&ASCII_Font24) ,即设置 2412的 ASCII字体
* 2. 相关字模存放在 lcd_fonts.c
*
*****************************************************************************************************************************************/

void LCD_SetAsciiFont(pFONT *Asciifonts)
{
LCD_AsciiFonts = Asciifonts;
}

/****************************************************************************************************************************************
* 函 数 名: LCD_Clear
*
* 函数功能: 清屏函数,将LCD清除为 LCD.BackColor 的颜色
*
* 说 明: 先用 LCD_SetBackColor() 设置要清除的背景色,再调用该函数清屏即可
*
*****************************************************************************************************************************************/

void LCD_Clear(void)
{
LCD_SetAddress(0,0,LCD.Width-1,LCD.Height-1); // 设置坐标

LCD_DC_Data; // 数据指令选择 引脚输出高电平,代表本次传输 数据

// 修改为16位数据宽度,写入数据更加效率,不需要拆分
LCD_SPI.Init.DataSize = SPI_DATASIZE_16BIT; // 16位数据宽度
HAL_SPI_Init(&LCD_SPI);

LCD_SPI_Transmit(&LCD_SPI, LCD.BackColor, LCD.Width * LCD.Height) ; // 启动传输

// 改回8位数据宽度,因为指令和部分数据都是按照8位传输的
LCD_SPI.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据宽度
HAL_SPI_Init(&LCD_SPI);
}

/****************************************************************************************************************************************
* 函 数 名: LCD_ClearRect
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* width - 要清除区域的横向长度
* height - 要清除区域的纵向宽度
*
* 函数功能: 局部清屏函数,将指定位置对应的区域清除为 LCD.BackColor 的颜色
*
* 说 明: 1. 先用 LCD_SetBackColor() 设置要清除的背景色,再调用该函数清屏即可
* 2. 使用示例 LCD_ClearRect( 10, 10, 100, 50) ,清除坐标(10,10)开始的长100宽50的区域
*
*****************************************************************************************************************************************/

void LCD_ClearRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
LCD_SetAddress( x, y, x+width-1, y+height-1); // 设置坐标

LCD_DC_Data; // 数据指令选择 引脚输出高电平,代表本次传输 数据

// 修改为16位数据宽度,写入数据更加效率,不需要拆分
LCD_SPI.Init.DataSize = SPI_DATASIZE_16BIT; // 16位数据宽度
HAL_SPI_Init(&LCD_SPI);

LCD_SPI_Transmit(&LCD_SPI, LCD.BackColor, width*height) ; // 启动传输

// 改回8位数据宽度,因为指令和部分数据都是按照8位传输的
LCD_SPI.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据宽度
HAL_SPI_Init(&LCD_SPI);

}

/****************************************************************************************************************************************
* 函 数 名: LCD_DrawPoint
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* color - 要绘制的颜色,使用 24位 RGB888 的颜色格式,用户无需关心颜色格式的转换
*
* 函数功能: 在指定坐标绘制指定颜色的点
*
* 说 明: 使用示例 LCD_DrawPoint( 10, 10, 0x0000FF) ,在坐标(10,10)绘制蓝色的点
*
*****************************************************************************************************************************************/

void LCD_DrawPoint(uint16_t x,uint16_t y,uint32_t color)
{
LCD_SetAddress(x,y,x,y); // 设置坐标

LCD_WriteData_16bit(color) ;
}

/****************************************************************************************************************************************
* 函 数 名: LCD_DisplayChar
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* c - ASCII字符
*
* 函数功能: 在指定坐标显示指定的字符
*
* 说 明: 1. 可设置要显示的字体,例如使用 LCD_SetAsciiFont(&ASCII_Font24) 设置为 2412的ASCII字体
* 2. 可设置要显示的颜色,例如使用 LCD_SetColor(0xff0000FF) 设置为蓝色
* 3. 可设置对应的背景色,例如使用 LCD_SetBackColor(0x000000) 设置为黑色的背景色
* 4. 使用示例 LCD_DisplayChar( 10, 10, 'a') ,在坐标(10,10)显示字符 'a'
*
*****************************************************************************************************************************************/

void LCD_DisplayChar(uint16_t x, uint16_t y,uint8_t c)
{
uint16_t index = 0, counter = 0 ,i = 0, w = 0; // 计数变量
uint8_t disChar; //存储字符的地址

c = c - 32; // 计算ASCII字符的偏移

for(index = 0; index < LCD_AsciiFonts->Sizes; index++)
{
disChar = LCD_AsciiFonts->pTable[c*LCD_AsciiFonts->Sizes + index]; //获取字符的模值
for(counter = 0; counter < 8; counter++)
{
if(disChar & 0x01)
{
LCD_Buff[i] = LCD.Color; // 当前模值不为0时,使用画笔色绘点
}
else
{
LCD_Buff[i] = LCD.BackColor; //否则使用背景色绘制点
}
disChar >>= 1;
i++;
w++;
if( w == LCD_AsciiFonts->Width ) // 如果写入的数据达到了字符宽度,则退出当前循环
{ // 进入下一字符的写入的绘制
w = 0;
break;
}
}
}
LCD_SetAddress( x, y, x+LCD_AsciiFonts->Width-1, y+LCD_AsciiFonts->Height-1); // 设置坐标
LCD_WriteBuff(LCD_Buff,LCD_AsciiFonts->Width*LCD_AsciiFonts->Height); // 写入显存
}

/****************************************************************************************************************************************
* 函 数 名: LCD_DisplayString
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* p - ASCII字符串的首地址
*
* 函数功能: 在指定坐标显示指定的字符串
*
* 说 明: 1. 可设置要显示的字体,例如使用 LCD_SetAsciiFont(&ASCII_Font24) 设置为 2412的ASCII字体
* 2. 可设置要显示的颜色,例如使用 LCD_SetColor(0x0000FF) 设置为蓝色
* 3. 可设置对应的背景色,例如使用 LCD_SetBackColor(0x000000) 设置为黑色的背景色
* 4. 使用示例 LCD_DisplayString( 10, 10, "FANKE") ,在起始坐标为(10,10)的地方显示字符串"FANKE"
*
*****************************************************************************************************************************************/

void LCD_DisplayString( uint16_t x, uint16_t y, char *p)
{
while ((x < LCD.Width) && (*p != 0)) //判断显示坐标是否超出显示区域并且字符是否为空字符
{
LCD_DisplayChar( x,y,*p);
x += LCD_AsciiFonts->Width; //显示下一个字符
p++; //取下一个字符地址
}
}

/****************************************************************************************************************************************
* 函 数 名: LCD_SetTextFont
*
* 入口参数: *fonts - 要设置的文本字体
*
* 函数功能: 设置文本字体,包括中文和ASCII字符,
*
* 说 明: 1. 可选择使用 3232/2424/2020/1616/1212 五种大小的中文字体,
* 并且对应的设置ASCII字体为 3216/2412/2010/1608/1206
* 2. 相关字模存放在 lcd_fonts.c
* 3. 中文字库使用的是小字库,即用到了对应的汉字再去取模
* 4. 使用示例 LCD_SetTextFont(&CH_Font24) ,即设置 2424的中文字体以及2412的ASCII字符字体
*
*****************************************************************************************************************************************/

void LCD_SetTextFont(pFONT *fonts)
{
LCD_CHFonts = fonts; // 设置中文字体
switch(fonts->Width )
{
case 12: LCD_AsciiFonts = &ASCII_Font12; break; // 设置ASCII字符的字体为 1206
case 16: LCD_AsciiFonts = &ASCII_Font16; break; // 设置ASCII字符的字体为 1608
case 20: LCD_AsciiFonts = &ASCII_Font20; break; // 设置ASCII字符的字体为 2010
case 24: LCD_AsciiFonts = &ASCII_Font24; break; // 设置ASCII字符的字体为 2412
case 32: LCD_AsciiFonts = &ASCII_Font32; break; // 设置ASCII字符的字体为 3216
default: break;
}
}
/******************************************************************************************************************************************
* 函 数 名: LCD_DisplayChinese
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* pText - 中文字符
*
* 函数功能: 在指定坐标显示指定的单个中文字符
*
* 说 明: 1. 可设置要显示的字体,例如使用 LCD_SetTextFont(&CH_Font24) 设置为 2424的中文字体以及2412的ASCII字符字体
* 2. 可设置要显示的颜色,例如使用 LCD_SetColor(0xff0000FF) 设置为蓝色
* 3. 可设置对应的背景色,例如使用 LCD_SetBackColor(0xff000000) 设置为黑色的背景色
* 4. 使用示例 LCD_DisplayChinese( 10, 10, "反") ,在坐标(10,10)显示中文字符"反"
*
*****************************************************************************************************************************************/

void LCD_DisplayChinese(uint16_t x, uint16_t y, char *pText)
{
uint16_t i=0,index = 0, counter = 0; // 计数变量
uint16_t addr; // 字模地址
uint8_t disChar; //字模的值
uint16_t Xaddress = 0; //水平坐标

while(1)
{
// 对比数组中的汉字编码,用以定位该汉字字模的地址
if ( *(LCD_CHFonts->pTable + (i+1)*LCD_CHFonts->Sizes + 0)==*pText && *(LCD_CHFonts->pTable + (i+1)*LCD_CHFonts->Sizes + 1)==*(pText+1) )
{
addr=i; // 字模地址偏移
break;
}
i+=2; // 每个中文字符编码占两字节

if(i >= LCD_CHFonts->Table_Rows) break; // 字模列表中无相应的汉字
}
i=0;
for(index = 0; index <LCD_CHFonts->Sizes; index++)
{
disChar = *(LCD_CHFonts->pTable + (addr)*LCD_CHFonts->Sizes + index); // 获取相应的字模地址

for(counter = 0; counter < 8; counter++)
{
if(disChar & 0x01)
{
LCD_Buff[i] = LCD.Color; // 当前模值不为0时,使用画笔色绘点
}
else
{
LCD_Buff[i] = LCD.BackColor; // 否则使用背景色绘制点
}
i++;
disChar >>= 1;
Xaddress++; //水平坐标自加

if( Xaddress == LCD_CHFonts->Width ) // 如果水平坐标达到了字符宽度,则退出当前循环
{ // 进入下一行的绘制
Xaddress = 0;
break;
}
}
}
LCD_SetAddress( x, y, x+LCD_CHFonts->Width-1, y+LCD_CHFonts->Height-1); // 设置坐标
LCD_WriteBuff(LCD_Buff,LCD_CHFonts->Width*LCD_CHFonts->Height); // 写入显存
}

/*****************************************************************************************************************************************
* 函 数 名: LCD_DisplayText
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* pText - 字符串,可以显示中文或者ASCII字符
*
* 函数功能: 在指定坐标显示指定的字符串
*
* 说 明: 1. 可设置要显示的字体,例如使用 LCD_SetTextFont(&CH_Font24) 设置为 2424的中文字体以及2412的ASCII字符字体
* 2. 可设置要显示的颜色,例如使用 LCD_SetColor(0xff0000FF) 设置为蓝色
* 3. 可设置对应的背景色,例如使用 LCD_SetBackColor(0xff000000) 设置为黑色的背景色
* 4. 使用示例 LCD_DisplayChinese( 10, 10, "反客科技STM32") ,在坐标(10,10)显示字符串"反客科技STM32"
*
**********************************************************************************************************************************fanke*******/

void LCD_DisplayText(uint16_t x, uint16_t y, char *pText)
{

while(*pText != 0) // 判断是否为空字符
{
if(*pText<=0x7F) // 判断是否为ASCII码
{
LCD_DisplayChar(x,y,*pText); // 显示ASCII
x+=LCD_AsciiFonts->Width; // 水平坐标调到下一个字符处
pText++; // 字符串地址+1
}
else // 若字符为汉字
{
LCD_DisplayChinese(x,y,pText); // 显示汉字
x+=LCD_CHFonts->Width; // 水平坐标调到下一个字符处
pText+=2; // 字符串地址+2,汉字的编码要2字节
}
}
}

/*****************************************************************************************************************************************
* 函 数 名: LCD_ShowNumMode
*
* 入口参数: mode - 设置变量的显示模式
*
* 函数功能: 设置变量显示时多余位补0还是补空格,可输入参数 Fill_Space 填充空格,Fill_Zero 填充零
*
* 说 明: 1. 只有 LCD_DisplayNumber() 显示整数 和 LCD_DisplayDecimals()显示小数 这两个函数用到
* 2. 使用示例 LCD_ShowNumMode(Fill_Zero) 设置多余位填充0,例如 123 可以显示为 000123
*
*****************************************************************************************************************************************/

void LCD_ShowNumMode(uint8_t mode)
{
LCD.ShowNum_Mode = mode;
}

/*****************************************************************************************************************************************
* 函 数 名: LCD_DisplayNumber
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* number - 要显示的数字,范围在 -2147483648~2147483647 之间
* len - 数字的位数,如果位数超过len,将按其实际长度输出,如果需要显示负数,请预留一个位的符号显示空间
*
* 函数功能: 在指定坐标显示指定的整数变量
*
* 说 明: 1. 可设置要显示的字体,例如使用 LCD_SetAsciiFont(&ASCII_Font24) 设置为的ASCII字符字体
* 2. 可设置要显示的颜色,例如使用 LCD_SetColor(0x0000FF) 设置为蓝色
* 3. 可设置对应的背景色,例如使用 LCD_SetBackColor(0x000000) 设置为黑色的背景色
* 4. 使用示例 LCD_DisplayNumber( 10, 10, a, 5) ,在坐标(10,10)显示指定变量a,总共5位,多余位补0或空格,
* 例如 a=123 时,会根据 LCD_ShowNumMode()的设置来显示 123(前面两个空格位) 或者00123
*
*****************************************************************************************************************************************/

void LCD_DisplayNumber( uint16_t x, uint16_t y, int32_t number, uint8_t len)
{
char Number_Buffer[15]; // 用于存储转换后的字符串

if( LCD.ShowNum_Mode == Fill_Zero) // 多余位补0
{
sprintf( Number_Buffer , "%0.*d",len, number ); // 将 number 转换成字符串,便于显示
}
else // 多余位补空格
{
sprintf( Number_Buffer , "%*d",len, number ); // 将 number 转换成字符串,便于显示
}

LCD_DisplayString( x, y,(char *)Number_Buffer) ; // 将转换得到的字符串显示出来

}

/***************************************************************************************************************************************
* 函 数 名: LCD_DisplayDecimals
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* decimals - 要显示的数字, double型取值1.7 x 10^(-308)~ 1.7 x 10^(+308),但是能确保准确的有效位数为15~16位
*
* len - 整个变量的总位数(包括小数点和负号),若实际的总位数超过了指定的总位数,将按实际的总长度位输出,
* 示例1:小数 -123.123 ,指定 len <=8 的话,则实际照常输出 -123.123
* 示例2:小数 -123.123 ,指定 len =10 的话,则实际输出 -123.123(负号前面会有两个空格位)
* 示例3:小数 -123.123 ,指定 len =10 的话,当调用函数 LCD_ShowNumMode() 设置为填充0模式时,实际输出 -00123.123
*
* decs - 要保留的小数位数,若小数的实际位数超过了指定的小数位,则按指定的宽度四舍五入输出
* 示例:1.12345 ,指定 decs 为4位的话,则输出结果为1.1235
*
* 函数功能: 在指定坐标显示指定的变量,包括小数
*
* 说 明: 1. 可设置要显示的字体,例如使用 LCD_SetAsciiFont(&ASCII_Font24) 设置为的ASCII字符字体
* 2. 可设置要显示的颜色,例如使用 LCD_SetColor(0x0000FF) 设置为蓝色
* 3. 可设置对应的背景色,例如使用 LCD_SetBackColor(0x000000) 设置为黑色的背景色
* 4. 使用示例 LCD_DisplayDecimals( 10, 10, a, 5, 3) ,在坐标(10,10)显示字变量a,总长度为5位,其中保留3位小数
*
*****************************************************************************************************************************************/

void LCD_DisplayDecimals( uint16_t x, uint16_t y, double decimals, uint8_t len, uint8_t decs)
{
char Number_Buffer[20]; // 用于存储转换后的字符串

if( LCD.ShowNum_Mode == Fill_Zero) // 多余位填充0模式
{
sprintf( Number_Buffer , "%0*.*lf",len,decs, decimals ); // 将 number 转换成字符串,便于显示
}
else // 多余位填充空格
{
sprintf( Number_Buffer , "%*.*lf",len,decs, decimals ); // 将 number 转换成字符串,便于显示
}

LCD_DisplayString( x, y,(char *)Number_Buffer) ; // 将转换得到的字符串显示出来
}


/***************************************************************************************************************************************
* 函 数 名: LCD_DrawLine
*
* 入口参数: x1 - 起点 水平坐标
* y1 - 起点 垂直坐标
*
* x2 - 终点 水平坐标
* y2 - 终点 垂直坐标
*
* 函数功能: 在两点之间画线
*
* 说 明: 该函数移植于ST官方评估板的例程
*
*****************************************************************************************************************************************/

#define ABS(X) ((X) > 0 ? (X) : -(X))

void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
int16_t deltax = 0, deltay = 0, x = 0, y = 0, xinc1 = 0, xinc2 = 0,
yinc1 = 0, yinc2 = 0, den = 0, num = 0, numadd = 0, numpixels = 0,
curpixel = 0;

deltax = ABS(x2 - x1); /* The difference between the x's */
deltay = ABS(y2 - y1); /* The difference between the y's */
x = x1; /* Start x off at the first pixel */
y = y1; /* Start y off at the first pixel */

if (x2 >= x1) /* The x-values are increasing */
{
xinc1 = 1;
xinc2 = 1;
}
else /* The x-values are decreasing */
{
xinc1 = -1;
xinc2 = -1;
}

if (y2 >= y1) /* The y-values are increasing */
{
yinc1 = 1;
yinc2 = 1;
}
else /* The y-values are decreasing */
{
yinc1 = -1;
yinc2 = -1;
}

if (deltax >= deltay) /* There is at least one x-value for every y-value */
{
xinc1 = 0; /* Don't change the x when numerator >= denominator */
yinc2 = 0; /* Don't change the y for every iteration */
den = deltax;
num = deltax / 2;
numadd = deltay;
numpixels = deltax; /* There are more x-values than y-values */
}
else /* There is at least one y-value for every x-value */
{
xinc2 = 0; /* Don't change the x for every iteration */
yinc1 = 0; /* Don't change the y when numerator >= denominator */
den = deltay;
num = deltay / 2;
numadd = deltax;
numpixels = deltay; /* There are more y-values than x-values */
}
for (curpixel = 0; curpixel <= numpixels; curpixel++)
{
LCD_DrawPoint(x,y,LCD.Color); /* Draw the current pixel */
num += numadd; /* Increase the numerator by the top of the fraction */
if (num >= den) /* Check if numerator >= denominator */
{
num -= den; /* Calculate the new numerator value */
x += xinc1; /* Change the x as appropriate */
y += yinc1; /* Change the y as appropriate */
}
x += xinc2; /* Change the x as appropriate */
y += yinc2; /* Change the y as appropriate */
}
}

/***************************************************************************************************************************************
* 函 数 名: LCD_DrawLine_V
*
* 入口参数: x - 水平坐标
* y - 垂直坐标
* height - 垂直宽度
*
* 函数功能: 在指点位置绘制指定长宽的 垂直 线
*
* 说 明: 1. 该函数移植于ST官方评估板的例程
* 2. 要绘制的区域不能超过屏幕的显示区域
* 3. 如果只是画垂直的线,优先使用此函数,速度比 LCD_DrawLine 快很多
* 性能测试:
*****************************************************************************************************************************************/

void LCD_DrawLine_V(uint16_t x, uint16_t y, uint16_t height)
{
uint16_t i ; // 计数变量

for (i = 0; i < height; i++)
{
LCD_Buff[i] = LCD.Color; // 写入缓冲区
}
LCD_SetAddress( x, y, x, y+height-1); // 设置坐标

LCD_WriteBuff(LCD_Buff,height); // 写入显存
}

/***************************************************************************************************************************************
* 函 数 名: LCD_DrawLine_H
*
* 入口参数: x - 水平坐标
* y - 垂直坐标
* width - 水平宽度
*
* 函数功能: 在指点位置绘制指定长宽的 水平 线
*
* 说 明: 1. 该函数移植于ST官方评估板的例程
* 2. 要绘制的区域不能超过屏幕的显示区域
* 3. 如果只是画 水平 的线,优先使用此函数,速度比 LCD_DrawLine 快很多
* 性能测试:
**********************************************************************************************************************************fanke*******/

void LCD_DrawLine_H(uint16_t x, uint16_t y, uint16_t width)
{
uint16_t i ; // 计数变量

for (i = 0; i < width; i++)
{
LCD_Buff[i] = LCD.Color; // 写入缓冲区
}
LCD_SetAddress( x, y, x+width-1, y); // 设置坐标

LCD_WriteBuff(LCD_Buff,width); // 写入显存
}
/***************************************************************************************************************************************
* 函 数 名: LCD_DrawRect
*
* 入口参数: x - 水平坐标
* y - 垂直坐标
* width - 水平宽度
* height - 垂直宽度
*
* 函数功能: 在指点位置绘制指定长宽的矩形线条
*
* 说 明: 1. 该函数移植于ST官方评估板的例程
* 2. 要绘制的区域不能超过屏幕的显示区域
*
*****************************************************************************************************************************************/

void LCD_DrawRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
// 绘制水平线
LCD_DrawLine_H( x, y, width);
LCD_DrawLine_H( x, y+height-1, width);

// 绘制垂直线
LCD_DrawLine_V( x, y, height);
LCD_DrawLine_V( x+width-1, y, height);
}


/***************************************************************************************************************************************
* 函 数 名: LCD_DrawCircle
*
* 入口参数: x - 圆心 水平坐标
* y - 圆心 垂直坐标
* r - 半径
*
* 函数功能: 在坐标 (x,y) 绘制半径为 r 的圆形线条
*
* 说 明: 1. 该函数移植于ST官方评估板的例程
* 2. 要绘制的区域不能超过屏幕的显示区域
*
*****************************************************************************************************************************************/

void LCD_DrawCircle(uint16_t x, uint16_t y, uint16_t r)
{
int Xadd = -r, Yadd = 0, err = 2-2*r, e2;
do {

LCD_DrawPoint(x-Xadd,y+Yadd,LCD.Color);
LCD_DrawPoint(x+Xadd,y+Yadd,LCD.Color);
LCD_DrawPoint(x+Xadd,y-Yadd,LCD.Color);
LCD_DrawPoint(x-Xadd,y-Yadd,LCD.Color);

e2 = err;
if (e2 <= Yadd) {
err += ++Yadd*2+1;
if (-Xadd == Yadd && e2 <= Xadd) e2 = 0;
}
if (e2 > Xadd) err += ++Xadd*2+1;
}
while (Xadd <= 0);
}


/***************************************************************************************************************************************
* 函 数 名: LCD_DrawEllipse
*
* 入口参数: x - 圆心 水平坐标
* y - 圆心 垂直坐标
* r1 - 水平半轴的长度
* r2 - 垂直半轴的长度
*
* 函数功能: 在坐标 (x,y) 绘制水平半轴为 r1 垂直半轴为 r2 的椭圆线条
*
* 说 明: 1. 该函数移植于ST官方评估板的例程
* 2. 要绘制的区域不能超过屏幕的显示区域
*
*****************************************************************************************************************************************/

void LCD_DrawEllipse(int x, int y, int r1, int r2)
{
int Xadd = -r1, Yadd = 0, err = 2-2*r1, e2;
float K = 0, rad1 = 0, rad2 = 0;

rad1 = r1;
rad2 = r2;

if (r1 > r2)
{
do {
K = (float)(rad1/rad2);

LCD_DrawPoint(x-Xadd,y+(uint16_t)(Yadd/K),LCD.Color);
LCD_DrawPoint(x+Xadd,y+(uint16_t)(Yadd/K),LCD.Color);
LCD_DrawPoint(x+Xadd,y-(uint16_t)(Yadd/K),LCD.Color);
LCD_DrawPoint(x-Xadd,y-(uint16_t)(Yadd/K),LCD.Color);

e2 = err;
if (e2 <= Yadd) {
err += ++Yadd*2+1;
if (-Xadd == Yadd && e2 <= Xadd) e2 = 0;
}
if (e2 > Xadd) err += ++Xadd*2+1;
}
while (Xadd <= 0);
}
else
{
Yadd = -r2;
Xadd = 0;
do {
K = (float)(rad2/rad1);

LCD_DrawPoint(x-(uint16_t)(Xadd/K),y+Yadd,LCD.Color);
LCD_DrawPoint(x+(uint16_t)(Xadd/K),y+Yadd,LCD.Color);
LCD_DrawPoint(x+(uint16_t)(Xadd/K),y-Yadd,LCD.Color);
LCD_DrawPoint(x-(uint16_t)(Xadd/K),y-Yadd,LCD.Color);

e2 = err;
if (e2 <= Xadd) {
err += ++Xadd*3+1;
if (-Yadd == Xadd && e2 <= Yadd) e2 = 0;
}
if (e2 > Yadd) err += ++Yadd*3+1;
}
while (Yadd <= 0);
}
}

/***************************************************************************************************************************************
* 函 数 名: LCD_FillCircle
*
* 入口参数: x - 圆心 水平坐标
* y - 圆心 垂直坐标
* r - 半径
*
* 函数功能: 在坐标 (x,y) 填充半径为 r 的圆形区域
*
* 说 明: 1. 该函数移植于ST官方评估板的例程
* 2. 要绘制的区域不能超过屏幕的显示区域
*
*****************************************************************************************************************************************/

void LCD_FillCircle(uint16_t x, uint16_t y, uint16_t r)
{
int32_t D; /* Decision Variable */
uint32_t CurX;/* Current X Value */
uint32_t CurY;/* Current Y Value */

D = 3 - (r << 1);

CurX = 0;
CurY = r;

while (CurX <= CurY)
{
if(CurY > 0)
{
LCD_DrawLine_V(x - CurX, y - CurY,2*CurY);
LCD_DrawLine_V(x + CurX, y - CurY,2*CurY);
}

if(CurX > 0)
{
// LCD_DrawLine(x - CurY, y - CurX,x - CurY,y - CurX + 2*CurX);
// LCD_DrawLine(x + CurY, y - CurX,x + CurY,y - CurX + 2*CurX);

LCD_DrawLine_V(x - CurY, y - CurX,2*CurX);
LCD_DrawLine_V(x + CurY, y - CurX,2*CurX);
}
if (D < 0)
{
D += (CurX << 2) + 6;
}
else
{
D += ((CurX - CurY) << 2) + 10;
CurY--;
}
CurX++;
}
LCD_DrawCircle(x, y, r);
}

/***************************************************************************************************************************************
* 函 数 名: LCD_FillRect
*
* 入口参数: x - 水平坐标
* y - 垂直坐标
* width - 水平宽度
* height -垂直宽度
*
* 函数功能: 在坐标 (x,y) 填充指定长宽的实心矩形
*
* 说 明: 要绘制的区域不能超过屏幕的显示区域
*
*****************************************************************************************************************************************/

void LCD_FillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
LCD_SetAddress( x, y, x+width-1, y+height-1); // 设置坐标

LCD_DC_Data; // 数据指令选择 引脚输出高电平,代表本次传输 数据

// 修改为16位数据宽度,写入数据更加效率,不需要拆分
LCD_SPI.Init.DataSize = SPI_DATASIZE_16BIT; // 16位数据宽度
HAL_SPI_Init(&LCD_SPI);

LCD_SPI_Transmit(&LCD_SPI, LCD.Color, width*height) ;

// 改回8位数据宽度,因为指令和部分数据都是按照8位传输的
LCD_SPI.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据宽度
HAL_SPI_Init(&LCD_SPI);
}


/***************************************************************************************************************************************
* 函 数 名: LCD_DrawImage
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* width - 图片的水平宽度
* height - 图片的垂直宽度
* *pImage - 图片数据存储区的首地址
*
* 函数功能: 在指定坐标处显示图片
*
* 说 明: 1.要显示的图片需要事先进行取模、获悉图片的长度和宽度
* 2.使用 LCD_SetColor() 函数设置画笔色,LCD_SetBackColor() 设置背景色
*
*****************************************************************************************************************************************/

void LCD_DrawImage(uint16_t x,uint16_t y,uint16_t width,uint16_t height,const uint8_t *pImage)
{
uint8_t disChar; // 字模的值
uint16_t Xaddress = x; // 水平坐标
uint16_t Yaddress = y; // 垂直坐标
uint16_t i=0,j=0,m=0; // 计数变量
uint16_t BuffCount = 0; // 缓冲区计数
uint16_t Buff_Height = 0; // 缓冲区的行数

// 因为缓冲区大小有限,需要分多次写入
Buff_Height = (sizeof(LCD_Buff)/2) / height; // 计算缓冲区能够写入图片的多少行

for(i = 0; i <height; i++) // 循环按行写入
{
for(j = 0; j <(float)width/8; j++)
{
disChar = *pImage;

for(m = 0; m < 8; m++)
{
if(disChar & 0x01)
{
LCD_Buff[BuffCount] = LCD.Color; // 当前模值不为0时,使用画笔色绘点
}
else
{
LCD_Buff[BuffCount] = LCD.BackColor; //否则使用背景色绘制点
}
disChar >>= 1; // 模值移位
Xaddress++; // 水平坐标自加
BuffCount++; // 缓冲区计数
if( (Xaddress - x)==width ) // 如果水平坐标达到了字符宽度,则退出当前循环,进入下一行的绘制
{
Xaddress = x;
break;
}
}
pImage++;
}
if( BuffCount == Buff_Height*width ) // 达到缓冲区所能容纳的最大行数时
{
BuffCount = 0; // 缓冲区计数清0

LCD_SetAddress( x, Yaddress , x+width-1, Yaddress+Buff_Height-1); // 设置坐标
LCD_WriteBuff(LCD_Buff,width*Buff_Height); // 写入显存

Yaddress = Yaddress+Buff_Height; // 计算行偏移,开始写入下一部分数据
}
if( (i+1)== height ) // 到了最后一行时
{
LCD_SetAddress( x, Yaddress , x+width-1,i+y); // 设置坐标
LCD_WriteBuff(LCD_Buff,width*(i+1+y-Yaddress)); // 写入显存
}
}
}


/***************************************************************************************************************************************
* 函 数 名: LCD_CopyBuffer
*
* 入口参数: x - 起始水平坐标
* y - 起始垂直坐标
* width - 目标区域的水平宽度
* height - 目标区域的垂直宽度
* *pImage - 数据存储区的首地址
*
* 函数功能: 在指定坐标处,直接将数据复制到屏幕的显存
*
* 说 明: 批量复制函数,可用于移植 LVGL 或者将摄像头采集的图像显示出来
*
*****************************************************************************************************************************************/

void LCD_CopyBuffer(uint16_t x, uint16_t y,uint16_t width,uint16_t height,uint16_t *DataBuff)
{

LCD_SetAddress(x,y,x+width-1,y+height-1);

LCD_DC_Data; // 数据指令选择 引脚输出高电平,代表本次传输 数据

// 修改为16位数据宽度,写入数据更加效率,不需要拆分
LCD_SPI.Init.DataSize = SPI_DATASIZE_16BIT; // 16位数据宽度
HAL_SPI_Init(&LCD_SPI);

LCD_SPI_TransmitBuffer(&LCD_SPI, DataBuff,width * height) ;

// HAL_SPI_Transmit(&hspi5, (uint8_t *)DataBuff, (x2-x1+1) * (y2-y1+1), 1000) ;

// 改回8位数据宽度,因为指令和部分数据都是按照8位传输的
LCD_SPI.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据宽度
HAL_SPI_Init(&LCD_SPI);

}

/**********************************************************************************************************************************
*
* 以下几个函数修改于HAL的库函数,目的是为了SPI传输数据不限数据长度的写入,并且提高清屏的速度
*
*****************************************************************************************************************FANKE************/


/**
* @brief Handle SPI Communication Timeout.
* @param hspi: pointer to a SPI_HandleTypeDef structure that contains
* the configuration information for SPI module.
* @param Flag: SPI flag to check
* @param Status: flag state to check
* @param Timeout: Timeout duration
* @param Tickstart: Tick start value
* @retval HAL status
*/
HAL_StatusTypeDef LCD_SPI_WaitOnFlagUntilTimeout(SPI_HandleTypeDef *hspi, uint32_t Flag, FlagStatus Status,
uint32_t Tickstart, uint32_t Timeout)
{
/* Wait until flag is set */
while ((__HAL_SPI_GET_FLAG(hspi, Flag) ? SET : RESET) == Status)
{
/* Check for the Timeout */
if ((((HAL_GetTick() - Tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
{
return HAL_TIMEOUT;
}
}
return HAL_OK;
}


/**
* @brief Close Transfer and clear flags.
* @param hspi: pointer to a SPI_HandleTypeDef structure that contains
* the configuration information for SPI module.
* @retval HAL_ERROR: if any error detected
* HAL_OK: if nothing detected
*/
void LCD_SPI_CloseTransfer(SPI_HandleTypeDef *hspi)
{
uint32_t itflag = hspi->Instance->SR;

__HAL_SPI_CLEAR_EOTFLAG(hspi);
__HAL_SPI_CLEAR_TXTFFLAG(hspi);

/* Disable SPI peripheral */
__HAL_SPI_DISABLE(hspi);

/* Disable ITs */
__HAL_SPI_DISABLE_IT(hspi, (SPI_IT_EOT | SPI_IT_TXP | SPI_IT_RXP | SPI_IT_DXP | SPI_IT_UDR | SPI_IT_OVR | SPI_IT_FRE | SPI_IT_MODF));

/* Disable Tx DMA Request */
CLEAR_BIT(hspi->Instance->CFG1, SPI_CFG1_TXDMAEN | SPI_CFG1_RXDMAEN);

/* Report UnderRun error for non RX Only communication */
if (hspi->State != HAL_SPI_STATE_BUSY_RX)
{
if ((itflag & SPI_FLAG_UDR) != 0UL)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_UDR);
__HAL_SPI_CLEAR_UDRFLAG(hspi);
}
}

/* Report OverRun error for non TX Only communication */
if (hspi->State != HAL_SPI_STATE_BUSY_TX)
{
if ((itflag & SPI_FLAG_OVR) != 0UL)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_OVR);
__HAL_SPI_CLEAR_OVRFLAG(hspi);
}
}

/* SPI Mode Fault error interrupt occurred -------------------------------*/
if ((itflag & SPI_FLAG_MODF) != 0UL)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_MODF);
__HAL_SPI_CLEAR_MODFFLAG(hspi);
}

/* SPI Frame error interrupt occurred ------------------------------------*/
if ((itflag & SPI_FLAG_FRE) != 0UL)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FRE);
__HAL_SPI_CLEAR_FREFLAG(hspi);
}

hspi->TxXferCount = (uint16_t)0UL;
hspi->RxXferCount = (uint16_t)0UL;
}


/**
* @brief 专为屏幕清屏而修改,将需要清屏的颜色批量传输
* @param hspi : spi的句柄
* @param pData : 要写入的数据
* @param Size : 数据大小
* @retval HAL status
*/

HAL_StatusTypeDef LCD_SPI_Transmit(SPI_HandleTypeDef *hspi,uint16_t pData, uint32_t Size)
{
uint32_t tickstart;
uint32_t Timeout = 1000; // 超时判断
uint32_t LCD_pData_32bit; // 按32位传输时的数据
uint32_t LCD_TxDataCount; // 传输计数
HAL_StatusTypeDef errorcode = HAL_OK;

/* Check Direction parameter */
assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE_2LINES_TXONLY(hspi->Init.Direction));

/* Process Locked */
__HAL_LOCK(hspi);

/* Init tickstart for timeout management*/
tickstart = HAL_GetTick();

if (hspi->State != HAL_SPI_STATE_READY)
{
errorcode = HAL_BUSY;
__HAL_UNLOCK(hspi);
return errorcode;
}

if ( Size == 0UL)
{
errorcode = HAL_ERROR;
__HAL_UNLOCK(hspi);
return errorcode;
}

/* Set the transaction information */
hspi->State = HAL_SPI_STATE_BUSY_TX;
hspi->ErrorCode = HAL_SPI_ERROR_NONE;

LCD_TxDataCount = Size; // 传输的数据长度
LCD_pData_32bit = (pData<<16)|pData ; // 按32位传输时,合并2个像素点的颜色

/*Init field not used in handle to zero */
hspi->pRxBuffPtr = NULL;
hspi->RxXferSize = (uint16_t) 0UL;
hspi->RxXferCount = (uint16_t) 0UL;
hspi->TxISR = NULL;
hspi->RxISR = NULL;

/* Configure communication direction : 1Line */
if (hspi->Init.Direction == SPI_DIRECTION_1LINE)
{
SPI_1LINE_TX(hspi);
}

// 不使用硬件 TSIZE 控制,此处设置为0,即不限制传输的数据长度
MODIFY_REG(hspi->Instance->CR2, SPI_CR2_TSIZE, 0);

/* Enable SPI peripheral */
__HAL_SPI_ENABLE(hspi);

if (hspi->Init.Mode == SPI_MODE_MASTER)
{
/* Master transfer start */
SET_BIT(hspi->Instance->CR1, SPI_CR1_CSTART);
}

/* Transmit data in 16 Bit mode */
while (LCD_TxDataCount > 0UL)
{
/* Wait until TXP flag is set to send data */
if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXP))
{
if ((hspi->TxXferCount > 1UL) && (hspi->Init.FifoThreshold > SPI_FIFO_THRESHOLD_01DATA))
{
*((__IO uint32_t *)&hspi->Instance->TXDR) = (uint32_t )LCD_pData_32bit;
LCD_TxDataCount -= (uint16_t)2UL;
}
else
{
*((__IO uint16_t *)&hspi->Instance->TXDR) = (uint16_t )pData;
LCD_TxDataCount--;
}
}
else
{
/* Timeout management */
if ((((HAL_GetTick() - tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
{
/* Call standard close procedure with error check */
LCD_SPI_CloseTransfer(hspi);

/* Process Unlocked */
__HAL_UNLOCK(hspi);

SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_TIMEOUT);
hspi->State = HAL_SPI_STATE_READY;
return HAL_ERROR;
}
}
}

if (LCD_SPI_WaitOnFlagUntilTimeout(hspi, SPI_SR_TXC, RESET, tickstart, Timeout) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
}

SET_BIT((hspi)->Instance->CR1 , SPI_CR1_CSUSP); // 请求挂起SPI传输
/* 等待SPI挂起 */
if (LCD_SPI_WaitOnFlagUntilTimeout(hspi, SPI_FLAG_SUSP, RESET, tickstart, Timeout) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
}
LCD_SPI_CloseTransfer(hspi); /* Call standard close procedure with error check */

SET_BIT((hspi)->Instance->IFCR , SPI_IFCR_SUSPC); // 清除挂起标志位


/* Process Unlocked */
__HAL_UNLOCK(hspi);

hspi->State = HAL_SPI_STATE_READY;

if (hspi->ErrorCode != HAL_SPI_ERROR_NONE)
{
return HAL_ERROR;
}
return errorcode;
}

/**
* @brief 专为批量写入数据修改,使之不限长度的传输数据
* @param hspi : spi的句柄
* @param pData : 要写入的数据
* @param Size : 数据大小
* @retval HAL status
*/
HAL_StatusTypeDef LCD_SPI_TransmitBuffer (SPI_HandleTypeDef *hspi, uint16_t *pData, uint32_t Size)
{
uint32_t tickstart;
uint32_t Timeout = 1000; // 超时判断
uint32_t LCD_TxDataCount; // 传输计数
HAL_StatusTypeDef errorcode = HAL_OK;

/* Check Direction parameter */
assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE_2LINES_TXONLY(hspi->Init.Direction));

/* Process Locked */
__HAL_LOCK(hspi);

/* Init tickstart for timeout management*/
tickstart = HAL_GetTick();

if (hspi->State != HAL_SPI_STATE_READY)
{
errorcode = HAL_BUSY;
__HAL_UNLOCK(hspi);
return errorcode;
}

if ( Size == 0UL)
{
errorcode = HAL_ERROR;
__HAL_UNLOCK(hspi);
return errorcode;
}

/* Set the transaction information */
hspi->State = HAL_SPI_STATE_BUSY_TX;
hspi->ErrorCode = HAL_SPI_ERROR_NONE;

LCD_TxDataCount = Size; // 传输的数据长度

/*Init field not used in handle to zero */
hspi->pRxBuffPtr = NULL;
hspi->RxXferSize = (uint16_t) 0UL;
hspi->RxXferCount = (uint16_t) 0UL;
hspi->TxISR = NULL;
hspi->RxISR = NULL;

/* Configure communication direction : 1Line */
if (hspi->Init.Direction == SPI_DIRECTION_1LINE)
{
SPI_1LINE_TX(hspi);
}

// 不使用硬件 TSIZE 控制,此处设置为0,即不限制传输的数据长度
MODIFY_REG(hspi->Instance->CR2, SPI_CR2_TSIZE, 0);

/* Enable SPI peripheral */
__HAL_SPI_ENABLE(hspi);

if (hspi->Init.Mode == SPI_MODE_MASTER)
{
/* Master transfer start */
SET_BIT(hspi->Instance->CR1, SPI_CR1_CSTART);
}

/* Transmit data in 16 Bit mode */
while (LCD_TxDataCount > 0UL)
{
/* Wait until TXP flag is set to send data */
if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXP))
{
if ((LCD_TxDataCount > 1UL) && (hspi->Init.FifoThreshold > SPI_FIFO_THRESHOLD_01DATA))
{
*((__IO uint32_t *)&hspi->Instance->TXDR) = *((uint32_t *)pData);
pData += 2;
LCD_TxDataCount -= 2;
}
else
{
*((__IO uint16_t *)&hspi->Instance->TXDR) = *((uint16_t *)pData);
pData += 1;
LCD_TxDataCount--;
}
}
else
{
/* Timeout management */
if ((((HAL_GetTick() - tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
{
/* Call standard close procedure with error check */
LCD_SPI_CloseTransfer(hspi);

/* Process Unlocked */
__HAL_UNLOCK(hspi);

SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_TIMEOUT);
hspi->State = HAL_SPI_STATE_READY;
return HAL_ERROR;
}
}
}

if (LCD_SPI_WaitOnFlagUntilTimeout(hspi, SPI_SR_TXC, RESET, tickstart, Timeout) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
}

SET_BIT((hspi)->Instance->CR1 , SPI_CR1_CSUSP); // 请求挂起SPI传输
/* 等待SPI挂起 */
if (LCD_SPI_WaitOnFlagUntilTimeout(hspi, SPI_FLAG_SUSP, RESET, tickstart, Timeout) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
}
LCD_SPI_CloseTransfer(hspi); /* Call standard close procedure with error check */

SET_BIT((hspi)->Instance->IFCR , SPI_IFCR_SUSPC); // 清除挂起标志位


/* Process Unlocked */
__HAL_UNLOCK(hspi);

hspi->State = HAL_SPI_STATE_READY;

if (hspi->ErrorCode != HAL_SPI_ERROR_NONE)
{
return HAL_ERROR;
}
return errorcode;
}

头文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#ifndef __spi_lcd
#define __spi_lcd

#include "stm32h7xx_hal.h"
#include "usart.h"

#include "lcd_fonts.h" // 图片和字库文件不是必须,用户可自行删减

/*----------------------------------------------- 参数宏 -------------------------------------------*/

#define LCD_Width 240 // LCD的像素长度
#define LCD_Height 320 // LCD的像素宽度

// 显示方向参数
// 使用示例:LCD_DisplayDirection(Direction_H) 设置屏幕横屏显示
#define Direction_H 0 //LCD横屏显示
#define Direction_H_Flip 1 //LCD横屏显示,上下翻转
#define Direction_V 2 //LCD竖屏显示
#define Direction_V_Flip 3 //LCD竖屏显示,上下翻转

// 设置变量显示时多余位补0还是补空格
// 只有 LCD_DisplayNumber() 显示整数 和 LCD_DisplayDecimals()显示小数 这两个函数用到
// 使用示例: LCD_ShowNumMode(Fill_Zero) 设置多余位填充0,例如 123 可以显示为 000123
#define Fill_Zero 0 //填充0
#define Fill_Space 1 //填充空格


/*---------------------------------------- 常用颜色 ------------------------------------------------------

1. 这里为了方便用户使用,定义的是24位 RGB888颜色,然后再通过代码自动转换成 16位 RGB565 的颜色
2. 24位的颜色中,从高位到低位分别对应 R、G、B 3个颜色通道
3. 用户可以在电脑用调色板获取24位RGB颜色,再将颜色输入LCD_SetColor()或LCD_SetBackColor()就可以显示出相应的颜色
*/
#define LCD_WHITE 0xFFFFFF // 纯白色
#define LCD_BLACK 0x000000 // 纯黑色

#define LCD_BLUE 0x0000FF // 纯蓝色
#define LCD_GREEN 0x00FF00 // 纯绿色
#define LCD_RED 0xFF0000 // 纯红色
#define LCD_CYAN 0x00FFFF // 蓝绿色
#define LCD_MAGENTA 0xFF00FF // 紫红色
#define LCD_YELLOW 0xFFFF00 // 黄色
#define LCD_GREY 0x2C2C2C // 灰色

#define LIGHT_BLUE 0x8080FF // 亮蓝色
#define LIGHT_GREEN 0x80FF80 // 亮绿色
#define LIGHT_RED 0xFF8080 // 亮红色
#define LIGHT_CYAN 0x80FFFF // 亮蓝绿色
#define LIGHT_MAGENTA 0xFF80FF // 亮紫红色
#define LIGHT_YELLOW 0xFFFF80 // 亮黄色
#define LIGHT_GREY 0xA3A3A3 // 亮灰色

#define DARK_BLUE 0x000080 // 暗蓝色
#define DARK_GREEN 0x008000 // 暗绿色
#define DARK_RED 0x800000 // 暗红色
#define DARK_CYAN 0x008080 // 暗蓝绿色
#define DARK_MAGENTA 0x800080 // 暗紫红色
#define DARK_YELLOW 0x808000 // 暗黄色
#define DARK_GREY 0x404040 // 暗灰色


/*------------------------------------------------ 函数声明 ----------------------------------------------*/

void SPI_LCD_Init(void); // 液晶屏以及SPI初始化
void LCD_Clear(void); // 清屏函数
void LCD_ClearRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height); // 局部清屏函数

void LCD_SetAddress(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2); // 设置坐标
void LCD_SetColor(uint32_t Color); // 设置画笔颜色
void LCD_SetBackColor(uint32_t Color); // 设置背景颜色
void LCD_SetDirection(uint8_t direction); // 设置显示方向

//>>>>> 显示ASCII字符
void LCD_SetAsciiFont(pFONT *fonts); // 设置ASCII字体
void LCD_DisplayChar(uint16_t x, uint16_t y,uint8_t c); // 显示单个ASCII字符
void LCD_DisplayString( uint16_t x, uint16_t y, char *p); // 显示ASCII字符串

//>>>>> 显示中文字符,包括ASCII码
void LCD_SetTextFont(pFONT *fonts); // 设置文本字体,包括中文和ASCII字体
void LCD_DisplayChinese(uint16_t x, uint16_t y, char *pText); // 显示单个汉字
void LCD_DisplayText(uint16_t x, uint16_t y, char *pText) ; // 显示字符串,包括中文和ASCII字符

//>>>>> 显示整数或小数
void LCD_ShowNumMode(uint8_t mode); // 设置变量显示模式,多余位填充空格还是填充0
void LCD_DisplayNumber( uint16_t x, uint16_t y, int32_t number,uint8_t len) ; // 显示整数
void LCD_DisplayDecimals( uint16_t x, uint16_t y, double number,uint8_t len,uint8_t decs); // 显示小数

//>>>>> 2D图形函数
void LCD_DrawPoint(uint16_t x,uint16_t y,uint32_t color); //画点

void LCD_DrawLine_V(uint16_t x, uint16_t y, uint16_t height); // 画垂直线
void LCD_DrawLine_H(uint16_t x, uint16_t y, uint16_t width); // 画水平线
void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); // 两点之间画线

void LCD_DrawRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height); //画矩形
void LCD_DrawCircle(uint16_t x, uint16_t y, uint16_t r); //画圆
void LCD_DrawEllipse(int x, int y, int r1, int r2); //画椭圆

//>>>>> 区域填充函数
void LCD_FillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height); //填充矩形
void LCD_FillCircle(uint16_t x, uint16_t y, uint16_t r); //填充圆

//>>>>> 绘制单色图片
void LCD_DrawImage(uint16_t x,uint16_t y,uint16_t width,uint16_t height,const uint8_t *pImage) ;

//>>>>> 批量复制函数,直接将数据复制到屏幕的显存
void LCD_CopyBuffer(uint16_t x, uint16_t y,uint16_t width,uint16_t height,uint16_t *DataBuff);

/*--------------------------------------------- LCD其它引脚 -----------------------------------------------*/

#define LCD_Backlight_PIN GPIO_PIN_15 // 背光 引脚
#define LCD_Backlight_PORT GPIOD // 背光 GPIO端口
#define GPIO_LDC_Backlight_CLK_ENABLE __HAL_RCC_GPIOD_CLK_ENABLE() // 背光 GPIO时钟

#define LCD_Backlight_OFF HAL_GPIO_WritePin(LCD_Backlight_PORT, LCD_Backlight_PIN, GPIO_PIN_RESET); // 低电平,关闭背光
#define LCD_Backlight_ON HAL_GPIO_WritePin(LCD_Backlight_PORT, LCD_Backlight_PIN, GPIO_PIN_SET); // 高电平,开启背光

#define LCD_DC_PIN GPIO_PIN_15 // 数据指令选择 引脚
#define LCD_DC_PORT GPIOE // 数据指令选择 GPIO端口
#define GPIO_LDC_DC_CLK_ENABLE __HAL_RCC_GPIOE_CLK_ENABLE() // 数据指令选择 GPIO时钟

#define LCD_DC_Command HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET); // 低电平,指令传输
#define LCD_DC_Data HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET); // 高电平,数据传输

#endif //__spi_lcd

字模是二维数组的格式,这边给出头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// lcd_fonts.h
#ifndef __FONTS_H
#define __FONTS_H

#include <stdint.h>


// 字体相关结构定义
typedef struct _pFont
{
const uint8_t *pTable; // 字模数组地址
uint16_t Width; // 单个字符的字模宽度
uint16_t Height; // 单个字符的字模长度
uint16_t Sizes; // 单个字符的字模数据个数
uint16_t Table_Rows; // 该参数只有汉字字模用到,表示二维数组的行大小
} pFONT;


/*------------------------------------ 中文字体 ---------------------------------------------*/

extern pFONT CH_Font12 ; // 1212字体
extern pFONT CH_Font16 ; // 1616字体
extern pFONT CH_Font20 ; // 2020字体
extern pFONT CH_Font24 ; // 2424字体
extern pFONT CH_Font32 ; // 3232字体


/*------------------------------------ ASCII字体 ---------------------------------------------*/

extern pFONT ASCII_Font32; // 3216 字体
extern pFONT ASCII_Font24; // 2412 字体
extern pFONT ASCII_Font20; // 2010 字体
extern pFONT ASCII_Font16; // 1608 字体
extern pFONT ASCII_Font12; // 1206 字体

#endif

比如说:

1
2
3
4
5
6
7
// 汉字字模数据,字体为1212
// 添加新字模时,一定要不超过数组第一维的大小,用户可根据需求自行调整
// 中文标点符号和英文标点符号是不一样的!如果没有对中文标点符号进行取模,显示时要用英文输入标点,使用ASCII的字模
// 每个字模后面都必须要有一个对应的汉字作为索引
const uint8_t Chinese_1212[20][24]=
{ {0x00,0x00,0x00,0x00,0x00,0x00,0x84,0x00,0xFF,0x07,0x84,0x00,0xFC,0x03,0x27,0x03,0xC4,0x00,0x37,0x07,0x00,0x00,0x00,0x00},{"技"},/*8*/
};

有了显示功能,接下来就是写显示什么的功能了,这里用到了ov5640,因为ov5640模块自带flash,所以还需要对ov5640模块写入固件,但是为了方便起见,这里通过h7进行开机写入,掉电清楚功能,这样可以方便不少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#ifndef __DCMI_OV5640_H
#define __DCMI_OV5640_H

// dcmi_ov5640.h

#include "stm32h7xx_hal.h"
#include "sccb.h"
#include "usart.h"
#include "lcd_spi_200.h"

// DCMI状态标志,当数据帧传输完成时,会被 HAL_DCMI_FrameEventCallback() 中断回调函数置 1
extern volatile uint8_t OV5640_FrameState; // 声明变量,方便其它文件进行调用
extern volatile uint8_t OV5640_FPS ; // 帧率

// 用于设置输出的格式,被 OV5640_Set_Pixformat() 引用
#define Pixformat_RGB565 0
#define Pixformat_JPEG 1
#define Pixformat_GRAY 2

#define OV5640_AF_Focusing 2 // 正在处于自动对焦中
#define OV5640_AF_End 1 // 自动对焦结束
#define OV5640_Success 0 // 通讯成功标志
#define OV5640_Error -1 // 通讯错误

#define OV5640_Enable 1
#define OV5640_Disable 0


// OV5640的特效模式,被 OV5640_Set_Effect() 引用
#define OV5640_Effect_Normal 0 // 正常模式
#define OV5640_Effect_Negative 1 // 负片模式,也就是颜色全部取反
#define OV5640_Effect_BW 2 // 黑白模式
#define OV5640_Effect_Solarize 3 // 正负片叠加模式

// 1. 定义OV5640实际输出的图像大小,可以根据实际的应用或者显示屏进行调整
// 2. 这两个参数不会影响帧率
// 3. 因为配置的OV5640的ISP窗口比例为4:3(1280*960),用户设置的输出尺寸也应满足这个比例
// 4. 如果需要其它比例,需要修改初始化配置里的参数
#define OV5640_Width 440 // 图像长度
#define OV5640_Height 330 // 图像宽度

// 1. 定义要显示的画面大小,数值一定要能被4整除!!
// 2. RGB565格式下,最终会由DCMI将OV5640输出的4:3图像裁剪为适应屏幕的比例
// 3. JPG模式下,数值一定要能被8整除!!
#define Display_Width LCD_Width // 图像长度
#define Display_Height LCD_Height // 图像宽度

#define Display_BufferSize Display_Width * Display_Height * 2 /4 // DMA传输数据大小(32位宽)

/*------------------------------------------------------------ 常用寄存器 ------------------------------------------------*/

#define OV5640_ChipID_H 0x300A // 芯片ID寄存器 高字节
#define OV5640_ChipID_L 0x300B // 芯片ID寄存器 低字节

#define OV5640_FORMAT_CONTROL 0x4300 // 设置数据接口输出的格式
#define OV5640_FORMAT_CONTROL_MUX 0x501F // 设置ISP的格式

#define OV5640_JPEG_MODE_SELECT 0x4713 // JPEG模式选择,有1~6模式,用户可数据手册里的说明
#define OV5640_JPEG_VFIFO_CTRL00 0x4600 // 用于设置JPEG模式2是否固定输出宽度
#define OV5640_JPEG_VFIFO_HSIZE_H 0x4602 // JPEG输出水平尺寸,高字节
#define OV5640_JPEG_VFIFO_HSIZE_L 0x4603 // JPEG输出水平尺寸,低字节
#define OV5640_JPEG_VFIFO_VSIZE_H 0x4604 // JPEG输出垂直尺寸,低字节
#define OV5640_JPEG_VFIFO_VSIZE_L 0x4605 // JPEG输出垂直尺寸,低字节

#define OV5640_GroupAccess 0X3212 // 寄存器组访问
#define OV5640_TIMING_DVPHO_H 0x3808 // 输出水平尺寸,高字节
#define OV5640_TIMING_DVPHO_L 0x3809 // 输出水平尺寸,低字节
#define OV5640_TIMING_DVPVO_H 0x380A // 输出垂直尺寸,高字节
#define OV5640_TIMING_DVPVO_L 0x380B // 输出垂直尺寸,低字节
#define OV5640_TIMING_Flip 0x3820 // Bit[2:1]用于设置是否垂直翻转
#define OV5640_TIMING_Mirror 0x3821 // Bit[2:1]用于设置是否水平镜像

#define OV5640_AF_CMD_MAIN 0x3022 // AF 主命令
#define OV5640_AF_CMD_ACK 0x3023 // AF 命令确认
#define OV5640_AF_FW_STATUS 0x3029 // 对焦状态寄存器

/*------------------------------------------------------------ 函数声明 ------------------------------------------------*/

int8_t DCMI_OV5640_Init(void); // 初始SCCB、DCMI、DMA以及配置OV5640

void OV5640_DMA_Transmit_Continuous(uint32_t DMA_Buffer,uint32_t DMA_BufferSize); // 启动DMA传输,连续模式
void OV5640_DMA_Transmit_Snapshot(uint32_t DMA_Buffer,uint32_t DMA_BufferSize); // 启动DMA传输,快照模式,传输一帧图像后停止
void OV5640_DCMI_Suspend(void); // 挂起DCMI,停止捕获数据
void OV5640_DCMI_Resume(void); // 恢复DCMI,开始捕获数据
void OV5640_DCMI_Stop(void); // 禁止DCMI的DMA请求,停止DCMI捕获,禁止DCMI外设
int8_t OV5640_DCMI_Crop(uint16_t Displey_XSize,uint16_t Displey_YSize,uint16_t Sensor_XSize,uint16_t Sensor_YSize ); // 裁剪画面

void OV5640_Reset(void); // 执行软件复位
uint16_t OV5640_ReadID(void); // 读取器件ID
void OV5640_Config(void); // 配置OV5640各项参数

void OV5640_Set_Pixformat(uint8_t pixformat); // 设置图像输出格式
void OV5640_Set_JPEG_QuantizationScale(uint8_t scale); // 设置JPEG格式的压缩等级,取值 0x01~0x3F
int8_t OV5640_Set_Framesize(uint16_t width,uint16_t height); // 设置实际输出的图像大小
int8_t OV5640_Set_Horizontal_Mirror( int8_t ConfigState ); // 用于设置输出的图像是否进行水平镜像
int8_t OV5640_Set_Vertical_Flip( int8_t ConfigState ); // 用于设置输出的图像是否进行垂直翻转
void OV5640_Set_Brightness(int8_t Brightness); // 设置亮度
void OV5640_Set_Contrast(int8_t Contrast); // 设置对比度
void OV5640_Set_Effect(uint8_t effect_Mode); // 用于设置特效,正常、负片等模式

int8_t OV5640_AF_Download_Firmware(void); // 将自动对焦固件写入OV5640
int8_t OV5640_AF_QueryStatus(void); // 对焦状态查询
void OV5640_AF_Trigger_Constant(void); // 自动对焦 ,持续 触发
void OV5640_AF_Trigger_Single(void); // 自动对焦 ,触发 单次
void OV5640_AF_Release(void); // 释放马达,镜头回到初始(对焦为无穷远处)位置

/*-------------------------------------------------------------- 引脚配置宏 ---------------------------------------------*/

#define OV5640_PWDN_PIN GPIO_PIN_14 // PWDN 引脚
#define OV5640_PWDN_PORT GPIOD // PWDN GPIO端口
#define GPIO_OV5640_PWDN_CLK_ENABLE __HAL_RCC_GPIOD_CLK_ENABLE() // PWDN GPIO端口时钟

// 低电平,不开启掉电模式,摄像头正常工作
#define OV5640_PWDN_OFF HAL_GPIO_WritePin(OV5640_PWDN_PORT, OV5640_PWDN_PIN, GPIO_PIN_RESET)

// 高电平,进入掉电模式,摄像头停止工作,此时功耗降到最低
#define OV5640_PWDN_ON HAL_GPIO_WritePin(OV5640_PWDN_PORT, OV5640_PWDN_PIN, GPIO_PIN_SET)


#endif //__DCMI_OV5640_H

详细代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
/***
***********************************************************************************************************************************************
* @file dcmi_ov5640.c
* @date 2022-4-6
* @author 反客科技、Minloha
* @brief OV5640驱动
***********************************************************************************************************************************************
* @description
*
* 驱动参考 Arduino/ArduCAM 和 OpenMV 的源码
*
>>>>> 驱动说明:
*
* 1.例程默认配置 OV5640 为 4:3(1280*960) 43帧 的配置(JPG模式2、3情况下帧率会减半)
* 2.开启了DMA并使能了中断,移植的时候需要移植对应的中断
*
*************************************************************************************************************************************************************************************************************************************************************************************FANke*****
***/

#include "dcmi_ov5640.h"
#include "dcmi_ov5640_cfg.h"

DCMI_HandleTypeDef hdcmi; // DCMI句柄
DMA_HandleTypeDef DMA_Handle_dcmi; // DMA句柄

volatile uint8_t OV5640_FrameState = 0; // DCMI状态标志,当数据帧传输完成时,会被 HAL_DCMI_FrameEventCallback() 中断回调函数置 1
volatile uint8_t OV5640_FPS ; // 帧率

/*****************************************************************************************************************************************
* 函 数 名: HAL_DCMI_MspInit
*
* 入口参数: hdcmi - DCMI_HandleTypeDef定义的变量,即表示定义的 DCMI 句柄
*
* 函数功能: 初始化 DCMI 引脚
*
*****************************************************************************************************************************************/
void HAL_DCMI_MspInit(DCMI_HandleTypeDef* hdcmi)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};

if(hdcmi->Instance==DCMI)
{
__HAL_RCC_DCMI_CLK_ENABLE(); // 使能 DCMI 外设时钟

__HAL_RCC_GPIOE_CLK_ENABLE();// 使能相应的GPIO时钟
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_OV5640_PWDN_CLK_ENABLE; // 使能PWDN 引脚的 GPIO 时钟

/****************************************************************************
数据引脚 时钟和同步引脚
PC6 ------> DCMI_D0 PB7 ------> DCMI_VSYNC
PC7 ------> DCMI_D1 PA4 ------> DCMI_HSYNC
PE0 ------> DCMI_D2 PA6 ------> DCMI_PIXCLK
PE1 ------> DCMI_D3
PE4 ------> DCMI_D4 SCCB 控制引脚,初始化在 sccb.c 文件
PD3 ------> DCMI_D5 PB8 ------> SCCB_SCL
PE5 ------> DCMI_D6 PB9 ------> SCCB_SDA
PE6 ------> DCMI_D7

掉电控制引脚
PD14 ------> PWDN
******************************************************************************/

GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_0|GPIO_PIN_5|GPIO_PIN_4
|GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_DCMI;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_DCMI;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_DCMI;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_DCMI;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_DCMI;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 初始化 PWDN 引脚
OV5640_PWDN_ON; // 高电平,进入掉电模式,摄像头停止工作,此时功耗降到最低

GPIO_InitStruct.Pin = OV5640_PWDN_PIN; // PWDN 引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 速度等级低
HAL_GPIO_Init(OV5640_PWDN_PORT, &GPIO_InitStruct); // 初始化
}
}

/***************************************************************************************************************************************
* 函 数 名: MX_DCMI_Init
*
* 函数功能: 配置DCMI相关参数
*
* 说 明: 8位数据模式,全数据、全帧捕捉,开启中断
*
*****************************************************************************************************************************************/
void MX_DCMI_Init(void)
{
hdcmi.Instance = DCMI;
hdcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE; // 硬件同步模式,即使用外部的VS、HS信号进行同步
hdcmi.Init.PCKPolarity = DCMI_PCKPOLARITY_RISING; // 像素时钟上升沿有效
hdcmi.Init.VSPolarity = DCMI_VSPOLARITY_LOW; // VS低电平有效
hdcmi.Init.HSPolarity = DCMI_HSPOLARITY_LOW; // HS低电平有效
hdcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME; // 捕获等级,设置每一帧都进行捕获
hdcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B; // 8位数据模式
hdcmi.Init.JPEGMode = DCMI_JPEG_DISABLE; // 不使用DCMI的JPEG模式
hdcmi.Init.ByteSelectMode = DCMI_BSM_ALL; // DCMI接口捕捉所有数据
hdcmi.Init.ByteSelectStart = DCMI_OEBS_ODD; // 选择开始字节,从 帧/行 的第一个数据开始捕获
hdcmi.Init.LineSelectMode = DCMI_LSM_ALL; // 捕获所有行
hdcmi.Init.LineSelectStart = DCMI_OELS_ODD; // 选择开始行,在帧开始后捕获第一行
HAL_DCMI_Init(&hdcmi) ;

HAL_NVIC_SetPriority(DCMI_IRQn, 0 ,5); // 设置中断优先级
HAL_NVIC_EnableIRQ(DCMI_IRQn); // 开启DCMI中断
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_DMA_Init
*
* 函数功能: 配置 DMA 相关参数
*
* 说 明: 使用的是DMA2,外设到存储器模式、数据位宽32bit、并开启中断
*
*****************************************************************************************************************************************/
void OV5640_DMA_Init(void)
{
__HAL_RCC_DMA2_CLK_ENABLE(); // 使能DMA2时钟

DMA_Handle_dcmi.Instance = DMA2_Stream7; // DMA2数据流7
DMA_Handle_dcmi.Init.Request = DMA_REQUEST_DCMI; // DMA请求来自DCMI
DMA_Handle_dcmi.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到存储器模式
DMA_Handle_dcmi.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址禁止自增
DMA_Handle_dcmi.Init.MemInc = DMA_MINC_ENABLE; // 存储器地址自增
DMA_Handle_dcmi.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // DCMI数据位宽,32位
DMA_Handle_dcmi.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; // 存储器数据位宽,32位
DMA_Handle_dcmi.Init.Mode = DMA_CIRCULAR; // 循环模式
DMA_Handle_dcmi.Init.Priority = DMA_PRIORITY_LOW; // 优先级低
DMA_Handle_dcmi.Init.FIFOMode = DMA_FIFOMODE_ENABLE; // 使能fifo
DMA_Handle_dcmi.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; // 全fifo模式,4*32bit大小
DMA_Handle_dcmi.Init.MemBurst = DMA_MBURST_SINGLE; // 单次传输
DMA_Handle_dcmi.Init.PeriphBurst = DMA_PBURST_SINGLE; // 单次传输

HAL_DMA_Init(&DMA_Handle_dcmi); // 配置DMA
__HAL_LINKDMA(&hdcmi, DMA_Handle, DMA_Handle_dcmi); // 关联DCMI句柄

HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0); // 设置中断优先级
HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn); // 使能中断
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_Delay
* 入口参数: Delay - 延时时间,单位 ms
* 函数功能: 简单延时函数,不是很精确
* 说 明: 为了移植的简便性,此处采用软件延时,实际项目中可以替换成RTOS的延时或者HAL库的延时
*****************************************************************************************************************************************/
void OV5640_Delay(uint32_t Delay)
{
volatile uint16_t i;

while (Delay --)
{
for (i = 0; i < 40000; i++);
}
// HAL_Delay(Delay); // 可使用HAL库的延时
}

/***************************************************************************************************************************************
* 函 数 名: DCMI_OV5640_Init
*
* 函数功能: 初始SCCB、DCMI、DMA以及配置OV5640
*
*****************************************************************************************************************************************/
int8_t DCMI_OV5640_Init(void)
{
uint16_t Device_ID; // 定义变量存储器件ID

SCCB_GPIO_Config(); // SCCB引脚初始化
MX_DCMI_Init(); // 初始化DCMI配置引脚
OV5640_DMA_Init(); // 初始化DMA配置
OV5640_Reset(); // 执行软件复位
Device_ID = OV5640_ReadID(); // 读取器件ID

if( Device_ID == 0x5640 ) // 进行匹配
{
printf ("OV5640 OK,ID:0x%X\r\n",Device_ID); // 匹配通过

OV5640_Config(); // 配置各项参数
OV5640_Set_Framesize(OV5640_Width,OV5640_Height); // 设置OV5640输出的图像大小
// OV5640_Set_Effect(OV5640_Effect_Normal);
OV5640_DCMI_Crop( Display_Width, Display_Height, OV5640_Width, OV5640_Height ); // 将输出图像裁剪成适应屏幕的大小,JPG模式不需要裁剪

return OV5640_Success; // 返回成功标志
}
else
{
printf ("OV5640 ERROR!!!!! ID:%X\r\n",Device_ID); // 读取ID错误
return OV5640_Error; // 返回错误标志
}
}


/***************************************************************************************************************************************
* 函 数 名: OV5640_DMA_Transmit_Continuous
*
* 入口参数: DMA_Buffer - DMA将要传输的地址,即用于存储摄像头数据的存储区地址
* DMA_BufferSize - 传输的数据大小,32位宽
*
* 函数功能: 启动DMA传输,连续模式
*
* 说 明: 1. 开启连续模式之后,会一直进行传输,除非挂起或者停止DCMI
* 2. OV5640使用RGB565模式时,1个像素点需要2个字节来存储
* 3. 因为DMA配置传输数据为32位宽,计算 DMA_BufferSize 时,需要除以4,例如:
* 要获取 240*240分辨率 的图像,需要传输 240*240*2 = 115200 字节的数据,
* 则 DMA_BufferSize = 115200 / 4 = 28800 。
*fanke
*****************************************************************************************************************************************/
void OV5640_DMA_Transmit_Continuous(uint32_t DMA_Buffer, uint32_t DMA_BufferSize)
{
DMA_Handle_dcmi.Init.Mode = DMA_CIRCULAR; // 循环模式

HAL_DMA_Init(&DMA_Handle_dcmi); // 配置DMA

// 使能DCMI采集数据,连续采集模式
HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS, (uint32_t)DMA_Buffer, DMA_BufferSize);
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_DMA_Transmit_Snapshot
*
* 入口参数: DMA_Buffer - DMA将要传输的地址,即用于存储摄像头数据的存储区地址
* DMA_BufferSize - 传输的数据大小,32位宽
*
* 函数功能: 启动DMA传输,快照模式,传输一帧图像后停止
*
* 说 明: 1. 快照模式,只传输一帧的数据
* 2. OV5640使用RGB565模式时,1个像素点需要2个字节来存储
* 3. 因为DMA配置传输数据为32位宽,计算 DMA_BufferSize 时,需要除以4,例如:
* 要获取 240*240分辨率 的图像,需要传输 240*240*2 = 115200 字节的数据,
* 则 DMA_BufferSize = 115200 / 4 = 28800 。
* 4. 使用该模式传输完成之后,DCMI会被挂起,再次启用传输之前,需要调用 OV5640_DCMI_Resume() 恢复DCMI
*
*****************************************************************************************************************************************/
void OV5640_DMA_Transmit_Snapshot(uint32_t DMA_Buffer,uint32_t DMA_BufferSize)
{
DMA_Handle_dcmi.Init.Mode = DMA_NORMAL; // 正常模式

HAL_DMA_Init(&DMA_Handle_dcmi); // 配置DMA

HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_SNAPSHOT, (uint32_t)DMA_Buffer,DMA_BufferSize);
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_DCMI_Suspend
*
* 函数功能: 挂起DCMI,停止捕获数据
*
* 说 明: 1. 开启连续模式之后,再调用该函数,会停止捕获DCMI的数据
* 2. 可以调用 OV5640_DCMI_Resume() 恢复DCMI
* 3. 需要注意的,挂起DCMI期间,DMA是没有停止工作的
*FANKE
*****************************************************************************************************************************************/
void OV5640_DCMI_Suspend(void)
{
HAL_DCMI_Suspend(&hdcmi); // 挂起DCMI
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_DCMI_Resume
*
* 函数功能: 恢复DCMI,开始捕获数据
*
* 说 明: 1. 当DCMI被挂起时,可以调用该函数恢复
* 2. 使用 OV5640_DMA_Transmit_Snapshot() 快照模式,传输完成之后,DCMI也会被挂起,再次启用传输之前,
* 需要调用本函数恢复DCMI捕获
*
*****************************************************************************************************************************************/
void OV5640_DCMI_Resume(void)
{
(&hdcmi)->State = HAL_DCMI_STATE_BUSY; // 变更DCMI标志
(&hdcmi)->Instance->CR |= DCMI_CR_CAPTURE; // 开启DCMI捕获
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_DCMI_Stop
*
* 函数功能: 禁止DCMI的DMA请求,停止DCMI捕获,禁止DCMI外设
*
*****************************************************************************************************************************************/
void OV5640_DCMI_Stop(void)
{
HAL_DCMI_Stop(&hdcmi);
}


/***************************************************************************************************************************************
* 函 数 名: OV5640_DCMI_Crop
*
* 入口参数: Displey_XSize 、Displey_YSize - 显示器的长宽
* Sensor_XSize、Sensor_YSize - 摄像头传感器输出图像的长宽
*
* 函数功能: 使用DCMI的裁剪功能,将传感器输出的图像裁剪成适应屏幕的大小
*
* 说 明: 1. 因为摄像头输出的画面比例不一定匹配显示器,所以需要裁剪
* 2. 摄像头的输出画面比例由 OV5640_Config()配置参数决定,最终画面大小由 OV5640_Set_Framesize()决定
* 3. DCMI的水平有效像素也必须要能被4整除!
* 4. 函数会计算水平和垂直偏移,尽量让画面居中裁剪
*****************************************************************************************************************************************/
int8_t OV5640_DCMI_Crop(uint16_t Displey_XSize,uint16_t Displey_YSize,uint16_t Sensor_XSize,uint16_t Sensor_YSize )
{
uint16_t DCMI_X_Offset,DCMI_Y_Offset; // 水平和垂直偏移,垂直代表的是行数,水平代表的是像素时钟数(PCLK周期数)
uint16_t DCMI_CAPCNT; // 水平有效像素,代表的是像素时钟数(PCLK周期数)
uint16_t DCMI_VLINE; // 垂直有效行数

if( (Displey_XSize>=Sensor_XSize)|| (Displey_YSize>=Sensor_YSize) )
{
printf("实际显示的尺寸大于或等于摄像头输出的尺寸,退出DCMI裁剪\r\n");
return OV5640_Error; //如果实际显示的尺寸大于或等于摄像头输出的尺寸,则退出当前函数,不进行裁剪
}
// 在设置为RGB565格式时,水平偏移,必须是奇数,否则画面色彩不正确,
// 因为一个有效像素是2个字节,需要2个PCLK周期,所以必须从奇数位开始,不然数据会错乱,
// 需要注意的是,寄存器值是从0开始算起的 !
DCMI_X_Offset = Sensor_XSize - Displey_XSize; // 实际计算过程为(Sensor_XSize - LCD_XSize)/2*2

// 计算垂直偏移,尽量让画面居中裁剪,该值代表的是行数,
DCMI_Y_Offset = (Sensor_YSize - Displey_YSize)/2-1; // 寄存器值是从0开始算起的,所以要-1

// 因为一个有效像素是2个字节,需要2个PCLK周期,所以要乘2
// 最终得到的寄存器值,必须要能被4整除!
DCMI_CAPCNT = Displey_XSize*2-1; // 寄存器值是从0开始算起的,所以要-1

DCMI_VLINE = Displey_YSize-1; // 垂直有效行数

// printf("%d %d %d %d\r\n",DCMI_X_Offset,DCMI_Y_Offset,DCMI_CAPCNT,DCMI_VLINE);

HAL_DCMI_ConfigCrop (&hdcmi,DCMI_X_Offset,DCMI_Y_Offset,DCMI_CAPCNT,DCMI_VLINE);// 设置裁剪窗口
HAL_DCMI_EnableCrop(&hdcmi); // 使能裁剪

return OV5640_Success;
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_Reset
*
* 函数功能: 执行软件复位
*
* 说 明: 期间有多个延时操作
*
*****************************************************************************************************************************************/
void OV5640_Reset(void)
{
OV5640_Delay(30); // 等待模块上电稳定,最少5ms,然后拉低PWDN

OV5640_PWDN_OFF; // PWDN 引脚输出低电平,不开启掉电模式,摄像头正常工作,此时摄像头模块的白色LED会点亮

// 根据OV5640的上电时序,PWDN拉低之后,要等待1ms再去拉高RESET,反客的OV5640模块采用硬件RC复位,持续时间大概在6~10ms,
// 因此加入延时,等待硬件复位完成并稳定下来
OV5640_Delay(5);

// 复位完成之后,要>=20ms方可执行SCCB配置
OV5640_Delay(20);

SCCB_WriteReg_16Bit(0x3103, 0x11); // 根据手册的建议,复位之前,直接将时钟输入引脚的时钟作为主时钟
SCCB_WriteReg_16Bit(0x3008, 0x82); // 执行一次软复位
OV5640_Delay(5); //延时5ms

}

/***************************************************************************************************************************************
* 函 数 名: OV5640_ReadID
*
* 函数功能: 读取 OV5640 的器件ID
*
*****************************************************************************************************************************************/
uint16_t OV5640_ReadID(void)
{
uint8_t PID_H,PID_L; // ID变量

PID_H = SCCB_ReadReg_16Bit(OV5640_ChipID_H); // 读取ID高字节
PID_L = SCCB_ReadReg_16Bit(OV5640_ChipID_L); // 读取ID低字节

return(PID_H<<8)|PID_L; // 返回完整的器件ID
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_Config
*
* 函数功能: 配置 OV5640 各个寄存器参数
*
* 说 明: 参数定义在 dcmi_ov5640_cfg.h
*
*****************************************************************************************************************************************/

void OV5640_Config(void)
{
uint32_t i; // 计数变量

uint8_t read_reg; // 读取配置,用于调试

for(i=0; i<(sizeof(OV5640_INIT_Config)/4); i++)
{
SCCB_WriteReg_16Bit(OV5640_INIT_Config[i][0], OV5640_INIT_Config[i][1]); // 写入配置

read_reg = SCCB_ReadReg_16Bit(OV5640_INIT_Config[i][0]); // 读取配置,用于调试

if(OV5640_INIT_Config[i][1] != read_reg ) // 配置不成功
{
printf("出错位置:%d\r\n",i); // 打印出错位置
printf("0x%x-0x%x-0x%x\r\n",OV5640_INIT_Config[i][0],OV5640_INIT_Config[i][1],read_reg);
}
}
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_Set_Pixformat
*
* 入口参数: pixformat - 像素格式,可选择 Pixformat_RGB565、Pixformat_GRAY、Pixformat_JPEG
*
* 函数功能: 设置输出的像素格式
*
*****************************************************************************************************************************************/

void OV5640_Set_Pixformat(uint8_t pixformat)
{
uint8_t OV5640_Reg; // 寄存器的值

if( pixformat == Pixformat_JPEG )
{
SCCB_WriteReg_16Bit(OV5640_FORMAT_CONTROL, 0x30); // 设置数据接口输出的格式
SCCB_WriteReg_16Bit(OV5640_FORMAT_CONTROL_MUX, 0x00); // 设置ISP的格式

SCCB_WriteReg_16Bit(OV5640_JPEG_MODE_SELECT, 0x02); // JPEG 模式2

SCCB_WriteReg_16Bit(OV5640_JPEG_VFIFO_CTRL00, 0xA0); // JPEG 固定行数

SCCB_WriteReg_16Bit(OV5640_JPEG_VFIFO_HSIZE_H, OV5640_Width>>8); // JPEG输出水平尺寸,高字节
SCCB_WriteReg_16Bit(OV5640_JPEG_VFIFO_HSIZE_L, (uint8_t)OV5640_Width); // JPEG输出水平尺寸,低字节
SCCB_WriteReg_16Bit(OV5640_JPEG_VFIFO_VSIZE_H, OV5640_Height>>8); // JPEG输出垂直尺寸,低字节
SCCB_WriteReg_16Bit(OV5640_JPEG_VFIFO_VSIZE_L, (uint8_t)OV5640_Height); // JPEG输出垂直尺寸,低字节

}
else if( pixformat == Pixformat_GRAY )
{
SCCB_WriteReg_16Bit(OV5640_FORMAT_CONTROL, 0x10); // 设置数据接口输出的格式
SCCB_WriteReg_16Bit(OV5640_FORMAT_CONTROL_MUX, 0x00); // 设置ISP的格式
}
else // RGB565
{
SCCB_WriteReg_16Bit(OV5640_FORMAT_CONTROL, 0x6F); // 此处设置为RGB565格式,序列为 G[2:0]B[4:0], R[4:0]G[5:3]
SCCB_WriteReg_16Bit(OV5640_FORMAT_CONTROL_MUX, 0x01); // 设置ISP的格式
}

OV5640_Reg = SCCB_ReadReg_16Bit(0x3821); // 读取寄存器值,Bit[5]用于是否使能JPEG模式
SCCB_WriteReg_16Bit(0x3821,(OV5640_Reg & 0xDF) | ((pixformat == Pixformat_JPEG) ? 0x20 : 0x00));

OV5640_Reg = SCCB_ReadReg_16Bit(0x3002); // 读取寄存器值,Bit[7]、Bit[4]和Bit[2]使能 VFIFO、JFIFO、JPG
SCCB_WriteReg_16Bit(0x3002,(OV5640_Reg & 0xE3) | ((pixformat == Pixformat_JPEG) ? 0x00 : 0x1C));

OV5640_Reg = SCCB_ReadReg_16Bit(0x3006); // 读取寄存器值,Bit[5]和Bit[3] 用于是否使能JPG时钟
SCCB_WriteReg_16Bit(0x3006,(OV5640_Reg & 0xD7) | ((pixformat == Pixformat_JPEG) ? 0x28 : 0x00));

}

/***************************************************************************************************************************************
* 函 数 名: OV5640_Set_JPEG_QuantizationScale
*
* 入口参数: scale - 压缩等级,取值 0x01~0x3F
*
* 函数功能: 数值越大,压缩就越厉害,得到的图片占用空间就越小,但相应的画质会变差,客户可自行调节
*
*****************************************************************************************************************************************/

void OV5640_Set_JPEG_QuantizationScale(uint8_t scale)
{
SCCB_WriteReg_16Bit(0x4407, scale); // JPEG 压缩等级
}


/***************************************************************************************************************************************
* 函 数 名: OV5640_Set_Framesize
*
* 入口参数: width - 实际输出图像的长度,height - 实际输出图像的宽度
*
* 函数功能: 设置实际输出的图像大小(缩放后)
*
* 说 明: 1. 需要注意的是,要设置的图像长、宽需要满足初始化配置时ISP窗口的比例,不然图像会变形
* 2. 并不是设置输出的图像分辨率越小帧率就越高,帧率只和初始化的配置(PLL、HTS和VTS)有关
*
*****************************************************************************************************************************************/

int8_t OV5640_Set_Framesize(uint16_t width,uint16_t height)
{
// OV5640的很多操作,都要加上这种对应 group 的配置
SCCB_WriteReg_16Bit(OV5640_GroupAccess,0X03); // 开始 group 3 的配置

SCCB_WriteReg_16Bit(OV5640_TIMING_DVPHO_H,width>>8); // DVPHO,设置输出水平尺寸
SCCB_WriteReg_16Bit(OV5640_TIMING_DVPHO_L,width&0xff);
SCCB_WriteReg_16Bit(OV5640_TIMING_DVPVO_H,height>>8); // DVPVO,设置输出垂直尺寸
SCCB_WriteReg_16Bit(OV5640_TIMING_DVPVO_L,height&0xff);

SCCB_WriteReg_16Bit(OV5640_GroupAccess,0X13); // 结束配置
SCCB_WriteReg_16Bit(OV5640_GroupAccess,0Xa3); // 启用设置

return OV5640_Success;
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_Set_Horizontal_Mirror
*
* 入口参数: ConfigState - 置1时,图像会水平镜像,置0时恢复正常
*
* 函数功能: 用于设置输出的图像是否进行水平镜像
*
*****************************************************************************************************************************************/
int8_t OV5640_Set_Horizontal_Mirror( int8_t ConfigState )
{
uint8_t OV5640_Reg; // 寄存器的值

OV5640_Reg = SCCB_ReadReg_16Bit(OV5640_TIMING_Mirror); // 读取寄存器值

// Bit[2:1]用于设置是否水平镜像
if ( ConfigState == OV5640_Enable ) // 如果使能镜像
{
OV5640_Reg |= 0X06;
}
else // 取消镜像
{
OV5640_Reg &= 0xF9;
}
return SCCB_WriteReg_16Bit(OV5640_TIMING_Mirror,OV5640_Reg); // 写入寄存器
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_Set_Vertical_Flip
*
* 入口参数: ConfigState - 置1时,图像会垂直翻转,置0时恢复正常
*
* 函数功能: 用于设置输出的图像是否进行垂直翻转
*
*****************************************************************************************************************************************/
int8_t OV5640_Set_Vertical_Flip( int8_t ConfigState )
{
uint8_t OV5640_Reg; // 寄存器的值

OV5640_Reg = SCCB_ReadReg_16Bit(OV5640_TIMING_Flip); // 读取寄存器值

// Bit[2:1]用于设置是否垂直翻转
if ( ConfigState == OV5640_Enable )
{
OV5640_Reg |= 0X06;
}
else // 取消翻转
{
OV5640_Reg &= 0xF9;
}
return SCCB_WriteReg_16Bit(OV5640_TIMING_Flip,OV5640_Reg); // 写入寄存器
}


/***************************************************************************************************************************************
* 函 数 名: OV5640_Set_Brightness
*
* 入口参数: Brightness - 亮度,可设置为9个等级:4,3,2,1,0,-1,-2,-3,-4 ,数字越大亮度越高
*
* 说 明: 1. 直接使用OV5640手册给出的代码
* 2. 亮度越高,画面就越明亮,但是会变模糊一些
* 2. 亮度太低,噪点会增多
*
*****************************************************************************************************************************************/
void OV5640_Set_Brightness(int8_t Brightness)
{
Brightness = Brightness+4;
SCCB_WriteReg_16Bit(OV5640_GroupAccess,0X03); // 开始 group 3 的配置

SCCB_WriteReg_16Bit( 0x5587, OV5640_Brightness_Config[Brightness][0]);
SCCB_WriteReg_16Bit( 0x5588, OV5640_Brightness_Config[Brightness][1]);

SCCB_WriteReg_16Bit(OV5640_GroupAccess,0X13); // 结束配置
SCCB_WriteReg_16Bit(OV5640_GroupAccess,0Xa3); // 启用设置
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_Set_Contrast
*
* 入口参数: Contrast - 对比度,可设置为7个等级:3,2,1,0,-1,-2 ,-3
*
* 说 明: 1. 直接使用OV5640手册给出的代码
* 2. 对比度越高,画面越清晰,黑白越加分明
*
*****************************************************************************************************************************************/
void OV5640_Set_Contrast(int8_t Contrast)
{
Contrast = Contrast+3;
SCCB_WriteReg_16Bit(OV5640_GroupAccess,0X03); // 开始 group 3 的配置

SCCB_WriteReg_16Bit( 0x5586, OV5640_Contrast_Config[Contrast][0]);
SCCB_WriteReg_16Bit( 0x5585, OV5640_Contrast_Config[Contrast][1]);

SCCB_WriteReg_16Bit(OV5640_GroupAccess,0X13); // 结束配置
SCCB_WriteReg_16Bit(OV5640_GroupAccess,0Xa3); // 启用设置
}
/***************************************************************************************************************************************
* 函 数 名: OV5640_Set_Effect
*
* 入口参数: effect_Mode - 特效模式,可选择参数 OV5640_Effect_Normal、OV5640_Effect_Negative、
* OV5640_Effect_BW、OV5640_Effect_Solarize
*
* 函数功能: 用于设置OV5640的特效,正常、负片、黑白、正负片叠加模式
*
* 说 明: 这里仅列举了4个模式,更多特效模式可以参考手册进行配置
*
*****************************************************************************************************************************************/
void OV5640_Set_Effect(uint8_t effect_Mode)
{
SCCB_WriteReg_16Bit(OV5640_GroupAccess,0X03); // 开始 group 3 的配置

SCCB_WriteReg_16Bit( 0x5580, OV5640_Effect_Config[effect_Mode][0]);
SCCB_WriteReg_16Bit( 0x5583, OV5640_Effect_Config[effect_Mode][1]);
SCCB_WriteReg_16Bit( 0x5584, OV5640_Effect_Config[effect_Mode][2]);
SCCB_WriteReg_16Bit( 0x5003, OV5640_Effect_Config[effect_Mode][3]);

SCCB_WriteReg_16Bit(OV5640_GroupAccess,0X13); // 结束配置
SCCB_WriteReg_16Bit(OV5640_GroupAccess,0Xa3); // 启用设置

}
/***************************************************************************************************************************************
* 函 数 名: OV5640_Download_AF_Firmware
*
* 函数功能: 将自动对焦固件写入OV5640
*
* 说 明: 因为OV5640片内没有flash,不能保存固件,因此每次上电都要写入一次
*
*****************************************************************************************************************************************/

int8_t OV5640_AF_Download_Firmware(void)
{
uint8_t AF_Status = 0; // 对焦状态
uint16_t i = 0; // 计数变量
uint16_t OV5640_MCU_Addr = 0x8000; // OV5640 MCU 存储器的起始地址为 0x8000,大小为4KB

SCCB_WriteReg_16Bit(0x3000, 0x20); // Bit[5],复位MCU,写入固件之前,需要执行此操作
// 开始写入固件,批量写入,提高写入速度
SCCB_WriteBuffer_16Bit( OV5640_MCU_Addr,(uint8_t *)OV5640_AF_Firmware,sizeof(OV5640_AF_Firmware) );
SCCB_WriteReg_16Bit(0x3000,0x00); // Bit[5],写入完毕,写0使能MCU

// 写入固件之后,会有个初始化的过程,因此尝试读取100次状态,根据状态进行判断
for(i=0;i<100;i++)
{
AF_Status = SCCB_ReadReg_16Bit(OV5640_AF_FW_STATUS); // 读取状态寄存器
if( AF_Status == 0x7E)
{
printf("AF固件初始化中>>>\r\n");
}
if( AF_Status == 0x70) // 释放马达,镜头回到初始(对焦为无穷远处)位置,意味着固件写入成功
{
printf("AF固件写入成功!\r\n");
return OV5640_Success;
}
}
// 尝试100次读取之后,还是没有读到0x70状态,说明固件没写入成功
printf("自动对焦固件写入失败!!!error!!\r\n");
return OV5640_Error;
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_AF_QueryStatus
*
* 返 回 值:OV5640_AF_End - 对焦结束, OV5640_AF_Focusing - 正在对焦
*
* 函数功能: 对焦状态查询
*
* 说 明: 1. 对焦过程大概会持续500多ms
* 2. 对焦没完成时,采集到的的图像不在焦点,会非常模糊
*
*****************************************************************************************************************************************/

int8_t OV5640_AF_QueryStatus(void)
{
uint8_t AF_Status = 0; // 对焦状态

AF_Status = SCCB_ReadReg_16Bit(OV5640_AF_FW_STATUS); // 读取状态寄存器
printf("AF_Status:0x%x\r\n",AF_Status);

// 单次对焦模式 下,返回 0x10,持续对焦模式下,返回0x20
if( (AF_Status == 0x10)||(AF_Status == 0x20) )
{
return OV5640_AF_End; // 返回 对焦结束 标志
}
else
{
return OV5640_AF_Focusing; // 返回 正在对焦 标志
}
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_AF_Trigger_Constant
*
* 函数功能: 持续触发对焦,当OV5640检测到当前画面不在焦点时,会一直触发对焦,无需用户干预
*
* 说 明: 1.可以调用 OV5640_AF_QueryStatus() 函数查询对焦状态
* 2.可以调用 OV5640_AF_Release() 退出持续对焦模式
* 3.对焦过程大概会持续500多ms
* 4.有时环境光线太暗,OV5640会反复的进行对焦,用户可根据实际情况切换到单次对焦模式
*
*****************************************************************************************************************************************/

void OV5640_AF_Trigger_Constant(void)
{
SCCB_WriteReg_16Bit(0x3022,0x04); // 持续对焦
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_AF_Trigger_Single
*
* 函数功能: 触发一次自动对焦
*
* 说 明: 对焦过程大概会持续500多ms,用户可以调用 OV5640_AF_QueryStatus() 函数查询对焦状态
*
*****************************************************************************************************************************************/

void OV5640_AF_Trigger_Single(void)
{
SCCB_WriteReg_16Bit(OV5640_AF_CMD_MAIN,0x03); // 触发一次自动对焦
}

/***************************************************************************************************************************************
* 函 数 名: OV5640_AF_Release
*
* 函数功能: 释放马达,镜头回到初始(对焦为无穷远处)位置
*
*****************************************************************************************************************************************/

void OV5640_AF_Release(void)
{
SCCB_WriteReg_16Bit(OV5640_AF_CMD_MAIN,0x08); // 对焦释放指令
}

/***************************************************************************************************************************************
* 函 数 名: HAL_DCMI_FrameEventCallback
*
* 函数功能: 帧回调函数,每传输一帧数据,会进入该中断服务函数
*
* 说 明: 每次传输完一帧,对相应的标志位进行操作,并计算帧率
*****************************************************************************************************************************************/

void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
{
static uint32_t DCMI_Tick = 0; // 用于保存当前的时间计数值
static uint8_t DCMI_Frame_Count = 0; // 帧数计数

if(HAL_GetTick() - DCMI_Tick >= 1000) // 每隔 1s 计算一次帧率
{
DCMI_Tick = HAL_GetTick(); // 重新获取当前时间计数值

OV5640_FPS = DCMI_Frame_Count; // 获得fps

DCMI_Frame_Count = 0; // 计数清0
}
DCMI_Frame_Count ++; // 每进入一次中断(每次传输完一帧数据),计数值+1

OV5640_FrameState = 1; // 传输完成标志位置1
}

/***************************************************************************************************************************************
* 函 数 名: HAL_DCMI_ErrorCallback
*
* 函数功能: 错误回调函数
*
* 说 明: 当发生DMA传输错误或者FIFO溢出错误就会进入
*****************************************************************************************************************************************/

void HAL_DCMI_ErrorCallback(DCMI_HandleTypeDef *hdcmi)
{
// if( HAL_DCMI_GetError(hdcmi) == HAL_DCMI_ERROR_OVR)
// {
// printf("FIFO溢出错误!!!\r\n");
// }
// printf("error:0x%x!!!!\r\n",HAL_DCMI_GetError(hdcmi));
}

一些寄存器与自动对焦固件的16进制形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
#ifndef __DCMI_OV2640_CFG_H
#define __DCMI_OV2640_CFG_H

// dcmi_ov5640_cfg.h

#include "stm32h7xx_hal.h"

// 亮度等级,总共有9个等级,数字越大亮度越高
const uint8_t OV5640_Brightness_Config[][2]=
{
{0x40, 0x09}, /* -4 */
{0x30, 0x09}, /* -3 */
{0x20, 0x09}, /* -2 */
{0x10, 0x09}, /* -1 */
{0x00, 0x01}, /* +0 */
{0x10, 0x01}, /* +1 */
{0x20, 0x01}, /* +2 */
{0x30, 0x01}, /* +3 */
{0x40, 0x01}, /* +4 */
};

// 对比度等级,总共有7个等级,数字越大对比度越高
const uint8_t OV5640_Contrast_Config[][2]=
{
{0x14, 0x14}, /* -3 */
{0x18, 0x18}, /* -2 */
{0x1c, 0x1c}, /* -1 */
{0x20, 0x00}, /* +0 */
{0x24, 0x10}, /* +1 */
{0x28, 0x18}, /* +2 */
{0x2c, 0x1c}, /* +3 */
};

// 特效设置,这里仅列举了4个模式,更多特效模式可以参考手册进行配置
const uint8_t OV5640_Effect_Config[][4]=
{
{0x06, 0x40, 0x10, 0x08}, /* +0 正常模式*/
{0x40, 0x08, 0x40, 0x10}, /* +1 负片模式*/
{0x1e, 0x80, 0x80, 0x08}, /* +2 黑白模式*/
{0x06, 0x40, 0x10, 0x09}, /* +3 正负片叠加模式*/
};


// OV5640初始化配置
const uint16_t OV5640_INIT_Config[][2]=
{
{0x3008, 0x42}, // 系统电源控制,Bit[6]设置为1进入掉电模式,Bit[5:0]手册里没有说明作用
{0x3103, 0x03}, // 系统时钟选择,设置使用PLL生成后的时钟,根据手册的配置,设置为0x03
{0x3017, 0xff}, // PCLK、VS、HS以及数据引脚 D9~D6 使能输出
{0x4740, 0X21}, // 设置PCLK、VS和HS的信号极性
{0x3018, 0xff}, // 数据引脚 D5~D0 使能输出

/*------- 时钟配置,可结合资料里的《时钟图》进行参考 -------------------------------------*/

// 以下所有关于时钟的配置,都是在 XVCLK=24MHz 的前提下进行设置

// PLL预分频,bit[7:5]没有说明作用
// bit[4], PLL R divider,用来设置是否将经过0x3035寄存器配置之后的时钟进行分频,设置为0不分频,为1则是2分频
// Bit[3:0],PLL pre-divider,预分频,此处设置为3,即将 XVCLK 3分频后得到8M的时钟
{0x3037, 0x13}, // 分频

// Bit[7:0] 用于设置倍频参数,倍频值可以是4~127的任何数值,大于127时,只能设置为偶数
// 此处设置为 100,即将上面分频得到的8M时钟进行100倍频得到800M时钟
{0x3036, 0x64}, // 倍频

// Bit[3:0] 用于设置MIPI时钟,这里用不到,保留默认值1
// Bit[7:4] 分频系数,此处设置为1,即不分频,此时主时钟还是800M,
// 如果需要降低帧率,可以设置为0x21,帧率会减半
{0x3035, 0x11}, // 分频

// 接着回到 0x3037 的 bit[4] 设置,上面设置为1,即2分频,此时时钟为400M
// 0x3034的 Bit[3:0] 的取值会影响分频系数,设置为 8 是2分频,A是2.5分频,其它值则不分频
// 此处设置是2.5分频,此时主时钟为 160M
{0x3034, 0x1A}, // 手册的默认值,0x1A

// 接下来这个寄存器是设置 PCLK(也就是DCMI接口里的像素时钟) 和 SCLK(应该是OV5640内部的各个处理单元的时钟)
// Bit[5:4]: PCLK 分频,此处为0,即不分频
// Bit[3:0] 都是和SCLK有关,直接使用手册给的参考值即可
{0x3108, 0x01}, // 分频

// 0x460c寄存器的Bit[7:4] 用于设置JPEG空数据速度,默认值0x20
// 0x460c寄存器的Bit[1] 用于设置PCLK的分频值,为0则是自动控制模式,设置为1时,PCLK的分频值由0x3824寄存器控制
// 自动模式下,生成的时钟非常高,通常都是80M,如果用户对EMC、 EMI有需求,可以手动调整
{0x460c, 0x20},

// Bit[4:0]有效,只能设置为 1、2、4、8、16 这几个分频值
// 在上面的配置中,最终生成了160M的时钟,然后再根据0x3824寄存器的分频值得到最终的PCLK输出时钟
// STM32H750/743的DCMI接口所允许的最高速度为80M,所以不管是自动配置还是手动配置,都不能超过这个值
{0x3824, 0x02}, // PCLK分频系数 , 0x460c寄存器的Bit[1] 设置为1时才有效 fanke

/*------------------------------------------------------------------ 时钟配置结束 -----*/

// 手册里没有说明这些寄存器的作用,这里直接保留手册给的设置参数
{0x3630,0x36}, {0x3631,0x0e}, {0x3632,0xe2}, {0x3633,0x12}, {0x3621,0xe0}, {0x3704,0xa0}, // FanKe
{0x3703,0x5a}, {0x3715,0x78}, {0x3717,0x01}, {0x370b,0x60}, {0x3705,0x1a}, {0x3905,0x02},
{0x3906,0x10}, {0x3901,0x0a}, {0x3731,0x12}, {0x3600,0x08}, {0x3601,0x33}, {0x302d,0x60},
{0x3620,0x52}, {0x371b,0x20}, {0x471c,0x50}, {0x3635,0x13}, {0x3636,0x03}, {0x3634,0x40},
{0x3622,0x01}, {0x440e,0x00}, {0x5025,0x00}, {0x3618,0x00}, {0x3612,0x29}, {0x3708,0x64},
{0x3709,0x52}, {0x370c,0x03}, {0x302e,0x00}, {0x460b,0x37},

{0x3000, 0x00}, // 使能所有系统单元,包括 BIST、MCU、OTP等
{0x3002, 0x1c}, // 复位 JFIFO, SFIFO, JPG
{0x3004, 0xff}, // 使能所有系统单元,包括 BIST、MCU、OTP等
{0x3006, 0xc3}, // 禁止 JPEG2x, JPEG 的时钟
{0x300e, 0x58}, // 禁止 MIPI,使用DVP接口(也就是STM32的DCMI接口)


// 设置数据接口输出的格式,Bit[7:4]用于设置 RGB或者YUV等 格式,Bit[3:0]设置输出的序列,例如是BGR还是RGB
// 此处设置为RGB565格式,序列为 G[2:0]B[4:0], R[4:0]G[5:3]
{0x4300, 0x6F},
// 除了要设置接口的输出格式,ISP(image sensor processor)图像处理单元也要设置成对应的格式
{0X501F, 0x01}, // ISP格式设置在Bit[2:0] ,此处使用RGB格式
{0x5000, 0xa7}, // ISP设置,使能 LENC补偿、黑色像素、白色像素以及颜色插值(CIP)
{0x5001, 0xA3}, // ISP设置,使能 SDE特效、图像缩放、Color Matrix 色彩矩阵 、AWB 自动白平衡

{0x3820, 0x47}, // Bit[2:1]用于设置是否垂直翻转,其余位的作用手册里没有说明
{0x3821, 0x01}, // Bit[2:1]用于设置是否水平镜像,Bit[0]用于使能水平像素合并

/*------- 窗口配置,参考OV5640数据手册 4.2 小节 image windowing ------------------------*/

// OV5640有好几个窗口的概念
// 摄像头的物理像素窗口:2624*1954 (包含黑电平矫正线和空像素,有效分辨率为2592*1944)
// ISP(image sensor processor)输入窗口:需要进行处理的像素窗口
// 预缩放窗口:ISP窗口的基础上,调整用于缩放输出的窗口
// 输出窗口: 根据预缩放窗口和要输出的分辨率,进行缩放,得到最终的图像

// 因为摄像头的默认像素比例是 2592/1944 = 4/3 (15帧),而我们实际使用的屏幕往往不是这个比例或者不需要这么高像素,
// 因此需要做一定的调整,最终再配合DCMI的窗口裁剪以匹配屏幕。
// (注:用户也可以直接使用OV5640裁剪 物理窗口 得到对应比例的 ISP窗口,然后缩放偏移按照默认设置即可,
// 例如可以直接将ISP窗口设置为 240/280等对应实际屏幕的比例,而无需DCMI去裁剪。不过为了例程的通用性,我们选择
// 使用 4:3固定比例+DCMI裁剪的方式 )

// 以下配置为 4:3(1280*960) 43帧 的配置,
// 可以设置 0x3035寄存器 为0x21,帧率会减半
// JPG模式2、3情况下,帧率也会减半
{0x3800, 0x00}, // HS,ISP窗口水平起始坐标 0
{0x3801, 0x00}, // HS
{0x3802, 0x00}, // VS,ISP窗口垂直起始坐标 4
{0x3803, 0x04}, // VS
{0x3804, 0x0a}, // HW (HE),ISP窗口水平终点坐标2623 ,实际尺寸=2624
{0x3805, 0x3f}, // HW (HE)
{0x3806, 0x07}, // VH (VE),ISP窗口垂直终点坐标1947 ,实际尺寸=1947-4+1=1944
{0x3807, 0x9b}, // VH (VE)
{0x380c, 0x07}, // HTS,输出的水平总尺寸,不懂要如何取值,手册没有说,但这里是影响帧率的最重要配置,这里直接使用手册给的参考值
{0x380d, 0x68}, // HTS
{0x380e, 0x03}, // VTS,输出的垂直总尺寸,不懂要如何取值,手册没有说,但这里是影响帧率的最重要配置,这里直接使用手册给的参考值
{0x380f, 0xd8}, // VTS

// 预缩放窗口的设置,用于设置 在ISP窗口的基础上,根据偏移量的多少,得到预缩放窗口,
// 这里直接使用OV5640的默认配置,水平偏移16,垂直偏移4,因为包含了一些空像素和无效行,不能取0
{0x3810,0x00}, // 水平偏移高字节
{0x3811,0x10}, // 水平偏移低字节
{0x3812,0x00}, // 垂直偏移高字节
{0x3813,0x04}, // 垂直偏移低字节

// 以下两个寄存器,手册的描述是 水平和垂直二次采样时,奇数和偶数的增量,
// 笔者猜测的理解是:因为OV5640的全画幅分辨率很大,当实际输出的图像尺寸小于最大分辨率时,
// 需要通过裁剪或者2次采样,这两个寄存器用于控制每多少个像素去合并成一个像素,但是能找到
// 的信息有限,因此只能根据手册的给出的参考代码去配置
{0x3814,0x31}, // timing X inc
{0x3815,0x31}, // timing Y inc

/*------------------------------------------------------------------ 窗口配置结束 -----*/

// BLC(Black Level Calibration )黑电平校正
// OV5640的像素阵列包含几条光学屏蔽(黑色)线,这些线被用作黑电平校准的参考,
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置
{0x4001,0x02}, {0x4004,0x02}, {0x4005,0x1a},

// 曝光时间相关
// 不清楚要如何去配置,这里使用的是 OpenMV 的配置
{0x3a02,0x05}, {0x3a03,0xc4}, {0x3a08,0x00}, {0x3a09,0x93}, {0x3a0a,0x00},
{0x3a0b,0x7b}, {0x3a0d,0x08}, {0x3a0e,0x06}, {0x3a14,0x05}, {0x3a15,0xc4},

// AEC 增益相关
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置
{0x3a13,0x43}, {0x3a18,0x00}, {0x3a19,0xf8},

// 50/60Hz 灯光条纹过滤
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置
{0x3c01,0x34}, {0x3c04,0x28}, {0x3c05,0x98}, {0x3c06,0x00}, {0x3c07,0x08},
{0x3c08,0x00}, {0x3c09,0x1c}, {0x3c0a,0x9c}, {0x3c0b,0x40},

// AWB 自动白平衡
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置
{0x5180,0xff}, {0x5181,0xf2}, {0x5182,0x00}, {0x5183,0x14}, {0x5184,0x25}, {0x5185, 0x24},
{0x5186,0x09}, {0x5187,0x09}, {0x5188,0x09}, {0x5189,0x75}, {0x518a,0x54}, {0x518b, 0xe0},
{0x518c,0xb2}, {0x518d,0x42}, {0x518e,0x3d}, {0x518f,0x56}, {0x5190,0x46}, {0x5191, 0xf8},
{0x5192,0x04}, {0x5193,0x70}, {0x5194,0xf0}, {0x5195,0xf0}, {0x5196,0x03}, {0x5197, 0x01},
{0x5198,0x04}, {0x5199,0x12}, {0x519a,0x04}, {0x519b,0x00}, {0x519c,0x06}, {0x519d, 0x82},
{0x519e,0x38},

// color matrix 色彩矩阵
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置
{0x5381,0x1e}, {0x5382,0x5b}, {0x5383,0x08}, {0x5384,0x0a}, {0x5385,0x7e}, {0x5386,0x88},
{0x5387,0x7c}, {0x5388,0x6c}, {0x5389,0x10}, {0x538a,0x01}, {0x538b,0x98},

// CIP 锐化和降噪
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置
{0x5300,0x08}, {0x5301,0x30}, {0x5302,0x10}, {0x5303,0x00}, {0x5304,0x08}, {0x5305,0x30},
{0x5306,0x08}, {0x5307,0x16}, {0x5309,0x08}, {0x530a,0x30}, {0x530b,0x04}, {0x530c,0x06},

// Gamma 伽玛曲线
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置
{0x5480,0x01}, {0x5481,0x08}, {0x5482,0x14}, {0x5483,0x28}, {0x5484,0x51}, {0x5485,0x65},
{0x5486,0x71}, {0x5487,0x7d}, {0x5488,0x87}, {0x5489,0x91}, {0x548a,0x9a}, {0x548b,0xaa},
{0x548c,0xb8}, {0x548d,0xcd}, {0x548e,0xdd}, {0x548f,0xea}, {0x5490,0x1d},

// UV adjust
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置
{0x5580,0x06}, {0x5583,0x40}, {0x5584,0x10}, {0x5589, 0x10},
{0x558a,0x00}, {0x558b,0xf8}, {0x501d,0x40},

// AEC 自动曝光补偿
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置,手册里还给出了好几种曝光设置
{0x3a0f,0x30}, {0x3a10,0x28}, {0x3a1b,0x30}, {0x3a1e,0x26}, {0x3a11,0x60}, {0x3a1f,0x14},

// AWB 环境光配置自动模式
// 不清楚要如何去配置,这里直接使用 OV5640应用指南 里的设置,手册里还给出了好几种环境光设置
{0x3406,0x00}, {0x3400,0x04}, {0x3401,0x00}, {0x3402, 0x04},
{0x3403,0x00}, {0x3404,0x04}, {0x3405,0x00},

// lens correction (LENC) 镜头补偿设置
// 这些设置主要为了弥补镜头的缺陷,通过计算增益来校正每个像素,以补偿改善由于透镜曲率造成的光分布状况,
// 这些配置严格上是需要摄像头模组厂家和 OmniVision FAE 联合去设置的,但由于条件有限,这里直接
// 使用 OV5640应用指南 里的设置
{0x5800,0x23}, {0x5801,0x14}, {0x5802,0x0f}, {0x5803,0x0f}, {0x5804,0x12}, {0x5805,0x26},
{0x5806,0x0c}, {0x5807,0x08}, {0x5808,0x05}, {0x5809,0x05}, {0x580a,0x08}, {0x580b,0x0d},
{0x580c,0x08}, {0x580d,0x03}, {0x580e,0x00}, {0x580f,0x00}, {0x5810,0x03}, {0x5811,0x09},
{0x5812,0x07}, {0x5813,0x03}, {0x5814,0x00}, {0x5815,0x01}, {0x5816,0x03}, {0x5817,0x08},
{0x5818,0x0d}, {0x5819,0x08}, {0x581a,0x05}, {0x581b,0x06}, {0x581c,0x08}, {0x581d,0x0e},
{0x581e,0x29}, {0x581f,0x17}, {0x5820,0x11}, {0x5821,0x11}, {0x5822,0x15}, {0x5823,0x28},
{0x5824,0x46}, {0x5825,0x26}, {0x5826,0x08}, {0x5827,0x26}, {0x5828,0x64}, {0x5829,0x26},
{0x582a,0x24}, {0x582b,0x22}, {0x582c,0x24}, {0x582d,0x24}, {0x582e,0x06}, {0x582f,0x22},
{0x5830,0x40}, {0x5831,0x42}, {0x5832,0x24}, {0x5833,0x26}, {0x5834,0x24}, {0x5835,0x22},
{0x5836,0x22}, {0x5837,0x26}, {0x5838,0x44}, {0x5839,0x24}, {0x583a,0x26}, {0x583b,0x28},
{0x583c,0x42}, {0x583d,0xce},

// 系统电源控制,Bit[5:0]手册里没有说明作用,根据应用指南的说明该配置是从掉电模式中唤醒 fk
{0x3008, 0x02},


};


// 自动对焦固件,需要下载到OV5640的片内MCU区域
const uint8_t OV5640_AF_Firmware[] =
{
0x02, 0x0f, 0xd6, 0x02, 0x0a, 0x39, 0xc2, 0x01, 0x22, 0x22, 0x00, 0x02, 0x0f, 0xb2, 0xe5, 0x1f,
0x70, 0x72, 0xf5, 0x1e, 0xd2, 0x35, 0xff, 0xef, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xe4, 0xf6, 0x08,
0xf6, 0x0f, 0xbf, 0x34, 0xf2, 0x90, 0x0e, 0x93, 0xe4, 0x93, 0xff, 0xe5, 0x4b, 0xc3, 0x9f, 0x50,
0x04, 0x7f, 0x05, 0x80, 0x02, 0x7f, 0xfb, 0x78, 0xbd, 0xa6, 0x07, 0x12, 0x0f, 0x04, 0x40, 0x04,
0x7f, 0x03, 0x80, 0x02, 0x7f, 0x30, 0x78, 0xbc, 0xa6, 0x07, 0xe6, 0x18, 0xf6, 0x08, 0xe6, 0x78,
0xb9, 0xf6, 0x78, 0xbc, 0xe6, 0x78, 0xba, 0xf6, 0x78, 0xbf, 0x76, 0x33, 0xe4, 0x08, 0xf6, 0x78,
0xb8, 0x76, 0x01, 0x75, 0x4a, 0x02, 0x78, 0xb6, 0xf6, 0x08, 0xf6, 0x74, 0xff, 0x78, 0xc1, 0xf6,
0x08, 0xf6, 0x75, 0x1f, 0x01, 0x78, 0xbc, 0xe6, 0x75, 0xf0, 0x05, 0xa4, 0xf5, 0x4b, 0x12, 0x0a,
0xff, 0xc2, 0x37, 0x22, 0x78, 0xb8, 0xe6, 0xd3, 0x94, 0x00, 0x40, 0x02, 0x16, 0x22, 0xe5, 0x1f,
0xb4, 0x05, 0x23, 0xe4, 0xf5, 0x1f, 0xc2, 0x01, 0x78, 0xb6, 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x78,
0x4e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x90, 0x30, 0x28, 0xf0,
0x75, 0x1e, 0x10, 0xd2, 0x35, 0x22, 0xe5, 0x4b, 0x75, 0xf0, 0x05, 0x84, 0x78, 0xbc, 0xf6, 0x90,
0x0e, 0x8c, 0xe4, 0x93, 0xff, 0x25, 0xe0, 0x24, 0x0a, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x78,
0xbc, 0xe6, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0xef, 0x12, 0x0f, 0x0b,
0xd3, 0x78, 0xb7, 0x96, 0xee, 0x18, 0x96, 0x40, 0x0d, 0x78, 0xbc, 0xe6, 0x78, 0xb9, 0xf6, 0x78,
0xb6, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x90, 0x0e, 0x8c, 0xe4, 0x93, 0x12, 0x0f, 0x0b, 0xc3, 0x78,
0xc2, 0x96, 0xee, 0x18, 0x96, 0x50, 0x0d, 0x78, 0xbc, 0xe6, 0x78, 0xba, 0xf6, 0x78, 0xc1, 0xa6,
0x06, 0x08, 0xa6, 0x07, 0x78, 0xb6, 0xe6, 0xfe, 0x08, 0xe6, 0xc3, 0x78, 0xc2, 0x96, 0xff, 0xee,
0x18, 0x96, 0x78, 0xc3, 0xf6, 0x08, 0xa6, 0x07, 0x90, 0x0e, 0x95, 0xe4, 0x18, 0x12, 0x0e, 0xe9,
0x40, 0x02, 0xd2, 0x37, 0x78, 0xbc, 0xe6, 0x08, 0x26, 0x08, 0xf6, 0xe5, 0x1f, 0x64, 0x01, 0x70,
0x4a, 0xe6, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xdf, 0x40, 0x05, 0x12, 0x0e, 0xda, 0x40, 0x39, 0x12,
0x0f, 0x02, 0x40, 0x04, 0x7f, 0xfe, 0x80, 0x02, 0x7f, 0x02, 0x78, 0xbd, 0xa6, 0x07, 0x78, 0xb9,
0xe6, 0x24, 0x03, 0x78, 0xbf, 0xf6, 0x78, 0xb9, 0xe6, 0x24, 0xfd, 0x78, 0xc0, 0xf6, 0x12, 0x0f,
0x02, 0x40, 0x06, 0x78, 0xc0, 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbf, 0xe6, 0xff, 0x78, 0xbe, 0xa6,
0x07, 0x75, 0x1f, 0x02, 0x78, 0xb8, 0x76, 0x01, 0x02, 0x02, 0x4a, 0xe5, 0x1f, 0x64, 0x02, 0x60,
0x03, 0x02, 0x02, 0x2a, 0x78, 0xbe, 0xe6, 0xff, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xe0, 0x40, 0x08,
0x12, 0x0e, 0xda, 0x50, 0x03, 0x02, 0x02, 0x28, 0x12, 0x0f, 0x02, 0x40, 0x04, 0x7f, 0xff, 0x80,
0x02, 0x7f, 0x01, 0x78, 0xbd, 0xa6, 0x07, 0x78, 0xb9, 0xe6, 0x04, 0x78, 0xbf, 0xf6, 0x78, 0xb9,
0xe6, 0x14, 0x78, 0xc0, 0xf6, 0x18, 0x12, 0x0f, 0x04, 0x40, 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f,
0x00, 0x78, 0xbf, 0xa6, 0x07, 0xd3, 0x08, 0xe6, 0x64, 0x80, 0x94, 0x80, 0x40, 0x04, 0xe6, 0xff,
0x80, 0x02, 0x7f, 0x00, 0x78, 0xc0, 0xa6, 0x07, 0xc3, 0x18, 0xe6, 0x64, 0x80, 0x94, 0xb3, 0x50,
0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xbf, 0xa6, 0x07, 0xc3, 0x08, 0xe6, 0x64, 0x80,
0x94, 0xb3, 0x50, 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xc0, 0xa6, 0x07, 0x12, 0x0f,
0x02, 0x40, 0x06, 0x78, 0xc0, 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbf, 0xe6, 0xff, 0x78, 0xbe, 0xa6,
0x07, 0x75, 0x1f, 0x03, 0x78, 0xb8, 0x76, 0x01, 0x80, 0x20, 0xe5, 0x1f, 0x64, 0x03, 0x70, 0x26,
0x78, 0xbe, 0xe6, 0xff, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xe0, 0x40, 0x05, 0x12, 0x0e, 0xda, 0x40,
0x09, 0x78, 0xb9, 0xe6, 0x78, 0xbe, 0xf6, 0x75, 0x1f, 0x04, 0x78, 0xbe, 0xe6, 0x75, 0xf0, 0x05,
0xa4, 0xf5, 0x4b, 0x02, 0x0a, 0xff, 0xe5, 0x1f, 0xb4, 0x04, 0x10, 0x90, 0x0e, 0x94, 0xe4, 0x78,
0xc3, 0x12, 0x0e, 0xe9, 0x40, 0x02, 0xd2, 0x37, 0x75, 0x1f, 0x05, 0x22, 0x30, 0x01, 0x03, 0x02,
0x04, 0xc0, 0x30, 0x02, 0x03, 0x02, 0x04, 0xc0, 0x90, 0x51, 0xa5, 0xe0, 0x78, 0x93, 0xf6, 0xa3,
0xe0, 0x08, 0xf6, 0xa3, 0xe0, 0x08, 0xf6, 0xe5, 0x1f, 0x70, 0x3c, 0x75, 0x1e, 0x20, 0xd2, 0x35,
0x12, 0x0c, 0x7a, 0x78, 0x7e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8b, 0xa6, 0x09, 0x18, 0x76,
0x01, 0x12, 0x0c, 0x5b, 0x78, 0x4e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8b, 0xe6, 0x78, 0x6e,
0xf6, 0x75, 0x1f, 0x01, 0x78, 0x93, 0xe6, 0x78, 0x90, 0xf6, 0x78, 0x94, 0xe6, 0x78, 0x91, 0xf6,
0x78, 0x95, 0xe6, 0x78, 0x92, 0xf6, 0x22, 0x79, 0x90, 0xe7, 0xd3, 0x78, 0x93, 0x96, 0x40, 0x05,
0xe7, 0x96, 0xff, 0x80, 0x08, 0xc3, 0x79, 0x93, 0xe7, 0x78, 0x90, 0x96, 0xff, 0x78, 0x88, 0x76,
0x00, 0x08, 0xa6, 0x07, 0x79, 0x91, 0xe7, 0xd3, 0x78, 0x94, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff,
0x80, 0x08, 0xc3, 0x79, 0x94, 0xe7, 0x78, 0x91, 0x96, 0xff, 0x12, 0x0c, 0x8e, 0x79, 0x92, 0xe7,
0xd3, 0x78, 0x95, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, 0x80, 0x08, 0xc3, 0x79, 0x95, 0xe7, 0x78,
0x92, 0x96, 0xff, 0x12, 0x0c, 0x8e, 0x12, 0x0c, 0x5b, 0x78, 0x8a, 0xe6, 0x25, 0xe0, 0x24, 0x4e,
0xf8, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8a, 0xe6, 0x24, 0x6e, 0xf8, 0xa6, 0x09, 0x78, 0x8a,
0xe6, 0x24, 0x01, 0xff, 0xe4, 0x33, 0xfe, 0xd3, 0xef, 0x94, 0x0f, 0xee, 0x64, 0x80, 0x94, 0x80,
0x40, 0x04, 0x7f, 0x00, 0x80, 0x05, 0x78, 0x8a, 0xe6, 0x04, 0xff, 0x78, 0x8a, 0xa6, 0x07, 0xe5,
0x1f, 0xb4, 0x01, 0x0a, 0xe6, 0x60, 0x03, 0x02, 0x04, 0xc0, 0x75, 0x1f, 0x02, 0x22, 0x12, 0x0c,
0x7a, 0x78, 0x80, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x12, 0x0c, 0x7a, 0x78, 0x82, 0xa6, 0x06, 0x08,
0xa6, 0x07, 0x78, 0x6e, 0xe6, 0x78, 0x8c, 0xf6, 0x78, 0x6e, 0xe6, 0x78, 0x8d, 0xf6, 0x7f, 0x01,
0xef, 0x25, 0xe0, 0x24, 0x4f, 0xf9, 0xc3, 0x78, 0x81, 0xe6, 0x97, 0x18, 0xe6, 0x19, 0x97, 0x50,
0x0a, 0x12, 0x0c, 0x82, 0x78, 0x80, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0x74, 0x6e, 0x2f, 0xf9, 0x78,
0x8c, 0xe6, 0xc3, 0x97, 0x50, 0x08, 0x74, 0x6e, 0x2f, 0xf8, 0xe6, 0x78, 0x8c, 0xf6, 0xef, 0x25,
0xe0, 0x24, 0x4f, 0xf9, 0xd3, 0x78, 0x83, 0xe6, 0x97, 0x18, 0xe6, 0x19, 0x97, 0x40, 0x0a, 0x12,
0x0c, 0x82, 0x78, 0x82, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0x74, 0x6e, 0x2f, 0xf9, 0x78, 0x8d, 0xe6,
0xd3, 0x97, 0x40, 0x08, 0x74, 0x6e, 0x2f, 0xf8, 0xe6, 0x78, 0x8d, 0xf6, 0x0f, 0xef, 0x64, 0x10,
0x70, 0x9e, 0xc3, 0x79, 0x81, 0xe7, 0x78, 0x83, 0x96, 0xff, 0x19, 0xe7, 0x18, 0x96, 0x78, 0x84,
0xf6, 0x08, 0xa6, 0x07, 0xc3, 0x79, 0x8c, 0xe7, 0x78, 0x8d, 0x96, 0x08, 0xf6, 0xd3, 0x79, 0x81,
0xe7, 0x78, 0x7f, 0x96, 0x19, 0xe7, 0x18, 0x96, 0x40, 0x05, 0x09, 0xe7, 0x08, 0x80, 0x06, 0xc3,
0x79, 0x7f, 0xe7, 0x78, 0x81, 0x96, 0xff, 0x19, 0xe7, 0x18, 0x96, 0xfe, 0x78, 0x86, 0xa6, 0x06,
0x08, 0xa6, 0x07, 0x79, 0x8c, 0xe7, 0xd3, 0x78, 0x8b, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, 0x80,
0x08, 0xc3, 0x79, 0x8b, 0xe7, 0x78, 0x8c, 0x96, 0xff, 0x78, 0x8f, 0xa6, 0x07, 0xe5, 0x1f, 0x64,
0x02, 0x70, 0x69, 0x90, 0x0e, 0x91, 0x93, 0xff, 0x18, 0xe6, 0xc3, 0x9f, 0x50, 0x72, 0x12, 0x0c,
0x4a, 0x12, 0x0c, 0x2f, 0x90, 0x0e, 0x8e, 0x12, 0x0c, 0x38, 0x78, 0x80, 0x12, 0x0c, 0x6b, 0x7b,
0x04, 0x12, 0x0c, 0x1d, 0xc3, 0x12, 0x06, 0x45, 0x50, 0x56, 0x90, 0x0e, 0x92, 0xe4, 0x93, 0xff,
0x78, 0x8f, 0xe6, 0x9f, 0x40, 0x02, 0x80, 0x11, 0x90, 0x0e, 0x90, 0xe4, 0x93, 0xff, 0xd3, 0x78,
0x89, 0xe6, 0x9f, 0x18, 0xe6, 0x94, 0x00, 0x40, 0x03, 0x75, 0x1f, 0x05, 0x12, 0x0c, 0x4a, 0x12,
0x0c, 0x2f, 0x90, 0x0e, 0x8f, 0x12, 0x0c, 0x38, 0x78, 0x7e, 0x12, 0x0c, 0x6b, 0x7b, 0x40, 0x12,
0x0c, 0x1d, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x18, 0x75, 0x1f, 0x05, 0x22, 0xe5, 0x1f, 0xb4, 0x05,
0x0f, 0xd2, 0x01, 0xc2, 0x02, 0xe4, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, 0x33, 0xd2, 0x36,
0x22, 0xef, 0x8d, 0xf0, 0xa4, 0xa8, 0xf0, 0xcf, 0x8c, 0xf0, 0xa4, 0x28, 0xce, 0x8d, 0xf0, 0xa4,
0x2e, 0xfe, 0x22, 0xbc, 0x00, 0x0b, 0xbe, 0x00, 0x29, 0xef, 0x8d, 0xf0, 0x84, 0xff, 0xad, 0xf0,
0x22, 0xe4, 0xcc, 0xf8, 0x75, 0xf0, 0x08, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xec, 0x33, 0xfc,
0xee, 0x9d, 0xec, 0x98, 0x40, 0x05, 0xfc, 0xee, 0x9d, 0xfe, 0x0f, 0xd5, 0xf0, 0xe9, 0xe4, 0xce,
0xfd, 0x22, 0xed, 0xf8, 0xf5, 0xf0, 0xee, 0x84, 0x20, 0xd2, 0x1c, 0xfe, 0xad, 0xf0, 0x75, 0xf0,
0x08, 0xef, 0x2f, 0xff, 0xed, 0x33, 0xfd, 0x40, 0x07, 0x98, 0x50, 0x06, 0xd5, 0xf0, 0xf2, 0x22,
0xc3, 0x98, 0xfd, 0x0f, 0xd5, 0xf0, 0xea, 0x22, 0xe8, 0x8f, 0xf0, 0xa4, 0xcc, 0x8b, 0xf0, 0xa4,
0x2c, 0xfc, 0xe9, 0x8e, 0xf0, 0xa4, 0x2c, 0xfc, 0x8a, 0xf0, 0xed, 0xa4, 0x2c, 0xfc, 0xea, 0x8e,
0xf0, 0xa4, 0xcd, 0xa8, 0xf0, 0x8b, 0xf0, 0xa4, 0x2d, 0xcc, 0x38, 0x25, 0xf0, 0xfd, 0xe9, 0x8f,
0xf0, 0xa4, 0x2c, 0xcd, 0x35, 0xf0, 0xfc, 0xeb, 0x8e, 0xf0, 0xa4, 0xfe, 0xa9, 0xf0, 0xeb, 0x8f,
0xf0, 0xa4, 0xcf, 0xc5, 0xf0, 0x2e, 0xcd, 0x39, 0xfe, 0xe4, 0x3c, 0xfc, 0xea, 0xa4, 0x2d, 0xce,
0x35, 0xf0, 0xfd, 0xe4, 0x3c, 0xfc, 0x22, 0x75, 0xf0, 0x08, 0x75, 0x82, 0x00, 0xef, 0x2f, 0xff,
0xee, 0x33, 0xfe, 0xcd, 0x33, 0xcd, 0xcc, 0x33, 0xcc, 0xc5, 0x82, 0x33, 0xc5, 0x82, 0x9b, 0xed,
0x9a, 0xec, 0x99, 0xe5, 0x82, 0x98, 0x40, 0x0c, 0xf5, 0x82, 0xee, 0x9b, 0xfe, 0xed, 0x9a, 0xfd,
0xec, 0x99, 0xfc, 0x0f, 0xd5, 0xf0, 0xd6, 0xe4, 0xce, 0xfb, 0xe4, 0xcd, 0xfa, 0xe4, 0xcc, 0xf9,
0xa8, 0x82, 0x22, 0xb8, 0x00, 0xc1, 0xb9, 0x00, 0x59, 0xba, 0x00, 0x2d, 0xec, 0x8b, 0xf0, 0x84,
0xcf, 0xce, 0xcd, 0xfc, 0xe5, 0xf0, 0xcb, 0xf9, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe,
0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xeb, 0x33, 0xfb, 0x10, 0xd7, 0x03, 0x99, 0x40, 0x04, 0xeb,
0x99, 0xfb, 0x0f, 0xd8, 0xe5, 0xe4, 0xf9, 0xfa, 0x22, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33,
0xfe, 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xc9, 0x33, 0xc9, 0x10, 0xd7, 0x05, 0x9b, 0xe9, 0x9a,
0x40, 0x07, 0xec, 0x9b, 0xfc, 0xe9, 0x9a, 0xf9, 0x0f, 0xd8, 0xe0, 0xe4, 0xc9, 0xfa, 0xe4, 0xcc,
0xfb, 0x22, 0x75, 0xf0, 0x10, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xcc, 0x33,
0xcc, 0xc8, 0x33, 0xc8, 0x10, 0xd7, 0x07, 0x9b, 0xec, 0x9a, 0xe8, 0x99, 0x40, 0x0a, 0xed, 0x9b,
0xfd, 0xec, 0x9a, 0xfc, 0xe8, 0x99, 0xf8, 0x0f, 0xd5, 0xf0, 0xda, 0xe4, 0xcd, 0xfb, 0xe4, 0xcc,
0xfa, 0xe4, 0xc8, 0xf9, 0x22, 0xeb, 0x9f, 0xf5, 0xf0, 0xea, 0x9e, 0x42, 0xf0, 0xe9, 0x9d, 0x42,
0xf0, 0xe8, 0x9c, 0x45, 0xf0, 0x22, 0xe8, 0x60, 0x0f, 0xec, 0xc3, 0x13, 0xfc, 0xed, 0x13, 0xfd,
0xee, 0x13, 0xfe, 0xef, 0x13, 0xff, 0xd8, 0xf1, 0x22, 0xe8, 0x60, 0x0f, 0xef, 0xc3, 0x33, 0xff,
0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xd8, 0xf1, 0x22, 0xe4, 0x93, 0xfc, 0x74,
0x01, 0x93, 0xfd, 0x74, 0x02, 0x93, 0xfe, 0x74, 0x03, 0x93, 0xff, 0x22, 0xe6, 0xfb, 0x08, 0xe6,
0xf9, 0x08, 0xe6, 0xfa, 0x08, 0xe6, 0xcb, 0xf8, 0x22, 0xec, 0xf6, 0x08, 0xed, 0xf6, 0x08, 0xee,
0xf6, 0x08, 0xef, 0xf6, 0x22, 0xa4, 0x25, 0x82, 0xf5, 0x82, 0xe5, 0xf0, 0x35, 0x83, 0xf5, 0x83,
0x22, 0xd0, 0x83, 0xd0, 0x82, 0xf8, 0xe4, 0x93, 0x70, 0x12, 0x74, 0x01, 0x93, 0x70, 0x0d, 0xa3,
0xa3, 0x93, 0xf8, 0x74, 0x01, 0x93, 0xf5, 0x82, 0x88, 0x83, 0xe4, 0x73, 0x74, 0x02, 0x93, 0x68,
0x60, 0xef, 0xa3, 0xa3, 0xa3, 0x80, 0xdf, 0x90, 0x38, 0x04, 0x78, 0x52, 0x12, 0x0b, 0xfd, 0x90,
0x38, 0x00, 0xe0, 0xfe, 0xa3, 0xe0, 0xfd, 0xed, 0xff, 0xc3, 0x12, 0x0b, 0x9e, 0x90, 0x38, 0x10,
0x12, 0x0b, 0x92, 0x90, 0x38, 0x06, 0x78, 0x54, 0x12, 0x0b, 0xfd, 0x90, 0x38, 0x02, 0xe0, 0xfe,
0xa3, 0xe0, 0xfd, 0xed, 0xff, 0xc3, 0x12, 0x0b, 0x9e, 0x90, 0x38, 0x12, 0x12, 0x0b, 0x92, 0xa3,
0xe0, 0xb4, 0x31, 0x07, 0x78, 0x52, 0x79, 0x52, 0x12, 0x0c, 0x13, 0x90, 0x38, 0x14, 0xe0, 0xb4,
0x71, 0x15, 0x78, 0x52, 0xe6, 0xfe, 0x08, 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8,
0xf9, 0x79, 0x53, 0xf7, 0xee, 0x19, 0xf7, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x31, 0x07, 0x78, 0x54,
0x79, 0x54, 0x12, 0x0c, 0x13, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x71, 0x15, 0x78, 0x54, 0xe6, 0xfe,
0x08, 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8, 0xf9, 0x79, 0x55, 0xf7, 0xee, 0x19,
0xf7, 0x79, 0x52, 0x12, 0x0b, 0xd9, 0x09, 0x12, 0x0b, 0xd9, 0xaf, 0x47, 0x12, 0x0b, 0xb2, 0xe5,
0x44, 0xfb, 0x7a, 0x00, 0xfd, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x5a, 0xa6, 0x06, 0x08, 0xa6,
0x07, 0xaf, 0x45, 0x12, 0x0b, 0xb2, 0xad, 0x03, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x56, 0xa6,
0x06, 0x08, 0xa6, 0x07, 0xaf, 0x48, 0x78, 0x54, 0x12, 0x0b, 0xb4, 0xe5, 0x43, 0xfb, 0xfd, 0x7c,
0x00, 0x12, 0x04, 0xd3, 0x78, 0x5c, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xaf, 0x46, 0x7e, 0x00, 0x78,
0x54, 0x12, 0x0b, 0xb6, 0xad, 0x03, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x58, 0xa6, 0x06, 0x08,
0xa6, 0x07, 0xc3, 0x78, 0x5b, 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76, 0x00,
0x08, 0x76, 0x08, 0xc3, 0x78, 0x5d, 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76,
0x00, 0x08, 0x76, 0x08, 0x78, 0x5a, 0x12, 0x0b, 0xc6, 0xff, 0xd3, 0x78, 0x57, 0xe6, 0x9f, 0x18,
0xe6, 0x9e, 0x40, 0x0e, 0x78, 0x5a, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x57, 0x12, 0x0c, 0x08,
0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, 0x78, 0x5e, 0x12, 0x0b, 0xbe, 0xff, 0xd3, 0x78, 0x59, 0xe6,
0x9f, 0x18, 0xe6, 0x9e, 0x40, 0x0e, 0x78, 0x5c, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x59, 0x12,
0x0c, 0x08, 0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, 0xe4, 0xfc, 0xfd, 0x78, 0x62, 0x12, 0x06, 0x99,
0x78, 0x5a, 0x12, 0x0b, 0xc6, 0x78, 0x57, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0x78, 0x66, 0x12,
0x0b, 0xbe, 0x78, 0x59, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0xe4, 0xfc, 0xfd, 0x78, 0x6a, 0x12,
0x06, 0x99, 0x12, 0x0b, 0xce, 0x78, 0x66, 0x12, 0x06, 0x8c, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x08,
0x12, 0x0b, 0xce, 0x78, 0x66, 0x12, 0x06, 0x99, 0x78, 0x54, 0x12, 0x0b, 0xd0, 0x78, 0x6a, 0x12,
0x06, 0x8c, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x0a, 0x78, 0x54, 0x12, 0x0b, 0xd0, 0x78, 0x6a, 0x12,
0x06, 0x99, 0x78, 0x61, 0xe6, 0x90, 0x60, 0x01, 0xf0, 0x78, 0x65, 0xe6, 0xa3, 0xf0, 0x78, 0x69,
0xe6, 0xa3, 0xf0, 0x78, 0x55, 0xe6, 0xa3, 0xf0, 0x7d, 0x01, 0x78, 0x61, 0x12, 0x0b, 0xe9, 0x24,
0x01, 0x12, 0x0b, 0xa6, 0x78, 0x65, 0x12, 0x0b, 0xe9, 0x24, 0x02, 0x12, 0x0b, 0xa6, 0x78, 0x69,
0x12, 0x0b, 0xe9, 0x24, 0x03, 0x12, 0x0b, 0xa6, 0x78, 0x6d, 0x12, 0x0b, 0xe9, 0x24, 0x04, 0x12,
0x0b, 0xa6, 0x0d, 0xbd, 0x05, 0xd4, 0xc2, 0x0e, 0xc2, 0x06, 0x22, 0x85, 0x08, 0x41, 0x90, 0x30,
0x24, 0xe0, 0xf5, 0x3d, 0xa3, 0xe0, 0xf5, 0x3e, 0xa3, 0xe0, 0xf5, 0x3f, 0xa3, 0xe0, 0xf5, 0x40,
0xa3, 0xe0, 0xf5, 0x3c, 0xd2, 0x34, 0xe5, 0x41, 0x12, 0x06, 0xb1, 0x09, 0x31, 0x03, 0x09, 0x35,
0x04, 0x09, 0x3b, 0x05, 0x09, 0x3e, 0x06, 0x09, 0x41, 0x07, 0x09, 0x4a, 0x08, 0x09, 0x5b, 0x12,
0x09, 0x73, 0x18, 0x09, 0x89, 0x19, 0x09, 0x5e, 0x1a, 0x09, 0x6a, 0x1b, 0x09, 0xad, 0x80, 0x09,
0xb2, 0x81, 0x0a, 0x1d, 0x8f, 0x0a, 0x09, 0x90, 0x0a, 0x1d, 0x91, 0x0a, 0x1d, 0x92, 0x0a, 0x1d,
0x93, 0x0a, 0x1d, 0x94, 0x0a, 0x1d, 0x98, 0x0a, 0x17, 0x9f, 0x0a, 0x1a, 0xec, 0x00, 0x00, 0x0a,
0x38, 0x12, 0x0f, 0x74, 0x22, 0x12, 0x0f, 0x74, 0xd2, 0x03, 0x22, 0xd2, 0x03, 0x22, 0xc2, 0x03,
0x22, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x02, 0x0a, 0x1d, 0xc2, 0x01, 0xc2, 0x02, 0xc2, 0x03,
0x12, 0x0d, 0x0d, 0x75, 0x1e, 0x70, 0xd2, 0x35, 0x02, 0x0a, 0x1d, 0x02, 0x0a, 0x04, 0x85, 0x40,
0x4a, 0x85, 0x3c, 0x4b, 0x12, 0x0a, 0xff, 0x02, 0x0a, 0x1d, 0x85, 0x4a, 0x40, 0x85, 0x4b, 0x3c,
0x02, 0x0a, 0x1d, 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x85, 0x40, 0x31, 0x85, 0x3f, 0x30, 0x85, 0x3e,
0x2f, 0x85, 0x3d, 0x2e, 0x12, 0x0f, 0x46, 0x80, 0x1f, 0x75, 0x22, 0x00, 0x75, 0x23, 0x01, 0x74,
0xff, 0xf5, 0x2d, 0xf5, 0x2c, 0xf5, 0x2b, 0xf5, 0x2a, 0x12, 0x0f, 0x46, 0x85, 0x2d, 0x40, 0x85,
0x2c, 0x3f, 0x85, 0x2b, 0x3e, 0x85, 0x2a, 0x3d, 0xe4, 0xf5, 0x3c, 0x80, 0x70, 0x12, 0x0f, 0x16,
0x80, 0x6b, 0x85, 0x3d, 0x45, 0x85, 0x3e, 0x46, 0xe5, 0x47, 0xc3, 0x13, 0xff, 0xe5, 0x45, 0xc3,
0x9f, 0x50, 0x02, 0x8f, 0x45, 0xe5, 0x48, 0xc3, 0x13, 0xff, 0xe5, 0x46, 0xc3, 0x9f, 0x50, 0x02,
0x8f, 0x46, 0xe5, 0x47, 0xc3, 0x13, 0xff, 0xfd, 0xe5, 0x45, 0x2d, 0xfd, 0xe4, 0x33, 0xfc, 0xe5,
0x44, 0x12, 0x0f, 0x90, 0x40, 0x05, 0xe5, 0x44, 0x9f, 0xf5, 0x45, 0xe5, 0x48, 0xc3, 0x13, 0xff,
0xfd, 0xe5, 0x46, 0x2d, 0xfd, 0xe4, 0x33, 0xfc, 0xe5, 0x43, 0x12, 0x0f, 0x90, 0x40, 0x05, 0xe5,
0x43, 0x9f, 0xf5, 0x46, 0x12, 0x06, 0xd7, 0x80, 0x14, 0x85, 0x40, 0x48, 0x85, 0x3f, 0x47, 0x85,
0x3e, 0x46, 0x85, 0x3d, 0x45, 0x80, 0x06, 0x02, 0x06, 0xd7, 0x12, 0x0d, 0x7e, 0x90, 0x30, 0x24,
0xe5, 0x3d, 0xf0, 0xa3, 0xe5, 0x3e, 0xf0, 0xa3, 0xe5, 0x3f, 0xf0, 0xa3, 0xe5, 0x40, 0xf0, 0xa3,
0xe5, 0x3c, 0xf0, 0x90, 0x30, 0x23, 0xe4, 0xf0, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, 0xc0,
0xd0, 0x90, 0x3f, 0x0c, 0xe0, 0xf5, 0x32, 0xe5, 0x32, 0x30, 0xe3, 0x74, 0x30, 0x36, 0x66, 0x90,
0x60, 0x19, 0xe0, 0xf5, 0x0a, 0xa3, 0xe0, 0xf5, 0x0b, 0x90, 0x60, 0x1d, 0xe0, 0xf5, 0x14, 0xa3,
0xe0, 0xf5, 0x15, 0x90, 0x60, 0x21, 0xe0, 0xf5, 0x0c, 0xa3, 0xe0, 0xf5, 0x0d, 0x90, 0x60, 0x29,
0xe0, 0xf5, 0x0e, 0xa3, 0xe0, 0xf5, 0x0f, 0x90, 0x60, 0x31, 0xe0, 0xf5, 0x10, 0xa3, 0xe0, 0xf5,
0x11, 0x90, 0x60, 0x39, 0xe0, 0xf5, 0x12, 0xa3, 0xe0, 0xf5, 0x13, 0x30, 0x01, 0x06, 0x30, 0x33,
0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x09, 0x30, 0x02, 0x06, 0x30, 0x33, 0x03, 0xd3, 0x80, 0x01,
0xc3, 0x92, 0x0a, 0x30, 0x33, 0x0c, 0x30, 0x03, 0x09, 0x20, 0x02, 0x06, 0x20, 0x01, 0x03, 0xd3,
0x80, 0x01, 0xc3, 0x92, 0x0b, 0x90, 0x30, 0x01, 0xe0, 0x44, 0x40, 0xf0, 0xe0, 0x54, 0xbf, 0xf0,
0xe5, 0x32, 0x30, 0xe1, 0x14, 0x30, 0x34, 0x11, 0x90, 0x30, 0x22, 0xe0, 0xf5, 0x08, 0xe4, 0xf0,
0x30, 0x00, 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x08, 0xe5, 0x32, 0x30, 0xe5, 0x12, 0x90, 0x56,
0xa1, 0xe0, 0xf5, 0x09, 0x30, 0x31, 0x09, 0x30, 0x05, 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x0d,
0x90, 0x3f, 0x0c, 0xe5, 0x32, 0xf0, 0xd0, 0xd0, 0xd0, 0x82, 0xd0, 0x83, 0xd0, 0xe0, 0x32, 0x90,
0x0e, 0x7e, 0xe4, 0x93, 0xfe, 0x74, 0x01, 0x93, 0xff, 0xc3, 0x90, 0x0e, 0x7c, 0x74, 0x01, 0x93,
0x9f, 0xff, 0xe4, 0x93, 0x9e, 0xfe, 0xe4, 0x8f, 0x3b, 0x8e, 0x3a, 0xf5, 0x39, 0xf5, 0x38, 0xab,
0x3b, 0xaa, 0x3a, 0xa9, 0x39, 0xa8, 0x38, 0xaf, 0x4b, 0xfc, 0xfd, 0xfe, 0x12, 0x05, 0x28, 0x12,
0x0d, 0xe1, 0xe4, 0x7b, 0xff, 0xfa, 0xf9, 0xf8, 0x12, 0x05, 0xb3, 0x12, 0x0d, 0xe1, 0x90, 0x0e,
0x69, 0xe4, 0x12, 0x0d, 0xf6, 0x12, 0x0d, 0xe1, 0xe4, 0x85, 0x4a, 0x37, 0xf5, 0x36, 0xf5, 0x35,
0xf5, 0x34, 0xaf, 0x37, 0xae, 0x36, 0xad, 0x35, 0xac, 0x34, 0xa3, 0x12, 0x0d, 0xf6, 0x8f, 0x37,
0x8e, 0x36, 0x8d, 0x35, 0x8c, 0x34, 0xe5, 0x3b, 0x45, 0x37, 0xf5, 0x3b, 0xe5, 0x3a, 0x45, 0x36,
0xf5, 0x3a, 0xe5, 0x39, 0x45, 0x35, 0xf5, 0x39, 0xe5, 0x38, 0x45, 0x34, 0xf5, 0x38, 0xe4, 0xf5,
0x22, 0xf5, 0x23, 0x85, 0x3b, 0x31, 0x85, 0x3a, 0x30, 0x85, 0x39, 0x2f, 0x85, 0x38, 0x2e, 0x02,
0x0f, 0x46, 0xe0, 0xa3, 0xe0, 0x75, 0xf0, 0x02, 0xa4, 0xff, 0xae, 0xf0, 0xc3, 0x08, 0xe6, 0x9f,
0xf6, 0x18, 0xe6, 0x9e, 0xf6, 0x22, 0xff, 0xe5, 0xf0, 0x34, 0x60, 0x8f, 0x82, 0xf5, 0x83, 0xec,
0xf0, 0x22, 0x78, 0x52, 0x7e, 0x00, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x02, 0x04, 0xc1, 0xe4, 0xfc,
0xfd, 0x12, 0x06, 0x99, 0x78, 0x5c, 0xe6, 0xc3, 0x13, 0xfe, 0x08, 0xe6, 0x13, 0x22, 0x78, 0x52,
0xe6, 0xfe, 0x08, 0xe6, 0xff, 0xe4, 0xfc, 0xfd, 0x22, 0xe7, 0xc4, 0xf8, 0x54, 0xf0, 0xc8, 0x68,
0xf7, 0x09, 0xe7, 0xc4, 0x54, 0x0f, 0x48, 0xf7, 0x22, 0xe6, 0xfc, 0xed, 0x75, 0xf0, 0x04, 0xa4,
0x22, 0x12, 0x06, 0x7c, 0x8f, 0x48, 0x8e, 0x47, 0x8d, 0x46, 0x8c, 0x45, 0x22, 0xe0, 0xfe, 0xa3,
0xe0, 0xfd, 0xee, 0xf6, 0xed, 0x08, 0xf6, 0x22, 0x13, 0xff, 0xc3, 0xe6, 0x9f, 0xff, 0x18, 0xe6,
0x9e, 0xfe, 0x22, 0xe6, 0xc3, 0x13, 0xf7, 0x08, 0xe6, 0x13, 0x09, 0xf7, 0x22, 0xad, 0x39, 0xac,
0x38, 0xfa, 0xf9, 0xf8, 0x12, 0x05, 0x28, 0x8f, 0x3b, 0x8e, 0x3a, 0x8d, 0x39, 0x8c, 0x38, 0xab,
0x37, 0xaa, 0x36, 0xa9, 0x35, 0xa8, 0x34, 0x22, 0x93, 0xff, 0xe4, 0xfc, 0xfd, 0xfe, 0x12, 0x05,
0x28, 0x8f, 0x37, 0x8e, 0x36, 0x8d, 0x35, 0x8c, 0x34, 0x22, 0x78, 0x84, 0xe6, 0xfe, 0x08, 0xe6,
0xff, 0xe4, 0x8f, 0x37, 0x8e, 0x36, 0xf5, 0x35, 0xf5, 0x34, 0x22, 0x90, 0x0e, 0x8c, 0xe4, 0x93,
0x25, 0xe0, 0x24, 0x0a, 0xf8, 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x22, 0xe6, 0xfe, 0x08, 0xe6, 0xff,
0xe4, 0x8f, 0x3b, 0x8e, 0x3a, 0xf5, 0x39, 0xf5, 0x38, 0x22, 0x78, 0x4e, 0xe6, 0xfe, 0x08, 0xe6,
0xff, 0x22, 0xef, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x22, 0x78, 0x89,
0xef, 0x26, 0xf6, 0x18, 0xe4, 0x36, 0xf6, 0x22, 0x75, 0x89, 0x03, 0x75, 0xa8, 0x01, 0x75, 0xb8,
0x04, 0x75, 0x34, 0xff, 0x75, 0x35, 0x0e, 0x75, 0x36, 0x15, 0x75, 0x37, 0x0d, 0x12, 0x0e, 0x9a,
0x12, 0x00, 0x09, 0x12, 0x0f, 0x16, 0x12, 0x00, 0x06, 0xd2, 0x00, 0xd2, 0x34, 0xd2, 0xaf, 0x75,
0x34, 0xff, 0x75, 0x35, 0x0e, 0x75, 0x36, 0x49, 0x75, 0x37, 0x03, 0x12, 0x0e, 0x9a, 0x30, 0x08,
0x09, 0xc2, 0x34, 0x12, 0x08, 0xcb, 0xc2, 0x08, 0xd2, 0x34, 0x30, 0x0b, 0x09, 0xc2, 0x36, 0x12,
0x02, 0x6c, 0xc2, 0x0b, 0xd2, 0x36, 0x30, 0x09, 0x09, 0xc2, 0x36, 0x12, 0x00, 0x0e, 0xc2, 0x09,
0xd2, 0x36, 0x30, 0x0e, 0x03, 0x12, 0x06, 0xd7, 0x30, 0x35, 0xd3, 0x90, 0x30, 0x29, 0xe5, 0x1e,
0xf0, 0xb4, 0x10, 0x05, 0x90, 0x30, 0x23, 0xe4, 0xf0, 0xc2, 0x35, 0x80, 0xc1, 0xe4, 0xf5, 0x4b,
0x90, 0x0e, 0x7a, 0x93, 0xff, 0xe4, 0x8f, 0x37, 0xf5, 0x36, 0xf5, 0x35, 0xf5, 0x34, 0xaf, 0x37,
0xae, 0x36, 0xad, 0x35, 0xac, 0x34, 0x90, 0x0e, 0x6a, 0x12, 0x0d, 0xf6, 0x8f, 0x37, 0x8e, 0x36,
0x8d, 0x35, 0x8c, 0x34, 0x90, 0x0e, 0x72, 0x12, 0x06, 0x7c, 0xef, 0x45, 0x37, 0xf5, 0x37, 0xee,
0x45, 0x36, 0xf5, 0x36, 0xed, 0x45, 0x35, 0xf5, 0x35, 0xec, 0x45, 0x34, 0xf5, 0x34, 0xe4, 0xf5,
0x22, 0xf5, 0x23, 0x85, 0x37, 0x31, 0x85, 0x36, 0x30, 0x85, 0x35, 0x2f, 0x85, 0x34, 0x2e, 0x12,
0x0f, 0x46, 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x90, 0x0e, 0x72, 0x12, 0x0d, 0xea, 0x12, 0x0f, 0x46,
0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x90, 0x0e, 0x6e, 0x12, 0x0d, 0xea, 0x02, 0x0f, 0x46, 0xe5, 0x40,
0x24, 0xf2, 0xf5, 0x37, 0xe5, 0x3f, 0x34, 0x43, 0xf5, 0x36, 0xe5, 0x3e, 0x34, 0xa2, 0xf5, 0x35,
0xe5, 0x3d, 0x34, 0x28, 0xf5, 0x34, 0xe5, 0x37, 0xff, 0xe4, 0xfe, 0xfd, 0xfc, 0x78, 0x18, 0x12,
0x06, 0x69, 0x8f, 0x40, 0x8e, 0x3f, 0x8d, 0x3e, 0x8c, 0x3d, 0xe5, 0x37, 0x54, 0xa0, 0xff, 0xe5,
0x36, 0xfe, 0xe4, 0xfd, 0xfc, 0x78, 0x07, 0x12, 0x06, 0x56, 0x78, 0x10, 0x12, 0x0f, 0x9a, 0xe4,
0xff, 0xfe, 0xe5, 0x35, 0xfd, 0xe4, 0xfc, 0x78, 0x0e, 0x12, 0x06, 0x56, 0x12, 0x0f, 0x9d, 0xe4,
0xff, 0xfe, 0xfd, 0xe5, 0x34, 0xfc, 0x78, 0x18, 0x12, 0x06, 0x56, 0x78, 0x08, 0x12, 0x0f, 0x9a,
0x22, 0x8f, 0x3b, 0x8e, 0x3a, 0x8d, 0x39, 0x8c, 0x38, 0x22, 0x12, 0x06, 0x7c, 0x8f, 0x31, 0x8e,
0x30, 0x8d, 0x2f, 0x8c, 0x2e, 0x22, 0x93, 0xf9, 0xf8, 0x02, 0x06, 0x69, 0x00, 0x00, 0x00, 0x00,
0x12, 0x01, 0x17, 0x08, 0x31, 0x15, 0x53, 0x54, 0x44, 0x20, 0x20, 0x20, 0x20, 0x20, 0x13, 0x01,
0x10, 0x01, 0x56, 0x40, 0x1a, 0x30, 0x29, 0x7e, 0x00, 0x30, 0x04, 0x20, 0xdf, 0x30, 0x05, 0x40,
0xbf, 0x50, 0x03, 0x00, 0xfd, 0x50, 0x27, 0x01, 0xfe, 0x60, 0x00, 0x11, 0x00, 0x3f, 0x05, 0x30,
0x00, 0x3f, 0x06, 0x22, 0x00, 0x3f, 0x01, 0x2a, 0x00, 0x3f, 0x02, 0x00, 0x00, 0x36, 0x06, 0x07,
0x00, 0x3f, 0x0b, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x40, 0xbf, 0x30, 0x01, 0x00,
0xbf, 0x30, 0x29, 0x70, 0x00, 0x3a, 0x00, 0x00, 0xff, 0x3a, 0x00, 0x00, 0xff, 0x36, 0x03, 0x36,
0x02, 0x41, 0x44, 0x58, 0x20, 0x18, 0x10, 0x0a, 0x04, 0x04, 0x00, 0x03, 0xff, 0x64, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x06, 0x00, 0x03, 0x51, 0x00, 0x7a,
0x50, 0x3c, 0x28, 0x1e, 0x10, 0x10, 0x50, 0x2d, 0x28, 0x16, 0x10, 0x10, 0x02, 0x00, 0x10, 0x0c,
0x10, 0x04, 0x0c, 0x6e, 0x06, 0x05, 0x00, 0xa5, 0x5a, 0x00, 0xae, 0x35, 0xaf, 0x36, 0xe4, 0xfd,
0xed, 0xc3, 0x95, 0x37, 0x50, 0x33, 0x12, 0x0f, 0xe2, 0xe4, 0x93, 0xf5, 0x38, 0x74, 0x01, 0x93,
0xf5, 0x39, 0x45, 0x38, 0x60, 0x23, 0x85, 0x39, 0x82, 0x85, 0x38, 0x83, 0xe0, 0xfc, 0x12, 0x0f,
0xe2, 0x74, 0x03, 0x93, 0x52, 0x04, 0x12, 0x0f, 0xe2, 0x74, 0x02, 0x93, 0x42, 0x04, 0x85, 0x39,
0x82, 0x85, 0x38, 0x83, 0xec, 0xf0, 0x0d, 0x80, 0xc7, 0x22, 0x78, 0xbe, 0xe6, 0xd3, 0x08, 0xff,
0xe6, 0x64, 0x80, 0xf8, 0xef, 0x64, 0x80, 0x98, 0x22, 0x93, 0xff, 0x7e, 0x00, 0xe6, 0xfc, 0x08,
0xe6, 0xfd, 0x12, 0x04, 0xc1, 0x78, 0xc1, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0xd3, 0xef, 0x9d, 0xee,
0x9c, 0x22, 0x78, 0xbd, 0xd3, 0xe6, 0x64, 0x80, 0x94, 0x80, 0x22, 0x25, 0xe0, 0x24, 0x0a, 0xf8,
0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x22, 0xe5, 0x3c, 0xd3, 0x94, 0x00, 0x40, 0x0b, 0x90, 0x0e, 0x88,
0x12, 0x0b, 0xf1, 0x90, 0x0e, 0x86, 0x80, 0x09, 0x90, 0x0e, 0x82, 0x12, 0x0b, 0xf1, 0x90, 0x0e,
0x80, 0xe4, 0x93, 0xf5, 0x44, 0xa3, 0xe4, 0x93, 0xf5, 0x43, 0xd2, 0x06, 0x30, 0x06, 0x03, 0xd3,
0x80, 0x01, 0xc3, 0x92, 0x0e, 0x22, 0xa2, 0xaf, 0x92, 0x32, 0xc2, 0xaf, 0xe5, 0x23, 0x45, 0x22,
0x90, 0x0e, 0x5d, 0x60, 0x0e, 0x12, 0x0f, 0xcb, 0xe0, 0xf5, 0x2c, 0x12, 0x0f, 0xc8, 0xe0, 0xf5,
0x2d, 0x80, 0x0c, 0x12, 0x0f, 0xcb, 0xe5, 0x30, 0xf0, 0x12, 0x0f, 0xc8, 0xe5, 0x31, 0xf0, 0xa2,
0x32, 0x92, 0xaf, 0x22, 0xd2, 0x01, 0xc2, 0x02, 0xe4, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2,
0x33, 0xd2, 0x36, 0xd2, 0x01, 0xc2, 0x02, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, 0x33, 0x22,
0xfb, 0xd3, 0xed, 0x9b, 0x74, 0x80, 0xf8, 0x6c, 0x98, 0x22, 0x12, 0x06, 0x69, 0xe5, 0x40, 0x2f,
0xf5, 0x40, 0xe5, 0x3f, 0x3e, 0xf5, 0x3f, 0xe5, 0x3e, 0x3d, 0xf5, 0x3e, 0xe5, 0x3d, 0x3c, 0xf5,
0x3d, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, 0x90, 0x3f, 0x0d, 0xe0, 0xf5, 0x33, 0xe5, 0x33,
0xf0, 0xd0, 0x82, 0xd0, 0x83, 0xd0, 0xe0, 0x32, 0x90, 0x0e, 0x5f, 0xe4, 0x93, 0xfe, 0x74, 0x01,
0x93, 0xf5, 0x82, 0x8e, 0x83, 0x22, 0x78, 0x7f, 0xe4, 0xf6, 0xd8, 0xfd, 0x75, 0x81, 0xcd, 0x02,
0x0c, 0x98, 0x8f, 0x82, 0x8e, 0x83, 0x75, 0xf0, 0x04, 0xed, 0x02, 0x06, 0xa5
};

#endif //__DCMI_OV2640_CFG_H

最后安排一下main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SPI_LCD_Init();      	// 液晶屏以及SPI初始化 
key_init(); // 按键初始化
DCMI_OV5640_Init(); // DCMI以及OV5640初始化

USB_init();
jpeg_init();

OV5640_AF_Download_Firmware(); // 写入自动对焦固件
OV5640_AF_Trigger_Constant(); // 自动对焦 ,持续 触发,当OV5640检测到当前画面不在焦点时,会一直触发对焦

OV5640_AF_Download_Firmware(); // 写入自动对焦固件
OV5640_AF_Trigger_Constant(); // 自动对焦 ,持续 触发,当OV5640检测到当前画面不在焦点时,会一直触发对焦
// OV5640_AF_Trigger_Single(); // 自动对焦 ,触发 单次

// 120度和160度的广角镜头默认的方向和带自动对焦的镜头不一样,用户可以根据实际去调整
// OV5640_Set_Vertical_Flip( OV5640_Disable ); // 取消垂直翻转
// OV5640_Set_Horizontal_Mirror( OV5640_Enable ); // 水平镜像

OV5640_DMA_Transmit_Continuous(Camera_Buffer, Display_BufferSize); // 启动DMA连续传输

OV5640_Set_Pixformat(Pixformat_RGB565); // 设置图像输出格式

再利用线性同余算法写一个随机数:

1
#define random (214013*HAL_GetTick() +2531011)>>16&((1<<15)-1);

如何判断相机是否捕获到一帧图像呢?答案如下:

1
2
3
4
5
6
7
8
if ( OV5640_FrameState == 1 )	// 采集到了一帧图像
{
OV5640_FrameState = 0; // 清零标志位
LCD_CopyBuffer(0, 0, Display_Width, Display_Height, (uint16_t *)Camera_Buffer); // 将图像数据复制到屏幕
// FatFs_GetVolume(); // 计算设备容量
// LCD_DisplayNumber(86,120,SD_CardCapacity , 2); // 显示SD卡容量
LED1_Toggle;
}

拍照部分也很好写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void TakePhoto(){
// 拍照并保存到SD卡
// 1. 拍照
if ( OV5640_FrameState == 1 ) // 采集到了一帧图像
{
OV5640_FrameState = 0;
char filename[20];
sprintf(filename, "0:%d.jpg", random);
UINT t;
FRESULT retSD = f_mount(&SDFatFS, SDPath, 1);
f_open(&SDFile, filename, FA_CREATE_ALWAYS | FA_WRITE);
retSD = f_write(&SDFile, (uint16_t*) Camera_Buffer, Display_Width * Display_Height, &t);
printf("size = %d ,retSD = %d\r\n",t, retSD);
HAL_Delay(1000);
printf("1");
f_close(&SDFile);
}
}

但是为了同时实现摄像,录像,稳定,这里使用树莓派zero的1500万像素的相机,这样就可以高清无损的录像了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import serial
import cv2
import random

# 开启摄像头
cap = cv2.VideoCapture(0)
# 开启ttyAMA0串口
ser = serial.Serial("/dev/ttyAMA0", 115200)

def savePhoto():
frame = cap.read()[1]
cv2.imwrite("photo.jpg", random.randint(1, 65535), frame)
return 3

def saveVideo(toSave):
while toSave == 1:
out = cv2.VideoWriter('output.avi', cv2.VideoWriter_fourcc(*'MJPG'), 20.0, (640, 480))
ret, frame = cap.read()
out.write(frame)
pass
return 4

def readData():
# 获取字符缓冲区的大小
recv_num = ser.inWaiting()
# 读取数据
recv_buffer = ser.read(recv_num)
# 返回所有的值
return recv_buffer

def main():
data = readData()
key = 0
if data == "1":
key = savePhoto()
elif data == "2":
key = saveVideo(1)
elif data == "0":
key = saveVideo(0)
else:
pass
ser.write(key)
ser.flushInput()
ser.flushOutput()


if __name__ == "__main__":
while True:
main()

总结

本次项目不难,咱已经处了一年了,所以本次项目的技术力相对来讲比较高,下期博客预定RTOS。本次项目用一个大容量锂电池:

5

测试显示如下:


送个小纪念品吧
https://blog.minloha.cn/posts/230411a55b968c2024010405.html
作者
Minloha
发布于
2024年1月4日
更新于
2024年9月15日
许可协议