---
# System prepended metadata

title: MSP-EXP430G2 LaunchPad 筆記
tags: [嵌入式系統]

---

# 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;
    }
}

```