从 16 位硬件寄存器读取
在嵌入式系统上,我们有一个设置,允许我们通过命令行界面读取任意数据以用于诊断目的。 对于大多数数据,这工作得很好,我们使用memcpy()在请求的地址复制数据并通过串行连接将其发送回。
然而,对于 16 位硬件寄存器,memcpy()
会导致一些问题。 如果我尝试使用两个 8 位访问来访问 16 位硬件寄存器,则无法正确读取高位字节。
有人遇到过这个问题吗? 我是一个“高级”(C#/Java/Python/Ruby)人员,正在向硬件靠拢,而这是陌生的领域。
处理这个问题的最佳方法是什么? 我看到一些信息,特别是[对我来说]有点令人困惑的帖子这里。 这篇文章的作者与我有着完全相同的问题,但我讨厌在没有完全理解我在做什么的情况下实施解决方案。
非常感谢您能就这个问题提供任何线索。 谢谢!
On an embedded system we have a setup that allows us to read arbitrary data over a command-line interface for diagnostic purposes. For most data, this works fine, we use memcpy()
to copy data at the requested address and send it back across a serial connection.
However, for 16-bit hardware registers, memcpy()
causes some problems. If I try to access a 16-bit hardware register using two 8-bit accesses, the high-order byte doesn't read correctly.
Has anyone encountered this issue? I'm a 'high-level' (C#/Java/Python/Ruby) guy that's moving closer to the hardware and this is alien territory.
What's the best way to deal with this? I see some info, specifically, a somewhat confusing [to me] post here. The author of this post has exactly the same issue I do but I hate to implement a solution without fully understanding what I'm doing.
Any light you can shed on this issue is much appreciated. Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
除了 Eddie 所说的之外,您通常还需要使用用于读取硬件寄存器的易失性指针(假设内存映射寄存器,并非所有系统都是这种情况,但听起来对您的系统来说是这样)。 类似于:
以下是 Dan Saks 撰写的一系列文章,其中应该为您提供了在 C/C++ 中有效使用内存映射寄存器所需的几乎所有信息:
“调整和对齐设备寄存器”
"明智地使用易失性"
In addition to what Eddie said, you typically need to use a volatile pointer to read a hardware register (assuming a memory mapped register, which is not the case for all systems, but it sounds like is true for yours). Something like:
Here's a series of articles by Dan Saks that should give you pretty much everything you need to know to be able to effectively use memory mapped registers in C/C++:
"Sizing and aligning device registers"
"Use volatile judiciously"
该硬件中的每个寄存器都公开为一个两字节数组,第一个元素在两字节边界对齐(其地址为偶数)。 memcpy() 运行一个循环并在每次迭代时复制一个字节,因此它以这种方式从这些寄存器复制(所有循环展开,char 是一个字节):
但是,由于某些硬件特定原因,第二行无法正常工作。 如果您一次复制两个字节而不是一次复制一个字节,则会以这种方式完成(short int 是两个字节):
这里您在一个操作中复制两个字节,并且第一个字节均匀对齐。 由于没有从奇怪对齐的地址进行单独复制,因此它可以工作。
修改后的 memcpy 检查地址是否均匀对齐,如果是,则复制成两个字节块。
Each register in this hardware is exposed as a two-byte array, the first element is aligned at a two-byte boundary (its address is even). memcpy() runs a cycle and copies one byte at each iteration, so it copies from these registers this way (all loops unrolled, char is one byte):
However the second line works incorrectly for some hardware specific reasons. If you copy two bytes at a time instead of one at a time, it is instead done this way (short int is two bytes):
Here you copy two bytes in one operation and the first byte is evenly aligned. Since there's no separate copying from an oddly aligned address, it works.
The modified memcpy checks whether the addresses are venely aligned and copies in tow bytes chunks if they are.
如果您需要访问特定大小的硬件寄存器,那么您有两种选择:
当然,读取硬件寄存器可能会产生副作用,具体取决于寄存器及其功能,因此以适当大小的访问权限访问硬件寄存器非常重要,这样您就可以一次性读取整个寄存器。
If you require access to hardware registers of a specific size, then you have two choices:
Reading hardware registers can have side affects, depending on the register and its function, of course, so it's important to access hardware registers with the proper sized access so you can read the entire register in one go.
通常使用与寄存器大小相同的整数类型就足够了。 在大多数编译器上,short 是 16 位。
Usually it's sufficient to use an integer type that is the same size as your register. On most compilers, a short is 16 bits.
我认为所有细节都包含在您发布的线程中,因此我将尝试对其进行一些分解;
具体来说;
因此,这里的问题是,如果硬件寄存器的值是在单个 16 位读取中读取的,则硬件寄存器仅报告正确的值。 这相当于做;
这使用单个 16 字节读取从地址读取到值寄存器。 另一方面,memcpy 一次复制一个字节的数据。 就像是;
因此,这会导致寄存器一次读取 8 位(1 个字节),从而导致值无效。
该线程中发布的解决方案是使用 memcpy 的一个版本,该版本将使用 16 位读取来复制数据,只要源和目标是 6 位对齐的。
I think all the detail is contained in that thread you posted so I'll try and break it down a little;
Specifically;
So the problem here is that the hardware registers only report the correct value if their values are read in a single 16-bit read. This would be equivalent to doing;
This reads from the address into the value register using a single 16-byte read. On the other hand you have memcpy which is copying data a single-byte at a time. Something like;
So this causes the registers to be read 8-bits (1 byte) at a time, resulting in the values being invalid.
The solution posted in that thread is to use a version of memcpy that will copy the data using 16-bit reads whereever the source and destination are a6-bit aligned.
你需要了解什么? 您已经找到了一篇单独的文章来解释它。 显然,CPU 文档要求通过 16 位读取和写入来访问 16 位硬件寄存器,但您的 memcpy 实现使用 8 位读取/写入。 所以他们不一起工作。
解决方案就是不使用 memcpy 访问该寄存器。
相反,编写您自己的例程来复制 16 位值。
What do you need to know? You've already found a separate post explaining it. Apparently the CPU documentation requires that 16-bit hardware registers are accessed with 16-bit reads and writes, but your implementation of memcpy uses 8-bit reads/writes. So they don't work together.
The solution is simply not to use memcpy to access this register.
Instead, write your own routine which copies 16-bit values.
不确定问题到底是什么 - 我认为该帖子有正确的解决方案。
正如您所说,问题在于标准 memcpy() 例程一次读取一个字节,这对于内存映射硬件寄存器无法正常工作。 这是处理器的限制 - 根本没有办法一次读取一个字节来获得有效值。
建议的解决方案是编写自己的 memcpy() ,它仅适用于字对齐地址,并且一次读取 16 位字。 这相当简单 - 该链接提供了 ac 和汇编版本。 唯一的问题是确保始终从有效对齐的地址进行 16 位副本。 您可以通过两种方式做到这一点:使用链接器命令或编译指示来确保内容对齐,或者为未对齐缓冲区前面的额外字节添加特殊情况。
Not sure exactly what the question is - I think that post has the right solution.
As you stated, the issue is that the standard memcpy() routine reads a byte at a time, which does not work correctly for memory mapped hardware registers. That is a limitation of the processor - there's simply no way to get a valid value reading a byte at at time.
The suggested solution is to write your own memcpy() which only works on word-aligned addresses, and reads 16-bit words at a time. This is fairly straightforward - the link gives both a c and an assembly version. The only gotcha is to make sure you always do the 16 bit copies from validly aligned address. You can do that in 2 ways: either use linker commands or pragmas to make sure things are aligned, or add a special case for the extra byte at the front of an unaligned buffer.