Recent

Author Topic: STM32 getting ADC to work in FPC [solved]  (Read 1931 times)

petex

  • Jr. Member
  • **
  • Posts: 69
STM32 getting ADC to work in FPC [solved]
« on: January 29, 2021, 09:54:28 pm »
hello,
I converted the ADC module and parts of the RCC module from the STM electronics library (written in C) to free pascal.

Now that my debugger is working I am trying to get it to work. I have a simple example to read the ADC.

The control block for the ADC is located at $40012400 which agrees with the spec. However it looks like that when I write to the registers, the contents can not be read back.
I issue the convert command and wait forever for the status to change. I don't think this part of the memory map is enabled ??

Code: Pascal  [Select][+][-]
  1.     ADC_DeInit(ADC1); //Power-on default
  2.  
  3.     RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, xENABLE);
  4.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, xENABLE);
  5.  
  6.     ADC_InitStructure.ADC_Mode := ADC_Mode_Independent; //Independent conversion mode (single)
  7.     ADC_InitStructure.ADC_ScanConvMode := xDISABLE; //Convert single channel only
  8.     ADC_InitStructure.ADC_ContinuousConvMode := xDISABLE; //Convert 1 time
  9.     ADC_InitStructure.ADC_ExternalTrigConv := xDISABLE; //No external triggering
  10.     ADC_InitStructure.ADC_DataAlign := ADC_DataAlign_Right; //Right 12-bit data alignment
  11.     ADC_InitStructure.ADC_NbrOfChannel := 1; //single channel conversion
  12.     ADC_Init(ADC1, ADC_InitStructure);
  13.  
  14.     ADC_TempSensorVrefintCmd(xENABLE); //wake up temperature sensor
  15.     ADC_Cmd(ADC1, xENABLE); //Enable ADC1
  16.     ADC_ResetCalibration(ADC1); //Enable ADC1 reset calibration register
  17.     while (ADC_GetResetCalibrationStatus(ADC1) = xRESET) do; //check the end of ADC1 reset calibration register
  18.  
  19.     ADC_StartCalibration(ADC1); //Start ADC1 calibration
  20.     while (ADC_GetCalibrationStatus(ADC1) = xRESET) do; //Check the end of ADC1 calibration
  21.  
  22.     ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_1Cycles5); //Select 1.5 cycles conversion for channel 16
  23.     ADC_SoftwareStartConvCmd(ADC1, xENABLE); //Start ADC1 software conversion
  24.     while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) = xRESET) do; //Wail for conversion complete
  25.  
  26.     an_val := ADC_GetConversionValue(ADC1); //Read ADC value
  27.     ADC_ClearFlag(ADC1, ADC_FLAG_EOC); //Clear EOC flag  
  28.  
« Last Edit: February 01, 2021, 09:24:55 pm by petex »

kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Re: STM32 getting ADC to work in FPC
« Reply #1 on: January 29, 2021, 11:42:58 pm »
Hello petex,

which device are you using? There is a partial translation of the Standard Peripheral Library for the STM32F103.

https://github.com/Turro75/stm32lazarustemplate/blob/master/stm32f103fw.pas

I once wrote a little programm to capture analog signals, see below my ADC and timer unit. The conversion is automaticly triggered by the timer-overflow. The ADC value is collected in the interrupt. There are two ADCs running synchronously. Each with one channel. The clocks for ADC, GPIO and Timer you have to enable seperately.

Hope it helps.

