SPI Communications for DAC

Hello, I have a question about implementing an SPI device into:

  1. STM32Duino
  2. Then, A SimpleFOC Driver

The device is a DAC controlled over SPI from a STM32L05C8T6 MCU, which will be used to send data from my motor back to a PLC over long distances. The DAC circuit will provide a 4-20mAh loop circuit, controlled by writing to one of its registers.

DAC: dac161s997
Library: GitHub - Accelovant/dac161s997: Platform independent driver for the DAC161S997 4/20mA driver chip

I am first trying to get general communications to work before I try and port it into a SFOC Driver, but I have not the embedded systems knowledge to know how to easily communicate with it.

When I try using the library posted above, I change the dac161s997_spi_xfer function to try and make it work with the STM32, and put it into the main sketch:

#include <stdint.h>
#include <errno.h>
#include <SPI.h>
#include <Arduino.h>
#include <stdio.h>

#include "dac161s997.h"


/* port header */
struct dac161s997_dev_t {
  uint8_t chip_select;
};

/* port c file */
error_t dac161s997_spi_xfer(dac161s997_dev_t *dev, uint8_t* tx_buf,
                            uint8_t* rx_buf, size_t size) {
  error_t err;
  
  SPI.beginTransaction(SPISettings(6000000, MSBFIRST, SPI_MODE0));
  digitalWrite(PA4, LOW);
  for (size_t i = 0; i < size; i++) {
    rx_buf[i] = SPI.transfer(tx_buf[i]);
  }
  digitalWrite(PA4, HIGH);
  SPI.endTransaction();
	
  err = 0; // temp
  return err; 
}


void setup() {
  Serial.begin(9600);
  Serial.println("Serial Init");
}

void loop() {
  uint32_t status = 0;
  dac161s997_dev_t dev0 = {.chip_select = PA4};
  while (dac161s997_init(&dev0) != 0); // Keep init ing until no error (means successful spi transfer?)
  dac161s997_set_output(&dev0, 5000000);
  dac161s997_get_status(&dev0, &status);
  
  Serial.println(status);
}

*note - the DAC must be pinged every <400ms to not have timeout error

When I run that code, I get an error reading of 120b1100. Since I am getting a number… I have at least some form of communication with the device? I am not sure. The error code may mean:

DAC161S997_STATUS_FRAME_ERR    0x08    SPI frame error
DAC161S997_LO_ALARM_ERR        0x10    Output at low error value

Is there an obvious SPI error anyone can see?

If anyone really wants to look into it further, here is the following code for how Texas Instruments implemented the DAC with their MSP430G2413 MCU, which is what the Github library is based on:

DAC161.h
#ifndef DAC161_H_
#define DAC161_H_
/*************************************************************************************************************************************************/
/*!     DAC161.h
*
*       Header file for DAC161.c library software
*
*       October 2013
*
*/


#define DAC161_XFER_REG                     0x01
#define DAC161_NOP_REG                      0x02
#define DAC161_WR_MODE_REG                  0x03
#define DAC161_DACCODE_REG                  0x04
#define DAC161_ERR_CONFIG_REG               0x05
#define DAC161_ERR_LOW_REG                  0x06
#define DAC161_ERR_HIGH_REG                 0x07
#define DAC161_RESET_REG                    0x08
#define DAC161_STATUS_REG                   0x09


#define DAC161_SPI_WRITE_CMD(address)       (address)
#define DAC161_SPI_READ_CMD(address)        (0x80 + address)


// XFER_REG Register Settings - 01h
#define DAC161_XFER_REG_CMD                 0x00FF

// WR_MODE Register Settings - 03h
#define DAC161_PROTECT_REG_WR               0x0001

//  DACCODE Register Settings - 04h
#define DAC161_OUTPUT_CONVERT(uA)           (((unsigned long)uA * 2732) / 1000)

// ERR_CONFIG Register Settings - 05h
#define DAC161_L_RETRY_TIME_MASK            0x0700
#define DAC161_L_RETRY_TIME_SHIFT           8

