微机与汇编接口实验

暑假小学期让做一个有关微机的小项目,看来也是为以后做嵌入式开发做准备。

设计

设计思路

设想的是使用led点阵做一个小型的贪吃蛇游戏,并且可以使用红外遥控器进行操控,并且配合实时的灯光和音效展现出一种复古的街机氛围。
设计项目要抓住项目的主要矛盾,做贪吃蛇就是做贪吃蛇,专心地先做出来贪吃蛇再想别的。

首先必须可以实时的控制led点阵,led点阵可以显示红绿两种颜色,将led点阵存放在red_buffer、green_buffer中,他们都是一个8字节地缓冲区,每一个字节存放了每一列中各行地明亮情况。下面说明一下程序的生命周期:程序开始运行,主菜单显示退出程序和开始贪吃蛇游戏,选择退出程序则退出程序,选择开始贪吃蛇游戏则开始贪吃蛇游戏。进入贪吃蛇游戏之后在led点阵上显示出图像,使用遥控器控制贪吃蛇进行移动吃食物,若撞到墙则从另一边出来,若撞到自己则失败回到主菜单。在游戏过程中实时播放音效和数码管计分。

功能设计

  1. 程序开始时展现出一个菜单,菜单分为:
    1. 退出程序
    2. 开始贪吃蛇游戏
  2. 贪吃蛇程序开始运行,在led点阵上面实时地显示。
  3. 使用数码管显示当前得分
  4. 在游戏中播放音乐
  5. 在游戏中根据贪吃蛇地状态进行灯光、音乐、文字等方面地提示。
  6. 游戏失败后返回主菜单。

模块设计

  1. show_led
    1. 负责根据led点阵缓冲区中地数据向上提供可靠的led点阵显示程序。
    2. 入口:led点阵缓冲区
    3. 出口:led点阵进行刷新一次
  2. delay
    1. 根据bx地值进行时长不等的延时。
    2. 入口:bx
    3. 出口:延时一定时间
  3. dispmenu
    1. 显示程序初始的菜单
    2. 入口:程序启动
    3. 出口:显示程序初始的菜单
  4. show_digital_tube
    1. 把bx中的10进制数据显示出来(0<=bx<=9999)
    2. 入口:bx
    3. 出口:数码管显示bx中的十进制数据
  5. buzzer_sound
    1. 根据bx中的频率,蜂鸣器响
    2. 入口:bx
    3. 出口:蜂鸣器响特定的频率
  6. infrared_input
    1. 红外线输入,接受红外线的输入并将红外线输入的信息存储到infrared_input_buffer中。
    2. 入口:红外线输入
    3. 出口:更新infrared_input_buffer
  7. keyboard_input
    1. 键盘输入,接受键盘的输入并将键盘输入的信息存储到keyboard_input_buffer中。
    2. 入口:键盘输入
    3. 出口:更新keyboard_input_buffer
  8. process_input
    1. 处理输入,根据infrared_input_buffer和keyboard_input_buffer和status_buffer更新程序的数据。相当于游戏逻辑控制。
    2. 入口:以上三个缓冲区
    3. 出口:根据游戏状态和逻辑更新游戏状态
  9. run
    1. 程序的状态机,程序的声明周期控制
    2. 入口:status_buffer
    3. 出口:status_buffer

详细模块设计

code

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
;点亮数码管  ********
;删除simple_input
;删除红外输入的有关内容
;键盘的BIOS调用 int 16h 入口: ah=0 出口 al=ascii 先使用 ah=01判断是否有输入
;注释掉 process_input中的 call keyboard_input 和snake_count_time 中的 call keyboard_input 并删除中断程序中对call keyboard_input的注释,验证使用中断输入是否可行
;删除snake中jmp snake_flush
;新增 delay_short
;尝试可不可以在任何时候按下9回到主菜单,做不到的话就删除主菜单中的这句话

;支持子程序库的简化段源程序格式
include io.inc
.model small ; 定义程序的存储模式
.stack ; 定义堆栈段(默认是1KB空间)
.data ;数据段

