DAC

STM programming ADC and true DAC

True DAC or PWM dac?!

Depends of which board we have. If chip is STM32f103c8, then there is no DAC on it, but we can mimic DAC by using PWM. If board has stm32f103vet6 on the other hand, it has DAC and we can  use both channels as stereo.  Please watch video first, then it will be clear why we need move SPI pins with AFIO_MAPR function to another place – both shares the same pins.

#include "stm32f10x.h"
#include "printMsg.h"
#include "delayUs.h"
#include "adc.h"
#include "dac.h"
#include "spi.h"
#include "sample.h"
#include "pwm_as_dac.h"

int main(void)
{
	usart_1_enable();
	timer2enablePWM();
	spiEnable();
	adcEnable(); 
	dacEnable(); 
	//setModule(433.120,0); // (frequency in MHz, power in steps from 0 to 7, or 1.26 mW to 100 mW)
	
	while(1) 
	{
		//adc();
		
		for (int i=0;i<raw;i++)
		{
			PWMdac(rawData[i]/4,rawData[i]/4); //since our PWM DAC imitation has only 8 bit resolution, we need to divide 4096 / 4
			dac(rawData[i]*10-2048,rawData[i]*10-2048); //8 bits to 12 bits resolution, but it may be needed to amplify by multiplying *10 and offset -2400
			delay(330);
		}
		dac(left,right); //normal DAC, if inputs are 12 bits, the output is also 12 bit
		delay(50000000); //long delay between two plays
	}
	
}
#include "stm32f10x.h"
#include "delayUs.h"
#include "adc.h"
#include "dac.h"

int left=0;
int right=0;

void adcEnable(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_ADC1EN |RCC_APB2ENR_ADC2EN | RCC_APB2ENR_AFIOEN; //enabling ADC clock, interrupt enable, 
	RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;// ADC clock = 12 MHz, maximum is 14, but there is no divider for that freq (72MHz / 6 = 12MHz). works with div6 too
	//while clock for port A and B is enabled down below
	// pin B0 is analog input, no need special GPIO setting
	// pin B1 is analog input, no need special GPIO setting
	
	ADC1->CR1 |=ADC_CR1_EOCIE;   //ADC interrupt enabled
	ADC2->CR1 |=ADC_CR1_EOCIE;   //ADC interrupt enabled
	//NVIC_EnableIRQ(ADC1_2_IRQn); //interrupt enabled
	
	ADC1->SMPR2 |= ADC_SMPR2_SMP8_0;//|ADC_SMPR2_SMP8_1|ADC_SMPR2_SMP8_2;
	ADC2->SMPR2 |= ADC_SMPR2_SMP8_0;//|ADC_SMPR2_SMP8_1|ADC_SMPR2_SMP8_2;

	ADC1->SQR3 |=8; //L B0
  ADC2->SQR3 |=9; //R B1
	ADC1->CR2 &= ~ADC_CR2_ALIGN; //data is right aligned (0bxxxx111111111111)
	ADC2->CR2 &= ~ADC_CR2_ALIGN; 

	ADC1->CR2 |= ADC_CR2_ADON | ADC_CR2_CONT; //ADC converter is on
	ADC2->CR2 |= ADC_CR2_ADON | ADC_CR2_CONT;
	delay(1000); //alow ADC to stabilize - 1 mS, but my delay is not exactly 1 mS, it is much shorter...
  ADC1->CR2 |= ADC_CR2_CAL;
	ADC2->CR2 |= ADC_CR2_CAL;
	delay(1000); //it is better to leave some time, just few clock cycles...
	ADC1->CR2 |= ADC_CR2_ADON; //not sure it requires to call it again?
	ADC2->CR2 |= ADC_CR2_ADON; 
	delay(1000); //After first ADON, ADC is just set, then second time ADC is actually enabled
}

/* For enabling IRQ, uncomment three lines above */
void ADC1_2_IRQHandler(void)
{ 
    left=ADC1->DR;
		right=ADC2->DR;
} 

/* Manual call of the ADC conversion channels */
void adc(void)
{
	ADC1->CR2 |=ADC_CR2_SWSTART;
	ADC2->CR2 |=ADC_CR2_SWSTART;
	left=ADC1->DR;
  right=ADC2->DR;
}
#ifndef adc_h
#define adc_h

extern int left;
extern int right;

void adcEnable(void);
void adc(void);

#endif
#include "stm32f10x.h"
#include "printMsg.h"
#include "delayUs.h"

