金融投资理财

详解极海G32R501 MCU的两核外设分配

《极海芯得》系列内容为用户使用极海系列产品的经验总结,均转载自21ic论坛极海半导体专区,全文未作任何修改,未经原文作者授权禁止转载。

你有没有遇到过这样的尴尬场景:当你想让CPU0和CPU1“好好相处”,却发现它俩居然会抢外设用?一边还在想“我的GPIO在哪?为什么跑到另一个核上了?”一边翻着用户手册,看着漫天寄存器名称脑壳疼。

如果你也有这种体验,那么恭喜你,说明你已经开始入坑G32R501这款双核MCU啦!今天咱们就来聊一聊,G32R501到底是如何让两核共享、分配外设的,顺便也整点代码案例,让你的双核之路不再迷茫。

1、双核背景:G32R501长啥样?

在这颗称为G32R501Dx的MCU里,最显著的特征当然是双核配置。该芯片的硬件方框图里,CPU0和CPU1像一对“欢喜冤家”,带着各自的Cache、RAM,整整齐齐地驻扎在芯片内部。

e18bc702-ed25-11f0-92de-92fbcf53809c.png

那么问题来了:既然是两核,那么外设该给谁用?假设把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。

e245fd52-ed25-11f0-92de-92fbcf53809c.png

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则不为所动,就说明“多核外部中断独立控制”成功啦!

e300f9cc-ed25-11f0-92de-92fbcf53809c.png

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

  • 随机文章
  • 热门文章
  • 热评文章

相关推荐