微机接口课程设计之远程控制步进电机实验

1.课程设计目的

  1. 熟悉各常用可编程接口芯片的工作原理、使用方法及引脚连接特性,正确操作实验设备,能根据设计要求制定总体方案,完成硬件连线,承担责任。
  2. 能够根据要求编写出满足功能要求的软件代码,能够利用开发工具进行设计调试、协助分析运行结果、定位设计错误等。
  3. 具有创新意识。尝试对所要求的功能设计提出有效的改进设想并努力实现;或尝试增加新的系统功能并努力实现。
  4. 具有对微机输入输出系统进行需求分析的能力,能针对具体的问题获取新知识完成系统的设计。
  5. 能够以口头和书面方式准确地描述、总结所完成的设计和主要成果,撰写比较规范的课程设计报告。
  6. 掌握综合控制键盘、数码管、步进电机的能力。
  7. 掌握有关中断服务程序的编制方法。

2. 需求分析

2.1 项目背景

远程控制技术在工业自动化领域有着广泛应用。本项目旨在通过远程控制步进电机系统,模拟工业现场中常见的远程精确控制场景。通过本项目,可以深入理解串行通信、电机控制等关键技术。

2.2 功能需求

2.2.1 用户需求

  1. 控制机功能需求:
    • 通过键盘输入控制命令
    • 实时显示电机位置信息
    • 支持多种控制模式(速度控制、步数控制)
    • 提供紧急停止功能
  2. 执行机功能需求:
    • 准确解析并执行控制命令
    • 实时计算并反馈位置信息
    • 控制步进电机运动
    • 本地显示位置信息

2.2.2 系统需求

  1. 功能性需求: 命令|功能 ---|--- A|停步进电机 B##|正转(速度:##转/分)(顺时针) C##|反转(速度:##转/分)(逆时针) D##|正向步进##步(顺时针) E##|反向步进##步(逆时针) F |程序结束

    位置信息格式 说明
    0### 正向位置(范围0~999)
    -### 反向位置(范围-999~0)
  2. 性能需求:

    • 命令响应时间 < 100ms
    • 位置信息更新频率 ≥ 10Hz
    • 步进电机速度范围:1~99转/分
    • 位置计数范围:±1000步

3.实验设备

本实验使用的主要设备包括: 1. 两套 SUN ES86PCIU+ 实验仪,分别作为: - 控制机:负责发送控制命令 - 执行机:负责接收命令并控制步进电机 2. 其中使用的主要硬件组件为: - 8255 可编程并行接口芯片 - 8259 可编程中断控制器 - 8253 可编程定时器 - 8251 串行通信接口 - 2×4 矩阵键盘 - 8 位数码管显示模块 - 步进电机(4相) 3. 开发环境:星研集成开发环境

4.硬件设计

4.1 系统硬件框图

4.2 关键硬件模块设计

4.2.1 步进电机驱动模块设计

  1. 原理图介绍与分析

    • 步进电机的步距角为18度(即转一圈走20步),采用单极型驱动1,其两个中心抽头(即图中的5线和6线)接VCC,因此驱动电机要给低电平。

    • ULN2003A是一个达林顿阵列驱动器,其包含7组达林顿对,每组达林顿对的工作原理是反相输出2,即输入为高电平则输出为低电平。我们在ULN2003A输入前加了四个非门,所以驱动电机我们要控制PC口输出的是低电平。

    • 从原理图中我们可以看出,顺时针运转需要依次给 D -> C -> B -> A 进行通电,逆时针则是 A -> B -> C -> D。 8255的PC4、PC5、PC6、PC7分别接电机的A、B、C、D,因此8255的PC4~PC7要配制成输出模式。

      驱动方式 逆时针(顺时针只需倒序激励序列)
      单四拍 A->B->C->D->A
      双四拍 AB->BC->CD->DA->AB
      单双八拍 A->AB->B->BC->C->CD->D->DA->A
  2. 原理简述(以图中步距角为45度的简单步进电机为例)3

    步进电机原理图

    复习一下安培定则:用右手握住通电螺线管,让四指指向电流的方向,那么大拇指所指的那一端是通电螺线管的N极。

    如图所示,电机的转子有磁性,我们在定子铁芯上绕上线圈,根据安培定则可知改变通过线圈的电流的方向就可以改变它的极性,为简化驱动,我们没有采用需要改变电流方向的双极型电机,而是采用类似上图所示的单极型。图中红色代表N极,蓝色代表S极,以单四拍为例,给D通电,转子的N极与定子的S极一一对应,之后再给C通电,通过同性相斥、异性相吸的动力就可以驱动电机旋转。由于中心抽头的存在,我们一次只使用了半个线圈,所以两个绕组可以当成四个来用,即四相。

  3. 速度调节: 以单四拍为例,给A通电,电机逆时针旋转18度,过一秒后给B通电,电机再逆时针旋转18度,依次类推,每一秒通一次电就走一步,而转一圈要20步,因此此时电机转一圈要20秒,即速度为3r/min。由此可知,我们通过调整两次通电间的时间间隔(比如上述的1秒),就可以调节电机的转速。要精准地控制时间间隔,我们采用了8253定时器来完成这一点。

    在速度为99r/min时,1min要走99 * 20 = 1980步,时间间隔约为0.03秒,在速度为1r/min时,1min走20步,时间间隔约为3s,公式表示一下时间间隔的计算即:\(n\ (r/min)\) 要求延时 \(= \frac{60\text{秒}}{n \times 20\text{步}} = \frac{3}{n} \text{秒}\)

    8253定时器的计数初值最多可以有16为,二进制下最大是65535。若根据不同的速度要求给定时器置入不同的计数初值,将定时器的out用作中断,就可以在每一次中断发生时给线圈通电,以达到调速的目的。假设给8253的定时器0接 1Hz 的时钟频率,则计数初值的计算公式表示为:\(n\ (r/min)\) 要求计数初值 \(= {3 \over n}s = {3000 \over n}ms\) ,在 65535 的范围之内。

    但是本次实验没有采用上述方式,而是固定计数初值,另设一个speed_delay变量来保存不同的速度下的延时,其单位为中断发生的次数,且8253定时器0接的是1MHz的时钟。这样,speed_delay的计算公式为:\({3\ \times 10 ^6 us \over {n\times N}}\) ,其中 \(N\) 为计数初值,在本次实验中N 取150,则speed_delay \(= {20000 \over n}\)

    由于采用了中断的方式,因此我们还使用了8259芯片,该芯片的参数配置为:边沿触发,单片方式,中断类型号高五位为 00001,一般全嵌套,缓冲方式,非自动结束,只开放 IR0 引脚的中断请求,即将 8253 的out0 连接到 IR0上。

