B.G*_*ill 7 c embedded microcontroller standards
微控制器有什么特殊的C标准吗?
我问,因为到目前为止,当我在Windows操作系统下编程时,我使用的编译器并不重要.如果我有一个C99的编译器,我知道我能用它做什么.
但是最近我开始用C编程用于微控制器,我感到震惊,即使它仍然是C的基础知识,比如循环,变量创建等等,有一些我从未在C中用于台式计算机的语法类型.此外,语法正在从版本变为版本.我使用AVR-GCC编译器,在以前的版本中,您使用了一个端口I/O函数,现在您可以在新版本中处理类似变量的端口.
是什么定义了什么函数以及如何将它们实现到编译器中并且仍将它称为C?
Cli*_*ord 17
微控制器有什么特殊的C标准吗?
不,有ISO C标准.由于许多小型设备具有需要支持的特殊体系结构功能,因此许多编译器都支持语言扩展.例如,因为8051具有位可寻址RAM,所以_bit
可以提供数据类型.它还具有哈佛架构,因此提供了用于指定不同地址空间的关键字,由于需要不同的指令来处理这些空间,因此单独的地址无法解析.这些扩展将在编译器文档中清楚地指出.此外,符合标准的编译器中的扩展应使用下划线作为前缀.但是,许多提供了用于向后兼容的简单别名,并且应该弃用它们.
...当我在Windows操作系统下编程时,我使用的编译器无关紧要.
由于Windows API是标准化的(由Microsoft提供),并且它仅在x86上运行,因此不需要考虑架构变体.也就是说,您可能仍然会在API中看到FAR和NEAR宏,这是对16位x86的回调及其分段寻址,这也需要编译器扩展来处理.
...即使它仍然是C的基础知识,如循环,变量创建等等,
我不确定这意味着什么.典型的微控制器应用程序没有操作系统或简单的内核,您应该会看到更多的"裸机"或"系统级"代码,因为没有广泛的操作系统API和设备驱动程序接口可以在你的引擎盖.所有这些图书馆电话就是这样; 他们不是语言的一部分; 它是相同的C语言; jut投入了不同的工作.
...有一些我在C中从未见过的用于台式计算机的语法类型.
例如...?
此外,语法正在从版本变为版本.
我对此表示怀疑.再次; 例如...?
我使用AVR-GCC编译器,在以前的版本中,您使用了一个端口I/O函数,现在您可以在新版本中处理类似变量的端口.
这不仅取决于语言或编译器的变化,更可能是简单的"预处理器魔术".在AVR上,所有I/O都是内存映射的,因此,例如,如果包含设备支持头,则它可能具有如下声明:
#define PORTA (*((volatile char*)0x0100))
Run Code Online (Sandbox Code Playgroud)
然后你可以写:
PORTA = 0xFF;
Run Code Online (Sandbox Code Playgroud)
将0xFF写入映射寄存器地址0x100的存储器.你可以看看头文件,看看它是如何做到的.
GCC文档描述了目标特定的变化; AVR是专门处理此节6.36.8,并在3.17.3.如果将其与GCC支持的其他目标进行比较,则其扩展非常少,可能是因为AVR架构和指令集专门用于干净,高效地实现没有扩展的C编译器.
是什么定义了什么函数以及如何将它们实现到编译器中并且仍将它称为C?
重要的是要认识到C编程语言是与其库不同的实体,并且库提供的函数与您自己编写的函数没有区别 - 它们不是语言的一部分 - 所以它可以是C而不是图书馆无论如何.最终,库函数使用相同的基本语言元素编写.您不能指望Win32 API中存在的抽象级别存在于用于微控制器的库中.在大多数情况下,您可以期望至少实现C标准库的一个子集,因为它被设计为具有很少目标硬件依赖性的系统级库.
多年来,我一直在为嵌入式和桌面系统编写C和C++,并且不认识到你似乎感受到的巨大差异,因此只能假设它们是对C语言构成误解的结果.以下书籍可能有所帮助.
嵌入式系统很奇怪,有时会对"标准"C有例外.
从系统到系统,您将有不同的方式来执行诸如声明中断,或定义哪些变量存在于不同的内存段中,或运行"内在函数"(直接映射到汇编代码的伪函数),或执行内联汇编代码.
但是控制流程的基础知识(对于/ if/while/switch/case)以及变量和函数声明应该是全面的.
在以前的版本中,您使用了端口I/O的功能,现在您可以在新版本中处理类似变量的端口.
那不是C语言的一部分; 这是设备支持库的一部分.这是每个制造商必须记录的内容.
C语言假设von Neumann体系结构(所有代码和数据都有一个地址空间),并非所有体系结构实际都具有这种体系结构,但大多数桌面/服务器类机器确实具有(或者至少存在于OS的帮助下).为了解决这个问题而不制作可怕的程序,C编译器(在链接器的帮助下)经常支持一些有助于有效利用多个地址空间的扩展.所有这些都可能对程序员隐藏起来,但它通常会减慢并使程序和数据膨胀.
至于如何访问设备寄存器 - 在不同的桌面/服务器类机器上,这也是非常不同的,但由于编写为在这些机器(Mac OS X,Windows,BSD或Linux)的通用现代操作系统下运行的程序通常直接访问硬件,这不是问题.但是,有一些操作系统代码必须处理这些问题.这通常通过定义在不同体系结构上以不同方式实现的宏和/或函数来完成,或者甚至在单个系统上具有多个版本,以便驱动程序可以用于特定设备(例如以太网芯片),无论它是否在PCI卡上或USB加密狗(可能插入插入PCI插槽的USB卡),或直接映射到处理器的地址空间.
此外,C标准库对托管使用它的程序的系统(C标准库)做出比编译器(和语言本身)更多的假设.当没有通用操作系统或文件系统时,这些事情就没有意义了.fopen
没有文件系统的系统没有意义,甚至printf
可能不容易定义.
至于AVR-GCC及其库所做的事情 - 有很多东西可以解决这个问题.AVR是哈佛的架构具有存储器映射的器件控制寄存器,特殊功能寄存器和通用寄存器(存储器地址0-31),以及用于代码和常量数据的不同地址空间.这已经超出了标准C假设的范围.某些寄存器(通用,特殊和器件控制)可通过特殊指令访问,例如翻转单个位和读/写某些多字节寄存器(多指令操作)会隐式阻止下一条指令的中断(所以下半部分可能发生).这些是桌面C程序不必了解的事情,因为AVR-GCC来自常规GCC,它最初并没有理解所有这些事情.这意味着编译器不会总是使用最佳指令来访问控制寄存器,因此:
*(DEVICE_REG_ADDR) |= 1; // Set BIT0 of control register REG
Run Code Online (Sandbox Code Playgroud)
会变成:
temp_reg = *DEVICE_REG_ADDR;
temp_reg |= 1;
*DEVICE_REG_ADDR = temp_reg;
Run Code Online (Sandbox Code Playgroud)
因为AVR通常必须在其通用寄存器中对它们进行位操作,但对于某些存储器位置,这不是真的.必须改变AVR-GCC以识别当某些操作中使用的变量的地址在编译时已知并且在某个范围内时,它可以使用不同的指令来执行这些操作.在此之前,AVR-GCC刚刚为您提供了一些具有内联汇编的宏(看起来像函数)(并使用GCC现在使用的单指令).如果他们不再提供这些操作的宏版本,那么这可能是一个糟糕的选择,因为它打破了旧代码,但允许您访问这些寄存器,就好像它们是正常变量一旦有效和原子地实现这样做的能力是好的.
我从未见过没有某些控制器特定扩展的微控制器的C编译器.有些编译器比其他编译器更接近ANSI标准,但对于许多微控制器,性能和ANSI符合性之间存在权衡.
在许多8位微控制器上,甚至是一些16位微控制器上,访问堆栈帧上的变量很慢.有些编译器总是在运行时堆栈上分配自动变量,尽管需要额外的代码,有些编译器会在编译时分配自动变量(允许永远不会同时存在的变量重叠),有些允许控制行为使用命令行选项或#pragma
指令.在为这样的机器编码时,我有时喜欢#define
一个名为"auto"的宏,如果它可以帮助事情更快地工作,它将被重新定义为"静态".
一些编译器具有各种存储器存储类.通过将事物声明为合适的存储类,您可以大大提高性能.例如,基于8051的系统可能具有96字节的"数据"存储器,224字节的"idata"存储器与前96字节重叠,以及4K的"xdata"存储器.
可以直接访问"数据"存储器中的变量.
"idata"存储器中的变量只能通过将其地址加载到单字节指针寄存器中来访问.在无论如何都需要的情况下,没有额外的开销来访问它们,因此idata内存非常适合数组.如果数组q
存储在idata内存中,则引用q[i]
将与数据存储器中的引用一样快,但引用q[0]
会更慢(在数据存储器中,编译器可以预先计算地址并在没有指针的情况下访问它注册;在idata内存中是不可能的).
xdata内存中的变量比其他类型的变量要慢得多,但是有更多的xdata内存可用.
如果一个人告诉8051编译器默认将所有内容都放在"数据"中,如果一个变量总共超过96个字节并且没有指示编译器将任何其他内容放在其他地方,则会"耗尽内存".如果默认情况下将所有内容放在"xdata"中,则可以使用更多内存而不会达到限制,但一切都会运行得更慢.最好的方法是放置在"数据"中直接访问的常用变量,在"idata"中间接访问的常用变量和数组,以及"xdata"中不常使用的变量和数组.
归档时间: |
|
查看次数: |
2812 次 |
最近记录: |