-
External 8-bit DAC Control with STM32F7 SPI
Posted by
A.Ersoz
on 11 Sep, 2018 18:16
-
Hello,
I try to control an external DAC with SPI communication in STM32F7. I see my Data Signals as inputs of DAC but the outputs of the DAC are just linear signal. I expect to get pulse signals as output. Has anyone any idea about it?
My code:
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
uint8_t dataTx[16]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
#define SpiEn HAL_GPIO_WritePin(GPIOG,GPIO_PIN_7,GPIO_PIN_RESET);
#define SpiDis HAL_GPIO_WritePin(GPIOG,GPIO_PIN_7,GPIO_PIN_SET);
/* USER CODE END PV */
...
while (1)
{
SpiEn;
HAL_SPI_Transmit(&hspi6, (uint8_t *)dataTx, 16, 100);
SpiDis;
while(HAL_SPI_GetState(&hspi6)!=HAL_SPI_STATE_READY);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
....
-
#1 Reply
Posted by
rstofer
on 11 Sep, 2018 18:33
-
When you transmit a word, do you have to wait for the buffer to be emptied before disabling the SPI link?
This is the very reason I bought the Rigol DS1054Z scope with SPI decoding. I wanted to see how the CS' signal framed the transaction.
The problem with using HAL code (that somebody else wrote) is that you really don't know if the Transmit function is blocking until completion (probably not) or returning after just putting the value in the buffer or shift register (more likely). And it matters...
Maybe the documentation talks about it.
-
-
Yes, AFAIR the HAL_SPI_Transmit() function is a blocking transfer. There are variants of it for interrupt-based or DMA-based transfers. The HAL_SPI_GetState() test should not be necessary, I believe it's already called in the HAL_SPI_Transmit() function itself.
Since the author "sees" the "data signals", we can assume they have a scope and have checked the output was at least as they intended? Surely SPI decoding is handy - hand decoding SPI on a scope is doable but pretty annoying.
Since we have no idea about the DAC itself and whether it needs some kind of configuration, and whether what's written to it is correct, we can't comment further on that.
One general remark though is that there is no pause whatsoever between 'SpiEn', the transfer and 'SpiDis'. We don't know what clock the STM32F7 runs at here, but it's a pretty fast device if it runs at full speed. The DAC may (and probably does) need a minimum time interval between the falling/rising edge of what is probably its CS pin and the data transfer itself. The SPI clock may also be too fast for the DAC specs. Some things to check.
-
#3 Reply
Posted by
newbrain
on 12 Sep, 2018 07:07
-
I try to control an external DAC with SPI communication in STM32F7. I see my Data Signals as inputs of DAC but the outputs of the DAC are just linear signal. I expect to get pulse signals as output. Has anyone any idea about it?
Disclaimer: English is my 3rd language.
That said, could you please clarify what is meant by "linear" signal?
Are you observing a constant value at the DAC output?
I imagine you are expecting a sawtooth, is that right?
Some minor considerations:
- The HAL_SPI_Transmit() function makes sure the SPI transaction is completed before returning (the last bit has been shifted out of MOSI / into MISO), for a successful execution the exit state is HAL_SPI_STATE_READY.
That makes this line is redundant:
while(HAL_SPI_GetState(&hspi6)!=HAL_SPI_STATE_READY);
- The (uint8_t *) cast in the transmit call is also redundant, the array name will decay to a pointer in this context.
But the major point I see is that the CS of the DAC will stay low during the whole array transmission.
Many DACs latch the value to be converted on the rising edge of CS (e.g.
MCP4802, see chap. 6.1), so this might be what's wrong.
Which DAC are you using?
Then, a couple of pet peeves of mine:
- SpiEn and SpiDis are defined as object-like macros. Since they perform an action, it would be clearer to define them as function-like macros, i.e. SpiEn() and SpiDis().
- Plese use the [ code] [ /code] tags (# button) when including code, it makes the post much more readable
-
#4 Reply
Posted by
A.Ersoz
on 13 Sep, 2018 21:13
-
Yes, I see a constant signal as an output. I can control the output voltage but this is still constant. DAC is DAC082S085 and MCU is STM32F767ZI.
-
#5 Reply
Posted by
HB9EVI
on 13 Sep, 2018 21:22
-
If you expect changes in the output voltage, you have to continously send data to the DAC, just from itself this DAC is not doing that; but keep in mind that those SPI DACs have a rather low slew rate; this one has 1V/uS; there is not much headroom for pulses
-
-
Then I would add pauses as I suggested, and also include newbrain's suggestion to frame EACH new DAC word with CS. As he said, usually most SPI DACs latch the input data to the DAC register upon the rising edge of CS. They rarely support "burst mode".
-
#7 Reply
Posted by
newbrain
on 14 Sep, 2018 06:38
-
Yes, I see a constant signal as an output. I can control the output voltage but this is still constant. DAC is DAC082S085 and MCU is STM32F767ZI.
From the
datasheet (emphasis mine):
Any data and clock pusles after the 16th falling clock edge are ignored. In either case, SYNC must be brought high for the minimum specified time before the next write sequence is initiated with a falling edge of SYNC.
So you simply see the first value that's written after bringing SYNC low, as I suspected.
pusles: I like this Texas-Rigol joint initiative!
EtA: and of course, as SiliconWizard notes, make sure to take the settling time into account: t
s is 4.5us worst case. The SPI seems to be fine for up to 30MHz, worst case.
-
#8 Reply
Posted by
A.Ersoz
on 14 Sep, 2018 19:29
-
Actually, I modified my code a little bit. But there is still have same issue.
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f7xx_hal.h"
#include "spi.h"
#include "gpio.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
const uint8_t testdata_out=102; // DAC Analog voltage is set for 2V
const uint8_t DAC_A_Write = 0x1; // DAC's A output is selected
const uint8_t DAC_B_Write = 0x5; // DAC's B output is selected
uint16_t DACA_Buf;
uint16_t DACB_Buf;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
//void shiftOut(uint8_t * data, uint16_t size);
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
*
* @retval None
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI6_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//for(i=0; i<8; i++)
//{
//shiftOut(dataOut+i,1);
//HAL_Delay(30);
//}
// Latch or SYNC Pin needs to LOW
HAL_GPIO_WritePin(Latch_Pin_GPIO_Port, Latch_Pin_Pin, GPIO_PIN_RESET);
// Write data with SPI
DACA_Buf = DAC_A_Write<<12 | testdata_out<<4;
HAL_SPI_Transmit(&hspi6,(uint8_t *)DACA_Buf,16,100);
HAL_Delay(100);
// When data is ready, LATCH will high
HAL_GPIO_WritePin(Latch_Pin_GPIO_Port, Latch_Pin_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(Latch_Pin_GPIO_Port, Latch_Pin_Pin, GPIO_PIN_RESET);
// Write data with SPI
DACB_Buf = DAC_B_Write<<12 | testdata_out<<4;
HAL_SPI_Transmit(&hspi6,(uint8_t *)DACB_Buf,16,100);
HAL_Delay(100);
// When data is ready, LATCH will high
HAL_GPIO_WritePin(Latch_Pin_GPIO_Port, Latch_Pin_Pin, GPIO_PIN_SET);
// This code for controlling two analog switches
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_9);
HAL_Delay(10);
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_9);
HAL_Delay(10);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
/**Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = 16;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 216;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Activate the Over-Drive mode
*/
if (HAL_PWREx_EnableOverDrive() != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Configure the Systick interrupt time
*/
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
/**Configure the Systick
*/
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @param file: The file name as string.
* @param line: The line in file as a number.
* @retval None
*/
void _Error_Handler(char *file, int line)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
while(1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/**
* @}
*/
/**
* @}
*/
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
-
#9 Reply
Posted by
newbrain
on 14 Sep, 2018 20:12
-
[ code] please...
// Latch or SYNC Pin needs to LOW
HAL_GPIO_WritePin(Latch_Pin_GPIO_Port, Latch_Pin_Pin, GPIO_PIN_RESET);
// Write data with SPI
DACA_Buf = DAC_A_Write<<12 | testdata_out<<4;
HAL_SPI_Transmit(&hspi6,(uint8_t *)DACA_Buf,16,100);
HAL_Delay(100);
// When data is ready, LATCH will high
HAL_GPIO_WritePin(Latch_Pin_GPIO_Port, Latch_Pin_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(Latch_Pin_GPIO_Port, Latch_Pin_Pin, GPIO_PIN_RESET);
// Write data with SPI
DACB_Buf = DAC_B_Write<<12 | testdata_out<<4;
HAL_SPI_Transmit(&hspi6,(uint8_t *)DACB_Buf,16,100);
HAL_Delay(100);
// When data is ready, LATCH will high
HAL_GPIO_WritePin(Latch_Pin_GPIO_Port, Latch_Pin_Pin, GPIO_PIN_SET);
I see a number of problems, but I won't spoil the fun giving you the solutions straight away
- You have defined DACA_Buf and DACB_Buf as uint16_t, and formatted the data and control fields correctly
- Now think how you are passing them to the HAL_SPI_Transmit() function:
its 2nd parameter is uint8_t *pData, and you have used a cast, but...something is wrong, as you are casting an integer into a pointer. Is that what you really want? - After you address that, remember that Cortex-M is a little endian platform but the SPI is MSB first out: do we need to do something more?
- An uint16_t is two byte long, how many bytes are you writing to the SPI?
- Minor, not an actual problem: The 100ms delays are really not needed, check tCSFR and tSYNC in the datasheet...
There might be something else, but this should get you going.
-
#10 Reply
Posted by
A.Ersoz
on 14 Sep, 2018 21:58
-
Thanks for your interest!
I updated testdata_out array.
uint8_t testdata_out[16]={0,102,0,102,0,102,0,102,0,102,0,102,0,102,0,102}; // DAC Analog voltage is set for 2V
-
#11 Reply
Posted by
newbrain
on 15 Sep, 2018 09:50
-
Thanks for your interest!
I updated testdata_out array.
uint8_t testdata_out[16]={0,102,0,102,0,102,0,102,0,102,0,102,0,102,0,102}; // DAC Analog voltage is set for 2V
Fine, but did you address the points in my post?
If yes, how?
-
#12 Reply
Posted by
A.Ersoz
on 15 Sep, 2018 15:26
-
Based on your respond, I understand that I should define an array. In addition to that I determined each bit as 0 Voltage and 2 Voltage bit value. I calculated 102 value with datasheet referencing. In the datasheet, Vout=Vin*(D/256).
-
#13 Reply
Posted by
newbrain
on 15 Sep, 2018 18:35
-
Based on your respond, I understand that I should define an array. In addition to that I determined each bit as 0 Voltage and 2 Voltage bit value. I calculated 102 value with datasheet referencing. In the datasheet, Vout=Vin*(D/256).
Ah, well...it seems I was maybe a bit too cryptic.
Ok, let's take it step by step then!
0. An array is for sure the right structure if you want to hold a series of values to be transmitted.
That said, the problems with the code you posted are not solved by this, so let's just use a single value for the moment, your variables below are perfectly fine.
const uint8_t testdata_out=102; // DAC Analog voltage is set for 2V
const uint8_t DAC_A_Write = 0x1; // DAC's A output is selected
const uint8_t DAC_B_Write = 0x5; // DAC's B output is selected
uint16_t DACA_Buf;
uint16_t DACB_Buf;
1. I was complaining about the pointer paramter to the transmit call. You wrote:
HAL_SPI_Transmit(&hspi6,(uint8_t *)DACA_Buf,16,100);What you are doing here is casting the
content of DACA_Buf to an uint8_t*, whereas you need to pass its
address to the function. What is needed is (uint8_t*)
&DACA_Buf
2. But when we have done this, we might find another problem:
Did you configure the SPI for 16 bits in CubeMX? If you did,
disregard this point.
If you configured it at 8 bits (the default IIRC), read the following.
Cortex-M Arms are little endian machines.
So DACA_Buf, that has been assigned 0x1660, will be stored in memory as 0x60, 0x16. The SPI will then transmit 0x6016: not what we need.
The easy way to solve the issue is swapping the bytes:
DACA_Buf = (DACA_Buf>>8) | (DACA_Buf<<8);
This, of course, before the HAL_SPI_Transmit() call!
3. The next thing to address is that we want to transmit just 2 bytes in every transaction, not 16 as it's written in the code. As said, this DAC will ignore everything after the first 16 bits.
HTH
-
#14 Reply
Posted by
A.Ersoz
on 16 Sep, 2018 04:12
-
Thank you very much! Appreciated!
I set 16 bits configuration in CubeMX in SPI Configuration part. Now, I got two errors which are
DAC and Analog Switch Control 9 14 18 v1\DAC and Analog Switch Control 9 14 18 v1.axf: Error: L6218E: Undefined symbol SystemClock_Config (referred from main.o).
DAC and Analog Switch Control 9 14 18 v1\DAC and Analog Switch Control 9 14 18 v1.axf: Error: L6218E: Undefined symbol _Error_Handler (referred from spi.o).
Do you have any idea? I googled this errors, and I understand that these errors are related with wrong address issue. What do you think?
-
#15 Reply
Posted by
newbrain
on 16 Sep, 2018 13:39
-
Do you have any idea?
Well, sometimes I do.
Some idea is also related to the problem at hand
The messages mean simply that the linker cannot find any function with those names.
Look at the main.c code you included above, those two functions (automatically generated by CubeMx) are at the end of the file (the assert one is not used in general, as USE_FULL_ASSERT defaults to non-defined).
You have probably botched an edit and somehow made them part of a comment or a false #if/#ifdef or something similar, so they are no longer part of the compiled code.
Aren't you getting any warning before the linker error?
-
#16 Reply
Posted by
A.Ersoz
on 17 Sep, 2018 15:14
-
Thank you! I solved the problem
My another question is that the output of DAC is not same as calculated value of mine. I defined test_data as '102', so I expected to see 2V. But the output is mV level. What do you think?
-
#17 Reply
Posted by
A.Ersoz
on 17 Sep, 2018 15:43
-
Now, my lat question is also OK. I managed to problem with modifying baud rate of SPI.
Now, there is an interesting point. I got output A of the DAC but output B does not generate signal.
-
#18 Reply
Posted by
A.Ersoz
on 17 Sep, 2018 16:19
-
I am sorry for making busy of blog. I also solved the last question.
I modified DACA_Buf and DACB_Buf declaration like switching the parameters.
DACA_Buf = DAC_A_Write<<4 | testdata_out<<12;
Now, I got what expected signals.
I hope that this conversation helps or provides a good example for SPI controlled external DAC.
Thank you very much newbrain and other contributors!
-
#19 Reply
Posted by
newbrain
on 23 Sep, 2018 20:53
-
I modified DACA_Buf and DACB_Buf declaration like switching the parameters.
DACA_Buf = DAC_A_Write<<4 | testdata_out<<12;
Mmmh...this is definitely not correct. Let's have a look:
DAC_A_Write = 0x01
testdata_out=102 = 0x66
DAC_A_Buf = (0x01 <<4) | (0x66 << 12) = 0x10 | 0x66000 = 0x66010, truncated to 0x6010 as it is an uint16_t.
By pure chance this command will write the value 0x01 to the output of both DACs, but that's not what you need...
The other major point is that in the HAL_SPI_Transmit call the length is indicated as 16: this parameter is the number of bytes (if SPI is set for <=8bit) or words (> 8 bit) to transfer.
From the code I got in PM the SPI is set for 16 bits, so this value should be
1. No bit shuffling needed to correct endianness.
Is the DAC correctly connected in the circuit? Is Vref at a correct value?
-
#20 Reply
Posted by
A.Ersoz
on 24 Sep, 2018 15:43
-
You mean DAC output A and DAC Output B definitions are not correct?
-
#21 Reply
Posted by
newbrain
on 24 Sep, 2018 18:17
-
You mean DAC output A and DAC Output B definitions are not correct?
DAC_A_Write and DAC_B_Write are correctly defined!
What is wrong is the shifting, that was instead correct in the original code:
DACB_Buf = DAC_B_Write<<12 | testdata_out<<4; // Correct, will be in binary 0b0101_0110_0110_0000
DACA_Buf = DAC_A_Write<<4 | testdata_out<<12; // Wrong, will be in binary 0b0110_0000_0001_0000!