4.2.2 通信模块设计

  1. 通信方式选择依据: 由于并行通信的8255已被其他器件(如数码管、按键)占用,且并行传输距离过短,无法支持两个实验箱的距离,因此我们采用了串行通信的方式。 实验箱虽有RS485模块,但我们不需要多点通信,RS232的一对一已足够完成本实验所需功能4。可惜实验中缺少RS232的连接线,因此我们将两个实验箱靠的较近,以使通信距离较短,进而可以直接采用TTL电平的方式通信,我们选用了8251芯片,并使控制机和执行机的8251芯片的RxD和TxD交叉相连(其中RxD为接受,TxD为发送,即发送对接受,接受对发送)。

  2. 8251通信参数配置

    • 通信模式:异步通信
    • 数据位:8位
    • 停止位:1位
    • 校验方式:偶校验
    • 波特率:4800bps
    • 波特率系数:16倍
    • 工作模式:全双工(同时支持发送和接收)
  3. 可靠性考虑

    • 由于线路连接比较简陋,选择较低波特率(4800)以提高通信可靠性
    • 采用偶校验位进行错误检测(但是本此实验中软件程序并没有对其处理,未来可以增加以提高通信的可靠性)
    • 确保共用同一地(GND)参考电平

4.2.3 按键模块设计

  1. 原理图介绍

    实验箱上的矩阵键盘部分虽然看上去是 \(4 \times 4\) 的,但是通过原理图我们可知实际上这是一个 \(2 \times 8\) 的矩阵键盘。键盘的列线接8255的PB口,有两个行线,其中 KL1 接 PC0,KL2接PC1。

  2. 按键扫描分析:从原理图中可知,行线KL1和KL2接了一个上拉电阻到VCC,因此当没有按键按下时为高电平,采用逐列扫描的方式,向某一列输出低电平(而其他列为高电平),当某个按键被按下时,对应的行线就会被拉低,因此通过检测到的低电平行线和当前扫描列的组合可以得到该按键的行列码。因此也可知8255的PC0~PC3配置成输入模式,而PB口配置成输出模式。

  3. 按键消抖处理

    由机械按键的抖动特性:

    1. 机械按键的抖动时间一般在5ms~10ms之间

    2. 一般的机械按键按下持续时间不会低于100ms

    因此我们采用延迟重采样的方式,一次延时间隔约10ms,一共重采样2次

4.2.4 数码管显示模块设计

  1. 原理图介绍

    数码管是共阳极的,段码和位选都是低电平有效,其段码接8255的PA口,位选接8255的PB口,因此8255的PB口和PA口都要配置成输出模式。

  2. 数码管扫描频率:扫描频率太低数码管会出现闪烁的现象,频率太高则亮度不够甚至无法看清,所以一般扫描间隔多为1ms或几毫秒,我们需要控制一下这个间隔时间。 根据对实验箱CPU模块的观察以及上网查找到的资料,本次实验的8086芯片由 SAB 8284B-P 时钟发生器和驱动器芯片提供系统时钟,SAB 8284B-P 外接的是 DX-A1V 12.000 晶振,最终供给8086的应该是8MHz5。 由此我们计算一下位扫描间隔时间,在delay中使用loop指令空循环以达到延时的目的,查阅资料可知在8086中loop指令在成功跳转情况下的时钟周期6是17,假设我们循环500次,这将耗费8500个时钟周期,为8500/8MHz = 1.0625ms,完全合适。

4.3 连线说明

  1. 控制机

    D3区(8255):CS、A0、A1 —— A3区:CS1、A0、A1
    D3区:PC0、PC1 —— F5区:KL1、KL2
    D3区:JP20(PB)、B、C —— F5区:A、B、C
    B3区:CS、A0 —— A3区:CS3、A0
    B3区:INT、INTA —— A3区:INTR、INTA
    C4区(8253):CS、A0、A1 —— A3区:CS2、A0、A1
    C4区:GATE —— C1区:VCC
    C4区:CLK1 —— B2区:2M
    C4区:OUT1 —— C3区:RxC TxC
    C3区(8251):CS、C/D —— A3区:CS4、A0
    C3区:CLK —— B2区:4M
    C3区:RXD、TXD —— C3区(执行机):TXD、RXD
  2. 执行机

    D3区(8255):CS、A0、A1 —— A3区:CS1、A0、A1
    D3区:PC4、PC5、PC6、PC7 —— D1区(步进电机):A、B、C、D
    B3区(8259):CS、A0 —— A3区:CS3、A0
    B3区:INT、INTA —— A3区:INTR、INTA
    C4区(8253):CS、A0、A1 —— A3区:CS2、A0、A1
    C4区:GATE —— C1区:VCC
    C4区:CLK0 —— B2区:1M
    C4区:OUT0 —— B3区:IR0
    C4区:CLK1 —— B2区:2M
    C4区:OUT1 —— C3区:RxC TxC
    C3区(8251):CS、C/D —— A3区:CS4、A0
    C3区:CLK —— B2区:4M
    C3区:RXD、TXD —— C3区(控制机):TXD、RXD

5.软件设计

5.1 数据定义

5.1.1 常量定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;各外设端口地址
IO8259_0 EQU 0250H
IO8259_1 EQU 0251H
Con_8253 EQU 0263H
T0_8253 EQU 0260H
T1_8253 EQU 0261H
IO8255_Con EQU 0273H
IO8255_PC EQU 0272H
Con_8251 EQU 0241H
Dat_8251 EQU 0240H

; 8251数据传递的帧头和帧尾
Fram_Head EQU 0FAH
Fram_Tail EQU 0FBH

5.1.2 变量定义

1
2
3
4
5
6
;控制机
CurrentPos DW 0 ; 当前位置计数,范围-1000到1000
Command DB 0 ; 当前命令
DisplayBuf DB 8 DUP(10H) ; 显示缓冲区
IObuf DB 4 DUP(10H)
Sign DB 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
;执行机
StepControl DB 33H ; 步进电机控制值
CurrentPos DW 0 ; 当前位置计数,范围-1000到1000
TargetSteps DW 0 ; 目标步数
Speed DW 0 ; 速度(转/分)
StepDelay DW 0 ; 转动一步后,延时常数
StepDelay1 DW 0 ; 备份
bRunning DB 0 ; 电机运行标志
Direction DB 0 ; 0=顺时针,1=逆时针
Command DB 0 ; 当前命令
Param DW 0 ; 命令参数
DisplayBuf DB 8 DUP(10H) ; 显示缓冲区
IObuf DB 4 DUP(10H)
Sign DB 0

