ESP32直接端口操作

7

亲爱的StackOverflow用户们,

我正在尝试使用Adafruit公司的HX8357D 3.5英寸TFT显示器(链接)与esp32进行连接。TFT驱动有两个接口:SPI和8位并行。Adafruit提供的库(链接)仅支持esp32上的SPI。我需要更高的显示速度,因此决定自己为esp32添加支持。虽然我没有这种编程方面的经验,但我喜欢挑战。

我通过逆向工程Arduino Uno/Mega的支持来弄清楚8位接口的工作原理。为了添加对esp32的支持,我需要一种直接操作控制esp32 gpio端口的寄存器的方法。我在网上搜索了一下,但是几乎没有关于如何操作的实例。Espressif的技术参考手册(链接)包含了所有需要的信息,但我不够熟练,无法将其转换为代码。

我使用esp32 Arduino内核编程。这个例子(链接)展示了如何使用寄存器将gpio引脚设置为输出,并直接将其设置为高电平和低电平。问题在于,我需要能够将8个引脚设置为输出、写入数据到它们、将它们设置为输入并从中读取数据,所有操作都需要使用寄存器,而不是使用pinMode、digitalRead和digitalWrite函数。

Arduino Uno/Mega的工作方式对我来说很清楚,每个端口有三个寄存器控制:

  • DDR* 读/写
  • PORT* 设置gpio为高电平/低电平
  • PIN* 读取输入gpio的高电平/低电平。

但是在esp32上这是如何工作的?我如何利用寄存器创建8位并行通信?

如果有任何比我更懂这方面知识的人,能够为我解释一下,我将非常感激。提前致谢!


这个有进展了吗?我想为另一个液晶屏做同样的事情。 - petrica.martinescu
4个回答

14

为了在操作8个引脚时最小化计算负担,您将希望这些引脚对应于连续的GPIO编号(例如,GPIO12到GPIO19)。 以下是一个可以同时操作多个输入/输出引脚并且如果满足上述要求(连续的GPIO编号)且GPIO编号都在0-31范围内,则可以正常工作的实现方法;我使用了GPIO12到GPIO19(GPIO12对应输入/输出8位值中的第0位),如果您有带有ESP-WROOM-32或ESP32-WROVER模块的ESP32开发板,则很方便使用。 因此,我将与第0位相对应的GPIO定义如下:

#define PARALLEL_0  12

在初始化时,您需要将所有8个引脚配置为GPIO,例如将它们全部设置为输入:

void setup() {
  for (int i = 0; i < 8; i++) {
    pinMode(PARALLEL_0 + i, INPUT);
  }
}

之后,您可以使用以下函数将8个引脚设置为输入或输出,并读取输入值和写入输出值:

void parallel_set_inputs(void) {
  REG_WRITE(GPIO_ENABLE_W1TC_REG, 0xFF << PARALLEL_0);
}

void parallel_set_outputs(void) {
  REG_WRITE(GPIO_ENABLE_W1TS_REG, 0xFF << PARALLEL_0);
}

uint8_t parallel_read(void) {
  uint32_t input = REG_READ(GPIO_IN_REG);

  return (input >> PARALLEL_0);
}

void parallel_write(uint8_t value) {
  uint32_t output =
    (REG_READ(GPIO_OUT_REG) & ~(0xFF << PARALLEL_0)) | (((uint32_t)value) << PARALLEL_0);

  REG_WRITE(GPIO_OUT_REG, output);
}

8

有很多方法可以做到这一点。我经常一针一线地完成。

一个简单的方法是通过定义变量来创建自己的“寄存器”。如果寄存器宽度是8位,则定义字节变量:

unsigned char disp_register;

接下来你需要像在显示硬件中一样写入该寄存器。当然,下一步你必须将该寄存器输出到ESP32的GPIO引脚。由于引脚分散在各处,因此必须逐个引脚进行操作。为了便于阅读,定义硬件引脚:

