# MSP-EXP430G2 LaunchPad > [User's Guide](https://www.ti.com/lit/ug/slau318g/slau318g.pdf?ts=1737451929196&ref_url=https%253A%252F%252Fduckduckgo.com%252F) ![image](https://hackmd.io/_uploads/BJy-nb0wkx.png) * Button: P1.3 * LED (Red, Green): P1.0, P1.6 * Timer UART Transmit/Receive: P1.1, P1.2 ## MSP430G2231 Microcontroller (MCU) > [Datasheet](https://www.ti.com/lit/ds/symlink/msp430g2231.pdf?ts=1737468925818&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252Fzh-tw%252FMSP430G2231) > [User's Guide](https://www.ti.com/lit/ug/slau144k/slau144k.pdf?ts=1737623837034&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252Fzh-tw%252FMSP430G2231) ### PIN ![image](https://hackmd.io/_uploads/HkrqpXTvkl.png) * P1.0 - P1.7 合稱為 Port 1 (P1) * P2.6, P2.7 合稱為 Port 2 (P2) * TA0CLK, TA0.0, TA0.1 為 Timer_A 使用 * A0 - A7 為 ADC 的 8 個 input channels * VREF 為 converter 的 reference voltage * ACLK, SMCLK 為 MCU 的 output clock signals * SCLK, SDO, SCL 為 universal serial interface (即 RS232,UART 的一種) 使用 * XIN, XOUT 用來與石英連接 * RST 為 active low 的 reset signal * NMI 為 nonmaskable interrupt input ### Block Diagram ![image](https://hackmd.io/_uploads/B1Tl-V6wkg.png) * Brownout Protection: 電壓過低 / 過高時自動停止 ### Features * 可執行 27 種 machine code * 7 種 addressing mode * 16 個 16-bit register ![image](https://hackmd.io/_uploads/H1KQQNavye.png) * status register: 例如 ALU 的運算結果 flag * constant register: 可放 constant 值 * Direct memory-to-memory transfers (例如: 可直接在 RAM <-> Flash 做傳遞) * little-endian,2 個 bytes 為一個 word > ![image](https://hackmd.io/_uploads/rk7gLNTwkx.png) * Memory-mapped I/O ### Memory Organization ![image](https://hackmd.io/_uploads/r1FZgWAwkg.png) * Information Memory: 放 nonvolatile data (例如: 板子的 serial number) * Peripherals (周邊設備): 給周邊設備做 memory-mapped I/O 使用 * SFR: Special Function Registers ### Low Power Modes ![image](https://hackmd.io/_uploads/BkIQkjNu1e.png) ![image](https://hackmd.io/_uploads/Bk8RbsVdyg.png) ![image](https://hackmd.io/_uploads/HyVtkiNdke.png) * CPU 下 Low Power Mode 指令時,CPU 內的 status register 內的 SCG1, SCG0, OSCOFF, CPUOFF bits 會依照 Table 2-2 設定,之後 CPU 即會 sleep。而當外界發生 interrupt,並傳至 CPU 後,CPU 會將 $pc 及 status register 存至 memory 內的 stack (即 interrupt 的標準程序),再用硬體把 SCG1, SCG0, OSCOFF, CPUOFF bits 設回,進入 Active Mode,再開始跑 ISR,ISR 內會將 stack 內的 $pc 及 status register 設回,故做完 ISR 後會 **繼續在原先的 Low Power Mode**。若不希望回到 Low Power Mode,則可 **在 ISR 內直接修改 memory 內 stack 的 status register 值 ### I/O ![image](https://hackmd.io/_uploads/SJHZ-WCDkg.png) ![image](https://hackmd.io/_uploads/rk-f--CwJl.png) * Port P1 及 P2 做 I/O * P1REN, P1SEL, ... 等 register 都是放在 P1/P2 裡面 * P1IN: CPU 可從此 register 讀 P1 的 data * P1OUT: CPU 可從此 register 寫入 data 進 P1 * P1DIR: 有 8 個 bit,對應 P1.0 - P1.7,bit 為 1/0 表示對應的 PIN 為做 output/input * P1SEL: Bits written as 1 configure the corresponding pin for use by the specialized peripheral; 0 configure general-purpose I/O * P1REN: 有 8 個 bit,對應哪個 PIN 是用 pull-up/pull-down resistor ### Timer System ![image](https://hackmd.io/_uploads/HJOQZq1Oke.png) 紅框處為 Timer 的 Block Diagram,藍框處為 CCR(0|1|2) 的 Block Diagram。CCRx 為 Capture / Compare Blocks,三組表示可同時做三個 capture / compare 的工作。每個 Capture / Compare Block 分別由 TACCTLx register 做控制;TACCRx 用來放要 capture / compare 的值。 Capture mode: 當輸入信號的某些條件成立,就會記錄目前 Timer 的值進 TACCRx ![image](https://hackmd.io/_uploads/Hk9h10kOyx.png) ![image](https://hackmd.io/_uploads/BJOpN9yOye.png) ![image](https://hackmd.io/_uploads/SJBWS9ydkx.png) ![image](https://hackmd.io/_uploads/rJH7kfZdJx.png) * Timer_A Registers 都是放在 Timer 內部 * TAIV: 發生 interrupt 的時候,值表示最高 priority 的 interrupt (由硬體來做,故可減短用軟體來做時需要 polling 最高 priority 的 interrupt 的時間) ![image](https://hackmd.io/_uploads/SklJigGdJe.png) :::warning * Up mode (上數) 時,例如要數 100,則 TACCR0 要設為 100 - 1 = **99**。當 TACCR0 == 99 時,TACCR0 CCIFG interrupt flag 會被 set。Timer reset 為 0 時,TAIFG 會被 set。 * Continuous mode 時,會算到 overflow (0xFFFF) 才會歸零。歸零時 TAIFG 一樣會被 set。 * Up / Down mode 時,上數至 TACCR0 並下數至 0,歸零時 TAIFG 一樣會被 set。 ::: ### Clock System ![image](https://hackmd.io/_uploads/r11rEfZukg.png) * LFXT1、XT2、DCO、VLO 為 clock source (振盪器發出,還不是完整方波),ACLK、MCLK、SMCLK 為 clock signal ### Interrupt ![image](https://hackmd.io/_uploads/rkuUYxMuJx.png) * 有三種 interrupt 1. System reset: 一開始供電時、按下 reset button 時、Watchdog Timer、flash key violation、$pc out-of-range 等 2. Non-maskable interrupt (NMI): 把 GIE disable 後還是會產生的 interrupt,例如 oscillator fault、flash access violation 3. Maskable interrupt: 把 GIE 或某個控制的 bit disable,此 interrupt 就不會發生 ### ADC (ADC10) ![image](https://hackmd.io/_uploads/HyzhJzfukg.png) ![image](https://hackmd.io/_uploads/BJF0jbzdJg.png) * ADC轉換好後結果會存至 ADC10MEM,並 raise ADC10IFG interrupt flag * 有內建兩個 voltage reference (Vref): 2.5V 及 1.5V,需先設定 ADC10CTL0 的 REFON 為 1 做 enable,再設定 REF2_5V,1 為使用 2.5V,0 為使用 1.5V,並等待 30µs * DTC (Data Transfer Controller): 直接把 ADC10MEM (轉換完成的數值) 寫入記憶體 * 若 ADC10DTC1 設為 0,則當轉換結果存入 ADC10MEM 後,ADC10IFG 會被 set;若設為 1,則當 data block transfer 完成後,ADC10IFG 才會被 set ## 範例程式 ### Toggle Red & Green LEDs ```c= #include <msp430x2231.h> void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer P1DIR |= 0x41; // set P1.0 & 6 to outputs //(red & green LEDs) for(;;) { volatile unsigned int i; P1OUT ^= 0x41; // Toggle P1.0 & 6 using XOR i = 50000; // Delay do (i--); while (i != 0); } } ``` :::info * `volatile` 修飾字為提醒 compiler此變數不能放進 register 做 optimization,因為此變數有可能會受非此程式的影響而被修改 ::: ### 按鈕控制 LED 按鈕按下時 LED 亮;放開時 LED 暗 ```c= #include <msp430g2231.h> #define LED1 BIT0 //P1.0 to red LED #define B1 BIT3 //P1.3 to button volatile unsigned int i,j; void main(void){ WDTCTL = WDTPW + WDTHOLD; //Stop watchdog timer P1OUT |= LED1; // 初始化 P1OUT 的值,避免初始值未定義 P1DIR = LED1; //Set pin with LED1 to output // P1REN = B1; // 若 MCU 為 msp430g2553 則要特別指定 pull-up resistor for(;;){ while((P1IN & B1) != 0){ //Loop on button up i = P1IN; j = P1OUT; } P1OUT &= ~LED1; // Turn LED1 off while((P1IN & B1) == 0){ //Loop on button down i = P1IN; j = P1OUT; } P1OUT |= LED1; // Turn LED1 on } } ``` ### Red LED 以 1 Hz toggle (使用 SMCLK 800 KHz) ```c= #include <msp430g2231.h> #define LED1 BIT0 void main (void) { WDTCTL = WDTPW|WDTHOLD; // Stop watchdog timer P1OUT = ~LED1; P1DIR = LED1; TACCR0 = 49999; TACTL = MC_1|ID_3|TASSEL_2|TACLR; //Setup Timer_A //up mode, divide clk by 8, use SMCLK, clr timer for (;;) { // Loop forever while (!(TACTL&TAIFG)) { // Wait time up } // doing nothing TACTL &= ~TAIFG; // Clear overflow flag P1OUT ^= LED1; // Toggle LEDs } // Back around infinite loop } ``` ### 設定 DCO 為 1 MHz 並 enable crystal ```c= #include <msp430g2231.h> void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF) while(1); // If TLV erased, TRAP! BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; P1DIR = 0x41; // P1.0 & 6 outputs (red/green LEDs) P1OUT = 0x01; // red LED on BCSCTL3 |= LFXT1S_0; // Enable 32768 crystal IFG1 &= ~OFIFG;// Clear OSCFault flag P1OUT = 0; // red LED off BCSCTL2 |= SELS_0 + DIVS_3; // SMCLK = DCO/8 // 做要做的事 } ``` ### 使用 Timer_A 的 interrupt signal 來 toggle LED ```c= #include <io430x11x1.h> // Specific device #include <intrinsics.h> // Intrinsic functions #define LED1 BIT0 void main (void) { WDTCTL = WDTPW|WDTHOLD; // Stop watchdog timer P1OUT = ˜LED1; P1DIR = LED1; TACCR0 = 49999; // Upper limit of count for TAR TACCTL0 = CCIE; // Enable interrupts TACTL = MC_1|ID_3|TASSEL_2|TACLR; // Up mode, divide clock by 8, clock from SMCLK, clear __enable_interrupt(); // Enable interrupts (intrinsic) for (;;) { // Loop forever doing nothing } } // Interrupt service routine for Timer_A #pragma vector = TIMERA0_VECTOR __interrupt void TA0_ISR (void){ // function 名稱可自訂 P1OUT ˆ= LED1; // Toggle LED } ``` ### 使用 Port1 的 interrupt signal 來 toggle LED ```c= void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer P1DIR = 0x01; // P1.0 output, else input P1OUT = 0x10; // P1.4 set, else reset P1REN |= 0x10; // P1.4 pullup P1IE |= 0x10; // P1.4 interrupt enabled P1IES |= 0x10; // P1.4 Hi/lo edge P1IFG &= ~0x10; // P1.4 IFG cleared _BIS_SR(GIE); // Enter interrupt (把 SR register 裡面 GIE 的 bit 設為 1,跟 __enable_interrupt() 是一樣的) while(1); } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { P1OUT ^= 0x01; // P1.0 = toggle P1IFG &= ~0x10; // P1.4 IFG cleared } ``` ### 透過 software 迴圈,一次讀一筆 ADC 的資料進 memory 讀完一筆資料就 interrupt CPU ```c= #include "msp430g2231.h" void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop WDT ADC10CTL0 = ADC10SHT_2 + ADC10ON + ADC10IE // H&S time 16x, interrupt enabled ADC10CTL1 = INCH_1; // Input A1 ADC10AE0 |= 0x02; // Enable pin A1 for analog in P1DIR |= 0x01; // Set P1.0 to output for (;;) { ADC10CTL0 |= ENC + ADC10SC; // Start sampling __bis_SR_register(CPUOFF + GIE); // Sleep (讓 CPU Sleep,等到 interrupt 時透過下面的 ISR 喚醒 CPU,才會繼續執行下面的程式) if (ADC10MEM < 0x1FF) // 0x1FF = 511 P1OUT &= ~0x01; // Clear P1.0 LED off else P1OUT |= 0x01; // Set P1.0 LED on } } // ADC10 interrupt service routine #pragma vector=ADC10_VECTOR __interrupt void ADC10_ISR(void) { __bic_SR_register_on_exit(CPUOFF); // Clear CPUOFF bit from 0(SR) } ``` ### 透過 Timer_A,連續讀 ADC 的資料進 memory 每一秒 sample 16 次 ```c= #include "msp430g2231.h" void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop WDT // Timer_A output 1 trigger sample start ADC10CTL1 = SHS_1 + CONSEQ_2 + INCH_1; ADC10CTL0 = SREF_1 + ADC10SHT_2 + REFON + ADC10ON + ADC10IE; /* Delay 30µs */ __enable_interrupt(); // Enable interrupts. TACCR0 = 30; // Delay for Volt Ref to settle TACCTL0 |= CCIE; // Compare-mode interrupt. TACTL = TASSEL_2 + MC_1; // SMCLK, Up mode. LPM0; // LPM: Low Power Mode (Sleep) TACCTL0 &= ~CCIE; // Disable timer Interrupt __disable_interrupt(); ADC10CTL0 |= ENC; // ADC10 Enable ADC10AE0 |= 0x02; // P1.1 ADC10 option select P1DIR |= 0x01; // Set P1.0 output TACCR0 = 2048-1; // Sampling period TACCTL1 = OUTMOD_3; // TACCR1 set/reset TACCR1 = 2047; // TACCR1 OUT1 on time TACTL = TASSEL_1 + MC_1; // ACLK, up mode // Enter LPM3 w/ interrupts __bis_SR_register(LPM3_bits + GIE); } // ADC10 interrupt service routine #pragma vector=ADC10_VECTOR __interrupt void ADC10_ISR(void){ if (ADC10MEM < 0x155) // ADC10MEM = A1 > 0.5V? P1OUT &= ~0x01; // Clear P1.0 LED off else P1OUT |= 0x01; // Set P1.0 LED on } #pragma vector=TIMERA0_VECTOR __interrupt void ta0_isr(void){ TACTL = 0; LPM0_EXIT; // Exit LPM0 on return } ``` ### software emulation RS-232 傳輸 使用 Timer_A、9600 baud、full duplex 1. Receive 端 使用 CCR1 block (TACCR1)。一開始 Timer_A 的 channel 會在 capture mode,當 capture 到 falling edge (i.e. 從 idle 到 ST bit) 時,把 TAR 的值存在 TACCRx 內並產生一個 interrupt,CPU 收到此 interrupt 後在 ISR 把 capture mode 改為 compare mode,並把 TACCRx 的值加 1.5 個 cycle 的時間長度 (偷懶跳過 ST bit) ![20250126_152837](https://hackmd.io/_uploads/r1yISwmd1g.jpg) 計算數到第 1.5 個 cycle 時的 Timer 值。當下次 interrupt 發生時就表示 timer 數到這個值了 (到了第 1.5 個 cycle),CPU 的 ISR 取 SCCI register 的值即為 sample 到的值 (從 LSb 開始),之後依此類推取得 8 個 bits 2. Transmit 端 使用 CCR0 block (TACCR0)。如果要送一個 1,則把 OUTMODx 設為 0,並把 OUT 設為 1;如果要送一個 0,則把 OUTMODx 設為 2。當有收到 Timer_A 的 interrupt 表示到了下一個 bit 要傳送的時間了 ```c= #include "msp430g2553.h“ #define UART_TXD 0x02 // TXD on P1.1 (Timer0_A.OUT0) #define UART_RXD 0x04 // RXD on P1.2 (Timer0_A.CCI1A) #define UART_TBIT_DIV_2 (1000000 / (9600 * 2)) #define UART_TBIT (1000000 / 9600) unsigned int txData; // UART internal TX variable unsigned char rxBuffer; // Received UART character void TimerA_UART_init(void); void TimerA_UART_tx(unsigned char byte); void TimerA_UART_print(char *string); void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer DCOCTL = 0x00; // Set DCOCLK to 1MHz BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P1OUT = 0x00; // Initialize all GPIO P1SEL = UART_TXD + UART_RXD; // Use TXD/RXD pins P1DIR = 0xFF & ~UART_RXD; // Set pins to output __enable_interrupt(); TimerA_UART_init(); // Start Timer_A UART TimerA_UART_print("G2xx2 TimerA UART\r\n"); TimerA_UART_print("READY.\r\n"); for (;;) { // Wait for incoming character __bis_SR_register(LPM0_bits); // Echo received character TimerA_UART_tx(rxBuffer); } } void TimerA_UART_print(char *string) { while (*string) TimerA_UART_tx(*string++); } void TimerA_UART_init(void) { TACCTL0 = OUT; // Set TXD Idle as Mark = '1' TACCTL1 = SCS + CM1 + CAP + CCIE; // SCS: 設定將 interrupt 與 clock 同步 // CAP: 設定為 capture mode // CM1: 設定 capture falling edge // CCIE: enable interrupt // CCIS1 = 0 // 設定以 CCI1A 做輸入,預設值即為 0 TACTL = TASSEL_2 + MC_2; // SMCLK, continuous mode } void TimerA_UART_tx(unsigned char byte) { while (TACCTL0 & CCIE); // Ensure last char TX'd (當前面一個 char 傳完之後,CCIE 才會被設為 1,此行可讓目前要傳的 char 等待前面一個 char 傳完) TACCR0 = TAR; // Current state of TA counter TACCR0 += UART_TBIT; // One bit time till first bit TACCTL0 = OUTMOD0 + CCIE; // Set TXD on EQU0, Int txData = byte; // Load global variable txData |= 0x100; // Add mark stop bit to TXData txData <<= 1; // Add space start bit } // Transmitter ISR #pragma vector = TIMER0_A0_VECTOR __interrupt void Timer_A0_ISR(void) { static unsigned char txBitCnt = 10; // 一個 frame 共要送 10 個 bits (含 8 個 bits + ST + SP) TACCR0 += UART_TBIT; // Add Offset to TACCR0 (傳送一個 bit 會讓 timer count 的數值) if (txBitCnt == 0) { // All bits TXed? TACCTL0 &= ~CCIE; // All bits TXed, disable interrupt txBitCnt = 10; // Re-load bit counter } else { if (txData & 0x01) // Check next bit to TX (如果要送的 data 是 1) TACCTL0 &= ~OUTMOD2; // TX Mark '1’ else TACCTL0 |= OUTMOD2; // TX Space '0‘ txData >>= 1; txBitCnt--; } } // Receiver ISR #pragma vector = TIMER0_A1_VECTOR __interrupt void Timer_A1_ISR(void) { static unsigned char rxBitCnt = 8; // 一個 frame 共要收 8 個 bits static unsigned char rxData = 0; switch (__even_in_range(TA0IV, TA0IV_TAIFG)) { case TA0IV_TACCR1: // TACCR1 CCIFG - UART RX (是 TACCR1 產生的 interrupt) TACCR1 += UART_TBIT; // Add Offset to TACCR1 if (TACCTL1 & CAP) { // 這次 interrupt 是第一次 interrupt (即 idle 到 ST bit 的 falling edge) TACCTL1 &= ~CAP; // Switch to compare mode TACCR1 += UART_TBIT_DIV_2; // To middle of bit,則共加 1.5 個 bit 的時間量 } else { // Get next data bit rxData >>= 1; if (TACCTL1 & SCCI) // 收到 1 rxData |= 0x80; rxBitCnt--; if (rxBitCnt == 0) { // All bits RXed? rxBuffer = rxData; // Store in global rxBitCnt = 8; // Re-load bit counter TACCTL1 |= CAP; // Switch to capture mode __bic_SR_register_on_exit(LPM0_bits); // 設定 CPU 離開 Low Power Mode } } break; } } ```