DAC from Portenta H7

I'm trying to use the inbuilt DAC in the Portenta H7 board.

The inbuilt Arduino IDE functionality doesn't appear to support the Portenta H7 yet - for example it is not referenced in the Arduino reference page for AnalogWrite(). If I try to use AnalogWrite I get a PWM simulated DAC output, which it not good enough for my application.

I've spent quite a lot of time looking at the reference manual for the processor STM32H747XI and I've tried to use the STM32Cube software to generate the code to get the DAC working, but to no avail...

I've posted my latest attempt at the code below. It runs without errors but I'm not getting a reasonable output voltage on pin A6 as I would expect.

Any advice would be appreciated!

#include <stm32h7xx_hal_dac.h>
#include <stm32h7xx_hal_gpio.h>

#define HSEM_ID_0 (0U) /* HW semaphore 0*/

// Overall loop rate
const unsigned long desDelay = 1000.0; // sampling dt in microseconds
const unsigned long desF = 1 / (desDelay / 1000000.0); // Calculate sampling frequency

// LED status indicator
bool stateLED = 0;

DAC_HandleTypeDef hdac1;

void setup() {
  long timeout;
  
  // initialise red and blue LEDs
  pinMode(LEDR, OUTPUT);
  pinMode(LEDB, OUTPUT);
  digitalWrite(LEDR, 0); // turn on red LED
  digitalWrite(LEDB, 1); // turn off blue LED

  HAL_Init();
  SystemClock_Config();

  /* When system initialization is finished, Cortex-M7 will release Cortex-M4 by means of
  HSEM notification */
  /*HW semaphore Clock enable*/
  __HAL_RCC_HSEM_CLK_ENABLE();
  /*Take HSEM */
  HAL_HSEM_FastTake(HSEM_ID_0);
  /*Release HSEM in order to notify the CPU2(CM4)*/
  HAL_HSEM_Release(HSEM_ID_0,0);
  /* wait until CPU2 wakes up from stop mode */
  timeout = 0xFFFF;
  while((__HAL_RCC_GET_FLAG(RCC_FLAG_D2CKRDY) == RESET) && (timeout-- > 0));
  if ( timeout < 0 )
  {
  Error_Handler();
  }
  /* USER CODE END Boot_Mode_Sequence_2 */

  MX_GPIO_Init();
  MX_DAC1_Init();

  // Write DAC value
  HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2048);

  // Start DAC
  HAL_DAC_Start(&hdac1, DAC_CHANNEL_1);


  digitalWrite(LEDR, 1); // turn off red LED
}

void loop() {
  // Grab time at start of loop
  unsigned long t0 = micros();

  // Change the blue LED state every second
  stateLED = (t0 / 1000000) % 2;
  digitalWrite(LEDB, stateLED);

  HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2048);

  // Calculate loop time and delay until finished
  delayMicroseconds(desDelay - (micros() - t0));
}

void MX_DAC1_Init()
{
  DAC_ChannelConfTypeDef sConfig = {0};
  hdac1.Instance = DAC1;
  if (HAL_DAC_Init(&hdac1) != HAL_OK)
  {
    Error_Handler();
  }
  /** DAC channel OUT1 config 
  */
  sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_ENABLE;
  sConfig.DAC_Trigger = DAC_TRIGGER_NONE;
  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
  sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE;
  sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
  sConfig.DAC_SampleAndHoldConfig.DAC_SampleTime = 1000;
  sConfig.DAC_SampleAndHoldConfig.DAC_HoldTime = 1000;
  sConfig.DAC_SampleAndHoldConfig.DAC_RefreshTime = 1;
  if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
}

void MX_GPIO_Init()
{
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
}

void Error_Handler()
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