5.2 各外设初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
;CLK0 = 1M Hz	
Init8253_T0 PROC NEAR
MOV DX, Con_8253
MOV AL, 35H
OUT DX, AL ;计数器T0设置在模式2状态, BCD码计数

MOV DX, T0_8253
MOV AL, 50H
OUT DX, AL
MOV AL, 01H
OUT DX, AL ;计数初值为150
RET
Init8253_T0 ENDP
1
2
3
4
5
6
7
8
9
10
Init8255	PROC	NEAR	
MOV DX, IO8255_Con
MOV AL, 81H
OUT DX, AL ;8255 PC输出
DEC DX
MOV AL, 0FFH
OUT DX, AL ;0FFH->8255 PC口中的PC4~PC7 输出高电平给电机的ABCD,即没有通电流,电机不转
;PC0~PC3会发生什么???
RET
Init8255 ENDP
1
2
3
4
5
6
7
8
9
10
11
12
13
Init8259	PROC	NEAR	
MOV DX, IO8259_0 ;ICW1
MOV AL, 13H
OUT DX, AL
MOV DX, IO8259_1 ;ICW2,中断类型号搞五位为1,所以IR0是08H
MOV AL, 08H
OUT DX, AL
MOV AL, 09H ;ICW4
OUT DX, AL
MOV AL, 0FEH ;OCW1,开放IR0
OUT DX, AL
RET
Init8259 ENDP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
; 8251初始化子程序
Init8251 PROC NEAR
CALL Reset8251
MOV DX,Con_8251
; 0111 1110
MOV AL,7EH ;波特率系数为16,8个数据位
OUT DX,AL ;一个停止位,偶校验
CALL DLTIME ;延时
; 0001 0101
MOV AL,15H ;允许接收和发送发送数据,清错误标志
OUT DX,AL
CALL DLTIME
RET
Init8251 ENDP
1
2
3
4
5
6
7
8
9
10
11
12
;CLK1 = 2M Hz		
Init8253_T1 PROC NEAR
MOV DX,Con_8253
; 01 - 01 - 011 - 1
MOV AL,57H ;定时器1,方式3
OUT DX,AL

MOV DX,T1_8253
MOV AL,26H ;BCD码26(2000000/26)=16*4800
OUT DX,AL
RET
Init8253_T1 ENDP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Reset8251	PROC	NEAR	
MOV DX,Con_8251
MOV AL,0
OUT DX,AL ;向控制口写入"0"
CALL DLTIME ;延时,等待写操作完成
OUT DX,AL ;向控制口写入"0"
CALL DLTIME ;延时
OUT DX,AL ;向控制口写入"0"
CALL DLTIME ;延时
MOV AL,40H ;向控制口写入复位字40H
OUT DX,AL
CALL DLTIME
RET
Reset8251 ENDP

5.3 控制机程序

5.3.1 控制机主程序

控制机主程序

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
; 控制机主程序
CONTROL_MAIN PROC NEAR
CALL Init8251
CALL Init8253_T1
CALL InitKeyDisplay

CONTROL_LOOP:
LEA SI, DisplayBuf
CALL Display8
CALL GetKeyA ; 读取键盘输入
JNB CHECK_POSITION ; 无按键则检查位置信息
CMP AL, 0AH ; 检查命令类型
JB CHECK_POSITION
MOV Command, AL ; 保存命令
; 发送命令
CALL Sendbyte ; 发送命令

; 如果是B, C, D, E命令,还需要发送参数
CMP AL, 0AH ; A命令
JE CHECK_POSITION
CMP AL, 0FH ; F命令
JE CONTROL_EXIT

; 读取并发送参数
CALL ReadTwoDigits
CALL Sendbyte
MOV AL, AH
CALL Sendbyte

CHECK_POSITION:
; 检查是否有位置信息
MOV DX, Con_8251
IN AL, DX
TEST AL, 02H ; 测试接收缓冲
JZ CONTROL_LOOP

; 接收并显示位置信息
; CALL RecvByte
; MOV Direction, AL
; CALL RecvByte
; MOV AH, AL
; CALL RecvByte
; XCHG AH, AL
; MOV CurrentPos, AX

LEA SI, IObuf
MOV CX, 3
CALL RecvGroup

MOV AL, IObuf[0]
MOV Sign, AL
MOV AH, IObuf[1]
MOV AL, IObuf[2]
MOV CurrentPos, AX

; 更新显示缓冲区并显示
LEA SI, DisplayBuf
CALL UpdateDisplay

JMP CONTROL_LOOP

CONTROL_EXIT:
RET
CONTROL_MAIN ENDP

5.3.2 读取两位参数子程序

读取两位参数子程序

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
; ReadTwoDigits - 读取两位十进制数字
; 输入: 无
; 输出: AX = 读取到的两位数值(0-99)
ReadTwoDigits PROC NEAR
PUSH CX

XOR AX, AX ; 清零AX用于存储结果
MOV CX, 2 ; 需要读取2位数字


ReadDigit_Wait:
LEA SI, DisplayBuf
CALL Display8
CALL GetKeyA ; 读取键盘输入
JNB ReadDigit_Wait ; 无按键则继续等待

; 检查输入是否为数字(0-9)
CMP AL, 9H
JA ReadDigit_Wait ; 大于'9'则无效

; 将当前数字合并到结果中
LEA SI, DisplayBuf
MOV AH, DisplayBuf[0]
MOV DisplayBuf[1], AH
MOV DisplayBuf[0], AL ; 显示到对应位置

LOOP ReadDigit_Wait

POP CX

RET
ReadTwoDigits ENDP

5.4 执行机程序

5.4.1 执行机主程序

执行机主程序

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
; 执行机主程序
EXEC_MAIN PROC NEAR
; 初始化各个外设

CALL Init8255
CALL Init8253_T0
CALL Init8253_T1
CALL Init8259
CALL Init8251
CALL WriIntver