/* OUTPUTS (numbers mean GPIO port) */
#define REGISTER_BIT7_ON_PIN        9
#define REGISTER_BIT6_ON_PIN        10
#define REGISTER_BIT5_ON_PIN        5
// continue with all the pins you need

在程序的开头某个位置,将这些引脚设置为输出,或者将它们的默认值设置为“0”:

io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pin_bit_mask =  ((1ULL<< REGISTER_BIT7_ON_PIN) | (1ULL<< REGISTER_BIT6_ON_PIN) | (1ULL<< REGISTER_BIT5_ON_PIN)); // of course, do like this all the pins
gpio_config(&io_conf);

gpio_set_level(REGISTER_BIT7_ON_PIN, 0); // do like this all the pins you need to set the boot-up value, pin-by-pin

接下来,您需要让函数将寄存器的值复制到GPIO引脚的外部世界:
/*
 * wrote this simply for ease of understanding, feel free to do this in a loop
 * or shifting bit by bit
 */
void copy_register_to_GPIO_pins(unsigned char disp_register)
{
    gpio_set_level(REGISTER_BIT7_ON_PIN, (disp_register & 0x80) >> 7);
    gpio_set_level(REGISTER_BIT6_ON_PIN, (disp_register & 0x40) >> 6);
    gpio_set_level(REGISTER_BIT5_ON_PIN, (disp_register & 0x20) >> 5);
    gpio_set_level(REGISTER_BIT4_ON_PIN, (disp_register & 0x10) >> 4);
    gpio_set_level(REGISTER_BIT3_ON_PIN, (disp_register & 0x08) >> 3);
    gpio_set_level(REGISTER_BIT2_ON_PIN, (disp_register & 0x04) >> 2);
    gpio_set_level(REGISTER_BIT1_ON_PIN, (disp_register & 0x02) >> 1);
    gpio_set_level(REGISTER_BIT0_ON_PIN, (disp_register & 0x01));
}

然后,在你将任何内容写入你的注册表之后,调用你的函数输出它:
disp_register = 0x2A; // example value you want to send to display
copy_register_to_GPIO_pins(disp_register);

// or, output byte WITHOUT using any register:
copy_register_to_GPIO_pins(0x2A);

希望你能自己完成反向操作,读取引脚的操作由另一个函数完成,在那里你需要复制每个GPIO引脚的值,并将其组装成字节变量。当然,在此时必须将引脚设置为输入模式。原则上:

/*
 * wrote this simply for ease of understanding
 */
unsigned char copy_GPIO_pins_to_register(void)
{
    unsigned char retval = 0;

    retval |= gpio_get_level(REGISTER_BIT7_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT6_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT5_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT4_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT3_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT2_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT1_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT0_ON_PIN);

    return retval;
}

谢谢你的回答,你为我解决了问题。我的问题是我正在寻找一种直接将数据写入所有GPIO端口的方法。我需要的8个引脚可以自由选择,而ESP32在一个寄存器上有32个引脚,所以这应该不是问题,对吧?我不太明白你为什么说“引脚都分散开来,所以你必须逐个引脚进行操作”?我认为如果你有正确的位掩码,你可以通过直接写入寄存器来同时写入多个GPIO引脚。在数据手册中,我发现这些寄存器被称为W1TS和W1TC。你知道如何做到这一点吗? - Daan van Driel
我不知道ESP32是否具有直接并行端口功能,以便像您所需的那样使用(请尝试联系他们的技术支持 - 他们很乐意提供帮助)。 - EmbeddedGuy

3

如果需要高带宽并行数据输出,则可以考虑使用ESP32的I2S外设的LCD模式。

请参阅ESP32 TRM中的第12.5.1节以及第4章,了解如何将外设映射到所需的引脚。这种方法的好处是,您可以将多达24位的输出从外设映射到输出引脚。

第12.4.4节指出:

ESP32 I2S模块执行数据传输操作[...]按照用户配置的方式串行或并行地时钟输出数据


0

请记住,您可能需要第9个“闪光”引脚来输出信号,以告诉接收设备其他8个引脚现在是有效的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接