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: outputing a signal from a microphone through a speaker

Part Number: MSP432P401R

Hello, I'm new to programming the MSP432 and I have problems with my code. What I want to achieve is getting audio from my microphone electret (Adafruit MAX9814), sample the signal at 44.1 kHz (the signal would be converted from analog coming from the microphone to digital), convert that digital signal back to analog and output it to a speaker so I can listen to the sound. I'm not hearing anything but static so far. Can I get guidance? I'll leave my code below.

#include "msp.h"
#include <stdint.h>

void main(void)
{
	WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD;		// stop watchdog timer

	//GPIO Setup
	P4->DIR &= ~(BIT7);// Analog input channel A6
	P4->SEL0 |= BIT7;  // Enable A/D channel A6
	P4->SEL1 |= BIT7;  // P4.7 - analog input

	P5->DIR |= BIT4;  // Output to speaker
	P5->SEL0 |= BIT4;

	//while(PCM->CTL1 & PCM_CTL1_PMR_BUSY);
	//PCM->CTL0 = PCM_CTL0_KEY_VAL | PCM_CTL0_AMR__AM_LDO_VCORE1;
	//while(PCM->CTL1 & PCM_CTL1_PMR_BUSY);

	//Set clock to 24 MHz using DCO
	CS->KEY  = CS_KEY_VAL; //Unlock CS registers
	CS->CTL0 = CS_CTL0_DCOEN | CS_CTL0_DCORSEL_4; //Choose 24 MHz clock speed
	CS->CTL1 = CS_CTL1_SELS_3 | CS_CTL1_SELM_3  ; //Use DCOCLK as source for MCLK, SMCLK + HSMCLK
	CS->KEY  = 0; //Re-lock CS registers

	// ADC14 Setup
	ADC14->CTL0 &= ~ADC14_CTL0_ENC; // Reset ENC bit for configuration

	ADC14->CTL0 = ADC14_CTL0_PDIV_1   | // Pre-divide clock by 4
	              ADC14_CTL0_SHS_1    | // Timer A0 trigger
	              ADC14_CTL0_SHP      | // Pulse mode
	              ADC14_CTL0_DIV_5    | // divide clock by 6
	              ADC14_CTL0_SSEL_3   | // ADC clock source is MCLK
	              ADC14_CTL0_CONSEQ_2 | // Repeat-single channel
	              ADC14_CTL0_SHT0_2   | // Sampling length of 16 ADC clock cycles
	              ADC14_CTL0_ON;        // ADC module on

	ADC14->CTL1 = ADC14_CTL1_RES_3; // 14-bit resolution

	ADC14->MCTL[0] = ADC14_MCTLN_INCH_6; // ref+ = AVcc, channel = A1

	// Timer_A0 Setup
	TIMER_A0->CCTL[0] = TIMER_A_CCTLN_CCIE;         //Enable CC interrupt

	TIMER_A0->CTL = TIMER_A_CTL_TASSEL_2 | // SMCLK as source for timer
	                TIMER_A_CTL_ID_1     | // Divide clock by 2 -> 48 MHz / 2 = 24 MHz
	                TIMER_A_CTL_MC_1     | // Up mode
	                TIMER_A_CTL_CLR      | // Clear timer count
	                TIMER_A_CTL_IE;        // Enable overflow interrupt

	TIMER_A0->CCR[0] = 544;    // 24 MHz / 44.1 kHz = 544
	// T = (TAxCCR0+1) / f_clk = (544+1) / 24 MHz = 2.27x10^(-5)
	TIMER_A0->CCTL[1] = TIMER_A_CCTLN_OUTMOD_7; // Set/Reset for trigger halfway through the cycle

	// Enable global interrupt
	__enable_irq();

    NVIC->ISER[0] = 1 << ((ADC14_IRQn) & 31); // Enable ADC interrupt in NVIC module

    // Wake up on exit from ISR
    SCB->SCR &= ~SCB_SCR_SLEEPONEXIT_Msk;

    // Ensures SLEEPONEXIT takes effect immediately
    __DSB();

	while(1)
	{
	    ADC14->CTL0 |= ADC14_CTL0_ENC | ADC14_CTL0_SC;   // Start conversion-software trigger

	    __sleep();
	    __no_operation(); // For debugger
	}
}