EXEC_LOOP:
; 检查是否有新命令
MOV DX, Con_8251
IN AL, DX
TEST AL, 2
JZ UPDATE_STATUS

; 接收并处理命令
CALL RecvByte
MOV Command, AL
MOV bRunning, 0 ; 停止电机运转
CLI ; 子程序Flags不入栈

; 如果需要参数则继续接收
CMP AL, 0AH
JE PROCESS_CMD
CMP AL, 0FH
JE EXEC_EXIT

; 接收参数
CALL RecvByte

; 存入参数
MOV DisplayBuf[0], AL
CALL RecvByte
MOV DisplayBuf[1], AL

LEA SI, DisplayBuf
MOV AL, DisplayBuf[0]
MOV AH, DisplayBuf[1]
; 计算最终结果
MOV BL, AL
MOV BH, 10
MOV AL, AH
MUL BH ; AX = AH * 10
ADD AL, BL ; 加上个位
MOV AH, 0 ; 清零高位
LEA SI, Param
MOV Param, AX

PROCESS_CMD:
CALL ProcessCommand

UPDATE_STATUS:
; 发送当前位置信息
; MOV AL, Direction
; CALL Sendbyte
; MOV AX, CurrentPos
; CALL Sendbyte
; MOV AL, AH
; CALL Sendbyte

LEA SI, IObuf
MOV AL, Sign
MOV [SI + 0], AL
MOV AX, CurrentPos
MOV [SI + 1], AH
MOV [SI + 2], AL
MOV CX, 3
CALL SendGroup

; 更新本地显示
LEA SI, DisplayBuf
CALL UpdateDisplay
JMP EXEC_LOOP

EXEC_EXIT:
RET
EXEC_MAIN ENDP

5.4.2 命令处理子程序

命令处理子程序
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
; 处理命令子程序
ProcessCommand PROC NEAR
PUSH AX

MOV AL, Command

CMP AL, 0AH ; 停止 / 启动命令
JE CMD_Stop
CMP AL, 0BH ; 正转命令
JE CMD_ClockWise
CMP AL, 0CH ; 反转命令
JE CMD_AntiClockWise
CMP AL, 0DH ; 正向步进
JE CMD_StepForward
CMP AL, 0EH ; 反向步进
JE CMD_StepBackward

CMD_Stop:
MOV bRunning, 0
CLI
JMP ProcessCommand_Exit

CMD_ClockWise:
MOV Direction, 0
JMP starc

CMD_AntiClockWise:
MOV Direction, 1
starc: MOV AX, Param
MOV Speed, AX
CALL StartMotor
JMP ProcessCommand_Exit

CMD_StepForward:
MOV Direction, 0
JMP stars

CMD_StepBackward:
MOV Direction, 1
stars: MOV AX, Param
MOV TargetSteps, AX
CALL StepMotor

ProcessCommand_Exit:
POP AX
RET
ProcessCommand ENDP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
; StartMotor - 启动电机连续运转
; 输入: Speed - 期望的转速(转/分)
; 输出: 无
StartMotor PROC NEAR
; 检查速度是否有效
CMP Speed, 0
JE StartMotor_Exit ; 速度为0则退出

CALL TakeDelay
MOV bRunning, 1
STI ; 开中断
StartMotor_Exit:
RET
StartMotor ENDP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; StepMotor - 控制电机运行指定步数
; 输入: TargetSteps - 目标步数
; 输出: 无
StepMotor PROC NEAR

; 检查步数是否有效
CMP TargetSteps, 0
JE StepMotor_Exit ; 步数为0则退出

; 设置固定速度(例如60转/分)
MOV Speed, 60
CALL TakeDelay
; 启动电机
MOV bRunning, 1
STI ; 开中断
StepMotor_Exit:
RET
StepMotor ENDP

5.4.3 延时计算子程序

延时计算子程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TakeDelay	PROC 	NEAR
PUSH AX
PUSH BX
PUSH DX
; 计算延时值

MOV AX, 20000
XOR DX, DX
MOV BX, Speed
DIV BX
MOV StepDelay, AX
MOV StepDelay1, AX

POP DX
POP BX
POP AX
RET
TakeDelay ENDP

5.4.4 中断

5.4.4.1 中断向量设置

1
2
3
4
5
6
7
8
9
10
11
12
WriIntver	PROC	NEAR	
PUSH ES
MOV AX, 0
MOV ES, AX ;中断向量表起始地址为00000H
MOV DI, 20H ;中断向量类型号为08H,则中断向量地址 = 08H * 4 = 20H
LEA AX, TIMER0 ;AX -> [(ES:DI)],中断服务函数入口地址(偏移地址)
STOSW
MOV AX, CS
STOSW ;中断向量表高位存放段地址
POP ES
RET
WriIntver ENDP

5.4.4.2 中断服务程序

中断服务程序

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
; 定时器中断服务程序
TIMER0 PROC
PUSH AX
PUSH DX

CMP bRunning, 0
JE TIMER0_Exit

DEC StepDelay
JNZ TIMER0_Exit

MOV AX, StepDelay1
MOV StepDelay, AX

; 更新电机控制
MOV AL, StepControl
MOV DX, IO8255_PC
OUT DX, AL

; 根据方向更新控制值
CMP Direction, 0
JE Rotate_CW

Rotate_CCW:
ROL AL, 1
JMP Rotate_Done

Rotate_CW:
ROR AL, 1

Rotate_Done:
MOV StepControl, AL

; 更新位置计数
CALL UpdatePosition
MOV AL, Command
CMP AL, 0BH ; 正转命令
JE TIMER0_Exit
CMP AL, 0CH ; 反转命令
JE TIMER0_Exit
; 检查是否需要停止
DEC TargetSteps
JNZ TIMER0_Exit


MOV bRunning, 0
ADD SP, 16 ;Flags、CS、IP、AX、DX
POPF ;从堆栈弹出标志寄存器(恢复原来的IF状态)
CLI ;关中断(设置IF=0)
PUSHF ;将标志寄存器压入堆栈(保存了IF=0的状态)
SUB SP, 16
NOP

TIMER0_Exit:
MOV DX, IO8259_0
MOV AL, 20H ;OCW2,发送EOI中断结束
OUT DX, AL
POP DX
POP AX
IRET
TIMER0 ENDP

5.4.5 位置计数更新子程序

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
; 位置计数更新子程序
UpdatePosition PROC NEAR
PUSH BX
LEA BX, CurrentPos
CMP Direction, 0
JE Pos_Inc

