51汇编:LED点阵与键盘按键
吐槽
用C51控制这两个内容都挺简单,用汇编的话。。。。。也一样吧。
(无论是51汇编还是C51,我在代码里都写有许多蹩脚的英文。原因是Keil5对中文编码支持不好,当从Keil上粘贴下代码或是粘贴上代码,都会变成乱码。。。)
让LED点阵显示0
C51版本可以参考两个月前的文章:C51玩8^2LED点阵:Point_Game
如果只是用两个8端口来控制88LED点阵倒还简单,但是我的单片机的行选必须通过一个译码器74HC595。由三个端口:P35(存储寄存器时钟信号MY_RCLK)\P34(串行输入口MY_SER)\P36(移位寄存器时钟信号MY_SRCLK)三个控制口来进行行选(D7~D0)。
org 0000h
ljmp main
main:
mov r0,#0 ;count time ready
mov r1,#7fh ;turning display ready
mov dptr,#Led_Rows_Table ;table info ready
LOOP:
lcall Select_Cols
lcall Select_Rows
lcall delay
mov p3,0
inc r0
cjne r0,#8,LOOP
ljmp main
;LED array
; P07 P06 P05 P04 P03 P02 P01 P00
;D7
;D6
;D5
;D4
;D3
;D2
;D1
;D0
;
delay:
mov r6,#8eh
DL0:
mov r5,#02h
djnz r5,$
djnz r6,DL0
ret
Select_Cols:
mov p0,r1
mov a,r1
rr a ;turn right
mov r1,a
ret
Select_Rows:
mov a,r0
movc a,@a+dptr
clr p3.5 ;MY_RCLK=0
clr p3.6 ;MY_SRCLK=0
clr c
mov r4,#8
Move:
rlc a
mov p3.4,c ;MY_SER=MY_DATA>>7
setb p3.6 ;MY_SRCLK=1
nop
nop
clr p3.6 ;MY_SRCLK=0
djnz r4,Move
setb p3.5 ;MY_RCLK=1
ret
Led_Rows_Table:
db 00h, 3Eh, 41h, 41h, 41h, 3Eh, 00h, 00h
- MY_DATA是一个8位数据
- MY_RCLK是74HC595中的存储寄存器时钟信号,上升沿有效。
- MY_SRCLK是74HC595中的移位寄存器时钟信号,上升沿有效。
注意:
1,显示的字符和我要显示的颠倒怎么办?
- 可以把Select_Rows函数内 rlc 改为 rrc
- 也有可能是用取字模软件时,把字序颠倒了
2,如果led点阵没有显示(全亮或者全灭怎么办)?
别忘了74HC595串转并寄存器的使用。JOE端需要接GND保证74HC595芯片OE输入端有效(表示并行输出有效)。
3,如果显示重影怎么办?
对于这个问题,我束手无措。如果是C51写的,常见的解决方法是要把通选(这里的列选)放在前面,再选另一边(这里是行选)。然后延迟,最后对另一边消隐就行。但是
用51汇编写我仍然遵从了这些原则,但是始终有重影的问题。
- 可能是74HC595芯片的传输延迟,你在做的时候可以考虑考虑。
这个问题反正我是解决不了。如果是上面这款单片机的话,把 lcall delay 放在列选和行选之间,可以减少重影的效果,但是仍然有重影。
检测键盘按键
矩阵键盘的逻辑图
行选是P17\P16\P15\P14
列选是P13\P12\P11\P10
按键检测原理
如果某个按键被按下,那么对应的行和列的状态就是一样的,都是零。
第一种检测方案:
给P1口置数0000 1111,如果后4位有某个1变为0,那么就找到按键所在列。同理,给P1口置数1111 0000,如果前4位有某个1变为0,那么就找到按键所在行。行列都找到了,按键位置就确定了。
第二种解决方案:
给P1口输入0111 1111,然后用0000 1111 “与” 上P1的数据来屏蔽高4位。如果低4位哪个是0,那么就能直接确定按键的位置。如果低4位都没有,就给P1口输入1011 1111来选下一行判断。然后是1101 1111、1110 1111
因为第二种方案的代码量一般是第一种的两倍多(C51写的话第二种4个switch,第一种2个switch)。所以,通常使用第一种方案。
这两种按键检测方法都是因为单片机上的矩阵按键是非偏码键盘的缘故,如果是实际的电脑键盘编码键盘就是先由键盘上的芯片收集按键信息,将其转变成键码再传给电脑
用汇编编按键检测的程序需要考虑得细一点。实现的话会遇到下面几个问题:
- 等待按键输入。
- 分情况进行散转。
- 将按键信息转变为散转的位置信息。
- 通过标记位置的变量查表,并显示在数码管上。
1. 等待按键输入。
就是两个等待输入的循环,如果有输入就往下跳。(也就是两个 if 模块)
两个等待之间需要有延迟消除抖动。
2. 分情况进行散转。
散转是程序里最复杂的内容了。
在进行找行的过程中,需要用到散转。散转就是多分支,一个 jmp 可以跳转到不同的地方,这样就实现了 switch 的功能。
要注意的是,一般散转进散转表后,再次跳转用的是AJMP(中跳,2字节) 或者 LJMP (长跳,3字节),不能用 SJMP (短跳,1字节)。
通过 A 来查表时,A 内保存的是0~N的跳转的位置。比如我要跳转到 _R2 ,但是我现在 A 的值是 1 ,而我用的是 LJMP (3字节),所以我需要把 A 乘以 3,才能跳到 ljmp _R2 的位置上去。
3. 将按键信息转变为散转的位置信息。
前面讲原理部分说过,我如果给P1设置0000 1111的话,假如低4位有零,那么我就找到按键所在列。找行是同样的道理。给P1设置1111 0000的话,假如高4位有零,那么我就找到按键所在行。而找行,找列都是看4位,所以把高低4位调换一下顺序( SWAP A ,交换 A 高低4位
)后,找行和找列就是一样的了。
比如:
0000 0111 -> 0
0000 1011 -> 1
0000 1101 ->2
0000 1110 ->3
这样的转变规则可以用减一判断加循环的结构实现。
(因为找列的时候直接可以得到0,1,2,3的列号,所以找列的时候就没用散转了!)
4. 通过标记位置的变量查表,并显示在数码管上。
这是最简单的内容了。只需要掌握查表就行了。
r0 寄存器保存着按键的位置。从0到15
p0 控制数码管显示
数据表的位置需要放在一些函数的边缘,不要放在函数体内。db 数据写成一行和写成几行是一样的,都是从某个内存一个个字节保存进数据。
键盘检测示例完整的代码:
org 0000h
ljmp main
main:
mov r0,#0
START:
lcall light_led
lcall keyboard_scan
ljmp START
;keyboard array
;P17 s1 s2 s3 s4
;P16 s5 s6 s7 s8
;P15 s9 s10 s11 s12
;P14 s13 s14 s15 s16
; P13 P12 P11 P10
;
keyboard_scan:
mov r0,#0 ;calculate the right position for pressed
mov p1,#0fh ;initialize the port
message:
mov a,p1
cjne a,#0fh,right
ljmp message
right:
lcall delay ;eliminating jitter
message1:
mov a,p1
cjne a,#0fh,right1
ljmp message1
right1:
;----------------------------------------
;search the vary column
mov p1,#0fh ;0000 1111
mov a,p1
;how to translate the code
;#07H to #1
;#0bH to #2
;#0dH to #3
;#0eH to #4
lcall TRANS
mov r0,a
;------------------------------------------
;search the vary row
mov p1,#0f0h ;1111 0000
mov a,p1
;how to translate the code
;# 70H to #1
;#0b0H to #2
;#0d0H to #3
;#0e0H to #4
SWAP a
lcall TRANS
;value of a had times three
mov r2,a
rl a
add a,r2
mov dptr,#TAB_ROWS
jmp @ a+dptr
TAB_ROWS:
ljmp _R1
ljmp _R2
ljmp _R3
ljmp _R4
_R1:
ljmp _RETURN1
_R2:
mov a,r0
add a,#4
mov r0,a
ljmp _RETURN1
_R3:
mov a,r0
add a,#8
mov r0,a
ljmp _RETURN1
_R4:
mov a,r0
add a,#12
mov r0,a
ljmp _RETURN1
_RETURN1:
lcall delay ;eliminating jitter
ret
light_led:
mov dptr,#TAB_SHOW_LED
mov a,r0
movc a,@ a+dptr
mov p0,a
ret
TRANS:
mov r2,#3
clr c
TRANS_LOOP:
rrc a
jnc _RETURN_ ;till c become zero
djnz r2,TRANS_LOOP
_RETURN_:
mov a,r2
ret
TAB_SHOW_LED:
db 3fh,06h,5bh,4fh ;0123
db 66h,6dh,7dh,07h ;4567
db 7fh,6fh,77h,7ch ;89ab
db 39h,5eh,79h,71h ;cdef
db 00h
delay:
mov r6,#05h
DL0:
mov r5,#32h
djnz r5,$
djnz r6,DL0
ret
代码在两款单片机上的显示效果:
还有按键消除抖动做的不好。数字时常不对。上面这个可能是因为单片机片选有初值,所以有选择不同的数码管。而片选这个无所谓。
这款单片机要把JOE短接VCC。在按键时有响声是因为按键输入是靠P1,而P1.5连接了蜂鸣器。
按键控制LED点阵
将按键检测显示和LED点阵结合起来,只需要注意标号和函数名的命名别重复,数据表安排好就行。
下面列的是这个程序单片机几个寄存器的作用。
R0:保存按键检测后获得的键值0~15(f)
R1:LED点阵列选
R2:按键检测子函数TRANS的中间变量
R3:未用
R4:未用
R5:延时
R6:延时
R7:LED点阵行选
P0: LED点阵列选
P1:按键检测
P3:三个端口P35\P34\P36控制和传输数据给74HC595串转并移位寄存器
因为数码管也是P0控制,所以把数码管显示的函数与其对应的#TAB_SHOW_LED删除了。
难点是对于16个符号,每个符号的动态显示需要8个字节的数据,总共就是一张16X8的大表格了,如何把查表时用的偏置(累加器A的值)定位到特定的字符的那一行呢???
第一行是0字符,相对地址(偏置)是从0到7
第二行是1字符,地址是从8到15
第三行是2字符,地址是从16到23
所以,只需要每增加一个键值,就增加8个单元地址就行。
按键检测并在LED点阵上显示的示例的完整代码:
org 0000h
ljmp main
main:
mov r0,#0 ;key value initialize
;keyboard detect
lcall keyboard_scan
;display
mov r7,#0 ;count time ready
mov r1,#7fh ;turning display ready
mov dptr,#Led_Rows_Table ;table info ready
LOOP:
lcall Select_Cols
lcall delay
lcall Select_Rows
inc r7
cjne r7,#8,LOOP
ljmp main
;keyboard array
;P17 s1 s2 s3 s4
;P16 s5 s6 s7 s8
;P15 s9 s10 s11 s12
;P14 s13 s14 s15 s16
; P13 P12 P11 P10
;
keyboard_scan:
mov r0,#0 ;calculate the right position for pressed
mov p1,#0fh ;initialize the port
message:
mov a,p1
cjne a,#0fh,right
ljmp message
right:
lcall delay ;eliminating jitter
message1:
mov a,p1
cjne a,#0fh,right1
ljmp message1
right1:
;----------------------------------------
;search the vary column
mov p1,#0fh ;0000 1111
mov a,p1
;how to translate the code
;#07H to #1
;#0bH to #2
;#0dH to #3
;#0eH to #4
lcall TRANS
mov r0,a
;------------------------------------------
;search the vary row
mov p1,#0f0h ;1111 0000
mov a,p1
;how to translate the code
;# 70H to #1
;#0b0H to #2
;#0d0H to #3
;#0e0H to #4
SWAP a
lcall TRANS
;value of a had times three
mov r2,a
rl a
add a,r2
mov dptr,#TAB_ROWS
jmp @ a+dptr
TAB_ROWS:
ljmp _R1
ljmp _R2
ljmp _R3
ljmp _R4
_R1:
ljmp _RETURN1
_R2:
mov a,r0
add a,#4
mov r0,a
ljmp _RETURN1
_R3:
mov a,r0
add a,#8
mov r0,a
ljmp _RETURN1
_R4:
mov a,r0
add a,#12
mov r0,a
ljmp _RETURN1
_RETURN1:
lcall delay ;eliminating jitter
ret
TRANS:
mov r2,#3
clr c
TRANS_LOOP:
rrc a
jnc _RETURN_ ;till c become zero
djnz r2,TRANS_LOOP
_RETURN_:
mov a,r2
ret
;only use the two registers r6 and r5
;delay 10ms
delay:
mov r6,#14h
DL0:
mov r5,#0f8h
djnz r5,$
djnz r6,DL0
ret
;LED array
; P07 P06 P05 P04 P03 P02 P01 P00
;D7
;D6
;D5
;D4
;D3
;D2
;D1
;D0
;
Select_Cols:
mov p0,r1
mov a,r1
rr a ;turn right
mov r1,a
ret
;through the microcontroller 74HC595 to move information
Select_Rows:
;how to select the right data from table #Led_Rows_Table
;select one data from the right column
;(a)=r7 + r0 * 8
mov a,r0
rl a
rl a
rl a
add a,r7
movc a,@ a+dptr
clr p3.5 ;MY_RCLK=0
clr p3.6 ;MY_SRCLK=0
clr c
mov r4,#8
Move:
rlc a
mov p3.4,c ;MY_SER=MY_DATA>>7
setb p3.6 ;MY_SRCLK=1
nop
nop
clr p3.6 ;MY_SRCLK=0
djnz r4,Move
setb p3.5 ;MY_RCLK=1
ret
Led_Rows_Table:
db 00h, 3Eh, 41h, 41h, 41h, 3Eh, 00h, 00h ;0
db 00h, 00h, 00h, 21h, 7Fh, 01h, 00h, 00h ;1
db 00h, 21h, 43h, 45h, 49h, 31h, 00h, 00h ;2
db 00h, 00h, 22h, 41h, 49h, 36h, 00h, 00h ;3
db 00h, 0Ch, 14h, 24h, 7Fh, 04h, 00h, 00h ;4
db 00h, 72h, 51h, 51h, 51h, 4Eh, 00h, 00h ;5
db 00h, 7Eh, 91h, 91h, 91h, 4Eh, 00h, 00h ;6
db 00h, 0C0h, 80h, 80h, 80h, 0FFh, 00h, 00h ;7
db 00h, 6Eh, 91h, 91h, 91h, 6Eh, 00h, 00h ;8
db 00h, 72h, 89h, 89h, 89h, 7Eh, 00h, 00h ;9
db 00h, 3Eh, 48h, 0C8h, 48h, 3Eh, 00h, 00h ;a
db 00h, 0FFh, 91h, 91h, 92h, 6Ch, 00h, 00h ;b
db 00h, 3Ch, 42h, 81h, 81h, 42h, 00h, 00h ;c
db 00h, 0FFh, 81h, 82h, 44h, 38h, 00h, 00h ;d
db 00h, 0FFh, 89h, 89h, 89h, 89h, 00h, 00h ;e
db 00h, 0FFh, 90h, 90h, 90h, 90h, 00h, 00h ;f
显示效果:
帧率太低了。