2024-12-11 05:43 AM - last edited on 2024-12-11 05:48 AM by SofLit
Hi,
I went through quite some earlier posts on this forum regarding DAC setup, but none of those are relevant to me nor helped me.
I am trying to setup a DAC on a STM32F756VGH6 via registers. I read all chapters about the DAC in the datasheet and it looked like quite a simple peripheral on this mcu, as it there is very little setup needed to be done (I just selected the trigger mode, enabled it and enabled the DAC1 itself on pin PA4.) My goal is just to setup a simple DAC without any continuous waves, DMA etc. Just a constant analog voltage value that I write to a register.
However, my problem is that I do not get anything on the DAC pin (always 0 V). I tried to put an external pulldown resistor, pullup resistor, but none of those worked. I also tried with both enabled and bypassed output buffer modes but that did not change anything either. I also tried to setup the PA4 pin as GPIO to blink some led, to see, if the pin itself was working, and everything was just fine...
It will either be unbelievably simple mistake or there is something fundamentally wrong with my perception of how this mcu DAC module works. In any case, I need some help or advice to get this thing going again. Thank you in advance
The code might look really long and complicated, but you just have to look at DAC_Setup and DAC_execution functions as those are just a few lines each.
#include "main.h"
#include <stdio.h>
uint8_t received_character;
void Core_Clock_Setup (void){
RCC->CR |= RCC_CR_HSEON; //Set the clock source to external crystal/resonator (HSE)
while (!(RCC->CR & RCC_CR_HSEON)); //Wait until clock gets stable
RCC->APB1ENR |= RCC_APB1ENR_PWREN; //Enable power interface clock
PWR->CR1 &= ~(1U << 14);
PWR->CR1 &= ~(1U << 15); //Set internal voltage regulator to is reset value (scale 1)
FLASH->ACR &= ~FLASH_ACR_ARTEN; //Disable ART accelerator
FLASH->ACR &= ~FLASH_ACR_ARTRST; //Reset ART accelerator
FLASH->ACR |= FLASH_ACR_PRFTEN; //Enable prefetch
FLASH->ACR |= FLASH_ACR_LATENCY_6WS; //Set 7 CPU clock cycle flash memory access time (in order to get 200 MHz core clock)
//@ 25 MHz crystal, 200 MHz core clock configuration down below (200 MHZ verified as output on MCO2)
RCC->CFGR &= ~(((1 << (7 - 4 + 1)) - 1) << 4);
RCC->CFGR &= ~(1 << 4); //Core clock division by 1 (core clock is not devided)
RCC->CFGR &= ~(1 << 5);
RCC->CFGR &= ~(1 << 6);
RCC->CFGR &= ~(1 << 7);
RCC->CFGR &= ~(1 << 29);
RCC->CFGR &= ~(1 << 30); //Set MCO2 as SYSCLK, no division
RCC->CFGR &= ~(1 << 31);
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE;//HSE is set to be PLL entry
RCC->PLLCFGR &= ~(1 << 16); //PLLP Setting corresponding PLL prescalers (division by 2)
RCC->PLLCFGR &= ~(1 << 17);
RCC->PLLCFGR &= ~((1 << 6) - 1);
RCC->PLLCFGR |= (16 & ((1 << 6) - 1)); //PLLM Setting corresponding PLL prescalers (division by 16)
//RCC->PLLCFGR |= (16 << 0);
RCC->PLLCFGR &= ~(((1 << (14 - 6 + 1)) - 1) << 6);
RCC->PLLCFGR |= (256 << 6); //PLLN Setting corresponding PLL prescalers ( multiplication by 256)
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; //APB1 Low speed prescaler of 4 (50 MHz, max is 54 Mhz)
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; //APB2 High speed prescaler of 2 (100 MHz, max is 108 Mhz)
RCC->CR |= RCC_CR_PLLON; //Enable PLL
while (!(RCC->CR & RCC_CR_PLLRDY)); //Wait until PLL gets stable
RCC->CFGR |= RCC_CFGR_SW_PLL; //PLL is set to be core clock
//RCC->CFGR |= RCC_CFGR_SW_HSE; //HSE is set to be core clock
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Wait until PLL indeed becomes core clock source
}
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Timer1_Setup(void){ //16 bit advanced timer
RCC->DCKCFGR1 &= ~ (1 << 24); //TIMxCLK = 2xPCLKx
//When TIMPRE bit of the RCC_DCKCFGR1 register is reset, if APBx prescaler is 1, then TIMxCLK = PCLKx, otherwise
//TIMxCLK = 2x PCLKx.
// When TIMPRE bit in the RCC_DCKCFGR1 register is set, if APBx prescaler is 1,2 or 4, then TIMxCLK = HCLK, otherwise
//TIMxCLK = 4x PCLKx.
//TIM1 CLK is HCLK in this case
RCC->APB2ENR |= (1 << 0); //Enable Timer 1 clock
TIM1->PSC = 99; //APB1 is 50 Mhz and 100 MHZ for timer (The number is set: Clock in MHz - 1) 1 full period equals 1 MHZ
TIM1->ARR = 0xFFFF; //Auto reload at 100 ticks -> around 100 micro seconds at 100 MHz timer clock
TIM1->CR1 |= (1 << 0); //Enable Timer 1 counter
while(!(TIM1->SR & (1<<0))); //Wait until timer update bit is set
}
void delay_ms (uint32_t ms){
SysTick->LOAD = (200000-1); // Configure for milisecond delay
SysTick->CTRL |= (1<<2); // Select Processor Clock for blocking delay function
SysTick->CTRL |= (1<<0); // Counnter Enable
SysTick->VAL = 0;
for(int i =0; i<ms ; i++)
{
while ((SysTick->CTRL & (1<<16)) == 0);
}
SysTick->CTRL &= ~(1<<0); // Counnter Disable
}
void delay_us (uint32_t us){
SysTick->LOAD = (200-1); // Configure for milisecond delay
SysTick->CTRL |= (1<<2); // Select Processor Clock for blocking delay function
SysTick->CTRL |= (1<<0); // Counnter Enable
SysTick->VAL = 0;
for(int i =0; i<us ; i++)
{
while ((SysTick->CTRL & (1<<16)) == 0);
}
SysTick->CTRL &= ~(1<<0); // Counnter Disable
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void GPIO_Setup(void){
RCC->AHB1ENR |= (1 << 2); //Enable clock for GPIO bank C
RCC->AHB1ENR |= (1 << 4); //Enable clock for GPIO bank E
delay_ms(1);
//SYSCLK Output at PC9
GPIOC->AFR[1] |= (0b0 << 4); //Select alternate function as MCO2 on pin PC9
GPIOC->OSPEEDR |= (0b11 << 18); //PC9 very high GPIO speed
GPIOC->MODER |= (0b10 << 18); //PC19 Assign alternate function
//PC14 OUTPUT
GPIOC->MODER |= (0b01 << 28); //PC14 General purpose output mode
GPIOC->OTYPER &= ~ (1 << 14); //PC14 Output push-pull (reset state)
GPIOC->OSPEEDR |= (0b11 << 28); //PC14 very high GPIO speed
GPIOC->PUPDR |= (0b10 << 28); //PC14 pull down resistors
//PC15 OUTPUT
GPIOC->MODER |= (0b01 << 30); //PC15 General purpose output mode
GPIOC->OTYPER &= ~ (1 << 15); //PC15 Output push-pull (reset state)
GPIOC->OSPEEDR |= (0b11 << 30); //PC15 very high GPIO speed
GPIOC->PUPDR |= (0b10 << 30); //PC15 pull down resistors
//PE4 OUTPUT
GPIOE->MODER |= (0b01 << 8); //PE4 General purpose output mode
GPIOE->OTYPER &= ~ (1 << 4); //PE4 Output push-pull (reset state)
GPIOE->OSPEEDR |= (0b11 << 8); //PE4 very high GPIO speed
GPIOE->PUPDR |= (0b10 << 8); //PE4 pull down resistors
//PE0 OUTPUT
GPIOE->MODER |= (0b01 << 0); //PE0 General purpose output mode
GPIOE->OTYPER &= ~ (1 << 0); //PE0 Output push-pull (reset state)
GPIOE->OSPEEDR |= (0b11 << 0); //PE0 very high GPIO speed
GPIOE->PUPDR |= (0b10 << 0); //PE0 pull down resistors
//-----------------------------------------------------------------------------------
//PE5 INPUT ALL BUTTONS EXTERNALLY PULLED UP
GPIOE->MODER |= (0b00 << 10); //PE5 General purpose input mode
//PE6 INPUT
GPIOE->MODER |= (0b00 << 12); //PE6 General purpose input mode
}
void UART1_Setup(){
RCC->AHB1ENR |= (1 << 0); //Enable clock for GPIO bank A
RCC->APB2ENR |= (1 << 4); //USART1 clock enabled
//TX1 at pin PA9 and RX1 at pin PA10
GPIOA->AFR[1] |= (0b0111 << 4); //Select alternate function as UART on pin PA9
GPIOA->AFR[1] |= (0b0111 << 8); //Select alternate function as UART on pin PA10
GPIOA->OSPEEDR |= (0b11 << 18); //Highest speed at pin PA9
GPIOA->OSPEEDR |= (0b11 << 20); //Highest speed at pin PA10
GPIOA->PUPDR |= (0b01 << 18); //Pulldown resistor on PA9
GPIOA->PUPDR |= (0b01 << 20); //Pulldown resistor on PA10
GPIOA->MODER |= (0b10 << 18); //Assign alternate function of UART to pin PA9
GPIOA->MODER |= (0b10 << 20); //Assign alternate function of UART to pin PA10
//UART configuration, CR1 and CR2 registers are all zero on reset
/*
USART1->CR1 = 0x00; //Reset register just in case
USART1->CR1 &= ~ (1 << 28); //M[1:0] = 00: 1 Start bit, 8 data bits,
USART1->CR1 &= ~ (1 << 12); //M[1:0] = 00: 1 Start bit, 8 data bits,
USART1->CR2 &= ~ (1 << 13); //1 Stop bit
USART1->CR2 &= ~ (1 << 12); //1 Stop bit
USART1->CR1 &= ~ (1 << 10); //Parity control disabled
USART1->CR1 &= ~ (1 << 15); //Oversampling by 16
*/
USART1->BRR = (USART1->BRR & ~0xFFFF) | (0x364 & 0xFFFF); //fclk/baud rate*2 200MHz / 115200*2
USART1->CR1 |= (1 << 3); //Transmitter is enabled
USART1->CR1 |= (1 << 2); //Receiver is enabled
USART1->CR1 |= (1 << 0); //USART enable
//Receive data interrupt setup
RCC->APB2ENR |= (1 << 14); //System configuration controller clock enabled
USART1->CR1 |= (1 << 5); //UART1 Receiver buffer not empty interrupt enable
NVIC_SetPriority(USART1_IRQn, 0);
NVIC_EnableIRQ (USART1_IRQn);
}
void UART1_Send(uint8_t character){
USART1->TDR = character; //Load data to transmit register
while(!(USART1->ISR & (1 << 6))); //Wait until transmission is executed
}
uint8_t UART1_Receive (void){
uint8_t character;
while(!(USART1->ISR & (1 << 5))); //Wait receive buffer fills up
character = USART1->RDR; //Read the receive register
return character;
}
void ADC_Setup (void){
//PA1 setup as analog pin
GPIOA->MODER |= (0b11 << 2); //Assign analog function to pin PA1
//Clock for GPIOA is already enabled
RCC->APB2ENR |= (1 << 8); //ADC1 clock enabled
ADC->CCR = (0b01 << 16); //Set ADC clock prescaler (APB2CLK/4)
ADC1->CR1 = (0b00 << 24); //Set 12-bit ADC resolution
ADC1->CR2 = (1 << 10); //The EOC bit is set at the end of each regular conversion
ADC1->SMPR2 = (0b111 << 3); //Sample time set to 480 cycles at 25 MHz
ADC1->SQR1 = (0b0000 << 20); //Using only one conversion
ADC1->CR2 |= (1 << 0); //Enable ADC module
delay_ms(1);
}
uint16_t AD_Conversion (void){
ADC1->SQR3 = 0; //Reset
ADC1->SQR3 |= (1<<0); //First channel conversion on regular sequence
ADC1->SR = 0; //Clear status register
ADC1->CR2 |= (1 << 30); //Start the conversion
while(!(ADC1->SR & (1 << 1))); //Waiting for conversion to execute
return (ADC1->DR & 0xFFFF);
}
void DAC_Setup (void){
//PA4 setup as analog pin for DAC_OUT1
//RCC->AHB1ENR = (1 << 0); //Enable clock for GPIO bank A
//Clock for GPIO A is already enabled in the UART setup function
GPIOA->MODER |= (0b11 << 8); //Assign analog function to pin PA4
/*Setup down bellow was used to test this pin as a GPIO, and it worked fine
GPIOA->MODER |= (0b01 << 8); // General purpose output mode
GPIOA->OTYPER &= ~ (1 << 4); // Output push-pull (reset state)
GPIOA->OSPEEDR |= (0b11 << 8); // very high GPIO speed
GPIOA->PUPDR |= (0b10 << 8); // pull down resistors
*/
DAC->CR = 0x0; //Reset whole register just for safety
//Since whole CR is reset, wave generation is off, output buffer is enabled
DAC->CR |= (1 << 2); //Enable trigger
DAC->CR |= (0b111 << 3); //Select software as trigger source
DAC->CR |= (1 << 0); //Enable DAC1
}
void DAC_execution (uint16_t value){
DAC->DHR12R1 = value; //Enter DAC value
DAC->SWTRIGR = (1 << 0); //Software trigger is set
}
void USART1_IRQHandler(void){
//GPIOC->BSRR |= (1 << 14);
//uint8_t received_character;
//KEYBOARD ECHO code
if (USART1->ISR & (1 << 5)){
while(!(USART1->ISR & (1 << 5))); //Wait receive buffer fills up
received_character = USART1->RDR; //Read the receive register
UART1_Send(received_character); //Echo received data
}
//delay_ms(10);
//GPIOC->BSRR |= (1 << 30);
}
int main (void){
Core_Clock_Setup();
Timer1_Setup();
GPIO_Setup();
UART1_Setup();
ADC_Setup ();
DAC_Setup ();
//GPIOC->BSRR = 0; //Initial value (important for background led toggling)
while(1){
//Just some random patterns to generate voltages
for (int i=0; i<3600; i++){
DAC_execution(i);
delay_ms(i);
}
for (int i=4096; i>260; i--){
DAC_execution(i);
delay_ms(i);
}
for (int i=700; i>1900; i++){
DAC_execution(i);
delay_ms(i);
}
}
}
2024-12-11 06:15 AM
Hello @Vilius
Please refer to the LL DAC examples Projects/STM32F767ZI-Nucleo/Examples_LL/DAC available on STM32Cube firmware F7 and update your DAC functions accordingly.
2024-12-11 07:25 AM
For a simple DC output without DMA, the F7 DAC is quite simple to use.
So check if the DAC peripheral clock is set and enabled, if the GPIO is set correctly.
I suggest to use some UART IO to check registers and change DAC level.
void DAC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* Peripheral clock enable */
__HAL_RCC_DAC_CLK_ENABLE();
/**DAC GPIO Configuration
PA4 ------> DAC_OUT1
PA5 ------> DAC_OUT2
*/
GPIO_InitStruct.Pin = DAC1_OUTPUT_Pin | DAC2_OUTPUT_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(DAC12_OUTPUT_Port, &GPIO_InitStruct);
/* DAC direct register settings */
DAC->CR = 0;
/* channel 1, enable only, DC output, no trigger, no DMA */
DAC->CR |= DAC_CR_EN1;
}
/* DAC 1 set DC value */
uint8_t DAC1_set_DC(uint16_t u16DcVal)
{
/* check input value */
if( u16DcVal > DAC_VAL_INT_MAX )
{
return 1;
}
/* write data to right aligned 12 bit register */
DAC->DHR12R1 = (uint32_t)u16DcVal;
return 0;
}
2024-12-11 10:16 AM
>>The code might look really long and complicated..
More sadly, for bare metal, it looks woefully inefficient
2024-12-11 10:41 AM
As @LCE said above, you need to set DAC clock in RCC first.
Style: Don't use "magic numbers", use symbols from the CMSIS mandated device header. Don't repeatedly RMW registers, calculate a value (by ORing together all fields you want to set) and perform a single write to the register.
JW