Code: Pascal  [Select][+][-]
  1. unit uADC;
  2.  
  3. {$mode objfpc}
  4.  
  5. interface
  6.  
  7. Procedure ADCInit(AChannelKanal1,AChannelKanal2: byte);
  8. Procedure ADCSingleConversion;
  9.  
  10. var ADCOnConversionsComplete: Procedure;
  11.  
  12. const AnalogKanal1Pin = GPIO_Pin_0;
  13. const AnalogKanal2Pin = GPIO_Pin_1;
  14. var   AnalogKanal1Port: TPortRegisters absolute PortB;
  15. var   AnalogKanal2Port: TPortRegisters absolute PortB;
  16.  
  17. var
  18.   ADC1Result: uInt16;
  19.   ADC2Result: uInt16;
  20.  
  21.  
  22.  
  23. IMPLEMENTATION
  24.  
  25. uses
  26.   stm32f103fw,STM32F10x_BitDefs,cortexm3;
  27.  
  28. //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  29. Procedure ADCInit(AChannelKanal1,AChannelKanal2: byte);
  30. var
  31.   ADC_InitStructure: TADC_InitTypeDef;
  32.   NVIC_InitStructure: TNVIC_InitTypeDef;
  33.   GPIO_InitStructure: TGPIO_InitTypeDef;
  34. begin
  35.  
  36.   // Analog Eingaenge Kanal 1 und 2
  37.   GPIO_InitStructure.GPIO_Pin := AnalogKanal1Pin;
  38.   GPIO_InitStructure.GPIO_Mode := GPIO_Mode_AIN;    //Analog In
  39.   GPIO_InitStructure.GPIO_Speed := GPIO_Speed_50MHz;
  40.   GPIO_Init(AnalogKanal1Port, GPIO_InitStructure);
  41.  
  42.   GPIO_InitStructure.GPIO_Pin := AnalogKanal2Pin;
  43.   GPIO_InitStructure.GPIO_Mode := GPIO_Mode_AIN;    //Analog In
  44.   GPIO_InitStructure.GPIO_Speed := GPIO_Speed_50MHz;
  45.   GPIO_Init(AnalogKanal1Port, GPIO_InitStructure);
  46.  
  47.   // ADCCLK = PCLK2/2
  48.   RCC_ADCCLKConfig(RCC_PCLK2_Div4);
  49.  
  50.   ////ADC1
  51.   ADC_InitStructure.ADC_Mode:= ADC_Mode_Independent;  //ADC1 unabhängig von ADC2, nicht dual-mode
  52.   ADC_InitStructure.ADC_ScanConvMode:= DISABLED;       //nicht Nur ein Kanal
  53.   ADC_InitStructure.ADC_ContinuousConvMode:= DISABLED;//Nur einzelne Wandlung
  54.   ADC_InitStructure.ADC_ExternalTrigConv:= ADC_ExternalTrigConv_T1_CC1;//Timer1 Kanal1 triggert
  55.   ADC_InitStructure.ADC_DataAlign:= ADC_DataAlign_Right;
  56.   ADC_InitStructure.ADC_NbrOfChannel:= 1;
  57.   ADC_Init(ADC1, ADC_InitStructure);
  58.  
  59.   // ADC1 regular channel14 configuration
  60.   ADC_RegularChannelConfig(ADC1, AChannelKanal1, 1, ADC_SampleTime_28Cycles5);
  61.   // Enable ADC1 external trigger
  62.   ADC_ExternalTrigConvCmd(ADC1, ENABLED);
  63.   // Enable EOC interrupt
  64.   ADC_ITConfig(ADC1, ADC_IT_EOC, DISABLED);
  65.  
  66.   // Enable ADC1
  67.   ADC_Cmd(ADC1, ENABLED);
  68.  
  69.   {////Injected Kanäle
  70.   ADC_InjectedSequencerLengthConfig(ADC1,2);
  71.   ADC_InjectedChannelConfig(ADC1, AChannelKanal2,1,ADC_SampleTime_13Cycles5);
  72.   ADC_InjectedChannelConfig(ADC1, AChannelKanal2,2,ADC_SampleTime_13Cycles5);
  73.  
  74.   // Interrupt für Injected-Gruppe
  75.   ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T2_CC1);
  76.   ADC_ExternalTrigInjectedConvCmd(ADC1,ENABLED);
  77.   ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLED);  }
  78.  
  79.   ////ADC2
  80.   ADC_InitStructure.ADC_Mode:= ADC_Mode_Independent;  //ADC1 unabhängig von ADC2, nicht dual-mode
  81.   ADC_InitStructure.ADC_ScanConvMode:= DISABLED;       //nicht Nur ein Kanal
  82.   ADC_InitStructure.ADC_ContinuousConvMode:= DISABLED;//Nur einzelne Wandlung
  83.   ADC_InitStructure.ADC_ExternalTrigConv:= ADC_ExternalTrigConv_T1_CC1;//Timer1 Kanal1 triggert
  84.   ADC_InitStructure.ADC_DataAlign:= ADC_DataAlign_Right;
  85.   ADC_InitStructure.ADC_NbrOfChannel:= 1;
  86.   ADC_Init(ADC2, ADC_InitStructure);
  87.  
  88.   // ADC1 regular channel14 configuration
  89.   ADC_RegularChannelConfig(ADC2, AChannelKanal2, 1, ADC_SampleTime_28Cycles5);
  90.   // Enable ADC external trigger
  91.   ADC_ExternalTrigConvCmd(ADC2, ENABLED);
  92.   // Enable EOC interrupt
  93.   ADC_ITConfig(ADC2, ADC_IT_EOC, ENABLED);
  94.  
  95.   // Enable ADC1
  96.   ADC_Cmd(ADC2, ENABLED);
  97.  
  98.  
  99.  
  100.   ////ADC1+2
  101.   // ADC-NVIC-Konfiguration
  102.   NVIC.IP[ADC1_2_IRQn]:= $10;     //IRQ-Priority, right 4bits always to be zero,low value=high prio
  103.   NVIC.ISER[ADC1_2_IRQn shr 5] := 1 shl (ADC1_2_IRQn and $1F); //Enable Interrupt
  104.  
  105.  
  106.   //ADC_SoftwareStartConvCmd(ADC1, ENABLED);
  107.  
  108.   // Enable ADC1 reset calibration register
  109.   //ADC_ResetCalibration(ADC1);
  110.   // Check the end of ADC1 reset calibration register
  111.   //while(ADC_GetResetCalibrationStatus(ADC1));
  112.  
  113.   // Start ADC1 calibration
  114.   //ADC_StartCalibration(ADC1);
  115.   // Check the end of ADC1 calibration
  116.   //while(ADC_GetCalibrationStatus(ADC1));
  117.  
  118.  
  119. end;
  120.  
  121. //---------------------------------------------------------
  122. procedure ADCSingleConversion;
  123. begin
  124.   ADC_SoftwareStartConvCmd(ADC1, ENABLED);
  125. end;
  126.  
  127.  
  128. //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  129. //++ INTERRUPT SERVICE ROUTINE
  130. procedure ADC_ISR; Alias: 'ADC1_and_ADC2_global_interrupt'; Interrupt; Public;
  131. var Timeout: Integer;
  132. begin
  133.   //comvisu.cmdLn('ADC1ISR');
  134.  
  135.   Timeout:= 20;
  136.   while (ADC1.SR and ADC1.SR and ADC_SR_EOC) = 0
  137.   do begin Timeout:= Timeout-1; if Timeout = 0 then BREAK; end;
  138.  
  139.   if (ADC1.SR and ADC_SR_EOC) > 0
  140.   then ADC1Result:= ADC1.DR else ADC1Result:= $FFFF;
  141.   if (ADC2.SR and ADC_SR_EOC) > 0
  142.   then ADC2Result:= ADC2.DR else ADC2Result:= $FFFF;
  143.  
  144.   if assigned(ADCOnConversionsComplete) then ADCOnConversionsComplete;
  145.   ADC_ClearITPendingBit(ADC1,ADC_IT_EOC);
  146.   ADC_ClearITPendingBit(ADC2,ADC_IT_EOC);
  147.  
  148. end;
  149.  
  150. //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  151.  
  152. end.

