Direct access to UART1 TX FIFO

XSRF32
Posts: 4
Joined: Sun May 24, 2020 6:23 pm

Direct access to UART1 TX FIFO

Postby XSRF32 » Sun May 24, 2020 7:09 pm

Hey,
on the ESP8266 (Arduino Framework) it was pretty easy to access the UART registers. All relevant registers were exposed in the esp8266_peri.h I used UART1 to generate a precise timed pulse using:

  1.     // Initial Setup
  2.     pinMode(2, SPECIAL); // Set GPIO2 as UART1 TX
  3.     U1S |= 0x01 << USTXC; // Set UART1 TX FIFO length
  4.     U1D = 10*duration; // Set pulse duration using divider
  5.    
  6.     // To generate one pulse whenever I want without blocking CPU
  7.     U1F = 0x80; // Pulse

On the ESP32 (Arduino Framework) it is pretty hard to figure out how to access the relevant registers... At least for me. The HardwareSerial.cpp uses a lot of internal APIs.
So far I found out how to set the pin matrix to route the UART1 TX to GPIO2 and set the clock divider:

  1.     // Initial Setup
  2.     Serial1.begin(9600); // Needed for Serial1.write to work (want to get rid of)
  3.     pinMode(2, OUTPUT);
  4.     pinMatrixOutAttach(2, U1TXD_OUT_IDX, false, false);
  5.     WRITE_PERI_REG(UART_CLKDIV_REG(1), 10*duration); // requires <soc/uart_reg.h>
  6.  
  7.     // To generate one pulse
  8.     Serial1.write(0x80);

But I was not able to find the FIFO to write to... Using Serial1.write() blocks the CPU.
Can someone help me out here? Thx!

XSRF32
Posts: 4
Joined: Sun May 24, 2020 6:23 pm

Re: Direct access to UART1 TX FIFO

Postby XSRF32 » Mon May 25, 2020 8:09 pm

Okay, guess I somehow missed it the last weekend, but now I found it ;)

  1.     // Initial Setup
  2.     DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_UART1_CLK_EN); // Enable Clock for UART1
  3.     DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_UART1_RST); // Enable UART1
  4.     pinMode(2, OUTPUT);
  5.     pinMatrixOutAttach(2, UART_TXD_IDX(1), false, false); // UART_TXD_IDX(u) / U1TXD_OUT_IDX
  6.     WRITE_PERI_REG(UART_CLKDIV_REG(1), 10*duration); // requires <soc/uart_reg.h>
  7.  
  8.     // To generate one pulse
  9.     WRITE_PERI_REG(UART_FIFO_REG(1), 0x80);

For those who are curious how this generates a pulse of a fixed duration - in this case a LOW pulse:
The UART is usually HIGH on idle.
When it sends a character/byte, it sends one start-bit first, which is LOW, then the usually 8 bits data, and then a STOP bit (HIGH).
By sending 0x80, it generates 8 consecutive LOW bits (1 Start, 7 data).
The length of each bit is derived from the base clock of 80MHz and the clock divider. A division by 10 results in an 8 MHz clock or 1µs duration for sending the 8 LOW bits.

XSRF32
Posts: 4
Joined: Sun May 24, 2020 6:23 pm

Re: Direct access to UART1 TX FIFO

Postby XSRF32 » Tue May 26, 2020 9:42 pm

Okay, I now ran into another issue ... The code works fine if there is a long pause between pulses.
For a 5µs pulse, issues arise when I try to send a pulse within the next ~160µs.
When I try to create a pulse every 100µs, they are batched up and sent every ~160µs.

Here is the code:
  1. void setup() {
  2.   pinMode(4, OUTPUT);
  3.   DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_UART1_CLK_EN); // Enable Clock
  4.   DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_UART1_RST); // Enable UART1
  5.   pinMode(2, OUTPUT);
  6.   pinMatrixOutAttach(2, UART_TXD_IDX(1), false, false); // UART_TXD_IDX(u) / U1TXD_OUT_IDX
  7.   WRITE_PERI_REG(UART_CLKDIV_REG(1), 10*5); // 5µs pulse
  8. }
  9.  
  10. void loop() {
  11.   digitalWrite(4,LOW);
  12.   WRITE_PERI_REG(UART_FIFO_REG(1), 0x80); // pulse
  13.   digitalWrite(4,HIGH);
  14.   while ( (READ_PERI_REG(UART_STATUS_REG(1)) & UART_TXD) < 1 )
  15.   {
  16.     digitalWrite(4,LOW); // Toggle while TX is LOW
  17.     digitalWrite(4,HIGH);
  18.   }
  19.  
  20.   //delayMicroseconds(180); // Works fine with 5µs
  21.   delayMicroseconds(100); // Out of sync with 5µs pulse, fine with 2µs pulses
  22. }
180µs pause (what I want):
2020-05-26 23_10_33-Window.png
2020-05-26 23_10_33-Window.png (12.88 KiB) Viewed 3761 times
100µs pause (out of sync / batched):
2020-05-26 23_13_01-Window.png
2020-05-26 23_13_01-Window.png (13.96 KiB) Viewed 3761 times

When I reduce the pulse length to 2µs, both work... So I guess the gap is always ~32 pulses/characters ...

How do I reduce this?

ESP_Sprite
Posts: 9730
Joined: Thu Nov 26, 2015 4:08 am

Re: Direct access to UART1 TX FIFO

Postby ESP_Sprite » Wed May 27, 2020 8:48 am

Not sure, but perhaps UART_TX_IDLE_NUM has something to do with it? You may want to try setting that to 0.

While you're reading the TRM anyway, can I advise you to read into the RMT peripheral? It's actually designed to generate arbitrary waveforms and is way better at it than the UART will ever be.

XSRF32
Posts: 4
Joined: Sun May 24, 2020 6:23 pm

Re: Direct access to UART1 TX FIFO

Postby XSRF32 » Wed May 27, 2020 2:13 pm

Thx, you're right. This is explained in the datasheet on "13.3.5 UART Data Frame" ... UART_TX_IDLE_NUM explains the gap.
  1.         CLEAR_PERI_REG_MASK( UART_IDLE_CONF_REG(1), UART_TX_BRK_NUM_M );
  2.         CLEAR_PERI_REG_MASK( UART_IDLE_CONF_REG(1), UART_TX_IDLE_NUM_M );
solved the issue!

I went with the UART1 because I used it also on the ESP8266 and it works just fine for me :)

My requirements are:
- Trigger a pulse of a given length (~1-500µs) without blocking the CPU and as fast as possible (usually from within an ISR)
- Ability to check if the pulse is done whenever I want
- let me invert the pulse
- nice to have: possibility to specify the IO

The ESP8266 has a fixed IO for the UART1 TX and also has an issue with the Status-Register actually not reflecting the TX level as it should, but I fixed that with loopback mode.
So, triggering the pulse and reading the state are actually both only one write/read instruction. Setting the length is also just one write.

But thx for pointing me to the RMT, I'll have a look at it.

Who is online

Users browsing this forum: Bing [Bot] and 46 guests