Pos_Dec:
CMP Sign, 1
JNE qwq
JMP Clear

qwq: CMP CurrentPos, 0
JNE qaq
XOR Sign, 01H
INC CurrentPos
JMP UpdatePos_Exit

qaq: DEC CurrentPos
JMP UpdatePos_Exit

Pos_Inc:
CMP Sign, 0
JNE qwq
JMP Clear

Clear: INC CurrentPos
CMP CurrentPos, 1000
JNE UpdatePos_Exit
MOV CurrentPos, 0

UpdatePos_Exit:
POP BX
RET
UpdatePosition ENDP

5.5 通信程序

5.5.1 发送一个字节

sendbyte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
;发送一个字节,输入参数 AL
Sendbyte PROC NEAR
PUSH DX
PUSH AX
MOV DX,Con_8251 ;读入状态
Wait_Tx:

; D0: TxRDY,发送器是否准备好
IN AL,DX
TEST AL,1
JZ Wait_Tx ;允许数据发送吗?
POP AX ;发送
MOV DX,Dat_8251
OUT DX,AL
POP DX
RET
Sendbyte ENDP

5.5.2 接收一个字节

recvbyte

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
; 接受一个字节,ZF = 0,且将结果存入AL
RecvByte PROC NEAR
PUSH DX
PUSH BX
MOV DX,Con_8251

Wait_Rx:
; D1: RxRDY,接收器是否准备好
IN AL,DX ;读入状态
TEST AL,2
JZ Wait_Rx ;有数据吗?
MOV DX,Dat_8251 ;有
IN AL,DX
; LEA BX,DATAbuf
; ADD BX,COUNT
; MOV [BX],AL
; INC WORD PTR COUNT
; LEA BX, COUNT
; CMP COUNT, 100
; JB Recv_Exit
;FULL:
; MOV COUNT, 0
Recv_Exit:
POP BX
POP DX
RET
RecvByte ENDP

5.5.3 发送一组数据

sendGroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;发送一组数据,输入参数 SI,CX
SendGroup PROC NEAR
MOV AL, Fram_Head
CALL Sendbyte

SendNext:
MOV AL, [SI]
CALL Sendbyte
INC SI
LOOP SendNext

MOV AL, Fram_Tail
CALL Sendbyte
RET
SendGroup ENDP

5.5.4 接受一组数据

recvgroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
RecvGroup	PROC	NEAR	
RecvFind:
CALL DLTIME
CALL RecvByte
CMP AL, Fram_Head
JNE RecvFind
CALL DLTIME
RecvNext:
CALL RecvByte
MOV [SI], AL
INC SI
LOOP RecvNext

CALL DLTIME
CALL RecvByte
; CMP AL, Fram_Tail
; JNE RecvFind
CALL DLTIME
RET
RecvGroup ENDP

5.5.5 延时

1
2
3
4
5
6
7
8
;延时
DLTIME PROC NEAR
PUSH CX
MOV CX,10
LOOP $
POP CX
RET
DLTIME ENDP

5.6 共同子程序

5.5.1 数码管扫描显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DISPLAY8	PROC	NEAR
PUSHF
PUSH ES
PUSH DI
PUSH SI
PUSH CX
PUSH DS
POP ES
ASSUME ES:DGROUP
CLD
MOV DI, OFFSET buffer
MOV CX, 8
REP MOVSB ; 重复 MOVSB 共 (CX) 次, [(ES:DI)] <- [(DS:SI)], SI++, DI++
POP CX
POP SI
POP DI
POP ES
ASSUME ES:nothing
POPF
CALL DIR
RETN
DISPLAY8 ENDP
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
DIR	PROC	NEAR                                     
PUSHF
PUSH AX
PUSH BX
PUSH DX
PUSH SI
CLD ; 使SI/DI自动增量
MOV SI, OFFSET buffer ; SI指向显示缓冲区起始地址
MOV AH, 0FEH ; AH初值为11111110B,用于位选
MOV BX, OFFSET SEG_TAB ; BX指向段码表起始地址

DIR_LOOP:
LODSB ; [(DS:SI)] -> AL, SI++ ; 取显示缓冲区一个字节到AL
MOV DX, AX ; 将未清除最高位的 AL 保存到 DL
AND AL, 7FH ; 清除最高位,最高位为1代表要显示小数点,但是为了避免越界,需先清除最高位
XLAT ; 查表,(AL) <- ((BX)+(AL))
TEST DL, 80H ; 检查原来的AL的最高位是否为1
JZ SHORT DIR_SHOW ; 不是1,直接送出显示
AND AL, 7FH ; 是1,说明要显示小数点,将取得的段码的最高位(dp位)置0,0为亮,1为不亮

DIR_SHOW:
MOV DX, 270H ; PA口,数码管段码
OUT DX, AL
INC DX ; PB口,数码管位选
MOV AL, AH
OUT DX, AL
CALL Delay
MOV DX, 271H ; PB口,数码管位选
MOV AL, 0FFH ; 全部不选,强制所有数码管灭掉,作用是消除残影
OUT DX, AL
TEST AH, 80H ; 检测位选最高位是否为0,为0代表当前扫到了第八个数码管,一次扫描结束
JZ SHORT DIR_EIXT
ROL AH, 1 ; 位选循环左移一位
JMP SHORT DIR_LOOP ; 扫描下一位

DIR_EIXT:
POP SI
POP DX
POP BX
POP AX
POPF
RETN
DIR ENDP

5.5.2 读取按键

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
GETKEYA	PROC NEAR
CALL SCAN_KEY
RETN
GETKEYA endp

SCAN_KEY PROC NEAR
PUSH BX
PUSH DX
CALL AllKey
JNZ SHORT Has_Key
CALL DIR
JMP SHORT NO_KEY
DB 90H
Has_Key:
CALL DIR ; 显示数码管, 当然也是用 DIR 中的延迟来作为消抖的延时,约9~10ms
CALL AllKey ; 延时后重采样
JZ SHORT NO_KEY ; 若无按键按下,认为是干扰,下面同理
CALL DIR
CALL AllKey
JZ SHORT NO_KEY
MOV BL, 0FEH ; 1111 1110 选择第0列
MOV BH, 0 ; 从第0列开始,存放列值

