This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

MSP432P401R: MSP432P401R ADC in differential mode

Part Number: MSP432P401R

Hi, the idea of the project is to detect the Back Electromagnetic Force (BEMF) of a BLDC and for this I have made this simple program where I wanted to use the ADC of the MSP432P401R in differential mode, since I have used six consecutive channels and the idea was to take the difference between two channels (the phase and the reference voltage) and study the change of sign, which would allow me to detect a zero crossing.
The problem is that I don't know if the idea of defining the channels I am interested in, enable the ADC, start conversion and assign the difference between the channels is correct in each case of the switch-case. The thing is that, despite of having caught six consecutive channels, I would only be interested in dealing with the difference between two channels depending on the state in which I find myself, but I do not know if it is the most indicated way as I have planned it.

  1. #include "msp.h"
  2. #include <stdint.h>
  3. #include <stdio.h>
  4.  
  5. uint8_t estado = 0; 
  6.  
  7. void inicializaciones(void){
  8.     // GPIO pin (P5.1) to be set to HIGH when zero-crossing to detect the BEMF
  9.     P5SEL0 &= ~BIT1;
  10.     P5SEL1 &= ~BIT1;
  11.     P5DIR |= BIT1; // output
  12. }
  13.  
  14. void configuracion_ADC(void){
  15.     ADC14->CTL0 |= ADC14_CTL0_ON;
  16.     ADC14->CTL0 |= ADC14_CTL0_MSC | ADC14_CTL0_CONSEQ_2 | ADC14_CTL0_SSEL_4 | ADC14_CTL0_SHT0_7 | ADC14_CTL0_SHP; 
  17.     ADC14->CTL1 =  ADC14_CTL1_RES_3 | ADC14_CTL1_DF;     /* 14-bit resolution; ADC14DF: -V(REF) = 8000h, +V(REF) = 7FFCh */
  18.     P8SEL1 |= (BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7); // Enable A/D input channels
  19.     P8SEL0 |= (BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
  20.     ADC14->CTL0 |= ADC14_CTL0_ENC; // ADC14ENC: Enable conversion after ADC configuration
  21. }
  22.  
  23. void init_interrupciones(void){
  24.     NVIC_EnableIRQ(ADC14_IRQn);
  25.     __enable_irq(); // Enable global interrupt
  26. }
  27.  
  28. void main(void)
  29. {
  30.     WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD;     // stop watchdog timer
  31.     inicializaciones();
  32.     configuracion_ADC();
  33.     init_interrupciones();
  34.  
  35.     while(1){
  36.         static int16_t last_delta = -1; // By using "static" on variables that are inside a function, it allows their value to persist between calls.
  37.         static int16_t delta = 0;
  38.  
  39.         switch(estado){
  40.             case 0: {
  41.                 P5OUT &= ~BIT1; // We set the GPIO of the zero-crossing to LOW
  42.                 ADC14->MCTL[0] |= ADC14_MCTLN_INCH_20 | ADC14_MCTLN_DIF; // ADC14DIF: ADC Differential Mode ADC; ADC14INCH_20 (when DIFF Mode is active): Ain+ = Channel A20, Ain- = Channel A21; Input in differential mode selection; Vref=AVCC
  43.                 ADC14->CTL0 |= ADC14_CTL0_ENC | ADC14_CTL0_SC; /* ADC14ENC | ADC14SC: Enable conversions and start conversions */
  44.                 while(!ADC14->IFGR0 & ADC14_IFGR0_IFG21) // wait till conversion is complete
  45.                 delta = ADC14->MEM[20] - ADC14->MEM[21]; 
  46.                 break;
  47.             }
  48.             case 1: {
  49.                 P5OUT &= ~BIT1; 
  50.                 ADC14->MCTL[0] |= ADC14_MCTLN_INCH_18 | ADC14_MCTLN_DIF; // ADC14DIF: ADC Differential Mode ADC; ADC14INCH_18 (when DIFF Mode is active): Ain+ = Channel A18, Ain- = Channel A19; Input in differential mode selection; Vref=AVCC
  51.                 ADC14->CTL0 |= ADC14_CTL0_ENC | ADC14_CTL0_SC; 
  52.                 while(!ADC14->IFGR0 & ADC14_IFGR0_IFG19) 
  53.                 delta = ADC14->MEM[18] - ADC14->MEM[19]; 
  54.                 break;
  55.             }
  56.             case 2: {
  57.                 P5OUT &= ~BIT1; 
  58.                 ADC14->MCTL[0] |= ADC14_MCTLN_INCH_22 | ADC14_MCTLN_DIF; // ADC14DIF: ADC Differential Mode ADC; ADC14INCH_22 (when DIFF Mode is active): Ain+ = Channel A22, Ain- = Channel A23; Input in differential mode selection; Vref=AVCC
  59.                 ADC14->CTL0 |= ADC14_CTL0_ENC | ADC14_CTL0_SC; 
  60.                 while(!ADC14->IFGR0 & ADC14_IFGR0_IFG23) 
  61.                 delta = ADC14->MEM[22] - ADC14->MEM[23]; 
  62.                 break;
  63.             }
  64.             case 3: {
  65.                 P5OUT &= ~BIT1; 
  66.                 ADC14->MCTL[0] |= ADC14_MCTLN_INCH_20 | ADC14_MCTLN_DIF; 
  67.                 ADC14->CTL0 |= ADC14_CTL0_ENC | ADC14_CTL0_SC; 
  68.                 while(!ADC14->IFGR0 & ADC14_IFGR0_IFG21) 
  69.                 delta = ADC14->MEM[20] - ADC14->MEM[21]; 
  70.                 break;
  71.             }
  72.             case 4: {
  73.                 P5OUT &= ~BIT1; 
  74.                 ADC14->MCTL[0] |= ADC14_MCTLN_INCH_18 | ADC14_MCTLN_DIF; 
  75.                 ADC14->CTL0 |= ADC14_CTL0_ENC | ADC14_CTL0_SC; 
  76.                 while(!ADC14->IFGR0 & ADC14_IFGR0_IFG19) 
  77.                 delta = ADC14->MEM[18] - ADC14->MEM[19];
  78.                 break;
  79.             }
  80.             case 5: {
  81.                 P5OUT &= ~BIT1; 
  82.                 ADC14->MCTL[0] |= ADC14_MCTLN_INCH_22 | ADC14_MCTLN_DIF; 
  83.                 ADC14->CTL0 |= ADC14_CTL0_ENC | ADC14_CTL0_SC; 
  84.                 while(!ADC14->IFGR0 & ADC14_IFGR0_IFG23) 
  85.                 delta = ADC14->MEM[22] - ADC14->MEM[23]; 
  86.                 break;
  87.             }
  88.         }
  89.  
  90.         if(last_delta < 0){
  91.             if(delta > 0){
  92.                 ADC14CTL0 &= ~ADC14ENC; // We stop the ADC, since it is NOT possible to change channel if the ADC is enabled.
  93.                 P5OUT |= BIT1; // Set GPIO to HIGH when zero-crossing is detected
  94.                 last_delta = delta;
  95.                 estado = estado + 1;
  96.                 if(estado > 5){
  97.                     estado = 1;
  98.                 }
  99.             }
  100.         }
  101.  
  102.         if(last_delta > 0){
  103.             if(delta < 0){
  104.                 ADC14CTL0 &= ~ADC14ENC; 
  105.                 P5OUT |= BIT1; 
  106.                 last_delta = delta;
  107.                 estado = estado + 1;
  108.                 if(estado > 5){
  109.                     estado = 1;
  110.                 }
  111.             }
  112.         }
  113.     }
  114. }
  •  ADC14->CTL0 |= ADC14_CTL0_MSC | ADC14_CTL0_CONSEQ_2 | ADC14_CTL0_SSEL_4 | ADC14_CTL0_SHT0_7 | ADC14_CTL0_SHP; 

    Once you start (SC=1) a channel with CONSEQ=2, it runs forever. I suspect you want CONSEQ_0 here.

    ------------

    >                ADC14->MCTL[0] |= ADC14_MCTLN_INCH_20 | ADC14_MCTLN_DIF; 

    [...]

    >                while(!ADC14->IFGR0 & ADC14_IFGR0_IFG21) 

    A conversion using MCTL[0] interrupts on IFG0, not IFG21. This only appears to work since you need parentheses around the "&" subexpression.

    Also, you can't change MCTL with ENC=1, you need to set ENC=0 first.

    Unsolicited: I suggest you set MCTL[0/1/2] to the channel pairs you want once at the beginning, then change CSTARTADD as needed. Even better: Sample all 3 at once using CONSEQ=1 (and MSC=1).

    ------------

    >                delta = ADC14->MEM[20] - ADC14->MEM[21]; 

    I'm pretty sure that you don't need to subtract the two MEM registers; if you are using MCTL[0], the (differential) result is in MEM[0]. [Disclaimer: I haven't used differential mode on the MSP432P, but I have on other devices, and I'm pretty sure that's what the book says.]

  • Regarding the CONSEQ_2 problem, let's say that my idea was to take ADC measurements consecutively until one of the two conditionals (if last_delta < 0 or if last_delta > 0) was reached, since I would know that at that moment the zero crossing had been detected and the ADC was disabled in order to change the channel to be measured according to the next state.

    To solve the second thing you tell me about the interrupts enables in MCTL[0] I have put for example the following for the case where state = 0: ADC14->MCTL[19] |= ADC14_MCTLN_INCH_20 | ADC14_MCTLN_DIF;

    And finally regarding that it is unnecessary to make the subtraction of the two channels and assign it to delta I have set for example the following for the case where state = 0:
    delta = ADC14->MEM[19];

    Even so, one thing I don't understand about the ADC in differential mode is if for example I use channel A20 and A21 if I have to wait until the conversion is completed in channel A20 or channel A21 and so I'm not 100% sure what to put in the while() loops in each state.

  • 1) OK. But you should wait for the ADC to finish the current conversion (ADC14BUSY=0) after setting ENC=0. [Ref TRM (SLAU256I) Sec 22.2.8.6]

    2) In the ADC registers, the only time you refer to a channel (An) number is when you set the MCTL register. All other references (CSTARTADD, IFGn, IEn) refer to MCTL[] indices. E.g. if you set MCTL[1]=INCH_3, that will trigger (when complete) IFG1, not IFG3.

    Setting the DIF bit in  MCTL[19]  with (e.g.) INCH=4 implicitly assigns INCH=(4+1) to that MCTL/MEM entry as well. [Ref TRM table 22-11] So waiting on the MCTL[19] entry waits for both channels to (simultaneously) complete to give you a single result (in MEM[19]).

  • Hi, combining the ideas you have suggested in your two answers and after looking at the Resource explorer examples I have something like this, how do you see it?

    1. void configuracion_ADC(void){
    2.     ADC14->CTL0 |= ADC14_CTL0_ON;
    3.     ADC14->CTL0 |= ADC14_CTL0_MSC | ADC14_CTL0_CONSEQ_1 | ADC14_CTL0_SSEL_4 | ADC14_CTL0_SHT0_7 | ADC14_CTL0_SHP; /* ADC14CONSEQ_1: Multi-channel-single-conversion; ADC14MSC: Multiple-sampling
    4.     ADC14_CTL0_SSEL_4: SMCLK; ADC14_CTL0_SHP: S/H del sampling Timer; ADC14_CTL0_SHT0_7: S/H with 192 clock cycles*/
    5.     ADC14->CTL1 =  ADC14_CTL1_RES_3 | ADC14_CTL1_DF;     /* 14-bit resolution; ADC14DF: -V(REF) = 8000h, +V(REF) = 7FFCh */
    6.     ADC14->MCTL[0] |= ADC14_MCTLN_INCH_18 | ADC14_MCTLN_DIF; 
    7.     ADC14->MCTL[1] |= ADC14_MCTLN_INCH_20 | ADC14_MCTLN_DIF; 
    8.     ADC14->MCTL[2] |= ADC14_MCTLN_INCH_22 | ADC14_MCTLN_DIF | ADC14_MCTLN_EOS; /* Vref=AVCC; ADC14EOS: End of Sequence */
    9.     P8SEL1 |= (BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7); 
    10.     P8SEL0 |= (BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
    11.     ADC14->CTL0 |= ADC14_CTL0_ENC;
    12. }

    This part of the code would be inside each case statement: 

    1. ADC14->CTL0 |= ADC14_CTL0_SC; /* ADC14SC: start conversions */
    2. while(!ADC14->IFGR0 & ADC14_IFGR0_IFG2); // We wait until the conversion has been completed on the last channel of the sequence
    3. delta = ADC14->MEM[1]; /* Waiting on the MCTL[19] entry waits for both channels (A20, A21) to (simultaneously) complete to give you a single result (in MEM[1]).

    Likewise, as a substitute for the ADCBUSY as I understand it also works a while loop where you wait until you have set the flag of the ADC channel, since you ensure that the measurement is taken to have it stored in the MEM register of that channel, as I do in my code above. 

  • It looks about right -- once you figure out you have a zero-crossing you already have the other (simultaneous) data.

    I think you need to read MEM[2] in order to clear IFG2. Alternatively, you can just wait (spin) on ADC14BUSY, since each CONSEQ=1 batch stops after one pass.

  • Okay, thank you very much! One last question to close the subject... in the users guide it specifies the following about the ADC working in differential mode:
    For differential inputs, this means that the result has an offset of 8192 added to it to make the number positive.
    As my goal is to be able to have negative ADC read cases for my program idea, is it as simple as when assigning to the delta variable, instead of directly assigning it the value of the MEM[channel] register do the following?
    delta = ADC14->MEM[0] - 8192

  • Table 22-2  tells me that, even though the reading is internally stored unsigned (as though DF=0) it is corrected by the ADC when output with DF=1 by subtracting 8192.

    You might try tying the An pin-pair together and see if you get apx 0 with DF=1.