void dacEnable(void)
{
	
	RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;// trying to divide clock by 2 for APB1, for max freq of 36 MHz
	RCC->APB1ENR |= RCC_APB1ENR_DACEN; //DAC interface clock enable
	RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
		
	DAC->CR |= DAC_CR_BOFF1|DAC_CR_BOFF2; //DAC buffer
	//DAC->CR |= DAC_CR_WAVE2_1|DAC_CR_WAVE2_0|DAC_CR_WAVE1_1|DAC_CR_WAVE1_0;
	//DAC->CR |= DAC_CR_MAMP1_3|DAC_CR_MAMP2_1|DAC_CR_MAMP1_1|DAC_CR_MAMP1_0; //maximum aplitude?
	//DAC->CR |= DAC_CR_MAMP2_3|DAC_CR_MAMP2_2|DAC_CR_MAMP2_1|DAC_CR_MAMP2_0; //maximum aplitude?
	DAC->CR |= DAC_CR_TSEL1_0|DAC_CR_TSEL1_1|DAC_CR_TSEL1_2; //software trigger 0b111
  DAC->CR |= DAC_CR_TSEL2_0|DAC_CR_TSEL2_1|DAC_CR_TSEL2_2; //software trigger 0b111
	DAC->CR |= DAC_CR_TEN1|DAC_CR_EN1; //software trigger for DAC1 enable, DAC1 enable
	DAC->CR |= DAC_CR_TEN2|DAC_CR_EN2; //software trigger for DAC2 enable, DAC2 enable
}

void dac(int left, int right)
{
	DAC->SWTRIGR |= DAC_SWTRIGR_SWTRIG1;
	DAC->SWTRIGR |= DAC_SWTRIGR_SWTRIG2; //not sure which trigger, so both...
	DAC->DHR12R1 = left; //currently left channel, pin A4
	DAC->DHR12R2 = right; //currently right channel, pin A5
}
#ifndef dac_h
#define dac_h

extern int audio;
void dacEnable(void);
void dac(int left,int right);

#endif
#include "stm32f10x.h"
#include "adc.h"

void timer2enablePWM(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; //port B clock enabled (3), port A clock enable (2), Alternate IO clock enable (0)
	RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //timer 2 clock enable (2)
	
	GPIOA->CRL |= GPIO_CRL_CNF0_1|GPIO_CRL_MODE0_0|GPIO_CRL_MODE0_1;
	GPIOA->CRL &= ~(GPIO_CRL_CNF0_0);  //PA0
	
  GPIOA->CRL |= GPIO_CRL_CNF1_1|GPIO_CRL_MODE1_0|GPIO_CRL_MODE1_1;
	GPIOA->CRL &= ~(GPIO_CRL_CNF1_0); //PA1
	
  TIM2->CCER |= TIM_CCER_CC1E; //capture/compare timer1 output enable
	TIM2->CCER |= TIM_CCER_CC2E; //capture/compare timer2 output enable
	
	TIM2->CR1 |= TIM_CR1_ARPE; //auto reload preload enable TIMxARR is buffered
	TIM2->CCMR1 |= TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2PE; //output compare 2 mode 0b110 (14:12)?, output compare 2 preload enable
	TIM2->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE;
	
	
	//PWM freq = Fclk/PSC/ARR  72MHz/1000
	//PWM Duty = CCR1/ARR
	TIM2->PSC = 0; //prescaler value, 72 MHz divided by:
	TIM2->ARR = 1024; //auto reload register, value of 1024 with prescaler value 0 result in PWM frequency of 70 kHz
	//TIM2->CCR1= 512; //first of two channels
	//TIM2->CCR2= 512; //capture/compare value, duty cycle (disabled here, enabling in call function in DSP_1.c)
	TIM2->EGR |= TIM_EGR_UG; //update generation, re-initialize
	TIM2->CR1 |= TIM_CR1_CEN; //counter enabled
}

void PWMdac(int left, int right)
{
	TIM2->CCR1 = left;  //duty cycle
	TIM2->CCR2 = right; //duty cycle
}
#ifndef pwm_as_dac_h
#define pwm_as_dac_h

void timer2enablePWM(void);
void PWMdac(int left,int right);

#endif
/* This software is provided by https://wildlab.org and
   Milan Karakas from Croatia. This is free program, but 
	 also "beerware". This means if you want this beta test
	 phase to grow into something really great, please consider 
	 some donation here: 
	 https://www.paypal.me/milankarakas?locale.x=en_US
	 It is still in beta test phase, and will be upgraded. 
	 So far it works at 433.120 MHz, and you need to change 
	 the frequency if you wish to something else - in library
	 'Si4432.c', there is math and you should to calculate 
	 and change values of the register for other frequencies. 
	 Out there exist also Si4432 - 868 MHz module, and this 
	 program works with this one too, but then you MUST change
	 the frequency, else output TX amplifier may burn. 
	 Later, will ad my own math to do that, but since it is still
	 in beta testing phase... stay tuned.
*/

#include "stm32f10x.h"