#define DAC161_CONVERT_LOOP_RETRY_TIME(ms)  ((((ms / 50) - 1) << DAC161_L_RETRY_TIME_SHIFT) & DAC161_L_RETRY_TIME_MASK)

#define DAC161_DIS_RETRY_LOOP_MASK          0x0080
#define DAC161_DISABLE_RETRY_LOOP           0x0080
#define DAC161_ENABLE_RETRY_LOOP            0x0000

#define DAC161_MASK_LOOP_ERR_MASK           0x0040
#define DAC161_LOOP_ERR_USE_ERR_LOW         0x0000
#define DAC161_LOOP_ERR_MAINTAIN_DACCODE    0x0040

#define DAC161_LOOP_ERR_ERRB_MASK           0x0020
#define DAC161_LOOP_ERR_DRIVE_ERRB          0x0000
#define DAC161_LOOP_ERR_DO_NOT_DRIVE_ERRB   0x0020

#define DAC161_MASK_SPI_ERR                 0x0010

#define DAC161_SPI_TIMEOUT_MASK             0x000e
#define DAC161_SPI_TIMEOUT_SHIFT            1
#define DAC161_CONVERT_SPI_TIMEOUT_TIME(ms) ((((ms / 50) - 1) << DAC161_SPI_TIMEOUT_SHIFT) & DAC161_SPI_TIMEOUT_MASK)

#define DAC161_MASK_SPI_TIMEOUT             0x0001

#define DAC161_STD_ERR_CONFIG_MASKED        (DAC161_DISABLE_RETRY_LOOP + DAC161_LOOP_ERR_MAINTAIN_DACCODE + DAC161_LOOP_ERR_DO_NOT_DRIVE_ERRB + DAC161_MASK_SPI_ERR + DAC161_MASK_SPI_TIMEOUT)
#define DAC161_STD_ERR_CONFIG               ((1 << DAC161_L_RETRY_TIME_SHIFT) + DAC161_ENABLE_RETRY_LOOP + DAC161_LOOP_ERR_USE_ERR_LOW + DAC161_LOOP_ERR_DRIVE_ERRB + (4 << DAC161_SPI_TIMEOUT_SHIFT) + DAC161_MASK_SPI_TIMEOUT)

// ERR_LOW Register Settings  - 06h and
// ERR_HIGH Register Settings - 07h and

#define DAC161_ERR_VALUE_MASK               0xff00
#define DAC161_ERR_VALUE_SHIFT              8
#define DAC161_CONVERT_ERR_VALUE(uA)        ((DAC161_OUTPUT_CONVERT(uA) & DAC161_ERR_VALUE_MASK))

// RESET Register Settings  - 08h
#define DAC161_RESET_VALUE                  0xc33c         // Note that a NOP command is required after this

// STATUS Register Settings  - 09h
#define DAC161_DAC_RES_MASK                 0x00e0
#define DAC161_DAC_RES_SHIFT                5

#define DAC161_ERRLVL_PIN                   0x0010

#define DAC161_FERR_STS                     0x0008

#define DAC161_SPI_TIMEOUT_ERR              0x0004

#define DAC161_LOOP_STS                     0x0002

#define DAC161_CURR_LOOP_STS                0x0001



/*************************************************************************************************************/
/*                              PROTOTYPES                                                                   */
/*************************************************************************************************************/

#ifdef __CPLUSPLUS
extern "C" {
#endif

void DAC161_Write_Regs (unsigned short *writeValues, unsigned char startReg, unsigned char lengthBytes);
void DAC161_Read_Regs (unsigned char *readValues, unsigned char startReg, unsigned char lengthBytes);
void Setup_DAC161  (Port_t ste_port, unsigned char ste_pin, unsigned short errConfig, unsigned short errLow_uA, unsigned short errHigh_uA);
void DAC161_Reset (void);
void DAC161_Nop (void);
void DAC161_Set_Out_Value (unsigned long uAmps);
unsigned char DAC161_Read_Status (void);

#ifdef __CPLUSPLUS
}
#endif


