在类似于 Arduino 的 AVR 微控制器中实现引脚号
Implementing pin numbers in AVR microcontroller similar to Arduino
我想为 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 内存位置,但不是与预期寄存器对应的位置。有人发现我尝试这样做的方式有问题吗?
您的代码不起作用的原因是这一步:
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;
}
}
作为对我给出的评论的回答:
what standard files do you refer to?
安装了 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 << PA4
来一个函数调用是很大的浪费。定义自己的寄存器名称和管脚名称是没有用的。如您所见,所有引脚都已定义。
你的想法只有一个逻辑端口号,它位于 "somewhere" 一个魔法定义的端口上,我觉得这不太自然。如果您设计电路和软件,则必须按照数据表中的描述处理引脚。没有"logical pin number"。这个想法除了更大的代码大小外别无帮助:-)
此外,您还必须记住哪些引脚还具有多种功能,例如 uart 和所有其他硬件。因此,转移到另一个设备将始终需要审查引脚/端口的使用情况。这里的逻辑端口号也是硬件和软件自然方式之间的复杂层。
我想为 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 内存位置,但不是与预期寄存器对应的位置。有人发现我尝试这样做的方式有问题吗?
您的代码不起作用的原因是这一步:
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;
}
}
作为对我给出的评论的回答:
what standard files do you refer to?
安装了 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 << PA4
来一个函数调用是很大的浪费。定义自己的寄存器名称和管脚名称是没有用的。如您所见,所有引脚都已定义。
你的想法只有一个逻辑端口号,它位于 "somewhere" 一个魔法定义的端口上,我觉得这不太自然。如果您设计电路和软件,则必须按照数据表中的描述处理引脚。没有"logical pin number"。这个想法除了更大的代码大小外别无帮助:-)
此外,您还必须记住哪些引脚还具有多种功能,例如 uart 和所有其他硬件。因此,转移到另一个设备将始终需要审查引脚/端口的使用情况。这里的逻辑端口号也是硬件和软件自然方式之间的复杂层。