void spiEnable(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPAEN|RCC_APB2ENR_IOPBEN|RCC_APB2ENR_AFIOEN; //already exist, but anyway...
	/* Remapping SPI pins */
	AFIO->MAPR |=AFIO_MAPR_SWJ_CFG_1; //first we should to enable afio clock (above), then this one "JTAG-DP Disabled and SW-DP Enabled", releasing PA4 and PA5
	AFIO->MAPR |=AFIO_MAPR_SWJ_CFG_0|AFIO_MAPR_SPI1_REMAP; //moving SPI1 from PA4/PA5/PA6/PA7 to PA15/PB3/PB4/PB5 (PA15=NSS, PB3=SCK, PB4=MISO, PB5=MOSI
	
	/* GPIO pin A15 (was A4) is NSS ("Not" Slave Sellect), inverted... if low (0), then it is selected */
	GPIOA->CRH |= GPIO_CRH_CNF15_1|GPIO_CRH_MODE15_0|GPIO_CRH_MODE15_1;// 50 MHz - NSS (nSEL on Si4432)
	GPIOA->CRH &= ~GPIO_CRH_CNF15_0; //alternate function PP, this A4->moved to A15 pin must be high with external resistor 3kOhm to 4.7kOhm
	/* GPIO pin B3 (was A5) is SPI clock SCK */
	GPIOB->CRL |= GPIO_CRL_CNF3_1|GPIO_CRL_MODE3_0|GPIO_CRL_MODE3_1;// 50 mhz - SCK A5->moved to B3
	GPIOB->CRL &= ~GPIO_CRL_CNF3_0; //alternate function PP
	/* GPIO pin B4 (was A6) is MISO */
	GPIOB->CRL &= ~(GPIO_CRL_MODE4_0|GPIO_CRL_MODE4_1); // INPUT - MISO, A6->moved to B4
	GPIOB->CRL |= GPIO_CRL_CNF4_0;
		
	/* GPIO pin B5 (was A7) is MOSI */
	GPIOB->CRL |= GPIO_CRL_CNF5_1|GPIO_CRL_MODE5_0|GPIO_CRL_MODE5_1;// 50 MHz - MOSI, A7->moved to B5
	GPIOB->CRL &= ~GPIO_CRL_CNF5_0; //alternate function PP
	
	/*  Configuring SPI */
	RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;// SPI enable
	SPI1->CR1 |= SPI_CR1_BR_2;//|SPI_CR1_BR_1;//|SPI_CR1_BR_1|SPI_CR1_BR_0; // 72MHz/?
	SPI1->CR1 &= ~(SPI_CR1_CPOL|SPI_CR1_CPHA|SPI_CR1_DFF); // modes, 0 for polarity, and 0 for iddle clock=0, dff=0 (8 bit), lsbfirst=0 (MSB first), *
	SPI1->CR2 |= SPI_CR2_SSOE; //NSS enabled         
	SPI1->CR1 |= SPI_CR1_MSTR;// master configuration  
}

void spiWrite(int reg, int cmd)
{
  SPI1->CR1 |= SPI_CR1_SPE;
	SPI1->DR = (0x80|reg); //usually 0x80 does not belongs to SPI, but here it is "command mode", specific for Si4432
	while (!(SPI1->SR & SPI_SR_TXE)){}; 
	SPI1->DR = cmd; 
	while (!(SPI1->SR &SPI_SR_TXE)){}; 
	while(SPI1->SR & SPI_SR_BSY);
	while (!(SPI1->SR & SPI_SR_OVR)){}; //if single byte is sent,...
  SPI1->CR1 &= ~SPI_CR1_SPE;
}

int spiRead(int reg)
{
	SPI1->CR1 |= SPI_CR1_SPE;
	SPI1->DR =reg;
	while (!(SPI1->SR & SPI_SR_TXE)){};
	while(SPI1->SR & SPI_SR_BSY){}; //waiting little bit longer between two sendings
  SPI1->DR = 0xFF; //dummy byte
	while (!(SPI1->SR & SPI_SR_RXNE)){};
	int rd=SPI1->DR; //dummy read? Whole thing does not working properly without
	while (!(SPI1->SR & SPI_SR_RXNE)){};
  int r=SPI1->DR;
	SPI1->CR1 &= ~SPI_CR1_SPE;
	return r;
}
#ifndef spi_h
#define spi_h

extern void spiEnable(void);
extern void spiWrite(int reg, int cmd);
extern int spiRead(int reg);

#endif

Sorry, but last file “sample.h” is too long for this page, so it is in separate window (just select all, copy and paste into your sample.h file:  sample

2 thoughts on “STM programming ADC and true DAC”

  1. Hey Milan,
    Your project is great! I am trying to run your code for adc but I get a very weird problem. On KEIL debugger window int left and int right values are randomly constantly changing. If I try to attach a potentiometer to B0 or B1 nothing happens and they are still changing. Do you have any idea how I can fix this problem?
    I am using these files: main.c, adc.c, adc.h

    1. You should to include all other libraries, there are many, not only three. Everything is related. Each of those files should be in the same directory.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.