SCAN_LOOP:
MOV DX, 271H ; PB口,键盘列线
MOV AL, BL
OUT DX, AL
INC DX ; PC口
IN AL, DX ; 读行线
TEST AL, 1 ; 测试第0行是否有按键按下
JNZ SHORT Line_1 ; 无按键按下,说明第一行可能有按键按下
XOR AL, AL ; 第0行有按键按下,则偏移值为0
JMP SHORT Line_0
ALIGN 2

Line_1:
TEST AL, 2 ; 测试第1行是否有按键按下
JNZ SHORT Next ; 无按键按下则跳转
MOV AL, 8 ; 第一行有按键按下,偏移值为8,即第0行的8个按键

Line_0:
ADD BH, AL ; 当前的第几列加上偏移量,比如第i行的第j列,值为 i * 8 + j

Wait_Key:
CALL DIR
CALL AllKey
JNZ SHORT Wait_Key ; 等到按键松开
MOV AL, BH ; AL中存放按键的值
STC ; 设置进位标志,表示有按键按下
JMP SHORT SCAN_EXIT
ALIGN 2

Next:
INC BH ; 列值加一
TEST BL, 80H ; 检测列扫描最高位是否为0
JZ SHORT NO_KEY ; 为0,本次8列扫描结束,依然没检测到按键按下,认为没有按键按下
ROL BL, 1 ; 循环左移一位,扫描下一列
JMP SHORT SCAN_LOOP

NO_KEY:
CLC ; 清除进位标志, 表示没有按键按下

SCAN_EXIT:
POP DX
POP BX
RETN
SCAN_KEY ENDP
1
2
3
4
5
6
7
8
9
10
11
AllKey	PROC 	NEAR               

MOV DX, 271H
XOR AL, AL
OUT DX, AL ; 列输出全0
INC DX ; PC口,PC0和PC1为两个行线,无按键按下是高电平,有按键按下是低电平
IN AL, DX
NOT AL ; 取反后是无按键按下是低电平,有按键按下是高电平
AND AL, 3 ; 和3相与,结果是有按键按下结果非零,无按键按下结果为零即ZF = 1
RETN
AllKey ENDP

5.5.3 UpdateDisplay

更新显示子程序

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
; 更新显示子程序
; 输入参数:SI
UpdateDisplay PROC NEAR
PUSH AX
PUSH DI
PUSH CX

CMP Sign, 0
JZ uwu
MOV BYTE PTR [SI + 7], 11H
JMP uwu1
uwu: MOV BYTE PTR [SI + 7], 00H
uwu1:
; 显示位置值
LEA DI, [SI + 4]
MOV AX, CurrentPos
MOV CX, 3
CALL ConvertToDisplay

; 分隔符
MOV BYTE PTR [SI + 3], 10H
; 命令信息
MOV AL, Command
MOV [SI + 2], AL

POP CX
POP DI
POP AX
RET
UpdateDisplay ENDP

5.5.4 ConvertToDisplay

ConvertToDisplay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
;for(int i = 0; i < CX; i++, AX /= 10)
; DI[i] = AX % 10
;将AX的每一位分别提取出来用于显示
;输入参数:DI, CX, AX
ConvertToDisplay PROC NEAR
PUSH BX
CLD
MOV BX, 10
ConvertLoop:
XOR DX, DX
DIV BX ;[DX, AX] / BX,商存在AX里,余数在DX里
XCHG AX, DX ;交换
STOSB ;AL -> [(ES:DI)], DI = DI + 1
MOV AX, DX
LOOP ConvertLoop
POP BX
RET
ConvertToDisplay ENDP

