文章目录
- 中值滤波
- 创建项目
- 1、点击project -> new project,选择stm32芯片
- 2、配置运行环境
- 3、创建.s汇编文件
- 汇编代码
- 源码
- 分析
- 修改
- 调试
- 报错
- 已解决
- 1、more than one section matches selector - cannot all be first/last
- 2、Error: L6218E: Undefined symbol main (referred from __rtentry2.o)
- 3、error: A1163E: Unknown opcode xxx, expecting opcode or Macro
- 4、数组赋值时,部分字节无法写入,以及影响后续写入
- 未解决
- 参考资料
中值滤波
滤波(Wave Filtering)是指将信号中特定波段频率滤除的操作,是抑制与防止干扰的重要措施。
中值滤波是一种信号处理手段,能够有效地抑制噪声。它的原理是将数字图像或者数字序列中某一点的值用它邻近点的中值代替。
方法如下:对一个数字信号序列xj(-∞<j<∞)进行滤波处理时,首先要定义一个长度为奇数的L长窗口。设在某一个时刻,窗口内的信号样本为x(1),…,x((N+1)/2),…,x(N),其中x((N+1)/2)为位于窗口中心的信号样本值。对这L个信号样本值按从小到大的顺序排列后,其中位数便定义为中值滤波的输出值。
由此可见,要实现中值滤波,实质上是实现排序算法,求中位数。
在嵌入式系统的数据采样应用中,采集数据收到噪声影响会出现起伏变化,因此经常采取中值滤波算法将干扰数据去除掉。
接下来我将在Keil5中用arm汇编语言编写一个排序求中位数的程序,演示中值滤波算法。
创建项目
1、点击project -> new project,选择stm32芯片
这里说明一下,本代码仅仅是演示arm汇编语言实现排序算法返回中值,使用仿真器调试,并不会真正烧录到单片机中。理论上在选择芯片的时候没有那么讲究。但是如果你手里有单片机的话,建议选择你拥有的那种类型。
2、配置运行环境
勾选图中的选项即可。
注意:在这里你不用配置运行环境点击cancel的话,在使用用仿真器进行调试的时候会出现问题。调试程序不会前往你的指令地址执行指令,而是不断地跳转到其他无指令地址。为什么会这样以及如何解决,笔者作为嵌入式开发初学者并没有找到解决办法。为了防止出现这种情况,请在这里配置运行环境。
点击ok按钮后,在上方点击魔术棒,点击output,勾选create hex file。
再点击debug,按图中所示进行配置:
3、创建.s汇编文件
点击左上角创建新文件按钮,再点击保存按钮,在弹出的窗口中将文件保存为汇编文件。
保存之后,将这个文件添加到项目中。
汇编代码
源码
在老师给我们发的参考资料中,代码如图所示:
这里将代码复制出来:
AREA SORT,CODE,READONLY
ENTRY
MOV R0,#9;排序个数
LDR R2,=0X40000000;原始数据起始地址
SUB R1,R0,#1
MOV R4,#4
MLA R3,R1,R4,R2;数组结束地址
SUB R4,R3,#4;倒数第二个数地址
LOOP1 ADD R5,R2,#4;内循环起始地址
LOOP2 LDR R6,[R2]
LDR R7,[R5]
CMP R6,R7;比较交换,从小到大排序
STRHI R6,[R5]
STRHI R7,[R2]
ADD R5,R5,#4;修改内循环地址
CMP R5,R3;内循环结束比较
BLS LOOP2
ADD R2,R2,#4;修改外循环地址
CMP R2,R4;外循环结束比较
BLS LOOP1
LDR R2,=0X40000000
MOV R0,R0,LSR #1
MOV R4,#4
MLA R3,R0,R4,R2
LDR R1,[R3];找到中间数赋值给R1
MOV R0,#100
END
注意,本段代码为原书代码,经测试不能直接使用,笔者使用的代码在后面给出
分析
正如前文所说,中值滤波的原理其实就是排序求中值,以上代码的功能也是如此,只是将结果放在了寄存器R1里面。经过分析,以上代码的等价c语言代码如下:
int data[9] //初始化省略
for (int i = 0 ; i < 8 ; i++)
for (int j = i+1 ; j < 9 ; j++){
if (data[i]>data[j]){
int t = data[j];
data[j] = data[i];
data[i] = t;
}
}
下面这一段是对汇编代码的分析,即解析如何通过汇编代码反推源程序的。读者可以选择性地阅读或者跳过。
根据原文的注释,寄存器R2保存的一个地址,是数组的起始地址。R0是数据个数,R1=R0-1。R4存了个4,根据下面R3 = R2+R1*R4,可知R4中的4表示一个数据的长度,int为4字节,R3为最后一个数据地址。R4又被赋值为R3-4,即倒数第二个数据的地址。
根据注解,R5是内循环起始地址,R5 = R2 +4 ,即内循环变量j = 外循环变量i +1,在内循环中比较外循环变量对应的下标的数据data[i]与内循环下标的数据data[j]进行比较,data[i]>data[j]时交换变量。交换之后将内循环变量j增加1(汇编程序是将地址增加4个字节),并判断有没有大于等于数组长度,没有的话就循环LOOP2内循环。
当离开LOOP2内循环后,外循环变量i增加1(汇编程序是将地址增加4个字节),并判断是否指向倒数第二个数据,没有的话就执行外循环LOOP1。
两个for循环执行完毕后,数组内部的数据已经按从小到大排好。再将数组中间的数字保存到R1寄存器中。
修改
通过上文的高级语言代码以及中值滤波原理可以知道,这段汇编代码是对数组进行排序,再选出数组中的中值。但是显而易见的是上文的汇编代码中,除了给出了数组的起始地址之外,并没有对数组进行赋值。因此在使用这段代码之前,我们要进行修改,即手动赋值。
首先,修改第一行的数组长度,将9改成3。从原理得知,我们仅需要使用一个奇数长度就行,为了方便演示使用3就行。
其次添加以下代码到LDR R2指令后面:
MOV R9,#0
STR R9,[R2]
MOV R9,#2
STR R9,[R2,4]
MOV R9,#1
STR R9,[R2,8]
STR指令,将寄存器中的数据保存到内存中,使用方式是STR 寄存器,内存地址。使用STR指令是因为MOV指令不接受内存地址。
上文得知R2是数组起点地址(第一个数据地址),首先将要保存的数据放在一个没用的寄存器里面,然后使用STR指令将这个寄存器里的值送到内存地址中。内存地址的表示方式是[起始地址,偏移量](起始地址+偏移量)。
上面的代码就是将0,2,1赋值给数组起点地址,起点地址+4字节(下标1),起点地址+8字节(下标2)
最后,为汇编代码加上main。
修改后的代码如下:
AREA SORT,CODE,READONLY
ENTRY
EXPORT __main
__main
MOV R0,#3
LDR R2,=0X40000000
MOV R9,#0
STR R9,[R2]
MOV R9,#2
STR R9,[R2,4]
MOV R9,#1
STR R9,[R2,8]
SUB R1,R0,#1
MOV R4,#4
MLA R3,R1,R4,R2
SUB R4,R3,#4
LOOP1 ADD R5,R2,#4
LOOP2 LDR R6,[R2]
LDR R7,[R5]
CMP R6,R7
STRHI R6,[R5]
STRHI R7,[R2]
ADD R5,R5,#4
CMP R5,R3
BLS LOOP2
ADD R2,R2,#4
CMP R2,R4
BLS LOOP1
LDR R2,=0X40000000
MOV R0,R0,LSR #1
MOV R4,#4
MLA R3,R0,R4,R2
LDR R1,[R3]
MOV R0,#100
END
这下这段代码就可以用了!
将这段代码写入文件!
调试
由于笔者也是个学习嵌入式的新手,写本文写是为了个人记录。因此对于Keil5调试还很不熟悉,如果我的操作有什么不当的地方希望大家批评指正!
第一步,点击左上角编译。
这一步笔者遇到的报错,会在后面的章节写下来供大家参考
第二步,点击右上角的调试按钮。请注意在debug设置中选择使用仿真器simulator,否则会报错。
点击调试按钮后,进入到调试页面。注意现在数组长度是三,从开头到结尾数据依次是0,2,1。根据上文提出的原理,数据0,2,1会写在起始地址0x40000000的数组里面,并且在调试完成后数组顺序会变成0,1,2,并将中值1保存到R1中。
首先按上图打上断点,然后按下图进行调试:
对main.s中main函数起点右键,点击set program counter,程序计数器保存这一条指令的地址,执行时直接从这里执行。
点击运行看看效果。
程序执行到了第一个断点,并对寄存器与内存进行相应的修改,如上图所示。
再点击执行按钮,程序会执行到第二个断点前面。第二个断点是给R1赋值中值的前一步,因此我们会看见在内存地址中的数组元素已经按从小到大进行排列。
再点击执行单步,将最终结果放入R1:
这样,我们就拿到了中值。这里数组是0,2,1,最终结果就是1。
报错
已解决
1、more than one section matches selector - cannot all be first/last
经过查阅资料,该报错是由启动文件重复造成。
当配置运行环境的时候,如果没有选择CMSIS/CORE与DEVICE/startup,直接运行上面的汇编代码会报错,需要将汇编代码第一行的SORT改成RESET。但是如果你在配置运行环境的时候已经选择了CMSIS/CORE与DEVICE/startup,然后第一行还是写的RESET,那么就会报这个错。
将第一行的RESET改成其他名字即可。
2、Error: L6218E: Undefined symbol main (referred from __rtentry2.o)
没有声明main函数。解决方式是为汇编代码加上main。
3、error: A1163E: Unknown opcode xxx, expecting opcode or Macro
arm编程的时候,汇编代码不能顶格写,要带空格。
但是对于__main,LOOP1,LOOP2这种标志必须顶格写,不然会报一样的错误。
4、数组赋值时,部分字节无法写入,以及影响后续写入
一个报错实例:设数组长度为7,初始地址为0x40000000。向数组中依次赋值0~7。
一个int4个字节,前四个数字的地址为0x40000000,0x40000004,0x40000008,0x4000000c,这四个数字正常写入内存。
但是第五个数字(内存地址0x40000010)无法正常写入。
不仅是不能写入第五个数,还会将第六个数字赋值给第五个数的内存。
解决方式:修改数组的起始地址。
将地址改成0x40000020,程序正常执行。
未解决
正如上文第四个报错,为什么有的内存地址会无法写入,并且还会影响后续的地址写入。在汇编语言中,写入的地址是自己写的:
STR R9,[R2,8]
[R2,偏移]算出来的地址是不会有错的。为什么地址0x40000010无法被写入,还会影响后续的内存写入操作?我在网络上搜集资料也找不到结果。求大佬解答。
参考资料