cancel
Showing results for 
Search instead for 
Did you mean: 

Register Level programming questions

SHobb.1
Associate II

I am just starting out with STM32 MCUs. Because I like pain, I am trying to do register level programming to make sure I really understand what the code is doing (rather than using automagically generated code). My environment is Visual Studio with VisualGDB, compiling with the ARM toolchain (I think? Not sure how to confirm that).

Current hardware is a NUCLEO-F103RB (hopefully soon switching to a NUCLEO-G071RB).

The purpose of this thread is to ask [potentially dumb] questions that arise. I hope some people with more experience than me can weigh in and point me in the right direction.

First question:

I am trying to write the SystemInit function to configure my clock tree. Because my board has an STM32F103RB, I am referring to RM0008 (rev 21). In particular, Chapter 8 (starting on p. 123). There appear to be two registers documented in RM0008 that are NOT in sthm32f103xb.h from CMSIS: RCC_AHBSTR and RCC_CFGR2. Is that because those two registers aren't present in the STM32F103RB (the RM is shared across several devices)? Looking at the clock tree diagram on p. 20 of the STM32F103xB data sheet, it is slightly different from the clock tree on p. 126 of RM0008 --> in particular, PLL2 and PLL3 are not present. If this is the case, it would make the RMs a lot more readable if there was some flag indicating a particular register is only present in certain devices covered by the RM.

20 REPLIES 20

> There appear to be two registers documented in RM0008 that are NOT in sthm32f103xb.h from CMSIS: RCC_AHBSTR

Its' RCC_AHBRSTR, offset 0x28. RCC_AHBSTR in the table at end of RCC chapter is a typo.

But yes, both these registers are present only in the *connectivity line*, i.e. 'F105 and 'F107. Note, that there are two separate RCC chapter in RM0008, and the preamble to each of those chapters clearly states to which group (lines) of mcu given chapter pertains.

JW

@Imen DAHMEN​ , please, can the RCC_AHBSTR (i.e. omission of R letter in middle) in table at end of Connectivity line devices: reset and clock control (RCC) chapter in RM0008 be fixed?

SHobb.1
Associate II

Wek,

Thanks for the prompt response. I think you were there at the beginning of my experience with AVRs (posting on AVRFreaks circa 2010) too. I completely missed the fact that there were two separate chapters for clock control. I was definitely referencing the wrong chapter for my device.

SHobb.1
Associate II

As stated above, I my development platform is a Nucleo-F103RB (64).

I have created a literal "Hello World" project to try to print something out the VCOM port on the Nucleo using STM32CubeIDE (see attached). It is very simple - set up clocks, set up UART2 (which I think is connected to the debugger), and print a few statements. Unfortunately, I have not been able to see any messages on any terminal program (tried putty, teraterm, and realterm). Do I need to do anything to get the VCOM port to work? Do I need to disconnect from the debugger in the programming software? Do I need to do some sort of reset? Please assume I know nothing when answering...

Try to reduce the unknowns and go step by step.

First, the "Hello World" of embedded world is a blinky, so try that first (there is one LED on the Nucleo), with a simple loopdelay, without any setting of system clocks, PLL etc. in RTC, leaving it at its reset values.

Then, echo-test the VCP - with the mcu held in reset - or with leaving the Rx/Tx pins simply at their reset values i.e. both Input - short the Rx and Tx solder bridges together, and you should see characters being echoed in the terminal on PC.

Make sure the solder bridges are at their place.

Then, still without changing system clock etc., relying on the default HSI (it should be precise enough for basic tests at room temperature), try simply just Tx one character, 0x55 for example, you should see "U" in the terminal. Observe Tx pin using oscilloscope or LA.

Only after you get this working, go for things like changing system clock source, PLL, semihosting.

Divide et impera.

JW

SHobb.1
Associate II

Thank you for the advice - which I will follow. However, I would still appreciate a sanity check on the VCOM port usage (meaning, "of course you need to disconnect from the embedded ST-LINK prior to using it" or "You can't rely on the 8MHz clock from the embedded ST-LINK" or something along those lines).

EDIT:

Just for the sake of posterity: After proving I could blink an LED an an expected frequency (thanks for the recommendation Wek!), I revisited my UART config code. Turns out I accidentally enabled the USART2 clock in RCC->APB2ENR (it is actually located in RCC->APB1ENR). Also, forgot to convert my BAUDDIV value to hex. Fixed those two bugs, and the USART is working as expected.

