-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.cpp.bak
1339 lines (1187 loc) · 44.8 KB
/
main.cpp.bak
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
/*
此项目模仿自稚晖君暂未开源的 MonoUI,用于实现类似 UltraLink 的丝滑界面
有四个版本:
* 分辨率 128 * 128 :主菜单,列表和关于本机页基本还原了 UltraLink 的界面和动态效果,电压测量页为原创设计
* 分辨率 128 * 64 :主菜单模仿 UltraLink 重新设计,去掉了小标题,电压测量页重新设计,关于本机页面改为列表,列表适配了该分辨率
* 分辨率 128 * 32 :在 128 * 64 分辨率的基础上,主界面只保留图标,电压测量页重新设计
* 通用版本:仅保留列表,主菜单也改为列表,删除电压测量页和与列表无关的动画(保留弹窗效果),经过简单修改可以适配任何分辨率,任何行高度的情况
WouoUI v2 功能:
* 全部使用非线性的平滑缓动动画,包括列表,弹窗,甚至进度条
* 优化平滑动画算法到只有两行,分类别定义平滑权重,并且每个权重值都分别可调
* 可以打断的非线性动画,当前动画未结束但下一次动画已经被触发时,动画可以自然过渡
* 非通用版本分别适配了类似 UltraLink 主菜单的磁贴界面(因为让我想起 WP7 的 Metron 风格,所以称之为磁贴)
* 通用版本仅保留列表类界面,经过简单修改可以适配所有分辨率的屏幕,包括屏幕内行数不是整数的情况
* 列表菜单,列表可以无限延长
* 列表文字选择框,选择框可根据选择的字符串长度自动伸缩,进入菜单时从列表开头从长度 0 展开,转到上一级列表时,长度和纵坐标平滑移动到上一级选择的位置
* 列表单选框,储存数据时也储存该值在列表中所在的位置,展开列表时根据每行开头的字符判断是否绘制外框,再根据位置数据判断是否绘制里面的点
* 列表多选框,储存数据的数组跟多选框列表的行数对应,不要求连续排列,展开列表时根据每行开头的字符判断是否绘制外框,再根据行数对应的储存数据位置的数值是否为1判断是否绘制里面的点
* 列表显示数值,与多选框原理相似,但不涉及修改操作
* 列表展开动画,初始化列表时,可以选择列表从头开始展开,或者从上次选中的位置展开
* 图标展开动画,初始化磁贴类界面时,可以选择图标从头开始展开,或者从上次选中的位置展开
* 弹出窗口,实现了窗口弹出的动画效果,可以自定义最大值,最小值,步进值,需要修改的参数等,窗口独立运行,调用非常简单
* 弹出窗口背景虚化可选项,背景虚化会产生卡顿感,但删掉代码有些可惜,因此做成可选项,默认关闭
* 亮度调节,在弹出窗口中调节亮度值可以实时改变当前亮度值
* 旋钮功能,使用EC11旋钮控制,旋钮方向可以软件调整,内置一个USB控制电脑的示例,在睡眠模式下旋转调整音量或者亮度,短按输入一个键值,长按进入主菜单,旋钮消抖时长等参数可以在弹出窗口中调整
* 循环模式,选择项超过头尾时,选择项跳转到另一侧继续,列表和磁贴类可以分别选择
* 黑暗模式,其实本来就是黑暗模式,是增加了白天模式,默认开启黑暗模式
* 消失动画适配两种模式,一种是渐变成全黑,另一种渐变成全白
* 断电存储,用简单直观的方式将每种功能参数写入EEPROM,只在修改过参数,进入睡眠模式时写入,避免重复擦写,初始化时检查11个标志位,允许一位误码
项目参考:
* B站:路徍要什么自行车:在线仿真:https://wokwi.com/projects/350306511434547796,https://www.bilibili.com/video/BV1HA411S7pv/
* Github:createskyblue:OpenT12:https://github.com/createskyblue/OpenT12
* Github:peng-zhihui:OpenHeat:https://github.com/peng-zhihui/OpenHeat
注意事项:
* 为防止使用者在上传程序后无法直接使用,认为是代码有问题,HID功能默认禁用,如需使用旋钮音量控制和点按输入功能,请在初始化函数中启用相关功能,上传成功后设置两个跳线帽都为0,断电再插上 USB 线
本项目不设置开源协议,如需商用或借鉴,在醒目处标注本项目开源地址即可
欢迎关注我的B站账号,用户名:音游玩的人,B站主页:https://space.bilibili.com/9182439?spm_id_from=..0.0
*/
/************************************* 屏幕驱动 *************************************/
//分辨率128*64,可以使用硬件IIC接口
#include <U8g2lib.h>
#include <Wire.h>
#define SCL 2
#define SDA 3
#define RST U8X8_PIN_NONE
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0,RST, SCL, SDA); // 分辨率:128*64 驱动:SSD1306 接口:IIC(硬件)
void ui_param_init();
/************************************* 定义页面 *************************************/
//总目录,缩进表示页面层级
enum
{
M_WINDOW,
M_SLEEP,
M_MAIN,
M_EDITOR,
M_VOLT,
M_MONITOR, //新增监控仪表盘
M_SETTING,
M_ABOUT,
};
//状态,初始化标签
enum
{
S_FADE, //转场动画
S_WINDOW, //弹窗初始化
S_LAYER_IN, //层级初始化
S_LAYER_OUT, //层级初始化
S_NONE, //直接选择页面
};
//菜单结构体
typedef struct MENU
{
const char *m_select;
} M_SELECT;
/************************************* 定义内容 *************************************/
/************************************* 文字内容 *************************************/
M_SELECT main_menu[]
{
{"睡眠"},
{"编辑器"},
{"电压测量"},
{"设置"},
};
M_SELECT editor_menu[]
{
{"[ Editor ]"},
{"- Function 0"},
{"- Function 1"},
{"- Function 2"},
{"- Function 3"},
{"- Function 4"},
{"- Function 5"},
{"- Function 6"},
{"- Function 7"},
{"- Function 8"},
{"- Function 9"},
};
M_SELECT volt_menu[]
{
{"A0"},
{"A1"},
{"A2"},
{"A3"},
{"A4"},
{"A5"},
{"A6"},
{"A7"},
{"B0"},
{"B1"},
};
M_SELECT setting_menu[]
{
{"[ Setting ]"},
{"~ Disp Bri"},
{"~ Tile Ani"},
{"~ List Ani"},
{"~ Win Ani"},
{"~ Spot Ani"},
{"~ Tag Ani"},
{"~ Fade Ani"},
{"~ Btn SPT"},
{"~ Btn LPT"},
{"+ T Ufd Fm Scr"},
{"+ L Ufd Fm Scr"},
{"+ T Loop Mode"},
{"+ L Loop Mode"},
{"+ Win Bokeh Bg"},
{"+ Dark Mode"},
{"- [ About ]"},
};
M_SELECT about_menu[]
{
{"[ WouoUI For ESP32 ]"},
{"- Version: v2.3"},
{"- Board: ESP32-S3"},
{"- RAM: 320KB"},
{"- Flash: 8MB"},
{"- CPU主频: 240MHz"},
{"- 原作者: RQNG"},
{"- BiliBili: 9182439"},
{"- 修改者: VRxiaojie"},
{"- BiliBili: 11526854"}
};
/************************************* 图片内容 *************************************/
PROGMEM const uint8_t main_icon_pic[][120]
{
{
0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xF1,0x3F,
0xFF,0xFF,0xC3,0x3F,0xFF,0xFF,0x87,0x3F,0xFF,0xFF,0x07,0x3F,0xFF,0xFF,0x0F,0x3E,
0xFF,0xFF,0x0F,0x3E,0xFF,0xFF,0x0F,0x3C,0xFF,0xFF,0x0F,0x3C,0xFF,0xFF,0x0F,0x38,
0xFF,0xFF,0x0F,0x38,0xFF,0xFF,0x0F,0x38,0xFF,0xFF,0x07,0x38,0xFF,0xFF,0x07,0x38,
0xFF,0xFF,0x03,0x38,0xF7,0xFF,0x01,0x38,0xE7,0xFF,0x00,0x3C,0x87,0x3F,0x00,0x3C,
0x0F,0x00,0x00,0x3E,0x0F,0x00,0x00,0x3E,0x1F,0x00,0x00,0x3F,0x3F,0x00,0x80,0x3F,
0x7F,0x00,0xC0,0x3F,0xFF,0x01,0xF0,0x3F,0xFF,0x07,0xFC,0x3F,0xFF,0xFF,0xFF,0x3F,
0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F
},
{
0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,0xFF,0xF9,0xE7,0x3F,
0xFF,0xF9,0xE7,0x3F,0xFF,0xF9,0xE7,0x3F,0xFF,0xF0,0xE7,0x3F,0x7F,0xE0,0xE7,0x3F,
0x7F,0xE0,0xC3,0x3F,0x7F,0xE0,0xC3,0x3F,0x7F,0xE0,0xC3,0x3F,0x7F,0xE0,0xE7,0x3F,
0xFF,0xF0,0xE7,0x3F,0xFF,0xF9,0xE7,0x3F,0xFF,0xF9,0xE7,0x3F,0xFF,0xF9,0xE7,0x3F,
0xFF,0xF9,0xE7,0x3F,0xFF,0xF9,0xC3,0x3F,0xFF,0xF9,0x81,0x3F,0xFF,0xF0,0x81,0x3F,
0xFF,0xF0,0x81,0x3F,0xFF,0xF0,0x81,0x3F,0xFF,0xF9,0x81,0x3F,0xFF,0xF9,0xC3,0x3F,
0xFF,0xF9,0xE7,0x3F,0xFF,0xF9,0xE7,0x3F,0xFF,0xF9,0xE7,0x3F,0xFF,0xFF,0xFF,0x3F,
0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F
},
{
0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,0xEF,0xFF,0xFF,0x3F,0xC7,0xFF,0xFF,0x3F,
0xC7,0xF3,0xFF,0x3F,0x83,0xC0,0xFF,0x3F,0xEF,0xCC,0xFF,0x3F,0x6F,0x9E,0xFF,0x3F,
0x6F,0x9E,0xFF,0x3F,0x2F,0x3F,0xFF,0x3F,0x2F,0x3F,0xFF,0x3F,0x8F,0x7F,0xFE,0x3F,
0x8F,0x7F,0xFE,0x39,0x8F,0x7F,0xFE,0x39,0xCF,0xFF,0xFC,0x3C,0xCF,0xFF,0xFC,0x3C,
0xEF,0xFF,0xFC,0x3C,0xEF,0xFF,0x79,0x3E,0xEF,0xFF,0x79,0x3E,0xEF,0xFF,0x33,0x3F,
0xEF,0xFF,0x33,0x3F,0xEF,0xFF,0x87,0x3F,0xEF,0xFF,0xCF,0x3F,0xEF,0xFF,0x7F,0x3E,
0xEF,0xFF,0x7F,0x38,0x0F,0x00,0x00,0x30,0xFF,0xFF,0x7F,0x38,0xFF,0xFF,0x7F,0x3E,
0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,
},
{
0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,
0xFF,0x1F,0xFE,0x3F,0xFF,0x1F,0xFE,0x3F,0xFF,0x0C,0xCC,0x3F,0x7F,0x00,0x80,0x3F,
0x3F,0x00,0x00,0x3F,0x3F,0xE0,0x01,0x3F,0x7F,0xF8,0x87,0x3F,0x7F,0xFC,0x8F,0x3F,
0x3F,0xFC,0x0F,0x3F,0x0F,0x3E,0x1F,0x3C,0x0F,0x1E,0x1E,0x3C,0x0F,0x1E,0x1E,0x3C,
0x0F,0x3E,0x1F,0x3C,0x3F,0xFC,0x0F,0x3F,0x7F,0xFC,0x8F,0x3F,0x7F,0xF8,0x87,0x3F,
0x3F,0xE0,0x01,0x3F,0x3F,0x00,0x00,0x3F,0x7F,0x00,0x80,0x3F,0xFF,0x0C,0xCC,0x3F,
0xFF,0x1F,0xFE,0x3F,0xFF,0x1F,0xFE,0x3F,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F,
0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0x3F
},
};
/************************************* 页面变量 *************************************/
//OLED变量
#define DISP_H 64 //屏幕高度
#define DISP_W 128 //屏幕宽度
uint8_t *buf_ptr; //指向屏幕缓冲的指针
uint16_t buf_len; //缓冲长度
//UI变量
#define UI_DEPTH 20 //最深层级数
#define UI_MNUMB 100 //菜单数量
#define UI_PARAM 16 //参数数量
enum
{
DISP_BRI, //屏幕亮度
TILE_ANI, //磁贴动画速度
LIST_ANI, //列表动画速度
WIN_ANI, //弹窗动画速度
SPOT_ANI, //聚光动画速度
TAG_ANI, //标签动画速度
FADE_ANI, //消失动画速度
BTN_SPT, //按键短按时长
BTN_LPT, //按键长按时长
TILE_UFD, //磁贴图标从头展开开关
LIST_UFD, //菜单列表从头展开开关
TILE_LOOP, //磁贴图标循环模式开关
LIST_LOOP, //菜单列表循环模式开关
WIN_BOK, //弹窗背景虚化开关
DARK_MODE, //黑暗模式开关
};
struct
{
bool init;
uint8_t num[UI_MNUMB];
uint8_t select[UI_DEPTH];
uint8_t layer;
uint8_t index = M_SLEEP;
uint8_t state = S_NONE;
bool sleep = true;
uint8_t fade = 1;
uint8_t param[UI_PARAM];
} ui;
//磁贴变量
//所有磁贴页面都使用同一套参数
#define TILE_B_FONT u8g2_font_wqy16_t_gb2312a //磁贴大标题字体
#define TILE_B_TITLE_H 18 //磁贴大标题字体高度
#define TILE_ICON_H 30 //磁贴图标高度
#define TILE_ICON_W 30 //磁贴图标宽度
#define TILE_ICON_S 36 //磁贴图标间距
#define TILE_INDI_H 27 //磁贴大标题指示器高度
#define TILE_INDI_W 7 //磁贴大标题指示器宽度
#define TILE_INDI_S 36 //磁贴大标题指示器上边距
struct
{
float title_y_calc = TILE_INDI_S + (TILE_INDI_H - TILE_B_TITLE_H) / 2 + TILE_B_TITLE_H * 2;
float title_y_trg_calc = TILE_INDI_S + (TILE_INDI_H - TILE_B_TITLE_H) / 2 + TILE_B_TITLE_H;
int16_t temp;
bool select_flag;
float icon_x;
float icon_x_trg;
float icon_y;
float icon_y_trg;
float indi_x;
float indi_x_trg;
float title_y;
float title_y_trg;
} tile;
//列表变量
//默认参数
#define LIST_FONT u8g2_font_wqy12_t_gb2312a //列表字体
#define LIST_TEXT_H 8 //列表每行文字字体的高度
#define LIST_LINE_H 16 //列表单行高度
#define LIST_TEXT_S 4 //列表每行文字的上边距,左边距和右边距,下边距由它和字体高度和行高度决定
#define LIST_BAR_W 5 //列表进度条宽度,需要是奇数,因为正中间有1像素宽度的线
#define LIST_BOX_R 0.5f //列表选择框圆角
struct
{
uint8_t line_n = DISP_H / LIST_LINE_H;
int16_t temp;
bool loop;
float y;
float y_trg;
float box_x;
float box_x_trg;
float box_y;
float box_y_trg[UI_DEPTH];
float bar_y;
float bar_y_trg;
} list;
//电压测量页面变量
//开发板模拟引脚
uint8_t analog_pin[10] = { 15,16 };
//曲线相关
#define WAVE_SAMPLE 20 //采集倍数
#define WAVE_W 94 //波形宽度
#define WAVE_L 24 //波形左边距
#define WAVE_U 0 //波形上边距
#define WAVE_MAX 27 //最大值
#define WAVE_MIN 4 //最小值
#define WAVE_BOX_H 32 //波形边框高度
#define WAVE_BOX_W 94 //波形边框宽度
#define WAVE_BOX_L_S 24 //波形边框左边距
//列表和文字背景框相关
#define VOLT_FONT u8g2_font_helvB18_tr //电压数字字体
#define VOLT_TEXT_BG_L_S 24 //文字背景框左边距
#define VOLT_TEXT_BG_W 94 //文字背景框宽度
#define VOLT_TEXT_BG_H 29 //文字背景框高度
struct
{
int ch0_adc[WAVE_SAMPLE * WAVE_W];
int ch0_wave[WAVE_W];
int val;
float text_bg_r;
float text_bg_r_trg;
} volt;
//选择框变量
//默认参数
#define CHECK_BOX_L_S 95 //选择框在每行的左边距
#define CHECK_BOX_U_S 2 //选择框在每行的上边距
#define CHECK_BOX_F_W 12 //选择框外框宽度
#define CHECK_BOX_F_H 12 //选择框外框高度
#define CHECK_BOX_D_S 2 //选择框里面的点距离外框的边距
/*
//超窄行高度测试
#define CHECK_BOX_L_S 99 //选择框在每行的左边距
#define CHECK_BOX_U_S 0 //选择框在每行的上边距
#define CHECK_BOX_F_W 5 //选择框外框宽度
#define CHECK_BOX_F_H 5 //选择框外框高度
#define CHECK_BOX_D_S 1 //选择框里面的点距离外框的边距
*/
struct
{
uint8_t *v;
uint8_t *m;
uint8_t *s;
uint8_t *s_p;
} check_box;
//弹窗变量
#define WIN_FONT u8g2_font_HelvetiPixel_tr //弹窗字体
#define WIN_H 32 //弹窗高度
#define WIN_W 102 //弹窗宽度
#define WIN_BAR_W 92 //弹窗进度条宽度
#define WIN_BAR_H 7 //弹窗进度条高度
#define WIN_Y - WIN_H - 2 //弹窗竖直方向出场起始位置
#define WIN_Y_TRG - WIN_H - 2 //弹窗竖直方向退场终止位置
struct
{
//uint8_t
uint8_t *value;
uint8_t max;
uint8_t min;
uint8_t step;
MENU *bg;
uint8_t index;
char title[20];
uint8_t select;
uint8_t l = (DISP_W - WIN_W) / 2;
uint8_t u = (DISP_H - WIN_H) / 2;
float bar;
float bar_trg;
float y;
float y_trg;
} win;
//聚光灯变量
struct
{
float l;
float l_trg;
float r;
float r_trg;
float u;
float u_trg;
float d;
float d_trg;
} spot;
/********************************** 自定义功能变量 **********************************/
/************************************* 断电保存 *************************************/
#include <EEPROM.h>
//EEPROM变量
#define EEPROM_CHECK 11
struct
{
bool init;
bool change;
int address;
uint8_t check;
uint8_t check_param[EEPROM_CHECK] = { 'a', 'b', 'c', 'd', 'e', 'f','g', 'h', 'i', 'j', 'k' };
} eeprom;
//EEPROM写数据,回到睡眠时执行一遍
void eeprom_write_all_data()
{
eeprom.address = 0;
for (uint8_t i = 0; i < EEPROM_CHECK; ++i) EEPROM.write(eeprom.address + i, eeprom.check_param[i]); eeprom.address += EEPROM_CHECK;
for (uint8_t i = 0; i < UI_PARAM; ++i) EEPROM.write(eeprom.address + i, ui.param[i]);
}
//EEPROM读数据,开机初始化时执行一遍
void eeprom_read_all_data()
{
eeprom.address = EEPROM_CHECK;
for (uint8_t i = 0; i < UI_PARAM; ++i) ui.param[i] = EEPROM.read(eeprom.address + i);
}
//开机检查是否已经修改过,没修改过则跳过读配置步骤,用默认设置
void eeprom_init()
{
eeprom.check = 0;
eeprom.address = 0; for (uint8_t i = 0; i < EEPROM_CHECK; ++i) if (EEPROM.read(eeprom.address + i) != eeprom.check_param[i]) eeprom.check ++;
if (eeprom.check <= 1) eeprom_read_all_data(); //允许一位误码
else ui_param_init();
}
/************************************* 旋钮相关 *************************************/
//可按下旋钮引脚
#define BTN0 12
#define BTN1 13
#define BTN2 14
//按键ID
#define BTN_ID_CC 0 //逆时针旋转
#define BTN_ID_CW 1 //顺时针旋转
#define BTN_ID_SP 2 //短按
#define BTN_ID_LP 3 //长按
//按键变量
#define BTN_PARAM_TIMES 2 //由于uint8_t最大值可能不够,但它存储起来方便,这里放大两倍使用
//按键变量
typedef struct
{
bool val;
bool last_val;
long count;
unsigned long chg_time;
bool operated;
}KEY;
KEY key[3]={false};
//按键信息
struct
{
uint8_t id;
bool pressed;
} volatile btn;
;
bool get_key_val(uint8_t ch)
{
switch (ch)
{
case 0:
return digitalRead(BTN0);
break;
case 1:
return digitalRead(BTN1);
break;
case 2:
return digitalRead(BTN2);
break;
default:
break;
}
return false;
}
void btn_scan() {
for (uint8_t i = 0; i < (sizeof(key) / sizeof(KEY)); ++i) {
key[i].val = get_key_val(i); //获取键值
if (key[i].last_val != key[i].val) //发生改变
{
key[i].last_val = key[i].val; //更新状态
//delay(ui.param[BTN_SPT]);
//key[i].val = get_key_val(i);
if (key[i].val == HIGH) {
key[i].chg_time = millis();
switch (i) {
case 0:
btn.id = BTN_ID_CC;
btn.pressed = 1;
break;
case 1:
btn.id = BTN_ID_CW;
btn.pressed = 1;
break;
default:
break;
}
}
if (key[i].val == LOW) {
switch (i) {
case 2:
if (key[i].operated == 1)
key[i].operated = 0;
else {
btn.id = BTN_ID_SP;
btn.pressed = 1;
}
break;
default:
break;
}
}
}
if (key[i].val == HIGH && millis() - key[i].chg_time > ui.param[BTN_LPT] && key[i].operated == 0) {
switch (i) {
case 0:
btn.id = BTN_ID_CC;
btn.pressed = 1;
break;
case 1:
btn.id = BTN_ID_CW;
btn.pressed = 1;
break;
case 2:
btn.id = BTN_ID_LP;
key[i].operated = 1;
btn.pressed = 1;
break;
default:
break;
}
}
}
}
void btn_init()
{
pinMode(BTN0, INPUT_PULLDOWN);
pinMode(BTN1, INPUT_PULLDOWN);
pinMode(BTN2, INPUT_PULLDOWN);
for(uint8_t i=0;i<(sizeof(key)/sizeof(KEY));++i)
{
key[i].val=key[i].last_val=get_key_val(i);
}
}
/************************************ 初始化函数 ***********************************/
/********************************* 初始化数据处理函数 *******************************/
//显示数值的初始化
void check_box_v_init(uint8_t *param)
{
check_box.v = param;
}
//多选框的初始化
void check_box_m_init(uint8_t *param)
{
check_box.m = param;
}
//单选框时的初始化
void check_box_s_init(uint8_t *param, uint8_t *param_p)
{
check_box.s = param;
check_box.s_p = param_p;
}
//多选框处理函数
void check_box_m_select(uint8_t param)
{
check_box.m[param] = !check_box.m[param];
eeprom.change = true;
}
//单选框处理函数
void check_box_s_select(uint8_t val, uint8_t pos)
{
*check_box.s = val;
*check_box.s_p = pos;
eeprom.change = true;
}
//弹窗数值初始化
void window_value_init(const char title[], uint8_t select, uint8_t *value, uint8_t max, uint8_t min, uint8_t step, MENU *bg, uint8_t index)
{
strcpy(win.title, title);
win.select = select;
win.value = value;
win.max = max;
win.min = min;
win.step = step;
win.bg = bg;
win.index = index;
ui.index = M_WINDOW;
ui.state = S_WINDOW;
}
/*********************************** UI 初始化函数 *********************************/
//在初始化EEPROM时,选择性初始化的默认设置
void ui_param_init()
{
ui.param[DISP_BRI] = 255; //屏幕亮度
ui.param[TILE_ANI] = 30; //磁贴动画速度
ui.param[LIST_ANI] = 60; //列表动画速度
ui.param[WIN_ANI] = 25; //弹窗动画速度
ui.param[SPOT_ANI] = 50; //聚光动画速度
ui.param[TAG_ANI] = 60; //标签动画速度
ui.param[FADE_ANI] = 30; //消失动画速度
ui.param[BTN_SPT] = 25; //按键短按时长
ui.param[BTN_LPT] = 150; //按键长按时长
ui.param[TILE_UFD] = 1; //磁贴图标从头展开开关
ui.param[LIST_UFD] = 1; //菜单列表从头展开开关
ui.param[TILE_LOOP] = 0; //磁贴图标循环模式开关
ui.param[LIST_LOOP] = 0; //菜单列表循环模式开关
ui.param[WIN_BOK] = 0; //弹窗背景虚化开关
ui.param[DARK_MODE] = 1; //黑暗模式开关
}
//列表类页面列表行数初始化,必须初始化的参数
void ui_init()
{
ui.num[M_MAIN] = sizeof( main_menu ) / sizeof(M_SELECT);
ui.num[M_EDITOR] = sizeof( editor_menu ) / sizeof(M_SELECT);
ui.num[M_VOLT] = sizeof( volt_menu ) / sizeof(M_SELECT);
ui.num[M_SETTING] = sizeof( setting_menu ) / sizeof(M_SELECT);
ui.num[M_ABOUT] = sizeof( about_menu ) / sizeof(M_SELECT);
}
/********************************* 分页面初始化函数 ********************************/
//进入磁贴类时的初始化
void tile_param_init()
{
ui.init = false;
tile.icon_x = 0;
tile.icon_x_trg = TILE_ICON_S;
tile.icon_y = -TILE_ICON_H;
tile.icon_y_trg = 0;
tile.indi_x = 0;
tile.indi_x_trg = TILE_INDI_W;
tile.title_y = tile.title_y_calc;
tile.title_y_trg = tile.title_y_trg_calc;
}
//进入睡眠时的初始化
void sleep_param_init()
{
u8g2.setDrawColor(0);
u8g2.drawBox(0, 0, DISP_W, DISP_H);
u8g2.setPowerSave(1);
ui.state = S_NONE;
ui.sleep = true;
if (eeprom.change)
{
eeprom_write_all_data();
eeprom.change = false;
}
}
//电压测量页初始化
void volt_param_init()
{
volt.text_bg_r = 0;
volt.text_bg_r_trg = VOLT_TEXT_BG_W;
}
//设置页初始化
void setting_param_init()
{
check_box_v_init(ui.param);
check_box_m_init(ui.param);
}
/********************************** 通用初始化函数 *********************************/
/*
页面层级管理逻辑是,把所有页面都先当作列表类初始化,不是列表类按需求再初始化对应函数
这样做会浪费一些资源,但跳转页面时只需要考虑页面层级,逻辑上更清晰,减少出错
*/
//弹窗动画初始化
void window_param_init()
{
win.bar = 0;
win.y = WIN_Y;
win.y_trg = win.u;
ui.state = S_NONE;
}
//进入更深层级时的初始化
void layer_init_in()
{
ui.layer ++;
ui.init = 0;
list.y = 0;
list.y_trg = LIST_LINE_H;
list.box_x = 0;
list.box_y = 0;
list.bar_y = 0;
ui.state = S_FADE;
switch (ui.index)
{
case M_MAIN: tile_param_init(); break; //睡眠进入主菜单,动画初始化
case M_VOLT: volt_param_init(); break; //主菜单进入电压测量页,动画初始化
case M_SETTING: setting_param_init(); break; //主菜单进入设置页,单选框初始化
}
}
//进入更浅层级时的初始化
void layer_init_out()
{
ui.select[ui.layer] = 0;
list.box_y_trg[ui.layer] = 0;
ui.layer --;
ui.init = 0;
list.y = 0;
list.y_trg = LIST_LINE_H;
list.bar_y = 0;
ui.state = S_FADE;
switch (ui.index)
{
case M_SLEEP: sleep_param_init(); break; //主菜单进入睡眠页,检查是否需要写EEPROM
case M_MAIN: tile_param_init(); break; //不管什么页面进入主菜单时,动画初始化
}
}
/************************************* 动画函数 *************************************/
//动画函数
void animation(float *a, float *a_trg, uint8_t n)
{
if (*a != *a_trg)
{
if (fabs(*a - *a_trg) < 0.15f) *a = *a_trg;
else *a += (*a_trg - *a) / (ui.param[n] / 10.0f);
}
}
//消失函数
void fade()
{
delay(ui.param[FADE_ANI]);
if (ui.param[DARK_MODE])
{
switch (ui.fade)
{
case 1: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] & 0xAA; break;
case 2: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] & 0x00; break;
case 3: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] & 0x55; break;
case 4: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] & 0x00; break;
default: ui.state = S_NONE; ui.fade = 0; break;
}
}
else
{
switch (ui.fade)
{
case 1: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] | 0xAA; break;
case 2: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] | 0x00; break;
case 3: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] | 0x55; break;
case 4: for (uint16_t i = 0; i < buf_len; ++i) if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] | 0x00; break;
default: ui.state = S_NONE; ui.fade = 0; break;
}
}
ui.fade++;
}
/************************************* 显示函数 *************************************/
//磁贴类页面通用显示函数
void tile_show(struct MENU arr_1[], const uint8_t icon_pic[][120])
{
//计算动画过渡值
animation(&tile.icon_x, &tile.icon_x_trg, TILE_ANI);
animation(&tile.icon_y, &tile.icon_y_trg, TILE_ANI);
animation(&tile.indi_x, &tile.indi_x_trg, TILE_ANI);
animation(&tile.title_y, &tile.title_y_trg, TILE_ANI);
//设置大标题的颜色,0透显,1实显,2反色,这里用实显
u8g2.setDrawColor(1);
//绘制大标题 中文 字号16
u8g2.setFont(TILE_B_FONT);
//Serial.print(u8g2.getUTF8Width(arr_1[ui.select[ui.layer]].m_select));
u8g2.drawUTF8((DISP_W - u8g2.getUTF8Width(arr_1[ui.select[ui.layer]].m_select)) / 2 , tile.title_y, arr_1[ui.select[ui.layer]].m_select);
//绘制大标题指示器
u8g2.drawBox(0, TILE_ICON_S, tile.indi_x, TILE_INDI_H);
//绘制图标
if (!ui.init)
{
for (uint8_t i = 0; i < ui.num[ui.index]; ++i)
{
if (ui.param[TILE_UFD]) tile.temp = (DISP_W - TILE_ICON_W) / 2 + i * tile.icon_x - TILE_ICON_S * ui.select[ui.layer];
else tile.temp = (DISP_W - TILE_ICON_W) / 2 + (i - ui.select[ui.layer]) * tile.icon_x;
u8g2.drawXBMP(tile.temp, (int16_t)tile.icon_y, TILE_ICON_W, TILE_ICON_H, icon_pic[i]);
}
if (tile.icon_x == tile.icon_x_trg)
{
ui.init = true;
tile.icon_x = tile.icon_x_trg = - ui.select[ui.layer] * TILE_ICON_S;
}
}
else for (uint8_t i = 0; i < ui.num[ui.index]; ++i) u8g2.drawXBMP((DISP_W - TILE_ICON_W) / 2 + (int16_t)tile.icon_x + i * TILE_ICON_S, 0, TILE_ICON_W, TILE_ICON_H, icon_pic[i]);
//反转屏幕内元素颜色,白天模式遮罩
u8g2.setDrawColor(2);
if (!ui.param[DARK_MODE]) u8g2.drawBox(0, 0, DISP_W, DISP_H);
}
/*************** 根据列表每行开头符号,判断每行尾部是否绘制以及绘制什么内容 *************/
//列表显示数值
void list_draw_value(int n) { u8g2.print(check_box.v[n - 1]); }
//绘制外框
void list_draw_check_box_frame() { u8g2.drawRFrame(CHECK_BOX_L_S, list.temp + CHECK_BOX_U_S, CHECK_BOX_F_W, CHECK_BOX_F_H, 1); }
//绘制框里面的点
void list_draw_check_box_dot() { u8g2.drawBox(CHECK_BOX_L_S + CHECK_BOX_D_S + 1, list.temp + CHECK_BOX_U_S + CHECK_BOX_D_S + 1, CHECK_BOX_F_W - (CHECK_BOX_D_S + 1) * 2, CHECK_BOX_F_H - (CHECK_BOX_D_S + 1) * 2); }
//列表显示旋钮功能
void list_draw_krf(int n)
{
switch (check_box.v[n - 1])
{
case 0: u8g2.print("OFF"); break;
case 1: u8g2.print("VOL"); break;
case 2: u8g2.print("BRI"); break;
}
}
//列表显示按键键值
void list_draw_kpf(int n)
{
if (check_box.v[n - 1] == 0) u8g2.print("OFF");
else if (check_box.v[n - 1] <= 90) u8g2.print((char)check_box.v[n - 1]);
else u8g2.print("?");
}
//判断列表尾部内容
void list_draw_text_and_check_box(struct MENU arr[], int i)
{
u8g2.drawUTF8(LIST_TEXT_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, arr[i].m_select);
u8g2.setCursor(CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S);
switch (arr[i].m_select[0])
{
case '~': list_draw_value(i); break;
case '+': list_draw_check_box_frame(); if (check_box.m[i - 1] == 1) list_draw_check_box_dot(); break;
case '=': list_draw_check_box_frame(); if (*check_box.s_p == i) list_draw_check_box_dot(); break;
case '#': list_draw_krf(i); break;
case '$': list_draw_kpf(i); break;
}
}
/******************************** 列表显示函数 **************************************/
//列表类页面通用显示函数
void list_show(struct MENU arr[], uint8_t ui_index)
{
//更新动画目标值
u8g2.setFont(LIST_FONT);
list.box_x_trg = u8g2.getUTF8Width(arr[ui.select[ui.layer]].m_select) + LIST_TEXT_S * 2;
list.bar_y_trg = ceil((ui.select[ui.layer]) * ((float)DISP_H / (ui.num[ui_index] - 1)));
//计算动画过渡值
animation(&list.y, &list.y_trg, LIST_ANI);
animation(&list.box_x, &list.box_x_trg, LIST_ANI);
animation(&list.box_y, &list.box_y_trg[ui.layer], LIST_ANI);
animation(&list.bar_y, &list.bar_y_trg, LIST_ANI);
//检查循环动画是否结束
if (list.loop && list.box_y == list.box_y_trg[ui.layer]) list.loop = false;
//设置文字和进度条颜色,0透显,1实显,2反色,这里都用实显
u8g2.setDrawColor(1);
//绘制进度条
u8g2.drawHLine(DISP_W - LIST_BAR_W, 0, LIST_BAR_W);
u8g2.drawHLine(DISP_W - LIST_BAR_W, DISP_H - 1, LIST_BAR_W);
u8g2.drawVLine(DISP_W - ceil((float)LIST_BAR_W / 2), 0, DISP_H);
u8g2.drawBox(DISP_W - LIST_BAR_W, 0, LIST_BAR_W, list.bar_y);
//绘制列表文字
if (!ui.init)
{
for (int i = 0; i < ui.num[ui_index]; ++ i)
{
if (ui.param[LIST_UFD]) list.temp = i * list.y - LIST_LINE_H * ui.select[ui.layer] + list.box_y_trg[ui.layer];
else list.temp = (i - ui.select[ui.layer]) * list.y + list.box_y_trg[ui.layer];
list_draw_text_and_check_box(arr, i);
}
if (list.y == list.y_trg)
{
ui.init = true;
list.y = list.y_trg = - LIST_LINE_H * ui.select[ui.layer] + list.box_y_trg[ui.layer];
}
}
else for (int i = 0; i < ui.num[ui_index]; ++ i)
{
list.temp = LIST_LINE_H * i + list.y;
list_draw_text_and_check_box(arr, i);
}
//绘制文字选择框,0透显,1实显,2反色,这里用反色
u8g2.setDrawColor(2);
u8g2.drawRBox(0, list.box_y, list.box_x, LIST_LINE_H, LIST_BOX_R);
//反转屏幕内元素颜色,白天模式遮罩,在这里屏蔽有列表参与的页面,使遮罩作用在那个页面上
if (!ui.param[DARK_MODE])
{
u8g2.drawBox(0, 0, DISP_W, DISP_H);
switch(ui.index)
{
case M_WINDOW:
case M_VOLT:
u8g2.drawBox(0, 0, DISP_W, DISP_H);
}
}
}
//电压页面显示函数
void volt_show()
{
//使用列表类显示选项
list_show(volt_menu, M_VOLT);
//计算动画过渡值
animation(&volt.text_bg_r, &volt.text_bg_r_trg, TAG_ANI);
//设置曲线颜色,0透显,1实显,2反色,这里用实显
u8g2.setDrawColor(1);
//绘制电压曲线和外框
volt.val = 0;
u8g2.drawFrame(WAVE_BOX_L_S, 0, WAVE_BOX_W, WAVE_BOX_H);
u8g2.drawFrame(WAVE_BOX_L_S + 1, 1, WAVE_BOX_W - 2, WAVE_BOX_H - 2);
if (list.box_y == list.box_y_trg[ui.layer] && list.y == list.y_trg)
{
for (int i = 0; i < WAVE_SAMPLE * WAVE_W; i++) volt.ch0_adc[i] = volt.val = analogRead(analog_pin[ui.select[ui.layer]]);
for (int i = 1; i < WAVE_W - 1; i++)
{
volt.ch0_wave[i] = map(volt.ch0_adc[int(5 * i)], 0, 4095, WAVE_MAX, WAVE_MIN);
u8g2.drawLine(WAVE_L + i - 1, WAVE_U + volt.ch0_wave[i - 1], WAVE_L + i, WAVE_U + volt.ch0_wave[i]);
}
}
//绘制电压值
u8g2.setFontDirection(0);
u8g2.setFont(VOLT_FONT);
u8g2.setCursor(39, DISP_H - 6);
u8g2.print(volt.val / 4096.0f * 3.3f);
u8g2.print("V");
//绘制列表选择框和电压文字背景
u8g2.setDrawColor(2);
u8g2.drawBox(VOLT_TEXT_BG_L_S, DISP_H - VOLT_TEXT_BG_H, volt.text_bg_r, VOLT_TEXT_BG_H);
//反转屏幕内元素颜色,白天模式遮罩
if (!ui.param[DARK_MODE]) u8g2.drawBox(0, 0, DISP_W, DISP_H);
}
//弹窗通用显示函数
void window_show()
{
//绘制背景列表,根据开关判断背景是否要虚化
list_show(win.bg, win.index);
if (ui.param[WIN_BOK]) for (uint16_t i = 0; i < buf_len; ++i) buf_ptr[i] = buf_ptr[i] & (i % 2 == 0 ? 0x55 : 0xAA);
//更新动画目标值