# 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); //就在這邊等中斷發生再去做事啦 } ``` ## 主從切換的應用 ### 工作流程  ### 主機(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) :::
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up