2

在 PHP 中,我有类似的东西

function doStuff($in, $value)  
{  
   $var = "V_" . $in;  
   $$var = $value;
}

有没有办法在 C 中做类似的事情?

基本上,我试图弄清楚如何制作一种库,以便更轻松地使用 AVR 上的 IO 引脚。因此,例如,将有一个功能可以将特定引脚设置为 OUTPUT。AVR 中的那个引脚是 PORTB 的一部分。将它设置为输出并给它一个值需要我引用DDRBPORTB常量并设置它们的值。而不是经历所有这些,我希望能够调用一个函数,例如SetMode(Pin #, Mode);. 我只是不知道该怎么做。

4

8 回答 8

4

您的问题仍然有点不清楚(如答案中的各种解释所示)。我假设您想通过物理引脚号来引用引脚。如果这不正确,请澄清您的问题,以便我们提供更好的答案。

如果有人拿着枪指着我的头,我大概会这样做:

免责声明:我没有对此进行测试,也没有特别注意检查文档。该代码是为 Linux 上的 avr-gcc/avr-libc 编写的,尽管它可能在其他地方工作。

// Map from physical pin number to associated direction register.
volatile uint8_t *ddr_map[] = {
    NULL, // Vcc, GND, or some other non-IO pin.
    &DDRB,
    &DDRB,
    &DDRC,
    // etc...  Values will vary for different target chips.
};

// Map from physical pin number to port mask.
uint8_t mask_map[] = {
    0x00,
    _BV(0),
    _BV(1),
    _BV(0),
    // etc...  Values will vary for different target chips.
}

typedef enum {
    IN,
    OUT
} PinDir;

void setMode(int pin, PinDir dir) {
    if(dir == OUT) {
        *ddr_map[pin] |= mask_map[pin];
    } else {
        *ddr_map[pin] &= ~mask_map[pin];
    }
}

http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_port_pass

这就是为什么这不是一个好主意:

  1. 它没有抽象出任何有意义的行为(它实际上消除了抽象——物理引脚号低于逻辑端口/引脚的级别)。此外,不同封装格式的物理管脚编号不一定相同。PORTB 的管脚在 QFP 封装上的物理管脚编号可能与 PDIP 封装不同。所以这段代码实际上令人困惑。

  2. 它增加了开销。您有一个额外的函数调用(需要花费周期和堆栈)和两个(或更多)用于查找的数组(除非您采取特殊措施,否则它们会花费 AVR 上的闪存和 RAM,在这种情况下它们会花费额外的周期和闪存或 EEPROM)更不用说所有的间接(数组查找,指针解引用)和额外的比较和分支。在桌面和 Web 开发中,你会嘲笑我对如此小的成本的担忧是对的,但在 AVR 上,这种浪费的影响要大得多。(注意:您可能能够说服编译器优化其中的一些内容,但如果您使用-Os它会很困难。现在您担心比以前更低级别的细节......)

  3. 所提供的操纵引脚的方法并不复杂到值得以这种方式隐藏。您应该对在脑海中转换十六进制和二进制感到满意(这并不难)。即使您不想弄乱十六进制,_BV()宏也可以使引脚操作变得非常容易(或者只使用(1 << x)更便携并且会被更多程序员识别的那个)。

顺便说一句,PORTB,DDRB等不是常数。它们是与特定地址或寄存器相关的变量。尝试用类似的东西修改常量CONST_THINGY |= 0x03会产生编译器错误。

变量变量

C没有您描述的功能。它是一种低级语言(有时被描述为“高级汇编”),不提供许多花哨的功能(按照今天的标准)。这就是为什么它是 AVR 的首选语言的原因——您希望接近硬件,并且您不希望有很多额外的开销。

C 有的是指针。根据你的问题和评论,我猜你对它们不是很熟悉,所以这里有一个快速的解释:

  • 操作符返回一个指向变量的&指针,用法如下:pointer = &variable;
  • *实际上有几个用途。
    • 第一个是声明一个指针变量(即一个保存指针而不是int、char 或float 的变量):int *pointer; 注意您必须指定它将指向什么类型的变量。
    • 第二种用途是所谓的取消引用指针。基本上,这意味着通过指针访问变量。如果pointer点在variable*pointer = 42;将设置variable为等于 42,other_var = *pointer并将设置other_var为 的值variable
  • 还有指针算术,但这超出了这个答案的范围。

所有这一切的重点是,您可以有效地将变量本身视为值,存储它们并传递它们。除了操纵它们的值之外,你不能以任何有意义的方式真正修改它们,但你也不需要这样做。

于 2010-07-28T04:36:44.057 回答
3

简而言之,不,C 中没有变量变量。您可以做的是制作某种类型的变量哈希图,以名称为键,并使用它。

于 2010-07-23T18:57:16.007 回答
1

预处理器定义或宏是在 C 中实现所需目标的典型方法。

于 2010-07-23T22:07:22.243 回答
1

C有宏特性,可以这样使用

#define oof(a, b) a##b

int x1 = 5;
oof(x, 1) = 10;
printf("%d", x1); //prints 10
int oof(x, 2) = 2;
printf("%d", x2); //printf 2

它可以是一个函数,它可以使用其他函数,它可以调用其他宏等等。这里的'##'是连接它旁边的对象的预处理器操作符。

于 2019-04-16T09:22:42.677 回答
0

所有 AVR 寄存器都有地址。您可以使用这些地址来实现通用功能。

于 2010-07-23T22:03:01.330 回答
0

对于一般情况,指针尽可能接近。C 在运行时不一定有任何名称的概念,尤其是在微控制器上(某些名称通常存在于具有动态链接的操作系统中,但即使在那里也不是必需的)。

对于引脚编号场景,查找表来确定任何给定编号的端口、端口中的位等都可以工作。这是 Arduino 采用的技术,它试图抽象出 AVR 上的 C++ 编程。他们喜欢重命名,例如将 PWM 信号称为“analogWrite”,将 C++ 信号称为“布线”,将程序称为“草图”,并且所有 I/O 引脚都按照它们在开发板上的位置进行编号。不利的一面是,当您在编写除第一块板之外的其他东西时会产生巨大的混乱,并且当您想做一些低级别的事情时,必须弄清楚隐藏在他们的库中的副作用。

于 2010-08-25T16:33:27.317 回答
0

当您说 pin # 时,您指的是物理芯片上的实际 pin 编号,对吗?

如果是。你可以这样做。

1-创建一个映射函数,该函数接受密码并返回相应的端口和密码

前任。

您想访问芯片上的 pin #1

SetMode( int pinNumber, char mode ) {

    typedef struct  {
        int pin;
        int port;
    }pinValues;


    pinValues pinStruct;
    mapPin( &pinStruct, pinNumber );  // this resolves the pin # on the chip to a port            
    // and pin.
    GPIO_init( pinStruct, mode ); // this initializes the pin;
}

mapPin 函数应该非常简单,只需创建一个包含引脚号的数组

前任。

说芯片只有4个引脚

常量字符 GPIO_pin[5] = { 1,2,3,4 };

并为每个引脚对应的端口和引脚创建一个结构#

前任

typedef struct {
  int pin;
  int port;
}pinPort;


pinPort pinPortStruct[5] = { (PORTA,0), (PORTA,1), (PORTB,1), (PORTB,1) };

所以引脚 #1 对应于 PORTA 0

所以你所做的只是搜索 GPIO_pin 然后返回对应于该索引的结构

for( int i = 0;i <4; i++)
{
 if( pin == GPIO_pin[i] )
     return pinPortStruct[i];
} 

我希望这是你需要的。

于 2010-07-23T21:49:20.990 回答
0

根据您正在谈论的引脚/端口的数量,使用 case 语句可能是最简单的:

void SetMode(int pin, int mode) {
    switch (pin) {
        case PIN_A:
            DDRA = mode;
            PORTA = mode;
            break;

        case PIN_B:
            DDRB = mode;
            PORTB = mode;
            break;
        ...
    }
}

常量PIN_A,PIN_B等可以通过#define宏或enum. 这种方法的一个优点是您可以使用类似的符号来引用所有端口/引脚,即使您必须以不同的方式对待其中的一些(每个case都可以不同)。如果您有大量的引脚/端口要处理,那么这可能不是最佳方法。

于 2010-07-23T22:14:05.887 回答