《極海芯得》系列內容為用戶使用極海系列產品的經驗總結,均轉載自21ic論壇極海半導體專區,全文未作任何修改,未經原文作者授權禁止轉載。
你有沒有遇到過這樣的尷尬場景:當你想讓CPU0和CPU1“好好相處”,卻發現它倆居然會搶外設用?一邊還在想“我的GPIO在哪?為什么跑到另一個核上了?”一邊翻著用戶手冊,看著漫天寄存器名稱腦殼疼。
如果你也有這種體驗,那么恭喜你,說明你已經開始入坑G32R501這款雙核MCU啦!今天咱們就來聊一聊,G32R501到底是如何讓兩核共享、分配外設的,順便也整點代碼案例,讓你的雙核之路不再迷茫。
1、雙核背景:G32R501長啥樣?
在這顆稱為G32R501Dx的MCU里,最顯著的特征當然是雙核配置。該芯片的硬件方框圖里,CPU0和CPU1像一對“歡喜冤家”,帶著各自的Cache、RAM,整整齊齊地駐扎在芯片內部。

那么問題來了:既然是兩核,那么外設該給誰用?假設把GPIO都分給CPU1了,那CPU0干瞪眼怎么辦?這就需要寄存器來進行訪問控制啦。
2、外設訪問控制:PERIPH_AC
如果你去翻《G32R501用戶手冊V1.5.pdf》,在12.10章節就能看到一個叫“PERIPH_AC”的訪問控制寄存器。它的作用是給各大外設(例如ADCA、ADCB等)指定訪問權限。可以簡單理解為:“CPU0能不能讀寫這個外設?”、“CPU1有沒有權限?”、“DMA能不能參與進來?”等。
2.1 寄存器結構
以ADCB為例,手冊里對它的訪問控制寄存器(ADCB_AC)是這樣描述的:
? CPU0_ACC、CPU1_ACC、DMA1_ACC這幾位,分別控制了 CPU0、CPU1和DMA1的讀寫權限。
? 如果組合值是00,就完全禁止讀寫;
? 10就是“只讀+清除類訪問,但禁寫”;
? 11就是讀寫全開,好比“VIP通道”。
手冊上寫的比我說的要嚴謹許多,歡迎自行參閱。大概的寄存器表長這樣:

2.2 庫函數調用
如果嫌翻寄存器表累,SDK已經貼心地給你封裝好了函數。在driverlib庫里,你能看到類似如下的函數原型
(SysCtl_setPeripheralAccessControl):
static inline void
SysCtl_setPeripheralAccessControl(SysCtl_AccessPeripheral peripheral,
SysCtl_AccessMaster master,
SysCtl_AccessPermission permission)
{
//
// Set master permissions for specified peripheral. Each master has
// two bits dedicated to its permission setting.
//
WRPRT_DISABLE;
HWREG(PERIPHAC_BASE + (uint16_t)peripheral * 2) =
(HWREG(PERIPHAC_BASE + (uint16_t)peripheral * 2) &
~(0x3U << (uint16_t)master)) |
((uint16_t)permission << (uint16_t)master);
WRPRT_ENABLE;
}
如果想快速禁止CPU1對ADCB的讀寫,只要一個函數調用搞定。這樣的封裝對于新手異常友好,再也不怕記不住寄存器偏移了!
3、GPIO訪問控制:主核歸誰?
GPIO對MCU來說簡直是門面擔當!想拿一個MCU“亮燈”(HelloWorld)的第一步,一般就是點GPIO嘛。這時,如果你拿到G32R501,會發現它可以對GPIO做“主核”綁定——告訴芯片,這個GPIO到底是歸CPU0還是CPU1來管理。
3.1 GPxCSELx寄存器
在用戶手冊21.9.16和21.9.36等章節里,你能找到GPxCSELx之類的寄存器說明,比如“GPACSEL1”,它里面每兩位就控制一個GPIO引腳的主核。00代表CPU0,01代表CPU1。