#endif /* DAC161_H_ */
DAC161.c
/*************************************************************************************************************************************************/
/*!     DAC161.c
*
*       This code is designed to perform standard command and control operations on the DAC161 over a SPI bus. Functions exist to setup, configure,
*       and control the DAC161.
*
*       The software is specifically written to execute on an MSP430G2413 on the SATxxxx board.
*
*       October 2013
*
*       \note that the functions in this file are not re-entrant. It is the user's responsibility to assure that these functions
*       are not called until the previous function has completed.
*/

#include "usci_spi.h"
#include "DAC161.h"

static unsigned char SpiHandle;
static unsigned char RcvData[3];

/*************************************************************************************************************************************************
*  DAC161_Write_Regs
**************************************************************************************************************************************************/
/*!
* @brief Writes registers on the DAC161.
*
* This function will execute a write register command to the DAC161. This function can be used to update one or more registers on the DAC161.
* No error checking is performed, so it is the user's responsibility to make sure they do not attempt to write past the end of the DAC161 registers.
*
* @param[out]   *writeValues    Pointer to the list of 8 bit register values to place in the DAC161
* @param[in]     startReg       Address of the first register to write
* @param[in]     length         Number of registers to write.
*
* @return  None
*
**************************************************************************************************************************************************/
void DAC161_Write_Regs (unsigned short *writeValues, unsigned char startReg, unsigned char lengthBytes)
{
    unsigned char outData[3];

    outData[0] = DAC161_SPI_WRITE_CMD(startReg);

    // Switch Endianess
    outData[1] = *writeValues >> 8;
    outData[2] = *writeValues & 0xff;

    Change_SPI_Phase (SPI_CAPT_ON_FIRST_EDGE);
    SPI_Write (SpiHandle, outData, RcvData, lengthBytes+1);    // Add 1 to length for command byte

}

/*************************************************************************************************************************************************
*  DAC161_Read_Regs
**************************************************************************************************************************************************/
/*!
* @brief Reads registers on the DAC161.
*
* This function will execute a read register command to the DAC161 and return the resultant data. This function can be used to read one or more
* registers from the DAC161. No error checking is performed, so it is the user's responsibility to make sure they do not attempt to read past
* the end of the DAC161 registers.
*
* @param[out]   *readValues     Pointer to place the 8 bit register values from the DAC161
* @param[in]     startReg       Address of the first register to read
* @param[in]     length         Number of registers to read.
*
* @return  None
*
**************************************************************************************************************************************************/
void DAC161_Read_Regs (unsigned char *readValues, unsigned char startReg, unsigned char lengthBytes)
{
    unsigned char outData[3] = {0x55, 0x55, 0x55};

    Change_SPI_Phase (SPI_CAPT_ON_FIRST_EDGE);

    // Sets the address for the read command
    outData[0] = DAC161_SPI_READ_CMD(startReg);
    SPI_Write (SpiHandle, outData, readValues, lengthBytes+1);    // Add 1 to length for command byte

    // Performs actual read of previous address
    outData[0] = DAC161_NOP_REG;
    SPI_Write (SpiHandle, outData, readValues, lengthBytes+1);    // Add 1 to length for command byte

}

