【问题标题】:Convert address of pointer to an int将指针的地址转换为 int
【发布时间】:2014-02-27 20:05:02
【问题描述】:

刚接触 ARM(以及为此进行编程)并发现 I/O 端口的位寻址令人困惑。您可以在特定端口引脚上定义一个常数,但仍必须写入其位值来设置它。例如:

#define MyOutput    (*((volatile unsigned long *)0x40025040)) //PF4
// But to set this bit you must write
MyOutput = 0x10;

这让我感觉很奇怪。如果我对某个引脚进行寻址,我应该能够将其设置为 1。因此,为了避免我忘记必须写入它的位值,我想创建一个函数来为我执行此操作。我想出了以下内容,但我认为指针语法或指针转换为 int 时遇到问题。

int SetOutput(volatile unsigned long* PIN), int ONOFF){ //ON defined as 1, OFF defined as 0
    volatile unsigned long PortBit = (PIN & 0xFF);
    if (ONOFF){
        return ((PortBit & 0xFF)>>2);
    } else {
        return 0;
    }
}

//Called by
MyOutput = SetOutput(&MyOutput, ON);

有人有什么想法或建议吗?谢谢!

【问题讨论】:

  • 你能告诉我们更多关于PF4或地址0x40025040的信息吗?这是用于什么 SOC/CPU 的?并非所有 GPIO 内存控制器都是相同的。
  • 这是一个 ARM Cortex M4。地址 0x40025040 是端口 F Pin4 的基址。它允许单独的位寻址,但您仍然必须使用其位值设置它,例如 MyOutput=0x10 而不是 PF4 的 1。我不想为每个 IO 的 SETvalue 创建大量常量,而是创建一个函数,该函数根据引脚的地址创建引脚位掩码。传入您想要的地址和值,并返回该引脚的正确位掩码。我认为逻辑是正确的,但似乎我无法将地址转换为 int。获取二进制表达式的无效操作数('long *'and'int')。
  • 您可以将指针转换为 int 并将其屏蔽。例如,bit = ((uint32)PIN & 0x1f0UL) >> 4;,然后使用mask = 1 << bit;。我猜端口是 0x40025000, 0x40025010, 0x40025020, 0x40025030, 0x40025040, ... 所以选择第二低的半字节并将其用作位移。通常引脚之间的间距是固定的,我猜是 16 个字节。

标签: c arm keil


【解决方案1】:

克里斯, 我不是 Cortex-M 专家,所以你的 MMV。但是根据您上面的描述,您正在尝试使用一种非常复杂的方式来修改外围寄存器中的单个位。通常有两种处理方式:

  1. 使用一组宏(我最熟悉的)。这将减少您在每次读/写引脚时调用函数的空间和 CPU 时间开销,因为宏在编译期间直接转换为所需的确切值/操作。

  2. 改用 PF4 寄存器的位带地址(从未使用过 Cortex-M)。这似乎是您的架构的首选方式。

根据 ARM Cortex-M4 技术参考手册第 3.4 节,您可以使用位带别名地址来修改 PF4 位。有关其工作原理的详细信息,请参阅 TRM 的第 3.7 节。

根据您上面的代码,PF4 是地址 0x40025040 中的第 4 位,位带公式给出(取自合理使用下的 TRM):

bit_band_base 是别名区域的起始地址。 (0x42000000) • byte_offset 是位带区域中包含目标位的字节数。 (0x00025040) • bit_number 是目标位的位位置,0 到 7。 (0x4)

bit_word_offset = (byte_offset x 32) + (bit_number × 4)

bwo = 0x25040* 0x20 + 0x4 * 0x4 = 0x004A0810

bit_word_addr = bit_band_base + bit_word_offset

bwa = 0x42000000 + 0x4A0810 = 0x424A0810

bit_word_offset 是目标位在位带内存区域中的位置。 • bit_word_addr 是别名内存区域中的字的地址,它映射到 目标位。

所以

*(volatile unsigned long *)0x424A0810 = 0x1;

等同于写作

*MyOutput |= 0x10;