Code: Pascal  [Select][+][-]
  1. unit uTimer1;
  2. {$mode objfpc}
  3. interface
  4. uses
  5.   STM32F10x_BitDefs, cortexm3;
  6.  
  7. var Timer1OnTimerCompare: Procedure;
  8.  
  9.     Procedure Timer1Init;
  10.     procedure Timer1SetPrescaler(AValue: uInt16);
  11.     procedure Timer1SetAutoReloadRegister(AValue: uInt16);
  12.     Procedure Timer1Enable;
  13.     Procedure Timer1Disable;
  14.  
  15.  
  16. //##############################################################################
  17. IMPLEMENTATION
  18. uses
  19.   stm32f103fw;
  20.  
  21. //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  22. procedure Timer1Init;
  23. var
  24.   GPIO_InitStructure: TGPIO_InitTypeDef;
  25. begin
  26.   Timer1OnTimerCompare:= nil;
  27.  
  28.   //RCC enable for Timer1
  29.   RCC.APB2ENR:= RCC.APB2ENR or RCC_APB2ENR_TIM1EN;
  30.  
  31.   ////Timer settings ----------------
  32.   //Various settings in default
  33.   Timer1.CR1:=   0;         //Control register 1; Wait with timer enable until settings complete
  34.   Timer1.CR2:=   0;         //Control register 2
  35.   Timer1.CCMR1:=                   //Capture/compare mode register;
  36.                  TIM_CCMR1_OC1PE   //oc preload enable
  37.               or TIM_CCMR1_OC1M_1  //PWM-Mode 1
  38.               or TIM_CCMR1_OC1M_2;
  39.   Timer1.CCER:=  TIM_CCER_CC1E; //Capture/compare enable register; CC1-output-enable}
  40.  
  41.   //Capture compare Register um Flanken zu bekommen
  42.   Timer1.CCR1:= 2;
  43.  
  44.   //Prescaler
  45.   Timer1.PSC:= 2000;
  46.   //AutoReload value
  47.   Timer1.Arr:= 10000;
  48.  
  49.   Timer1.DIER:= TIM_DIER_CC1IE; //Compare 1 interrupt enable
  50.   Timer1.EGR:= Timer1.EGR or TIM_EGR_UG; //Werte übernehmen
  51.   Timer1.BDTR:= Timer1.BDTR or TIM_BDTR_MOE; //Output enable
  52.  
  53.   // NVIC configuration
  54.   NVIC.IP[TIM1_CC_IRQn]:= $20;     //IRQ-Priority, right 4bits always to be zero,low value=high prio
  55.   NVIC.ISER[TIM1_CC_IRQn shr 5] := 1 shl (TIM1_CC_IRQn and $1F); //Enable Interrupt
  56.  
  57.   //Enable Counter
  58.   Timer1.CR1:= Timer1.CR1 or TIM_CR1_CEN;
  59.  
  60.   {a pwm mode in CCMR
  61.   CC1EN in Tim1_CCER }
  62.  
  63.  
  64.  
  65.   // PA8 PWM-output
  66.   GPIO_InitStructure.GPIO_Pin := GPIO_Pin_8;
  67.   GPIO_InitStructure.GPIO_Speed := GPIO_Speed_50MHz;
  68.   GPIO_InitStructure.GPIO_Mode := GPIO_Mode_AF_PP;
  69.   GPIO_Init(PortA, GPIO_InitStructure);
  70.  
  71.  
  72. end;
  73.  
  74. //---------------------------------------------------------
  75. procedure Timer1SetPrescaler(AValue: uInt16);
  76. begin
  77.   Timer1Disable;
  78.   Timer1.PSC:= AValue;
  79.   Timer1Enable;
  80. end;
  81.  
  82. //---------------------------------------------------------
  83. procedure Timer1SetAutoReloadRegister(AValue: uInt16);
  84. begin
  85.   Timer1Disable;
  86.   Timer1.Arr:= AValue;
  87.   Timer1Enable;
  88. end;
  89.  
  90. //---------------------------------------------------------
  91. procedure Timer1Enable;
  92. begin Timer1.CR1:= Timer1.CR1 or TIM_CR1_CEN; end;
  93.  
  94. //---------------------------------------------------------
  95. procedure Timer1Disable;
  96. begin Timer1.CR1:= Timer1.CR1 and not TIM_CR1_CEN; end;
  97.  
  98. //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  99. //++ INTERRUPT SERVICE ROUTINE
  100. procedure TIM1_ISR; Alias: 'TIM1_Capture_Compare_interrupt'; Interrupt; Public;
  101. begin
  102.   Timer1.SR:= Timer1.SR and not TIM_SR_CC1IF;
  103.  
  104.   if assigned(Timer1OnTimerCompare) then Timer1OnTimerCompare;
  105.  
  106. end;
  107.  
  108. //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  109. end.

