《极海芯得》系列内容为用户使用极海系列产品的经验总结,均转载自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
- 随机文章
- 热门文章
- 热评文章
- 湘粤合作发展避暑康养产业 共拓大湾区市场,湘粤合作发展避暑康养产业 共拓大湾区市场
- 武汉出台行动方案加快打造全国科技金融中心,武汉出台行动方案加快打造全国科技金融中心
- 从世俱杯到家庭厨房,海信真空冰箱用“MVP OF FRESHNESS”抢占保鲜C位,从世俱杯到家庭厨房,海信真空冰箱用“MVP OF FRESHNESS”抢占保鲜C位
- 广州中欧班列跨境电商中心发出首趟跨境电商零售出口班列,广州中欧班列跨境电商中心发出首趟跨境电商零售出口班列
- 祁连山下“学霸民族”:肃南18年免费教育的“草原智慧”,祁连山下“学霸民族”:肃南18年免费教育的“草原智慧”
- 广州办展回溯爱国音乐家萧友梅的艺术人生和家国情怀,广州办展回溯爱国音乐家萧友梅的艺术人生和家国情怀
- 6月13日 6股盘前利好公告速递
- 甘肃能源(000791):民勤100万千瓦风光电一体化项目获得核准(备案)
- 1[风险]嘉实原油LOF (160723): 嘉实原油证券投资基金(QDII-LOF)溢价风险提示公告
- 2广西三地接连发布公告:禁止前往!后果自负!
- 3甜蜜的事业,幸福的滋味……
- 4SCADA系统的NAT转换与网段隔离解决方案
- 5芦苇任中国邮政集团副总经理
- 6A股三大股指集体收涨,沪指续创10年新高,A股三大股指集体收涨,沪指续创10年新高
- 7穆勒矩阵椭偏仪:DVRMME技术的系统误差建模与校准补偿
- 8政策定调催生新主线,A股跨年行情蓄势待发
- 1废砖瓦里砌出风景线 湖南靖州深山村寨美丽嬗变,废砖瓦里砌出风景线 湖南靖州深山村寨美丽嬗变
- 2文科生也可以学医了!多所高校中医学类专业选科放宽,文科生也可以学医了!多所高校中医学类专业选科放宽
- 3湖南零陵发展数字农业 现代农民种粮更轻松,湖南零陵发展数字农业 现代农民种粮更轻松
- 4上午买食材、下午逛潮店 古城老菜场人气足,上午买食材、下午逛潮店 古城老菜场人气足
- 5广东怀集所有国省道主线均已抢通,广东怀集所有国省道主线均已抢通
- 6520分钟攻坚!成渝中线高铁成都站改建工程进度过半,520分钟攻坚!成渝中线高铁成都站改建工程进度过半
- 7广东清远连山紧急转移8名村民 避险山体滑坡,广东清远连山紧急转移8名村民 避险山体滑坡
- 8中意经典著作互译出版项目启动 四部重磅作品首批入选,中意经典著作互译出版项目启动 四部重磅作品首批入选
