armfly 发表于 2022-3-25 00:59:05

开启pio 50M 后台DMA采集程序后,触发GPIO中断时引起异常

发现新问题, 一晚上没解决。
开启pio 50M 后台DMA采集程序后,触发中断后引起异常。中断程序也没执行。
不写 bsp_StartDso(DSO_MODE_CH1CH2, DSO_FREQ_50M, 8 * 1024); 这句话,中断正常响应(仅仅熄灭LED), 写了就触发异常。



pio程序启用不开dma是正常可执行GPIO中断。
只要开启DMA,当GPIO触发中断时,CPU异常停机。(已排除超频问题)

开启DMA的代码如下:双DMA循环触发

/*
*********************************************************************************************************
*    函 数 名: bsp_StartDso
*    功能说明: 启动示波器程序
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_StartDso(DSO_MODE_E _mode, DSO_FREQ_E _FreqId, uint32_t _SampleSize)
{
    dma_channel_config c;
    uint offset;
    int ctrl_chan;
    int data_chan;      
   
    static uint32_t s_transfer_count = 0;   
    PIO pio = pio0;
    uint8_t sm = 3;   
   
    /* 设置DMA优先级为高 */
    bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS;

    if (_mode == DSO_MODE_CH1CH2)       /* 双通道 */
    {
      offset = pio_add_program(pio0, &dso_ch1ch2_program);
      pio_dso_ch1ch2_init(pio, sm, offset, g_PioClkDiv);
    }
    else if (_mode == DSO_MODE_CH1)   /* 单通道交错 */
    {
      offset = pio_add_program(pio0, &dso_ch1_program);
      pio_dso_ch1_init(pio, sm, offset, g_PioClkDiv);         
    }
    else
    {
      return;
    }
   
    if (_SampleSize > DSO_SAMPLE_SIZE_MAX)
    {
      return;
    }
   
    s_transfer_count = _SampleSize;
   
    /* 启用2个DMA通道,实现不间断DMA传输 */
    ctrl_chan = dma_claim_unused_channel(true);   
    data_chan = dma_claim_unused_channel(true);
    g_tDSO.ctrl_chan = ctrl_chan;
    g_tDSO.data_chan = data_chan;
   
    /* 控制通道 */
    c = dma_channel_get_default_config(ctrl_chan);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_32);    /* DMA控制寄存器bit */
    channel_config_set_read_increment(&c, false);
    channel_config_set_write_increment(&c, false);

    dma_channel_configure(
      ctrl_chan,
      &c,
      &dma_hw->ch.al1_transfer_count_trig,   /* 目标地址: 数据通道的传输个数 */
      &s_transfer_count,                                  /* 源地址: 内存变量 */
      1,                                                /* 传送1次 */
      false                                             /* 暂时不启动DMA传输 */
    );

    /* 数据通道 */
    c = dma_channel_get_default_config(data_chan);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_16);         /* 双通道8bit,合计16bit */
    channel_config_set_dreq(&c, pio_get_dreq(pio, sm, false));      /* pio状态机的fifo数据请求 */
    channel_config_set_read_increment(&c, false);                   /* 源地址不地址 FIFO */
    channel_config_set_write_increment(&c, true);                   /* 目标地址递增,内存 */
    channel_config_set_chain_to(&c, ctrl_chan);                     /* 数据通道传输完毕后,自动启动控制通道 */
    channel_config_set_ring(&c, true, 15);                        /* 1 << 15 (32K) */
   
    dma_channel_configure(
      data_chan,
      &c,
      g_dso_rxbuf,      /* 目标地址: 内存 */
      &pio->rxf,      /* 源地址: pio fifo 读 */
      0,                  /* DMA控制通道将填充这个域 */
      false               /* 暂时不启动DMA传输 */
    );
   
    dma_channel_start(ctrl_chan);/* 启动DMA传输 */
}


armfly 发表于 2022-3-26 00:42:44