;存放每一列的信息 1为亮 两种颜色交替进行闪亮 要把每一个字节的信息传送给行,来控制某一行的亮
;比如 某一个字节为 1000_0010b 则最上面一行和倒数第二行的灯亮,此时只需要对应列全为1即可控制该列的8个灯的亮暗。
;红色 颜色是通过列线进行输出的
red_buffer byte 8 dup(0)
;绿色
green_buffer byte 8 dup(0)
;infrared_input_buffer 存储红外线输入的缓冲区 每一个bit可以自由定义其含义
infrared_input_buffer word 0
;keyboard_input_buffer 存储键盘输入的缓冲区 每一个bit可以自由定义其含义
keyboard_input_buffer word 0
;Status buffer 状态缓冲区,用来存放程序运行的各种状态
status_buffer word 0
;时间缓冲区,负责提供同步信息 由中断程序每隔10ms加一
sync_time_buffer word 0
;蛇身位置 不包含蛇头 蛇的每一个位置需要两个字节,8*8*2=128B
snake_body_buffer byte 128 dup(0)
;蛇身的长度,不包括蛇头,也是当前的分数
snake_body_length word 0
;蛇头位置
snake_head_position_x byte 0
snake_head_position_y byte 0
;蛇尾位置
snake_tail_position_x byte 0
snake_tail_position_y byte 0
;食物位置
food_x byte 0
food_y byte 0
;是否吃了食物 0:没有 1:吃了
food_eaten byte 0
;碰撞与否
collision byte 0
;蛇的前近方向 2:上 0:下 2;左 1:右
snake_direction byte 0
;保存原中断程序入口
int_0b_seg word 0
int_0b_off word 0
;蛇的时间量,每经过100个全局时间量 加一,即蛇每1s移动一次
snake_time word 0
;数码管
ledtb byte 3fh,06h,5bh,4fh,66h,6dh,7dh,07h,7fh,6fh
;得分(当前长度减去初始长度)存储 分别存储四个数码管的数字
lednum byte 4 dup(0)
byte 128 dup(0)
;main_menu 主菜单
main_menu byte '---------------main menu---------------',10,13
byte '----press corrsepnding key to continue----',10,13
byte '1. exit',10,13
byte '2. play snake_gluttony ',10,13
byte '*********************************************',10,13,0


;退出语句
abort_msg byte 'exit,thanks',10,13,0

msgstatus0 byte 'msgstatus0',10,13,0

msgstatus1 byte 'msgstatus1',10,13,0

msgsetint byte 'msgsetint',10,13,0

msgscankeyboard byte 'msgscankeyboard',10,13,0

msgintirq10 byte 'msgintirq10',10,13,0

snake_gluttony_running byte 'snake_gluttony is running',10,13,0

msg_direction byte 'now, the direction is ',10,13,0

msg_snake_head_position byte 'msg_snake_head_position is ',10,13,0

msg_snake_tail_position byte 'msg_snake_tail_position is ',10,13,0

msg_snake_body_position byte 'msg_snake_body_position is ',10,13,0

msg_snake_gluttony_game_over byte 'game over!!!',10,13,0

msg_snake_gluttony_game_start byte 'snake gluttony game start, have fun!!!',10,13,0

msg_int_test byte 'msg_int_test',10,13,0


.code ;代码段
start:
mov ax,@data
mov ds,ax

;设置中断
call set_int
call run
.exit 0

;状态机 负责处理所有的状态 程序的所有状态全部由status_buffer所体现
run proc
push ax
push bx
push cx
push dx
push si
push di


;程序开始运行,清空程序的所有缓冲区
run_begin:
call clear_digital_buffer
call clear_screen
call disp_main_menu


;清空缓冲区的数据
mov red_buffer,0
mov green_buffer,0
mov status_buffer,1 ;初始状态为1 主菜单
mov infrared_input_buffer,0
mov keyboard_input_buffer,0

;显示主菜单 没有收到遥控器的命令就一直进行查询
show_main_menu:
;所有的输入已经通过每10ms的中断传入到输入缓冲区了
call process_input ;改变程序状态 下面判断当前程序状态 000为退出
;判断是否有有效输入进行下一步
;状态0 退出
mov ax,status_buffer
and al,00000111B
cmp al,0
jz run_abort
;状态1
mov ax,status_buffer
and al,00000111B
cmp al,1
jz show_main_menu
;判断是否要进入贪吃蛇游戏 就是判断当前主状态为2
mov ax,status_buffer
and al,00000111B
cmp al,2
jz run_snake_gluttony
;判断是否退出
jz run_abort
jmp show_main_menu
;运行贪吃蛇
run_snake_gluttony:
snake_gluttony_game_start:
;每一个主状态都是一个自循环,除非中断迫使其退出
call snake_gluttony
;贪吃蛇由于某种原因运行完成退出 回到主菜单
snake_gluttony_game_over:
mov si,offset msg_snake_gluttony_game_over
call dispstr
mov bx,1000
call delay
jmp run_begin