3.2驅動庫函數:GPIO_setMasterCore
驅動庫同樣貼心地幫你把“燒腦寄存器”給封裝起來了:
void
GPIO_setMasterCore(uint32_t pin, GPIO_CoreSelect core)
{
volatile uint32_t *gpioBaseAddr;
uint32_t cSelIndex;
uint32_t shiftAmt;
//
// Check the arguments.
//
ASSERT(GPIO_isPinValid(pin));
gpioBaseAddr = (uint32_t *)GPIOCTRL_BASE +
((pin / 32U) * GPIO_CTRL_REGS_STEP);
shiftAmt = (uint32_t)GPIO_GPACSEL1_GPIO1_S * (pin % 8U);
cSelIndex = GPIO_GPxCSEL_INDEX + ((pin % 32U) / 8U);
//
// Write the core parameter into the register.
//
WRPRT_DISABLE;
gpioBaseAddr[cSelIndex] &= ~((uint32_t)GPIO_GPACSEL1_GPIO0_M << shiftAmt);
gpioBaseAddr[cSelIndex] |= (uint32_t)core << shiftAmt;
WRPRT_ENABLE;
}
一句話:傳入你想設置的pin號碼,以及指定的CPU是CPU0還是CPU1,函數就會幫你把寄存器給改好,能省不少功夫。
4、外部中斷分配:EXTI_xMASKx
被搶得最慘的除了GPIO,另一個八成就是中斷了。你肯定不想讓A核測溫度時觸發的外部中斷,把B核的運動控制流程打斷了吧?因此,需要由寄存器來控制“這個中斷我想讓誰來響應”,把中斷優雅地“送”到某個核心。
4.1 EXTI_IMASKx、EXTI_EMASKx
在14.6.5章節,官方給我們準備了 EXTI_IMASK0、EXTI_IMASK1以及 EMASK0、EMASK1等寄存器。它們就是中斷、事件屏蔽寄存器,用來控制相應的外部中斷或外部事件是否允許CPU0或CPU1進行響應。屏蔽位為0時就啥也收不到,置1就能開啟。

