为 PIC24 重新定义寄存器掩码以提高可读性是否是一种不好的做法?

Is it bad practice to redefine register masks for PIC24 in order to improve readability?

我对使用 PIC 芯片还比较陌生,所以这可能是一个新手级别的问题,但我正在尝试编写一个头文件,其中包含所有的 TRIS/ODC/INIT 掩码I/O 个端口。

在这个芯片内置的 PCB 上,任何给定的组件都可能使用来自多个端口的引脚,并且可能有十几个单独的组件值得详细评论。例如,与特定 SPI ADC 模块的接口使用来自端口 A、D 和 F 的引脚。

对我来说,reader 友好的编写方式似乎是按组件组织文件,这样 reader 一目了然就可以分辨出哪些引脚正在使用中,它们是配置为输入还是输出,以及它们是如何初始化的。

例如,仅显示 TRIS 掩码信息,这是我用来演示我在说什么的特定 ADC 模块的代码片段:

#define PORTD_TRIS_MASK 0x00
#define PORTF_TRIS_MASK 0x00

// ...
// lots of hardware configuration stuff goes here
// ...

// ANALOG INPUT - THERMOCOUPLE 1
// Thermocouple ADC chip MAX31856 DNP communicates over SPI
// Accepts any type of thermocouple
// TC1_CS pulled high
// TC1_DRDY pulled high

#define TC1_MOSI    LATAbits.LATA14
#define TC1_MISO    PORTDbits.RD10
#define TC1_SCK     LATDbits.LATD11
#define TC1_CS      LATFbits.LATF6
#define TC1_DRDY    PORTFbits.RF7

#define TC1_MISO_SHIFT  1<<10
#define TC1_DRDY_SHIFT  1<<7

#define PORTD_TRIS_MASK ( PORTD_TRIS_MASK | TC1_MISO_SHIFT )
#define PORTF_TRIS_MASK ( PORTF_TRIS_MASK | TC1_DRDY_SHIFT )

以上代码没有抛出任何错误,但确实抛出警告:

HardwareProfile.h:1173:0: warning: "PORTD_TRIS_MASK" redefined
HardwareProfile.h:1029:0: note: this is the location of the previous definition
HardwareProfile.h:1174:0: warning: "PORTF_TRIS_MASK" redefined
HardwareProfile.h:1095:0: note: this is the location of the previous definition

编译器抱怨它的事实向我表明这可能不是鼓励的行为,但对我来说这似乎没有什么本质上的问题。我是不是遗漏了什么,或者这是一种合理的组织代码的方式,使引脚配置细节保持在它们的定义附近?

或者,是否有更传统的方法来完成我想要完成的工作,同时保持更广泛使用或可接受的可读性?


更新:
可能我的原文post说的不够清楚。它的结构是这样的,因为头文件中有十几个这样的代码块。假设恰好有 13 个这样的代码块,任何特定的掩码最初都将定义为 0x00 并重新定义 13 次,其想法是每次重新定义都会添加与特定块相关的配置信息。


更新:
在回答有关如何使用这些宏的问题时,它们只是用于一次配置端口中的所有引脚。在这个 PIC24 上,每个端口有 16 个引脚,每个引脚都有一个 TRIS(数据方向控制)寄存器、ODC(开漏控制)寄存器和 LAT(锁存器)寄存器,如果配置为输出,则需要一个初始值.通常,不鼓励一次写入一个位 16 次,而是一次写入整个端口。例如,考虑一个简化的情况,其中有四个寄存器而不是十六个。而不是这样写:

// In source file
TRISABITS.TRISA0 = 1;
TRISABITS.TRISA1 = 1;
TRISABITS.TRISA2 = 0;
TRISABITS.TRISA3 = 0;

按照惯例(据我理解)这样写:

// In header file
#define BIT_0_SHIFT ( 1<<0 )
#define BIT_1_SHIFT ( 1<<1 )
#define BIT_2_SHIFT ( 0<<2 )
#define BIT_3_SHIFT ( 0<<3 )
#define TRISA_MASK ( BIT_0_SHIFT | BIT_1_SHIFT | BIT_2_SHIFT | BIT_3_SHIFT )

// In source file
TRISA = TRISA_MASK;