因为GPIO中断会引起异常,暂时用软件方式判断先跳过去。结果定时器中断也异常,一步一步地填坑。
我的代码:
/*
*********************************************************************************************************
*    函 数 名: TrigCallback
*    功能说明: 启动硬件触发,并设置触发电平
*    形    参: _TrigVolt 触发电压,单位V
*    返 回 值: 无
*********************************************************************************************************
*/
void TrigCallback(uint gpio, uint32_t events)
{
    if (gpio_get(25) == 0)
    {
      gpio_put(25, 1); /* 点亮LED */
    }
    else
    {
      gpio_put(25, 0); /* 熄灭LED */      
    }


    /* 停止GPIO中断 */
//    gpio_set_irq_enabled(PIN_TRIG, GPIO_IRQ_EDGE_RISE, false);
   
    // 立即读取触发位置
    dma_channel_hw_t *hw = dma_channel_hw_addr(g_tDSO.data_chan);
    g_tDSO.TrigPosDma = hw->write_addr;
    g_tDSO.Trigged = 2;
//   
    /* 计算触发结束时刻 */
    int64_t t0;
   
    t0 = 50000;   //


    add_alarm_in_ms(t0, TrigAlarmCallbak, 0, true);
}


alarm_callback_t TrigAlarmCallbak(alarm_id_t id, void *user_data)
{
    if (gpio_get(25) == 0)
    {
      gpio_put(25, 1); /* 点亮LED */
    }
    else
    {
      gpio_put(25, 0); /* 熄灭LED */      
    }
   
    //dma_channel_abort(g_tDSO.ctrl_chan);
    //dma_channel_abort(g_tDSO.data_chan);
   
    dma_hw->abort = (1u << g_tDSO.ctrl_chan) | (1u << g_tDSO.data_chan);
    while (dma_hw->abort & ((1ul << g_tDSO.ctrl_chan) | (1u << g_tDSO.data_chan))) ;
   
    bsp_StopDso();
}



开启定时器时产生异常,异常位置:

/*! \brief Acquire a spin lock without disabling interrupts (hence unsafe)
*\ingroup hardware_sync
*
* \param lock Spinlock instance
*/
__force_inline static void spin_lock_unsafe_blocking(spin_lock_t *lock) {
    // Note we don't do a wfe or anything, because by convention these spin_locks are VERY SHORT LIVED and NEVER BLOCK and run
    // with INTERRUPTS disabled (to ensure that)... therefore nothing on our core could be blocking us, so we just need to wait on another core
    // anyway which should be finished soon
    while (__builtin_expect(!*lock, 0));
    __mem_fence_acquire();
}

解释

1.引言在很多源码如Linux内核、Glib等,我们都能看到likely()和unlikely()这两个宏,通常这两个宏定义是下面这样的形式。#define likely(x)      __builtin_expect(!!(x), 1)#define unlikely(x)    __builtin_expect(!!(x), 0)
可以看出这2个宏都是使用函数 __builtin_expect()实现的, __builtin_expect()函数是GCC的一个内建函数(build-in function).
2. 函数声明函数__builtin_expect()是GCC v2.96版本引入的, 其声明如下:long __builtin_expect(long exp, long c);
2.1. 功能描述由于大部分程序员在分支预测方面做得很糟糕,所以GCC 提供了这个内建函数来帮助程序员处理分支预测.你期望 exp 表达式的值等于常量 c, 看 c 的值, 如果 c 的值为0(即期望的函数返回值), 那么 执行 if 分支的的可能性小, 否则执行 else 分支的可能性小(函数的返回值等于第一个参数 exp).GCC在编译过程中,会将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降, 达到优化程序的目的.通常,你也许会更喜欢使用 gcc 的一个参数 '-fprofile-arcs' 来收集程序运行的关于执行流程和分支走向的实际反馈信息,但是对于很多程序来说,数据是很难收集的。2.2. 参数详解  ① exp   exp 为一个整型表达式, 例如: (ptr != NULL)   ② c     c 必须是一个编译期常量, 不能使用变量2.3. 返回值  返回值等于 第一个参数 exp





armfly 发表于 2022-3-26 01:34:08

硬汉1:08:21
如果锁定了,树莓派论坛网友遇到了同样问题。与我们不同的是,他用的定时器中断,只要调试状态运行必定卡顿在lock函数里面,而将下载器拔掉,板子正常上电运行就可以,这个您有测试过没。
另外那个while大循环里面最好简单执行点东西,不让CPU闲置下来,还有就是优化等级,当前应该一直用的最高等级优化。

他还测试了官方的例子hello_timer就存在这种问题。
https://forums.raspberrypi.com/viewtopic.php?p=1984685
这个帖子是这个月发布的。

页: [1]
查看完整版本: 开启pio 50M 后台DMA采集程序后,触发GPIO中断时引起异常