4

I'm trying to read an 8-bit value being sent in parallel to an STM32L476VG MCU Discovery board. Bits 7 and 6 of the data are being sent to pins PC15 and PC14, respectively, while bits 6-0 are sent to pins PE15-PE10. I tested the wires to those pins on an oscilloscope to guarantee that there is in fact a signal coming to the board. I'm pretty sure that the GPIO pins in question are properly initialized as inputs:

void init_adc_gpio (void) {
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN;        // Enable clock for GPIOC
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOEEN;        // GPIOE
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOHEN;        // GPIOH
    GPIOC->MODER &= (uint32_t)0x0FFFFFFFU;  // Pins 14-15 of C -> input (2 most significant bits of ADC data)
    GPIOE->MODER &= (uint32_t)0x000FFFFFU;  // Pins 10-15 of E -> input (6 least significant bits of ADC data)
    GPIOH->MODER &= (uint32_t)0xFFFFFFFCU;  // Pin 0 of H -> input (ADC data ready flag)
}

I'm trying to read the 8 bit data using this function, which is called whenever a flag is set (which indicates that the data from the ADC is ready to be processed):

uint8_t read_adc_data (void) {
  uint8_t adc_data;
  adc_data = ((GPIOC->IDR & (uint32_t)0x0000C000U) >> 8);
  adc_data |= ((GPIOE->IDR & (uint32_t)0x0000FC00U) >> 10);
  return adc_data;
}

However, according to debugging, adc_data is always 0 for some reason. Even changing it to this didn't work:

uint8_t read_adc_data (void) {
  uint8_t adc_data;
  adc_data = (GPIOC->IDR >> 8) | (GPIOE->IDR >> 10);
  return adc_data;
}

I feel like there's something ridiculously obvious I'm missing here, but my professor and his assistants couldn't figure it out either.

Pete
  • 41
  • 3

1 Answers1

-1

Check if the RCC and or the MODER registers are applied correctly.
Try to add some delay after setting the RCC register.

Maybe your compiler optimizes something and the following condition is not met:

When the peripheral clock is not active, the peripheral registers read or write accesses are not supported.
The enable bit has a synchronization mechanism to create a glitch free clock for the peripheral.
After the enable bit is set, there is a 2 clock cycles delay before the clock be active.
Caution:
Just after enabling the clock for a peripheral, software must wait for a delay before accessing the peripheral registers.

Edit:

Note: The following is a solution approach and appeared to be wrong. See comments for details.

I'm just compiled the above init_adc_gpio function and my compiler generates the following assembler instructions (-O3):

  RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN;        // Enable clock for GPIOC
0x080004d0  ldr r3, [pc, 60]    ; (0x8000510 <init_adc_gpio+64>) 
  GPIOE->MODER &= (uint32_t)0x000FFFFFU;  // Pins 10-15 of E -> input (6 least significant bits of ADC data)
0x080004d2  ldr r0, [pc, 64]    ; (0x8000514 <init_adc_gpio+68>) 
0x080004d4  ldr r2, [r3, 76]    ; 0x4c 
  GPIOH->MODER &= (uint32_t)0xFFFFFFFCU;  // Pin 0 of H -> input (ADC data ready flag)
0x080004d6  ldr r1, [pc, 64]    ; (0x8000518 <init_adc_gpio+72>) 
0x080004d8  orr.w r2, r2, 4 
  void init_adc_gpio(void) {
0x080004dc  push {r4} 
0x080004de  str r2, [r3, 76]    ; 0x4c 
  RCC->AHB2ENR |= RCC_AHB2ENR_GPIOEEN;        // GPIOE
0x080004e0  ldr r2, [r3, 76]    ; 0x4c 
  GPIOC->MODER &= (uint32_t)0x0FFFFFFFU;  // Pins 14-15 of C -> input (2 most significant bits of ADC data)
0x080004e2  ldr r4, [pc, 56]    ; (0x800051c <init_adc_gpio+76>) 
0x080004e4  orr.w r2, r2, 16 
0x080004e8  str r2, [r3, 76]    ; 0x4c 
  RCC->AHB2ENR |= RCC_AHB2ENR_GPIOHEN;        // GPIOH
0x080004ea  ldr r2, [r3, 76]    ; 0x4c 
0x080004ec  orr.w r2, r2, 128   ; 0x80 
0x080004f0  str r2, [r3, 76]    ; 0x4c 
0x080004f2  ldr r3, [r4, 0] 
0x080004f4  bic.w r3, r3, 4026531840    ; 0xf0000000 
0x080004f8  str r3, [r4, 0] 
0x080004fa  ldr r3, [r0, 0] 
  }
