Hello, I have a question about implementing an SPI device into:
- STM32Duino
- 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 12
→ 0b1100
. 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
Thanks!