SHobb.1
Associate II

Next question: trying to setup the ADC for a simple read (single channel, polling - no interrupt yet). However, the conversion never seems to complete (or start?). Due to previous work, I am confident my clocks are set up correctly (printf is working, led blinking working, external interrupt (button) working).

Still using a NUCLEO-F103RB

I am setting up the ADC with the following code:

/**
 * @brief Configure ADC GPIO pins: PA0, PA1, PA4
 */
void adc_GPIO_config(void)
{
  //Enable Port A clock
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
  //PA0: Input / Analog
  GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0);
  //PA1: Input / Analog
  GPIOA->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_MODE1);
  //PA4: Input / Analog
  GPIOA->CRL &= ~(GPIO_CRL_CNF4 | GPIO_CRL_MODE4);
}
 
/**
 * @brief Configure ADC Single Channel
 */
void adc_singleConfig(ADC_SingleSelect_e channel)
{
  //Enable ADC clock
  RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
 
  //Right align, single conversion
  ADC1->CR2 &= ~(ADC_CR2_ALIGN | ADC_CR2_CONT);
  //Software Trigger
  ADC1->CR2 |= ADC_CR2_EXTSEL;
  //Sample time to 28 cycle = (12+28)/12MHz = 3.3us
  //PA0 (IN0)
  ADC1->SMPR2 &= ~ADC_SMPR2_SMP0;
  ADC1->SMPR2 |= (ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_0);
  //PA1 (IN1)
  ADC1->SMPR2 &= ~ADC_SMPR2_SMP1;
  ADC1->SMPR2 |= (ADC_SMPR2_SMP1_1 | ADC_SMPR2_SMP1_0);
  //PA4 (IN4)
  ADC1->SMPR2 &= ~ADC_SMPR2_SMP4;
  ADC1->SMPR2 |= (ADC_SMPR2_SMP4_1 | ADC_SMPR2_SMP4_0);
 
  //Number of conversions: 1
  ADC1->SQR1 &= ~ADC_SQR1_L;
  //Rank1 = Channel 1, 2, 4
  ADC1->SQR3 = (channel << ADC_SQR3_SQ1_Pos);
  //Power up ADC
  ADC1->CR2 |= ADC_CR2_ADON;
  //Wait for ADC to stabilize
  //Delay estimate: 1 iteration = 4 cycles.  Clock is 72MHz --> total delay: (4*36)/72e6 = 2us = 24 ADC clocks (12MHz)
  for(uint16_t i = 0; i < 36; i++);
}

Main is:

int main(void)
{
  uint16_t adcValue = 0;
  //Select test to run
  testbed_test_t activeTest = TEST_ADC;
 
  //Max clock of 72MHz
  rcc_HSE_config();
  rcc_SysTick_config(72000); //Clock is 72MHz, want 1ms tick (1kHz)
 
  uart_UART2_GPIO_config(); //UART Configuration
  uart_UART2_config();
  gpio_LED_config();        //LED Configuration
  gpio_PB_config();         //Push button configuration
  exti_buttonConfig();      //EXTI configuration
  adc_GPIO_config();        //ADC Configuration
  adc_singleConfig(ADC_SingleSelect_A0);
 
  printf("Program is starting\r\n");
  while(1)
  {
    if(activeTest == TEST_LED)
    {
      printf("Hello World!\r\n");
      gpio_LED_toggleGreen();                 //Toggle based on timer
      rcc_msDelay(500);
    }
    else if(activeTest == TEST_BUTTON)
    {
      gpio_LED_writeGreen(gpio_PB_read());    //Push button controls LED
    }
    else if(activeTest == TEST_INT)
    {
      if(extiFlag)
      {
        extiFlag = false;
        gpio_LED_toggleGreen();
        printf("Button Interrupt!\r\n");
        rcc_msDelay(200);             //Allow switch to settle
        EXTI->IMR |= EXTI_IMR_MR13;   //Unmask interrupt again
      }
      rcc_msDelay(1000);
      printf("Hello World!\r\n");
    }
    else if(activeTest == TEST_ADC)
    {
      adc_start();
      if(adc_pollForEndOfConv(15))
      {
        adcValue = adc_readValue();
        printf("ADC Read Successfully!\r\n");
      }
      else
      {
        printf("ADC Failed!\r\n");
      }
      printf("ADC Value = %d\r\n", (int)adcValue);
      rcc_msDelay(300);
    }
    else
    {
      printf("Unknown Test\r\n");
      rcc_msDelay(1000);
    }
  }
}

