这是禁用 PIC24 中断的安全方法吗?
Is this a safe way to disable interrupts on PIC24?
我正在从事一个使用 Microchip PIC24FJ256GA702 的项目。几天来,我一直在寻找一些 UART 代码的间歇性错误。它使用中断服务例程 (ISR) 来传输名为 char txBuff[]
的缓冲区及其长度 int txLen
.
在主要(即非 ISR)代码中,我偶尔会访问缓冲区以添加字节或查看还有多少字节要传输。为确保主代码看到缓冲区的干净副本,它具有以下形式的关键部分:
Disable interrupts
Read and write 'txBuff' and its length 'txLen'
Enable interrupt
有几种方法可以禁用此 PIC 上的 ISR。包括:使用DISI
指令,或清除GIE
位,或改变中断优先级。
但是我选择清除中断以启用IECx
中特定中断位的位。在本例中,它是 IEC1bits.U2TXIE,因为我使用的是 UART2。我这样做是因为它很简单,它不会禁用不相关的中断,而且它在我的其他项目中一直运行良好。所以在C中,临界区是:
IEC1bits.U2TXIE = 0;
copyOfTxLen = txLen;
...
IEC1bits.U2TXIE = 1;
反汇编清单开始:
BCLR 0x9B, #7 // disable interrupt
MOV txLen, W1 // read txLen
...
问题:我现在相当确定 ISR 在 read txLen
期间偶尔会被调用,因此 copyOfTxLen
最终成为 txLen
在调用 ISR 之前,但关键部分的其余部分看到的变量处于调用 ISR 之后的状态。
问题:我的代码有问题吗?如果是,如何避免故障?
一些背景:
- UART 是 运行 高速(250 kBaud),PIC24 时钟为 8 MHz,每 160 条指令一个字节,因此 ISR 非常繁忙,我认为这增加了命中的机会竞争条件与我之前的项目相比。
- 我看到 dsPIC33/PIC24 Family Reference Manual / Interrupts,第 2.3.1 节注释 2 说“清除 GIE 位和禁用中断之间有一个周期延迟。”现在这是指
GIE
,而不是 U2TXIE
,我不确定它是否会导致像我看到的问题,因为 MOV
指令是一个 2 周期指令。但是,尽管如此,它看起来像是一种诱捕粗心程序员的狡猾方法。
- 如果我尝试调试它或添加任何调试代码,本已非常间歇性的问题就会消失(感觉像是竞争条件)。我不想只是添加一些
NOP
s 而不知道我为什么要添加,因为除非你真正修复它们,否则竞争条件总是会反咬你。
问题中的代码有误。
要修复它,请将旨在禁用中断的表达式包装在这样的宏中,例如 IEC1bits.U2TXIE = 0
:
__write_to_IEC(IEC1bits.U2TXIE = 0);
XC16 compiler release notes 说:
__write_to_IEC(X)
- a macro that wraps the expression X with an appropriate number of nop instructions to ensure that the write to the IEC
register has taken effect before the program executes. For example, __write_to_IEC(IEC0bits.T1IE = 0);
will not progress until the device has disabled the interrupt enable bit for T1 (Timer1).
在XC16编译器自带的p24FJ256GA702.h
中定义了宏:
#define __write_to_IEC(X) \
( (void)(X), \
__builtin_nop() \
)
因此,尽管 PIC24FJ256GA705 FAMILY datasheet or in the separate Interrupts document 中的任何地方都没有提及,但很明显这张图片需要一个 NOP
。
更新:我在 dsPIC33EP256MU806(dsPIC33E 系列)上发现了完全相同的问题,它需要两条 NOP
指令。
我正在从事一个使用 Microchip PIC24FJ256GA702 的项目。几天来,我一直在寻找一些 UART 代码的间歇性错误。它使用中断服务例程 (ISR) 来传输名为 char txBuff[]
的缓冲区及其长度 int txLen
.
在主要(即非 ISR)代码中,我偶尔会访问缓冲区以添加字节或查看还有多少字节要传输。为确保主代码看到缓冲区的干净副本,它具有以下形式的关键部分:
Disable interrupts
Read and write 'txBuff' and its length 'txLen'
Enable interrupt
有几种方法可以禁用此 PIC 上的 ISR。包括:使用DISI
指令,或清除GIE
位,或改变中断优先级。
但是我选择清除中断以启用IECx
中特定中断位的位。在本例中,它是 IEC1bits.U2TXIE,因为我使用的是 UART2。我这样做是因为它很简单,它不会禁用不相关的中断,而且它在我的其他项目中一直运行良好。所以在C中,临界区是:
IEC1bits.U2TXIE = 0;
copyOfTxLen = txLen;
...
IEC1bits.U2TXIE = 1;
反汇编清单开始:
BCLR 0x9B, #7 // disable interrupt
MOV txLen, W1 // read txLen
...
问题:我现在相当确定 ISR 在 read txLen
期间偶尔会被调用,因此 copyOfTxLen
最终成为 txLen
在调用 ISR 之前,但关键部分的其余部分看到的变量处于调用 ISR 之后的状态。
问题:我的代码有问题吗?如果是,如何避免故障?
一些背景:
- UART 是 运行 高速(250 kBaud),PIC24 时钟为 8 MHz,每 160 条指令一个字节,因此 ISR 非常繁忙,我认为这增加了命中的机会竞争条件与我之前的项目相比。
- 我看到 dsPIC33/PIC24 Family Reference Manual / Interrupts,第 2.3.1 节注释 2 说“清除 GIE 位和禁用中断之间有一个周期延迟。”现在这是指
GIE
,而不是U2TXIE
,我不确定它是否会导致像我看到的问题,因为MOV
指令是一个 2 周期指令。但是,尽管如此,它看起来像是一种诱捕粗心程序员的狡猾方法。 - 如果我尝试调试它或添加任何调试代码,本已非常间歇性的问题就会消失(感觉像是竞争条件)。我不想只是添加一些
NOP
s 而不知道我为什么要添加,因为除非你真正修复它们,否则竞争条件总是会反咬你。
问题中的代码有误。
要修复它,请将旨在禁用中断的表达式包装在这样的宏中,例如 IEC1bits.U2TXIE = 0
:
__write_to_IEC(IEC1bits.U2TXIE = 0);
XC16 compiler release notes 说:
__write_to_IEC(X)
- a macro that wraps the expression X with an appropriate number of nop instructions to ensure that the write to theIEC
register has taken effect before the program executes. For example,__write_to_IEC(IEC0bits.T1IE = 0);
will not progress until the device has disabled the interrupt enable bit for T1 (Timer1).
在XC16编译器自带的p24FJ256GA702.h
中定义了宏:
#define __write_to_IEC(X) \
( (void)(X), \
__builtin_nop() \
)
因此,尽管 PIC24FJ256GA705 FAMILY datasheet or in the separate Interrupts document 中的任何地方都没有提及,但很明显这张图片需要一个 NOP
。
更新:我在 dsPIC33EP256MU806(dsPIC33E 系列)上发现了完全相同的问题,它需要两条 NOP
指令。