對應的driverlib函數舉個栗子:
static inline void
EXTI_enableInterrupt(EXTI_CoreSelect core, EXTI_LineNumber lineNumber)
{
WRPRT_DISABLE;
if(core == EXTI_CORE_CPU0)
{
HWREG(EXTI_BASE + EXTI_O_IMASK0) |= (1U << (uint32_t)lineNumber);
}
else
{
HWREG(EXTI_BASE + EXTI_O_IMASK1) |= (1U << (uint32_t)lineNumber);
}
WRPRT_ENABLE;
}
static inline void
EXTI_disableInterrupt(EXTI_CoreSelect core, EXTI_LineNumber lineNumber)
{
WRPRT_DISABLE;
if(core == EXTI_CORE_CPU0)
{
HWREG(EXTI_BASE + EXTI_O_IMASK0) &= ~(1U << (uint32_t)lineNumber);
}
else
{
HWREG(EXTI_BASE + EXTI_O_IMASK1) &= ~(1U << (uint32_t)lineNumber);
}
WRPRT_ENABLE;
}
static inline void
EXTI_enableEvent(EXTI_CoreSelect core, EXTI_LineNumber lineNumber)
{
WRPRT_DISABLE;
if(core == EXTI_CORE_CPU0)
{
HWREG(EXTI_BASE + EXTI_O_EMASK0) |= (1U << (uint32_t)lineNumber);
}
else
{
HWREG(EXTI_BASE + EXTI_O_EMASK1) |= (1U << (uint32_t)lineNumber);
}
WRPRT_ENABLE;
}
static inline void
EXTI_disableEvent(EXTI_CoreSelect core, EXTI_LineNumber lineNumber)
{
WRPRT_DISABLE;
if(core == EXTI_CORE_CPU0)
{
HWREG(EXTI_BASE + EXTI_O_EMASK0) &= ~(1U << (uint32_t)lineNumber);
}
else
{
HWREG(EXTI_BASE + EXTI_O_EMASK1) &= ~(1U << (uint32_t)lineNumber);
}
WRPRT_ENABLE;
}
只要指定core(CPU0orCPU1)和外部中斷線,就可以輕松讓中斷“跑”到想去的核。
5、CPU1響應外部中斷示例
既然說到這里,咱們就結合SDK例程來場“真刀真槍”。SDK里有兩個經典示例:
1.CPU1基礎例程(ipc_ex2_mailbox_pollingcpu1)
2.外部中斷例程(interrupt_ex1_external)
假設我們想在CPU1那邊也玩外部中斷,咔咔地檢測某個輸入腳“電平墜落”。那就需要做哪些修改呢?
5.1 修改代碼
ipc_ex2_mailbox_pollingcpu1原始代碼里只是一個單純的IPC消息收發demo,沒有外部中斷,我們新增了CPU1對于XINT1(等效EXTI_LINE_4)的中斷響應。要點如下:
1.注冊并開啟INT_XINT1中斷向量
2.在初始化階段,禁用CPU0對XINT1的中斷,啟用CPU1對XINT1的中斷
3.將GPIO0配置為觸發XINT1
4.在ISR中,通過EXTI_getInterruptStatus等方法判斷并清除中斷,順便做點LED狀態改變、計數打印等事情。
5.2 實際代碼
復現時可以把下面的代碼全部復制至ipc_ex2_mailbox_pollingcpu1sourceipc_ex2_mailbox_polling_cpu1.c
//#############################################################################
//
// FILE: ipc_ex2_mailbox_polling_cpu1.c
//
// TITLE: IPC Mailbox with Polling Example
//
// VERSION: 1.0.0
//
// DATE: 2025-01-15
//
//! addtogroup driver_example_list
//!
IPC Mailbox with Polling
//!
//! This example demonstrates how to use the mailbox mechanism of the Inter-Processor
//! Communication (IPC) module, to send and receive data between two CPUs in polling mode.
//!
//! In this example:
//! 1. CPU0 sends a message to CPU1 via the IPC module in polling mode.
//! 2. CPU1 sends a message back to CPU0 in polling mode.
//! 3. CPU0 receives the message sent from CPU1 in polling mode, and so on.
//!
//! ote Note: The IPC example includes both CPU0 and CPU1 projects.
//! Please compile the CPU1 project before compiling the CPU0 project.
//!
//! Running the Application
//! Open a COM port with the following settings using a terminal:
//! - Find correct COM port
//! - Bits per second = 115200
//! - Data Bits = 8
//! - Parity = None
//! - Stop Bits = 1
//! - Hardware Control = None
//!
//! External Connections
//! Connect the UART-A port to a PC via a transceiver and cable.
//! - GPIO28 is UART_RXD (Connect to Pin3, PC-TX, of serial DB9 cable)
//! - GPIO29 is UART_TXD (Connect to Pin2, PC-RX, of serial DB9 cable)
//!
//! Watch Variables
//! - None.
//!
//
//#############################################################################
//
//
// $Copyright:
// Copyright (C) 2024 Geehy Semiconductor - http://www.geehy.com/
//
// You may not use this file except in compliance with the
// GEEHY COPYRIGHT NOTICE (GEEHY SOFTWARE PACKAGE LICENSE).
//
// The program is only for reference, which is distributed in the hope
// that it will be useful and instructional for customers to develop
// their software. Unless required by applicable law or agreed to in
// writing, the program is distributed on an "AS IS" BASIS, WITHOUT
// ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied.
// See the GEEHY SOFTWARE PACKAGE LICENSE for the governing permissions
// and limitations under the License.
// $
//#############################################################################
//
// Included Files
//
#include"driverlib.h"
#include"device.h"
#include"board.h"
#include
//
// How many message is used to test mailbox sending and receiving
//
#defineMESSAGE_LENGTH 4U
//
// Variables
//
static uint32_t g_msgRecv[MESSAGE_LENGTH];
volatile uint32_t xint1Count;
//
// Function Prototypes
//
void clearMsgRecv(void);
void UART_Init(void);
void xint1ISR(void);
//
// Main
//
void example_main(void)
{
//
// Initialize device clock and peripherals
//
Device_init();
//
// Initialize NVIC and clear NVIC registers. Disables CPU interrupts.
//
Interrupt_initModule();
//
// Initialize the NVIC vector table with pointers to the shell Interrupt
// Service Routines (ISR).
//
Interrupt_initVectorTable();
//
// Interrupts that are used in this example are re-mapped to
// ISR functions found within this file.
//
Interrupt_register(INT_XINT1, &xint1ISR);
//
// Board Initialization
//
Board_init();
//
// Enables CPU interrupts
//
Interrupt_enableMaster();
//
// UART initialize
//
UART_Init();
xint1Count = 0; // Count XINT1 interrupts
//
// CPU1 Print information
//
printf("CPU1 has completed booting, Interrupt. ");
//
// Clear the g_msgRecv array before receive
//
clearMsgRecv();
//
// CPU1 receive message from CPU0
//
while (IPC_isRxBufferFull(IPC_RX_0) != true);
g_msgRecv[0] = IPC_receive32Bits(IPC_RX_0);
while (IPC_isRxBufferFull(IPC_RX_1) != true);
g_msgRecv[1] = IPC_receive32Bits(IPC_RX_1);
while (IPC_isRxBufferFull(IPC_RX_2) != true);
g_msgRecv[2] = IPC_receive32Bits(IPC_RX_2);
while (IPC_isRxBufferFull(IPC_RX_3) != true);
g_msgRecv[3] = IPC_receive32Bits(IPC_RX_3);
//
// CPU1 send message back to CPU0
//
while (IPC_isTxBufferEmpty(IPC_TX_0) != true);
IPC_transmit32Bits(IPC_TX_0, g_msgRecv[0]);
while (IPC_isTxBufferEmpty(IPC_TX_1) != true);
IPC_transmit32Bits(IPC_TX_1, g_msgRecv[1]);
while (IPC_isTxBufferEmpty(IPC_TX_2) != true);
IPC_transmit32Bits(IPC_TX_2, g_msgRecv[2]);
while (IPC_isTxBufferEmpty(IPC_TX_3) != true);
IPC_transmit32Bits(IPC_TX_3, g_msgRecv[3]);
//
// Set the priority group to indicate the PREEMPT and SUB priortiy bits.
//
Interrupt_setPriorityGroup(INTERRUPT_PRIGROUP_PREEMPT_7_6_SUB_5_0);
//
// Set the global and group priority to allow CPU interrupts
// with higher priority
//
Interrupt_setPriority(INT_XINT1,1,0);
//
// Enable XINT1 and XINT2 in the NVIC.
// Enable INT1 which is connected to WAKEINT:
//
Interrupt_enable(INT_XINT1);
//
// Enable Global Interrupt and real time interrupt
//
EINT;
ERTM;
//
// GPIO0 is mapped to XINT1
//
GPIO_setMasterCore(0, GPIO_CORE_CPU1);
GPIO_setInterruptPin(0, GPIO_INT_XINT1);
EXTI_disableInterrupt(EXTI_CORE_CPU0, EXTI_LINE_4);
EXTI_enableInterrupt(EXTI_CORE_CPU1, EXTI_LINE_4);
//
// Configure falling edge trigger for XINT1
//
GPIO_setInterruptType(GPIO_INT_XINT1, GPIO_INT_TYPE_FALLING_EDGE);
//
// Enable XINT1
//
GPIO_enableInterrupt(GPIO_INT_XINT1); // Enable XINT1
//
// Loop.
//
for(;;)
{
}
}
//
// xint1ISR - External Interrupt 1 ISR
//
void xint1ISR(void)
{
//
// Get External Interrupt 1 status
//
if(EXTI_getInterruptStatus(EXTI_CORE_CPU1, EXTI_LINE_4))
{
GPIO_togglePin(myGPIOOutput_LED2);
xint1Count++;
printf("CPU1 EXTI Interrupt: %02d ",xint1Count);
//
// Clear external interrupt 1 status
//
EXTI_clearInterruptStatus(EXTI_CORE_CPU1, EXTI_LINE_4);
}
}
//
// Function to clear the g_msgRecv array.
// This function set g_msgRecv to be 0.
//
void clearMsgRecv(void)
{
uint32_t i;
for (i = 0U; i < MESSAGE_LENGTH; i++)
{
g_msgRecv[i] = 0U;
}
}
//
// UART initialize
//
void UART_Init(void)
{
//
// GPIO28 is the UART Rx pin.
//
GPIO_setMasterCore(DEVICE_GPIO_PIN_UARTRXDA, GPIO_CORE_CPU1);
GPIO_setPinConfig(DEVICE_GPIO_CFG_UARTRXDA);
GPIO_setDirectionMode(DEVICE_GPIO_PIN_UARTRXDA, GPIO_DIR_MODE_IN);
GPIO_setDrivingCapability(DEVICE_GPIO_PIN_UARTRXDA,GPIO_DRIVE_LEVEL_VERY_HIGH);
GPIO_setPadConfig(DEVICE_GPIO_PIN_UARTRXDA, GPIO_PIN_TYPE_STD);
GPIO_setQualificationMode(DEVICE_GPIO_PIN_UARTRXDA, GPIO_QUAL_ASYNC);
//
// GPIO29 is the UART Tx pin.
//
GPIO_setMasterCore(DEVICE_GPIO_PIN_UARTTXDA, GPIO_CORE_CPU1);
GPIO_setPinConfig(DEVICE_GPIO_CFG_UARTTXDA);
GPIO_setDirectionMode(DEVICE_GPIO_PIN_UARTTXDA, GPIO_DIR_MODE_OUT);
GPIO_setDrivingCapability(DEVICE_GPIO_PIN_UARTTXDA,GPIO_DRIVE_LEVEL_VERY_HIGH);
GPIO_setPadConfig(DEVICE_GPIO_PIN_UARTTXDA, GPIO_PIN_TYPE_STD);
GPIO_setQualificationMode(DEVICE_GPIO_PIN_UARTTXDA, GPIO_QUAL_ASYNC);
//
// Initialize UARTA and its FIFO.
//
UART_performSoftwareReset(UARTA_BASE);
//
// Configure UARTA for echoback.
//
UART_setConfig(UARTA_BASE, DEVICE_LSPCLK_FREQ, 115200, (UART_CONFIG_WLEN_8 |
UART_CONFIG_STOP_ONE |
UART_CONFIG_PAR_NONE));
UART_resetChannels(UARTA_BASE);
UART_resetRxFIFO(UARTA_BASE);
UART_resetTxFIFO(UARTA_BASE);
UART_clearInterruptStatus(UARTA_BASE, UART_INT_TXFF | UART_INT_RXFF);
UART_enableFIFO(UARTA_BASE);
UART_enableModule(UARTA_BASE);
UART_performSoftwareReset(UARTA_BASE);
}
#ifdefined(__CC_ARM) || defined(__ARMCC_VERSION)
//
// Redefine the fputc function to the serial port
//
int fputc(int ch, FILE* f)
{
if (ch == ' ')
{
UART_writeCharBlockingNonFIFO(UARTA_BASE, (uint8_t)' ');
}
UART_writeCharBlockingNonFIFO(UARTA_BASE, (uint8_t)ch);
return (ch);
}
#elifdefined(__ICCARM__)
int __io_putchar(int ch)
{
if (ch == ' ')
{
UART_writeCharBlockingNonFIFO(UARTA_BASE, (uint8_t)' ');
}
UART_writeCharBlockingNonFIFO(UARTA_BASE, (uint8_t)ch);
return (ch);
}
int __write(int file, char* ptr, int len)
{
int i;
for (i = 0; i < len; i++)
{
__io_putchar(*ptr++);
}
return len;
}
#elifdefined (__clang__) && !defined (__ARMCC_VERSION)
int uart_putc(char ch, FILE *file)
{
if (ch == ' ')
{
UART_writeCharBlockingNonFIFO(UARTA_BASE, (uint8_t)' ');
}
UART_writeCharBlockingNonFIFO(UARTA_BASE, (uint8_t)ch);
return (ch);
}
static FILE __stdio = FDEV_SETUP_STREAM(uart_putc, NULL, NULL, _FDEV_SETUP_WRITE);
FILE *const stdin = &__stdio;
__strong_reference(stdin, stdout);
__strong_reference(stdin, stderr);
#endif
//
// End of File
//
5.3 運行效果
如果一切順利,當GPIO0產生上下跳沿時,CPU1的XINT1服務例程會被觸發。
LED隨之閃爍,串口打印出類似“CPU1EXTIInterrupt:01”“CPU1EXTIInterrupt:02”等等。
當你看到CPU1自信地響應外部中斷,CPU0則不為所動,就說明“多核外部中斷獨立控制”成功啦!