;这里可以有很多程序和贪吃蛇并列,但是我并不想要。
run_abort:
;打印出一段退出程序的语句
mov si,offset abort_msg
call dispstr

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
run endp

;显示主菜单
disp_main_menu proc
push ax

push bx
push cx
push dx
push si
push di

mov ax,@data
mov ds,ax
mov si,offset main_menu
call dispstr
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
disp_main_menu endp

;处理输入结果 逻辑控制
process_input proc
push ax
push bx
push cx
push dx
push si
push di

;判断主状态
judge_main_status:
;状态0 ,退出
mov ax,status_buffer
and al,00000111B
cmp al,0
jz main_status_0 ;跳转到主状态0 退出
;判断是否为主状态1,即在主菜单
mov ax,status_buffer
and al,00000111B
cmp al,1
jz main_status_1 ;跳转到主状态1来执行
;判断是否为主状态2,即贪吃蛇游戏
mov ax,status_buffer
and al,00000111B ;只留下低三位
cmp al,2 ;状态1
jz main_status_2 ;跳转到主状态1,即执行贪吃蛇

;还要依次遍历很多别的状态
jmp process_input_done

;状态0 ,退出
main_status_0:
mov bx,status_buffer
and bl,11111000B
mov status_buffer,bx
jmp process_input_done

;主状态1 主菜单
main_status_1:
;将程序状态变为1
mov al,00000001B
mov bx,status_buffer
and bl,11111000B
or bl,al
mov status_buffer,bx
;主菜单一件事情,等待输入,将红外和键盘输入的对于处于主菜单的程序有效的输入存储在缓冲区内
;判断红外输入。 1:退出 2:开始贪吃蛇 9:回到主菜单
;键盘输入,1-9位对应输入
mov ax,keyboard_input_buffer
and al,00000010B ;判断第一位 退出
cmp al,00000010B
jz main_status_0
;键盘输入,1-9位对应输入
mov ax,keyboard_input_buffer
and al,00000100B ;判断第二位 贪吃蛇
cmp al,00000100B
jz main_status_2
jmp process_input_done

;主状态2 贪吃蛇
main_status_2:
;将程序状态变为2
mov al,00000010B
mov bx,status_buffer
and bl,11111000B
or bl,al
mov status_buffer,bx

;没有符合条件的状态,直接退出
jmp process_input_done

process_input_done:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
process_input endp

;Infrared input 红外线输入
infrared_input proc
push ax
push bx
push cx
push dx
push si
push di

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
infrared_input endp

;simple_input
simple_input proc
push ax
push bx
push cx
push dx
push si
push di

mov dx,282h ;读入 pc
in al,dx
mov ah,al
cmp al,0
jz simple_input_end

simple_input_1:
mov al,ah
and al,00000010B
jz simple_input_2
mov bx,keyboard_input_buffer
;or ax,0000 0000 0000 0010b
or bx,0002h
mov keyboard_input_buffer,bx
simple_input_2:
mov al,ah
and al,00000100B
jz simple_input_w
mov bx,keyboard_input_buffer
;or ax,0000 0000 0000 0100b
or bx,0004h
mov keyboard_input_buffer,bx
simple_input_w:
mov al,ah
and al,00001000B
jz simple_input_a
mov bx,keyboard_input_buffer
;or ax,0000 0100 0000 0000b
or bx,0400h
mov keyboard_input_buffer,bx
simple_input_a:
mov al,ah
and al,00010000B
jz simple_input_s
mov bx,keyboard_input_buffer
;or ax,0000 0100 0000 0000b
or bx,0800h
mov keyboard_input_buffer,bx
simple_input_s:
mov al,ah
and al,00100000B
jz simple_input_d
mov bx,keyboard_input_buffer
;or ax,0000 0100 0000 0000b
or bx,1000h
mov keyboard_input_buffer,bx
simple_input_d:
mov al,ah
and al,01000000B
jz simple_input_end
mov bx,keyboard_input_buffer
;or ax,0000 0100 0000 0000b
or bx,2000h
mov keyboard_input_buffer,bx


