移位与数组索引,更适合 32 位 MCU 上的 uart 接口
Posted
技术标签:
【中文标题】移位与数组索引,更适合 32 位 MCU 上的 uart 接口【英文标题】:Bitshifting vs array indexing, which is more appropriate for usart interfaces on 32bit MCUs 【发布时间】:2019-07-13 15:00:00 【问题描述】:我有一个带有 USART HAL 的嵌入式项目。此 USART 一次只能发送或接收 8 或 16 位(取决于我选择的 usart 寄存器,即单/双输入/输出)。由于它是一个 32 位 MCU,我想我不妨传递 32 位字段,因为(据我所知)这是 MPU 更有效地使用位。这同样适用于 64 位 MPU,即传递大约 64 位整数。也许那是误导性的建议,或者断章取义的建议。
考虑到这一点,我通过位移将 8 位打包成一个 32 位字段。我对 usart 上的 tx 和 rx 都这样做。
仅8位寄存器的代码如下(16位寄存器的移位轮数只有一半):
int zg_usartTxdataWrite(USART_data* MPI_buffer,
USART_frameconf* MPI_config,
USART_error* MPI_error)
MPI_error = NULL;
if(MPI_config != NULL)
zg_usartFrameConfWrite(MPI_config);
HPI_usart_data.txdata = MPI_buffer->txdata;
for (int i = 0; i < USART_TXDATA_LOOP; i++)
if((USART_STATUS_TXC & usart->STATUS) > 0)
usart->TXDATAX = (i == 0 ? (HPI_usart_data.txdata & USART_TXDATA_DATABITS) : (HPI_usart_data.txdata >> SINGLE_BYTE_SHIFT) & USART_TXDATA_DATABITS);
usart->IFC |= USART_STATUS_TXC;
return 0;
编辑:重新输入上述代码的逻辑,并添加定义以明确评论部分中讨论的三元运算符隐式提升问题
(HPI_usart 和 USART_data 结构相同,只是层次不同,我已经删除了 HPI_usart 层,但为了这个示例,我将保留它)
#define USART_TXDATA_LOOP 4
#define SINGLE_BYTE_SHIFT 8
typedef struct HPI_USART_DATA
...
uint32_t txdata;
...
HPI_usart
HPI_usart HPI_usart_data = '\0';
const uint8_t USART_TXDATA_DATABITS = 0xFF;
int zg_usartTxdataWrite(USART_data* MPI_buffer,
USART_frameconf* MPI_config,
USART_error* MPI_error)
MPI_error = NULL;
if(MPI_config != NULL)
zg_usartFrameConfWrite(MPI_config);
HPI_usart_data.txdata = MPI_buffer->txdata;
for (int i = 0; i < USART_TXDATA_LOOP; i++)
if((USART_STATUS_TXC & usart->STATUS) > 0)
usart->TXDATAX = (i == 0 ? (HPI_usart_data.txdata & USART_TXDATA_DATABITS) : (HPI_usart_data.txdata >> SINGLE_BYTE_SHIFT) & USART_TXDATA_DATABITS);
usart->IFC |= USART_STATUS_TXC;
return 0;
但是,我现在意识到,这可能会导致比它解决的问题更多的问题,因为我本质上是在内部对这些位进行编码,然后当它们传入/传出不同的数据层时,几乎必须立即对其进行解码。我觉得这是一个聪明而性感的解决方案,但我现在正试图解决一个我一开始就不应该创造的问题。就像在存在偏移时如何提取可变位字段一样,即在 gps nmea 句子中,前 8 位可能是一个相关字段,然后其余是 32 位字段。所以它最终是这样的:
32位数组成员0:
bits 24-31 bits 15-23 bits 8-15 bits 0-7
| 8 位值 | 32 位值 A,位 24-31 | 32 位值 A,位 16-23 | 32 位值 A,位 8-15 |
32 位数组成员 1:
bits 24-31 bits 15-23 bits 8-15 bits 0-7
| 32 位值 A,位 0-7 | 32 位值 B,位 24-31 | 32 位值 B,位 16-23 | 32 位值 B,位 8-15 |
32 位数组成员 2:
bits 24-31 15-23 8-15 ...
| 32 位值 B,位 0-7 |等等... | .... | .... |
上面的例子需要手动解码,我猜这很好,但是对于每个 nmea 句子来说都是不同的,感觉比编程更手动。
我的问题是:位移与数组索引,哪个更合适?
我是否应该将每个传入/传出值分配给一个 32 位数组成员,然后以这种方式进行索引?我觉得这就是解决方案,因为它不仅可以更轻松地遍历其他层上的数据,而且我可以消除所有这些位移逻辑,然后 rx 或 tx 函数之间的唯一区别就是数据的走向。
这确实意味着对接口和生成的 gps 模块层进行小幅重写,但这感觉像是在我的项目早期的工作量更少,而且也是一个廉价的教训。
此外,对此的任何想法和一般经验都会很棒。
【问题讨论】:
tl;博士。如果有一个数组并且您只想访问 8 位边界上的值,索引一个char
-array(或您想要访问的 char*
别名)总是比位移更合适且更容易阅读。
太棒了。感谢您的健全性检查。我想我对这个......坏脑......坏......去坐在角落里,别想太多了!
不要写usart->TXDATAX = (i == 0 ? (HPI_usart_data.txdata & USART_TXDATA_DATABITS) : (HPI_usart_data.txdata >> SINGLE_BYTE_SHIFT) & USART_TXDATA_DATABITS);
之类的代码。这是危险的,不可读的,需要分成几个表达式。
@Swordfish char
数组完全不适合用于字符串以外的任何内容。它永远不应该用于反序列化更大的数据块。如果尝试,当将位运算符与可能有符号的char
混合时,程序员将最终陷入隐式类型提升地狱。
@Medicineman25 我的评论主要是关于“一条线上的大多数运营商都赢得了价格”。当你写出像那行一样的混乱时,你就会发生多个隐含的促销活动。其中大约 8 个。你写那行的时候把所有这些都考虑进去了吗?不?坦率地说,你编写的代码你不知道它做了什么——错误和危险。是的, ?: 运算符增加了一点,通过平衡第二个和第三个操作数,无论哪个被评估。
【参考方案1】:
因为它是一个 32 位的 MCU,我想我不妨绕过 32 位字段
这并不是程序员真正要做的。将 8 位或 16 位变量放入结构中。如果需要,让编译器添加填充。或者,您可以使用uint_fast8_t
和uint_fast16_t
。
我的问题是:位移与数组索引,哪个更合适?
数组索引用于访问数组。如果您有一个数组,请使用它。如果没有,那就不要。
虽然可以逐字节咀嚼更大的数据块,但必须更加仔细地编写此类代码,以防止遇到各种微妙的类型转换和指针别名错误。
通常,在访问 CPU 字长(在本例中为 32 位)的数据时,首选移位。它既快速又便携,因此您不必考虑字节序。它是整数序列化/反序列化的首选方法。
【讨论】:
同意。这是针对代码非常模块化的用例,并且我使用的 gps 层将是开源的,因此我需要考虑到并非每个人都希望将每个 8/16 位数据编码为一个 32 位的字段。无论哪种方式,像这样编码然后立即解码都是浪费功率。以上是关于移位与数组索引,更适合 32 位 MCU 上的 uart 接口的主要内容,如果未能解决你的问题,请参考以下文章