如果你真的想走使用函数直接写的路线,试试这个(仅限PF31,如果PF需要高于31,实现留给读者);此代码包含基于 PC 的测试 #define,因此您可以在命令行上使用 gcc 对其进行编译。

#include <inttypes.h>
#include <stdio.h>

#define PC_TESTING 1

#if PC_TESTING
    unsigned long FAKE_PFBASE;
    #define PFBASE &FAKE_PFBASE
#else
    #define PFBASE (volatile unsigned long *) 0x40025040
#endif

#define SUCCESS 0
#define ERROR_INVALID_PIN -1
#define ERROR_INVALID_STATE -2

typedef enum {OFF = 0, ON} ONOFF_t;

typedef enum { PF0 = 0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PF8, PF9, PF10, PF11, PF12, PF13, PF14, PF15, PF16, PF17, PF18, PF19, PF20, PF21, PF22, PF23, PF24, PF25, PF26, PF27, PF28, PF29, PF30, PF31 } PIN_t;

int SetOutput( PIN_t PIN, ONOFF_t ONOFF)
{
    uint32_t mask, value;

    // Implementing more than 32 pins is exercise for the reader
    if (PIN > 31)
        return ERROR_INVALID_PIN;

    // In case someone did something wrong
    if (ONOFF != OFF && ONOFF != ON)
        return ERROR_INVALID_STATE;

    //Broken into separate steps for ease of reading
    //Select the bit of interest
    mask = 1 << PIN;

    //Clear the bit of interest
    value = *PFBASE & ~mask;  //~ is a bit-wise invert.  0x0000 0010 becomes 0xFFFF FFEF

    //Set the bit of interest if that is requested
    if( ON == ONOFF)
        value |= mask;

    *PFBASE = value;

    return SUCCESS;
}

int main()
{
int success = 0;

FAKE_PFBASE = 0l;

success = SetOutput( PF4, ON);
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );

success = SetOutput(PF0, ON );
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );

success = SetOutput(PF0, OFF );
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );

//Error handling left to reader
success = SetOutput(33, OFF );
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );

success = SetOutput(PF2, 2 );
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );

return 0;
}

示例输出:

$ ./a.out
 Success = 0, *PFBASE = 0x00000010
 Success = 0, *PFBASE = 0x00000011
 Success = 0, *PFBASE = 0x00000010
 Success = -1, *PFBASE = 0x00000010
 Success = -2, *PFBASE = 0x00000010

【讨论】:

    【解决方案2】:

    您无法处理单个位; C 中(通常在硬件中)的最小可寻址单元是一个char,即通常是一个 8 位字节。

    典型的方法是编写包装宏或函数。至于你的SetOutput,它似乎很糟糕,例如,它试图从void 函数返回一个值,而0xFF 掩码隔离了 8 位,而不是 1(可能是引脚),并且它从不写入输出寄存器。如果0x10 位控制您想要的引脚,典型的方式是:

    MyOutput |= 0x10; // set bit
    MyOutput &= ~0x10; // unset bit
    MyOutput ^= 0x10; // toggle bit
    

    您可以根据需要围绕这些创建宏。要检查输入寄存器中相应位的状态,可以使用:

    if (MyInput & 0x10) {
        // bit is set
    }
    

    【讨论】:

    • 另外,它实际上并没有设置位。
    • 一些ARM CPU,比如Cortex-M有bit banding;在涉及硬件的情况下,正常的假设可能不正确。也就是说,我不知道这个问题什么是正确的。但我认为这个答案可能有一些假设,在大多数情况下可能是正确的。
    • @artlessnoise 假设基本上是标准 C,它将sizeof(char) 定义为1,所以从这个意义上说,在纯 C 中没有办法使用指针算术直接寻址任何更精确的东西.我想位带是一种通过将特定的单词或字节映射到单个位来解决这个问题的方法。
    猜你喜欢
    • 2012-01-24
    • 1970-01-01
    • 2021-10-31
    • 2017-02-12
    • 2012-03-02
    • 2014-05-02
    • 1970-01-01
    • 2013-11-18
    • 2015-03-10
    相关资源
    最近更新 更多