---
# System prepended metadata

title: pic16f18854_I2C_主從切換
tags: [主從切換, I2C, PIC16F18854, teach]

---

# pic16f18854_I^2^C_主從切換
###### tags:`teach` `PIC16F18854` `主從切換` `I2C` 

## 在實作之前要先知道的暫存器功能
:star2:datasheet裡用到i2c的暫存器 : [I2C_PIC16F18854](https://hackmd.io/Au375pdfQ1KGA5u98tTthA)
PIC16F18854的datasheet : [datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/PIC16(L)F18854-Data-Sheet-40001826E.pdf)

<style> .red {color:red;} </style> 

## 範例
### 大家都要有的初始化
```c=
void init(void){
    PMD_Initialize();
    PIN_MANAGER_Initialize();
    OSCILLATOR_Initialize();
}

void OSCILLATOR_Initialize(void){
    // NOSC HFINTOSC; NDIV 1; 
    OSCCON1 = 0x60;
    // CSWHOLD may proceed; SOSCPWR Low power; 
    OSCCON3 = 0x00;
    // MFOEN disabled; LFOEN disabled; ADOEN disabled; SOSCEN disabled; EXTOEN disabled; HFOEN disabled; 
    OSCEN = 0x00;
    // HFFRQ 4_MHz; 
    OSCFRQ = 0x02;
    // HFTUN 0; 
    OSCTUNE = 0x00;
}

void PMD_Initialize(void){
    // CLKRMD CLKR enabled; SYSCMD SYSCLK enabled; SCANMD SCANNER enabled; FVRMD FVR enabled; IOCMD IOC enabled; CRCMD CRC enabled; NVMMD NVM enabled; 
    PMD0 = 0x00;
    // TMR0MD TMR0 enabled; TMR1MD TMR1 enabled; TMR4MD TMR4 enabled; TMR5MD TMR5 enabled; TMR2MD TMR2 enabled; TMR3MD TMR3 enabled; NCOMD DDS(NCO) enabled; TMR6MD TMR6 enabled; 
    PMD1 = 0x00;
    // ZCDMD ZCD enabled; DACMD DAC enabled; CMP1MD CMP1 enabled; ADCMD ADC enabled; CMP2MD CMP2 enabled; 
    PMD2 = 0x00;
    // CCP2MD CCP2 enabled; CCP1MD CCP1 enabled; CCP4MD CCP4 enabled; CCP3MD CCP3 enabled; CCP5MD CCP5 enabled; PWM6MD PWM6 enabled; PWM7MD PWM7 enabled; 
    PMD3 = 0x00;
    // CWG3MD CWG3 enabled; CWG2MD CWG2 enabled; CWG1MD CWG1 enabled; MSSP1MD MSSP1 enabled; UART1MD EUSART enabled; MSSP2MD MSSP2 enabled; 
    PMD4 = 0x00;
    // DSMMD DSM enabled; CLC3MD CLC3 enabled; CLC4MD CLC4 enabled; SMT1MD SMT1 enabled; SMT2MD SMT2 enabled; CLC1MD CLC1 enabled; CLC2MD CLC2 enabled; 
    PMD5 = 0x00;
}

void PIN_MANAGER_Initialize(void){
    /**
    LATx registers
    */
    LATA = 0x00;
    LATB = 0x00;
    LATC = 0x00;

    /**
    TRISx registers
    */
    TRISA = 0x00;
    TRISB = 0xFF;
    TRISC = 0xFF;

    /**
    ANSELx registers
    */
    ANSELC = 0xE7;//RC3 RC4設為數位
    ANSELB = 0xFF;
    ANSELA = 0x00;//PORTA 設為數位

    /**
    WPUx registers
    */
    WPUE = 0x00;
    WPUB = 0x00;
    WPUA = 0x00;
    WPUC = 0x00;

    /**
    ODx registers
    */
    ODCONA = 0x00;
    ODCONB = 0x00;
    ODCONC = 0x00;

    /**
    SLRCONx registers
    */
    SLRCONA = 0xFF;
    SLRCONB = 0xFF;
    SLRCONC = 0xFF;

    /**
    INLVLx registers
    */
    INLVLA = 0x00;
    INLVLB = 0xFF;
    INLVLC = 0xFF;
    INLVLE = 0x08;

//智能SDA和SCL兩隻腳
    SSP1CLKPPS = 0x13;   //RC3->MSSP1:SCL1;    
    RC3PPS = 0x14;   //RC3->MSSP1:SCL1;    
    RC4PPS = 0x15;   //RC4->MSSP1:SDA1;    
    SSP1DATPPS = 0x14;   //RC4->MSSP1:SDA1;    
}
```
### Master

#### master暫存器設定
```c=
void master_init(void){
    INTCONbits.GIE = 0;//中斷不智能
    INTCONbits.PEIE = 0;//中斷不智能
    PIR3bits.SSP1IF = 1;//等待傳輸
    PIR3bits.BCL1IF = 1;//未檢測到衝突
    PIE3bits.SSP1IE = 0;//不允許中斷
    PIE3bits.BCL1IE = 0;//衝突不允許中斷
    
    SSP1CON1=0x28; //智能SDA和SCL兩隻腳與設定為I2C master模式
    SSP1CON2=0x00;
    SSP1CON3=0x00;
    SSP1STAT=0x00;
    SSP1BUF =0x00;
    SSP1STATbits.SMP=1; //1 =在標準速度模式（100 kHz和1 MHz）下禁用slew rate control
    SSP1STATbits.CKE=1; //1 =智能輸入邏輯，以使閾值符合SMBus規範
    SSP1ADD=0x06; //100k Hz=0x06 數字越高頻率越慢
    SSP1MSK=0xFF; //理論上這個在master應該是沒有用的，但還是讓她都為1乖一點
    RA3=1; //可以用來看這個時候轉換成master了
}
```
#### 等等喔
```c=
void I2C_Master_Wait(){
    while ((SSP1STAT & 0x04) || (SSP1CON2 & 0x1F));//確認有發起start後再動作
}
```
#### start
```c=
void I2C_Master_Start(){
    I2C_Master_Wait();
    SSP1CON2bits.SEN = 1; //start
}
```
#### restart
```c=
void I2C_Master_RepeatedStart(){
    I2C_Master_Wait();
    SSP1CON2bits.RSEN = 1; //restart
}
```
#### stop
```c=
void I2C_Master_Stop(){
    I2C_Master_Wait();
    SSP1CON2bits.PEN = 1; //stop
}
```
#### 寫入
```c=
void I2C_Master_Write(uint8_t data){
    I2C_Master_Wait(); 
    SSP1BUF = data; //將要傳送的data放進要經由I2C傳出的buffer裡
    while(!PIR3bits.SSP1IF); // 等待bus上空閒
    PIR3bits.SSP1IF = 0; //傳輸完成之後會為1，要用軟體回歸0，代表做完事了
}
```

#### 讀取
```c=
uint8_t I2C_Master_Read(uint8_t get){//get=0代表所有讀取資料完畢後會回傳nack(通常都是這個)
                                    //get=1代表所有讀取資料完畢後會回傳nack
    uint8_t temp; // 用來儲存讀取的資料的變數
    I2C_Master_Wait(); 
    SSP1CON2bits.RCEN = 1; //智能I2C的接收模式
    I2C_Master_Wait(); 
    temp = SSP1BUF; //將buffer收到的資料放進用來存取的變數裡
    I2C_Master_Wait();
    SSP1CON2bits.ACKDT = (get)?0:1;//get=1時，ack_data=NACK(0)  //get=0時，ack_data=ACK(1)
    SSP1CON2bits.ACKEN = 1; //回傳ack_data給發送端
    return temp; //引用I2C_Master_Read(uint8_t get)這行函式可得到讀取的資料
}
```
#### master應用
```c=
#define SLAVE_ADD 0x3A //可以改成義工通的SLAVE的ID
uint8_t slave_feedback; //從SLAVE讀到的資料
void main(void) {
    init(); //所有暫存器的初始化
    master_init(void); //初始化成I2C的MASTER
    I2C_Master_Start();  
    I2C_Master_Write((SLAVE_ADD << 1)|0); //7 bit address + Write(0)
    I2C_Master_Write(0xDB); //傳0b0101 0101
    I2C_Master_Stop(); 
    
    I2C_Master_Start(); 
    I2C_Master_Write((SLAVE_ADD << 1)|1); //7 bit address + Read(1)
    slave_feedback=I2C_Master_Read(0);
    I2C_Master_Stop();//read後會過36ms才stop
    
    if(slave_feedback==0xDB){ //用來驗證傳給SLAVE再傳回來的植有沒有錯誤
        RA1=1;
    }
    while(1){
    ;
    }
}
```
### Slave
#### slave暫存器設定
```c=
void slave_init(void uint8_t address){ //設定自己的id
    INTCONbits.GIE = 1;//中斷智能
    INTCONbits.PEIE = 1;//中斷智能
    PIR3bits.SSP1IF = 0;//等待傳輸
    PIR3bits.BCL1IF = 0;//未檢測到衝突
    PIE3bits.SSP1IE = 1;//允許中斷
    PIE3bits.BCL1IE = 1;//衝突允許中斷
    
    SSP1STAT = 0xC0;//智能輸入邏輯
    SSP1ADD = (uint8_t)(address<<1); //設定自己的id
    SSP1MSK = 0xFF; //7bit的id必須全對才能溝通
    SSP1CON1 = 0x36; //智能SDA和SCL兩隻腳和CKP(用來拉住SCL變化的)與設定為I2C slave模式
    SSP1CON2 = 0x81; //如果有對ID=00h呼叫時就中斷，允許拉住SCL
    SSP1CON3 = 0x00; //看到的資料都不設這個，覺得有點怪
    SSP1CON3bits.BOEN=1;//更新SSP1BUF
    SSP1BUF = 0x00;
}
```
#### slave讀與傳
```c=
uint8_t z,y;
void __interrupt() I2C_Slave_Read(){
    if(PIR3bits.SSP1IF == 1){//傳輸已完成
        SSP1CON1bits.CKP = 0;//clock拉伸，確保資料的讀取

        if ((SSP1CON1bits.SSPOV) || (SSP1CON1bits.WCOL)){//接收溢出或發生衝突
            z = SSP1BUF; // 清空SSP1BUF
            SSP1CON1bits.SSPOV = 0; // 清除溢位標籤
            SSP1CON1bits.WCOL = 0;  // 清除衝突標籤
            SSP1CON1bits.CKP = 1;  //恢復SCL的動作
        }

    if(!SSP1STATbits.D_nA && !SSP1STATbits.R_nW){//接收master寫入的資料
        while(!SSP1STATbits.BF); //當SSP1SUF滿時
        z = SSP1BUF; //接收slave的id
        SSP1CON1bits.CKP = 1;//在一進中斷的時候設為0，拉伸clk，現在接收完slave的id後恢復scl的動作
        while(!SSP1STATbits.BF); //當SSP1SUF滿時
        y=SSP1BUF; //接收master傳給slave的data
        //如果只有RA0和RA3=1，RA1和RA2=0代表接收的都對 //用來驗證傳輸的
        if(y==0xDB) RA0=1;//data接收正確
        if(y==0x74) RA1=1;
        if(z==0xDB) RA2=1;
        if(z==0x74) RA3=1;//slave接收正確
        SSP1CON1bits.CKP = 1; //恢復SCL的動作
    }
        
    else if(!SSP1STATbits.D_nA && SSP1STATbits.R_nW){//傳送slave的資料給master讀
        z = SSP1BUF;//清空SSP1BUF
        SSP1STATbits.BF = 0;//清空SSP1BUF
        SSP1BUF = y; //傳資料給MASTER
        SSP1CON1bits.CKP = 1; //恢復SCL的動作
        while(SSP1STATbits.BF);
    }

    PIR3bits.SSP1IF = 0;//正在傳輸
    }
}
```
#### slave應用
```c=
#define SLAVE_ID 0XA3 //這邊可以改ID
void main(){
  init(); //所有暫存器的初始化
  slave_init(SLAVE_ID); //I2C SLAVE初始化
  while(1); //就在這邊等中斷發生再去做事啦
}
```
## 主從切換的應用
### 工作流程
![](https://i.imgur.com/8IDpmJ8.png)

### 主機(leader)程式
#### slave_改
```c=
void __interrupt() I2C_Slave_Read(){
    if(PIR3bits.SSP1IF == 1){//傳輸已完成
        SSP1CON1bits.CKP = 0;//clock拉伸，確保資料的讀取

        if ((SSP1CON1bits.SSPOV) || (SSP1CON1bits.WCOL)){//接收溢出或發生衝突
            z = SSP1BUF; // 清空SSP1BUF
            SSP1CON1bits.SSPOV = 0; // 清除溢位標籤
            SSP1CON1bits.WCOL = 0;  // 清除衝突標籤
            SSP1CON1bits.CKP = 1;
        }
        else if(!SSP1STATbits.D_nA && !SSP1STATbits.R_nW){//接收master寫入的資料
            while(!SSP1STATbits.BF); //當SSP1SUF滿時
            leader_id = SSP1BUF;//接收自己的id
            SSP1CON1bits.CKP = 1;//在一進中斷的時候設為0，拉伸clk，現在接收完slave的id後恢復scl的動作

            while(!SSP1STATbits.BF); //當SSP1SUF滿時
            leader_single=SSP1BUF; //接收mode傳給leader的data

            if(leader_single==0xBC){
               RA0=1;//data接收正確
               SSP1CON1bits.CKP = 1; //恢復SCL的動作
            }
        }

        else if((!SSP1STATbits.D_nA) && (SSP1STATbits.R_nW) && (leader_single==0xBC)){//傳送mode的編號
            z = SSP1BUF;//清空SSP1BUF
            SSP1STATbits.BF = 0;//清空SSP1BUF
            SSP1BUF = give_mode_ID; //傳編號(0x5A)給mode
            SSP1CON1bits.CKP = 1; //恢復SCL的動作
            while(SSP1STATbits.BF);
            RA1=1;
            can_be_master=0x01; //可以轉換成master了
        }
        PIR3bits.SSP1IF = 0;//正在傳輸      
    }
}
```
#### main
```c=
#define LEADER_ID 0x67
#define MODE_ID 0x5A

uint8_t mode_data=0x00;
uint8_t z;//用來清空SSP1BUF
uint8_t leader_id,leader_single;//用來驗證mode(master)傳的資料正不正確
uint8_t can_be_master=0x00; //發完編號給mode，可以轉換成master時=0x01

void main(void) {
    init();
    
    while(can_be_master==0x00){//slave
        slave_init(LEADER_ID);//初始化一開始為slave的leader
        
        //接收mode傳來的信號
    
        //傳編號給MODE //can_be_master==0x01
    }
    
    while(can_be_master==0x01){//master
        //轉換成master
        master_init();//RA3=1

        //跟mode(slave)要資料(0xDB)
        I2C_Master_Start(); 
        I2C_Master_Write((MODE_ID << 1)|1); //7 bit address + Read(1)
        mode_data=I2C_Master_Read(0);//接收mode傳來的資料
        I2C_Master_Stop();

        //驗證接收到的資料是否正確
        if(mode_data==0xDB) RA4=1;
        else RA5=1;
    }
    while(1);
}
```
### 模組(mode)程式
#### slave_改
```c=
#define give_to_leader 0xDB

uint8_t z,y;

void __interrupt() I2C_Slave_Read(){
    if(PIR3bits.SSP1IF == 1){//傳輸已完成
        SSP1CON1bits.CKP = 0;//clock拉伸，確保資料的讀取

        if ((SSP1CON1bits.SSPOV) || (SSP1CON1bits.WCOL)){//接收溢出或發生衝突
            z = SSP1BUF; // 清空SSP1BUF
            SSP1CON1bits.SSPOV = 0; // 清除溢位標籤
            SSP1CON1bits.WCOL = 0;  // 清除衝突標籤
            SSP1CON1bits.CKP = 1;
        }

//    if(!SSP1STATbits.D_nA && !SSP1STATbits.R_nW){//接收master寫入的資料
//        while(!SSP1STATbits.BF); //當SSP1SUF滿時
//        z = SSP1BUF;//接收slave的id
//        SSP1CON1bits.CKP = 1;//在一進中斷的時候設為0，拉伸clk，現在接收完slave的id後恢復scl的動作
//        while(!SSP1STATbits.BF); //當SSP1SUF滿時
//        y=SSP1BUF; //接收master傳給slave的data
//        if(y==0xDB) RA0=1;//data接收正確
//        if(y==0x74) RA1=1;
//        if(z==0xDB) RA2=1;
//        if(z==0x74) RA3=1;//slave接收正確
//        SSP1CON1bits.CKP = 1; //恢復SCL的動作
//    }
        
        else if(!SSP1STATbits.D_nA && SSP1STATbits.R_nW){//傳送mode(slave)的資料給leader(master)讀
        z = SSP1BUF;//清空SSP1BUF
        SSP1STATbits.BF = 0;//清空SSP1BUF
        SSP1BUF = give_to_leader; //傳資料給MASTER
        SSP1CON1bits.CKP = 1; //恢復SCL的動作
        while(SSP1STATbits.BF);
    }

    PIR3bits.SSP1IF = 0;//正在傳輸
    }
}
```
#### main
```c=
#define LEADER_ID 0x67 //要溝通的slave ID
#define single 0xBC //要傳給leader的值

uint8_t mode_id=0x01; //初始當SLAVE的自己的ID
uint8_t be_master=0x00; //0=初始 1=master 2=slave
uint8_t init_be_slave=0x00; //0=slave 1=master

int i=0;//用來設置一開始當slave的delay時間

void main(void) {
    init();

    while(1){
        if(init_be_slave==0x00){//初始 slave
            slave_init(mode_id);//將編號設為成為slave的mode的ID    RA2=1

            //如果這段時間過完，就轉成master
            for(i=0;i<=1000;i++){//27ms
                if(i==999){
                    init_be_slave=0x01;
                    be_master=0x01; //master
                    RA0=1;
                }
            }
        }

        else if(be_master == 0x02){//之後的 slave
            slave_init(mode_id);//將編號設為成為slave的mode的ID    RA2=1

            //將資料(0xDB)傳給leader
        }

        else if(be_master == 0x01){//master
            master_init();

            //發信號給leader(slave)
            I2C_Master_Start();
            I2C_Master_Write((LEADER_ID << 1)|0); //7 bit address + Write(0)
            I2C_Master_Write(single);//傳信號給leader
            I2C_Master_Stop();

            //跟leader(slave)要編號
            I2C_Master_Start(); 
            I2C_Master_Write((LEADER_ID << 1)|1); //7 bit address + Read(1)
            mode_id=I2C_Master_Read(0);//接收leader要定義的編號
            I2C_Master_Stop(); 

            //可以切換成slave了
            be_master = 0x02;//slave
        }
    }
}
```
## 要注意的:star2:
:::danger
### 對於master
做任何事前面都加個wait，確認是否可以動作了，master通常不難寫

### 對於slave
slave就難寫多了，其中ckp是最重要的，如果ckp=0，那由master所主導的scl就會不能動作，以至於無法達成想要的成效，可以在[這兒](https://hackmd.io/l_rsXslcStSo6GBomd1F1A?both#slave%E8%AE%80%E8%88%87%E5%82%B3)的14~18行看到，一進slave的中斷後ckp就會被拉住(ckp=0)，所以存在buffer理的值只有master傳出的第一個byte，也就是身為slave自己的id，而要繼續讀master之後傳送的資料就必須先鬆開ckp(ckp=1)，在接收下一筆資料，等到buffer滿(收完一個完整的1byte)再轉到其他暫存器存放
:::

## 程式連結
:::warning
### 主從切換程式連結

[模組程式](https://github.com/linda8832825/i2c_18854_mode_nomcc.git)
\
[主機程式](https://github.com/linda8832825/i2c_18854_leader_nomcc.git)

### 主從分別程式連結

[master 程式](https://github.com/linda8832825/i2c_master_nomcc.git)
\
[slave 程式](https://github.com/linda8832825/i2c_slave_nomcc.git)
:::