Additional ADC functions (called from main):

/**
 * @brief ADC Start - SWSTART
 */
void adc_start(void)
{
  ADC1->SR = 0; //Clear status register
  ADC1->CR2 |= (ADC_CR2_SWSTART | ADC_CR2_ADON);   //Need to write ADCON second time to start conv.
}
 
/**
 * @brief ADC Stop
 */
void adc_stop(void)
{
  ADC1->SR = 0;                     //Clear status register
  ADC1->CR2 &= ~ADC_CR2_SWSTART;
}
 
/**
 * @brief ADC Poll for end of conversion
 */
bool adc_pollForEndOfConv(uint32_t msTimeout)
{
  uint32_t startTicks = rcc_msGetTicks();
  while((ADC1->SR & ADC_SR_EOC) == 0)
  {
    if((rcc_msGetTicks()-startTicks) > msTimeout)
    {
      return(false);
    }
  }
  return(true);
}

Unfortunately, even for arbitrarily large timeout values as an argument in the call to adc_pollForEndOfConv still result in timeouts (ADC failed message printing).

Am I missing something obvious here? I've read this code ten times at this point...

SHobb.1
Associate II

Additional note for above: I am using USART2 to communicate (via the VCOM port on the ST-LINK) with my host PC. If I am using USART2 with no remapping, does that mean that PA0 and PA1 are reserved for USART2_CTS and USART2_RTS, respectively - even if I am not using hardware flow control? Is it not possible to configure those pins as analog inputs if USART2 is enabled?

ADC in STM32 is very complex, and is perhaps the module which varies the most from STM32 family to other, so knowledge is not very transferrable and one has to meticulously follow guidelines in RM.

For example, I see this in RM0008 below description of ADON:

Note: If any other bit in this register apart from ADON is changed at the same time, then

conversion is not triggered. This is to prevent triggering an erroneous conversion.

Note, that debugging is intrusive, e.g. reading ADC_DR (using whatever means, including viewing it in debugger) clears EOC.

> If I am using USART2 with no remapping, does that mean that PA0 and PA1 are reserved for

> USART2_CTS and USART2_RTS, respectively - even if I am not using hardware flow control?

Only if you set them as Alternate Function. If you set them as Analog, they serve the analog purpose described in DS.

JW

[EDIT] Sylistics:

SHobb.1
Associate II

Thanks for the response.

  1. Understood about the complexity of the ADC module. Good point about the note about other bits. However, I felt like I was not "changing" ADON there - I was only writing to it a second time. On p. 218 of RM0008, it states,

The ADC can be powered-on by setting the ADON bit in the ADC_CR2 register. When the

ADON bit is set for the first time, it wakes up the ADC from Power Down mode.

Conversion starts when ADON bit is set for a second time by software after ADC power-up

time (tSTAB).

I have changed the code slightly to this:

  1. End of init function:
//Number of conversions: 1
  ADC1->SQR1 &= ~ADC_SQR1_L;
  //Rank1 = Channel 1, 2, 4, 8
  ADC1->SQR3 = (channel << ADC_SQR3_SQ1_Pos);
  //Power up ADC
  ADC1->CR2 |= ADC_CR2_ADON;
  //Wait for ADC to stabilize
  //Delay estimate: 1 iteration = 4 cycles.  Clock is 72MHz --> total delay: (4*36)/72e6 = 2us = 24 ADC clocks (12MHz)
  for(uint16_t i = 0; i < 36; i++);
  //Need to write to ADON a second time (See RM0008, p. 218)
  ADC1->CR2 |= ADC_CR2_ADON;

and altered the adc_start function to not touch ADON again:

void adc_start(void)
{
  ADC1->SR = 0; //Clear status register
  ADC1->CR2 |= ADC_CR2_SWSTART; 
}

But EOC is still never getting set.

The strange thing is when I try to debug, I never see the SWSTART bit get set in CR2, even with breakpoints at line 4 (above), or immediately afterward. Page 241 of RM0008 states, "This bit is set by software to start conversion and cleared by hardware as soon as conversion starts". I am not sure if I am not setting it properly or if it is getting cleared by hardware before I get to see it.

Finally, good point regarding the style suggestion. I know my repeated RMW actions are inefficient. It's just easier for me to see exactly what I'm doing right now as I learn the registers. I'll condense later.