/*************************************************************************************************************************************************
*  Setup_DAC161
**************************************************************************************************************************************************/
/*!
* @brief Performs the setup of the DAC161.
*
* This function will configure the DAC161.
*
* @param[in]     ste_port        The MSP430 port that contains the CS or STE pin that is connected to the DAC161. (PORT_1, PORT_2, ...)
* @param[in]     ste_pin         The MSP430 pin that contains the CS or STE pin that is connected to the DAC161.  (BIT0, BIT1, ...)
* @param[in]     errConfig       Error Configuration for the DAC161 (DAC161_STD_ERR_CONFIG)
* @param[in]     errLow_uA       Output level (micro Amps) for a Low Error
* @param[in]     errHigh_uA      Output level (micro Amps) for a High Error
*
* @return  None
*
**************************************************************************************************************************************************/
void Setup_DAC161  (Port_t ste_port, unsigned char ste_pin, unsigned short errConfig, unsigned short errLow_uA, unsigned short errHigh_uA)
{
    unsigned short errValue;

    SpiHandle = RegisterSpiDevice (ste_port, ste_pin);

    DAC161_Write_Regs (&errConfig, DAC161_ERR_CONFIG_REG, 2);

    errValue = DAC161_CONVERT_ERR_VALUE(errLow_uA) & 0x7f00;
    DAC161_Write_Regs (&errValue, DAC161_ERR_LOW_REG, 2);

    errValue = DAC161_CONVERT_ERR_VALUE(errHigh_uA) & 0xff00;
    if (errValue < 0x80)
        errValue = 0x80;
    DAC161_Write_Regs (&errValue, DAC161_ERR_HIGH_REG, 2);

}


/*************************************************************************************************************************************************
*  DAC161_Reset
**************************************************************************************************************************************************/
/*!
* @brief Sends a Reset Command to the DAC161.
*
* This function sends a Reset command to the DAC161 on the SPI bus.
*
* @return  None
*
**************************************************************************************************************************************************/
void DAC161_Reset (void)
{
    unsigned short resetCode = DAC161_RESET_VALUE;

    DAC161_Write_Regs (&resetCode, DAC161_RESET_REG, 2);
    DAC161_Write_Regs (&resetCode, DAC161_NOP_REG, 2);          // Nop required after reset

}

/*************************************************************************************************************************************************
*  DAC161_Nop
**************************************************************************************************************************************************/
/*!
* @brief Sends a Nop Command to the DAC161.
*
* This function sends a Nop command to the DAC161 on the SPI bus. The DAC161 will timeout and move into an error state if it does not receive
* regular commands for the SPI master. This command can be used to notify the DAC161 that the system is still operational, but no change to the
* DAC161 is desired.
*
* @return  None
*
**************************************************************************************************************************************************/
void DAC161_Nop (void)
{
    unsigned short deadCode = 0xdead;

    DAC161_Write_Regs (&deadCode, DAC161_NOP_REG, 2);

}

/*************************************************************************************************************************************************
*  DAC161_Set_Out_Value
**************************************************************************************************************************************************/
/*!
* @brief Sets the output current from the DAC161.
*
* The DAC161 is designed to be used in a 4-20mAmp system. Data is communicated by changing the current output from the DAC161. This function sets
* the desired output current.
*
* @param[in]     uAmps         Value in uAmps to output from the DAC161
*
* @return  None
*
**************************************************************************************************************************************************/
void DAC161_Set_Out_Value (unsigned long uAmps)
{
    unsigned short value = DAC161_OUTPUT_CONVERT(uAmps);

    DAC161_Write_Regs (&value, DAC161_DACCODE_REG, 2);

}

/*************************************************************************************************************************************************
*  DAC161_Read_Status
**************************************************************************************************************************************************/
/*!
* @brief Reads thE status register from the DAC161.
*
* This function returns the current value in the status register of the DAC161
*
* @return  DAC161 Status     (DAC161_FERR_STS, DAC161_SPI_TIMEOUT_ERR, DAC161_LOOP_STS, DAC161_CURR_LOOP_STS)
**************************************************************************************************************************************************/
unsigned char DAC161_Read_Status (void)
{
    unsigned char returnValue[3];

    DAC161_Read_Regs (returnValue, DAC161_STATUS_REG, 2);

    return (returnValue[2]);
}

Here is the schematic for how the DAC is set up with the MCU:

Sorry for this not being strictly SFOC related, but I do hope to contribute this knowledge into drivers and other things for SFOC :slight_smile:

Thanks!