My long path to configuring the RMT and PCNT to share GPIO
Posted: Sat Mar 10, 2018 4:32 pm
Background
I am working on a controller for an stepper motor. There are two types of steppers, relative and absolute. A relative stepper moves a certain number of steps from its current position. For example, you might command the relative stepper to move 137 CW steps or possibly move 42 steps CCW. With an absolute stepper, you command the stepper to move to an absolute position, say 45 degrees or 276 degrees.
A relative stepper can be programmed using only the RMT peripheral. The RMT can be programmed to send the required number of pulses, with the correct pulse width, to the stepper. The absolute stepper requires both the RMT peripheral as well as a PCNT peripheral. The RMT device is used to issue a continuous pulse stream to the stepper. The PCNT counts the pulses and stops the RMT pulse transmission when the stepper has moved to the target position. A home sensor is also used to clear the PCNT.
Once I understood how the RMT and PCNT peripherals worked, it was relatively easy to program the ESP32 RMT and PCNT devices. However to get to this point, I had to spend a lot of time with the TRM and the examples in the ESP-IDF.
Integrating the two peripherals together was a bit harder. To do that, I had to connect the RMT output to the PCNT input. I also used a GPIO for the direction output. This output was connected to the stepper controller. It also had to be connected to the PCNT control input. In summary there were two signals that were sent to the stepper controller, a step signal (generated by the RMT) and a direction signal (generated by application logic). Both of these signals also needed to be routed to the PCNT.
The Problem
It would have been very easy to configure the PCNT to use different GPIO for it’s inputs. For example my GPIO layout could have been as follows:
Step signal from RMT: GPIO15
Direction signal: GPIO4
PCNT count input: GPIO16
PCNT control input: GPIO5
Had I done this, I could jumper GPIO15 to GPIO16 and GPIO4 to GPIO5 and I would have been in business! But I did not want to do that. GPIO is a precious resource on micro controllers and I did not want to waste them.
What I wanted was to share the GPIO as follows:
Step signal from RMT: GPIO15
PCNT count input: GPIO15
Direction signal: GPIO4
PCNT control input: GPIO4
Initially this seemed pretty simple so I barged ahead and configured my RMT and PCNT as follows:
Unfortunately, this did not work! So I reversed the order of the code, configuring the PCNT first and the RMT second. That did not work either! At this point, I assumed that the PCNT configuration was conflicting with the RMT configuration, and visa versa. As it turns out, I was right. It also turns out that it was easily fixable. If you’ve stayed with me up to now, keep reading…
The Solution
At that point, I decided I would need to understand more about the RMT, PCNT, and GPIO peripherals including how the registers were set during configuration. I also needed to better understand the driver code in rmt.c and pcnt.c.
I soon reached another stumbling block - I could not understand the GPIO chapter (chapter 4) in the TRM. After reading it, I still couldn’t understand how to set GPIO configuration registers. After explaining my problem on this forum, the ESP engineers quickly rewrote Chapter 4. Outstanding work guys! They did a good job and now I was able to understand how the GPIOs functioned at the register level. If you are having problems with this, make sure you update to the latest version of the TRM.
After doing all this homework, I was finally ready to begin analyzing the relevant registers.
Here are the registers I was interested in monitoring:
GPIO_ENABLE_REG - the GPIO output enable register
GPIO_PIN15_REG - configure interrupt and drive for GPIO15
GPIO_PIN4_REG - configure interrupt and drive for GPIO4
GPIO_FUNC43_IN_SEL_REG - primarily assigns a GPIO to the PCNT signal input
GPIO_FUNC45_IN_SEL_REG - primarily assigns a GPIO to the PCNT control input
GPIO_FUNC15_OUT_SEL_REG - primarily assigns a peripheral to control output for GPIO15. In this case, the RMT output is used to control GPIO15.
IO_MUX_GPIO15_REG - enables GPIO input, IO_MUX function, pull up / down enable for GPIO15
IO_MUX_GPIO4_REG - enables GPIO input, IO_MUX function, pull up / down enable for GPIO15
Here is the code I wrote to print out these registers:
So I called this code after configuring the RMT and the PCNT. I ran two tests. The first configured the RMT before the PCNT. The second configured the PCNT before the RMT. I got different results with each test. Here are my results:
As you can see, the GPIO_PIN15_REG and GPIO_PIN4_REG registers were not changed during any of my tests. Also the FUNC_43_IN_SEL_REG and FUNC_45_IN_SEL_REG were not modified by the RMT. These registers were not causing any problems.
The bad news was that the GPIO_ENABLE_REG, GPIO_FUNC15_OUT_SEL_REG, IO_MUX_GPIO15_REG, and IO_MUX_GPIO4_REG were being set to incorrect values. However the results varied depending on which peripheral was configured first. Here is a summary of the changes:
If the RMT was configured first:
The RMT peripheral used the GPIO_ENABLE_REG to set GPIO15 as an output. The PCNT used this register to set it as an input. It had to be set as an output or the RMT peripheral could not send the pulses to the stepper controller.
The RMT peripheral used the GPIO_FUNC15_OUT_SEL_REG to allow the RMT to control GPIO15. The PCNT disconnected GPIO15 from the RMT peripheral.
The solution was to issue specific instructions to set these registers to their correct value. The code to do this is as follows:
If the PCNT was configured first:
The PCNT peripheral used the IO_MUX_GPIO15_REG to allow it to read GPIO15 (which is the PCNT signal input). The RMT cleared this bit. This prevented the PCNT from counting RMT’s pulses.
The PCNT peripheral used the IO_MUX_GPIO4_REG to it to allow it to read GPIO4 (which is the PCNT control input). The logic that setup GPIO4 as an output cleared this bit. This prevented the PCNT detecting the direction signal to the stepper controller.
The solution was to issue specific instructions to set these registers to their correct value. The code to do this is as follows:
If the PCNT was configured first:
After issuing these instructions, both the RMT and the PCNT peripherals began working correctly, no matter which was configured first! Outstanding!
Here is a debug printout showing the setting before and after the registers were fixed:
While I was only concerned with the RMT and PCNT peripherals, this method of analyzing the register settings should work for most GPIO configuration problems.
I am working on a controller for an stepper motor. There are two types of steppers, relative and absolute. A relative stepper moves a certain number of steps from its current position. For example, you might command the relative stepper to move 137 CW steps or possibly move 42 steps CCW. With an absolute stepper, you command the stepper to move to an absolute position, say 45 degrees or 276 degrees.
A relative stepper can be programmed using only the RMT peripheral. The RMT can be programmed to send the required number of pulses, with the correct pulse width, to the stepper. The absolute stepper requires both the RMT peripheral as well as a PCNT peripheral. The RMT device is used to issue a continuous pulse stream to the stepper. The PCNT counts the pulses and stops the RMT pulse transmission when the stepper has moved to the target position. A home sensor is also used to clear the PCNT.
Once I understood how the RMT and PCNT peripherals worked, it was relatively easy to program the ESP32 RMT and PCNT devices. However to get to this point, I had to spend a lot of time with the TRM and the examples in the ESP-IDF.
Integrating the two peripherals together was a bit harder. To do that, I had to connect the RMT output to the PCNT input. I also used a GPIO for the direction output. This output was connected to the stepper controller. It also had to be connected to the PCNT control input. In summary there were two signals that were sent to the stepper controller, a step signal (generated by the RMT) and a direction signal (generated by application logic). Both of these signals also needed to be routed to the PCNT.
The Problem
It would have been very easy to configure the PCNT to use different GPIO for it’s inputs. For example my GPIO layout could have been as follows:
Step signal from RMT: GPIO15
Direction signal: GPIO4
PCNT count input: GPIO16
PCNT control input: GPIO5
Had I done this, I could jumper GPIO15 to GPIO16 and GPIO4 to GPIO5 and I would have been in business! But I did not want to do that. GPIO is a precious resource on micro controllers and I did not want to waste them.
What I wanted was to share the GPIO as follows:
Step signal from RMT: GPIO15
PCNT count input: GPIO15
Direction signal: GPIO4
PCNT control input: GPIO4
Initially this seemed pretty simple so I barged ahead and configured my RMT and PCNT as follows:
Code: Select all
rmt_config_t rmt_tx[8];
rmt_tx[channel].gpio_num = 15;
pcnt_config_t pcnt_config = {
.pulse_gpio_num = 15,
.ctrl_gpio_num = 4,
.channel = PCNT_CHANNEL_0,
.unit = channel,
};
The Solution
At that point, I decided I would need to understand more about the RMT, PCNT, and GPIO peripherals including how the registers were set during configuration. I also needed to better understand the driver code in rmt.c and pcnt.c.
I soon reached another stumbling block - I could not understand the GPIO chapter (chapter 4) in the TRM. After reading it, I still couldn’t understand how to set GPIO configuration registers. After explaining my problem on this forum, the ESP engineers quickly rewrote Chapter 4. Outstanding work guys! They did a good job and now I was able to understand how the GPIOs functioned at the register level. If you are having problems with this, make sure you update to the latest version of the TRM.
After doing all this homework, I was finally ready to begin analyzing the relevant registers.
Here are the registers I was interested in monitoring:
GPIO_ENABLE_REG - the GPIO output enable register
GPIO_PIN15_REG - configure interrupt and drive for GPIO15
GPIO_PIN4_REG - configure interrupt and drive for GPIO4
GPIO_FUNC43_IN_SEL_REG - primarily assigns a GPIO to the PCNT signal input
GPIO_FUNC45_IN_SEL_REG - primarily assigns a GPIO to the PCNT control input
GPIO_FUNC15_OUT_SEL_REG - primarily assigns a peripheral to control output for GPIO15. In this case, the RMT output is used to control GPIO15.
IO_MUX_GPIO15_REG - enables GPIO input, IO_MUX function, pull up / down enable for GPIO15
IO_MUX_GPIO4_REG - enables GPIO input, IO_MUX function, pull up / down enable for GPIO15
Here is the code I wrote to print out these registers:
Code: Select all
void printGPIORegisters() {
ESP_LOGI(STEPPER_TAG, "ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN")
ESP_LOGI(STEPPER_TAG, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
REG_READ(GPIO_ENABLE_REG), REG_READ(GPIO_PIN15_REG),
REG_READ(GPIO_FUNC43_IN_SEL_CFG_REG), REG_READ(GPIO_FUNC15_OUT_SEL_CFG_REG),
REG_READ(IO_MUX_GPIO15_REG), REG_READ(GPIO_PIN4_REG),
REG_READ(IO_MUX_GPIO4_REG), REG_READ(GPIO_FUNC45_IN_SEL_CFG_REG)
);
}
Code: Select all
********** RMT initialized before PCNT **********
After RMT init
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00038350 0x00000000 0x00000030 0x00000058 0x00002900 0x00000000 0x00002880 0x00000030
After PCNT init
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00030340 0x00000000 0x0000008f 0x00000100 0x00002b00 0x00000000 0x00002a80 0x00000084
********** PCNT initialized before RMT **********
After PCNT init
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00030140 0x00000000 0x0000008f 0x00000100 0x00002b00 0x00000000 0x00002a80 0x00000084
After RMT init
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00038350 0x00000000 0x0000008f 0x00000058 0x00002900 0x00000000 0x00002880 0x00000084
The bad news was that the GPIO_ENABLE_REG, GPIO_FUNC15_OUT_SEL_REG, IO_MUX_GPIO15_REG, and IO_MUX_GPIO4_REG were being set to incorrect values. However the results varied depending on which peripheral was configured first. Here is a summary of the changes:
If the RMT was configured first:
The RMT peripheral used the GPIO_ENABLE_REG to set GPIO15 as an output. The PCNT used this register to set it as an input. It had to be set as an output or the RMT peripheral could not send the pulses to the stepper controller.
The RMT peripheral used the GPIO_FUNC15_OUT_SEL_REG to allow the RMT to control GPIO15. The PCNT disconnected GPIO15 from the RMT peripheral.
The solution was to issue specific instructions to set these registers to their correct value. The code to do this is as follows:
Code: Select all
REG_SET_BIT(GPIO_ENABLE_REG, 32784);
REG_SET_FIELD(GPIO_FUNC15_OUT_SEL_CFG_REG, GPIO_FUNC15_OUT_SEL, RMT_SIG_OUT1_IDX);
The PCNT peripheral used the IO_MUX_GPIO15_REG to allow it to read GPIO15 (which is the PCNT signal input). The RMT cleared this bit. This prevented the PCNT from counting RMT’s pulses.
The PCNT peripheral used the IO_MUX_GPIO4_REG to it to allow it to read GPIO4 (which is the PCNT control input). The logic that setup GPIO4 as an output cleared this bit. This prevented the PCNT detecting the direction signal to the stepper controller.
The solution was to issue specific instructions to set these registers to their correct value. The code to do this is as follows:
If the PCNT was configured first:
Code: Select all
PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[15]);
REG_SET_BIT(IO_MUX_GPIO4_REG, 512);
Here is a debug printout showing the setting before and after the registers were fixed:
Code: Select all
********** RMT initialized before PCNT **********
After RMT init
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00038350 0x00000000 0x00000030 0x00000058 0x00002900 0x00000000 0x00002880 0x00000030
After PCNT init
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00030340 0x00000000 0x0000008f 0x00000100 0x00002b00 0x00000000 0x00002a80 0x00000084
After Fixup
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00038350 0x00000000 0x0000008f 0x00000058 0x00002b00 0x00000000 0x00002a80 0x00000084
********** PCNT initialized before RMT **********
After PCNT init
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00030140 0x00000000 0x0000008f 0x00000100 0x00002b00 0x00000000 0x00002a80 0x00000084
After RMT init
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00038350 0x00000000 0x0000008f 0x00000058 0x00002900 0x00000000 0x00002880 0x00000084
After Fixup
ENA_REG PIN15_REG FUNC43_IN FUNC15_OUT IO_MUX_15 PIN4_REG IOMUX_4 FUNC45_IN
0x00038350 0x00000000 0x0000008f 0x00000058 0x00002b00 0x00000000 0x00002a80 0x00000084