6.软硬件调试

  1. 数码管不亮

    调用了Display8但是实验箱的数码管就是不亮,想了一下Display8是怎么实现的,原因是Display8只扫描一遍,而我没有循环重复调用显示数码管,以至于它是灭的。

  2. 实现F命令时出现PC指针超出范围

    前期我在整个程序的最后,才调用INT 21H退出程序,但是由于代码太长,有三四百行,而JZ这种跳转指令只能跳转-128 ~ +127范围内的偏移量,显然超出范围了。(虽然后期我没有再把退出程序放到代码最后) 解决办法:中间加个 JMP 指令接力一下

  3. 尝试根据转速要求计算延时,但不知道电机的步距角

    我先上网查了一下,好像大多数是1.8度,后来和老师讨论延时的计算时,有同学说这个电机看上去比较小,老师在淘宝上搜了一下微型步进电机,查看了参数,有一个产品的步距角是18度,觉得很像,让我测试一下转一圈是不是20步。我在电机的风扇上粘了一小块透明胶带,作为起始位置,将电机的转速调低,转过一圈时立马停止电机,此时数码管上的记录刚好是20步!

  4. 除法溢出

    根据之前推导的计算延时的公式:\(StepDelay = {2000 \over n}\) ,我一开始想我的 \(n\) 最大只有 99,在 8 个 bit 之内,就想着采用 16 位的除法,但是没有考虑到:商是存在AL 中的,当 \(n\) 很小时,比如 \(n = 1\),商应该为 2000,显然 AL 这个8bit当寄存器最大只能存255,就产生的除法溢出。 解决办法:改用32位的除法。

  5. 步数减到0后突然从535开始继续递减,而不是999

    这个问题是另一个同学调试我的程序时发现的,我之前还没有注意到这个问题,但是一直没有管这个问题,觉得可能是位置更新有点小问题影响不大,做完后续的其他模块后,我想起来这个问题,打算解决一下,发现问题没我想象的那么简单。

    原本的 UpdatePosition 子程序是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    UpdatePosition 	PROC 	NEAR
    CMP Direction,0
    JE Pos_Inc

    Pos_Dec:
    DEC CurrentPos
    CMP CurrentPos,-1000
    JNE UpdatePos_Exit
    MOV CurrentPos,0
    JMP UpdatePos_Exit

    Pos_Inc:
    INC CurrentPos
    CMP CurrentPos,1000
    JNE UpdatePos_Exit
    MOV CurrentPos,0

    UpdatePos_Exit:
    RET
    UpdatePosition ENDP

    逻辑非常简单,是顺时针就将当前位置加一,加到1000变成0,是逆时针就将当前位置减一,减到-1000就变成0。但是在计算机的二进制中0 - 1 = -1,负数是用补码表示的,-1的补码是FFFFH,FFFFH经过ConvertToDisplay(内部是无符号除法)会变成65535,而我数码管显示只取低三位,所以减到0后就从535开始减了,非常合理地解释了我看到的一切!我很高兴的想,那把ConvertToDisplay里的除法改成有符号除法不就行了吗?!假的,FFFFH除以0AH后商是0,余数是FFFFH,按理说余数结果是-1一点没错,但是我数码管支持直接显示'-1'这个数吗?!段码里没有'-1'这个东西吧?毕竟这是一个'-'和一个'1',用两个数码管来显示的。没有办法,只能分类讨论了(见软件设计相应部分),并且把原本的第7位表示顺时针还是逆时针的定义改成了是正号还是负号。

  6. 想要让A命令实现暂停/启动的功能而不是单单只能停止,失败

    我想实现暂停/启动的代码是这样的(由于错误的代码没有保存,这里用伪代码描述):

    1
    2
    3
    4
    5
    6
    if bRunning == 0:
    bRunning = 1
    STI
    else:
    bRunning = 0
    CLI
    结果我按A它都停止不了了,然后我打断点调试的时候发现每次它都进入到第一个分支,溯源一下,发现上面的代码中,执行机收到命令后就停止电机运行了,所以走到A命令的处理时bRunning永远都是0,然后永远开中断让电机运行。但是我确实想用户在给电机新的命令时电机先停止运转,所以就放弃了把A命令实现为暂停/启动的功能。不过现在想来,可以在上面的代码中特判一下A命令,遇到A命令就不像遇到其他命令一样无脑关中断停止电机运转,而是直接进入命令处理环节。

  7. 8251通信,控制机发过去但是执行机收不到,或者说收到的都是错误标志位拉满的奇怪数据

    找了一个晚上的错,差点以为自己想做通信的念想到此为止了,准备写一对简单的收发程序专门对通信功能进行测试,这样避免了电机的其他部分代码的干扰,写新的测试程序的时候发现,原来执行机的定时器1忘记初始化了,导致两边波特率不一样,所以产生了数据错误。

  8. 可以收发数据,但是数据错位

    在新的简单测试通信的代码中,我发现发一位数据可以正常接受一位数据,满心欢喜,然后放到电机程序上跑的时候,命令参数可以正确接收并显示,但是位置信息控制机显示的完全不对,简直是一团胡乱的数据,不知道是怎么回事,向老师询问之后说可能是不是错位了,让我写个静态的测一下。我就将之前发一位收一位的测试代码改成了发两位收两位,结果果真是错位了,发21但是收到的显示却是12,发三位791结果收成917。 我想是不是两次发送之间没有延迟,太快了导致的,就在两次发送之间尝试了很多不同的延时值,但是结果依然都是错位。到底为什么会错位呢?我想尝试单步调试一下寻找原因,但是在我手动控制发送机发一位执行机收一位,一共发收三次传完三个数据的时候,没有错位!全部正常接收了!而且无论重复多少次这个过程都是没有错位,太诡异了。

    我实在是没有办法了,但是这个错位,让我想到了《计算机网络》里有提到过数据错位的问题,我寻思着数据链路层的任务是:封装层帧、透明传输、差错检测。那我将要发送的数据封装成帧是否可行呢?我看了一下要发送的数据,第一个字节(位置的符号位)只有可能是0或者1,就选择了0FAH这个较大的数当作帧头,用0FBH这个字节当作帧尾。在写的时候我还是担心,因为我没有实现透明传输,会不会把中间的正常数据误认为帧头或者帧尾之类的,因为毕竟位置的范围是0~999,包含FA也包含FB,不过先试试看吧。

    要怎么实现封装成帧呢?如果仅仅是发送数据前先调用Sendbyte发送帧头,数据发完再调用帧尾的话,好像太不智能了,我每次发数据都要手动添加这两行代码,接收的时候也是,感觉太冗余了,就又写了一个SendGroup的函数,但是用一个函数的话,我怎么知道要发什么数据,发几个字节?这个好像通过寄存器很难传递,总不能AL是要发送第一个数据,AH是要发送的第二个数据诸如此类吧?借鉴了一下Display8的实现方式,采用存储器来传递数据,即在程序中定义一个缓冲区,我称之为IObuf,将要发送的数据存入这里,函数的入口参数中,SI就存着这个缓冲区的首地址,而CX就存要发送几个字节。

    很可惜,在封装成帧(我的SendGroup)之后,我的数据收发还是不正确,但是单步调试又无法找出问题,有什么办法能在全速运行的情况下还能看到真实的收发数据是什么吗?我没有串口助手,就又开了一个大的存储空间(DATAbuf),专门用来存放收到的数据,存数据的实现就写在RecvByte里,一接受到就存下来,如下注释部分代码:

    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
    ; 接受一个字节,ZF = 0,且将结果存入AL
    RecvByte PROC NEAR
    PUSH DX
    PUSH BX
    MOV DX,Con_8251

    Wait_Rx:
    ; D1: RxRDY,接收器是否准备好
    IN AL,DX ;读入状态
    TEST AL,2
    JZ Wait_Rx ;有数据吗?
    MOV DX,Dat_8251 ;有
    IN AL,DX
    ; LEA BX,DATAbuf
    ; ADD BX,COUNT
    ; MOV [BX],AL
    ; INC WORD PTR COUNT
    ; LEA BX, COUNT
    ; CMP COUNT, 100
    ; JB Recv_Exit
    ;FULL:
    ; MOV COUNT, 0
    Recv_Exit:
    POP BX
    POP DX
    RET
    RecvByte ENDP

    然后在FULL标记的代码处打一个断点,全速断点后就能在存储器窗口看到收的100个数据了,如下:

    一帧的数据有三个字节,我们可以从接收到的数据中看出帧尾FB经常会出现两遍,但是帧头只有一个,并且数据的位置信息部分从01 9A 到 01 9B 到01 9C等等,看上去是完全正确接收了,那我就回过头来看一下我代码是怎么解析接受到的数据了,这一看就发现,之前手误把高低字节接受的顺序和发送机的搞反了,改完这个问题后,我的机子就实现了正常通信啦。

  9. 控制机的数码管显示有明显的闪烁

    这是中途问老师问题,老师提出来的,让我把数码管的频率改改,我当时想着这系统时钟不是固定吗?我哪能提高?而且数码管的显示图方便是调用的库函数,也不是自己写的,这库函数我也改不了呀,就一直搁浅了这个问题。写报告的硬件设计部分涉及到了数码管显示的问题,我想起来这个问题,就好好研究了一番,发现库函数设计的扫描频率挺合适的,并且我执行机数码管不怎么闪,就控制机闪,我又回头看了一下我写的代码,坏!答辩的时候老师问我JZ CONTROL_LOOP的作用是什么,我说它如果没有位置更新还要跳回去显示数码管(上一次的值),还要去扫描按键等等,结果我代码里居然没有显示数码管(因为之前大量的简单测试代码,以及帮某人调试代码时都是把Display8放在循环的开头就显示了,以至于我理所当然的认为我的最终代码也是这样的,结果居然不是),只在 updateDisplay 里显示了,而 updateDisplay 只有有位置更新的时候才会调用,位置更新是通过8251通信从执行机里接收过来的,通信肯定有延迟吧,怪不得闪的厉害,我觉得原因应该是这样,并且在写报告的时候在循环开头把数码管显示加上去了,只是可惜如今实验室已经关门了,没法再测一下了。

  10. 单步调试的时候进中断就出不来了

    为了解决一些在中断函数中出现的疑问,常常想单步调试一下中断,在特定情况下再返回主程序,但是一旦在中断里单步,就算把断点撤了,再全速运行,结果还是在中断函数的开头停下来了,哪怕我没在那里打断点。

    解决办法,可以临时加一些在特定条件下才会执行到的代码,然后再此处打上断点,全速断点到这里后再把这个断点删了,再在想回到的主程序的地方打断点,再全速断点,避免在中断函数里进行单步。

    原因解释:首先单步一定是走不出中断程序的,因为我们太慢了,8253定时器是独立于8086工作的,在中断的时候8253的定时器依然在计数,依然在产生中断。至于为什么全速运行也走不出中断,还是会在中断入口处停下,暂时没有找到合理的解释。

