hideakitai / ESP32DMASPI

SPI library for ESP32 which use DMA buffer to send/receive transactions
MIT License
181 stars 37 forks source link

Getting Raw ADC Data from ATM90E36 via DMA #54

Closed alireza7313 closed 3 months ago

alireza7313 commented 4 months ago

Hello I am working on a project using ATM90E36A chip to analyze power components of low voltage line (220 Volts). I am having trouble getting raw ADC data via DMA when connecting ATM90E36A to NODE MCU ESP32 module.

I have followed the ATM90E36A datasheet and application note. I initialized the Slave DMA on the ESP32 module, asserted the DMA_CTRL pin, and set the DMA control register to 0xFE84. However, I always receive 255 as the raw ADC data for all seven channels on the M90E36A. I am coding in Arduino IDE 2.3.2 and using the ESP32DMASPISLAVE library and Blocking big transfer() one by one method for DMA data transactions.

Below is a screenshot of the serial port showing the raw ADC data values for each channel. The data is separated into 8-bit MSB and 8-bit LSB, receiving 14 bytes per transaction. I have also attached my code file, atm90e36_DMA.ino.

I would appreciate any guidance you can provide. ADC data screenshot atm90e36_DMA.zip

hideakitai commented 4 months ago

You don't need to use DMA for the transfer less than 64 bytes. Please try https://github.com/hideakitai/ESP32SPISlave/blob/main/examples/transfer_one_by_one/transfer_one_by_one_slave/transfer_one_by_one_slave.ino

alireza7313 commented 4 months ago

Hello and thank you for your response. Based on the provided link, I have corrected my code and managed to read the raw ADC data via SPI protocol. However, I am now facing a new issue. When switching from slave mode to master mode and then back to slave mode, no data is received, and the result of the received bytes is zero. The relevant code is written in two methods: blocking with the function slave.transfer() and non-blocking with the functions slave.trigger() and slave.queue(). The serial port screenshot and the relevant code for the blocking method are attached below. atm90e36_block.zip blocking one by one screen shot 1 blocking one by one screen shot 2

Additionally, the serial port screenshot and the relevant code for the non-blocking method are attached below. atm90e36_non_block.zip non blocking screenshot 1 non blocking screenshot 2

How can the mode of a single SPI (e.g., VSPI) port be switched alternately between slave mode and master mode on an ESP32? I appreciate your help.

hideakitai commented 4 months ago

Why do you need to switch to master/slave mode? You can send data to the master. I haven't heard the device requires switching master/slave mode.

alireza7313 commented 4 months ago

According to section four of the ATM90E36 datasheet, accessing raw ADC data requires switching the ATM90E36 between master and slave modes. In slave mode, this device sends power network parameters such as RMS current and voltage values, power, energy, and total harmonic distortion to the ESP32. In master mode, it dumps raw ADC data from 7 ADC channels at an 8 kHz rate over the SPI line, allowing the ESP32 to receive this data in slave mode. Can the ESP32 switch between slave and master modes with ESP32SPISlave library? Atmel-M90E36A-Datasheet.pdf

hideakitai commented 4 months ago

Ah… weird…

If you call master/slave.end() and slave/master.begin() correctly, maybe it works. Can you confirm it? Please give me a minimal code to reproduce the problem if you have already done so.

alireza7313 commented 4 months ago

Hi dear …..

Thank you for your prompt response,

Two sample code files have been prepared as attached below. In the first file (atm90e36_with_slave_begin_end_in_loop.ino), the slave.begin/end() command is used within the loop(). When executing this file, after the second run of the slave.begin () command, we encounter this error message : ”SPI3 already claimed by spi slave,” in the serial port, as shown in the screenshot below. SPI claimed 2

