ESP32直接端口操作
ESP32 direct port manipulation
亲爱的 Whosebugers,
我正在尝试使用 Adafruit 的 HX8357D 3.5" TFT (link) with an esp32. The TFT driver has two interfaces: SPI and 8-bit parallel. The provided library from Adafruit (link) 仅支持 esp32 上的 SPI。我需要更高的显示速度,所以我决定自己尝试添加对 esp32 的支持.我对这种编程一点经验都没有,但我喜欢挑战。
我通过逆向工程 Arduino Uno/Mega 支持弄清楚了 8 位接口的工作原理。要添加 esp32 支持,我需要一种方法来直接操作控制 esp32 的 gpio 端口的寄存器。我在互联网上四处张望,但很少有关于如何做到这一点的例子。 Espressif (link) 的技术参考手册包含了所有需要的信息,但我还不够熟练,不知道如何将其转化为代码。
为了对 esp32 进行编程,我使用了 esp32 Arduino 内核。此示例 (link) 展示了如何将 gpio 引脚设置为输出并直接使用寄存器将它们设置为高电平和低电平。问题是我需要能够将 8 个引脚设置为输出,向它们写入数据,使它们成为输入,然后从中读取数据,所有这些都使用寄存器而不是使用 pinMode、digitalRead 和 digitalWrite 函数。
它在 Arduino 上的工作方式 Uno/Mega 我很清楚,有三个寄存器控制一个端口:
- DDR* 到 read/write
- PORT* 设置 gpio HIGH/LOW
- PIN* 读取 HIGH/LOW 如果 gpio 是输入。
但这在 esp32 上是如何工作的,我如何利用寄存器来创建这个 8 位并行通信?
如果有人比我更了解这个话题,我将非常感谢您的解释。提前致谢。
有很多方法可以做到这一点。我经常一个接一个地做。
一种简单的方法是通过定义一个变量来创建您自己的 'register'。如果寄存器是8位宽,定义byte变量:
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 引脚值并将其 assemble 到字节变量中。当然,此时必须将引脚设置为输入。原则上:
/*
* 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;
}
为了在操作 8 个引脚时最小化计算负担,您将希望这些引脚对应于连续的 GPIO 编号(例如 GPIO12 到 GPIO19)。
下面是一个并行操作多个 input/output 引脚的实现,如果满足上述要求(连续的 GPIO 编号)并且 GPIO 编号都在 0-31 范围内,则可以工作;我使用了 GPIO12 到 GPIO19(GPIO12 对应于 input/output 8 位值中的位 0),如果您有带有 ESP-WROOM-32 或 ESP32-WROVER 模块的 ESP32 开发板,则可以方便地使用它们。
所以我定义了bit 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);
}
对于高带宽并行数据输出,您可能需要研究 ESP32 的 I2S 外设的 LCD 模式。
请参阅 ESP32 TRM 中的第 12.5.1 节和第 4 章,了解如何将外设映射到所需的引脚。这种方法的好处是您可以将最多 24 位的输出从外设映射到输出引脚。
第 12.4.4 节指出:
The ESP32 I2S module carries out a data-transmit operation [...] Clock out data serially, or in parallel, as configured by the user
请记住,您可能需要第 9 个“选通”引脚用于输出,以告知接收设备其他 8 个引脚现在有效。
亲爱的 Whosebugers,
我正在尝试使用 Adafruit 的 HX8357D 3.5" TFT (link) with an esp32. The TFT driver has two interfaces: SPI and 8-bit parallel. The provided library from Adafruit (link) 仅支持 esp32 上的 SPI。我需要更高的显示速度,所以我决定自己尝试添加对 esp32 的支持.我对这种编程一点经验都没有,但我喜欢挑战。
我通过逆向工程 Arduino Uno/Mega 支持弄清楚了 8 位接口的工作原理。要添加 esp32 支持,我需要一种方法来直接操作控制 esp32 的 gpio 端口的寄存器。我在互联网上四处张望,但很少有关于如何做到这一点的例子。 Espressif (link) 的技术参考手册包含了所有需要的信息,但我还不够熟练,不知道如何将其转化为代码。
为了对 esp32 进行编程,我使用了 esp32 Arduino 内核。此示例 (link) 展示了如何将 gpio 引脚设置为输出并直接使用寄存器将它们设置为高电平和低电平。问题是我需要能够将 8 个引脚设置为输出,向它们写入数据,使它们成为输入,然后从中读取数据,所有这些都使用寄存器而不是使用 pinMode、digitalRead 和 digitalWrite 函数。
它在 Arduino 上的工作方式 Uno/Mega 我很清楚,有三个寄存器控制一个端口:
- DDR* 到 read/write
- PORT* 设置 gpio HIGH/LOW
- PIN* 读取 HIGH/LOW 如果 gpio 是输入。
但这在 esp32 上是如何工作的,我如何利用寄存器来创建这个 8 位并行通信?
如果有人比我更了解这个话题,我将非常感谢您的解释。提前致谢。
有很多方法可以做到这一点。我经常一个接一个地做。
一种简单的方法是通过定义一个变量来创建您自己的 'register'。如果寄存器是8位宽,定义byte变量:
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 引脚值并将其 assemble 到字节变量中。当然,此时必须将引脚设置为输入。原则上:
/*
* 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;
}
为了在操作 8 个引脚时最小化计算负担,您将希望这些引脚对应于连续的 GPIO 编号(例如 GPIO12 到 GPIO19)。 下面是一个并行操作多个 input/output 引脚的实现,如果满足上述要求(连续的 GPIO 编号)并且 GPIO 编号都在 0-31 范围内,则可以工作;我使用了 GPIO12 到 GPIO19(GPIO12 对应于 input/output 8 位值中的位 0),如果您有带有 ESP-WROOM-32 或 ESP32-WROVER 模块的 ESP32 开发板,则可以方便地使用它们。 所以我定义了bit 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);
}
对于高带宽并行数据输出,您可能需要研究 ESP32 的 I2S 外设的 LCD 模式。
请参阅 ESP32 TRM 中的第 12.5.1 节和第 4 章,了解如何将外设映射到所需的引脚。这种方法的好处是您可以将最多 24 位的输出从外设映射到输出引脚。
第 12.4.4 节指出:
The ESP32 I2S module carries out a data-transmit operation [...] Clock out data serially, or in parallel, as configured by the user
请记住,您可能需要第 9 个“选通”引脚用于输出,以告知接收设备其他 8 个引脚现在有效。