simple_input_end:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
simple_input endp
;键盘输入
keyboard_input proc
push ax
push bx
push cx
push dx
push si
push di
;mov si,offset msgscankeyboard
;call dispstr
;扫描输入
;键盘输入 al中存放键盘输入的ascii码
scan_keynoard:
;处理键盘的W A S D 10-13位存放 将对应位置1 输入小写
;call readkey
;测试 int 16h
mov ah,01h
int 16h
jz scan_keynoard_end ;若键盘缓冲区没有则结束
mov ah,00h ;键盘缓冲区中有则读取
int 16h

scan_keynoard_w:
cmp al,'w'
jnz scan_keynoard_a
mov ax,keyboard_input_buffer
;or ax,0000 0100 0000 0000b
or ax,0400h
mov keyboard_input_buffer,ax
jmp scan_keynoard_end
scan_keynoard_a:
cmp al,'a'
jnz scan_keynoard_s
mov ax,keyboard_input_buffer
;or ax,0000 1000 0000 0000b
or ax,0800h
mov keyboard_input_buffer,ax
jmp scan_keynoard_end
scan_keynoard_s:
cmp al,'s'
jnz scan_keynoard_d
mov ax,keyboard_input_buffer
;or ax,0001 0000 0000 0000b
or ax,1000h
mov keyboard_input_buffer,ax
jmp scan_keynoard_end
scan_keynoard_d:
cmp al,'d'
jnz scan_keynoard_num
mov ax,keyboard_input_buffer
;or ax,0010 0000 0000 0000b
or ax,2000h
mov keyboard_input_buffer,ax
jmp scan_keynoard_end
;扫描num num存在1-9位对应1-9
scan_keynoard_num:
sub al,48
cmp al,1 ;小于1不行
jb scan_keynoard_end
cmp al,9 ;大于9不行
ja scan_keynoard_end
mov bx,1
mov cl,al
shl bx,cl
mov ax,keyboard_input_buffer
or ax,bx
mov keyboard_input_buffer,ax
jmp scan_keynoard_end

;将来增加新功能

scan_keynoard_end:

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
keyboard_input endp

;点亮数码管 用来记录分数等信息 使用pc
show_digital_tube proc
push ax
push bx
push cx
push dx
push si
push di

call clear_digital_buffer
;先得到每一个数码管应该显示的数字
mov ax,snake_body_length
mov si,offset lednum
sub ax,2

mov cx,4
get_one_tube_num:
mov bl,10
div bl
mov [si],ah
xor ah,ah
cmp al,0
jz get_one_tube_num_over
inc si
loop get_one_tube_num

get_one_tube_num_over:

;依次显示出来
mov si,offset lednum
mov ah,00000001B
mov cx,4
show_digital_tube_one_by_one:
xor bx,bx
mov bl,[si]
inc si
mov al,ledtb[bx] ;段码

;clear tube ;清空位码 灭掉所有的灯
push ax
mov al,0
mov dx,280h
out dx,al
mov dx,282h ;清空段码
mov al,0ffh
out dx,al
pop ax

;控制数字 段码
mov dx,282h
out dx,al
;控制哪一个tube去亮 位码
mov al,ah
mov dx,280h
out dx,al
shl ah,1
;mov bx,1
;call delay_short
loop show_digital_tube_one_by_one


;clear tube ;清空位码 灭掉所有的灯
push ax
mov al,0
mov dx,280h
out dx,al
mov dx,282h ;清空段码
mov al,0ffh
out dx,al
pop ax


;验证数字算的duibudui
;mov si,offset lednum
;add si,3
;mov cx,4
disp_led_num:
;mov al,[si]
;dec si
;call dispuib
;loop disp_led_num
;call dispcrlf

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
show_digital_tube endp

;clear
clear_digital_buffer proc
push ax
push bx
push cx
push dx
push si
push di

mov si,offset lednum
mov byte ptr [si],0
mov byte ptr [si+1],0
mov byte ptr [si+2],0
mov byte ptr [si+3],0


pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
clear_digital_buffer endp