In the second file (atm90e36_without_slave_begin_end_in_loop.ino), the slave.begin/end() command is not used within the loop(). When running this file, code execution stops at the line 188. In order to prevent the code failure, the fourth argument of the slave.transfer (NULL, rx_buf, BUFFER_SIZE, 5000) function which determines the time_out, is chosen to be 5000(ms). Therefore, after 5000ms, subsequent lines are executed. But, zero bytes are received after the first run of the loop(). In other words, when the loop() is executed for the first time, as slave mode has been activated by slave.begin() command, the received data is correct. However, from the first execution of the loop onwards (i.e., the second to the last iterations), the received data is zero, which means that, slave.transfer (NULL, rx_buf, BUFFER_SIZE, 5000) don’t work correctly.

atm90e36_with_slave_begin_end_in_loop.zip atm90e36_without_slave_begin_end_in_loop.zip

Thank you in advance

Please let me know if you need any further explanation!

hideakitai commented 4 months ago

I briefly reviewed the datasheet and code, but some points needed clarification.

The SPI bus should be the same for the ATM90E36 in Slave Mode (SPI used by ATM90E36_mine.cpp) and for the ATM90E36 in Master Mode (ESP32DMASPISlave). If this is the case, you should SPI.end() the ATM90E36 SPI first and then do slave.begin(), but I couldn't find that (I could find SPI.end() -> eic1.begin() and slave.end() -> slave.begin(), but that makes no sense). Why is slave.begin() performed after slave.end()?

Also, SPI.begin() is called (HSPI) in the ATM90E36_mine.cpp, but you also call slave.begin(VSPI). As you can see from the pin assignments in the README, the pins used for these APIs are different. What kind of wiring are you using?

https://github.com/hideakitai/ESP32DMASPI?tab=readme-ov-file#spi-buses-and-spi-pins

Furthermore, SD also uses the same SPI that ATM90E36_mine.cpp uses (HSPI). Are both CSs driven or communicating at the same time?

Last, I suggest you try a minimal code without any SD, WiFi, etc., and simply use only SPI Master and SPI Slave to see if it works. It is easier to determine the cause. Also, this library is something I maintain in my spare time, so I can save time reading code ;)

alireza7313 commented 3 months ago

Thank you for your attention.

Since the microcontroller is in Slave mode at the beginning of the loop() function, to switch the microcontroller to Master mode (to receive data from ATM90), we need to first execute the SPI.end() command to exit Slave mode, and then execute the eic.begin() command to enter Master mode. This procedure was obtained through trial and error; because without using SPI.end(), it is not possible to establish communication between the microcontroller in Master mode and ATM90 in slave mode, and the program stops when executing the SPI.transfer() command. Also, the eic.begin() function actually performs the SPI.begin() operation in addition to the initial setup of atm90e36. Therefore, after SPI.end(), it is necessary to execute eic.begin().

However, I am not very sure about slave.end() and slave.begin(VSPI). Like what we did in the previous stge, these two commands are used to switch the ESP32 from Master to Slave mode, which encounters the error: “SPI3 already claimed by spi slave” and then ESP32 resets.

The SPI interface wiring between ESP32 and atm90e36 is as follows: MOSI >> 23 MISO >> 19 SCLK >> 18 CS >> 5

However, it should be noted that without executing the slave.end() and slave.begin(VSPI) commands, reading data from the SPI port of the atm90e36 IC where the ESP32 is in Master mode is correctly performed in each loop, but no bytes are received by the following command from the ESP32 in Slave mode: const size_t received_bytes = slave.transfer(NULL, rx_buf, BUFFER_SIZE, 5000);

In the two new files sent below (with the same previous names), the WiFi and SD sections have been removed to simplify the code. Please review them if possible. Now that is the question. Is it possible to switch the ESP32 between Master and Slave modes and vice versa?

For the reminder, in the file named "atm90e36_with_slave_begin_end_in_loop.ino" , we encounter this error: “SPI3 already claimed by spi slave”

But in the file named "atm90e36_without_slave_begin_end_in_loop.ino" , the program is stuck at the following line:

const size_t received_bytes = slave.transfer(NULL, rx_buf, BUFFER_SIZE, 5000);

atm90e36_with_slave_begin_end_in_loop.zip atm90e36_without_slave_begin_end_in_loop.zip

Thank you for your guidance.

hideakitai commented 3 months ago

At a glance, your setup() calls as follows, but I couldn't understand what you'd like to do... eic1.begin() starts spi master in hspi and slave.begin() starts spi slave in vspi. So you are starting both master and slave in different spi bus/pins.

  eic1.begin(5, 389, 21, 33000, 8500, 8500, 8500, 8500);

  // ... 

  slave.begin(VSPI);  // default: HSPI (please refer README for pin assignments)`
hideakitai commented 3 months ago

Now that is the question. Is it possible to switch the ESP32 between Master and Slave modes and vice versa?

Yes, maybe. I think it is possible as far as using ESP32DMASPI master and slave, not sure if it is possible when using the SPI library and not planning to support it. (I'm using cleanup sequence correctly with esp-idf spi APIs, so I can't do anymore if it doesn't work ESP-IDF Programming Guide / API Reference / SPI Master Driver ESP-IDF Programming Guide / API Reference / SPI Slave Driver)

When switching between master and slave, please include a delay to ensure each terminates (try 1 second or so first).

master.begin();
...
master.end();
delay(1000);
slave.begin();
...
slave.end();
delay(1000);
master.begin();
...
hideakitai commented 3 months ago

I added a feature in v0.6.1, waiting for the internal task to terminate when you call end(). This will help you not inserting delay() after end().

alireza7313 commented 3 months ago

Hello and thank you for your response.

You previously suggested using the following code, but the ESP32DMASPISlave library has now been updated. Can the end() function be replaced in the same way for the ESP32SPISlave library? Or, if possible, could you please add this feature to the ESP32SPISlave library and provide a new version? https://github.com/hideakitai/ESP32SPISlave/blob/main/examples/transfer_one_by_one/transfer_one_by_one_slave/transfer_one_by_one_slave.ino

Thank you.

hideakitai commented 3 months ago

You can use following code to begin/end master/slave without delay()

master.begin();
...
master.end();
// no delay()
slave.begin();
...
slave.end();
// no delay()
master.begin();
...
alireza7313 commented 3 months ago

I know this, but currently, version 0.6.0 of the ESP32SPISlave library is available. According to this comment (https://github.com/hideakitai/ESP32DMASPI/issues/54#issuecomment-2119132820) , I am not using DMA and have written the code based on the ESP32SPISlave library. So, can I replace the end() function in the ESP32DMASPI (v0.6.1) library with the same function in the ESP32SPISlave (v0.6.0) library? Or, if possible, could you please add this end() feature to the ESP32SPISlave library and release version 0.6.1?

hideakitai commented 3 months ago

I understood, released ESP32SPISlave v0.6.1 https://github.com/hideakitai/ESP32SPISlave/releases/tag/v0.6.1

alireza7313 commented 3 months ago

Hello and thank you, I tested version 0.6.1 of the ESP32SPISlave library, but the error ‘SPI3 already claimed by spi slave’ still exists. I reviewed the ESP32SPISlave library again. As I mentioned before, when changing the ESP32 mode from master to slave, we encounter the following error and the ESP32 resets. SPI claimed 2

Regarding the part ‘Stack canary watchpoint triggered (spi_slavetask)’, I researched and changed the parameter static constexpr int SPI_SLAVE_TASK_STACK_SIZE = 1024 2; to static constexpr int SPI_SLAVE_TASK_STACK_SIZE = 1024 4;

Now I am faced with the following error: host already in use

From the phrase ‘spi_slave: spi_slave_initialize(131): host already in use’, it can be understood that this error is related to lines 118 and 119 of the file ESP32SPISlave.h, which are as below:

117=> // initialize spi slave 118=> esp_err_t err = spi_slave_initialize(ctx->host, &ctx->bus_cfg, &ctx->if_cfg, SPI_DMA_DISABLED); 119=> assert(err == ESP_OK);

For this reason, I commented out line 119 as follows. 117=> // initialize spi slave 118=> esp_err_t err = spi_slave_initialize(ctx->host, &ctx->bus_cfg, &ctx->if_cfg, SPI_DMA_DISABLED); 119=> // assert(err == ESP_OK). Under these conditions, lines 118 to 135 of the file ‘atm90e36_with_slave_begin_end_in_loop.ino’ were tested in different states: State 1- ESP32 is only in SPIslave mode, but the commands Slave.end(); and then Slave.begin(VSPI); are executed.

 119=> slave.end();

 121=> Serial.println("slave end")
 124=> /*SPI.end();   
 127=> eic1.begin(5, 389, 21, 33000, 8500, 8500, 8500, 8500);
 128=> Serial.println(String(eic1.GetValueRegister(DMACtrl)));
 129=>Serial.println("DMACtrl register read");*/

 132=> slave.begin(VSPI);

In this state, ESP32 does not reset. While like before, the error “SPI3 already claimed by spi slave” is printed on the serial port (as shown in the figure below), but the program does not hang. host already in use 2

State 2 - ESP32 is first in SPIslave mode and then with the following command eic1.begin(5, 389, 21, 33000, 8500, 8500, 8500, 8500); I intend to set up communication between esp32 in master mode and atm90e36 in slave mode:

 119=> slave.end();

 121=> Serial.println("slave end")
 124=> //SPI.end();   
 127=> eic1.begin(5, 389, 21, 33000, 8500, 8500, 8500, 8500);
 128=> /*Serial.println(String(eic1.GetValueRegister(DMACtrl)));
 129=>Serial.println("DMACtrl register read");*/

 132=> slave.begin(VSPI);

Now I expect ESP32 to exit SPIslave mode with the command slave.end(). But since the program hangs on the command “eic1.begin(5, 389, 21, 33000, 8500, 8500, 8500, 8500);” it can be concluded that slave.end(); does not properly exit ESP32 from SPIslave mode.

State 3- ESP32 is first in SPIslave mode and by placing the command SPI.end() between slave.end() and slave.begin(VSPI) I intend to take ESP32 to SPImaster mode:

 119=> slave.end();

 121=> Serial.println("slave end")
 124=> SPI.end();   
 127=> eic1.begin(5, 389, 21, 33000, 8500, 8500, 8500, 8500);
 128=> Serial.println(String(eic1.GetValueRegister(DMACtrl)));
 129=>Serial.println("DMACtrl register read");

 132=> slave.begin(VSPI);

The serial port output (red box in the figure below) shows that ESP32 has successfully gone from SPIslave mode to SPImaster mode and read the desired data. reading data in master State 4: the ESP32 initially enters SPIslave mode, then switches to SPImaster mode and correctly reads the desired data. Now, I intend to read data from atm90e36 in the second iteration of the loop(), after re-executing slave.begin(VSPI), but the program gets stuck at the line below, which probably indicates that the ESP32 has not switched back from SPImaster to SPIslave mode. const std::vector received_bytes = slave.wait(); wait in slave_transfer With this in mind, it is possible that the issue stems from lines 118 and 119 of the ESP32SPISlave.h file. I am thoroughly reviewing this file to perhaps resolve the problem with a series of changes. Please share with me any solutions that come to mind.

hideakitai commented 3 months ago

Thanks for the investigation. Fixed in v0.6.3 and confirmed that slave.end() -> slave.begin(VSPI), slave.end() -> SPI.begin(VSPI) and SPI.end() -> slave.begin(VSPI) all worked correctly.

hideakitai commented 3 months ago

If you still need help, please reopen this issue