void SystemClock_Config()
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Supply configuration update enable 
  */
  HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);
  /** Configure the main internal regulator output voltage 
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);

  while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
                              |RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV1;
  RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

Preynolds101 what are the expected 2 DAC pins. I think DAC1 is pin D6, you have mentioned A6, I can't find any DAC0 in the pinout. Can you try your code with pin D6 - PWM1 which seems connected with DAC1.

Anyone have any idea which pin is DAC0?

As I understand:
There is just one Dual Channel DAC. And it is called DAC1.
So, there are just two outputs as:
DAC1_OUT1 = PA4 = A6 on the header pins (free, but intended also as CAM DCMI_HS signal)
DAC1_OUT2 = PA5 - internal as UH_CLK (for USB HS controller) - not usable!

DAC needs VREF+, but schematics shows it is populated with +3V1.

So far, it looks correct to me (DAC1, Channel 1 used - via PA4 which is A6).
Value 2048 as 12-bit right aligned should be half level of VREF (3V1 / 2) - I guess.
But the DAC value set seems to be in code always the same (not changing).

What do you see on A6?
Is there a stable output voltage? (what is it?)
Probe with a volt-meter: what happens if you touch with fingers, or a resistor, e.g. 3K to GND or VDD - does the signal look floating? (just to verify that the output pin is driven, but it could be still something else using PA4 still/again).

For the easiest DAC setup what pinmode do we use and is any include files needed?

This does not compile for me

pinMode(DAC1, OUTPUT);

Well actually it dies at

analogWrite(DAC1, 1000);

I tried to use DAC1 (just for my own fun):

  • use mbed OS and the DAC functions there ("AnalogOut.h", "analogout_api.h")
  • you can 'ask' mbed OS, which pin is analog out (it gives me 0x04 - no idea what does it mean)
  • using this pinMap after query analogout_pin - it looks like, I can open DAC and I can set values

BUT:
--> the DAC output seems to be on pin A0 (not A6)!

BTW:
I tried first the Scatch in Arduino Pro IDE (which is a nightmare: it creates very strange compiler errors after
source code was modified and it requests all the time to toggle and select the port again - it is even not an Alpha release, sorry).

So, it looks like, it seems to work, if you probe A0 (instead of A6).
Maybe the PCB layout/schematic is different (I have also a lot of trouble to understand the I2C on PCB and pins).

Here is the code I have used:
REMARK: you had to open and connect an UART terminal in order to proceed in to/in the loop() !
Open a terminal (in IDE, or TeraTerm, with 9600 baud) - I see a voltage toggling between 2.21V and 0.48V.
If UART is not open, the loop() with DAC has not started (after reset) - I see fix 3.1V (as VREF). It makes a bit sense to me (except that the pins seem to be 'wrong' in pinout diagram).

#include "AnalogOut.h"
#include "hal/analogout_api.h"

////AnalogOut *dac;
dac_t myDac;

void setup() {
  // put your setup code here, to run once:
  const PinMap *pinMap;
  pinMap = analogout_pinmap();

  pinMode(LEDR,OUTPUT);
  pinMode(LEDG,OUTPUT);
  pinMode(LEDB,OUTPUT); 

  digitalWrite(LEDR, HIGH); //red LED OFF - wait for terminal: default 9600baud
  digitalWrite(LEDG, HIGH); //HIGH is OFF!
  digitalWrite(LEDB, HIGH);

  // Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  Serial.println("*** DAC demo setup ***");
  Serial.printf("pin: 0x%x\n", pinMap->pin);

  ////dac = AnalogOut(pinMap->pin);
  analogout_init_direct(&myDac, pinMap);
  analogout_write(&myDac, 2048);
}

void loop() {
  Serial.println("Loop | DAC demo:");
  digitalWrite(LEDG, HIGH);
  analogout_write(&myDac, 0);
  delay(1000);
  digitalWrite(LEDG, LOW);
  analogout_write(&myDac, 1024);
  delay(1000);
  analogout_write(&myDac, 2048);
  delay(1000);
}

DAC1 is not a simple GPIO pin, it is a structure to the DAC PERIPHERAL (not a GPIO).
You cannot toggle DAC pin this way (unpredictable what happens).
I think - it 'has' to crash.

Best debug might be:
try to toggle A0 and/or A6 pin on PCB header (similar as we do for LEDs, just this as Output - no idea how to
specify a pin, e.g. as PA4?).

Even I thought A6 is DAC1_OUT1 - but it looks like it is on A0.
So, if we would debug via trying to toggle PA4 and/or PA0_C (from chip, as GPIO) - we might figure out that it seems to be connected on a different PCB header pin.

That the DAC outputs the nalog voltage on PA0_C is not possible, just that pinout diagram is not really correct (A0 instead of A6).

I think, it makes sense to see DAC1_OUT1 on PA4, but not as A6 - instead it is on A0:

If I check files as: C:/Users/tj/AppData/Local/Arduino15/packages/arduinobeta/hardware/mbed/1.2.1/cores/arduino/mbed/targets/TARGET_SIM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/PinNames.h - I see:

PA_4 = 0x04,
PA_4_ALT0 = PA_4 | ALT0, // same pin used for alternate HW
PA_4_ALT1 = PA_4 | ALT1, // same pin used for alternate HW

So, 0x04 seems to be PA_4 (which is correct for DAC1_OUT1).

In file: C:/Users/tj/AppData/Local/Arduino15/packages/arduinobeta/hardware/mbed/1.2.1/cores/arduino/mbed/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/TARGET_ARDUINO_NANO33BLE/PinNames.h
I see:

// Analog Pins
A0 = p4,

So, it looks like, A0 is flipped with A6. PA4 seems to be on A0 (instead of A6). No idea if flipped due to pinMap files or on PCB (this NORDIC file should not be in use, but maybe the same in 'correct' file, potentially a schematics/PCB issue).

So, this DAC signal seems to come out on A0, instead of A6.

(this board is soooo nice, but the IDE, this Ardunio stuff, mbed etc. drives me soooo crazy - sorry.
Why all is hidden in protected folders .../Users/<username/AppData/Local/Arduino15/... ? It does not make sense to me - I want to see at least all the H-files for the system. And I cannot browse in IDE into H-Files - never seen such a bad IDE, even called 'PRO')

I tried again, here what I've found.

Verified (correct):

  • DAC out is PA6 - correct as A6 on header pins
  • I tried via toggling PA6 as GPIO, checking interactive PCB viewer - it is correct

But I've found some issues:

  • the mbed function analogout_write(&myDac, 0.5); seems to expect a floating point value
  • it seems to be the multiplier as: DACout = Value * VREF+
  • every value larger as 1.0 sets the maximum VREF+ voltage
  • VREF+ is not stable: it has 100K to 3V1 and it changes voltage if DAC changes output voltage
  • you have to provide a stable VREF+, on first pin of the header, e.g. connect it with 3V3 (pin 3 on other header - then it makes sense and seems to work

Here is my Scatch to use DAC: use values as 0.0 to 1.0 and provide a stable VREF+.

#include "AnalogOut.h"
#include "hal/analogout_api.h"

////AnalogOut *dac;
dac_t myDac;

void setup() {
  // put your setup code here, to run once:
  const PinMap *pinMap;
  pinMap = analogout_pinmap();

  pinMode(LEDR,OUTPUT);
  pinMode(LEDG,OUTPUT);
  pinMode(LEDB,OUTPUT); 

  digitalWrite(LEDR, HIGH); //red LED OFF - wait for terminal: default 9600baud
  digitalWrite(LEDG, HIGH); //HIGH is OFF!
  digitalWrite(LEDB, HIGH);

  // Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  Serial.println("*** DAC demo setup ***");
  Serial.printf("pin: 0x%x\n", pinMap->pin);

  ////dac = AnalogOut(pinMap->pin);
  analogout_init_direct(&myDac, pinMap);
  analogout_write(&myDac, 2048);
}

void loop() {
  Serial.println("Loop | DAC demo:");

  //ATTENTION: it looks like the value paramter has to be a float number
  //it is: Value * VREF+, so 1.0 is the max value, every value larger as 1
  //will generate always the same VREF+ voltage on PA6 (A6)

  digitalWrite(LEDG, HIGH);
  analogout_write(&myDac, 0);
  delay(4000);
  digitalWrite(LEDG, LOW);
  analogout_write(&myDac, 0.3);
  delay(4000);
  digitalWrite(LEDG, HIGH);
  analogout_write(&myDac, 0.5);
  delay(4000);
  digitalWrite(LEDG, LOW);
  analogout_write(&myDac, 1.0); //seems to set VREF+
  //ATTENTION: VREF+ is not stable (100K), it changes reference voltage:
  //connect VREF+ with 3V3 - then it makes sense!
  //it looks like, for DAC - you had to provide a VREF+ on first pin
  delay(4000);
}

@tjaekel do you think that the following method would give more precise adc readings as well?

"you have to provide a stable VREF+, on first pin of the header, e.g. connect it with 3V3 (pin 3 on other header - then it makes sense and seems to work"

Here is my fairly simple code that connects A6 DAC to A0 for analogRead and A6 DAC to D6 for digitalRead.
Also remember to connect 3V3 to AREF

/* 
 * DAC to ADC for the PortentaH7
 * Digital to Analog Converter (DAC) and  Analog to Digital Converter (ADC)
 * DAC converts a digital signal into analog signal.
 * ADC converts an Analog signal into a digital signal
 *
 * Connect Pin 3V3 to AREF
 * Connect Pin A6 (DAC) to A0
 * Connect Pin A6 (DAC) to Pin 3 (D3)
 *   
 *
 *  Update Aug 3rd, 2020
 *
 *  analogWriteResolution(10) assumed default for DAC 0 - 1023 --> 0 to 3.3 Volts
 *  
 *  By Jeremy Ellis twitter @rocksetta
 *  Webpage http://rocksetta.com
 *  Arduino High School Robotics Course at
 *  https://github.com/hpssjellis/arduino-high-school-robotics-course
 *  
 */


