如何解决对AVR芯片进行编程时如何定义内存指针?
序言:在作为应用程序开发人员工作了几年之后,软件工程的世界变得比以往更加晦涩难懂。原因是真正的东西隐藏在成千上万的抽象层之下:操作系统,框架等。年轻一代被剥夺了使用类似PDP的机器的乐趣,在这些机器上所有编程都是通过电开关切换完成的。另一个问题是现代编程语言的短暂性质。曾经有Python 2.x,现在已不推荐使用,而Python 3.x则将在几个月后不推荐使用。同上其他语言。 ANSI C看起来像是Cheops的金字塔:它在70年代就存在了,我毫不怀疑它会在太阳变成红矮星之后出现。
现在看来,了解硬件和软件之间相互作用的唯一方法是玩嵌入式开发。从教学的角度来看,物理芯片非常方便,因为它们可以解决C语言最困难的部分,即指针。在OS环境中进行编码时,* /&表示法仍然很令人困惑,因为它指向虚拟内存内部的某个位置。并且在您了解什么是虚拟内存之前,您必须阅读关于操作系统开发等的一些专着。您可能会觉得它很愚蠢,但是我确实很想知道现在哪个晶体管在阻碍我的发展。至少,我可以将物理引脚电压连接到编程抽象。
由于许多教科书和可访问的硬件,目前我正在使用Atmel芯片和WinAVR软件包。尽管所有书籍都承诺使用纯C语言教授AVR编码,但实际情况是所有指针都隐藏在PORTA,DDRB等宏后面。所有代码示例均包括头文件io.h,该文件又引用了其他特定的头文件给定的芯片,例如“ iomx8.h”。到目前为止,我在这些标头中找不到任何宏定义。在Atmega168上增加物理引脚14上的电压的代码看起来像
DDRB = 0x01;
PORTB = 0x01;
幸运的是,Microchip网站提供了一些基本文档,例如,如果要提高物理引脚14的电压,则需要执行以下步骤:
unsigned char *ddrB;
ddrB = (unsigned char*)0x24; // the address of ddrB is 0x24
*ddrB |= 0x01; // set up low impedance/ high current state for the transistor 0
unsigned char *portB;
portB = (unsigned char*)0x25;
*portB |= 0x01; // voltage on
*portB &= ~(0x01); // voltage off
不幸的是,这是我潜伏一周后获得的唯一信息。现在,我正在经历USART编程,而所有这些UBRR0H,UCSR0C的事情都变得更加复杂。由于提供的头文件不包含任何寄存器的宏定义,因此我还能在哪里找到它?
几年前,有人问过类似的问题:accessing AVR registers with C?。但是,除了GCC本身可以将一些神话般的PORTB映射到实际物理位置的线索外,所提供的答案多少没有用。有人可以描述映射背后的机制吗?
解决方法
从内存映射的角度来看:通用寄存器,特殊功能+ I / O寄存器和SRAM共享非重叠范围的单个地址空间,如数据表中针对不同处理器的描述AVR系列。您的所有指针都将引用此内存空间,除非注释为指向PROGMEM的指针(这将导致发出不同的指令)。无需任何虚拟内存映射即可进行引用。
例如,ATtiny 25/45/85的第18页上显示以下地图:
您的链接器知道此内存映射,并将相应地放置变量。例如,在上述示例设备中,在您的一个编译单元中声明的全局变量将以高于0x0060的地址结束,从而最终在SRAM中结束。
从指令编码的角度来看::尽管只有一个地址空间,但为某些重要区域保留了特殊功能。例如,IN和OUT指令的指令编码中有6位,可用于直接引用[0x20,0x5F)
中64个地址之一。
IN和OUT指令具有独特的加载和存储到直接在指令中编码的固定地址的能力,因为正常的加载和存储指令需要先加载“ Z”寄存器的间接加载。
结果,当编译器看到对固定I / O寄存器的内存操作时,它可能生成这些更有效的指令。但是,通过指针进行的正常加载/存储将具有相同的效果(尽管所需的时钟周期数不同)。对于不适合前64个扩展I / O寄存器(例如atmega328p上的OSCCAL),将始终生成正常的加载/存储指令。
,简短答案-Atmel包含的标头中隐藏着一组宏,这些宏创建指向寄存器位置的指针。如果要查看任何源以及其他必要的标头(例如interrupt.h),它们位于WinAVR-20100110 / avr / include /
以下是该过程的简要概述:
您的Makefile定义了要使用的设备,然后将其定义传递给编译器。
DEVICE = atmega2560
...
-D__$(DEVICE)__
然后,您包含io.h,它会根据您的设备自动包含必要的标头:
// In main source file
#include <io.h>
// In io.h
#include <avr/sfr_defs.h>
// ...
#elif defined (__AVR_ATmega2560__)
# include <avr/iom2560.h>
// In sfr_defs.h
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define __SFR_OFFSET 0x20
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
// In iom2560.h
#include <avr/iomxx0_1.h>
// Other device specific definitions
// Om iomxx0_1.h
#define PINA _SFR_IO8(0X00)
// Other device family shared definitions
因此,如果您全部展开,则得到的是指向寄存器地址的易失性指针。每当您在代码中使用PINA时,预处理器都会将其替换为所有扩展的宏:
PINA
_SFR_IO8(0X00)
_MMIO_BYTE((0X00) + __SFR_OFFSET)
(*(volatile uint8_t *)((0X00) + 0x20))
哪个指定PINA是指向0x20的易失8位存储器地址的指针。然后,内部芯片架构会在每次访问该地址时将该地址映射到适当的外设寄存器。
不同的设备具有不同的寄存器地址和偏移量。如果要定义自己的数据,则需要查看相关的数据表。对于大多数AVR芯片,在结尾处有一个标题为“寄存器摘要”的部分,列出了所有寄存器地址和各个控制位的名称。以我的经验(至少对于AVR而言),数据表中的寄存器和位的名称与io.h文件中的定义完全相同。
还要注意使用“ uint8_t”而不是“ char”。通常(强烈建议)使用
typedef unsigned char uint8_t
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。