关于另一个关于可读性的问题,我支持这种结构的论点是端口在这个芯片上的组织方式在物理上没有意义。任何特定端口上的引脚不一定彼此靠近或按顺序排列,并且没有单独的设备(例如外部 SPI 模块)仅限于单个端口。按端口组织头文件意味着 reader 需要滚动整个头文件来检查单个设备的配置,而按设备组织文件允许整个设备的定义和配置在屏幕上清晰可见单屏.

预处理器的工作方式与代码的工作方式不同。例如,考虑以下代码:

int main(void)
{
    int A = (B+C);
    int B = (C+2);
    int C =  3;
    int x = A;
    return x;
}

那是行不通的,因为 B 和 C 在声明之前就被使用了。编译器的输出是:

cc -Wall demo.c -o demo
demo.c:3:14: error: use of undeclared identifier 'B'
    int A = (B+C);
             ^
demo.c:3:16: error: use of undeclared identifier 'C'
    int A = (B+C);
               ^
demo.c:4:14: error: use of undeclared identifier 'C'
    int B = (C+2);
             ^

现在对 A、B 和 C 使用 #defines 尝试同样的事情:

#define A (B+C)
#define B (C+2)
#define C  3

int main(void)
{
    int x = A;
    return x;
}

这次没有警告或错误,即使 #define 出现故障。当预处理器看到 #define 时,它只是将一个条目添加到其字典中。所以在阅读了三个 #defines 之后,字典包含

Search   Replacement
 Text       Text
--------------------
   A       (B+C)
   B       (C+2)
   C       3

请注意,预处理器尚未评估替换文本。它只是存储文本。当预处理器在代码中找到搜索词时,它会使用替换文本。所以行

int x = A;

变成

int x = (B+C);

执行替换后,预处理器重新扫描文本以查看是否可以进行更多替换。第二次扫描后,我们有:

int x = ((C+2)+3);

最后的结果是:

int x = ((3 +2)+3);

大多数编译器都可以选择在预处理完成后输出代码。对于 gcc 或 clang,使用 -E 选项查看预处理器输出。


好的,现在我们应该有足够的背景知识来实际解决您的问题。考虑以下定义:

#define PORTD_TRIS_MASK 0x00
#define PORTD_TRIS_MASK ( PORTD_TRIS_MASK | TC1_MISO_SHIFT )
#define PORTD_TRIS_MASK ( PORTD_TRIS_MASK | SB1_DATA_SHIFT )

我们这里有 3 个主要问题:

  1. 替换文本未被评估,因此这些位未被一起或运算。
  2. 预处理器字典中只会保留这些定义中的一个(最后一个)。这就是警告的原因。预处理器告诉您它已经有了该搜索词的条目,并且它将丢弃之前的替换文本。
  3. 当在代码中找到 PORTD_TRIS_MASK 时,预处理器将其替换为 ( PORTD_TRIS_MASK | SB1_DATA_SHIFT )。然后重新扫描,再次找到 PORTD_TRIS_MASK。结果是无限递归。幸运的是,预处理器可以防止这种情况发生,并且会停止。稍后编译器会产生错误。

解决方案是为每个组件创建唯一命名的定义:

#define TRIS_MASK_D1 TC1_MISO_SHIFT
#define TRIS_MASK_F1 TC1_DRDY_SHIFT

#define TRIS_MASK_D2 SB1_DATA_SHIFT
#define TRIS_MASK_F2 0

然后将它们全部进行或运算:

#define PORTD_TRIS_MASK (TRIS_MASK_D1 | TRIS_MASK_D2 | ... | TRIS_MASK_D13)
#define PORTF_TRIS_MASK (TRIS_MASK_F1 | TRIS_MASK_F2 | ... | TRIS_MASK_F13)

PORTD_TRIS_MASK这样的宏是由PIC根据所使用的微控制器定义的。

我不建议您更改或重新定义这些宏。

您可以改为使用自己的宏来实现特定功能。例如

#define TC1_MISO_SHIFT  1<<10
#define TC1_DRDY_SHIFT  1<<7

#define TC1_MISO_MASK ( PORTD_TRIS_MASK | TC1_MISO_SHIFT )
#define TCI_DRDY_MASK ( PORTF_TRIS_MASK | TC1_DRDY_SHIFT )