# 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)

* 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

* 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

* Brownout Protection: 電壓過低 / 過高時自動停止
### Features
* 可執行 27 種 machine code
* 7 種 addressing mode
* 16 個 16-bit register

* status register: 例如 ALU 的運算結果 flag
* constant register: 可放 constant 值
* Direct memory-to-memory transfers (例如: 可直接在 RAM <-> Flash 做傳遞)
* little-endian,2 個 bytes 為一個 word
> 
* Memory-mapped I/O
### Memory Organization

* Information Memory: 放 nonvolatile data (例如: 板子的 serial number)
* Peripherals (周邊設備): 給周邊設備做 memory-mapped I/O 使用
* SFR: Special Function Registers
### Low Power Modes



* 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


* 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

紅框處為 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




* Timer_A Registers 都是放在 Timer 內部
* TAIV: 發生 interrupt 的時候,值表示最高 priority 的 interrupt (由硬體來做,故可減短用軟體來做時需要 polling 最高 priority 的 interrupt 的時間)

:::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

* LFXT1、XT2、DCO、VLO 為 clock source (振盪器發出,還不是完整方波),ACLK、MCLK、SMCLK 為 clock signal
### Interrupt

* 有三種 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)


* 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)

計算數到第 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;
}
}
```