;蜂鸣器响 入口参数:响的频率
buzzer_sound proc
push ax
push bx
push cx
push dx
push si
push di

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
buzzer_sound endp

;写入红色灯亮,ah为行号,al为列号 注意这里 (0,0)在左下角
make_it_red proc
push ax
push bx
push cx
push dx
push si
push di

;ah,al hang lie
mov si,offset red_buffer
mov bx,ax
xor ah,ah
add si,ax
mov ax,bx
mov cl,ah
mov al,00000001B
shl al,cl
mov bl,[si]
or bl,al
mov [si],bl

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
make_it_red endp

make_it_green proc
push ax
push bx
push cx
push dx
push si
push di
;ah,al hang lie
mov si,offset green_buffer
mov bx,ax
xor ah,ah
add si,ax
mov ax,bx
mov cl,ah
mov al,00000001B
shl al,cl
mov bl,[si]
or bl,al
mov [si],bl

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
make_it_green endp

make_it_dark proc
push ax
push bx
push cx
push dx
push si
push di
;ah,al hang lie
mov si,offset red_buffer
mov bx,ax
xor ah,ah
add si,ax
mov ax,bx
mov cl,ah
mov al,11111110B
rol al,cl
mov bl,[si]
and bl,al
mov [si],bl

;ah,al hang lie
mov si,offset green_buffer
mov bx,ax
xor ah,ah
add si,ax
mov ax,bx
mov cl,ah
mov al,11111110B
rol al,cl
mov bl,[si]
and bl,al
mov [si],bl

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
make_it_dark endp

make_all_dark proc
push ax
push bx
push cx
push dx
push si
push di

mov si,offset red_buffer
mov cx,8
make_all_dark_again:
mov byte ptr [si],0
inc si
loop make_all_dark_again

make_all_dark_end:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
make_all_dark endp

clear_snake_body proc
push ax
push bx
push cx
push dx
push si
push di

mov si,offset snake_body_buffer
mov cx,128
clear_snake_body_again:
mov byte ptr [si],0
inc si
loop clear_snake_body_again

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
clear_snake_body endp


;逐列点亮所有的led灯阵 只向上提供可靠的点亮程序,其余不管(例如延时显示、定时改变颜色等)
show_led proc
push ax
push bx
push cx
push dx

push si
push di

;写入8255方式控制字
mov dx , 283h
mov al , 80h
out dx , al


;逐列刷新 红 si控制红行 di控制绿行
mov si,offset red_buffer ;控制每一列的红8行
mov di,offset green_buffer ;控制每一列的绿8行
mov cl,10000000b ;第一列 颜色通过列线进行输出,281h输出则为第零列为红,282h输出则为第零列为绿
again_show_led_col:
;红列 是控制哪一列是什么颜色的 红:281h 绿:282h 第0列为红色
mov al,cl
mov dx,281h
out dx,al
;每一列控制哪一行的红灯亮 这里有问题 对于控制行线来讲,若要红灯亮绿灯不亮则都不会亮(但是由于时间间隔很短,所以我在实验室的效果就是看起来不太亮)
mov al,[si] ;所以这里必须解决红绿灯互相影响的问题
add si,1 ;因为时按列亮灯,共用列,所以不可能在一列中同时显示红灯和绿灯。
mov dx,280h ;要不试试逐个点亮
out dx,al ;我觉得实验室器材不太好,必不能支持如此高频率的数据传送
;所以贪吃蛇只能做到食物和蛇一个颜色,让食物闪烁吧。
;mov bx ,10000
;call delay
;绿列
;mov al,cl
;mov dx,282h
;out dx,al
;每一列控制哪一行的绿灯亮
;mov al,[di]
;add di,1
;mov dx,280h
;out dx,al

;显示完每一列进行delay 以bx为参数
;mov bx,1
;call delay_short

;关闭所有的列显示
mov al,0
mov dx,281h
out dx,al
mov dx,280h
out dx,al

shr cl,1 ;下一列
cmp cl,0
jnz again_show_led_col

;趁这段时间列显示关闭,赶快将行信息输出到数码管(复用pa)
call show_digital_tube

show_led_end:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
show_led endp

;运行贪吃蛇的程序 所有按键信息由中断进行扫描,程序只管从缓冲区中读取即可
snake_gluttony proc
push ax
push bx
push cx
push dx
push si
push di

