入門
如今,無需進行一些編程就很難創建任何電子電路。不幸的是,您學習為一個制造商創建固件的知識不一定適用于另一個制造商。
出于這個原因,購買評估板通常是一個好主意,這樣您就可以學習在一個肯定可以正常工作的板上對您的微控制器進行編程,然后再用自己的設計來解決問題。熟悉制造商為您提供的資源(論壇、數據表、用戶指南、視頻等)也是一個好主意。
在這種情況下,我使用了MSP430FR2633,因此我在MSP CapTIvate MCU 開發套件上進行了練習,并向TI 的 E2E 社區尋求建議。
本文并未涵蓋源代碼的每一行;相反,它以源代碼為例提供了有關固件編程的一般信息。
添加 ASCII 引腳圖
最后,我想為自己添加一些額外的參考。由于這是一個自定義板,我想在一個位置向自己提供盡可能多的信息。很有可能一個小時后我不會記得我的引腳連接是什么,而且我肯定不會記得一個星期后。如果我進行任何編程更改,不必挖掘原理圖會很好。
出于這個原因,我在源代碼中包含了一個連接圖。網絡上有各種ASCII 圖表生成器,這使得操作相對快速。
// 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
我們需要做的第一件事是處理我們的引腳,以便編譯器知道哪些設備連接到什么。為此,您需要了解寄存器的概念。如果您已經熟悉這些概念,請隨意跳到下面的“定義寄存器描述快捷方式”。
什么是寄存器?
寄存器是內存中由十六進制數字標識的位置,位屬于寄存器。每個寄存器中的位控制微控制器功能的特定方面。
每個寄存器控制一個字節(8 位)或一個字(16 位)。然而,為了使討論簡單,下圖說明了控制單個字節的寄存器。
定義寄存器描述快捷方式
現在我們對寄存器有了非常簡短的背景,讓我們在上下文中使用它。
MSP430FR2xx 系列用戶指南中的寄存器描述指示哪些位控制哪些功能。當寄存器不允許直接訪問單個位時,您可以將字節操作與適當的位掩碼結合使用;單個位可以使用“|=”運算符設置,使用“&=”運算符清除,或使用“^=”運算符切換。
上圖顯示了四種字節操作前后虛寄存器 W、X、Y 和 Z 中的數據。
配置位在數據表中以十六進制或二進制表示法提供。但是,鍵入諸如 0x3F2A &= 0xC9 之類的內容并跟蹤程序中發生的事情是相當不方便的。因此寄存器和引腳名稱在引用的頭文件中或在程序的開頭定義。并且使用寄存器的名稱和與該寄存器關聯的引腳而不是原始的十六進制和二進制數。例如,“WDTCTL = WDTHOLD”(WatchDog Timer ConTroL 寄存器 = WatchDog Timer HOLD 值)—換句話說,停止看門狗定時器。
因此,編程不再使用十六進制地址,而是使用這些命名的快捷方式指定寄存器和位,并直接或使用字節操作(例如上面提到的那些)修改位。
void main(void) { WDTCTL = (WDTPW | WDTHOLD); // Stop watchdog timer … CSCTL3 |= SELREF__REFOCLK; // Set REFO as FLL reference source CSCTL0 = 0; // clear DCO and MOD registers CSCTL1 &= ~(DCORSEL_7); // Clear DCO frequency select bits first CSCTL1 |= DCORSEL_5; // Set DCO = 16MHz …
命名變量——引腳的位位置
對于 MSP430,每個端口控制一組引腳 — GPIO 通常為 8 位,因此一個端口對應一個字節。雖然您不能立即定義端口,但控制端口引腳的寄存器不可位尋址,因此無法創建與單個引腳對應的名稱(例如,“#define somePinVar 1.5”)。
相反,我們將名稱附加到引腳的位位置,并使用該名稱與端口寄存器一起控制它。
首先命名引腳(它們所連接的端口將在程序后面說明)。這可以在主程序的開頭或在單獨的頭文件中完成。選擇變量和常量的名稱是為了便于以后理解,并基于 PCB 上相應的網絡名稱。
#define UART_RX_PIN BIT5 // P2.5 eUSCI_A1 #define UART_TX_PIN BIT6 // P2.6 eUSCI_A1 #define SPI_EN_PIN BIT0 // P1.0 eUSCI_B0 -- not used #define SPI_CLK_PIN BIT1 // P1.1 eUSCI_B0 #define SPI_MOSI_PIN BIT2 // P1.2 eUSCI_B0 -- not used yet #define SPI_MISO_PIN BIT3 // P1.3 eUSCI_B0 #define ADC24_RDL BIT0 // P3.0 set low always #define ADC24_CNV BIT1 // P3.1 L->H->L (20 ns) to convert #define ADC24_BUSY BIT4 // P2.4 goes low after conversion #define ST_IN1 BIT0 // 3PST switch input 0 #define ST_IN2 BIT1 // 3PST switch input 1
程序的下一部分定義了使用的變量以及函數原型。我使用全局變量并將數組保留為易失性,以避免 SPI 和 UART 中斷服務例程的任何潛在問題。本節中定義的所有變量都可用于所有函數。
// Variable Declarations … uint16_t numTrials; // number of times to repeat reads uint16_t numReads; // number of conversions per read … uint8_t byteCounter; // need 24 bits + 16 bits (5 bytes) volatile uint8_t dataBuffer[5]; // Holder for all SPI data // Function Prototypes void readADC(uint16_t); // Runs conversions uint8_t spix(uint8_t); // SPI interface communication void uartx(uint8_t); // UART interface communication one-way
分配引腳功能
MSP430FR2633 具有可支持多種功能的引腳。因此,必須告知 MCU 引腳是用于通用輸入/輸出還是用于UART或I 2 C等集成外設。
以下幾行代碼配置端口 2 使用,首先為 UART Tx/Rx 連接對應的端口 2 引腳選擇備用功能(即集成外設),然后配置 UART_TX 引腳和兩個自檢開關引腳作為輸出。
// Select module function for UART (page 63) P2SEL0 |= (UART_TX_PIN | UART_RX_PIN); // Set port pins as output for UART and SP3T Switch P2DIR |= (UART_TX_PIN | ST_IN1 | ST_IN2);
自定義函數
自定義函數用于讀取 ADC — 基本上只是切換啟動轉換標志,等待繁忙指示器關閉,然后再次切換啟動轉換標志 — 由在開始時設置的常數所指示的次數該程序。
void readADC(uint16_t numReads) { // Start conversion, wait for busy flag to turn off, // and start the next conversion. for (counter = 1; counter <= numReads; counter++) { P3OUT ^= ADC24_CNV; // Toggle convert flag high > 20 ns to start __delay_cycles(1); // measurement. 1/16MHz * 1 cycles = 62.5 ns P3OUT &= ~ADC24_CNV; // Set convert flag back low // Wait to let conversion finish - might never enter function while ((P2IN & ADC24_BUSY)) { __delay_cycles(1); // 1/16Mhz = 62.5 ns } }
收集數據
數據將從我的微控制器中出來,并通過 USB 快速傳輸到 PC。如果我在通過 UART 到 USB 轉換器發送數據之前將此數據轉換為十進制等效值或度/分/秒,則很難看到數據以每秒約 100 行的速度滾動時有多穩定。相反,我們將數據轉換為二進制。
通過將數據轉換為二進制,我可以在串行監視器中快速查看有多少未更改的位,只需從左側開始計數。一旦位開始變化,我就會遇到噪音(當然,假設傳感器完全靜止)。數據以我在完整程序中用制表符分隔的塊的形式出現:三個字節用于角度數據,兩個字節用于每次讀取的樣本數(我后來又添加了兩個字節來創建一個計數器)。
for (byteCounter = 0; byteCounter < 5; byteCounter++) //0,1,2,3,4 { for (bitCounter = 0; bitCounter < 8; bitCounter++) { if (((dataBuffer[byteCounter] >> (7 - bitCounter)) & 0b1) == 0) { uartx('0'); // ASCII 0 } Else { uartx('1'); // Ascii 1 } }
您會注意到我必須更改每個字節的“字節順序”,以便它首先到達 UART MSB。
樣本數據顯示了 ADC 讀數的一列、自上次數據傳輸以來讀取的讀數數量以及一個計數器。
使用 Mathematica 處理數據
在最終程序中,我最終添加了一個試用計數器并使用Mathematica(在Rasbian OS上免費)處理數據。未來的文章將更詳細地解釋數據和數據處理。
初步試驗的數據如上所示。帶有彩色三角形的垂直刻度顯示三個縮放級別的最大值、+1σ、平均值、-1σ 和最小值。還包括散點圖、直方圖及其伴隨的理想化正態分布。
本文解釋了該項目的一小部分固件編程。下一篇文章將描述設備中的噪聲,未來的文章將分析數據。
您將在下面找到 MCU 上程序的完整源代碼的可下載文件。如果您也有興趣訪問 Mathematica 源代碼文件,請在下面的評論中告訴我!
Precision_Inclinometer_Firmware.zip
// _ _ _ _ _____ _ _ _ // /\ | | | /\ | | | | / ____(_) (_) | // / \ | | | / \ | |__ ___ _ _| |_| | _ _ __ ___ _ _ _| |_ ___ // / /\ \ | | | / /\ \ | '_ \ / _ \| | | | __| | | | '__/ __| | | | | __/ __| /// ____ \| | |/ ____ \| |_) | (_) | |_| | |_| |____| | | | (__| |_| | | |_\__ \ //_/ \_\_|_/_/ \_\_.__/ \___/ \__,_|\__|\_____|_|_| \___|\__,_|_|\__|___/ // _____ _ __ _____ _ // | |___ ___| |_ __| | | | |_ _ ___| |_ ___ ___ // | | | | .'| _| '_| | | | | | | | . | | -_|_ -| // |_|_|_|__,|_| |_,_| |_____| |__|__|___|_ |_|_|___|___| // w/ support by Bruce McKenney |___| 2018/11/09 // _ __ // ' ) ) _/_ // ______ . ./--'__. / __. // / / / <(_// \(_/|<_(_/|_ // __ __ _ ___ __ __ __ _ __ _ _ ____ ____ ____ ____ // ( | ( \/ __| ) ( | ( \/ \( \/ | __|_ _| __| _ \ // )(/ ( (__/ (_/\)(/ ( O ) \/ \) _) )( ) _) ) / // (__)_)__)\___)____(__)_)__)\__/\_)(_(____)(__)(____|__\_) // /****************************** 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 /********************************* Description ********************************/ // by Mark Hughes for AllAboutCircuits.com. Find complete information for this // project at http://allaboutcircuits.com/author/mark-hughes // 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 at 115200, and then from the CP2102N to the computer. // The data is sent over UART in Binary format MSB first using ASCII characters. // self-test and auto-calibration not yet implemented. // When ST_IN1 != ST_IN2, enters self-test mode for sensor /*********************************** Headers **********************************/ #include #include #include /******************************* Pin Definitions ******************************/ #define UART_RX_PIN BIT5 // P2.5 eUSCI_A1 #define UART_TX_PIN BIT6 // P2.6 eUSCI_A1 #define SPI_EN_PIN BIT0 // P1.0 eUSCI_B0 -- not used #define SPI_CLK_PIN BIT1 // P1.1 eUSCI_B0 #define SPI_MOSI_PIN BIT2 // P1.2 eUSCI_B0 -- not used yet #define SPI_MISO_PIN BIT3 // P1.3 eUSCI_B0 #define ADC24_RDL BIT0 // P3.0 set low always #define ADC24_CNV BIT1 // P3.1 L->H->L (20 ns) to convert #define ADC24_BUSY BIT4 // P2.4 goes low after conversion #define ST_IN1 BIT0 // 3PST switch input 0 #define ST_IN2 BIT1 // 3PST switch input 1 /**************************** Variable Declarations ***************************/ // Can be consolidated and revised later with proper function calls and // data returns later on. uint8_t bitCounter; // 0-7 counter for bits in a byte. uint8_t byteCounterSPI; // 0-4 counter for SPI data bytes. uint8_t byteCounter; // need 24 bits + 16 bits (5 bytes) uint16_t counter; // Temporary counter uint16_t numTrials; // Temporary counter uint16_t numTrialsMax = 1024; // Number of times to repeat measurement uint16_t numReads = 4; // number of conversions per read. // Can replace volatile buffer with pointer later on. volatile uint8_t dataBuffer[5]; // Holder for all SPI data /****************************** Function Prototypes ***************************/ // Function Prototypes void readADC(uint16_t); // Decides number of conversions uint8_t spix(uint8_t); // SPI interface void uartx(uint8_t); // UART interface /******************************** Main Program ********************************/ void main(void) { //********************* Begin Configuration ******************************** WDTCTL = (WDTPW | WDTHOLD); // Stop watchdog timer FRCTL0 = FRCTLPW | NWAITS_1; // FRAM configuration for > 8 MHz __bis_SR_register(SCG0); // Disable Frequency Locked Loop (FLL) CSCTL3 |= SELREF__REFOCLK; // Set REFO as FLL reference source CSCTL0 = 0; // Clear DCO and MOD registers CSCTL1 &= ~(DCORSEL_7); // Clear DCO frequency select bits first CSCTL1 |= DCORSEL_5; // Set DCO = 16MHz CSCTL2 = FLLD_0 + 487; // DCOCLKDIV = 16MHz __delay_cycles(3); // Wait to allow stabilization of clock __bic_SR_register(SCG0); // Reenable FLL while (CSCTL7 & (FLLUNLOCK0 | FLLUNLOCK1)) /*EMPTY*/; // FLL locked // default DCOCLKDIV as MCLK and SMCLK source CSCTL4 = SELMS__DCOCLKDIV | SELA__REFOCLK; // Disable GPIO power-on default high-impedance mode PM5CTL0 &= ~LOCKLPM5; // Disable GPIO power-on default high-impedance mode // PxDIR: 0(In) 1(Out) // PxSEL: Function Select Register (see datasheet) // PxOUT: 0(L) 1(H): Output Register // PxREN: 0(L) 1(En): Resistor Enable (on input only) // Select SPI module function for SPI (page 60) P1SEL0 |= (SPI_MISO_PIN | SPI_MOSI_PIN | SPI_CLK_PIN); // Set MOSI and CLK as outputs. P1DIR |= (SPI_MOSI_PIN | SPI_CLK_PIN); // Select module function for UART (page 63) P2SEL0 |= (UART_TX_PIN | UART_RX_PIN); // Set port pins for UART and SP3T Switch P2DIR |= (UART_TX_PIN | ST_IN1 | ST_IN2); // IN1/IN2 initially low to open SPST switch. P2OUT &= ~(ST_IN1 | ST_IN2); // Set port pins for ADC P3SEL0 &= ~(ADC24_RDL | ADC24_CNV); // Set direction for RDL and CNV P3DIR |= (ADC24_RDL | ADC24_CNV); // Set port output low for RDL and CNV P3OUT &= ~(ADC24_RDL | ADC24_CNV); // Setup SPI in UCB0 Control Word 0 // Place UCB in reset state before modifying settings UCB0CTLW0 |= UCSWRST; // Master-mode, synchronous clock, inactive state high, MSB first. UCB0CTLW0 |= (UCMST | UCMSB | UCCKPH | UCSYNC | UCMSB | UCSSEL__SMCLK); // Bit clock prescaler UCB0BRW = 0x0002; // Bit rate clock = SMCLK/2 = 8 MHz // Release reset and initialize state machine UCB0CTLW0 &= ~UCSWRST; // Put UCA state machine in reset, select mode and clock. UCA1CTLW0 = UCMODE_0 | UCSSEL__SMCLK | UCSWRST; // UART, SMCLK, Reset // 16M/(UCOS)16/9600 UCA1BRW = 104 // UCA1BRW = 104; // UCBRS=0xD6, 16x oversample, UCBRF=2 // UCA1MCTLW = (0xD6 << 8) | UCOS16 | UCBRF_2; // 16M/(UCOS)16/115200 UCA1BRW = 8; UCA1BRW = 8; // 115200 Baud. UCA1MCTLW = (0xD6 << 8) | UCOS16 | UCBRF_11; // 115200 Baud. // Release reset and initialize state machine UCA1CTLW0 &= ~UCSWRST; /**************************** End Configuration ***************************/ /****************************** Main Program ******************************/ // Read the ADC a certain number of times. Toggle ST_IN1 and ST_IN2 to // flash LED after certain number of reads. for (numTrials = 1; numTrials <= numTrialsMax; numTrials++) { // Turn LEDS on/off every 100 trials if (numTrials % 100 == 1) { P2OUT ^= (ST_IN1 | ST_IN2); } __delay_cycles(1); readADC(numReads); // Perform n conversions } // Turn LEDS off because numTrials likely didn't. P2OUT &= ~(ST_IN1 | ST_IN2); // Add an additional carriage return and newline. uartx('\r'); // Carriage return uartx('\n'); // Newline // End main. } /**************************** Function to read ADC ****************************/ void readADC(uint16_t numReads) { // Start conversion, wait for busy to turn off, and start the next conversion. for (counter = 1; counter <= numReads; counter++) { P3OUT ^= ADC24_CNV; // Toggle convert flag high > 20 ns to start __delay_cycles(1); // measurement. 1/16MHz * 1 cycles = 62.5 ns P3OUT &= ~ADC24_CNV; // Set convert flag back low // Wait to let conversion finish - might never enter function while ((P2IN & ADC24_BUSY)) { __delay_cycles(1); // 1/16Mhz = 62.5 ns } } // Shift out dummy bytes to allow ADC data to shift into dataBuffer[] for (byteCounterSPI = 0; byteCounterSPI < 5; byteCounterSPI++) { // Shift out useless data to allow shift in of ADC data. dataBuffer[byteCounterSPI] = spix(0xFF); } // Find binary equivalent of each dataBuffer byte. Send it out over UART. // Each byte is shifted to the right n-bits and the LSB is read as 1 or 0 // (7 - bitCounter) is used to change Endianess. for (byteCounter = 0; byteCounter < 5; byteCounter++) //0,1,2,3,4 { for (bitCounter = 0; bitCounter < 8; bitCounter++) //0,1,2,3,4,5,6,7 { // Need to change endianness of data before sending out UART if (((dataBuffer[byteCounter] >> (7 - bitCounter)) & 0b1) == 0) { uartx('0'); // ASCII 0 } else { uartx('1'); // Ascii 1 } } // Data Formatting if (byteCounter == 2) { // After the three data-bytes, add a tab to separate number of reads uartx('\t'); // Ascii tab } // After the last byte is out, send a carriage return and line feed. if (byteCounter == 4) { uartx('\t'); for (bitCounter = 0; bitCounter < 16; bitCounter++) { if (((numTrials >> (15 - bitCounter)) & 0b1) == 0) { uartx('0'); // ASCII 0 } else { uartx('1'); // Ascii 1 } } uartx('\r'); // Return uartx('\n'); // Line Feed } } return; } uint8_t spix(uint8_t c) { while (!(UCB0IFG & UCTXIFG)) /*EMPTY*/; UCB0TXBUF = c; while (!(UCB0IFG & UCRXIFG)) /*EMPTY*/; c = UCB0RXBUF; return (c); } void uartx(uint8_t c) { while (!(UCA1IFG & UCTXIFG)) /*EMPTY*/; UCA1TXBUF = c; return; }
-
微控制器
+關注
關注
48文章
7487瀏覽量
151042 -
msp430
+關注
關注
180文章
2393瀏覽量
229186 -
開發板
+關注
關注
25文章
4943瀏覽量
97188
發布評論請先 登錄
相關推薦
評論