Tool/software: Code Composer Studio
I've created a custom board for AllAboutCircuits.com and now it's time to create the firmware. The board has a sensor (muRata SCA103T-D04), an ADC (LTC2380IDE), a MCU (MSP430FR2633), and a UART to USB converter (CP2102N)
I started writing code today, and the first order of business is to read the data over SPI.
There are 40 bits -- the first 24 is the data I really need -- in two's complement notation. The next 16 bits just indicate the number of times the inclinometer was sampled. Useful, but not critical, as I could just perform a single measurement.

I'm not sending any data into the ADC -- so whatever I shift out of the MSP430 is irrelevant.
So my first question is this: what is the "best practice" way to grab those first 24 bits? It appears that the MSP430 RXBuffer has 16-bits in it -- so bit shifting & masking is an option....provided of course I did it correctly.
An array of 3 8-bit numbers is the least complex method? Are there any gotcha's? Is there a better way?
Then I'll need to move the data over UART. I'm just getting it over the debug interface now. What is the matter with my clock configuration? Did I make a stupid mistake or do I have a fundamental misunderstanding? I do realize that the UART portion of the code appears in some places and is absent in others. I backed off and started focusing on just the SPI for now.
//
// ************************** Connection Diagram ******************************
// CP2102N ┌──────┬──────┐ ┌────────────┐
// ┌────────────┐ │ │ P1.0 │→ UCB0STE →│ EN (NC) │
// │ USB │→ RX →│ P2.5 │ P1.1 │→ UCB0CLK →│ CLK │
// │ TO │→ TX ←│ P2.6 │ P1.2 │→ UCBSIMO │ SIMO (NC) │
// │ UART │ │ │ P1.3 │← UCBSOMI ←│ SOMI │
// └────────────┘ ├──────┼──────┤ │ │
// ST_IN1 ←│ P2.0 │ P2.4 │← BUSY ←│ BUSY │
// ST_IN2 →│ P2.1 │ P3.0 │→ RDL →│ RDL │
// │ │ P3.1 │→ CNV →│ CNV │
// └──────┴──────┘ └────────────┘
// MSP430FR2633 ADC
//
// ****************************************************************************
// Mark Hughes for AllAboutCircuits.com
// This is a one-directional SPI to UART to USB bridge interface
// CNV is toggled one/multiple times (up to 65535) and data is averaged inside
// the ADC until SPI_CLK toggles data out and into MSP430. Data is then sent
// via UART to CP2102N, and then from the CP2102N to the computer.
// UART communication, self-test and auto-calibration not yet implemented.
//*****************************************************************************
#include <msp430.h>
#include <driverlib.h>
#include <stdio.h>
#define SPI_EN_PIN BIT0 // P1.0 UCB -- not used at all
#define SPI_CLK_PIN BIT1 // P1.1 UCB
#define SPI_MOSI_PIN BIT2 // P1.2 UCB -- not used yet
#define SPI_MISO_PIN BIT3 // P1.3 UCB
#define UART_RX_PIN BIT5 // P2.5 eUSCI_A
#define UART_TX_PIN BIT6 // P2.6 eUSCI_A
#define ADC24_RDL BIT0 // P3.0 set low always
#define ADC24_CNV BIT1 // P3.1 toggle to start conversion
#define ADC24_BUSY BIT4 // P2.4 goes low after conversion
#define ST_IN1 BIT0 // 3PST input 0
#define ST_IN2 BIT1 // 3PST input 1
// Variable Declarations. Volatile during first phase of programming.
// Will eliminate with proper function calls & data returns later on.
volatile uint8_t byteCounter; // need 24 bits + 16 bits (5 bytes)
volatile uint8_t counter; // for various for loops
volatile uint16_t numReads; // number of times to sample
volatile int16_t angleData; // Holder for SPI data 32 bits
volatile int32_t ADCData; // Holder for all SPI data
volatile uint16_t averageData; // Holder for SPI data 16 bits
volatile int8_t dataBuffer[5]; // Holder for all SPI data
// Function Prototypes
void readADC(uint16_t); // Decide how many conversions
void main(void)
{
printf("Inside Main\r\n");
WDTCTL = (WDTPW | WDTHOLD); // Stop watchdog timer
// Really could stand to test this section better
// FRCTL0 = FRCTLPW | NWAITS_1; // Configure one FRAM waitstate per datasheet
// __bis_SR_register(SCG0); // Disable Frequency Locked Loop (FLL)
// CSCTL3 |= SELREF__REFOCLK; // Set REFO as FLL source
// CSCTL0 = 0; // clear DCO and MOD registers
// CSCTL1 &= ~(DCORSEL_7); // Clear DCO frequency select bits
// CSCTL2 = FLLD_0;// + 487; // DCOCLKDIV = 16 MHz (from sample code)
// __delay_cycles(3); // wait 3/(16 MHz)
// __bis_SR_register(SCG0); // Enable Frequency Locked Loop (FLL)
// while(CSCTL7 & (FLLUNLOCK0 | FLLUNLOCK1)); // FLL locked
//
// CSCTL4 = SELMS__DCOCLKDIV | SELA__REFOCLK; // Set default REFO (~32768Hz)
// // as ACLK source, ACLK = 32768Hz. Default DCOCLKDIV as MCLKand SMCLK source
// // page 117 programming guide. SELA is ACLK, SELMS is MCLK and SMCLK
//
PM5CTL0 &= ~LOCKLPM5; // Disable GPIO power-on default high-impedance mode
// Select module function for SPI (page 60) and UART (page 63)
// Port direction does not need to be defined for any pin
P1SEL0 |= (SPI_MISO_PIN | SPI_MOSI_PIN | SPI_CLK_PIN);
P1DIR |= (SPI_MOSI_PIN | SPI_CLK_PIN); // programming guide 314 says to set
//direction. Datasheet says it doesn't matter. Which is correct?
P2SEL0 |= (UART_TX_PIN | UART_RX_PIN);
P2DIR |= (UART_TX_PIN);
// ╔════════╦════════╦═══════╦════════╦═══════╦═════════╦═════════╦═══════╗
// ║ 15 ║ 14 ║ 13 ║ 12 ║ 11 ║ 10 ║ 9 ║ 8 ║
// ╠════════╬════════╬═══════╬════════╬═══════╬═════════╬═════════╬═══════╣
// ║ UCCKPH ║ UCCKPL ║ UCMSB ║ UC7BIT ║ UCMST ║ UCMODEx ║ UCMODEx ║ USYNC ║
// ╚════════╩════════╩═══════╩════════╩═══════╩═════════╩═════════╩═══════╝
//
// ╔═════════╦═════════╦══════╦══════╦══════╦══════╦════════╦═════════╗
// ║ 7 ║ 6 ║ 5 ║ 4 ║ 3 ║ 2 ║ 1 ║ 0 ║
// ╠═════════╬═════════╬══════╬══════╬══════╬══════╬════════╬═════════╣
// ║ UCSSELx ║ UCSSELx ║ RES ║ RES ║ RES ║ RES ║ UCSTEM ║ UCSWRST ║
// ╚═════════╩═════════╩══════╩══════╩══════╩══════╩════════╩═════════╝
// Setup SPI in UCB0 Control Word 0
UCB0CTLW0 |= UCSWRST; //place in reset state before modifying settings
// Master-mode, synchronous clock, inactive state high, MSB first.
UCB0CTLW0 |= (UCMST | UCSYNC | UCCKPL | UCMSB | UCSSEL__SMCLK);
// Bit clock prescaler
UCB0BRW = 0x0002; // Bit rate clock = SMCLK/2 = 8 MHz
UCB0CTLW0 &= ~UCSWRST; // Initialize USCI state machine
// Select module function for UART -- adding Rx interrupt....may use later
// P2IES |= UART_RX_PIN; // Port 2 Interrupt Edge Select
// P2IFG &= ~UART_RX_PIN; // Clear Interrupt flag
// P2IE |= UART_RX_PIN; // Enable interrupt on RX -- no code implemented yet.
// Set port pins & directions for ADC and switch control
P3DIR |= (ADC24_RDL | ADC24_CNV);
P2IN |= ADC24_BUSY;
P2REN |= ADC24_BUSY; // Enable pulldown resistor
P2DIR |= (ST_IN1 | ST_IN2);
// P2.0, P2.1 control the Self-Test pins.
// Only one self test can or should be activated at a time.
// Look at ADC convert and read results before activating other self-test.
//
// Use the LED as a status indicator.
// Consider simple flashing, or more complex signals for status
//
// ┌────┬────┬───┬───┬───┐
// │P2.0│P2.1│ST1│ST2│LED│
// ├────┼────┼───┼───┼───┤
// │ L │ L │ │ │ │
// │ L │ H │ │ * │ │
// │ H │ L │ * │ │ │
// │ H │ H │ │ │ * │
// └────┴────┴───┴───┴───┘
//*****************************************************************************
//*****************************************************************************
// Toggle ST_IN1 and ST_IN2 flash LED. Later use self-test feature.
for (byteCounter = 1; byteCounter <= 10; byteCounter += 1)
{
P2OUT ^= (ST_IN1 | ST_IN2);
__delay_cycles(50000);
}
P2OUT &= ~(ST_IN1 | ST_IN2); // Turn LED off
printf("Reading ADC:\r\n");
readADC(4); // Perform n conversions
printf("Returned from reading ADC:\r\n");
}
void readADC(uint16_t numReads)
{
// Start conversion, wait for busy flag to turn off,
// and start the next conversion.
printf("Inside readADC\r\n");
printf("Initialize Conversion: ");
for (counter = 1; counter <= numReads; counter++)
{
printf("%d H - ", counter);
P3OUT ^= ADC24_CNV; // Toggle convert flag high to start measurement
__delay_cycles(4); // Convert needs 20 ns of logic high to activate
P3OUT ^= ADC24_CNV; // 1/16MHz * 4 cycles = 250 ns at 16MHz
printf("L- ");
if (P2IN & ADC24_BUSY != 0)
{ // Wait to let conversion finish -- likely will never enter
printf("ADC Reading: BUSY\r\n");
__delay_cycles(50); // 1/16Mhz = 62.5 ns
}
printf("\r\n");
}
// Trial 1 -- two variables & bitshifts
// results: got data -- but data appeared inaccurate
// printf("\n\rEntering SPI Readout Routine\n\r");
//
// for (byteCounter = 1; byteCounter <= 5; byteCounter++)
// { // started at 1 instead of 0.
// UCB0IFG ^= ~UCB0IFG; // Clear Rx flag
// UCB0TXBUF = 0xFF; // Dummy Byte to shift out
// printf("Inside receive Code Section\r\n"); // Wait for RX flag
// // Store received byte
// if (byteCounter <= 3)
// {
// printf("Storing UCB0RXBUF");
// angleData |= UCB0RXBUF;
// angleData <<= 8;
// printf("RXBUF: %u \r\n", UCB0RXBUF);
// printf("angle: %u \r\n", angleData);
// }
// if (byteCounter >= 4 & byteCounter <= 5)
// {
// printf("Storing UCB0RXBUF");
// averageData |= UCB0RXBUF;
// averageData <<= 8;
// printf("RXBUF: %u \r\n", UCB0RXBUF);
// printf("Average: %u \r\n", averageData);
// }
// Trial 2 -- one big variable to hold everything.
// results: got data -- but data appeared inaccurate
// for (byteCounter = 1; byteCounter <= 3; byteCounter++)
// { // started at 1 instead of 0.
// //UCB0IFG ^= ~UCB0IFG; // Clear Rx flag
// UCB0TXBUF = 0xCCCC; // Dummy 16-bits to shift out
// printf("Inside receive Code Section\r\n"); // Wait for RX flag
// // Store received byte
// if (byteCounter <= 2)
// {
// printf("Storing UCB0RXBUF\r\n");
// ADCData |= UCB0RXBUF;
// ADCData <<= 16;
// printf("RXBUF: %u \r\n", UCB0RXBUF);
// printf("ADCData: %llu \r\n", ADCData);
// }else
// {
// ADCData |= UCB0RXBUF;
// printf("RXBUF: %u \r\n", UCB0RXBUF);
// printf("ADCData: %llu \r\n", ADCData);
// }
//
// Trial 3 -- use an array to hold bytes.
// Obviously the easier way to go -- but again, data appears inaccurate.
for (byteCounter=1; byteCounter <=5; byteCounter++){
dataBuffer[byteCounter]|=UCB0RXBUF;
}
printf("ADCData: %p\n", &dataBuffer);
//Read ADC -- variables are volatile right now, so no need to return data
return;
}