0x080004fc  ldr.w r4, [sp], 4 
0x08000500  ubfx r3, r3, 0, 20 
0x08000504  str r3, [r0, 0] 
0x08000506  ldr r3, [r1, 0] 
0x08000508  bic.w r3, r3, 3 
0x0800050c  str r3, [r1, 0] 
0x0800050e  bx  lr
0x08000510  .word   0x40021000 ; [RCC]
0x08000514  .word   0x48001000 ; [GPIOE]
0x08000518  .word   0x48001c00 ; [GPIOH]
0x0800051c  .word   0x48000800 ; [GPIOC]

As you can see the compiler reorder the instructions. Therefore the registers are not set properly. Note: Actually the sequence is right. The way how the disassembler show it can mislead someone.

To solve this problem you can use a explicit compiler barrier wich prevents the compiler to reorder the commands:

void init_adc_gpio(void) {
  RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN;     // Enable clock for GPIOC
  RCC->AHB2ENR |= RCC_AHB2ENR_GPIOEEN;    // GPIOE
  RCC->AHB2ENR |= RCC_AHB2ENR_GPIOHEN;    // GPIOH
  asm("" ::: "memory"); // prevent instruction reordering
  GPIOC->MODER &= (uint32_t)0x0FFFFFFFU;  // Pins 14-15 of C -> input (2 most significant bits of ADC data)
  GPIOE->MODER &= (uint32_t)0x000FFFFFU;  // Pins 10-15 of E -> input (6 least significant bits of ADC data)
  GPIOH->MODER &= (uint32_t)0xFFFFFFFCU;  // Pin 0 of H -> input (ADC data ready flag)
}

A simple nop instructions before setting the MODER registers should do it also.

Note: The compiler barrier leads to a slightly different assembler code. But both still correct.

veeman
  • 780
  • 1
  • 8
  • 17
  • There is an implicit delay of at least 4 (and probably much more) clock cycles in the code above, because setting the enable bit for each peripheral is followed by 2 another read-modify-write operations before touching that peripheral again. – followed Monica to Codidact May 10 '17 at 10:42
  • The peripheral registers are declared as `volatile` in the controller headers, no compiler in the world would optimize accesses to them. – followed Monica to Codidact May 10 '17 at 11:01
  • You are right, this shouldn't be a problem. But nevertheless i would check if this registers are set correctly. Also during the runtime. Otherwise nothing else can be wrong. – veeman May 10 '17 at 18:59
  • Updated my answer with explanation and solution. – veeman May 11 '17 at 17:44
  • 1
    What you are seeing is the compiler reordering the address loads from the literal pool (`ldr` instructions with PC-relative addressing). Accesses to hardware registers, which are always declared as `volatile`, are never reordered. I'd be surprised if inserting the memory barrier had changed the assembly code at all. – followed Monica to Codidact May 12 '17 at 13:25
  • I tried using "asm("" ::: "memory")" but the compiler throws this error: adc_gpio.c(16): error: #18: expected a ")". I'm really not sure why none of this is working. The pins/GPIO worked just fine on my other projects. I'm 99.99% sure I'm setting the registers correctly with the correct pin functions ("00" = input), so I have no idea what the problem is. – Pete May 12 '17 at 18:33
  • Berendi is right. And the assembler code did not change much with the memory barrier. The order of setting the registers seems in both cases to be correct. Just the way like the disasambler represent it misleading me. I try to investegate your problem furter. – veeman May 12 '17 at 19:44
  • Actually, I think this might be a problem related to my stm32l476xx.h file. I originally included the file in my Project directory because I didn't remember how I included it in previous Projects without actually copying it into the same directory (leaving it in C:\Keil_v5\ARM\PACK\Keil\STM32L4xx_DFP\1.2.0\Device\Include instead). I deleted the file in my Project directory and rebuilt it, but I got the error "adc_gpio.c(8): error: #5: cannot open source input file "stm32l476xx.h": Permission denied" 6 times. – Pete May 12 '17 at 19:56
  • ^Just tested putting the files in a new project directory. It fixed the issue with stm32l476xx.h, but the GPIO issue still wasn't fixed. – Pete May 12 '17 at 20:32
  • 2
    I FIXED IT! It turns out that files LED.c and LED.h were the culprits. LED.c initialized the GPIO pins for Port E, which I intended to use for the data. – Pete May 12 '17 at 21:35
  • Okay, well, I sort of fixed it. I'm getting different values than expected right now, but at least it's better than just 0 all the time. I think the pins on the Discovery board may be incorrectly labeled, or at least labeled with regards to something other than the actual GPIO pins on the actual MCU. For example, it looks like PC15 and 14 on the board are actually pins 10 and 9, respectively, on GPIOC, since its IDR appears to be always 0x00000600. Also the lowest 8 bits of GPIOE->IDR appears to always be 0xXXXXXX31. – Pete May 12 '17 at 22:09