摘要:MAXQ3180微控制器是電表多相模擬前端。它具備現代多功能電表的所有功能。MAXQ3180通過串行外設互聯(lián)(SPI?)總線將其讀數傳送給主機微控制器。本應用筆記介紹怎樣實現這一接口,演示實例代碼以幫助設計人員實現這一通信機制。
圖1. SPI從機示意圖
SPI通信采用了四種不同的電路:
有些SPI外設犧牲速率以模擬半雙工工作。MAXQ3180微控制器沒有采用這一方式,它是真正的全雙工SPI從機。
本應用筆記的其他部分介紹怎樣連接并成功使用SPI總線上的MAXQ3180。
有些MAXQ3180 “存儲”位置觸發(fā)器件內部操作,“隨時”計算電表測量結果。向這些位置寫入的是“nop”。對RAM和虛擬ROM位置特殊功能和目的的討論已經超出了本文檔的范圍。此處最重要的是微控制器的確只有兩種SPI通信操作:讀和寫。
MAXQ3180中的每一次操作以主機發(fā)送兩個字節(jié)開始,它含有命令(例如,讀或者寫)、要訪問的地址、訪問的字節(jié)數。如前所述,每個SPI外設對接收到的每個字節(jié)返回一個字節(jié)。因此,MAXQ3180在接收到第一個命令字節(jié)后返回0xC1,第二個命令字節(jié)后返回0xC2。該協(xié)議顯示在下面的圖2中。
圖2. 主機向MAXQ3180讀寫數據
如果主機讀取一個或者多個字節(jié),它必須發(fā)送空字節(jié)。記住,主機不能接收來自從機的任何信息,除非它發(fā)送某些信息:發(fā)送一個字節(jié)以得到一個字節(jié)。但是接收一條命令后,MAXQ3180要計算結果,當主機發(fā)送空字節(jié)時,它可能還沒有準備好。出于這一原因,MAXQ3180 總是在發(fā)送數據之前發(fā)送零或者NAK字符等多個字節(jié)(0x4E或者ASCII 'N'),隨后是一個ACK字符(0x41,或者ASCII 'A')。
如果主機寫入一個或者多個字節(jié),發(fā)送命令后,它立即發(fā)送要寫入的數據。MAXQ3180為每一個數據字節(jié)返回ACK (0x41)。然后,它返回NAK (0x4E),直到寫周期完成,隨后返回最終ACK。
注意,最終ACK之后,MAXQ3180立即準備開始下一操作;它不需要進行任何其他等待。它甚至不需要觸發(fā)SSEL以開始下一操作。MAXQ3180知道第一次操作已經完成,準備進行下一操作。
不論什么原因,如果需要復位主機和MAXQ3180之間的通信(例如,如果通信是異步的),從第一個命令字節(jié)重新啟動通信之前,主機只需要等待200ms。200ms延時指示MAXQ3180,主機放棄了前面的操作。
圖3. 命令字節(jié)結構
第一個命令字節(jié)(圖3)告訴MAXQ3180,所申請的操作是READ還是WRITE,以及操作的長度。命令字節(jié)結構如下:
命令字節(jié)1的其他部分和所有的命令字節(jié)2提供要訪問的RAM字節(jié)的地址(或者一樣的虛擬ROM功能)。
圖4. 讀取MAXQ3180的流程圖
圖5. 寫入MAXQ3180的流程圖
在下面的清單中,dly_us子程序使程序線程停止執(zhí)行幾個微秒。定義SPI_TIMEOUT常數以提供比字符超時時間更長的參數。
在高層子程序中,采用ENUM按名稱來選擇寄存器。在其他參數中,它提供register_lookup_table陣列的索引,含有每個MAXQ3180寄存器的寄存器長度。請參考圖6、圖7和圖8。
圖6. Send_SPI原代碼
圖7. ReadAFE (SPI_Read)子程序代碼
圖8. Write_AFE (SPI_Write)子程序代碼
SPI簡介
串行外設接口(SPI)是器件間總線協(xié)議,實現芯片間的快速、同步、全雙工通信。由一個主機驅動同步時鐘,選擇對哪些從機尋址。每個SPI外設含有一個移位寄存器和控制電路,使被尋址的串行外設互聯(lián)SPI外設能夠同時發(fā)送和接收數據。圖1. SPI從機示意圖
SPI通信采用了四種不同的電路:
- SCLK:所有器件使用的同步時鐘。主機驅動該時鐘,從機接收時鐘。注意,SCLK可以被選通,不需要在SPI操作之間進行驅動。
- MOSI:主機出,從機入。這是主機在SPI總線上驅動所有從機的主要數據線。只有所選的從機同步來自MOSI的數據。
- MISO:主機入,從機出。這是所選從機向主機發(fā)送時驅動的主要數據線。只有所選的從機可以驅動該電路。實際上,這是SPI總線安排中允許從機驅動的唯一電路。
- SSEL:該信號在每一從機上都不同。當有效(通常為低電平)時,所選從機必須驅動MISO。
有些SPI外設犧牲速率以模擬半雙工工作。MAXQ3180微控制器沒有采用這一方式,它是真正的全雙工SPI從機。
本應用筆記的其他部分介紹怎樣連接并成功使用SPI總線上的MAXQ3180。
MAXQ3180通信簡介
對于主機,MAXQ3180看起來象一個存儲器陣列,同時含有RAM和ROM。這是因為MAXQ3180中的ROM固件讀取RAM的工作參數,將結果放到RAM中。因此,配置MAXQ3180和對RAM進行塊寫入一樣簡單。有些MAXQ3180 “存儲”位置觸發(fā)器件內部操作,“隨時”計算電表測量結果。向這些位置寫入的是“nop”。對RAM和虛擬ROM位置特殊功能和目的的討論已經超出了本文檔的范圍。此處最重要的是微控制器的確只有兩種SPI通信操作:讀和寫。
MAXQ3180中的每一次操作以主機發(fā)送兩個字節(jié)開始,它含有命令(例如,讀或者寫)、要訪問的地址、訪問的字節(jié)數。如前所述,每個SPI外設對接收到的每個字節(jié)返回一個字節(jié)。因此,MAXQ3180在接收到第一個命令字節(jié)后返回0xC1,第二個命令字節(jié)后返回0xC2。該協(xié)議顯示在下面的圖2中。
圖2. 主機向MAXQ3180讀寫數據
如果主機讀取一個或者多個字節(jié),它必須發(fā)送空字節(jié)。記住,主機不能接收來自從機的任何信息,除非它發(fā)送某些信息:發(fā)送一個字節(jié)以得到一個字節(jié)。但是接收一條命令后,MAXQ3180要計算結果,當主機發(fā)送空字節(jié)時,它可能還沒有準備好。出于這一原因,MAXQ3180 總是在發(fā)送數據之前發(fā)送零或者NAK字符等多個字節(jié)(0x4E或者ASCII 'N'),隨后是一個ACK字符(0x41,或者ASCII 'A')。
如果主機寫入一個或者多個字節(jié),發(fā)送命令后,它立即發(fā)送要寫入的數據。MAXQ3180為每一個數據字節(jié)返回ACK (0x41)。然后,它返回NAK (0x4E),直到寫周期完成,隨后返回最終ACK。
注意,最終ACK之后,MAXQ3180立即準備開始下一操作;它不需要進行任何其他等待。它甚至不需要觸發(fā)SSEL以開始下一操作。MAXQ3180知道第一次操作已經完成,準備進行下一操作。
不論什么原因,如果需要復位主機和MAXQ3180之間的通信(例如,如果通信是異步的),從第一個命令字節(jié)重新啟動通信之前,主機只需要等待200ms。200ms延時指示MAXQ3180,主機放棄了前面的操作。
命令字節(jié)
命令字節(jié)告訴MAXQ3180:圖3. 命令字節(jié)結構
第一個命令字節(jié)(圖3)告訴MAXQ3180,所申請的操作是READ還是WRITE,以及操作的長度。命令字節(jié)結構如下:
Length Code | Data Length |
0b00 | 1 byte |
0b01 | 2 bytes |
0b10 | 4 bytes |
0b11 | 8 bytes |
命令字節(jié)1的其他部分和所有的命令字節(jié)2提供要訪問的RAM字節(jié)的地址(或者一樣的虛擬ROM功能)。
主機軟件設計
雖然MAXQ3180含有一個硬件SPI控制器,ROM固件中的軟件程序還是要處理每一消息字節(jié)。出于這一原因,連續(xù)字節(jié)之間需要有延時。在當前的MAXQ3180型號中,這一延時不得小于100μs才能實現可靠的工作。請參考圖4和圖5。圖4. 讀取MAXQ3180的流程圖
圖5. 寫入MAXQ3180的流程圖
代碼清單
提供代碼以實現具有內置SPI主機的MAXQ2000微控制器和MAXQ3180的接口。其他微控制器用戶需要提供自己的SPI原語,還可能要修改高層子程序。在下面的清單中,dly_us子程序使程序線程停止執(zhí)行幾個微秒。定義SPI_TIMEOUT常數以提供比字符超時時間更長的參數。
在高層子程序中,采用ENUM按名稱來選擇寄存器。在其他參數中,它提供register_lookup_table陣列的索引,含有每個MAXQ3180寄存器的寄存器長度。請參考圖6、圖7和圖8。
unsigned char Send_SPI(unsigned char x) { unsigned char y = 0; int z; int error = 0; SPICN = 3; /* MSTSM, SPIEN */ z = 0; while ((SPICN_bit.STBY) && (++z < SPI_TIMEOUT)); if (z == SPI_TIMEOUT) error = 1; SPICN_bit.SPIC = 0; /* Clear transfer complete flag */ SPIB = x; z = 0; while ((!SPICN_bit.SPIC) && (++z < SPI_TIMEOUT)); if (z == SPI_TIMEOUT) error = 1; y = SPIB; SPICN_bit.SPIC = 0; dly_us(100); if (error) return 0; return y; } |
long Read_AFE(enum METER_REGISTER_RECORD reg, uint16 reg_addr) { extern unsigned char record[8]; unsigned long x = 0; unsigned char i, regadd, command_code = 0; for(i=0; i<8; i++) record[i] = 0; switch(register_lookup_table[reg].register_length) { case 2: command_code |= 0x10; break; case 4: command_code |= 0x20; break; case 8: command_code |= 0x30; break; } command_code |= reg_addr >> 8; regadd = reg_addr & 0xff; /* Disable SPI to reset it */ SPICN_bit.SPIEN = 0; for(x=0; x<300; x++); SPICN_bit.SPIEN = 1; SPI_SELECT_0; i = 0; while((Send_SPI(command_code)!= 0xC1)&&(++i < SPI_COMMAND_RETRIES)) spi_comm_timeout(); x = 0xffffffff; if (i == SPI_COMMAND_RETRIES) goto spierror; Send_SPI(regadd); i = 0; while((Send_SPI(0) != 'A') && (++i < SPI_RETRIES)); if (i == SPI_RETRIES) goto spierror; x = 0; for(i=0; i |
void Write_AFE(enum METER_REGISTER_RECORD reg, uint16 reg_addr, uint32 data) { uint8 i, regadd, command_code = 0x80; int x; switch(register_lookup_table[reg].register_length) { case 2: command_code |= 0x10; break; case 4: command_code |= 0x20; break; case 8: command_code |= 0x30; break; } command_code |= reg_addr >> 8; regadd = reg_addr & 0xff; /* Disable SPI hardware to reset it */ SPICN_bit.SPIEN = 0; for(x=0; x<300; x++); SPICN_bit.SPIEN = 1; SPI_SELECT_0; i = 0; while((Send_SPI(command_code)!=0xC1)&&(++i < SPI_COMMAND_RETRIES)) spi_comm_timeout(); if (i == SPI_COMMAND_RETRIES) goto spierror; Send_SPI(regadd); for(i=0; i |
評論
查看更多