c - 抽象引脚和端口 - 使用指针

标签 c pointers embedded avr

我正在开发一个嵌入式项目,试图了解嵌入式板编程的一些细节。正如人们可能已经猜到的那样,这涉及到用 C 语言编写代码。我在正确设置方面并没有太大困难(就端口/引脚/等而言),但我希望抽象一些代码使其更具可读性。

例如,以下代码打开板上的绿色 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 的设置/功能(我最终希望将其扩展到其他概念,例如定时器/中断/等......)。

如何使用结构/指针来正确抽象它?

我目前正在尝试以下方法,但效果不佳,并且 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(;;) { }
}

我知道在这种情况下我应该使用指针,特别是数据方向寄存器和端口(以便我正确引用该内存位置),但我不知道如何正确引用它们。

我在这里缺少什么?

注意:如果需要,我将发布 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 之类的东西是将地址从地址空间映射到 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;
}

请注意,我使用了缓冲区作为我的假内存空间,因为您没有像在微 Controller 中那样的x86架构中免费使用的平面内存模型。

关于c - 抽象引脚和端口 - 使用指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35375472/

相关文章:

embedded - 16 位处理器的任意精度算术 (Bignum)

c++ - fstream 文件 I/O 问题 - 何时关闭文件流

c - c 中的矩阵乘法

c++ - 为什么我获得未分配内存的读写权限?

c++ - 在不同类型的对象指针之间切换

c++ - 如果我删除 C++ 中的指针,将会删除什么

c - PIC32 UART 未接收数据

c - 为什么在编程中使用常量?

c - C语言中的指针

c - 您如何在内存非常受限的嵌入式系统上处理大量数据传输?