在类似于 Arduino 的 AVR 微控制器中实现引脚编号

Posted

技术标签:

【中文标题】在类似于 Arduino 的 AVR 微控制器中实现引脚编号【英文标题】:Implementing pin numbers in AVR microcontroller similar to Arduino 【发布时间】:2016-11-16 13:49:24 【问题描述】:

我想为 Atmel ATMega32U4 实现类似于 Arduino 的引脚号的东西。我查看了 Arduino 的 digitalWrite 命令和相关的源文件以了解它们是如何做到的,但我认为这有点令人费解,所以我想实现一个更基本的版本。

这个想法是让整数 1 到 n 代表 AVR 芯片上的每个 I/O 引脚。我从一个指向 DDR/PORT 寄存器地址的指针数组开始,其中的索引代表引脚:

volatile uint8_t *pin_port_dir_regs[] = 
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    &DDRE,            // PIN_7
    &DDRB,            // PIN_8
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    &DDRB,            // PIN_15
    &DDRB             // PIN_16
;

volatile uint8_t *pin_port_out_regs[] = 
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    &PORTE,            // PIN_7
    &PORTB,            // PIN_8
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    &PORTB,            // PIN_15
    &PORTB             // PIN_16
;

我还需要知道每个 DDRx/PORTx 寄存器中的位数,所以我创建了另一个数组:

const uint8_t pin_bits[] = 
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(6),  // PIN_7
    _BV(4),  // PIN_8
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // PIN_14
    _BV(1),  // PIN_15
    _BV(3)   // PIN_16
;

为了设置引脚模式和写入引脚,我创建了以下函数:

void pin_mode(uint8_t pin, uint8_t direction) 
    // defeference the pointer to the direction register
    uint8_t port_dir_register = *(pin_port_dir_regs[pin]);

    // get pin mask
    uint8_t mask = pin_bits[pin];

    // set its mode
    if (direction == INPUT) 
        port_dir_register &= ~mask;
     else 
        port_dir_register |= mask;
    


void pin_write(uint8_t pin, uint8_t level) 
    // defeference the pointer to the output register
    uint8_t port_out_register = *(pin_port_out_regs[pin]);

    // get pin mask
    uint8_t mask = pin_bits[pin];

    // set output
    if (level == LOW) 
        port_out_register &= ~mask;
     else 
        port_out_register |= mask;
    

应该发生的是你会打电话给例如pin_mode(7, OUTPUT) 将引脚 7 设置为输出,然后 pin_write(7, HIGH) 将输出设置为 1(其中 OUTPUT 和 HIGH 是预定义的宏)。代码成功编译并上传到 AVR,但是当我测试输出时它没有响应。我想我必须写入 some 内存位置,但不是与预期寄存器对应的位置。有人发现我尝试这样做的方式有问题吗?

【问题讨论】:

为什么不使用 gcc 提供的标准文件 for avr?另外:如果您使用 pin_write 方法为引脚编写一些初始化,那么您只会在 prog 的启动阶段浪费大量代码。所有这些都可以在编译时通过模板代码完成,而不是单独使用每个引脚。同一作业设置 ddr 和端口寄存器一次而不是 8 次。 @Klaus,你指的是什么标准文件?我不知道现有的代码可以做我想做的事。此外,我不关心现阶段的启动优化 - 但感谢您的建议。 这对我来说似乎是在浪费宝贵的 RAM 和 ROM 空间。你的问题不是这里的主题。我们是 noi 讨论网站,它太自以为是了。编辑:对不起,我想到了最后一句话。请提供更多细节什么是错的。见How to Ask。 【参考方案1】:

作为对我给出的评论的回答:

你指的是什么标准文件?

来自安装了 avrlibc 的 avr-gcc 是每个可用控制器的标准文件集。

如果您的代码类似于:

#include <avr/io.h>  // automatic include correct io from device like
                     // <avr/iom32u4.h> in your case. That file defines
                     // all registers and pin descriptions and also irq
                     // vectors!

// So you can write:
int main()

     DDRA= 1 << PA4;  // enable output Pin 4 on Port A
     PORTA= 1 << PA4; // set output Pin4 on Port A high

     // and normally on startup all needed output pins will be 
     // set with only one instruction like:
     // To set Pin 2..4 as output
     DDRA= ( 1 << PA4) | ( 1 << PA3 ) | ( 1 << PA2 );

对函数调用做一个简单的PORTA= 1 &lt;&lt; PA4 是很浪费的。并且定义自己的寄存器名称和引脚名称是没有用的。如您所见,所有引脚都已定义。

您认为只有一个逻辑端口号位于魔术定义端口上的“某处”的想法对我来说不是很自然。如果您设计电路和软件,则必须按照数据表中的说明处理引脚。没有“逻辑引脚号”。这个想法对更大的代码大小没有任何帮助:-)

此外,您还必须记住哪些引脚还具有多种功能,例如用于 uart 和所有其他硬件的东西。因此,移动到另一台设备总是需要审查引脚/端口的使用情况。这里的逻辑端口号也是硬件和软件自然方式之间的一个复杂层。

【讨论】:

【参考方案2】:

您的代码无法运行的原因是这一步:

uint8_t port_out_register = *(pin_port_out_regs[pin]);

您想要写入位于特定地址的硬件寄存器。为此,您需要:

*(pin_port_out_regs[pin]) = some_value;

否则你只会修改堆栈内存没有任何收获。

试着改成这个:

void pin_write(uint8_t pin, uint8_t level)

    uint8_t *port_out_register = pin_port_out_regs[pin];
    uint8_t mask = pin_bits[pin];

    if (level == LOW) 
        *port_out_register &= ~mask;
     else 
        *port_out_register |= mask;
    

【讨论】:

谢谢,成功了!

以上是关于在类似于 Arduino 的 AVR 微控制器中实现引脚编号的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法处理 AVR/Arduino 微控制器中的堆内存碎片?

AVR 微控制器中的 USART 数字逻辑是啥?

如何延迟 avr 微控制器的组装?

USB 编程

在 Mac 上与 AVR 微控制器交互

是否可以在 AVR 微控制器中永久保存一些数据?