#include <Arduino.h>  // only needed for https://platformio.org/

void setup() {
  
  analogWriteResolution(10);
  randomSeed(A0);  
  Serial.begin(9600);
  pinMode(DAC, OUTPUT);
  pinMode(6, INPUT_PULLDOWN);
  // pinMode(A0, INPUT); You don't have to declare A0 for Analog input, that is the default

}

void loop() {
  
  myDac(0);
  myDac(800); 
  int myRandomNumber  = rand() % 1024;  // a random number between 0 and 1023
  myDac(myRandomNumber);
  Serial.println("----------------------");
  delay(6000); // wait a few seconds

}


void myDac(int myDacSet){
  
  analogWrite(DAC, myDacSet);
  Serial.print("DAC set to: ");
  Serial.print(myDacSet);

  Serial.print(", A0: ");
  Serial.print(analogRead(A0));

  Serial.print(", Pin(D6): ");
  Serial.println(digitalRead(6));
  
}

All my PortentaH7 code at

Portenta H7 DAC Simplified.

Just analogWrite() to A6(PA_4) pin, then it works as DAC, not PWM.

Here is the example code.

void setup() {    
 //analogWriteResolution(12);    // up to 16, default 8bit(0~255)
 Serial.begin(115200);
}

void loop() {
 analogWrite(A6, 0);
 Serial.println("DAC   0%  0.000V");
 delay(4000);
 
 analogWrite(A6, 64);
 Serial.println("DAC  25%  0.825V");
 delay(4000);
 
 analogWrite(A6, 128);
 Serial.println("DAC  50%  1.650V");
 delay(4000);
 
 analogWrite(A6, 192);
 Serial.println("DAC  75%  2.475V");
 delay(4000);
 
 analogWrite(A6, 255);
 Serial.println("DAC 100%  3.300V");
 delay(4000);
}