;初始化,灭掉所有的灯 并清空蛇的身体
call make_all_dark
call clear_snake_body
mov si,offset msg_snake_gluttony_game_start
call dispstr
mov bx,1000
call delay
;初始化贪吃蛇
;设置蛇的初始位置 head(3,3) tail(3,4) dir=left
mov snake_head_position_x,3
mov snake_head_position_y,3
mov ah,3
mov al,3
call make_it_red
;初始化一节身体 这里有问题
mov si,offset snake_body_buffer
mov byte ptr [si],03H ;横坐标 列 al
mov byte ptr [si+1],04H ;纵坐标 行 ah
mov ah,4
mov al,3
call make_it_red
mov byte ptr [si+2],03H
mov byte ptr [si+3],05H
mov snake_tail_position_x,3
mov snake_tail_position_y,5
mov ah,5
mov al,3
call make_it_red
;初始化一块食物
mov food_x,1
mov food_y,5
mov ah,5
mov al,1
call make_it_red
;初始化方向为left 蛇身长度为1,此时蛇身只有一个尾巴
mov snake_direction,2
mov snake_body_length,2
mov collision,0

snake_gluttony_begin:

snake_count_time:
;蛇的身体信息已经全部适配为灯亮的信息
call show_led
mov ax,sync_time_buffer
mov bl,10
div bl ;得到余数在ah中 商在al
xor ah,ah
cmp ax,[snake_time] ;只要和之前的时间不相等就刷新
jnz snake_flush
;为防止因为溢出而造成的错误判断
mov [snake_time],ax
jmp snake_count_time
;snake的时间到了,可以刷新 ;现在要解决的问题是蛇不会移动
snake_flush:
;ax中存放当前贪吃蛇时间,放入snake_time
mov [snake_time],ax
;判断按键,进行贪吃蛇的逻辑操作 ;蛇的前近方向 3:上 0:下 2:左 1:右
mov ax,keyboard_input_buffer
;处理键盘的W A S D 改变方向 10-13位存放 ;用开关连入 8255的pc 进行输入
mov cl,10
shr ax,cl
and al,00001111B
cmp al,00000001B
jz snake_fulsh_process_key_w
cmp al,00000010B
jz snake_fulsh_process_key_a
cmp al,00000100B
jz snake_fulsh_process_key_s
cmp al,00001000B
jz snake_fulsh_process_key_d

;没有任何按键按下则直接进行处理
jmp snake_flush_process

;处理按键w
snake_fulsh_process_key_w:
mov al,snake_direction
cmp al,0 ;当前方向不为下时改变方向为上
jz snake_flush_process
mov snake_direction,3
jmp snake_flush_process
;处理按键s
snake_fulsh_process_key_s:
mov al,snake_direction
cmp al,3 ;当前方向不为上时改变方向为下
jz snake_flush_process
mov snake_direction,0
jmp snake_flush_process
;处理按键a
snake_fulsh_process_key_a:
mov al,snake_direction
cmp al,1 ;当前方向不为右时改变方向为左
jz snake_flush_process
mov snake_direction,2
jmp snake_flush_process
;处理按键d
snake_fulsh_process_key_d:
mov al,snake_direction
cmp al,2 ;当前方向不为左时改变方向为右
jz snake_flush_process
mov snake_direction,1
jmp snake_flush_process


;按键处理完了,处理贪吃蛇的位置
snake_flush_process:
;现在已经得到了贪吃蛇的方向。输出检查是否成功

;将按键信息清零
snake_flush_process_clear_key:
mov ax,keyboard_input_buffer
;处理键盘的W A S D 都置为零
and ax,1100001111111111B
mov keyboard_input_buffer,ax

