【问题标题】:Working with 32 bit data types and 8 bit data type in ARM在 ARM 中使用 32 位数据类型和 8 位数据类型
【发布时间】:2015-03-28 08:09:57
【问题描述】:

我是 ARM LPC2148 微控制器的新手,也是 StackOverflow 的新手。我刚刚在其中一个评估板上看到了一段代码。我正在粘贴,如下所示。

端口引脚 P0.19 到 P0.22 映射到 LCD 的 D4 到 D7。以下函数用于向 4 位模式下运行的 LCD 发送命令:

void LCD_Command(unsigned int data)     // This function is used to send LCD commands
{
 unsigned int temp=0;
 EN_LOW();                      // Set EN pin of LCD to to Low
 COMMAND_PORT();
 WRITE_DATA();

 temp=data;
 IO0PIN&=0xFF87FFFF;
 IO0PIN|=(temp & 0xF0) << 15;

 EN_HI();   // Give strobe by enabling and disabling En pin of LCD
 EN_LOW();

 temp=data & 0x0F;
 IO0PIN&=0xFF87FFFF;
 IO0PIN|=(temp) << 19;

 EN_HI();
 EN_LOW();
 while(Busy_Wait());
 Delay(10);
} 

我的问题是:

  1. 变量“数据”已经是 32 位宽。以这种方式转移数据是否有效?编码器可能已经传递了 32 位数据,然后被屏蔽 (&)/ORed (|)。还是有其他影响?

  2. 如果我们使用 unsigned char 而不是 unsigned int,我们是否会在 LPC21xx 中保存任何内存?由于寄存器是 32 位宽的,我不确定内部是否进行了任何分段以节省内存。

  3. 有什么方法可以轻松地将 8 位数据映射到 32 位数据的 8 位部分之一?在上面的代码中,移位是通过硬编码完成的(&lt;&lt;15&lt;&lt;19 等)。我们可以避免这种硬编码并使用一些#defines 来映射位吗?

