Skip to main content
Visitor II
June 6, 2025
Question

control of 2 DAC and ADC converters, DAC81402 et ADS8691 with the STM32G431CBU

  • June 6, 2025
  • 8 replies
  • 752 views

Hello at all

To do this, I use STM32CubeIDE and STMCubeMX to configure my I/O interfaces and serial buses, including two SPI buses to control my converters.
Only the hardware configuration of an SPI bus is limited to between 8 and 16 data buses, whereas my converters operate with between 24 and 32 bits of data...

However, in hardware SPI, my sync (CS) goes back after an 8- to 16-bit frame, whereas I need it to remain at 0 to meet the TI requirement.

So I tried to manage the sync pin in IO mode, but it still goes back...

I tried using DMA, but the CS signal delay I'm managing is too long before the start of my frame and ends long after it; I must be at about sixty microseconds, too long.

Note that I'm running my MCU at 24 MHz and I want to be able to retrieve my converted data in about 10 µS.

Currently, I'm using SPI bit bang, or I've configured my SPI port as IO, and I'm retrieving my values ​​in 212 µS, which is too long...

So, should I try assembler?...

Do you have any advice on this? A recommendation that could guide me to the best solution.

Thank you for your invaluable help.

Have a good day everyone.

    This topic has been closed for replies.

    8 replies

    Explorer
    June 6, 2025

    Use I2S, 24 or 32-bits with DMA

    LIFER.1Author
    Visitor II
    June 7, 2025

    Thanks for your reply, it's not I2S but SPI... and I've already done DMA and the exchange times were very long too.
    By managing the CS (Sync) independently, it dropped a few dozen μs before the exchanges and a few dozen after...

    Explorer
    June 7, 2025

    Than switch to I2S.

    I have successfully interface dac80501 & ad5689 (both 16-bit plus 8-bit control, total 24-bits) to F446.

     

    Super User
    June 7, 2025

    @LIFER.1 wrote:

    the hardware configuration of an SPI bus is limited to between 8 and 16 data buses (sic?), whereas my converters operate with between 24 and 32 bits of data....


    Did you mean, "between 8 and 16 data bits" ?

    So 24 bits is just 8 bits three times;

    32 bits is either 16 bits twice, or 8 bits four times.

    No?

    LIFER.1Author
    Visitor II
    June 7, 2025

    Yes, exactly, and during this time, the CS (or sync) signal must be low and not high before the 24- or 32-bit frame passes...
    Which I can't do with the MCU's hardware SPI.

    Super User
    June 7, 2025

    You certainly should be able to.

    Please show your code.

    How to insert source code

    LIFER.1Author
    Visitor II
    June 7, 2025

    hi 

    In fact, in bit bang, I get this:

    20250607_165114.jpg

     

    With this code :

    void SPI_Write32_Manual(uint32_t data) {
     for (int i = 31; i >= 0; i--) {
     if (data & (1UL << i)) SDO_HIGH();
     else SDO_LOW();
    
     SCK_HIGH(); // Front montant
     // __NOP(); __NOP(); // Très court délai (~80-100ns)
     SCK_LOW(); // Front descendant
     }
    
     SCK_LOW();
    }
    
    
    
    
    
    
    
    
    void ADS8691_WriteRegister(uint8_t adc_num, uint8_t address, uint16_t data) {
     uint32_t cmd = 0x40000000
     | ((uint32_t)(address & 0x3F) << 16)
     | (uint32_t)(data & 0xFFFF);
    
     if (adc_num == 1) SYNC1_LOW();
     else SYNC2_LOW();
    
     SPI_Write32_Manual(cmd);
    
     if (adc_num == 1) {
     SYNC1_HIGH();
     SCK_LOW(); // <== ajout ici
     } else {
     SYNC2_HIGH();
     SCK_LOW(); // <== ajout ici
     }
    
     // CLK_DELAY_US(1);
    }
    
    
    void ADS8691_Init(void) {
     // INPUT RANGE register (0x02) :
     // Code 0x0003 = ±10.24V range (bipolaire, full-scale)
     ADS8691_WriteRegister(1, 0x02, 0x0003); // ADC1
     ADS8691_WriteRegister(2, 0x02, 0x0003); // ADC2
    }
    
    
    
    uint16_t SPI_Read16_Manual(void) {
     uint16_t val = 0;
    
     for (int i = 15; i >= 0; i--) {
     SCK_HIGH();
     //CLK_DELAY_US(1);
     if (READ_SDI()) val |= (1 << i);
     SCK_LOW();
     // CLK_DELAY_US(1);
     }
    
     return val;
    }
    
    
    uint32_t SPI_Read24_Manual(void) {
     uint32_t val = 0;
    
     for (int i = 23; i >= 0; i--) {
     SCK_HIGH();
     //CLK_DELAY_US(1);
     if (READ_SDI()) val |= (1 << i);
     SCK_LOW();
     //CLK_DELAY_US(1);
     }
    
     return val;
    }
    
    
    float ADS8691_ReadVoltageFast(uint8_t adc_num, float offset_correction) {
     // 1. Déclenche la conversion
     if (adc_num == 1) SYNC1_LOW();
     else SYNC2_LOW();
    
     SPI_Write32_Manual(0xC0000000); // NOP
    
     if (adc_num == 1) {
     SYNC1_HIGH();
     SCK_LOW();
     } else {
     SYNC2_HIGH();
     SCK_LOW();
     }
    
     // 2. Lecture du résultat précédent
     if (adc_num == 1) SYNC1_LOW();
     else SYNC2_LOW();
    
     SPI_Write32_Manual(0xC0000000); // NOP
    
     if (adc_num == 1) {
     SYNC1_HIGH();
     SCK_LOW();
     } else {
     SYNC2_HIGH();
     SCK_LOW();
     }
    
     // 3. Récupération des 24 bits
     if (adc_num == 1) SYNC1_LOW();
     else SYNC2_LOW();
    
     uint32_t raw24 = SPI_Read24_Manual();
    
     if (adc_num == 1) {
     SYNC1_HIGH();
     SCK_LOW();
     } else {
     SYNC2_HIGH();
     SCK_LOW();
     }
    
     // Traitement : 18 bits signés dans les 24 bits
     int32_t signed_code = ((int32_t)(raw24 << 8)) >> 14;
    
     float voltage = signed_code * (10.24f / 131072.0f); // 2^17 = 131072
     return voltage + offset_correction;
     //return ADS8691_CodeToVoltage_Calibrated(signed_code, offset_correction);
    
    }
    
    
    
    
    /*float ADS8691_CodeToVoltage_Calibrated(uint16_t code, float offset_correction)
    {
     float voltage = ((float)code - 32768.0f) * (10.24f / 32768.0f);
     return voltage + offset_correction;
    }*/
    
    float ADS8691_CodeToVoltage_Calibrated(uint16_t code, float offset_correction)
    {
     float adc_voltage = ((float)code - 32768.0f) * (10.24f / 32768.0f);
     // Ajustement linéaire calibré à partir de mesures
     return (-1.1984f * adc_voltage - 8.42f)+offset_correction;
    }

     

    about 200us ...

    Graduate II
    June 7, 2025

    - Consider using clock with higher frequency.
    - You can use HW SPI (not bit bang) in simple polling mode and manage CS manualy in IO mode. 
    - If you need speed or more efficient code consider using LL API or "bare metal" approach instead HAL
    - You can also use Timer to generate DMA requests to start SPI transfers and timer hardware output to generate CS signal

    LIFER.1Author
    Visitor II
    June 7, 2025
    void SPI_Write32_Manual(uint32_t data) {
     for (int i = 31; i >= 0; i--) {
     if (data & (1UL << i)) SDO_HIGH();
     else SDO_LOW();
    
     SCK_HIGH(); // Front montant
     // __NOP(); __NOP(); // Très court délai (~80-100ns)
     SCK_LOW(); // Front descendant
     }
    
     SCK_LOW();
    }
    
    
    
    
    
    
    
    
    void ADS8691_WriteRegister(uint8_t adc_num, uint8_t address, uint16_t data) {
     uint32_t cmd = 0x40000000
     | ((uint32_t)(address & 0x3F) << 16)
     | (uint32_t)(data & 0xFFFF);
    
     if (adc_num == 1) SYNC1_LOW();
     else SYNC2_LOW();
    
     SPI_Write32_Manual(cmd);
    
     if (adc_num == 1) {
     SYNC1_HIGH();
     SCK_LOW(); // <== ajout ici
     } else {
     SYNC2_HIGH();
     SCK_LOW(); // <== ajout ici
     }
    
     // CLK_DELAY_US(1);
    }
    
    
    void ADS8691_Init(void) {
     // INPUT RANGE register (0x02) :
     // Code 0x0003 = ±10.24V range (bipolaire, full-scale)
     ADS8691_WriteRegister(1, 0x02, 0x0003); // ADC1
     ADS8691_WriteRegister(2, 0x02, 0x0003); // ADC2
    }
    
    
    
    uint16_t SPI_Read16_Manual(void) {
     uint16_t val = 0;
    
     for (int i = 15; i >= 0; i--) {
     SCK_HIGH();
     //CLK_DELAY_US(1);
     if (READ_SDI()) val |= (1 << i);
     SCK_LOW();
     // CLK_DELAY_US(1);
     }
    
     return val;
    }
    
    
    uint32_t SPI_Read24_Manual(void) {
     uint32_t val = 0;
    
     for (int i = 23; i >= 0; i--) {
     SCK_HIGH();
     //CLK_DELAY_US(1);
     if (READ_SDI()) val |= (1 << i);
     SCK_LOW();
     //CLK_DELAY_US(1);
     }
    
     return val;
    }
    
    
    float ADS8691_ReadVoltageFast(uint8_t adc_num, float offset_correction) {
     // 1. Déclenche la conversion
     if (adc_num == 1) SYNC1_LOW();
     else SYNC2_LOW();
    
     SPI_Write32_Manual(0xC0000000); // NOP
    
     if (adc_num == 1) {
     SYNC1_HIGH();
     SCK_LOW();
     } else {
     SYNC2_HIGH();
     SCK_LOW();
     }
    
     // 2. Lecture du résultat précédent
     if (adc_num == 1) SYNC1_LOW();
     else SYNC2_LOW();
    
     SPI_Write32_Manual(0xC0000000); // NOP
    
     if (adc_num == 1) {
     SYNC1_HIGH();
     SCK_LOW();
     } else {
     SYNC2_HIGH();
     SCK_LOW();
     }
    
     // 3. Récupération des 24 bits
     if (adc_num == 1) SYNC1_LOW();
     else SYNC2_LOW();
    
     uint32_t raw24 = SPI_Read24_Manual();
    
     if (adc_num == 1) {
     SYNC1_HIGH();
     SCK_LOW();
     } else {
     SYNC2_HIGH();
     SCK_LOW();
     }
    
     // Traitement : 18 bits signés dans les 24 bits
     int32_t signed_code = ((int32_t)(raw24 << 8)) >> 14;
    
     float voltage = signed_code * (10.24f / 131072.0f); // 2^17 = 131072
     return voltage + offset_correction;
     //return ADS8691_CodeToVoltage_Calibrated(signed_code, offset_correction);
    
    }
    
    
    
    
    /*float ADS8691_CodeToVoltage_Calibrated(uint16_t code, float offset_correction)
    {
     float voltage = ((float)code - 32768.0f) * (10.24f / 32768.0f);
     return voltage + offset_correction;
    }*/
    
    float ADS8691_CodeToVoltage_Calibrated(uint16_t code, float offset_correction)
    {
     float adc_voltage = ((float)code - 32768.0f) * (10.24f / 32768.0f);
     // Ajustement linéaire calibré à partir de mesures
     return (-1.1984f * adc_voltage - 8.42f)+offset_correction;
    }
    LIFER.1Author
    Visitor II
    June 7, 2025

    In fact, with bit bang, I get this:

    with Hardware SPI, i'm 54us, ... 

     

    20250607_173244.jpg

     

    I'm at a 54us frame rate, much better, but still not there. I configured the STM32CUBEMX to have an spi2 port with the CS (sync) pin as IO, but it takes a long time to respond...
    Furthermore, my frames aren't close enough together; I have a latency between them that I can't reduce.

    float ADS8691_ReadVoltageFast(uint8_t adc_num, float offset_correction)
    {
     GPIO_TypeDef* sync_port;
     uint16_t sync_pin;
    
     if (adc_num == 1) {
     sync_port = ADC1_SYNC_GPIO_Port;
     sync_pin = ADC1_SYNC_Pin;
     } else {
     sync_port = ADC2_SYNC_GPIO_Port;
     sync_pin = ADC2_SYNC_Pin;
     }
    
     uint32_t nop = __REV(0x00000000); // NOP command (32 bits)
     uint8_t rx_buf[4];
    
     // SYNC LOW
     sync_port->BSRR = (uint32_t)sync_pin << 16;
    
     // Envoyer NOP et recevoir 4 octets
     HAL_SPI_TransmitReceive(&hspi2, (uint8_t*)&nop, rx_buf, 4, HAL_MAX_DELAY);
    
     // SYNC HIGH
     sync_port->BSRR = sync_pin;
    
     // Reconstituer le mot reçu en little endian
     uint32_t raw = __REV(*(uint32_t*)rx_buf);
    
     // Extraire les 16 bits de conversion : bits 23:8
     uint16_t raw_code = (raw >> 8) & 0xFFFF;
    
     // Convertir en float : plage ±10.24V -> ±32768
     float voltage = ((int16_t)raw_code) * (10.24f / 32768.0f);
    
     // Appliquer la correction d’offset (ex: calibrage)
     return voltage + offset_correction;
    }

     

     

     

     

    LIFER.1Author
    Visitor II
    June 7, 2025

    20250607_174718.jpg

     

    So, when I initialize my ADS8691, since it's only a write operation, can I send 32 clock pulses without latency?

    void ADS8691_Init(uint8_t adc_num)
    {
     GPIO_TypeDef* sync_port;
     uint16_t sync_pin;
    
     if (adc_num == 1) {
     sync_port = ADC1_SYNC_GPIO_Port;
     sync_pin = ADC1_SYNC_Pin;
     } else {
     sync_port = ADC2_SYNC_GPIO_Port;
     sync_pin = ADC2_SYNC_Pin;
     }
    
     uint32_t cmd = 0x40000000 // CMD = Write Register
     | ((uint32_t)(INPUT_RANGE_REGISTER & 0x3F) << 16) // Registre
     | (uint32_t)(RANGE_BIPOLAR_10V24 & 0xFFFF); // Valeur
    
     // SYNC LOW
     sync_port->BSRR = (uint32_t)sync_pin << 16;
    
     // SPI send 32 bits (MSB first)
     uint32_t tx = __REV(cmd); // Reverse byte order for SPI transmission
     HAL_SPI_Transmit(&hspi2, (uint8_t*)&tx, 4, 10);
    
     // SYNC HIGH
     sync_port->BSRR = sync_pin;
    
     HAL_Delay(1); // Optionnel
    }