6、踩坑
凡事說起來都挺美好,可實際開發過程中,踩坑是免不了的。下面就分享一些常見“翻車”瞬間,讓各位少走點彎路:
1. 開小差就忘了WRPRT_DISABLE/WRPRT_ENABLE
在寫各種訪問控制寄存器時,經常需要先“解鎖”再寫,然后再“上鎖”。如果忘了在修改前后來一句
WRPRT_DISABLE;
…(寄存器操作)…
WRPRT_ENABLE;
那么你會發現自己改了半天沒生效。這個“解鎖-上鎖”機制就像給寄存器設置了“防熊孩子模式”,不解鎖是改不動的。沒做對的話,一定會抓瞎很久。
2. 訪問權限沒開足
你可能會納悶:“為什么CPU0能讀寫外設,CPU1卻獲取不到數據?”別苦惱,先檢查一下PERIPH_AC里面有沒有給CPU1設置Full Access。要是權限只開了半截(Protected Read之類),CPU1寫不進去也正常啊!
3. GPIO主核選擇忘了改
當你興致勃勃地在CPU1里調用GPIO_writePin(XX, HIGH),結果測量腳位卻毫無波動——很可能是GPIO主核依然掛在CPU0身上… 莫名其妙,就像你在隔壁家燈的開關上亂按,當然亮不了自家燈。
所以一定別忘了GPIO_setMasterCore(pin, GPIO_CORE_CPU1)或GPIO_CORE_CPU0!
歡迎各位在評論區留下配置雙核訪問不同外設的小tip吧!
原文地址:https://bbs.21ic.com/icview-3501421-1-1.html?_dsign=fe2bb841
-
mcu
+關注
關注
147文章
18924瀏覽量
398021 -
寄存器
+關注
關注
31文章
5608瀏覽量
129966 -
GPIO
+關注
關注
16文章
1328瀏覽量
56218
原文標題:極海芯得 EP.71 | G32R501的兩核外設分配,竟然還能這么玩!
文章出處:【微信號:geehysemi,微信公眾號:Geehy極海半導體】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
使用vscode和cmake工具開發極海G32R501 MCU
極海G32R501實時控制 MCU 跨域新境界丨面向新一代高效能高實時控制設備
實時自控 精準調速 | 極海G32R501 2.2kW高性能矢量變頻器參考方案
極海出席慕尼黑上海電子展,展示全球首款雙核架構G32R5系列實時控制MCU
極海于electronica 2024展示G32R5高性能實時控制MCU
高效雙控 精準卓越 | 極海G32R501低壓無感雙電機參考方案
高效轉換 實時調控 | 基于G32R501的800W雙路MPPT微型逆變器參考方案
極海半導體榮登2025中國IC設計Fabless100排行榜之TOP10微控制器公司
極海半導體全數字雙向電源參考方案助力能效躍升
極海半導體G32R501:面向具身機器人的高性能、高安全實時控制MCU/DSP
極海G32R501工業六軸機械臂參考方案釋放工業4.0產業價值
極海半導體G32R501D實時控制MCU正式通過SGS IEC 61508功能安全產品認證
特來電攜手極海半導體推出G32R501T實時控制MCU
極海G32R501芯片與Jlink適配的過程解析
詳解極海G32R501 MCU的兩核外設分配
評論