DOOM Watch on ESP32. Part 1

Having tried development with ready-made ESP32 modules, I wanted to do something small and native. I decided to make a watch. At first I thought about the ESP32-PICO-D4 . Since it has only 4Mb flash for the program, I decided to make a full version with an extension of up to 16Mb flash and 8Mb SRAM. Whatever the clock you can run the first Doom. In general, that was all at full stuffing!



What has not been done or needs improvement:

  1. Battery indicator
  2. Charging barrier circuit implemented on Schottky diode
  3. The antenna is not very well located on another layer from ESP32

Not a tutorial!



By display




I applied a color display on the ST7789 controller with a resolution of 240x240. It is quite compact and cheap. More and more drivers and ports are appearing on the network. For example, there is a port for LittlevGL , but there is no wheelbarrow on this display. I think it can be bought and glued. Maybe someone has experience? Share.

I developed one board on ST7789 and it started up without any problems.



LittlevGL esp32


By filling programs and debug


I used the CP2102 USB-TO-UART BRIDGE converter. Firstly, it is compact and secondly, the connection diagram is very simple and almost no additional parts are required.
Option 2: A 4.7 μF capacitor can be added if powering other devices from the on-chip regulator.
If you connect REGIN and VBUS to a USB 5V power supply, you will only need a shunt capacitor at the input. Although I think it can work without it. VDD on a chip in this case is the way out! I made a mistake and connected it to the 3.3V power circuit and could not understand why I have 4.65V on the power supply ?!

The link in the documentation below has options for connecting to 3.3V power. But I think there’s absolutely no need to power CP2102 when we don’t need to upload or debug the device



Modulo battery charging


The resistor on the LTC4054 sets the charging current:



Nutrition


Powered by 3.7V batteries with stabilizer HT7833. Output current 500mA. It has a small ~ 300mV voltage drop. LD1117-3.3 has “a little” more.

A note about the conclusions of VDD_SDIO. This pin power supply output is 1.8V or 3.3V, depending on the state of the IO12 microcontroller at startup . 3.3V GPIO12 is 0 (default)
VDD_SDIO works as the power supply for the related IO, and also for an external device.

When VDD_SDIO operates at 1.8 V, it can be generated from ESP32’s internal LDO. The maximum currentthis LDO can offer is 40 mA, and the output voltage range is 1.65 V~2.0 V.

When the VDD_SDIO outputs 1.8 V, the value of GPIO12 should be set to 1 when the chip boots and it is recommended that users add a2 kΩ ground resistor and a 4.7 mF filter capacitor close to VDD_SDIO.

When VDD_SDIO operates at 3.3 V, it is driven directly by VDD3P3_RTC through a 6Ωresistor, therefore,there will be some voltage drop from VDD3P3_RTC.

When the VDD_SDIO outputs 3.3 V, the value of GPIO12 is 0 (default) when the chip boots and it is recommended that users add a 1mF capacitor close to VDD_SDIO

This is very convenient if you have flash 1.8V. But in my case, I scored this conclusion and connected my 3V3 flash and PSRAM to the general power supply.

Some ESP32 modules use VDD_SDIO, therefore I can’t hang anything from the peripherals on IO12 at startup. You can, for example, hang a button. In one of my solutions, I hung an SPI leg on IO12 and the module did not start. Apparently a unit from this SPI port got on IO12, but 0 was needed or vice versa. This must be taken into account!

All together:

image

8MB PSRAM


8MB PSRAM Upgrade Mod
Support for external RAM
PSRAM / CE (pin 1)> ESP32 GPIO 16
PSRAM SO (pin 2)> flash DO
PSRAM SIO [2] (pin 3)> flash WP
PSRAM SI (pin 5)> flash DI
PSRAM SCLK (pin 6)> ESP32 GPIO 17
PSRAM SIO [3] (pin 7)> flash HOLD
PSRAM Vcc (pin 8)> ESP32 VCC_SDIO


Pcb antenna


I used Small Size 2.4 GHz PCB antenna . It is in the Eagle Autodesk library and occupies a small area. You can probably use CERAMIC DIELECTRIC ANTENNA but you need to buy it, and the price of a PCB antenna is a little more occupied. However, a ceramic antenna is less effective. Any option

Antenna Selection Quick Guide Antenna Design and RF Layout Guidelines is suitable for checking the concept.

image



A bit about matching the antenna




The ESP32 Hardware Design Guidelines on page 7 provide guidelines for implementing an RF filter:
The output impedance of the RF pins of ESP32 (QFN 6 * 6) and ESP32 (QFN 5 * 5) are (30 + j10) Ω and (35 + j10) Ω, respectively
To calculate, use the Online Smith Chart Tool . The main idea is to get to the center of the circle at (30 + j10). However, this is calculated data and the actual use of the parameters can be affected by the thickness of the tracks and the PCB, as well as the location relative to other components of the circuit.



