实验分为两块儿板子,一块儿作为发送,一块儿作为吸收。
经由各种交叉验证,可以测试出来发送端没问题,可是吸收端一上电就挂掉了。
调试之后创造是进入了hardfault缺点。
而发送和吸收都是公用的底层代码,为什么只有吸收端进入hardfault,但是发送端却没有。
只能一步步跟踪,终极定位在HAL库的SPI发送函数里面。
如下图所示,箭头所指地方,实行到这里一下就over了。

看下这条语句,首先想到的进入hardfault的缘故原由:是不是操作pData越界了?检讨定义的buffer和要发送的数据长度没有问题,buffer长度为5,发送个数也为5。
以是不存在这种情形。
那么还有什么可能?
先来看看我定义的发送数组,也便是pData指针所操作的缓冲区:
TX_ADR_WIDTH RX_ADR_WIDTH 这两个是宏定义都为5,也便是这里我在nrf24l01Class类里面定义两个数组,这两个数组大小都为5。
发送端会发送TX_ADDRESS这个数组,吸收端会发送RX_ADDRESS这个数组。
奇怪的便是HAL_SPI_Transmit 函数发送TX_ADDRESS不会进入hardfault,发送RX_ADDRESS会进入hardfault,这两个数组也除了名称不一样,长度都千篇一律。
是该揭开它真正面纱的时候了:我们再看涌现问题的那条语句:
hspi->Instance->DR = ((uint16_t )pData);
再看我们数组的定义:uint8_t
也便是涌现问题这条语句把我们定义的uint8_t 数组转换成了uint16_t 同时进行半字的操作(同时操作两个byte)。
这样看确实提高了实行效率,但是却也埋下了隐患。
产生这样的问题,我们就不得不扯得更远一点,arm内核对数据的非对齐数据访问。
Arm对内存的访问支持字(4byte)、半字(2byte)、字节(1byte)的直接访问,但是呢他们是有一定的哀求的:
存取字时哀求地址按字对齐,也便是地址假如4的整数倍,如0x0000、0x0004、0x0008(该地址只是举例,mcu的地址分配请参考详细手册的地址映射图)存取半字是哀求地址按半字对齐,也便是地址是2的倍数,这样如果通过0x0001、0x0003这样非2倍数的地址来读取一个半字就会产生缺点存取字节大略,只要地址不超范围就可以这么看来是不是有点清晰了,我们涌现缺点的地方不便是在操作一个半字(uint16占用两个byte也便是半字),那么进入到了hardfault该当便是操作了读取了非半字对齐的地址
好,下面我们再来剖析一下TX_ADDRESS 和RX_ADDRESS在存储区到底是怎么存储的。
这两个数组是都放在nrf24L01Class里面的,并且RX_ADDRESS 是在TX_ADDRESS。
所有分配的存储空间也是挨着的。
如图中所示左侧浅蓝色的5个方格代表TX_ADDRESS在内存中的排列,橙色5个方格代表RX_ADDRESS在内存中的排列。
假设TX_ADDRESS的开始地址为0x20000000,那么RX_ADDRESS的开始地址便是0x20000005
0x20000005 不是半字对齐的(不是2的整数倍),这样利用(uint16_t )半字指针直接读取就产生了hardfault。
这样缘故原由就一目了然了,再来剖析一下TX_ADDRESS为什么不会产生hardfault,现在再去转头看下出错的地方的函数。
有个if判断,也便是剩余的数量大于1的时候都是按照半字读取,以是前面四个byte按照半字读取两次并且地址也按半字对齐了,就没问题。
而剩的末了一个在else语句里面按照字节来读取了,这样就恰好不会产生hardfault。
好了,缘故原由知道了,那么我们该如何避免这种情形发生。
很大略,按照上面的剖析我们只要担保RX_ADDRESS按照半字对齐就没问题。
以是供应三种办理思路:
1、把TX_ADDRESS 和RX_ADDRESS的长度都改成6,自己可以画下内存分配图,看看是不是半字对齐了(往后定义数组只管即便定义成偶数的长度,顶多摧残浪费蹂躏一个byte的内存空间这个完备不用在乎)
修正后的代码如下:
2、通过一些预编译对齐指令#pragma pack(2) 进行地址对齐,#pragma pack() 结束对齐,能编译通过但是测试弗成
查资料后#pragma pack(2) 并不一定会按照2字节对齐,而是和成员自身大小比较,选择较小的,由于我们定义的是uint8_t,也便是一个成员之占用1byte 小于2,以是还是会按照1byte对齐的。
3、在数组定义前添加 __align(2) ,但是测试创造,在c里面可以,在c++类定义里面包含的数组前面加它编译不过去。
c++
利用 __attribute((aligned (2)))
终极测试通过方法1和方法3都可以