Page 1 of 1

Call WRITE_RTC_REG in ULP using value from a register

Posted: Fri May 21, 2021 11:15 am
by istokm
I've just now realized, that the C macros used in the ULP programs, like WRITE_RTC_REG, do not accept register values (r0, r1, etc).
Is there a way around that? I've got a register that contains the RTCIO pin number of the pin I'd like to control, but I've got no way to use that value to actually write to the pin's register.

Example:

Code: Select all

move	r0, 14

// Doesn't work
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + r0, 1, 1)
// Works
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 14, 1, 1)
The error is

Code: Select all

Error: syntax error. Input text was r0.

Re: Call WRITE_RTC_REG in ULP using value from a register

Posted: Fri May 21, 2021 12:31 pm
by boarchuz
There's no (reasonable) way to actually modify register write instructions at runtime, but if this is representative of what you need to do then it might be feasible to include *all* possible instructions and jump to the one you need.

eg:

Code: Select all

// RTCIO 14
move r0 14

// Each RTCIO requires 2 instructions so jump to (gpio_set_entry + 2 * rtcio)
move r1 gpio_set_entry
lsh r0 r0 1
add r1 r0 r1
jump r1

gpio_set_entry:
// RTCIO 0
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 0, 1, 1)
jump gpio_set_done
// RTCIO 1
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 1, 1, 1)
jump gpio_set_done
// RTCIO 2
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 2, 1, 1)
jump gpio_set_done
// ...
// RTCIO 17
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 17, 1, 1)
gpio_set_done:

Unreasonably, however, since we're on the topic, I recently made a little demo showing that the ULP can encode and execute its own variable register writes.
It's too inefficient, fragile, limited, etc to be viable but it might be interesting to some and was fun to figure out.
https://github.com/boarchuz/HULP/blob/m ... ain/main.c
In short: Execute a store instruction at a specific PC to generate a store instruction at a specific PC, execute that store instruction to generate a register write instruction, then execute that register write instruction. 6 bits of the original variable can end up in the ultimate register write.

Re: Call WRITE_RTC_REG in ULP using value from a register

Posted: Fri May 21, 2021 12:50 pm
by istokm
That's quite unfortunate.. My first thought was the same as your proposal, but just thinking about it makes me nauseous - even though I'll probably have to go down that route (except that I'll hide it in a macro). I'll pass on your variable register writes, because that does indeed look quite dodgy :D
These seemingly arbitrary limitations is what makes working with the ULP such a pain... I'd love to know if there is acutally a valid underlying reason for this.

Thank you for your input boarchuz, I appreciate it, I'm always a bit hesitant about asking ULP-related questions as I know it's quite a niche topic :D

Re: Call WRITE_RTC_REG in ULP using value from a register

Posted: Mon May 24, 2021 1:14 pm
by istokm
If anyone is ever interested in this... for some reason ...here is the macro I've wrote for it. It's huge and totally impractical, as it is so large, that if you use 2 of them like this: A -> ioset -> ioset -> B, you will not be able to call a jump from A to B, because of Error: relocation out of range. As discussed here, this is because that jump exceeds some arbitrary relative address value..

This is the macro. As you see, due to the nested jumps one has to use the .altmacro keyword, more info about that here.

Code: Select all

// Needed to be able to use LOCAL labels inside of macros
.altmacro

.text

.macro ioset pin state
	LOCAL ioset_state
	LOCAL ioset_done

	move	r0, \pin
	move	r1, \state
	move	r2, ioset_state

	// Multiply pin number by 4 (each RTCIO requires 4 instructions)
	lsh		r0, r0, 2
	// Multiply state by 2 (instruction for setting HIGH is offset by 2 instr.)
	lsh		r1, r1, 1

	// Add together the label address, instruction offset & the state offset
	add		r0, r0, r1
	add		r0, r0, r2

	// Jump to the correct instruction
	jump	r0

ioset_state:
	// RTCIO 0 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 0, 1, 1)
	jump ioset_done
	// RTCIO 0 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 0, 1, 1)
	jump ioset_done

	// RTCIO 1 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 1, 1, 1)
	jump ioset_done
	// RTCIO 1 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 1, 1, 1)
	jump ioset_done

	// RTCIO 2 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 2, 1, 1)
	jump ioset_done
	// RTCIO 2 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 2, 1, 1)
	jump ioset_done

	// RTCIO 3 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 3, 1, 1)
	jump ioset_done
	// RTCIO 3 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 3, 1, 1)
	jump ioset_done

	// RTCIO 4 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 4, 1, 1)
	jump ioset_done
	// RTCIO 4 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 4, 1, 1)
	jump ioset_done

	// RTCIO 5 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 5, 1, 1)
	jump ioset_done
	// RTCIO 5 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 5, 1, 1)
	jump ioset_done

	// RTCIO 6 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 6, 1, 1)
	jump ioset_done
	// RTCIO 6 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 6, 1, 1)
	jump ioset_done

	// RTCIO 7 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 7, 1, 1)
	jump ioset_done
	// RTCIO 7 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 7, 1, 1)
	jump ioset_done

	// RTCIO 8 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 8, 1, 1)
	jump ioset_done
	// RTCIO 8 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 8, 1, 1)
	jump ioset_done

	// RTCIO 9 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 9, 1, 1)
	jump ioset_done
	// RTCIO 9 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 9, 1, 1)
	jump ioset_done

	// RTCIO 10 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 10, 1, 1)
	jump ioset_done
	// RTCIO 10 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 10, 1, 1)
	jump ioset_done

	// RTCIO 11 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 11, 1, 1)
	jump ioset_done
	// RTCIO 11 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 11, 1, 1)
	jump ioset_done

	// RTCIO 12 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 12, 1, 1)
	jump ioset_done
	// RTCIO 12 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 12, 1, 1)
	jump ioset_done

	// RTCIO 13 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 13, 1, 1)
	jump ioset_done
	// RTCIO 13 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 13, 1, 1)
	jump ioset_done

	// RTCIO 14 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 14, 1, 1)
	jump ioset_done
	// RTCIO 14 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 14, 1, 1)
	jump ioset_done

	// RTCIO 15 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 15, 1, 1)
	jump ioset_done
	// RTCIO 15 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 15, 1, 1)
	jump ioset_done

	// RTCIO 16 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 16, 1, 1)
	jump ioset_done
	// RTCIO 16 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 16, 1, 1)
	jump ioset_done

	// RTCIO 17 LOW
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 17, 1, 1)
	jump ioset_done
	// RTCIO 17 HIGH
	WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 17, 1, 1)
	jump ioset_done