【问题讨论】:

    标签: c arm embedded


    【解决方案1】:
    1. 如果我们使用 unsigned char 而不是 unsigned int,是否会在 LPC21xx 中保存任何内存?

    仅在将它们存储到 RAM 时,一旦优化器打开,这个小功能将不会执行。请注意,使用char 类型可能会引入要生成的附加代码以正确处理溢出。

    1. [...] 我们可以避免这种硬编码并使用一些#defines 来映射位吗?

    简单:

    #define LCD_SHIFT_BITS 19
    
    void LCD_Command(unsigned int data)     // This function is used to send LCD commands
    {
     unsigned int temp=0;
     EN_LOW();                      // Set EN pin of LCD to to Low
     COMMAND_PORT();
     WRITE_DATA();
    
     temp=data;
     IO0CLR = 0x0F << LCD_SHIFT_BITS;
     IO0SET = (temp & 0xF0) << (LCD_SHIFT_BITS - 4);
    
     EN_HI();   // Give strobe by enabling and disabling En pin of LCD
     EN_LOW();
    
     temp=data & 0x0F;
     IO0CLR = 0x0F << LCD_SHIFT_BITS;
     IO0SET = temp << LCD_SHIFT_BITS;
    
     EN_HI();
     EN_LOW();
     while(Busy_Wait());
     Delay(10);
    } 
    

    我还更改了引脚设置并清除为原子。

    【讨论】:

      【解决方案2】:
      1. 变量“data”已经是 32 位宽了。以这种方式转移数据是否有效? Coder 可以传递 32 位数据,然后进行屏蔽 (&)/ORed (|)。还是有其他影响?

      2. 如果我们使用 unsigned char 而不是 unsigned int,我们会在 LPC21xx 中保存任何内存吗?由于寄存器是 32 位宽的,我不确定是否在内部进行了任何分段以节省内存。

      由于您使用的是 32 位 MCU,因此减少变量大小不会使代码更快。它可能会使其变慢,即使您也可以通过这种方式节省几个字节的 RAM。

      但是,这些是您不应该关心的微优化。启用优化并将它们留给编译器。如果你出于某种未知的原因必须对你的代码进行微优化,那么你可以改用uint_fast8_t。它是一种至少 8 位的类型,编译器会选择最快的类型。

      在 32 位 CPU 上尽可能多地使用 32 位整数通常是一个合理的想法,以避免 C 语言中各种复杂的隐式类型提升规则引起的许多细微错误。特别是在嵌入式系统中,整数提升和类型平衡因导致许多微妙的错误而臭名昭著。 (MISRA-C 检查器可以帮助防止这种情况发生。)

      1. 有什么方法可以轻松地将 8 位数据映射到 32 位数据的 8 位部分之一?在上面的代码中,移位是通过硬编码(

      通常你应该避免“幻数”之类的。不是出于性能原因,而是出于可读性。

      最简单的方法是使用处理器的预制寄存器映射,如果你有编译器的话。如果没有,您必须手动#define 注册:

      #define REGISTER (*(volatile uint32_t*)0x12345678)
      #define REGISTER_SOMETHING 0x00FF0000 // some part of the register
      

      然后定义所有可能的值,例如

      #define REGISTER_SOMETHING_X 0x00010000
      #define REGISTER_SOMETHING_Y 0x00020000
      ...
      
      REGISTER = REGISTER_SOMETHING & REGISTER_SOMETHING_X;
      // or just:
      REGISTER |= REGISTER_SOMETHING_X;
      
      REGISTER = REGISTER_SOMETHING_X | REGISTER_SOMETHING_Y;
      // and so on
      

      或者,如果寄存器的一部分是可变的:

      #define REGISTER_SOMETHING_VAL(val) \
        ( REGISTER_SOMETHING & ((uint32_t)val << 16) )
      ...
      REGISTER = REGISTER_SOMETHING_VAL(5);
      

      您可以通过多种方式编写此类宏以及使用它们的代码。专注于使调用代码可读且没有“魔术数字”。对于更复杂的东西,请考虑使用内联函数而不是类似函数的宏。

      同样对于嵌入式系统,考虑是否所有寄存器部分都使用一次访问写入是否有任何区别。在某些情况下,如果不这样做,您可能会遇到严重的错误,具体取决于特定寄存器的性质。清除中断掩码等时需要特别小心。最好总是反汇编此类代码并查看最终得到的机器代码。


      一般建议:

      始终考虑字节序、对齐和可移植性。您可能不会认为您的代码永远不会被移植,但可移植性可能意味着在其他项目中重用您自己的代码。

      如果您将结构/联合用于任何形式的硬件或数据传输协议映射,则必须使用static_assert 以确保没有填充或其他对齐技巧。 Do not use struct bit-fields 在任何情况下!由于多种原因,它们很糟糕,不能在任何形式的程序中可靠地使用,尤其是在嵌入式微控制器应用程序中。

      【讨论】:

        【解决方案3】:

        三个问题,很多很多编程风格。

        此代码绝对是错误代码。没有原子访问...帮自己一个忙,不要将其用作参考。

        1. 变量“数据”已经是 32 位宽。效率高吗...

        没有其他影响。程序员只是在函数内部使用了一个额外的 4 字节局部变量。

        1. 如果我们使用 unsigned char 而不是 unsigned int,是否会在 LPC21xx 中保存任何内存?

        一般来说,您只能将内存保存在 RAM 中。大多数链接脚本以 4 或 8 个字节对齐数据。当然,您可以使用结构来绕过 RAM 和 Flash。前考虑:

        // ...
        struct lala {
           unsigned int  a  :12;
           unsigned int  b  :20;
           long          c;
           unsigned char d;
        };
        const struct lala l1;   // l1 is const so it lives in Flash.
        // Also l1.d is 8byte long ;)
        // ...
        

        最后一个问题将我们带到问题 3。

        1. 有什么方法可以轻松地将 8 位数据映射到 32 位数据的 8 位部分之一? ...

        NXP 的 LPC2000 是一个 little-endian CPU see here for details。这意味着您可以以成员适合您要访问的内存位置的方式创建结构。为此,您必须首先放置低内存地址。例如:

        // file.h
        // ...
        #include <stdint.h>
        typedef volatile union {
           struct {
              uint8_t  p0  :1;
              uint8_t  p1  :1;
              uint8_t  p2  :1;
              uint8_t  p3  :1;
              ...
              uint8_t  p30 :1;
              uint8_t  p31 :1;
           }pin;
           uint32_t port;
        }port_io0clr_t;
        // You have to check it make sure
        
        // Now we can "put" it in memory.
        #define REG_IO0CLR    ((port_io0clr_t *) 0xE002800C)
           //!< This is the memory address of IO0CLR in address space of LPC21xx
        

        现在我们可以使用REG_IO0CLR 指针。例如:

        // file.c
        // ...
        int main (void) {
           // ...
           REG_IO0CLR->port = 0x0080;   // Clear pin P0.7
           // or even better
           REG_IO0CLR->pin.p4 = 1;      // Clear pin p0.4
           // ...
        
        return 0;
        }
        

        【讨论】:

        • 您的位域解决方案 `REG_IO0CLR->pin.p4 = 1;` 将创建一个无用的读取-修改-写入循环,而不是单个写入。
        • 在结构中,我们是否保证特定成员的特定位位置?例如:我希望 LED1(例如:P0.19)仅位于第 19 位。无论我指定什么,编译器都会保证这个顺序,或者编译器有时可能会改变它!有没有什么办法我们也可以在 32 位寄存器中为结构的特定成员指定位数?
        • @TurboJ Yes 在大多数情况下 REG_IO0CLR->pin.lala 会生成一些额外的代码。这只是一个如何简化事情的例子,所以我们不必使用#defines。
        • @ShankariAmma 我们可以。实际上,对于大多数内核,公司使用相同的技术提供头文件,不同之处在于它们不使用位域。例如,下面的结构来自 STM32F10x 库 // stm32f10x.h // ... typedef struct { __IO uint32_t CRL; __IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR; } GPIO_TypeDef; #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
        • 使用位域是一个非常糟糕的想法。这段代码完全是“外面”,依赖于我不知道有多少种形式的未定义/未指定/实现定义的行为。 Read this。例如,使用#define 远优于位域,因为它可以使代码变得可靠、无错误且可移植。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-15
        • 2020-12-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多