抽象引脚和端口——使用指针
Abstracting Pins and Ports - Using Pointers
我正在从事一个嵌入式项目,试图了解对嵌入式板进行编程的一些来龙去脉。正如人们可能已经猜到的那样,这涉及到用 C 语言编写代码。我在掌握正确设置方面并没有太多困难(根据 ports/pins/etc..),但我希望抽象出我的一些代码使其更具可读性。
例如,以下代码打开电路板上的绿色 LED:
// Required CPU Speed Define
#define F_CPU 16000000
// Include the necissary library header files
#include <avr/io.h>
int main() {
DDRD |= (1 << DDD5);
PORTD |= (1 << PD5);
PORTD ^= (1 << PD5);
for(;;) { }
}
另一方面,我可以 #define
端口的名称,这样它们就更清楚了,但这似乎不是理想的解决方案(不幸的是,这是我目前使用的方法) .
我想抽象一些启用板载 LED 的 setting/functionality(我最终希望扩展到其他概念,例如 timers/interrupts/etc..)。
我如何使用 Struct/Pointers 正确抽象它?
我目前正在尝试以下方法,但效果不佳,LED 无法打开:
OnBoardLED.h
typedef struct {
unsigned int dataDirectionRegister;
unsigned int portNumber;
unsigned int pinNumber;
} OnBoardLED;
void setDataDirectionRegister(OnBoardLED* led, unsigned int DDR);
void setPortNumber(OnBoardLED* led, unsigned int port);
void setPinNumber(OnBoardLED* led, unsigned int pin);
void turnOn(OnBoardLED* led);
void turnOff(OnBoardLED* led);
和
main.c
#include "inc/OnBoardLED.h"
int main(void) {
OnBoardLED greenLED;
setDataDirectionRegister(&greenLED, DDRD);
setPortNumber(&greenLED, PORTD);
setPinNumber(&greenLED, 5);
turnOn(&greenLED);
for(;;) { }
}
我知道我应该在这种情况下使用指针,特别是数据方向寄存器和端口(以便我正确引用该内存位置),但我不知道如何正确引用它们。
我在这里错过了什么?
注意:如果需要,我将 post 我当前对每个功能的实现,在 OnBoardLED.c
中定义
编辑:
OnBoardLED.c
#include "inc/OnBoardLED.h"
void setDataDirectionRegister(OnBoardLED* led, unsigned int DDR) {
led->dataDirectionRegister = DDR;
}
void setPortNumber(OnBoardLED* led, unsigned int port) {
led->portNumber = port;
}
void setPinNumber(OnBoardLED* led, unsigned int pin) {
led->pinNumber = pin;
}
void turnOn(OnBoardLED* led) {
led->dataDirectionRegister |= (1 << led->pinNumber);
led->portNumber |= (1 << led->pinNumber);
led->portNumber ^= (1 << led->pinNumber);
}
我对 AVR 工具链不太实用,但据我所知,DDRD
是宏,它将地址从地址 space 映射到 CPU 的寄存器.
因此,通过检查源代码,您可以获得如下信息:
#define __SFR_OFFSET 0
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define DDRD _SFR_IO8(0x0A)
这意味着替换 DDRD
会产生如下内容:
*(volatile uint8_t*)((0x0A) + 0)
这是一个智能宏,当用作左手操作数时允许您在该内存地址存储一个值,当用作右手操作数时您可以读取寄存器的值。但这仍然是一个宏,宏可以像在这种情况下隐藏邪恶的细节。
你的代码是
led->dataDirectionRegister = *(volatile uint8_t*)((0x0A) + 0);
led->dataDirectionRegister |= (1 << led->pinNumber);
所以你所做的只是将数据方向寄存器的值保存到结构成员中,然后用你想保存到寄存器中的值覆盖它。所以基本上什么都没有发生。
您需要保存数据方向寄存器的地址,以便稍后在调用 turnOn
方法时能够取消引用它。这很容易通过将成员声明为
来实现
volatile uint8_t* dataDirectionRegister;
这样你就可以做到
void setDataDirectionRegister(OnBoardLED* led, volatile uint8_t* DDR) {
led->dataDirectionRegister = DDR;
}
并将其调用为 setDataDirectionRegister(&led, &DDRD);
。注意 &
运算符用于获取寄存器的内存位置的地址。
但是现在您有了一个地址,所以在您的 turnOn
方法中您必须取消引用变量以将数据存储到其中:
void turnOn(OnBoardLED* led) {
*led->dataDirectionRegister |= (1 << led->pinNumber);
...
所以让我们制作一个 concrete example 来向您展示问题以及如何解决它:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define __SFR_OFFSET 0
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(&buffer[0] + mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define DDRD _SFR_IO8(0x0A)
uint8_t buffer[256];
int main(void) {
memset(&buffer[0], 0, 256);
DDRD = 0x40;
printf("value: %x\n", (int)DDRD);
*(volatile uint8_t*)(&buffer[0] + (0x0A + 0)) = 0x41;
printf("value: %x\n", (int)DDRD);
// now let's do what you are doing in your struct
unsigned int dataDirectionRegister = DDRD; // value of register is copied into variable
dataDirectionRegister = 0x80; // variable is changed but not real register
printf("register: %x, variable: %x\n", (int)DDRD, dataDirectionRegister);
// you must save an address to do what you need
volatile uint8_t* realDataDirectionRegister = &DDRD;
*realDataDirectionRegister = 0x80;
printf("register: %x, variable: %x\n", (int)DDRD, (int)*realDataDirectionRegister);
return 0;
}
请注意,我使用了 buffer
作为我的假内存 space,因为您没有像在 x86 架构中那样可以像在微控制器中那样免费使用的平面内存模型。
我正在从事一个嵌入式项目,试图了解对嵌入式板进行编程的一些来龙去脉。正如人们可能已经猜到的那样,这涉及到用 C 语言编写代码。我在掌握正确设置方面并没有太多困难(根据 ports/pins/etc..),但我希望抽象出我的一些代码使其更具可读性。
例如,以下代码打开电路板上的绿色 LED:
// Required CPU Speed Define
#define F_CPU 16000000
// Include the necissary library header files
#include <avr/io.h>
int main() {
DDRD |= (1 << DDD5);
PORTD |= (1 << PD5);
PORTD ^= (1 << PD5);
for(;;) { }
}
另一方面,我可以 #define
端口的名称,这样它们就更清楚了,但这似乎不是理想的解决方案(不幸的是,这是我目前使用的方法) .
我想抽象一些启用板载 LED 的 setting/functionality(我最终希望扩展到其他概念,例如 timers/interrupts/etc..)。
我如何使用 Struct/Pointers 正确抽象它?
我目前正在尝试以下方法,但效果不佳,LED 无法打开:
OnBoardLED.h
typedef struct {
unsigned int dataDirectionRegister;
unsigned int portNumber;
unsigned int pinNumber;
} OnBoardLED;
void setDataDirectionRegister(OnBoardLED* led, unsigned int DDR);
void setPortNumber(OnBoardLED* led, unsigned int port);
void setPinNumber(OnBoardLED* led, unsigned int pin);
void turnOn(OnBoardLED* led);
void turnOff(OnBoardLED* led);
和
main.c
#include "inc/OnBoardLED.h"
int main(void) {
OnBoardLED greenLED;
setDataDirectionRegister(&greenLED, DDRD);
setPortNumber(&greenLED, PORTD);
setPinNumber(&greenLED, 5);
turnOn(&greenLED);
for(;;) { }
}
我知道我应该在这种情况下使用指针,特别是数据方向寄存器和端口(以便我正确引用该内存位置),但我不知道如何正确引用它们。
我在这里错过了什么?
注意:如果需要,我将 post 我当前对每个功能的实现,在 OnBoardLED.c
中定义编辑:
OnBoardLED.c
#include "inc/OnBoardLED.h"
void setDataDirectionRegister(OnBoardLED* led, unsigned int DDR) {
led->dataDirectionRegister = DDR;
}
void setPortNumber(OnBoardLED* led, unsigned int port) {
led->portNumber = port;
}
void setPinNumber(OnBoardLED* led, unsigned int pin) {
led->pinNumber = pin;
}
void turnOn(OnBoardLED* led) {
led->dataDirectionRegister |= (1 << led->pinNumber);
led->portNumber |= (1 << led->pinNumber);
led->portNumber ^= (1 << led->pinNumber);
}
我对 AVR 工具链不太实用,但据我所知,DDRD
是宏,它将地址从地址 space 映射到 CPU 的寄存器.
因此,通过检查源代码,您可以获得如下信息:
#define __SFR_OFFSET 0
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define DDRD _SFR_IO8(0x0A)
这意味着替换 DDRD
会产生如下内容:
*(volatile uint8_t*)((0x0A) + 0)
这是一个智能宏,当用作左手操作数时允许您在该内存地址存储一个值,当用作右手操作数时您可以读取寄存器的值。但这仍然是一个宏,宏可以像在这种情况下隐藏邪恶的细节。
你的代码是
led->dataDirectionRegister = *(volatile uint8_t*)((0x0A) + 0);
led->dataDirectionRegister |= (1 << led->pinNumber);
所以你所做的只是将数据方向寄存器的值保存到结构成员中,然后用你想保存到寄存器中的值覆盖它。所以基本上什么都没有发生。
您需要保存数据方向寄存器的地址,以便稍后在调用 turnOn
方法时能够取消引用它。这很容易通过将成员声明为
volatile uint8_t* dataDirectionRegister;
这样你就可以做到
void setDataDirectionRegister(OnBoardLED* led, volatile uint8_t* DDR) {
led->dataDirectionRegister = DDR;
}
并将其调用为 setDataDirectionRegister(&led, &DDRD);
。注意 &
运算符用于获取寄存器的内存位置的地址。
但是现在您有了一个地址,所以在您的 turnOn
方法中您必须取消引用变量以将数据存储到其中:
void turnOn(OnBoardLED* led) {
*led->dataDirectionRegister |= (1 << led->pinNumber);
...
所以让我们制作一个 concrete example 来向您展示问题以及如何解决它:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define __SFR_OFFSET 0
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(&buffer[0] + mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define DDRD _SFR_IO8(0x0A)
uint8_t buffer[256];
int main(void) {
memset(&buffer[0], 0, 256);
DDRD = 0x40;
printf("value: %x\n", (int)DDRD);
*(volatile uint8_t*)(&buffer[0] + (0x0A + 0)) = 0x41;
printf("value: %x\n", (int)DDRD);
// now let's do what you are doing in your struct
unsigned int dataDirectionRegister = DDRD; // value of register is copied into variable
dataDirectionRegister = 0x80; // variable is changed but not real register
printf("register: %x, variable: %x\n", (int)DDRD, dataDirectionRegister);
// you must save an address to do what you need
volatile uint8_t* realDataDirectionRegister = &DDRD;
*realDataDirectionRegister = 0x80;
printf("register: %x, variable: %x\n", (int)DDRD, (int)*realDataDirectionRegister);
return 0;
}
请注意,我使用了 buffer
作为我的假内存 space,因为您没有像在 x86 架构中那样可以像在微控制器中那样免费使用的平面内存模型。