【问题标题】:Odd PIC behavior with variables set by ISR带有由 ISR 设置的变量的奇怪 PIC 行为
【发布时间】:2019-07-21 23:10:59
【问题描述】:

我在我的 PIC 代码中观察到一些奇怪的行为,我从 由 ISR 写入两个不同缓冲区的全局变量 连续,但该值似乎只成功写入 两个缓冲区之一。当我尝试使用不同的变量时 不是由 ISR 写入的,它会正确地复制到两个缓冲区中。

我的设置是使用 XC8 编译器版本 1.45 的 PIC16LF15355。我是 将 PIC MSSP 模块之一配置为 SPI 接口以写入 测量数据和开关状态到射频收发器。我还有一个 接收和处理数据的远程射频收发器 PIC16LF15355 通过 SPI 接口。对于这两个 PIC,我正在配置 第二个 MSSP 模块作为连接到 Raspberry Pi 的 I2C 接口 调试目的。写入到 SPI 缓冲区的任何数据 发送端也被复制到 I2C 缓冲区,这样就可以 定期阅读以查看正在发送的内容。同样,读取的数据 进入接收端的 SPI 缓冲区被复制到 I2C 缓冲区,所以 可以定期读取它以查看正在接收的内容。

发射端有一个开关输入,用 定时器 ISR(下面代码中的 timer0_handler)。状态被写入 通过此 ISR 进入 sw1State。当需要传输数据时, 开关状态和其他测量数据被复制到 I2C 缓冲区 然后将 I2C 缓冲区复制到 SPI 缓冲区中进行传输。 当我从 Pi 定期对 I2C 缓冲区内容进行采样时,我可以看到 开关状态响应于开关的按下而改变。但是当我 在接收端定期采样 I2C 缓冲区内容,我只 见过开关状态默认值 (1)。

奇怪的是,如果我更改代码以从 不是由 ISR 写入的变量,该值正确显示在 接收端。但是从 I2C 缓冲区复制到 SPI 缓冲区是 只是一个数组到另一个数组的通用副本,以及所有其他测量 数据在接收端正确显示。两个缓冲区应该 相同。我的代码中没有任何东西可以修改 SPI 缓冲区从 I2C 缓冲区复制到 当 SPI 缓冲区被写入收发器时。

代码的精简副本包含在下面。我一直在寻找 在这几天,我只是看不出是什么原因造成的。

volatile uint8_t sw1State;            // current switch 1 state
volatile uint8_t sw1TimeExpired;      // true if switch 1 debounce time expired
uint8_t spiOutBuf[13]; // SPI output buffer
uint8_t spiInBuf[13];  // SPI input buffer
uint8_t i2cBuff[13];   // I2C input/output buffer

void interrupt main_ISR( void ) {
    I2C_slave_handler();
    timer0_handler();
    switch_input_handler();
}

int main( void ) {
    ... device initialization code omitted for brevity

    sw1State = 1;
    sw1TimeExpired = 1;

    for ( ; ; )
    {        
        // Read the ADC values and add to the I2C buffer (for debugging).
        for ( uint8_t chan = 0; chan < 9; chan++ )
        {
            i2cBuff[chan + 1] = ADC_read( chan );
        }

        // Read the switch status and add to the I2C buffer.
        i2cBuff[10] = sw1State;

        // Load the write transmit payload command into the SPI buffer.
        spiOutBuf[0] = CMD_W_TX_PAYLOAD;

        // Copy the payload data from the I2C buffer to the SPI buffer.
        for ( uint8_t i = 1; i < 13; i++ )
        {
            spiOutBuf[i] = i2cBuff[i];
        }

        // Write the SPI buffer to the transceiver module payload register.
        writeReadSPI( 13 );

        // Clear the RB4 interrupt-on-change interrupt flag (IRQ change) and 
        // set CE high to initiate the transmit.  Hold CE high until an ack
        // is received or there is an ack timeout.
        IOCBFbits.IOCBF4 = 0;
        CE = 1;
        __delay_us( 130 );    // ensure minimum state change transition time

        ... code here omitted which checks the transceiver status

        __delay_ms( 80 );
    }
}