Code: Pascal  [Select][+][-]
  1.    //Timer1
  2.    Timer1Init;
  3.    Timer1OnTimerCompare:= @TimerCompareHandler;
  4.  
  5.    //ADC
  6.    ADCInit(AnalogChannelKanal1,AnalogChannelKanal2);
  7.    ADCOnConversionsComplete:= @ADCConversionCompleteHandler;

petex

  • Jr. Member
  • **
  • Posts: 69
Re: STM32 getting ADC to work in FPC
« Reply #2 on: January 30, 2021, 11:00:16 am »
that's great thanks.
I shall have a play about with this. The device is STM32F103C8T6.
I didn't realise there was a translation of the library around (I did ask!).
I am sure I am missing a vital piece of initialisation.

petex

  • Jr. Member
  • **
  • Posts: 69
Re: STM32 getting ADC to work in FPC
« Reply #3 on: January 30, 2021, 04:45:03 pm »
The previous code is not going to compile with my version of compiler, so i am doing some simple debugging on the existing code at the moment.

I am not convinced that the peripheral registers are getting updated at all. It works fine for variable memory @ 2000000. (I can do pointer manipulations and every thing is hunky dory.)

A simple example to illustrate the problem :

I have declared a record to map the peripheral registers for ADC 1:
ADC1_BASE seems to be an internal compiler definition (since I cannot find a definition of it anywhere in the code) that has a value of $4012400 as expected.
ie

