Skip to main content
LIFER.1
Associate II
June 6, 2025
Question

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

  • June 6, 2025
  • 11 replies
  • 769 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.

11 replies

MasterT
Lead II
June 6, 2025

Use I2S, 24 or 32-bits with DMA

LIFER.1
LIFER.1Author
Associate 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...

MasterT
Lead II
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.

 

Andrew Neil
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?

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
LIFER.1
LIFER.1Author
Associate 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.

Andrew Neil
Super User
June 7, 2025

You certainly should be able to.

Please show your code.

How to insert source code

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
LIFER.1
LIFER.1Author
Associate 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 ...

Michal Dudka
Lead
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.1
LIFER.1Author
Associate 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.1
LIFER.1Author
Associate 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.1
LIFER.1Author
Associate 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
}