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
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
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.