void ADC14_IRQHandler(void)
{
    while((ADC14->IFGR0 & ADC14_IFGR0_IFG6) == 0);  // waiting for conversion to be done
    //audio = ADC14->MEM[0]; // Conversion stored in audio variable.
    P5->DIR ^= BIT4;
}

//void TA0_A0_IRQHandler(void)
//{
//    TIMER_A0->CCTL[0] &= ~TIMER_A_CCTLN_CCIFG;
//}

  • > P5->DIR |= BIT4; // Output to speaker
    > P5->SEL0 |= BIT4;
    It's not clear to me what you're trying to do here. Per Data Sheet (SLAS826G) Table 6-71, P5.4 with PSEL0=1 puts out Vss. P2.5 puts out TA0.2, which you could configure to put out a PWM code based on your ADC reading. (I don't know how good it will sound, but I think it will be detectable.)
    -----------------------
    > while((ADC14->IFGR0 & ADC14_IFGR0_IFG6) == 0); // waiting for conversion to be done
    1) You should be looking for IFG0, since you're using MCTL[0].
    2) You have to enable IER0:IE0 to get the interrupt.
    3) Don't spin for the IFG in the ISR, since that fact that you got there means it's set.
    -----------------------
    > ADC14->CTL0 |= ADC14_CTL0_ENC | ADC14_CTL0_SC; // Start conversion-software trigger
    The timer will take care of triggering the ADC. Just set ENC (before the loop) and don't set SC at all.
    -----------------------
    You're dividing MCLK (24MHz) by (6*4), so your ADC clock is 1MHz. With 16 SHT clocks, and (16+1) clocks for conversion, that's 33 usec per sample. My calculator says 44.1kHz allows 22.6 usec per sample, so you'll overrun. Try reducing your clock dividers to aim for an ADC clock around 5MHz. Even better: Turn off the dividers and use MODCLK.
  • You can also find an example here.  In this example the sampling rate is 8Khz and the FFT is updated in between sample sets.

    Regards,

    Chris

  • I modified the code, but I'm still not hearing anything from my speaker. I thought it might be the microphone electret I'm using but when I test it with the oscilloscope it works.

    #include "msp.h"
    #include <stdint.h>
    
    uint16_t audio[0];
    
    void main(void)
    {
    WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD; // stop watchdog timer
    
    //GPIO Setup
    P4->DIR &= ~(BIT7); // Analog input channel A6
    P4->SEL0 |= BIT7; // Enable A/D channel A6
    P4->SEL1 |= BIT7; // P4.7 - analog input
    
    P2->DIR |= BIT4; // Output to speaker (outputs TA0.1)
    P2->SEL0 |= BIT4; // TA0.1
    
    P1->OUT &= ~BIT0; // Clear LED to start
    P1->DIR |= BIT0; // Set P1.0/LED to output
    
    //while(PCM->CTL1 & PCM_CTL1_PMR_BUSY);
    //PCM->CTL0 = PCM_CTL0_KEY_VAL | PCM_CTL0_AMR__AM_LDO_VCORE1;
    //while(PCM->CTL1 & PCM_CTL1_PMR_BUSY);
    
    //Set clock to 24 MHz using DCO
    CS->KEY = CS_KEY_VAL; //Unlock CS registers
    CS->CTL0 = CS_CTL0_DCOEN | CS_CTL0_DCORSEL_4; //Choose 24 MHz clock speed
    CS->CTL1 = CS_CTL1_SELS_3 | CS_CTL1_SELM_3 ; //Use DCOCLK as source for MCLK, SMCLK + HSMCLK
    CS->KEY = 0; //Re-lock CS registers
    
    // ADC14 Setup
    ADC14->CTL0 &= ~ADC14_CTL0_ENC; // Reset ENC bit for configuration
    
    ADC14->CTL0 = ADC14_CTL0_PDIV_0 | // Pre-divide clock by 1
    ADC14_CTL0_SHS_1 | // Timer A0 trigger
    ADC14_CTL0_SHP | // Pulse mode
    ADC14_CTL0_DIV_4 | // divide clock by 5
    ADC14_CTL0_SSEL_3 | // ADC clock source is MCLK (24 MHz)
    ADC14_CTL0_CONSEQ_2 | // Repeat-single channel
    ADC14_CTL0_SHT0_2 | // Sampling length of 16 ADC clock cycles
    ADC14_CTL0_ON; // ADC module on
    // ADC14 CLK is 24 MHz / (1*5) = 4.8 MHz
    ADC14->IER0 = ADC14_IER0_IE0; //Enable interrupt when conversion on A0 is complete
    ADC14->CTL1 = ADC14_CTL1_RES_3; // 14-bit resolution
    
    ADC14->MCTL[0] = ADC14_MCTLN_VRSEL0 | ADC14_MCTLN_INCH_6; // ref+ = AVcc, channel = A6
    
    // Timer_A0 Setup
    TIMER_A0->CCTL[0] = TIMER_A_CCTLN_CCIE; //Enable CC interrupt
    
    TIMER_A0->CTL = TIMER_A_CTL_TASSEL_2 | // SMCLK as source for timer
    TIMER_A_CTL_ID_1 | // Divide clock by 2 -> 48 MHz / 2 = 24 MHz
    TIMER_A_CTL_MC_1 | // Up mode
    TIMER_A_CTL_CLR | // Clear timer count
    TIMER_A_CTL_IE; // Enable overflow interrupt
    
    TIMER_A0->CCR[0] = 544; // 24 MHz / 44.1 kHz = 544
    TIMER_A0->CCR[1] = 272;
    // T = [(TAxCCR0+1) / f_clk] = [(544+1) / 24 MHz] = 2.27x10^(-5)
    // fpwm = [fclk / (TACCR0 + 1)] = 44.04 kHz
    //
    TIMER_A0->CCTL[1] = TIMER_A_CCTLN_OUTMOD_7; // Set/Reset for trigger halfway through the cycle
    
    // Enable global interrupt
    __enable_irq();
    
    NVIC->ISER[0] = 1 << ((ADC14_IRQn) & 31); // Enable ADC interrupt in NVIC module
    NVIC->ISER[0] = 1 << ((TA0_0_IRQn) & 31); // Enable TA0 interrupt in NVIC module
    // Wake up on exit from ISR
    SCB->SCR &= ~SCB_SCR_SLEEPONEXIT_Msk;
    
    // Ensures SLEEPONEXIT takes effect immediately
    __DSB();
    
    ADC14->CTL0 |= ADC14_CTL0_ENC; // Start conversion-software trigger
    
    while(1)
    {
    __sleep();
    __no_operation(); // For debugger
    }
    }
    
    void ADC14_IRQHandler(void)
    {
    if((ADC14->IFGR0 & ADC14_IFGR0_IFG0) == 0) // waiting for conversion to be done
    {
    audio[0] = ADC14->MEM[0]; // Conversion stored in audio variable
    P2->OUT ^= BIT4;
    }
    }
    
    void TA0_0_IRQHandler(void)
    {
    TIMER_A0->CCTL[0] &= ~TIMER_A_CCTLN_CCIFG;
    P1->OUT ^= BIT0;
    }

  • > P2->OUT ^= BIT4;
    This has no effect since (due to SEL0) the timer has control of the pin. If it did have an effect, it would be the same as what the timer is doing now, which is not what you want. The timer is putting out on TA0.1 a 44.1kHz square wave (50% duty cycle) due to CCR[0] and CCR[1], which in PWM terms is DC (constant), which doesn't describe a sound.

    I suggest you use P2.5 (TA0.2 with SEL0=1) with OUTMOD_7 and put the value from MEM[0] into CCR[2]. You can't put it directly, though -- you have to scale it to fit in [0..544] with something like "544u*MEM[0]/16384" where 16384 (2^14) represents the (14-bit) sample range.
    -----------------------------
    > if((ADC14->IFGR0 & ADC14_IFGR0_IFG0) == 0) // waiting for conversion to be done
    This will never be true (when executed). Try:
    > if((ADC14->IFGR0 & ADC14_IFGR0_IFG0) != 0) // waiting for conversion to be done
    (or you can probably just leave it out).
    -----------------------------
    I should probably ask: What are you using for a speaker? Current output from a port pin is limited to a few millamps.

**Attention** This is a public forum