详解极海G32R501 MCU的两核外设分配
《极海芯得》系列的内容是用户使用集海系列产品的体验总结。均转载自21ic论坛集海半导体专区。全文未经过任何形式的修改,未经原作者授权禁止转载。
你是否遇到过这样尴尬的场景:当你想让CPU0和CPU1“好好相处”时,却发现他们居然在争夺外设?当我还在想“我的GPIO在哪里?为什么它去了另一个核心?”我正在翻阅用户手册,当我看到到处都是寄存器名称时,我的头很痛。
如果您也有这样的经历,那么恭喜您,这意味着您已经开始深入了解双核MCU G32R501了!今天我们来谈谈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):
静态内联空
SysCtl_setPeripheralAccessControl(SysCtl_AccessPeripheral 外设,
SysCtl_AccessMaster主控,
SysCtl_AccessPermission 权限)
{
//
//设置指定外设的主控权限。每个大师都有
//两位专用于其权限设置。
//
WRPRT_DISABLE;
HWREG(PERIPHAC_BASE + (uint16_t)外设* 2)=
(HWREG(PERIPHAC_BASE + (uint16_t)外设* 2)
~(0x3U (uint16_t)master)) |
((uint16_t)权限(uint16_t)master);
WRPRT_ENABLE;
}
如果你想快速禁止CPU1读写ADCB,你只需要调用一个函数即可完成。这种封装对于新手来说极其友好,再也不用担心记住寄存器偏移量了!
3. GPIO访问控制:谁拥有主核心?
GPIO简直就是MCU的脸面! “打开”MCU (Hello World) 的第一步是单击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
驱动库还贴心的帮你封装了“烧脑寄存器”:
无效
GPIO_setMasterCore(uint32_t 引脚, GPIO_CoreSelect 内核)
{
易失性uint32_t *gpioBaseAddr;
uint32_t cSelIndex;
uint32_t shiftAmt;
//
//Check the arguments.
//
ASSERT(GPIO_isPinValid(引脚));
gpioBaseAddr=(uint32_t *)GPIOCTRL_BASE +
((引脚/32U) * GPIO_CTRL_REGS_STEP);
shiftAmt=(uint32_t)GPIO_GPACSEL1_GPIO1_S * (引脚% 8U);
cSelIndex=GPIO_GPxCSEL_INDEX + ((引脚% 32U)/8U);
//
//将核心参数写入寄存器。
//
WRPRT_DISABLE;
gpioBaseAddr[cSelIndex]=~((uint32_t)GPIO_GPACSEL1_GPIO0_M shiftAmt);
gpioBaseAddr[cSelIndex] |=(uint32_t)core shiftAmt;
WRPRT_ENABLE;
}
一句话:传入你要设置的引脚号,以及指定的CPU是CPU0还是CPU1,函数就会帮你改寄存器,可以省很多力气。
4. 外部中断分配:EXTI_xMASKx
除了GPIO之外,另外80%被抢最多的是中断。你肯定不希望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 内核, EXTI_LineNumber 行号)
{
WRPRT_DISABLE;
if(核心==EXTI_CORE_CPU0)
{
HWREG(EXTI_BASE + EXTI_O_IMASK0) |=(1U (uint32_t)lineNumber);
}
否则
{
HWREG(EXTI_BASE + EXTI_O_IMASK1) |=(1U (uint32_t)lineNumber);
}
WRPRT_ENABLE;
}
静态内联空
EXTI_disableInterrupt(EXTI_CoreSelect 内核, EXTI_LineNumber 行号)
{
WRPRT_DISABLE;
if(核心==EXTI_CORE_CPU0)
{
HWREG(EXTI_BASE + EXTI_O_IMASK0)=~(1U (uint32_t)lineNumber);
}
否则
{
HWREG(EXTI_BASE + EXTI_O_IMASK1)=~(1U (uint32_t)lineNumber);
}
WRPRT_ENABLE;
}
static inline void
EXTI_enableEvent(EXTI_CoreSelect 核心, EXTI_LineNumber 行号)
{
WRPRT_DISABLE;
if(核心==EXTI_CORE_CPU0)
{
HWREG(EXTI_BASE + EXTI_O_EMASK0) |=(1U (uint32_t)lineNumber);
}
否则
{
HWREG(EXTI_BASE + EXTI_O_EMASK1) |=(1U (uint32_t)lineNumber);
}
WRPRT_ENABLE;
}
静态内联空
EXTI_disableEvent(EXTI_CoreSelect 核心, EXTI_LineNumber 行号)
{
WRPRT_DISABLE;
if(核心==EXTI_CORE_CPU0)
{
HWREG(EXTI_BASE + EXTI_O_EMASK0)=~(1U (uint32_t)lineNumber);
}
否则
{
HWREG(EXTI_BASE + EXTI_O_EMASK1)=~(1U (uint32_t)lineNumber);
}
WRPRT_ENABLE;
}
只要指定了核心(CPU0或CPU1)和外部中断线,就可以轻松地让中断“运行”到所需的核心。
5、CPU1响应外部中断示例
既然说到这里,我们就用SDK例程来练练“真刀真枪”吧。 SDK中有两个经典的例子:
1.CPU1基本例程(ipc_ex2_mailbox_pollingcpu1)
2. 外部中断例程(interrupt_ex1_external)
假设我们想在CPU1上使用外部中断来检测某个输入引脚的“电平下降”。那么需要进行哪些修改呢?
5.1 修改代码
ipc_ex2_mailbox_pollingcpu1的原始代码只是一个简单的IPC消息发送和接收演示,没有外部中断。我们将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 邮箱示例
//
//版本: 1.0.0
//
//日期: 2025-01-15
//
//!添加到组driver_example_list
//!
IPC Mailbox with Polling
//!
//!本例演示如何使用Inter-Processor的邮箱机制
//!通信(IPC)模块,以轮询模式在两个CPU之间发送和接收数据。
//!
//!在这个例子中:
//! 1. CPU0 通过IPC 模块以轮询方式向CPU1 发送消息。
//! 2. CPU1 以轮询方式向CPU0 发送消息。
//! 3. CPU0 以轮询方式接收CPU1 发送的消息,以此类推。
//!
//!ote Note: IPC 示例包括CPU0 和CPU1 项目。
//!请先编译CPU1 工程,然后再编译CPU0 工程。
//!
//!运行应用程序
//!使用terminal:打开具有以下设置的COM端口
//! - 找到正确的COM端口
//! - 每秒位数=115200
//! - 数据位=8
//! - 奇偶校验=无
//! - 停止位=1
//! - 硬件控制=无
//!
//!外部连接
//!通过收发器和电缆将UART-A 端口连接到PC。
//! - GPIO28 是UART_RXD(连接到串行DB9 电缆的Pin3、PC-TX)
//! - GPIO29 是UART_TXD(连接到串行DB9 电缆的Pin2、PC-RX)
//!
//!观察变量
//! - 没有任何。
//!
//
//##########################################################################
//
//
//$Copyright:
//版权所有(C) 2024 吉希半导体- http://www.geehy.com/
//
//除非符合以下规定,否则您不得使用此文件
//GEEHY 版权声明(GEEHY 软件包许可证)。
//
//程序仅供参考,希望分发
//这对于客户的开发来说是有用且具有指导意义的
//他们的软件。除非适用法律要求或双方同意
//写作时,程序是按“原样”分发的,没有
//任何明示或暗示的保证或条件。
//请参阅GEEHY 软件包许可证以获取管理权限
//以及许可证下的限制。
//$
//##########################################################################
//
//包含的文件
//
#include'driverlib.h'
#include'设备.h'
#include'board.h'
#包括
//
//用多少条消息来测试邮箱发送和接收
//
#defineMESSAGE_LENGTH 4U
//
//变量
//
静态uint32_t g_msgRecv[MESSAGE_LENGTH];
易失性uint32_t xint1Count;
//
//函数原型
//
无效clearMsgRecv(无效);
无效UART_Init(无效);
无效xint1ISR(无效);
//
//主要
//
无效example_main(无效)
{
//
//初始化设备时钟和外设
//
Device_init();
//
//初始化NVIC 并清除NVIC 寄存器。禁用CPU 中断。
//
中断_initModule();
//
//使用指向shell 中断的指针初始化NVIC 向量表
//服务例程(ISR)。
//
中断_initVectorTable();
//
//本例中使用的中断被重新映射到
//在此文件中找到ISR 函数。
//
中断_寄存器(INT_XINT1, xint1ISR);
//
//板初始化
//
Board_init();
//
//允许CPU中断
//
中断_enableMaster();
//
//串口初始化
//
UART_Init();
xint1Count=0; //计数XINT1 中断
//
//CPU1打印信息
//
printf('CPU1已完成启动,中断。');
//
//接收前清空g_msgRecv数组
//
清除MsgRecv();
//
//CPU1接收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将消息发送回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]);
//
//设置优先级组以指示PREEMPT 和SUB 优先级位。
//
Interrupt_setPriorityGroup(INTERRUPT_PRIGROUP_PREEMPT_7_6_SUB_5_0);
//
//设置全局和组优先级以允许CPU中断
//具有更高的优先级
//
中断_设置优先级(INT_XINT1,1,0);
//
//启用XINT1 和XINT2 i
n 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