嵌入式系统实验报告
实验步骤与实验结果:
实验1:实现奇校验:将待检测的数据存入到寄存器中,通过循环移位和异或运算计算出奇校验码(设校验位在最低位)
- 每次取出原数据的最低位,这个可以通过原始数据和 $1$ 做位与运算得到
- 将取出的最低位与校验位做异或,得到的结果保留到校验位
- 重复上面两个步骤,直到原数据的每一位都被取出来过
- 这时候校验位的值相当于记录了 $1$ 的个数
- 由于是奇校验,所以对校验位再做一次和 $1$ 的异或,得到校验位的值
- 将原数据左移 $1$ 位,然后把校验位的值填入最低位
- 可以通过将左移后的值与校验位做位与运算完成上面这个操作
- 最后得到的就是答案
实验得到正确的结果,如图所示
实验2:实现海明码校验:将要检测的数据存入寄存器R1中,通过移位和异或运算计算出冗余位;可再通过修改数据中一位,存入寄存器R0中,利用校正因子找出该位,并修改。
本题的困难之处在于,对于输入的数据,是什么信息都没有的,也就是说,所有的操作都要自己想办法完成
不少同学为简化该选做题,采取了 $面向数据编程$ 这个方法
因题目所给样例是 $10101100$ 的 $8$ 位原数据,那么海明码就是 $4$ 位,且位于 $1,2,4,8$ 三个位置
而样例同时给出了校验位的计算公式
$$
r_0={I_7}\oplus{I_5}\oplus{I_4}\oplus{I_2}\oplus{I_1} \
r_1={I_7}\oplus{I_6}\oplus{I_4}\oplus{I_3}\oplus{I_1} \
r_2={I_8}\oplus{I_4}\oplus{I_3}\oplus{I_2} \
r_3={I_8}\oplus{I_7}\oplus{I_6}\oplus{I_5}
$$
有了校验位,就可以求出海明码,然后随便修改一位,再次运用上面的公式,对校验位求异或,就得到了错误那一位的值
同样地,样例给出了计算公式
$$
s_0={I_7}\oplus{I_5}\oplus{I_4}\oplus{I_2}\oplus{I_1}\oplus{r_0} \
s_1={I_7}\oplus{I_6}\oplus{I_4}\oplus{I_3}\oplus{I_1}\oplus{r_1} \
s_2={I_8}\oplus{I_4}\oplus{I_3}\oplus{I_2}\oplus{r_2} \
s_3={I_8}\oplus{I_7}\oplus{I_6}\oplus{I_5}\oplus{r_3}
$$
之后,将 $s_i$ 看作二进制,累加、乘 $2$、累加、乘 $2$……就可以得到是哪一位错了
然后定位到那一位,取反,得到正确的答案
除了寄存器可能会用到很多很多,以及,代码会写得很长很长,其他的,只要按照步骤一步步来,是没有难度的
但是这样做,其实就是 $面向数据编程$,完全是硬编码的东西,只能解决这一个问题
这样的代码实在是太丑陋了
坚决反对用这种投机的方法完成作业骗分
作为计算机系的学生,事情要做的优雅!优雅!
其实写一个通用的海明码汇编,太难了,用 $C$ 语言可能还好写些
主要问题有下面这些
- 输入的原数据是不知道长度的,如何知道有多少位 $K$ ?
- 如何根据公式 $\displaystyle 2^R \geqslant K + R + 1$ 确定 $R$ 的值?
- 确定了 $R$ 的值以后,如何知道是哪几位是校验位?
- 计算海明码的时候,对于第 $i$ 位的数据,如何确定要和哪个或者哪几个校验位做异或?
- 异或的结果如何保存?
- 如何随便修改一位?
- 重新校验的时候,对于第 $i$ 位的数据,如何跳过校验位?
- 根据校验后的结果,如何确定第几位出错了?
- 修改后的数据,如何还原为原数据?
下面一一说明我是怎么处理的
- 输入的原数据是不知道长度的,如何知道有多少位 $K$ ?
这个比较简单,将一个数循环右移多少次以后得到 $0$,就是这个数有多少位,于是可以得到 $K$ 的值 -
如何根据公式 $\displaystyle 2^R \geqslant K + R + 1$ 确定 $R$ 的值?
由于 $K$ 最少是 $1$、最多是 $32$
所以由公式 $\displaystyle 2^R \geqslant K + R + 1$ 显然有,$2 \leqslant R \leqslant 6$
那么,就可以令 $R$ 为 $2$,每次判断 $\displaystyle 2^R \geqslant K + R + 1$ 是否成立,如果成立,则找到 $R$ 的值,否则$R$ 加 $1$
最后必然会有一个 $2 \leqslant R \leqslant 6$ 满足 $\displaystyle 2^R \geqslant K + R + 1$
在实际操作的过程中,可以用一个寄存器 $R_1$ 记录 $2^R$,用一个寄存器 $R_2$ 记录 $K+R+1$,如果$R_1 \lt R_2$,那么 $R_1$ 左移一位,$R_2$ 加 $1$
CMP R4,R5 BGE TODO LSL R4,#1 ;R+1,2^{R+1}=R4<<1 ADD R5,R5,#1 ;R+1,K+(R+1)+1=R5+1 B LOOPR
- 确定了 $R$ 的值以后,如何知道是哪几位是校验位?
校验位一定是第 $1,2,4,8,\ldots$ 位所以可以令寄存器 $R_1$ 从 $1$ 开始,每次左移 $1$ 位,直到超过了 $K+R$ 位
当已知了 $R$,那么 $N=K+R$ 就是一个常数了
-
计算海明码的时候,对于第 $i$ 位的数据,如何确定要和哪个或者哪几个校验位做异或?
$i$ 是下标,所以需要有一个寄存器作为 $INDEX$,从 $N$ 遍历到 $0$,每次取出第 $INDEX$ 位的数据
记取出的数据为 $X$,那么 $X$ 就是要参与异或运算的,$X=0$ 或者 $X=1$
而 $INDEX=i$ 是下标,下面将这个十进制的下标转化为二进制
例如对于 $i=6$,其二进制表示是 $110$,说明需要对第 $3$ 和第 $2$ 个校验位做异或,例如对于 $i=5$,其二进制表示是 $101$,说明需要对第 $3$ 和第 $1$ 个校验位做异或
那么,将一个数从十进制转化为二进制,就是除 $2$ 取余、除 $2$ 取余依次循环
当取出当前位为 $0$ 的时候,说明不需要对该位进行校验异或
当取出当前位为 $0$ 的时候,说明需要对该位进行校验异或,此时,应该将刚才取出的 $X$ 的值,与当前的校验位做异或
EOR R8,R8,R1 ;R1是当前位的值,R8是对应校验位的值,做异或
由于除 $2$ 取余这个过程每次取出的是最低位,所以令 $R_1$ 表示当前校验位的位置的话,$R_1$ 的初始值为 $1$
之后每取出一位,如果这一位是 $1$,就令 $X$ 与 $R_1$ 表示的校验位做异或,然后 $R_1$ 左移一位,如果这一位是 $0$,就直接 $R_1$ 左移一位
显然的,当 $INDEX=i$ 右移到 $0$ 的时候, $R_1$ 也刚好超过了 $N=K+R$,两者的结束条件一致
CMP R7,#0 BEQ ITERNEXT ;这一位的二进制全处理完了,遍历下一个数字 TST R7,#1 BEQ SKIPXOR ;说明这一位二进制是0,那么不用与对应的校验位做异或
当这一位的校验结束以后,遍历 $INDEX$,即执行 $INDEX=INDEX-1$,然后重置 $R_1$ 为 $1$
MOV R4,#1 SUB R2,R2,#1 ;INDEX-- CMP R2,#0 ;INDEX到0说明全部做完了
特别的,当 $INDEX$ 遇到校验位的时候,应该跳过该校验位,不做异或,如何知道是哪几位是校验位见上一条
CMP R2,R5 BNE BEGINITER ;R2不等于R5,说明这一位不是校验位,就开始处理 ;这一位是校验位,那么跳过,继续遍历 LSR R5,#1 ;校验位除2 B ITERNEXT LSL R4,#1 ;校验位从1、2、4……移动 LSR R7,#1 ;右移一位,开始看下一位要不要做异或
- 异或的结果如何保存?
原数据存哪,异或的结果就存哪,我是存内存的 -
如何随便修改一位?
因为汇编语言没有产生随机数的功能,所以必须指定一位,考虑到 $K$ 最小是 $1$,我就修改了第 $3$ 位
-
重新校验的时候,对于第 $i$ 位的数据,如何跳过校验位?
与上面类似的,有一个寄存器 $R_1$ 从 $1$ 开始,每次左移 $1$ 位,即 $R_1$ 表示下一个校验位的位置
当满足 $INDEX=R_1$ 的时候,说明这一位是校验位,跳过,然后 $R_1$ 左移 $1$ 位
-
根据校验后的结果,如何确定第几位出错了?
校验的过程与计算海明码的过程是类似的
$i$ 是下标,所以需要有一个寄存器作为 $INDEX$,从 $N$ 遍历到 $0$,每次取出第 $INDEX$ 位的数据
记取出的数据为 $X$,那么 $X$ 就是要参与异或运算的,$X=0$ 或者 $X=1$
令 $R_1$ 表示当前校验位的位置,对 $INDEX$ 循环右移,每取出一位,如果这一位是 $1$,就令 $X$ 与 $R_1$ 表示的校验位做异或,然后 $R_1$ 左移一位,如果这一位是 $0$,就直接 $R_1$ 左移一位
特别的,当 $INDEX$ 遇到校验位的时候,应该跳过该校验位,不做异或
之后,所有校验位的值都是校验后的值,依次遍历所有的校验位,乘 $2$ 累加,将二进制转化为十进制,求出的就是第几位数据出错了
LOOPPRODSUM CMP R4,#0 BEQ FINDWHO ; 说明已经算完了全部的校验位 LSL R5,#1 ;左移1位 SUB R6,R3,R4 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R7,[R0,R6] ;R7是当前要处理的校验位的值 ADD R5,R5,R7 ;R5=R5+R7,加上当前的校验位的值,0,或者1 LSR R4,#1 ;校验位除2 B LOOPPRODSUM ;二进制转十进制
至此,已经求出了出错的位在哪里
然后取反,还原得到原数据
-
修改后的数据,如何还原为原数据?
二进制转十进制,乘 $2$ 累加
特别的,需要跳过校验位
LOOPBINTDEC SUB R4,R4,#1 ;R4-- CMP R4,#0 BEQ EXIT CMP R2,R4 BEQ BINTDECNEXT ;这一位是校验位,跳过 LSL R5,#1 ;乘2 SUB R6,R3,R4 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R7,[R0,R6] ;从内存取出一个数 ADD R5,R5,R7 ;加到R5 B LOOPBINTDEC BINTDECNEXT LSR R2,#1 ;下一个校验位的位置,除2 B LOOPBINTDEC EXIT
实验结果非常令人满意,能够支持任意位数的海明码计算
首先看把1101
存入内存,结果正确
这里把校验位都留空了为 $0$
计算校验位以后的海明码
随机修改,不妨修改第 $3$ 位,对这一位取反
计算校验值,然后对校验值的二进制转化为十进制,发现能正确找到是第 $3$ 位错了
然后把错误这位还原
还原后的校验位的值就不关心了,所以就丢着没有处理
最后得到正确结果
事实上,如果随机修改为第 $5$ 位、任意一位,都是可以正确找到错误所在的
得到正确结果
换成 $8$ 位也可以
$1$ 位也行
甚至不是 $4$ 位 $8$ 位,而是任意位,都是可以做到的
实验3:开发板测试实验
学会将代码烧录到开发板,并观察结果
首先要设置环境
然后烧录程序,等待,观察结果
发现小灯闪烁
图略
修改循环,加大,发现小灯闪烁的频率变慢了
图略
观察开发板
这表示小灯 $1$ 只有红色,是 $P1.0$,对应代码的
而小灯 $2$ 有 $3$ 种颜色,为红色、绿色、蓝色,引脚分别对应 $P2.0,P2.1,P2.2$
不妨这里修改为
GPIO_PORT_P2, GPIO_PIN1
意思是,当前选择的是第 $2$ 号引脚(小灯 $2$ ),且设置颜色为绿色 $COLOR=1$
观察得到绿灯闪烁
核心代码:
实验1:实现奇校验:将待检测的数据存入到寄存器中,通过循环移位和异或运算计算出奇校验码(设校验位在最低位)
AREA Reset,DATA,READONLY DCD 0X12345678 DCD Reset_Handler AREA CODE_SEGMET,CODE,READONLY Reset_Handler proc export Reset_Handler [weak] ;MOV R0,#0xA ;用例1输入值0xA,结果0x15 ;MOV R0,#0xBA ;用例2输入值0xBA,结果0x174 ;MOV R0,#0xCBA ;用例3输入值0xCBA,结果0x1974 MOV R0,#0x7654 ;用例4输入值0x7654,结果0xECA9 MOV R1,R0 ;R1存储结果 MOV R2,#0 ;R2存储1的个数 MOV R3,#0 ;R3作为临时寄存器,最多只能使用R0、R1、R2、R3及xPSR寄存器 ;//---------------请在以下空白区域内编写代码------------// MOV R1,R0 ;R1存储结果,先把原始数据保留下来 LOOP CMP R0,#0 ;循环右移,每次取出最低位,直到取完所有位数 BEQ BRK AND R3,R0,#1 ;数与1做位与,得到的就是最低位的值,保留到R3中 LSR R0,#1 ;R0循环右移 EOR R2,R3,R2 ;R2是异或运算的结果,R3是最低位,做异或 B LOOP BRK EOR R2,R2,#1 ;因为奇校验,所以再做一次异或,该值就是校验位的值 LSL R1,#1 ;R1是原始数据,左移1位 ORR R1,R1,R2 ;然后和校验位做位或,就是把校验位放到最低位,得到的结果就是最后的值 ;//---------------请在以上空白区域内编写代码------------// ;最终结果存入R1 NOP ;检查时,请直接跳转至此 ENDP
实验2:实现海明码校验:将要检测的数据存入寄存器R1中,通过移位和异或运算计算出冗余位;可再通过修改数据中一位,存入寄存器R0中,利用校正因子找出该位,并修改。
完整代码比较长
; 海明码 AREA Reset,DATA,READONLY DCD 0x12345678 DCD Reset_Handler AREA CODE_SEGMET,CODE,READONLY Reset_Handler proc export Reset_Handler [weak] MOV R0,#0x20000000 ;内存基址 ;MOV R1,#0xAC ;8位 MOV R1,#0xD ;4位 ;MOV R1,#0x1 ;1位 ;MOV R1,#0x7 ;3位 MOV R10,R1 ;把R1的值保留到R10,是为了最后检查是不是正确还原 MOV R3,#0 ;R3计算数据有多少位 MOV R2,R1 ;保留原数据 CALCLEN CMP R2,#0 ;循环右移直到0,计算有多少位 BEQ BEGINCALCR LSR R2,#1 ADD R3,R3,#1 B CALCLEN BEGINCALCR MOV R4,#2 ;R4计算需要多少位校验位,显然最少是2位,从2开始尝试,R4=2^{R} MOV R5,#1 ADD R5,R5,R3 ADD R5,R5,R4 ;R5=1+K+R MOV R6,#1 LSL R4,R6,R4 ;R4=2^{R}=1<<R=R6<<R4 LOOPR CMP R4,R5 BGE BEGINSTORE LSL R4,#1 ;R+1,2^{R+1}=R4<<1 ADD R5,R5,#1 ;R+1,K+(R+1)+1=R5+1 B LOOPR BEGINSTORE ;SUB R3,R5,#1 ;R=(K+R+1)-1-K=R5-1-R3,N=K+R=R3+R5-1-R3=R5-1 MOV R3,R5 ;R=(K+R+1)-1-K=R5-1-R3,N=K+R=R3+R5-1-R3=R5-1 ; 至此,准备工作完成,R0是基址,R1是数据,R3是N=K+R,其余寄存器已经没用了 MOV R4,#1 ;R4表示INDEX,即当前在处理第几位,一开始处理第1位,之后每次加1 MOV R5,#1 ;R5表示下一个校验位要放在那里,一开始放在1,之后每次乘2 LOOPSTORE CMP R4,R3 BEQ BEGINHAMMING ;INDEX=N,说明处理完了,BREAK AND R2,R1,#1 ;R1是尚未处理的数据,与1做位与,取出最低位放在R2 LSR R1,#1 ;R1>>1,处理了最后一位,剩下的右移 RECMPOBLIGATE CMP R4,R5 BNE STORESKIPOBLIGATE ;R4=R5说明这一位是校验位,留空 MOV R4,#0 ;借用寄存器R4,存个0 SUB R6,R3,R5 ;R6=R3-R4=R3-R5=N-INDEX,是内存的偏移量,因R4被借用了,R5=R4 SUB R6,R6,#1 STRB R4,[R0,R6] ;这一位是校验位,用0填充 ADD R4,R5,#1 ;R4=R5+1,要填的是下一位 LSL R5,#1 ;下一个校验位的位置是乘2 B RECMPOBLIGATE STORESKIPOBLIGATE ;否则说明这一位不是校验位,要填在这一位 SUB R6,R3,R4 ;R6=R3-R4=N-INDEX,是内存的偏移量 SUB R6,R6,#1 STRB R2,[R0,R6] ;用字节存储,存到R0基址偏移R6的地方 ADD R4,R4,#1 ;INDEX++ B LOOPSTORE BEGINHAMMING ;开始计算海明码 ;至此,R0表示基址,R3表示N=K+R,R5表示下一个校验位在第几位,其余寄存器已经没用了 LSR R5,#1 ;先把表示校验位的标志号退回来 MOV R9,R5 ;把最高位的校验位所在的位置记下来,省的后面再算(虽然后面也可以算得出来) MOV R2,R3 ;R2=INDEX,表示当前遍历到第几位 ITERNEXT SUB R2,R2,#1 ;INDEX-- CMP R2,#0 ;INDEX到0说明全部做完了 BEQ ENDHAMMING CMP R2,R5 BNE BEGINITER ;R2不等于R5,说明这一位不是校验位,就开始处理 ;这一位是校验位,那么跳过,继续遍历 LSR R5,#1 ;校验位除2 B ITERNEXT BEGINITER MOV R4,#1 ;表示当前在做哪一个校验位,每一个数字肯定从1开始检查 SUB R6,R3,R2 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R1,[R0,R6] ;从内存R0偏移R6取出一个字节到寄存器R1,R1表示当前处理的值 MOV R7,R2 ;R2是INDEX,不能动,但是要取出R2的二进制,所以只能复制一份 LOOPXOR CMP R7,#0 BEQ ITERNEXT ;这一位的二进制全处理完了,遍历下一个数字 TST R7,#1 BEQ SKIPXOR ;说明这一位二进制是0,那么不用与对应的校验位做异或 SUB R6,R3,R4 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R8,[R0,R6] ;R8是当前要处理的校验位的值 EOR R8,R8,R1 ;R1是当前位的值,R8是对应校验位的值,做异或 STRB R8,[R0,R6] ;存回去 SKIPXOR LSL R4,#1 ;校验位从1、2、4……移动 LSR R7,#1 ;右移一位,开始看下一位要不要做异或 B LOOPXOR ENDHAMMING ;至此,海明码已经求完,结果见内存 ;R0表示基址,R3表示N=K+R,其余寄存器已经没用了 MODIFY ;下面随机修改一个值,不妨修改第3位为他的取反 SUB R6,R3,#3 SUB R6,R6,#1 LDRB R1,[R0,R6] ;这里以修改掉第3位为例 EOR R1,R1,#1 ;取反,和1做异或 STRB R1,[R0,R6] BEGINCHECK ;开始计算校验海明码 MOV R5,#1 ;下一个校验位在几号 MOV R2,#0 ;R2=INDEX,表示当前遍历到第几位 CHECKNEXT ADD R2,R2,#1 ;INDEX++ CMP R2,R3 ;INDEX到N+1说明全部做完了 BEQ ENDCHECK CMP R2,R5 BNE CHECKITER ;R2不等于R5,说明这一位不是校验位,就开始处理 ;这一位是校验位,那么跳过,继续遍历 LSL R5,#1 ;校验位乘2 B CHECKNEXT CHECKITER MOV R4,#1 ;表示当前在做哪一个校验位,每一个数字肯定从1开始检查 SUB R6,R3,R2 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R1,[R0,R6] ;从内存R0偏移R6取出一个字节到寄存器R1,R1表示当前处理的值 MOV R7,R2 ;R2是INDEX,不能动,但是要取出R2的二进制,所以只能复制一份 LOOPCHECKXOR CMP R7,#0 BEQ CHECKNEXT ;这一位的二进制全处理完了,遍历下一个数字 TST R7,#1 BEQ SKIPCHECKXOR ;说明这一位二进制是0,那么不用与对应的校验位做异或 SUB R6,R3,R4 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R8,[R0,R6] ;R8是当前要处理的校验位的值 EOR R8,R8,R1 ;R1是当前位的值,R8是对应校验位的值,做异或 STRB R8,[R0,R6] ;存回去 SKIPCHECKXOR LSL R4,#1 ;校验位从1、2、4……移动 LSR R7,#1 ;右移一位,开始看下一位要不要做异或 B LOOPCHECKXOR ENDCHECK ;至此,海明码校验结果已经保存在各个校验位,R0是基址,R3是一共多少位,其余寄存器已经没用了 ;下面开始找哪一位出错了 MOV R4,R9 ;从最左边的校验位开始 MOV R5,#0 ;R5表示S,计算哪一位出错了 LOOPPRODSUM CMP R4,#0 BEQ FINDWHO ; 说明已经算完了全部的校验位 LSL R5,#1 ;左移1位 SUB R6,R3,R4 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R7,[R0,R6] ;R7是当前要处理的校验位的值 ADD R5,R5,R7 ;R5=R5+R7,加上当前的校验位的值,0,或者1 LSR R4,#1 ;校验位除2 B LOOPPRODSUM ;二进制转十进制 FINDWHO ;至此,已经找到了哪一位是错的,就是第R5位 SUB R6,R3,R5 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R7,[R0,R6] ;从内存取出错掉的那个数 EOR R7,R7,#1 ;取反 STRB R7,[R0,R6] ;存回去 ;至此,内存的数据应该已经恢复了 ;可以看一眼截图,发现正确 ;下面开始把这个数字取出来,还原 MOV R2,R9 ;下一个校验位的位置 MOV R4,R3 ;依次遍历,二进制还原为十进制 MOV R5,#0 LOOPBINTDEC SUB R4,R4,#1 ;R4-- CMP R4,#0 BEQ EXIT CMP R2,R4 BEQ BINTDECNEXT ;这一位是校验位,跳过 LSL R5,#1 ;乘2 SUB R6,R3,R4 SUB R6,R6,#1 ;R6表示内存的偏移量 LDRB R7,[R0,R6] ;从内存取出一个数 ADD R5,R5,R7 ;加到R5 B LOOPBINTDEC BINTDECNEXT LSR R2,#1 ;下一个校验位的位置,除2 B LOOPBINTDEC EXIT ;此时,R5的值,就应该是原来的数字的值,即R5=R10说明正确 NOP ENDP END
实验3:开发板测试实验
#include <ti/devices/msp432p4xx/driverlib/driverlib.h> int main(void) { volatile uint32_t i; // Stop watchdog timer WDT_A_hold(WDT_A_BASE); // Set P1.0 to output direction GPIO_setAsOutputPin( GPIO_PORT_P1, GPIO_PIN0 ); while(1) { // Toggle P1.0 output GPIO_toggleOutputOnPin( GPIO_PORT_P1, GPIO_PIN0 ); // Delay for(i=10000; i>0; i--); } }