#define   PERIPH_BASE   ((uint32_t)0x40000000)
#define   APB2PERIPH_BASE   (PERIPH_BASE + 0x10000)
#define   ADC1_BASE   (APB2PERIPH_BASE + 0x2400)

from the C code.

Code: Pascal  [Select][+][-]
  1. const
  2.   ADC1 = ( ADC1_BASE );
  3.  
  4.  
  5. type
  6.   ADC_TypeDef = record
  7.     SR: uint32_t;
  8.     CR1: uint32_t;
  9.     CR2: uint32_t;
  10.     SMPR1: uint32_t;
  11.     SMPR2: uint32_t;
  12.     JOFR1: uint32_t;
  13.     JOFR2: uint32_t;
  14.     JOFR3: uint32_t;
  15.     JOFR4: uint32_t;
  16.     HTR: uint32_t;
  17.     LTR: uint32_t;
  18.     SQR1: uint32_t;
  19.     SQR2: uint32_t;
  20.     SQR3: uint32_t;
  21.     JSQR: uint32_t;
  22.     JDR1: uint32_t;
  23.     JDR2: uint32_t;
  24.     JDR3: uint32_t;
  25.     JDR4: uint32_t;
  26.     DR: uint32_t;
  27.   end;
  28.  
  29.  
  30. var
  31.   vADC1 : ADC_TypeDef absolute ADC1;
  32.  
  33.  