void switch_input_handler( void ) {
    // Check for switch 1 trigger.
    if ( IOCCFbits.IOCCF6 == 1 )
    {
        IOCCFbits.IOCCF6 = 0;   // clear IOC interrupt

        // Process switch 1 trigger if debounce time has expired.
        if ( sw1TimeExpired == 1 )
        {
            // Toggle between OFF state and ON state.
            if ( sw1State == 1 )
            {
                sw1State = 2;
            }
            else
            {
                sw1State = 1;
            }
            // Load and restart Timer0 with 2-second counter value.
            T0CON0bits.T0EN = 0;
            TMR0H = 0xc2;
            TMR0L = 0xf7;
            T0CON0bits.T0EN = 1;
            sw1TimeExpired = 0;
        }
    }
}

void timer0_handler( void ) {
    if ( PIR0bits.TMR0IF == 1 )
    {
        PIR0bits.TMR0IF = 0;    // clear the interrupt
        T0CON0bits.T0EN = 0;    // disable Timer0

        if ( sw1TimeExpired == 0 )
        {
            sw1TimeExpired = 1; // Note the switch 1 timeout for switch handler.
                                // No state transition here.  This just enables
                                // the next switch interrupt to change state.
        }
    }
}

void I2C_slave_handler( void ) {
   ... code omitted for brevity
}

【问题讨论】:

  • 你指的是哪个变量,是sw1State
  • 您是否查看了生成的程序集?它可能会给你一些提示。您可以使用 DSO 记录两条 SPI 线路上传输的数据,以查看实际传输的内容。
  • 是的,我的意思是 sw1State。感谢您对查看装配的建议。也许正在进行一些优化来解释这一点。我有一个 Bitscope DSO,所以我也可以检查传输的数据。

标签: c embedded microcontroller pic microchip


【解决方案1】:

从代码 sn-p 看来,SPI 通道作为后台任务传输,而 I2C 通道在前台传输(中断处理程序)。这可能会导致竞态条件并解释您遇到的行为。

建议您尝试为两个通道使用单个传输缓冲区,并设置一个变量来指示两个通道何时成功发送数据。一旦两个通道都指示数据传输,然后为下一个周期生成一个新的数据包/缓冲区。

【讨论】:

  • 树莓派大约每 300 毫秒从 I2C 缓冲区读取数据,从不写入缓冲区。由于 I2C 接口由中断处理程序管理,因此缓冲区可能在读取时仅使用测量值和开关设置进行部分更新。但是由于每个测量和开关设置都是独立于其他的,所以如果我得到部分结果并不重要,并且 sw1State 变量会在主循环的每次通过时复制到 I2C 缓冲区,然后复制到 SPI 缓冲区。
  • SPI接口函数writeReadSPI不是中断驱动的,所以它会阻塞主循环的执行,直到所有的SPI数据都从输出缓冲区发送出去。即使 I2C 中断处理程序在 SPI 输出缓冲区的传输过程中触发,它也不会影响任一缓冲区的内容,因为它只是读取。尽管如此,我不认为我真的需要两个缓冲区,因为正如我指出的那样,我不在乎我是否得到部分结果。所以我会尝试使用单个缓冲区来查看是否有任何改变。
  • 我通过重新设计去抖动和检查开关输入的方式消除了这个问题。我现在没有在输入端触发从高到低转换的 ISR,而是在需要时读取输入并使用计时器执行软件去抖动。这消除了对全局变量报告状态的需要。我想我永远不会真正知道确切的问题是什么,尽管知道原始技术是将来要避免的东西是有用的。
  • 好吧,如果不知道导致问题的原因,避免某些特定技术听起来不是一个好主意。它也可能是一种劣质的编码风格。 (没有冒犯 ;-)
猜你喜欢
  • 2015-01-04
  • 1970-01-01
  • 1970-01-01
  • 2011-12-05
  • 1970-01-01
  • 2015-01-17
  • 2021-10-27
  • 2014-06-05
  • 2017-08-06
相关资源
最近更新 更多