英創公司推出了基于ESM7000的異構CPU架構的實時應用,其中RPMsg提供了Crotex-A7核心與M4核心之間的雙向數據通道。RPMsg(Remote Processor Messaging)是基于virtio的消息傳遞總線,它允許在非對稱多處理器(AMP)中存在的同構或異構內核上運行的獨立軟件上下文之間進行處理器間通信。目前RPMsg已經成為非對稱多處理系統中核與核之間的標準通信方式,廣泛的被應用在各種場合中。
為了更加方便客戶使用,英創公司提供了驅動emx_rpmsg_tty.ko,這個驅動會將RPMsg虛擬為一個串口設備/dev/ttyEMX,這時客戶只需要使用標準的串口程序操作/dev/ttyEMX就可以在User app中和M4進行通訊了。操作方法和標準串口完全一致,調用串口的發送函數是向M4發送數據,調用讀取函數則是讀取M4發送過來的數據。客戶還可以直接使用我們在光盤資料中提供的串口的例程step2_serialtest來測試,將OpenPort函數中打開的設備節點由/dev/ttySx改為/dev/ttyEMX就可以了,十分容易。
RPMsg適合小數據的傳輸,可以用于Crotex-A7核心和M4之間的事件傳輸。大數據的傳輸如果采用RPMsg,效率會比較低,而且會占用很多系統資源,所以英創公司提供了采用共享內存的方式來傳輸大數據的方法。
在此基礎上英創公司定義了一套較簡單靈活的通訊協議作為例子提供給客戶參考。旨在為客戶的開發拋磚引玉,縮短客戶的開發周期,下面來詳細介紹英創公司定義的這套通訊協議。通訊協議主要由兩部分構成,一部分是事件傳輸,另外一部分是大數據的傳輸。
事件傳輸主要負責傳輸操作指令和設置指令等,都是由linux系統的User app發起,M4收到后會執行相應的操作,然后回復(ACK),根據回復User app就能夠知道通信是否成功。而大數據的傳輸由M4發起,M4將特定的消息通過RPMsg傳輸給User app,User app收到后去讀取共享內存數據,可以參考下面的框圖:
首先是定義通訊使用的數據結構體,User app和M4之間的通訊都是以這個結構體為單位,具體定義如下:
struct emx_rpmsg_t { //時間戳,用于保存系統內部定時器的值 uint32_t time_stamp; //操作指令 uint32_t cmd; //標志,用于一些操作指令的附加功能 uint32_t flags; //data中數據的長度 uint32_t len; //預留,用于保存數據 uint8_t data[0]; }__attribute__ ((packed));
結構體中cmd變量用于保存需要執行的操作指令,對操作指令具體定義如下:
//操作指令掩碼 #define CMD_MARK (0x55AA << 16) //復位指令,由Crotex-A7發送到M4上,M4收到后執行相應的操作 #define CMD_CODE_RESET (CMD_MARK | 0X01) //設置指令,由Crotex-A7發送到M4上,設置內容可由預留的data變量保存,M4收到后執行相應的操作 #define CMD_CODE_SETUP (CMD_MARK | 0X02) //啟動指令,由Crotex-A7發送到M4上,M4收到后執行相應的操作 #define CMD_CODE_RUN (CMD_MARK | 0X03) //停止指令,由Crotex-A7發送到M4上,M4收到后執行相應的操作 #define CMD_CODE_STOP (CMD_MARK | 0X04) //狀態查詢指令,由Crotex-A7發送到M4上,M4收到后回復相應狀態 #define CMD_CODE_STATUS (CMD_MARK | 0X20) //數據指令,由M4發送到Crotex-A7上,表示已經在共享內存填寫了數據 #define CMD_CODE_DATA (CMD_MARK | 0X40) //指令執行成功標志 #define CMD_CODE_ACK_SUCCESS 0x8000 //指令執行失敗標志 #define CMD_CODE_ACK_FAILED 0x4000
第一步先介紹事件傳輸的情況,事件傳輸主要負責操User app和M4之間的消息傳遞,比如操作指令或者設置指令等。將需要執行的指令賦值給emx_rpmsg_t結構體的成員變量cmd,然后將結構體發送給M4就完成了一次通訊。當M4收到指令后,會執行對應的操作并回復相應的結果,這時通過User app就可以接收到M4發送過來的emx_rpmsg_t數據結構體,回復的數據會在收到的成員變量cmd的基礎上,或上成功或者失敗標志。所以User app通過判斷回發數據結構體成員變量cmd的值,就可以知道指令執行的結果。英創公司提供了測試好的函數供客戶使用,下面分別是User app中和M4 app中的代碼:
User app中的代碼:
/* * Send_cmd函數實現向M4發送操作指令 * * 參數說明: * cmd:定義的操作指令 * param:參數,有一些指令可能需要參數的輔助,參數存放在data成員變量中 * len:參數字節長度,保存在len成員變量中 * */ int Rpmsg::Send_cmd( uint32_t cmd, void *param, int len ) { struct emx_rpmsg_t *msg_tx; struct emx_rpmsg_t *msg_rx; char Buf[4095]; int data_len, sendlen, recvlen; data_len = sizeof(struct emx_rpmsg_t) + len; msg_tx = (struct emx_rpmsg_t *)malloc(data_len); //給msg_tx賦值 msg_tx->cmd = cmd; msg_tx->len = len; if(param != NULL) memcpy(msg_tx->data, param, len); //發送指令 sendlen = write(m_fd, msg_tx, data_len); if(sendlen != data_len) { printf("failed to send cmd!\n"); return -1; } //發送完成后等待返回數據 while(1) { recvlen = read( m_fd, Buf, 4096 ); msg_rx = (struct emx_rpmsg_t *)malloc(recvlen); memcpy(msg_rx, Buf, recvlen); if(msg_rx->cmd == CMD_CODE_DATA) { free(msg_rx); continue; } else { break; } } //判斷是否成功執行操作 if((msg_tx->cmd | CMD_CODE_ACK_SUCCESS) == msg_rx->cmd) { printf("Send successfully\n"); free(msg_tx); free(msg_rx); return 0; } else { printf("Send failed\n"); free(msg_tx); free(msg_rx); return -1; } }
M4 app中的代碼:
static void RpmsgRevTask(void *pvParameters) { uint32_t samplingRate, cmd; int result, len; rpmsg_packed_t *rpmsg = (rpmsg_packed_t *)msg_buf; RPMSG_Init(); for (;;) { /* Get RPMsg rx buffer with message */ result = RPMSG_Recv(msg_buf, &len, 0xFFFFFFFF); assert(result == 0); cmd = rpmsg->cmd; CMD_ACK_SUCCESS(rpmsg->cmd); switch (cmd) { case CMD_CODE_RESET: sprintf((char *)rpmsg->data, "EMX2001 v%d.%c%c%c%c%c%c.%d[Ads8588s 16bit 8-Channel ADC], Maximum sampling rate:100KHz", VERSION_MAJOR, COMLETE_TIME); rpmsg->len = strlen((char *)rpmsg->data); len = sizeof(rpmsg_packed_t) + rpmsg->len; break; case CMD_CODE_SETUP: if (rpmsg->len == 4) { samplingRate = *((uint32_t *)rpmsg->data); PRINTF("ADC Sampling rate = %d.\r\n", samplingRate); if (samplingRate > 100000) { rpmsg->flags = 1; } else { adc_stop(); pwmConfig.period_ns = 1000000000 / samplingRate; } } else { CMD_ACK_FAILED(rpmsg->cmd); } break; case CMD_CODE_START: adc_start(); break; case CMD_CODE_STOP: adc_stop(); break; case CMD_CODE_STATUS: break; default: CMD_ACK_FAILED(rpmsg->cmd); } result = RPMSG_Send(rpmsg, len); assert(result == 0); } }
可以看到這個函數會將填寫的操作指令和參數發送給M4,并且會等待M4的回發響應數據,然后判斷是否成功執行。下面給出兩個簡單的實例供客戶參考,第一個是復位指令:
ret = Send_cmd(CMD_CODE_RESET, NULL, 0); if( ret<0 ) { printf( "send failed\n"); }
第二個是設置指令,示例代碼是設置ADC采樣頻率為10K:
int freq = 10000; ret = Send_cmd(CMD_CODE_SETUP, (void *)&freq, sizeof(freq)); if( ret<0 ) { printf( "send failed\n"); }
接下來介紹大數據的傳輸方式,大數據的傳輸是由M4主動發起。當User app接收到的成員變量cmd的值為CMD_CODE_DATA時,就表示M4已經將數據填入了共享內存中了,這時就需要User app去讀取。因為數據是采用共享內存的方式,User app中提前映射好共享內存,當需要讀取數據時,直接調用memcpy函數即可。
內存映射的代碼如下:
int mem_fd; void *base; mem_fd = open("/dev/mem", O_RDWR|O_SYNC); printf("mem_fd is %d\n", mem_fd); /* mmap mem */ base = mmap( NULL, //Any adddress in our space will do MEM_DEV_SIZE, //Map length PROT_READ|PROT_WRITE,// Enable reading & writting to mapped memory MAP_SHARED, //Shared with other processes mem_fd, //File to map MEM_BASE //Offset to DMTimer peripheral ); close(em_adc.mem_fd);
下面是分別是M4 app寫共享內存和Linux系統User app讀取共享內存數據的代碼,示例中有兩塊映射區域,通過emx_rpmsg_t結構體成員變量flags來區分,User app的這部分代碼在接收線程中運行,一旦收到數據就會進行讀取:
User app部分:
int Rpmsg::PackagePro( char* Buf, int len ) { int i1, num; int buf_len; struct emx_rpmsg_t msg; memcpy(&msg, (char *)Buf, sizeof(struct emx_rpmsg_t)); if(msg.cmd == CMD_CODE_DATA) { Mlen = msg.len; //讀取內存數據 if(msg.flags == 0) { memcpy(MemBuf, (void *)base0, msg.len); } else { memcpy(MemBuf, (void *)base1, msg.len); } //處理數據,這里只是簡單的保存 save_data(MemBuf, Mlen); } return 0; }
M4 app部分:
/*! * SPI中斷服務程序,為了提高SPI操作效率,當SPI FIFO中接收超過32個DWORD數時產生中斷 * 在中斷服務程序中將SPI數據讀出 */ void ESM_SPI1_HANDLER(void) { uint32_t tmp; BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* Disable all SPI interrupt */ ECSPI_INTREG_REG(ESM_SPI1) = 0; while (ECSPI_STATREG_REG(ESM_SPI1) & ECSPI_STATREG_RR_MASK) { tmp = ECSPI_RXDATA_REG(ESM_SPI1); /* 將AD數據存放在M4與A7(Linux)的共享內存中 */ *((uint16_t *)mem_addr + shared_buf_idx++) = (uint16_t)(tmp >> 16); *((uint16_t *)mem_addr + shared_buf_idx++) = (uint16_t)tmp; if (shared_buf_idx >= sampling_len) { /* 當采樣到指定長度的AD數據后,對乒乓buffer進行切換,并將xDataReadySemaphore信號量設置為有效 */ shared_buf_idx = 0; if (mem_addr == SHARED_MEMORY_BASE + SHARED_MEMORY_BLOCK0) { data_packed.flags = 0; mem_addr = SHARED_MEMORY_BASE + SHARED_MEMORY_BLOCK1; } else { data_packed.flags = 1; mem_addr = SHARED_MEMORY_BASE + SHARED_MEMORY_BLOCK0; } /* Unlock the task to process the event. */ xSemaphoreGiveFromISR(xDataReadySemaphore, &xHigherPriorityTaskWoken); /* Perform a context switch to wake the higher priority task. */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } ECSPI_ClearStatusFlag(ESM_SPI1, ecspiFlagRxfifoDataRequest); ECSPI_SetIntCmd(ESM_SPI1, ecspiFlagRxfifoDataRequest, true); }
在英創公司給出的協議基礎上,客戶可以進行擴展并實現需要的應用。英創公司在后續也會給出更多實例的例子供客戶參考。如果對于這一套異構CPU平臺十分熟悉,也可以根據需求自行設計方案。
如果需要詳細的例程代碼,可與英創的工程師聯系。
成都英創信息技術有限公司 028-8618 0660