【问题标题】:Finding position of '1's efficiently in an bit array在位数组中有效地查找 '1' 的位置
【发布时间】:2012-02-15 15:14:28
【问题描述】:

我正在编写一个程序来测试一组电线的开路或短路情况。该程序在 AVR 上运行,将测试向量(行走的“1”)驱动到电线上并接收返回的结果。它将这个结果向量与已经存储在 SD 卡或外部 EEPROM 上的预期数据进行比较。

这是一个示例,假设我们有一组 8 根电线,所有这些电线都是直通的,即它们没有接头。因此,如果我们驱动 0b00000010,我们应该会收到 0b00000010。

假设我们收到 0b11000010。这意味着电线 7,8 和电线 2 之间存在短路。我可以通过 0b00000010 ^ 0b11000010 = 0b11000000 检测到我感兴趣的位。这清楚地告诉我 7 和 8 线有问题,但是我如何在大型位数组中有效地找到这些 '1' 的位置。使用位掩码只需 8 根线即可轻松完成此操作,但我正在开发的系统必须处理多达 300 根线(位)。在我开始使用如下宏并测试 300*300 位数组中的每个位之前,我想在这里询问是否有更优雅的解决方案。

 #define BITMASK(b) (1 << ((b) % 8))
 #define BITSLOT(b) ((b / 8))
 #define BITSET(a, b) ((a)[BITSLOT(b)] |= BITMASK(b))
 #define BITCLEAR(a,b) ((a)[BITSLOT(b)] &= ~BITMASK(b))
 #define BITTEST(a,b) ((a)[BITSLOT(b)] & BITMASK(b))
 #define BITNSLOTS(nb) ((nb + 8 - 1) / 8)

只是为了进一步展示如何检测开路。预期数据:0b00000010,接收数据:0b00000000(电线未拉高)。 0b00000010 ^ 0b00000000 = 0b0b00000010 - 电线 2 已打开。

注意:我知道测试 300 根线不是 AVR Mega 1281 内的微型 RAM 可以处理的,这就是为什么我将把它分成几组,即测试 50 根线,比较,显示结果,然后继续前进。

【问题讨论】:

    标签: c embedded bit-manipulation avr


    【解决方案1】:

    许多体系结构都提供了用于定位字中第一个设置位或计算设置位数量的特定指令。编译器通常为这些操作提供内在函数,因此您不必编写内联汇编。例如,GCC 提供了__builtin_ffs__builtin_ctz__builtin_popcount 等,每个都应该映射到目标架构上的适当指令,利用位级并行性。

    如果目标架构不支持这些,编译器会发出一个高效的软件实现。在软件中逐位测试向量的幼稚方法效率不高。

    如果您的编译器没有实现这些,您仍然可以使用 de Bruijn sequence 编写自己的实现代码。

    【讨论】:

    • 我阅读了 de Brujin 链接,它似乎需要连续的 0。不保证故障将是连续的。我的编译器是 AVR-GCC。我要去做一些研究,看看它是否实现了这些。
    【解决方案2】:

    您预计故障的频率如何?如果您不经常期望它们,那么优化“存在故障”的情况似乎毫无意义——唯一真正影响速度的部分是“无故障”情况。

    要优化无故障情况,只需将实际结果与预期结果进行异或运算并进行input ^ expected == 0 测试以查看是否设置了任何位。

    您可以使用类似的策略来优化“少数故障”的情况,如果您进一步期望故障数量在确实存在时通常很小 - 屏蔽 input ^ expected 值以仅获取前 8 位,只是第二个 8 位,依此类推,并将这些结果中的每一个都与零进行比较。然后,您只需要在不等于 0 的位中搜索设置位,这应该会将搜索空间缩小到可以很快完成的范围内。

    【讨论】:

      【解决方案3】:

      您可以使用查找表。例如 255 字节的 log-base-2 查找表可用于查找字节中最重要的 1 位:

      uint8_t bit1 = log2[bit_mask];
      

      其中log2定义如下:

      uint8_t const log2[] = {
         0,               /* not used log2[0] */
         0,               /* log2[0x01] */
         1, 1             /* log2[0x02], log2[0x03] */
         2, 2, 2, 2,      /* log2[0x04],..,log2[0x07] */
         3, 3, 3, 3, 3, 3, 3, 3, /* log2[0x08],..,log2[0x0F */ 
         ... 
      } 
      

      在大多数处理器上,像这样的查找表将转到 ROM。但是 AVR 是一台哈佛机器,将数据放入代码空间 (ROM) 需要特殊的非标准扩展,这取决于编译器。例如,IAR AVR 编译器需要使用扩展关键字 __flash。在 WinAVR (GNU AVR) 中,您需要使用 PROGMEM 属性,但它比这更复杂,因为您还需要使用特殊的宏来读取程序空间。

      【讨论】:

        【解决方案4】:

        我认为只有一种方法可以做到这一点:

        • 从“outdata”中创建一个数组。例如,数组中的每一项都可以对应一个 8 位端口寄存器。
        • 通过网络发送输出数据。
        • 将此数据读回为“indata”。
        • 将 indata 存储在与 outdata 完全映射的数组中。
        • 在循环中,将 outdata 的每个字节与 indata 的每个字节进行异或。

        我强烈推荐内联函数而不是那些宏。

        为什么你的 MCU 不能处理 300 根电线?

        300/8 = 37.5 字节。四舍五入为38。需要存储两次,outdata和indata,38*2 = 76字节。

        你不能腾出 76 字节的 RAM 吗?

        【讨论】:

          【解决方案5】:

          我认为你错过了树木之间的森林。似乎是指甲床测试。首先测试一些假设: 1)您知道每个测试/通电的引脚应该带电。 2)您已将第 1 步的网表翻译成 sd 上的文件

          如果您在字节级别和位上操作,它会简化问题。如果您为引脚通电,则会在您的文件中存储一个预期的模式。首先找到不匹配的字节;识别字节中不匹配的引脚;最后存储带有错误引脚号的通电引脚。

          您不需要用于搜索或结果的数组。总体思路:

          numwires=300;
          
          numbytes=numwires/8 + (numwires%8)?1:0;
          
          for(unsigned char currbyte=0; currbyte<numbytes; currbyte++)
          {
             unsigned char testbyte=inchar(baseaddr+currbyte)
            unsigned char goodbyte=getgoodbyte(testpin,currbyte/*byte offset*/);
            if( testbyte ^ goodbyte){
            // have a mismatch report the pins
              for(j=0, mask=0x01; mask<0x80;mask<<=1, j++){
                 if( (mask & testbyte) != (mask & goodbyte)) // for clarity
                    logbadpin(testpin, currbyte*8+j/*pin/wirevalue*/, mask & testbyte /*bad value*/);
          
               }
          
          }
          

          【讨论】:

            猜你喜欢
            • 2013-06-06
            • 1970-01-01
            • 1970-01-01
            • 2021-08-08
            • 2014-04-13
            • 1970-01-01
            • 2021-12-24
            • 1970-01-01
            • 2018-10-23
            相关资源
            最近更新 更多