7.设计总结

7.1 主要功能实现情况

本设计成功实现了以下功能:

  1. 基本控制功能
    • 步进电机的启停控制
    • 正反转速度控制(1-99转/分)
    • 正反向步数控制
    • 实时位置显示
  2. 通信功能
    • 控制机与执行机之间的串行通信
    • 命令和参数的可靠传输
    • 位置信息的实时反馈
  3. 显示功能
    • 控制机显示当前命令和位置信息
    • 执行机显示实时位置计数和接收到的命令

7.2 技术特点

  1. 硬件设计
    • 采用8251芯片实现TTL电平的串行通信
    • 使用8253定时器实现精确的速度控制
    • 通过8259中断控制器处理定时器中断
    • 利用8255并行接口实现键盘扫描、数码管显示和电机驱动
  2. 软件设计
    • 采用帧格式封装提高通信可靠性
    • 实现位置计数的正负值显示
    • 使用中断方式控制电机转速
    • 采用查表法实现数码管显示

7.3 创新点

  1. 通信可靠性设计
    • 采用帧头(0FAH)和帧尾(0FBH)标识,实现数据包的完整性检验
    • 通过缓冲区管理提高通信效率
  2. 人机交互优化
    • 实现正负位置值的直观显示
    • 提供实时位置反馈
    • 支持多种控制模式切换

7.4 存在的不足与改进建议

  1. 功能扩展
    • 增加电机缓启动功能
    • 实现更多控制模式,如增加选择是单四步还是双四步还是单双八步
    • 当电机走完设定的步数时用蜂鸣器提醒用户
  2. 可靠性提升
    • 完善通信协议,实现透明传输
    • 添加通信超时处理
    • 实现数据校验和重传机制

8.心得体会

初版代码部分借鉴星研的 SUN ES86PCIU+ 使用手册中的综合实验四:步进电机实验 所提供的代码,最初实现的也是步进电机综合控制实验,想着和远程控制步进电机实验功能基本一样,后期扩展成通信也方便实现。这是我第一次读别人的这么长的汇编代码,读得非常非常难受,基本上是捧着课本在读,好多指令的作用要翻书看一下,好多重复出现的指令的意思也记不住,看到只觉得眼熟,或者大概知道是做什么的,但是具体是从哪个隐含的寄存器放到哪个隐含的寄存器就说不上来了,还得翻书,尤其是串操作类指令,这部分上课也没讲过。这个代码我读了有一两天,边读边加注释,最后感觉对整个运行的框架和流程还是感觉有些不清晰,就照着代码画了一份流程图来帮助我理解,如下: 此时我觉得我基本上能非常清晰地理解整个代码逻辑了,甚至还见识到了神奇的函数跳转表这种写法,想着照葫芦画瓢是不是就能完成这次实验了,然后发现也没那么容易,命令处理那边好多要改,基本上除了初始化和一些转换的代码外全都重写了。 好处是这份代码给我提供了控制电机的方式和大体框架,坏处是我读懂代码,且觉得实现的很合理,但是没有细细思索背后这么设计的原因,而直接把大体控制流程复刻到我的实验当中,以至于我回头写报告再细细分析硬件设计部分时,我觉得在目前功能实现较为简单的情况下,可能有更方便的解决办法,如步进电机的延迟处理,可以不用那么麻烦,而是采用我在硬件设计部分提到的重新置计数初值的方式。不过我现在也知道为什么他要这么设计延时方式了,因为他还多一个当电机设置速度过快时缓慢启动的过程,而我没做这个。

9.参考文献

10.答辩记录

  1. Q:你是怎么解决数据错位的?

    A:我在数据发送时添加了帧头和帧尾,只有检测到帧头才开始接收一帧数据。

  2. Q:CONTROL_LOOP 的作用是什么?

    A:负责数码管显示的动态刷新和按键状态的定期扫描,维持系统状态的连续性,即使在没有新的位置信息时,系统也需要通过这个循环来维持上一次的显示状态。


  1. "单极步进电机和双极步进电机的区别" https://blog.csdn.net/qq_40862304/article/details/106266399↩︎

  2. "快速上手ULN2003达林顿管阵列,并学会控制步进电机" https://www.bilibili.com/video/BV1Uy4y1g7gY/↩︎

  3. "步进电机的巧妙原理" https://www.bilibili.com/video/BV1nA4m1A7Ke/↩︎

  4. "RS232和RS485通信总线" https://blog.csdn.net/m0_61298445/article/details/124108925↩︎

  5. "SAB8284B-P Datasheet" https://www.alldatasheet.com/html-pdf/45585/SIEMENS/SAB8284B-P/3551/14/SAB8284B-P.html↩︎

  6. "Intel 8086 Instruction Timing" https://www.oocities.org/mc_introtocomputers/Instruction_Timing.PDF↩︎