PS) I have connected the AREF pin to +3V3 pin.

Hey Dspexpert. I must say that your code is more simple than mine. I like it. That does bring up a really good point. When does DAC A6 actually output PWM and when does it output variable voltage DAC?

In the file ArduinoCore-mbed/pins_arduino.h at master · arduino/ArduinoCore-mbed · GitHub

define PIN_A6 (21u)
static const uint8_t A0  = PIN_A0;
static const uint8_t A1  = PIN_A1;
static const uint8_t A2  = PIN_A2;
static const uint8_t A3  = PIN_A3;
static const uint8_t A4  = PIN_A4;
static const uint8_t A5  = PIN_A5;
static const uint8_t A6  = PIN_A6;
#define ADC_RESOLUTION 12

//DACs
#define DAC           (A6)

It looks to me like pin 21 is defined as A6, PIN_A6, and also as DAC. Your code looks great, and unlike me you probably tested the output for the correct voltage, but how do you define A6 for PWM output? Looking at the pin_diagram I don't think A6 does PWM I think it only does DAC. Anyone got an opinion.

tjaekel:

#include "AnalogOut.h"

#include "hal/analogout_api.h"

dac_t myDac;

Hey Tjaekel, you did a much more advanced DAC than dspexpert and I have done. Are we just making it generate PWM and your way is the proper DAC method or do both methods work? I am just trying to simplify things. I noticed your method used pin 0x04 which is interesting. My oscilloscope is at school so I can't test the output properly. Curious if A0 to A5 give PWM outputs, while A6 give DAC?

