我写了一个python 3脚本来测试到FPGA的SPI链接。它运行在Raspberry Pi 3上。测试的工作方式如下:将FPGA置于测试模式(按动开关)后,发送第一个字节,该字节可以是任何值。然后再无限期发送其他字节。每个值都按发送的第一个值递增,并被截断为8位。因此,如果第一个值为37,则FPGA期望以下顺序:
37、74、111、148、185、222、4、41 ...
一些额外的IO引脚用于在设备之间发出信号-RUN(RPi输出)开始测试(这是必需的,因为如果期望字节,FPGA会在15ms左右超时),而ERR(FPGA输出)会发出错误信号。因此可以在两端计算错误。
此外,RPi脚本编写了一行摘要的已发送字节和每百万字节错误数。
所有这些都很好。但是运行约3天后,我在RPi上收到以下错误:
free():无效的指针:0x00405340
在两个相同的测试设置上,即使在相同的内存地址下,我也得到这个完全相同的错误。最近的报告说 “发送了4294M字节,0个错误”
我似乎已经证明了SPI链接,但是我担心这个长时间运行的程序由于没有明显的原因而崩溃。
这是我的测试代码的重要部分:
def _report(self, msg):
now = datetime.datetime.now()
os.system("echo \"{} : {}\" > spitest_last.log".format(now, msg))
def spi_test(self):
global end_loop
input("Put the FPGA board into SPI test mode (SW1) and press any key")
self._set_run(True)
self.END_LOOP = False
print("SPI test is running, CTRL-C to end.")
# first byte is sent without LOAD, this is the seed
self._send_byte(self._val)
self._next_val()
end_loop = False
err_flag = False
err_cnt = 0
byte_count = 1
while not end_loop:
mb = byte_count % 1000000
if mb == 0:
msg = "{}M bytes sent, {} errors".format(int(byte_count/1000000), err_cnt)
print("\r" + msg, end="")
self._report(msg)
err_flag = True
else:
err_flag = False
#print("sending: {}".format(self._val))
self._set_load(True)
if self._errors and err_flag:
self._send_byte(self._val + 1)
else:
self._send_byte(self._val)
if self.is_error():
err_cnt += 1
msg = "{}M bytes sent, {} errors".format(int(byte_count/1000000), err_cnt)
print("\r{}".format(msg), end="")
self._report(msg)
self._set_load(False)
# increase the value by the seed and truncate to 8 bits
self._next_val()
byte_count += 1
# test is done
input("\nSPI test ended ({} bytes sent, {} errors). Press ENTER to end.".format(byte_count, err_cnt))
self._set_run(False)
(为便于说明,有一个命令行选项可以每百万字节人为地创建一个错误。因此有“ err_flag”变量。)
我已经尝试在控制台模式下使用python3,并且byte_count变量的大小似乎没有问题(根据我对python整数大小限制的了解,应该没有。)
任何人都知道可能是什么原因造成的?
答案 0 :(得分:4)
此问题仅与早于3.5的spidev版本有关。以下评论是在我使用的是spidev升级版的前提下完成的。
############################## ##############################
我可以确认此问题。 RPi3B和RPi4B都具有持久性。在RPi3和RPi4上都使用python 3.7.3。我尝试过的spidev版本是3.3、3.4和最新的3.5。通过单行循环,我能够多次重现此错误。
def spiLoop():
spidevice2 = spidev.SpiDev()
spidevice2.open(0, 1)
spidevice2.max_speed_hz = 15000000
spidevice2.mode = 1 # Data is clocked in on falling edge
for j in range(100000):
spidevice2.xfer2([0x00, 0x00, 0x00, 0x00])
spidevice2.close()
最多可能需要11个小时,具体取决于RPi版本。在1073014000调用(四舍五入为1000)之后,脚本由于“无效指针”而崩溃。发送的字节总数与 danmcb 的情况相同。似乎2 ^ 32个字节代表一个限制。
我尝试了不同的方法。例如,不时调用close(),然后再调用open()。这没有帮助。
然后,我尝试在本地创建spiDev对象,以便为每批数据重新创建它。
def spiLoop():
spidevice2 = spidev.SpiDev()
spidevice2.open(0, 1)
spidevice2.max_speed_hz = 25000000
spidevice2.mode = 1 # Data is clocked in on falling edge
to_send = [0x00] * 2**12 # 4096 bytes
for j in range(100):
spidevice2.xfer2(to_send)
spidevice2.close()
del spidevice2
def runSPI():
for i in range(2**31 - 1):
spiLoop()
print((2**12 * 100 * (i + 1)) / 2**20, 'Mbytes')
它仍然在约xfer2([0x00,0x00,0x00,0x00])的2 ^ 30次调用对应于大约2 ^ 32字节。
EDIT1
为了加快处理速度,我使用下面的代码发送4096字节的块。然后,我在本地重复创建SpiDev对象。花了2个小时才达到2 ^ 32字节计数。
import importlib
def spiLoop():
importlib.reload(spidev)
spidevice2 = spidev.SpiDev()
spidevice2.open(0, 1)
spidevice2.max_speed_hz = 25000000
spidevice2.mode = 1 # Data is clocked in on falling edge
to_send = [0x00] * 2**12 # 4096 bytes
for j in range(100):
spidevice2.xfer2(to_send)
spidevice2.close()
del spidevice2
def runSPI():
for i in range(2**31 - 1):
spiLoop()
print((2**12 * 100 * (i + 1)) / 2**20, 'Mbytes')
EDIT2
即时重新加载spidev也无济于事。我在RPi3和RPi4上都尝试了此代码,结果相同:
program = '''
import spidev
spidevice = None
def configSPI():
global spidevice
# We only have SPI bus 0 available to us on the Pi
bus = 0
#Device is the chip select pin. Set to 0 or 1, depending on the connections
device = 1
spidevice = spidev.SpiDev()
spidevice.open(bus, device)
spidevice.max_speed_hz = 250000000
spidevice.mode = 1 # Data is clocked in on falling edge
def spiLoop():
to_send = [0xAA] * 2**12
loops = 1024
for j in range(loops):
spidevice.xfer2(to_send)
return len(to_send) * loops
configSPI()
bytes_total = 0
while True:
bytes_sent = spiLoop()
bytes_total += bytes_sent
print(int(bytes_total / 2**20), "Mbytes", int(1000 * (bytes_total / 2**30)) / 10, "% finished")
if bytes_total > 2**30:
break
'''
for i in range(100):
exec(program)
print("program executed", i + 1, "times, bytes sent > ", (i + 1) * 2**30)
EDIT3
执行代码片段也不能隔离问题。发送第4个1GB数据后,它崩溃了。
// RecipeList
const toggleFavoriteAction = (recipe: RecipeConfig) => {
return favorites.includes(recipe)
? dispatch(addToFavorites(recipe))
: dispatch(removeFromFavorites(recipe));
};
const props = {
toggleFavoriteAction,
favorites,
};
return (
<div>
{recipes.results &&
Array.isArray(recipes.results) &&
recipes.results.map((data: RecipeConfig, key: number) => (
<div key={key}>
<RecipeListItem {...data} {...props} />
</div>
))}
</div>
);
答案 1 :(得分:0)
我相信原始提问者的问题是参考泄漏。具体是py-spidev issue 91。所说的参考泄漏已在spidev的3.5版本中得到修复。
Python使用共享的对象池表示较小的整数值*,而不是每次都重新创建它们。因此,当代码泄漏对小数字的引用时,结果不是内存泄漏,而是引用计数不断增加。 python spidev库有一个问题,它以这种方式泄漏了对小整数的引用。
在32位系统上**,最终结果是引用计数溢出。然后减少溢出的引用计数,然后引用计数系统释放对象。
我无法解释的是另一个答案,声称他们仍然可以在3.5版本中重现该问题。该问题本应在该版本中得到解决。
*具体为-3到256之间的数字,因此可以用无符号字节表示的任何内容加上一些负值(大概是因为它们通常用作错误返回)和256(大概是因为它经常用作乘数)。
**在64位系统上,引用计数在人类生命周期内不会溢出。