STM32 getting ADC to work in FPC [solved] lazarus / 79-Embedded - ARM ---------------------------------------------------------------------- petex · 2021-01-29 20:54 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     ADC_DeInit(ADC1); //Power-on default     RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, xENABLE);    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, xENABLE);     ADC_InitStructure.ADC_Mode := ADC_Mode_Independent; //Independent conversion mode (single)    ADC_InitStructure.ADC_ScanConvMode := xDISABLE; //Convert single channel only    ADC_InitStructure.ADC_ContinuousConvMode := xDISABLE; //Convert 1 time    ADC_InitStructure.ADC_ExternalTrigConv := xDISABLE; //No external triggering    ADC_InitStructure.ADC_DataAlign := ADC_DataAlign_Right; //Right 12-bit data alignment    ADC_InitStructure.ADC_NbrOfChannel := 1; //single channel conversion  ADC_Init(ADC1, ADC_InitStructure);     ADC_TempSensorVrefintCmd(xENABLE); //wake up temperature sensor    ADC_Cmd(ADC1, xENABLE); //Enable ADC1    ADC_ResetCalibration(ADC1); //Enable ADC1 reset calibration register    while (ADC_GetResetCalibrationStatus(ADC1) = xRESET) do; //check the end of ADC1 reset calibration register     ADC_StartCalibration(ADC1); //Start ADC1 calibration    while (ADC_GetCalibrationStatus(ADC1) = xRESET) do; //Check the end of ADC1 calibration     ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_1Cycles5); //Select 1.5 cycles conversion for channel 16    ADC_SoftwareStartConvCmd(ADC1, xENABLE); //Start ADC1 software conversion    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) = xRESET) do; //Wail for conversion complete     an_val := ADC_GetConversionValue(ADC1); //Read ADC value    ADC_ClearFlag(ADC1, ADC_FLAG_EOC); //Clear EOC flag ---------------------------------------------------------------------- kupferstecher · 2021-01-29 22:42 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/stm32f103f w.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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit uADC; {$mode objfpc} interface Procedure ADCInit(AChannelKanal1,AChannelKanal2: byte);Procedure ADCSingleConversion; var ADCOnConversionsComplete: Procedure; const AnalogKanal1Pin = GPIO_Pin_0;const AnalogKanal2Pin = GPIO_Pin_1;var   AnalogKanal1Port: TPortRegisters absolute PortB;var   AnalogKanal2Port: TPortRegisters absolute PortB; var  ADC1Result: uInt16;  ADC2Result: uInt16;   IMPLEMENTATION uses  stm32f103fw,STM32F 10x_BitDefs,cortexm3; //++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++Procedure ADCInit(AChannelKanal1,AChannelKanal2: byte);var  ADC_InitStructure: TADC_InitTypeDef;  NVIC_InitStructure: TNVIC_InitTypeDef;  GPIO_InitStructure: TGPIO_InitTypeDef;begin   // Analog Eingaenge Kanal 1 und 2  GPIO_InitStructure.GPIO_Pin := AnalogKanal1Pin;  GPIO_InitStructure.GPIO_Mode := GPIO_Mode_AIN;    //Analog In  GPIO_InitStructure.GPIO_Speed := GPIO_Speed_50MHz;  GPIO_Init(AnalogKanal1Port, GPIO_InitStructure);   GPIO_InitStructure.GPIO_Pin := AnalogKanal2Pin;  GPIO_InitStructure.GPIO_Mode := GPIO_Mode_AIN;    //Analog In  GPIO_InitStructure.GPIO_Speed := GPIO_Speed_50MHz;  GPIO_Init(AnalogKanal1Port, GPIO_InitStructure);   // ADCCLK = PCLK2/2  RCC_ADCCLKConfig(RCC_PCLK2_Div4);   ////ADC1  ADC_InitStructure.ADC_Mode:= ADC_Mode_Independent;  //ADC1 unabhängig von ADC2, nicht dual-mode  ADC_InitStructure.ADC_ScanConvMode:= DISABLED;       //nicht Nur ein Kanal  ADC_InitStructure.ADC_ContinuousConvMode:= DISABLED;//Nur einzelne Wandlung  ADC_InitStructure.ADC_ExternalTrigConv:= ADC_ExternalTrigConv_T1_CC1;//Timer1 Kanal1 triggert  ADC_InitStructure.ADC_DataAlign:= ADC_DataAlign_Right;  ADC_InitStructure.ADC_NbrOfChannel:= 1;  ADC_Init(ADC1, ADC_InitStructure);   // ADC1 regular channel14 configuration  ADC_RegularChannelConfig(ADC1, AChannelKanal1, 1, ADC_SampleTime_28Cycles5);  // Enable ADC1 external trigger  ADC_ExternalTrigConvCmd(ADC1, ENABLED);  // Enable EOC interrupt  ADC_ITConfig(ADC1, ADC_IT_EOC, DISABLED);   // Enable ADC1  ADC_Cmd(ADC1, ENABLED);   {////Injected Kanäle  ADC_InjectedSequencerLengthConfig(ADC1,2);  ADC_InjectedChannelConfig(ADC1, AChannelKanal2,1,ADC_SampleTime_13Cycles5);  ADC_InjectedChannelConfig(ADC1, AChannelKanal2,2,ADC_SampleTime_13Cycles5);   // Interrupt für Injected-Gruppe  ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T2_CC1);  ADC_ExternalTrigInjectedConvCmd(ADC1,ENABLED);  ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLED);  }   ////ADC2  ADC_InitStructure.ADC_Mode:= ADC_Mode_Independent;  //ADC1 unabhängig von ADC2, nicht dual-mode  ADC_InitStructure.ADC_ScanConvMode:= DISABLED;       //nicht Nur ein Kanal  ADC_InitStructure.ADC_ContinuousConvMode:= DISABLED;//Nur einzelne Wandlung  ADC_InitStructure.ADC_ExternalTrigConv:= ADC_ExternalTrigConv_T1_CC1;//Timer1 Kanal1 triggert  ADC_InitStructure.ADC_DataAlign:= ADC_DataAlign_Right;  ADC_InitStructure.ADC_NbrOfChannel:= 1;  ADC_Init(ADC2, ADC_InitStructure);   // ADC1 regular channel14 configuration  ADC_RegularChannelConfig(ADC2, AChannelKanal2, 1, ADC_SampleTime_28Cycles5);  // Enable ADC external trigger  ADC_ExternalTrigConvCmd(ADC2, ENABLED);  // Enable EOC interrupt  ADC_ITConfig(ADC2, ADC_IT_EOC, ENABLED);   // Enable ADC1  ADC_Cmd(ADC2, ENABLED);     ////ADC1+2  // ADC-NVIC-Konfiguration  NVIC.IP[ADC1_2_IRQn]:= $10;     //IRQ-Priority, right 4bits always to be zero,low value=high prio  NVIC.ISER[ADC1_2_IRQn shr 5] := 1 shl (ADC1_2_IRQn and $1F); //Enable Interrupt    //ADC_SoftwareStartConvCmd(ADC1, ENABLED);   // Enable ADC1 reset calibration register  //ADC_ResetCalibration(ADC1);  // Check the end of ADC1 reset calibration register  //while(ADC_GetResetCalibrationStatus(ADC1));   // Start ADC1 calibration  //ADC_StartCalibration(ADC1);  // Check the end of ADC1 calibration  //while(ADC_GetCalibrationStatus(ADC1));  end; //-------- -------------------------------------------------procedure ADCSingleConversion;begin  ADC_SoftwareStartConvCmd(ADC1, ENABLED);end ;  //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++//++ INTERRUPT SERVICE ROUTINEprocedure ADC_ISR; Alias: 'ADC1_and_ADC2_global_interrupt'; Interrupt; Public;var Timeout: Integer;begin  //comvisu.cmdLn('ADC1ISR');   Timeout:= 20;  while (ADC1.SR and ADC1.SR and ADC_SR_EOC) = 0  do begin Timeout:= Timeout-1; if Timeout = 0 then BREAK; end;   if (ADC1.SR and ADC_SR_EOC) > 0  then ADC1Result:= ADC1.DR else ADC1Result:= $FFFF;  if (ADC2.SR and ADC_SR_EOC) > 0  then ADC2Result:= ADC2.DR else ADC2Result:= $FFFF;   if assigned(ADCOnConversionsComplete) then ADCOnConversionsComplete;  ADC_ClearITPendingBit(ADC1,ADC_IT_EOC);  AD C_ClearITPendingBit(ADC2,ADC_IT_EOC); end; //+++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ end. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit uTimer1;{$mode objfpc}interfaceuses  STM32F10x_BitDefs, cortexm3; var Timer1OnTimerCompare: Procedure;     Procedure Timer1Init;    procedure Timer1SetPrescaler(AValue: uInt16);    procedure Timer1SetAutoReloadRegister(AValue: uInt16);    Procedure Timer1Enable;    Procedure Timer1Disable;  //######################### #####################################################IMPLEMENTATIONuse s  stm32f103fw; //++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++procedure Timer1Init;var  GPIO_InitStructure: TGPIO_InitTypeDef;begin  Timer1OnTimerCompare:= nil;   //RCC enable for Timer1  RCC.APB2ENR:= RCC.APB2ENR or RCC_APB2ENR_TIM1EN;   ////Timer settings ----------------  //Various settings in default  Timer1.CR1:=   0;         //Control register 1; Wait with timer enable until settings complete  Timer1.CR2:=   0;         //Control register 2  Timer1.CCMR1:=                   //Capture/compare mode register;               TIM_CCMR1_OC1PE   //oc preload enable              or TIM_CCMR1_OC1M_1  //PWM-Mode 1              or TIM_CCMR1_OC1M_2;  Timer1.CCER:=  TIM_CCER_CC1E; //Capture/compare enable register; CC1-output-enable}   //Capture compare Register um Flanken zu bekommen  Timer1.CCR1:= 2;   //Prescaler  Timer1.PSC:= 2000;  //AutoReload value  Timer1.Arr:= 10000;   Timer1.DIER:= TIM_DIER_CC1IE; //Compare 1 interrupt enable  Timer1.EGR:= Timer1.EGR or TIM_EGR_UG; //Werte übernehmen  Timer1.BDTR:= Timer1.BDTR or TIM_BDTR_MOE; //Output enable   // NVIC configuration  NVIC.IP[TIM1_CC_IRQn]:= $20;     //IRQ-Priority, right 4bits always to be zero,low value=high prio  NVIC.ISER[TIM1_CC_IRQn shr 5] := 1 shl (TIM1_CC_IRQn and $1F); //Enable Interrupt   //Enable Counter  Timer1.CR1:= Timer1.CR1 or TIM_CR1_CEN;   {a pwm mode in CCMR  CC1EN in Tim1_CCER }     // PA8 PWM-output  GPIO_InitStructure.GPIO_Pin := GPIO_Pin_8;  GPIO_InitStructure.GPIO_Speed := GPIO_Speed_50MHz;  GPIO_InitStructure.GPIO_Mode := GPIO_Mode_AF_PP;  GPIO_Init(PortA, GPI O_InitStructure);  end; //-------------------------------------------- -------------procedure Timer1SetPrescaler(AValue: uInt16);begin  Timer1Disable;  Timer1.PSC:= AValue;  Timer1Enable;end; //------------ ---------------------------------------------procedure Timer1SetAutoReloadRegister(AValue: uInt16);begin  Timer1Disable;  Timer1.Arr:= AValue;  Timer1Enable;end; //---------------------------- -----------------------------procedure Timer1Enable;begin Timer1.CR1:= Timer1.CR1 or TIM_CR1_CEN; end; //------------------------------------ ---------------------procedure Timer1Disable;begin Timer1.CR1:= Timer1.CR1 and not TIM_CR1_CEN; end; //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++//++ INTERRUPT SERVICE ROUTINEprocedure TIM1_ISR; Alias: 'TIM1_Capture_Compare_interrupt'; Interrupt; Public;begin  Timer1.SR:= Timer1.SR and not TIM_SR_CC1IF;   if assigned(Timer1OnTimerCompare) then Timer1OnTimerCompare; end; //++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++end. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i    //Timer1   Timer1Init;   Timer1OnTimerCompare:= @TimerCompareHandler;    //ADC   ADCInit(AnalogChannelKanal1,AnalogChannelKanal2);   ADCOnConversionsComplete:= @ADCConversionCompleteHandler; ---------------------------------------------------------------------- petex · 2021-01-30 10:00 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 · 2021-01-30 15:45 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i const  ADC1 = ( ADC1_BASE );   type  ADC_TypeDef = record    SR: uint32_t;    CR1: uint32_t;    CR2: uint32_t;    SMPR1: uint32_t;    SMPR2: uint32_t;  JOFR1: uint32_t;    JOFR2: uint32_t;    JOFR3: uint32_t;    JOFR4: uint32_t;    HTR: uint32_t;    LTR: uint32_t;    SQR1: uint32_t;    SQR2: uint32_t;    SQR3: uint32_t;    JSQR: uint32_t;    JDR1: uint32_t;    JDR2: uint32_t;    JDR3: uint32_t;    JDR4: uint32_t;    DR: uint32_t;  end;  var  vADC1 : ADC_TypeDef absolute ADC1; Now in the code to set up the internal temperature measurement there is a code line := Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  const  CR2_TSVREFE_Set = uint32_t($00800000);   {''' ''' ''' etc etc}     vADC1.CR2 := vADC1.CR2 or CR2_TSVREFE_Set; 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i      tmp :=  vADC1.CR2;    tmp :=  tmp or CR2_TSVREFE_Set;    vADC1.CR2 := tmp; ---------------------------------------------------------------------- kupferstecher · 2021-01-31 09:12 Quote from: petex on January 30, 2021, 04:45:03 pmThe 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. QuoteI 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 · 2021-01-31 09:39 Did you check that your code activates the clock for the adc? Without clock the peripheral does not change values. ---------------------------------------------------------------------- petex · 2021-01-31 12:17 hello QuoteI 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. QuoteDid 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 · 2021-01-31 13:24 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 · 2021-01-31 16:04 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i ADC_InitStructure.ADC_ExternalTrigConv := ADC_ExternalTrigConv_None; //No external triggering  {NOT} ADC_InitStructure.ADC_ExternalTrigConv := xDISABLE; //No external triggering 3) Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  while (ADC_GetFlagStatus(vADC1, ADC_FLAG_EOC) = xRESET) do; //Wait for conversion complete  {The calibration loops have = xSET !!} ADC is actually working for Analog pin 0. Need to clean up the code a bit but making progress.