So I did a quick test and it looks like analgoWrite(A1, 255); crashes the board (flashing red). So possibly A0-A5 can not do analogWrite only A6 can do analogWrite as DAC and D0 - D5 are the only main pinout PWM pins. I will need to do some testing and Jerry-rig an oscilloscope.

Dear jerteach,

A6(PA_4) pin is defined as DAC pin, and OLNY the DAC pin works as DAC when analogWrite().

in ...mbed\1.2.2\cores\arduino\wiring_analog.cpp,
analogWrite() to DAC pin calls analogWriteDAC() actually.

#if DEVICE_ANALOGOUT
#include "mbed/drivers/AnalogOut.h"
mbed::AnalogOut* dac = NULL;
void analogWriteDAC(PinName pin, int val) {
  if (dac == NULL) {
    dac = new mbed::AnalogOut(pin);
  }
  float percent = (float)val/(float)(1 << write_resolution);
  if (percent > 1.0f) {
    percent = 1.0f;
  }
  dac->write(percent);
}
#endif

void analogWrite(PinName pin, int val)
{
  pin_size_t idx = PinNameToIndex(pin);
  if (idx != NOT_A_PIN) {
    analogWrite(idx, val);
  } else {
    mbed::PwmOut* pwm = new mbed::PwmOut(pin);
    pwm->period_ms(2); //500Hz
    float percent = (float)val/(float)(1 << write_resolution);
    pwm->write(percent);
  }
}

void analogWrite(pin_size_t pin, int val)
{
  if (pin >= PINS_COUNT) {
    return;
  }
#ifdef DAC
    if (pin == DAC) {
      analogWriteDAC(digitalPinToPinName(pin), val);
      return;
    }
#endif
  float percent = (float)val/(float)(1 << write_resolution);
  mbed::PwmOut* pwm = digitalPinToPwm(pin);
  if (pwm == NULL) {
    pwm = new mbed::PwmOut(digitalPinToPinName(pin));
    digitalPinToPwm(pin) = pwm;
    pwm->period_ms(2); //500Hz
  }
  pwm->write(percent);
}

That makes sense. Can you try analogWrite with A0 to A5. I get a nasty crash.

If you try analogWrite() to non-PWM pin, it will cause mbed system crash.(except the DAC pin :slight_smile: )

hey @preynolds101 ,

were you able to get your code to work?

I am interested in using the DAC with the DMA and I believe your way is the start, but I am still not getting any values on the DAC same as you even after connecting the VREF to 3.3V.

The other ways using arduino's analogWrite work but that doesn't give me the chance to use the full capabilities of the DAC.

I am interested in using the HAL Library functions.