This is not the only antenna matching circuit. For example, matching on the esp32-pic board is a little different:

esp32-pico-kit-v4_schematic

Smith Chart and Impedance Matching The

location of the antenna and the free area around it are also important. As I wrote earlier, in my case, the position is not optimally selected. But for the first iteration of the board and verification of the concept, the



second part will be devoted to the board itself and the third porting of software. Perhaps everything fits into one.

I’ll get ahead a bit on the Doom port from Espressif Systems. The port uses ILI9341, we have ST7789. But since the initialization and output of the buffer is taken out in a separate file and divided into separate methods, adaptation to my display should not cause great difficulties.

  • Ili_init and displayTask are responsible for initializing the display
  • DisplayTask is responsible for the display.

displayTask
void IRAM_ATTR displayTask(void *arg) {
	int x, i;
	int idx=0;
	int inProgress=0;
	static uint16_t *dmamem[NO_SIM_TRANS];
	spi_transaction_t trans[NO_SIM_TRANS];
	spi_transaction_t *rtrans;

    esp_err_t ret;
    spi_bus_config_t buscfg={
        .miso_io_num=-1,
        .mosi_io_num=PIN_NUM_MOSI,
        .sclk_io_num=PIN_NUM_CLK,
        .quadwp_io_num=-1,
        .quadhd_io_num=-1,
        .max_transfer_sz=(MEM_PER_TRANS*2)+16
    };
    spi_device_interface_config_t devcfg={
        .clock_speed_hz=26000000,               //Clock out at 26 MHz. Yes, that's heavily overclocked.
        .mode=0,                                //SPI mode 0
        .spics_io_num=PIN_NUM_CS,               //CS pin
        .queue_size=NO_SIM_TRANS,               //We want to be able to queue this many transfers
        .pre_cb=ili_spi_pre_transfer_callback,  //Specify pre-transfer callback to handle D/C line
    };

	printf("*** Display task starting.\n");

    //Initialize the SPI bus
    ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1);
    assert(ret==ESP_OK);
    //Attach the LCD to the SPI bus
    ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
    assert(ret==ESP_OK);
    //Initialize the LCD
    ili_init(spi);

	//We're going to do a fair few transfers in parallel. Set them all up.
	for (x=0; x<NO_SIM_TRANS; x++) {
		dmamem[x]=pvPortMallocCaps(MEM_PER_TRANS*2, MALLOC_CAP_DMA);
		assert(dmamem[x]);
		memset(&trans[x], 0, sizeof(spi_transaction_t));
		trans[x].length=MEM_PER_TRANS*2;
		trans[x].user=(void*)1;
		trans[x].tx_buffer=&dmamem[x];
	}
	xSemaphoreGive(dispDoneSem);

	while(1) {
		xSemaphoreTake(dispSem, portMAX_DELAY);
//		printf("Display task: frame.\n");
#ifndef DOUBLE_BUFFER
		uint8_t *myData=(uint8_t*)currFbPtr;
#endif

		send_header_start(spi, 0, 0, 320, 240);
		send_header_cleanup(spi);
		for (x=0; x<320*240; x+=MEM_PER_TRANS) {
#ifdef DOUBLE_BUFFER
			for (i=0; i<MEM_PER_TRANS; i+=4) {
				uint32_t d=currFbPtr[(x+i)/4];
				dmamem[idx][i+0]=lcdpal[(d>>0)&0xff];
				dmamem[idx][i+1]=lcdpal[(d>>8)&0xff];
				dmamem[idx][i+2]=lcdpal[(d>>16)&0xff];
				dmamem[idx][i+3]=lcdpal[(d>>24)&0xff];
			}
#else
			for (i=0; i<MEM_PER_TRANS; i++) {
				dmamem[idx][i]=lcdpal[myData[i]];
			}
			myData+=MEM_PER_TRANS;
#endif
			trans[idx].length=MEM_PER_TRANS*16;
			trans[idx].user=(void*)1;
			trans[idx].tx_buffer=dmamem[idx];
			ret=spi_device_queue_trans(spi, &trans[idx], portMAX_DELAY);
			assert(ret==ESP_OK);

			idx++;
			if (idx>=NO_SIM_TRANS) idx=0;

			if (inProgress==NO_SIM_TRANS-1) {
				ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
				assert(ret==ESP_OK);
			} else {
				inProgress++;
			}
		}
#ifndef DOUBLE_BUFFER
		xSemaphoreGive(dispDoneSem);
#endif
		while(inProgress) {
			ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
			assert(ret==ESP_OK);
			inProgress--;
		}
	}
}


Video Esp32-Doom quick demo



The order for the board has been sent to the Jlcpcb factory.

Challenge Launched!

All Articles