;位置刷新,根据上一次是否吃了食物决定身体更新策略
;若上一次刷新吃到了食物,则这一次蛇头变为蛇尾的一部分,然后蛇头向前走一步,若没有吃到,则从最后一节开始,每一节的位置时前一个的位置,第一个位置更新为蛇头的位置。
move_snake_body:
mov ax,@data
mov ds,ax
;每个位置需要两个字节
mov si,offset snake_body_buffer
mov di,snake_body_length ;其实有没有吃都从最后一节向前更新即可,无非是length的不同
dec di ;减一 方便指向每一节
shl di,1
add di,si ;指向最后一节 目标地址
mov si,di
sub si,2 ;源地址
;更新蛇的身体的每一节,第一节不更新
move_snake_body_every_section:
;先将旧的尾巴灭掉
mov ah,snake_tail_position_y
mov al,snake_tail_position_x
call make_it_dark
;更新蛇的尾巴信息 si所指向的
mov al,[si]
mov snake_tail_position_x,al
mov al,[si+1]
mov snake_tail_position_y,al
;串传送的个数 (word)第一节不传送
mov cx,snake_body_length
dec cx
mov ax,@data
mov ds,ax
mov es,ax
std ;反向传送
rep movsw

;更新第一节,将头的位置赋予即可
move_snake_body_first_section:
;高位存放纵坐标
mov si,offset snake_body_buffer
mov ah,snake_head_position_y
mov al,snake_head_position_x
mov [si],ax

;位置刷新,将贪吃蛇头朝对应方向移动一格
move_snake_head:
mov cl,snake_direction
cmp cl,3 ;上
jz move_snake_head_up
cmp cl,0 ;下
jz move_snake_head_down
cmp cl,2 ;左
jz move_snake_head_left
cmp cl,1 ;右
jz move_snake_head_right
;不可能哪个方向都不对
;jmp move_snake_head_direction_error
jmp move_snake_head_left
;下
move_snake_head_down:
mov ah,snake_head_position_y
sub ah,1
jnc snake_judge_conflict
mov ah,7 ;小于0,则从下面钻出来
jmp snake_judge_conflict
;上
move_snake_head_up:
mov ah,snake_head_position_y
add ah,1
cmp ah,8
jnz snake_judge_conflict
mov ah,0 ;等于8,超界,则从上面钻出来
jmp snake_judge_conflict
;左
move_snake_head_left:
mov al,snake_head_position_x
sub al,1
jnc snake_judge_conflict
mov al,7 ;小于0,则从下右面钻出来
jmp snake_judge_conflict
;右
move_snake_head_right:
mov al,snake_head_position_x
add al,1
cmp al,8
jnz snake_judge_conflict
mov al,0 ;等于8 ,则从左面钻出来
jmp snake_judge_conflict
;判断是否冲突
snake_judge_conflict:
;此时 蛇头坐标为(ah,al)(行列) ah中存放纵坐标,代表行值,al存放横坐标,代表列值
mov snake_head_position_y,ah ;行,纵坐标
mov snake_head_position_x,al

call show_snake_info

;首先判断是否蛇头和食物重合,若没有重合则蛇尾向前一步走,若重合则蛇尾不动,将蛇的身体信息全部向后移动一位
;清空上一次吃到食物的信息
;将当前食物位置存放在(bh,bl)中和(ah,al)比较
mov bh,food_y ;行
mov bl,food_x ;列
mov food_eaten,0
mov ah,snake_head_position_y
mov al,snake_head_position_x
cmp al,bl
jnz snake_judge_conflict_not_eat
cmp ah,bh
jnz snake_judge_conflict_not_eat
;下面时蛇吃了食物
mov food_eaten,1
add snake_body_length,1
;生成新的食物 位置为(7-当前蛇头x,7-当前蛇尾y)
snake_create_new_food:
;原来的食物不用灭,因为现在蛇头在这里
mov ah,food_y
mov al,food_x
call make_it_dark
mov ax,7
mov bl,snake_head_position_x
sub ax,bx
mov food_x,al
mov ax,7
mov bl,snake_tail_position_y
sub ax,bx
mov food_y,al

;蛇这一步没有吃到食物
snake_judge_conflict_not_eat:
mov food_eaten,0

;判断是否碰撞到自己 头坐标(al,ah) 身体坐标(bl,bh)
snake_judge_collision:
mov ah,snake_head_position_y
mov al,snake_head_position_x
mov si,offset snake_body_buffer
mov cx,snake_body_length
snake_judge_collision_again:
mov bl,[si]
mov bh,[si+1]
add si,2
cmp ah,bh
jnz snake_judge_collision_again_not_collision
cmp al,bl
jnz snake_judge_collision_again_not_collision
;碰撞了
snake_judge_collision_again_collision:
mov collision,1
jmp snake_gluttony_end
snake_judge_collision_again_not_collision:
loop snake_judge_collision_again


