在 ARM 中使用 32 位数据类型和 8 位数据类型

Working with 32 bit data types and 8 bit data type in ARM

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

端口引脚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. 变量"data"已经是32位宽了。以这种方式转移数据是否有效?编码器可以传递 32 位数据,然后屏蔽 (&)/ORed (|)。或者还有其他影响吗?

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

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

  1. Do we save any memory in LPC21xx if we use unsigned char instead of unsigned int?

仅当将它们存储到 RAM 中时,一旦优化器打开,这个小函数将不会执行此操作。请注意,使用 char 类型可能会引入 额外的 代码以正确处理溢出。

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

简单:

#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);
} 

我还更改了 pin set 和 clear 为 atomic。

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

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

  1. The variable "data" is already 32 bit wide. Is it efficient ...

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

  1. Do we save any memory in LPC21xx if we use unsigned char instead of unsigned int?

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

// ...
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. Is there any way we can easily map 8 bit data to one of the 8 bit portions of 32 bit data? ...

NXP 的 LPC2000 是小端 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;
}
  1. The variable "data" is already 32 bit wide. Is it efficient to shift the data in this way? Coder could have passed 32 bit data and then masked (&)/ORed (|). Or are there any other impacts?

  2. Do we save any memory in LPC21xx if we use unsigned char instead of unsigned int? Since registers are 32 bit wide, I am not sure whether internally any segmentation is done to save memory.

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

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

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

  1. Is there any way we can easily map 8 bit data to one of the 8 bit portions of 32 bit data? In the above code, shifting is done by hard coding (<<15 or <<19 etc). Can we avoid this hard coding and use some #defines to map the bits?

通常您应该避免使用“幻数”等。不是出于性能原因,而是为了可读性。

最简单的方法是使用处理器的预制寄存器映射,如果您有编译器的话。如果没有,您将不得不 #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);

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

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


一般建议:

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

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