Now in the code to set up the internal temperature measurement there is a code line :=
Code: Pascal  [Select][+][-]
  1.  
  2. const
  3.   CR2_TSVREFE_Set = uint32_t($00800000);  
  4.  
  5. {''' ''' ''' etc etc}
  6.  
  7.  
  8.    vADC1.CR2 := vADC1.CR2 or CR2_TSVREFE_Set;
  9.  
  10.  

I put a break point here. I inspected vADC1.CR2 and it was zero. After oring it with $00800000 it is still zero. I would have thought that the register would be persistent and the value would change.

I tried the following and it does not work either, although "tmp" is updated correctly.
Code: Pascal  [Select][+][-]
  1.  
  2.     tmp :=  vADC1.CR2;
  3.     tmp :=  tmp or CR2_TSVREFE_Set;
  4.     vADC1.CR2 := tmp;
  5.  





kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Re: STM32 getting ADC to work in FPC
« Reply #4 on: January 31, 2021, 10:12:58 am »
The previous code is not going to compile with my version of compiler, so i am doing some simple debugging on the existing code at the moment.
I just checked, my code compiles with the current stable version FPC3.2.0 (Revision 48091). The code I posted is not complete, though, just part of a project.

Quote
I inspected vADC1.CR2 and it was zero.
The addresses look correct.
Did you try to read out CR2 and print it to the UART? Not that the debugger is fetching a wrong address.

MiR

  • Sr. Member
  • ****
  • Posts: 250
Re: STM32 getting ADC to work in FPC
« Reply #5 on: January 31, 2021, 10:39:47 am »
Did you check that your code activates the clock for the adc?
Without clock the peripheral does not change values.


petex

  • Jr. Member
  • **
  • Posts: 69
Re: STM32 getting ADC to work in FPC
« Reply #6 on: January 31, 2021, 01:17:27 pm »
hello

Quote
I just checked, my code compiles with the current stable version FPC3.2.0 (Revision 48091). The code I posted is not complete, though, just part of a project.
I am using a fork produced by Michael Ring, there are differences. I will attempt to build your project in due course.


Quote
Did you check that your code activates the clock for the adc?

I seemed to have the wrong address for the RCC base. I fixed that and the commands to reset the ADC and enable it in APB peripheral reset register 2 (RCC_APBRSTR2) (APB2ENR) now work.
lo and behold, the ADC registers are read/writeable.

The problem I have now is which clock to enable. It looks a bit complicated so any directions to just get a simple example working would be a great leap forward.....

MiR

  • Sr. Member
  • ****
  • Posts: 250
Re: STM32 getting ADC to work in FPC
« Reply #7 on: January 31, 2021, 02:24:18 pm »
The reference manual is your friend, fortunately STM Reference Manuals are quite easy to understand, grab your copy here:

https://www.st.com/resource/en/reference_manual/cd00171190-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

When you do not increase CPU Speed in your code the limits for ADC will be fine, when I remember correctly the Core Clock is set <= 8MHz so the limit of ADC Clock maxing out at 14MHz should not affect you in any way and defaults should work so for your first tests with ADC you should be fine.

Things are different when you scale speed up to full 72MHz, then you need to set the prescaler to proper value.

Also check for C-Code examples, they usually port well to FreePascal.

This Code looks pretty staightforward:

https://github.com/getoffmyhack/STM32F103-Bare-Metal/blob/master/06_ADC/src/main.c


petex

  • Jr. Member
  • **
  • Posts: 69
Re: STM32 getting ADC to work in FPC
« Reply #8 on: January 31, 2021, 05:04:42 pm »
Thanks for your help !!

A couple of real sillies committed by me......

1) Looking at the wrong data sheet (although looks remarkably like STM32) The ADC enable bit on the APB2ENR register is actually 9 for ADC1 and not 20 which was stated in the datasheet I was using. I was also confused by other bit positions not being the same as the defines. Also wondered why setting bit 20 didn't work but brute forcing every bit worked !

2)
Code: Pascal  [Select][+][-]
  1. ADC_InitStructure.ADC_ExternalTrigConv := ADC_ExternalTrigConv_None; //No external triggering
  2.  
  3. {NOT}
  4.  
  5. ADC_InitStructure.ADC_ExternalTrigConv := xDISABLE; //No external triggering
  6.  

3)
Code: Pascal  [Select][+][-]
  1.  while (ADC_GetFlagStatus(vADC1, ADC_FLAG_EOC) = xRESET) do; //Wait for conversion complete
  2.  
  3. {The calibration loops have = xSET !!}
  4.  

ADC is actually working for Analog pin 0. Need to clean up the code a bit but making progress.

 

TinyPortal © 2005-2018