;在led点阵中点亮蛇阵 使用红色
snake_lighten_led_red:
;首先点亮食物
;写入红色灯亮,ah为行号,al为列号 注意这里 (0,0)在左下角 即ah为y al为x
mov ah,food_y
mov al,food_x
call make_it_red
;点亮头
mov ah,snake_head_position_y
mov al,snake_head_position_x
call make_it_red
;点亮身体 记得在更新身体的时候灭掉尾巴的灯
mov si,offset snake_body_buffer
mov cx,snake_body_length
dec cx
snake_lighten_led_red_body:
mov al,[si]
mov ah,[si+1]
call make_it_red
add si,2
loop snake_lighten_led_red_body

jmp snake_gluttony_begin

snake_gluttony_end:

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
snake_gluttony endp

;延时 bx为传入的参数
delay proc
push ax
push bx
push cx
push dx
push si
push di
again1_delay:
mov cx , 0
again2_delay:
nop
loop again2_delay
dec bx
jnz again1_delay
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
delay endp

;短延时 bx为传入的参数 为了增加亮度
delay_short proc
push ax
push bx
push cx
push dx
push si
push di
again1_delay_short:
mov cx , 0ffh
again2_delay_short:
nop
loop again2_delay_short
dec bx
jnz again1_delay_short
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
delay_short endp

;8253 设置方式控制字为 00110100 计时器0先低后高方式二二进制 初值为 1000 连在1MHZ上 每10ms进行一次中断 out口接在主板irq上(主板irq已经固定在了irq10)

set_int proc
push ax
push bx
push cx
push dx
push si
push di
cli
;mov si,offset msgsetint
;call dispstr
;设置8253
mov dx,28bh
mov ax,00110110B
out dx,ax
mov dx,288h
mov ax,1000
out dx,al
mov al,ah
out dx,al

;设置中断处理程序 irq10使用0B号中断
mov ax,3572h
int 21h
mov int_0b_seg,es
mov int_0b_off,bx
push ds
mov ax,seg int_irq10
mov ds,ax
mov dx,offset int_irq10
mov ax,2572h
int 21h
pop ds
;imr
in al,0a1h
and al,0fbh
out 0a1h,al

;EOI
mov al,20h
out 0a0h,al
out 20h,al

sti

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
set_int endp

;中断处理程序 每一次中断时10ms,这10ms用来扫描输入和同步时间量
int_irq10 proc
push ax
push bx
push cx
push dx
push si
push di

;mov si,offset msgintirq10
;call dispstr
;设置时间量
add sync_time_buffer,1

;键盘输入 键盘可以用的话就用键盘吧
call keyboard_input

;红外输入
;call infrared_input

;EOI
mov al,20h
out 0a0h,al
out 20h,al
int_irq10_end:

pop di
pop si
pop dx
pop cx
pop bx
pop ax
iret
int_irq10 endp

dispstr proc
push ax
push bx
push cx
push dx
push si
push di
dps1:
mov al,[si]
cmp al,0
jz dps2
mov bx,0
mov ah,0eh
int 10h

inc si
jmp dps1
dps2:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
dispstr endp


show_snake_info proc
push ax
push bx
push cx
push dx
push si
push di

mov si,offset msg_direction
call dispstr
mov al,snake_direction
call dispuib
call dispcrlf

mov si,offset msg_snake_body_position
call dispstr
;头
mov al,snake_head_position_x
call dispuib
mov al,snake_head_position_y
call dispuib
;身
mov si,offset snake_body_buffer
mov ax,snake_body_length
mov cl,1
shl ax,cl
mov cx,ax
snake_show_body_again:
mov al,[si]
inc si
call dispuib
loop snake_show_body_again

call dispcrlf

pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
show_snake_info endp

;清屏
clear_screen proc
push ax
push bx
push cx
push dx
push si
push di

mov ax,0b800h
mov es,ax
mov cx,4000
xor si,si
clear_screen_again:
mov byte ptr es:[si],0
inc si
loop clear_screen_again

;入口参数:AH=02H
;BH=显示页码
;DH=行(Y坐标)
;DL= 列(X坐标)
;设置光标位置
mov ax,02h
mov bh,0
mov dx,0
int 10h


pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
clear_screen endp

end start