ioset_done:
.endm //ioset
From my limited testing on pins 10, 11, 17, it seems to work fine and the rest of the program runs as expected as well.

And the usage looks like this (all are valid):

Code: Select all

// Set RTCIO_17 HIGH
ioset 17 1
// Set RTCIO_4 LOW
ioset 4, 0
// Set RTCIO_14 LOW
move r0, 14
ioset r0 0
// Set RTCIO_4 HIGH
move r0, 4
move r2, 1
ioset r0, r2
Providing the macro with state value other than 0 or 1 is undefined behaviour

Who knows if this is the best way of doing it. Macros of course have readability disadvantages (and will override registers if you aren't careful), but I believe this to be the only good option for my use case.

Re: Call WRITE_RTC_REG in ULP using value from a register

Posted: Thu Jan 05, 2023 10:14 am
by EnifCH
I know that this thread is quite old, but maybe some other people struggle with the same problem and will find my solution to the problem useful...

I was trying to implement a ULP based blinking alarm on one of my projects. As the code is highly configurable at run time, I wanted the RTC GPIO pin used for blinking not to be hard coded, and this way stumbled on the same problem as the originator of this thread, i.e. that register writes need hard coded pin numbers... I was glad to find the solution here with the explicit enumeration of the pins in a kind of instruction array, using a programmed jump to go to the right instruction for the selected pin.

However, given the very limited space for ULP programs (using about 80% of the available space just for the 2*18 register writes and corresponding jumps), I continued to search for some way to avoid the 18 repetition of the REG_WR/JUMP instructions. I first tried to modify the instruction directly from within the main CPU program, by defining a global label and overwriting it in my main sketch. This compiled well, but gave a write protection error at run time, obviously the ULP program memory (.text section) is protected.

But as I can write to the data section, I simply moved the WRITE_RTC_REG and a JUMP instruction into the .data section and gave it a global label. This works! So I can now modify the WRITE_RTC_REG instruction to write to the desired pin at run time.

Here is my little ULP program with the WRITE_RTC_REG moved to the .data section

Code: Select all

// Blinking in ULP mode

#include "soc/soc_ulp.h"     // for WRITE_RTC_REG
#include "soc/rtc_io_reg.h"  // for RTC_GPIO_*


.global entry                // ulp start address used by main core program

.bss

.data
//  blink control: blink<2:OFF  blink>=2:ON - accessible as ulp_blink in main program
   .global blink
blink:
   .long 0
// mson:  blink on-time in ms - accessible as ulp_mson in main program
   .global mson
mson:
   .long 100
// msoff:  blink off-time in ms - accessible as ulp_msoff in main program
   .global msoff
msoff:
   .long 2900

// instruction to set RTC_GPIO pin - accessible as ulp_setpin in main program
   .global setpin
setpin:
  WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + 0, 1, 1)
  jump pinset

// instruction to clear RTC_GPIO pin - accessible as ulp_clearpin in main program
   .global clearpin
clearpin:
  WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + 0, 1, 1)
  jump pincleared

.text
entry:
  jump setpin

pinset:
  move  r3, mson          // get address of mson
  ld  r1, r3, 0           // get value of mson
  move  r2, clearpin      // return address
  jump  delay             // call subroutine

pincleared:
  move r3, blink          // get blink address
  ld   r0, r3, 0             // load blink
  jumpr finish, 2, lt     // halt if blink < 2

  move  r3, msoff         // get address of msoff
  ld  r1, r3, 0               // get value of msoff
  move  r2, setpin        // return address

delay:
  wait  8000               // wait 8000 clock ticks at 8MHz -> 1ms
  sub   r1, r1, 1          // decrement ms count
  jump  r2, eq            // if ms count is zero then return to caller
  jump  delay             // else continue to wait

finish:
  halt

This ULP program uses only 64 bytes program storage, compared to more than 400 in the version with the computed jumps.

In my sketch I use the following sequence to modify the instructions at the setpin and clearpin labels:

Code: Select all

...
extern uint32_t ulp_setpin; // REG_WR instruction to set RTC_GPIO pin
extern uint32_t ulp_clearpin; // REG_WR instruction to clear RTC_GPIO pin
...
uint32_t bit = rtcpin+14;                  // rtcpin is the RTC_GPIO pin (0-17),  14 is the bit position of
ulp_setpin&=0xf003ffff;                    // mask off bits 18-27
ulp_setpin|=(bit<<18)|(bit<<23);    // modify from and to bit to read
ulp_clearpin&=0xf003ffff;                 // mask off bits 18-27
ulp_clearpin|=(bit<<18)|(bit<<23); // modify from and to bit to read
...
For a better understanding of the above, I added an excerpt of the ESP32 technical reference manual page 601 as attachment which shows the bit structure of the REG_WR instruction.

I still wonder, if there is some way to modify the instructions in the .text section directly, i.e. to overcome the write protect error - maybe someone else knows?