《DN8P1开发指南_V1.0》第五章 RA8P1基础知识入门
第五章 RA8P1基础知识入门
本章,我们着重介绍RA8P1的一些基础知识,让大家对RA8P1开发有一个初步的了解,为后面RA8P1的学习做铺垫,方便后面的学习。本章内容大家第一次看的时候可以只了解一个大概,后面需要用到这方面的知识的时候再回过头来仔细看看。
本章将分为如下几个小节:
5.1 C语言基础知识复习
5.2 RA8P1系统架构
5.1 C语言基础知识复习
本节我们给大家介绍一下C语言基础知识,对于C语言比较熟练的读者,可以跳过此节,对于基础比较薄弱的读者,建议好好学习一下本节内容。
由于C语言博大精深,不可能我们一小节就全讲明白了,所以本节我们只是复习RA8P1开发时常用的几个C语言知识点,以便大家的更好的学习并编写RA8P1代码。
5.1.1 位操作
C语言位操作相信学过C语言的人都不陌生了,简而言之,就是对基本类型变量可以在位级别进行操作。这节的内容很多朋友都应该很熟练了,我这里也就点到为止,不深入探讨。下面我们先讲解几种位操作符,然后讲解位操作使用技巧。C语言支持如下6种位操作:

表5.1.1.1 六种位操作
这些与或非,取反,异或,右移,左移这些到底怎么回事,这里我们就不多做详细,相信大家学C语言的时候都学习过了。如果不懂的话,可以百度一下,非常多的知识讲解这些操作符。下面我们想着重讲解位操作在单片机开发中的一些实用技巧。
1,先清零再置位(最常用操作)
操作寄存器特定位的标准流程是:先通过&运算清零,再通过|运算赋值。这种方式可以保证只修改目标位,其他位保持不变。
REGx &= ~(1 << n); /* 清零第n位 */
REGx |= (1 << n); /* 设置第n位为1 */
2,移位操作提高代码可读性
在操作寄存器时,使用移位操作1 << n比直接写十六进制数更直观、可读性更高。
REGx |= 1 << 1; /* 将第1位设置为1 */
3, ~按位取反操作技巧
REGx &= ~(1 << 0); /* 仅把第0位清零,其他位不变 */
这种写法比直接写0xFFFFFFFE更易读、更安全。
4,^按位异或操作技巧
按位异或^最典型的用途是位翻转。对某一位执行异或1,该位会翻转(0变1,1变0)。
REGx ^= 1 << 5; /* 翻转第5位的状态*/
5.1.2 define宏定义
define是C语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方便。常见的格式:
#define 标识符 字符串
“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:
#define HSE_VALUE 8000000U
定义标识符HSE_VALUE的值为8000000,数字后的U表示unsigned的意思。
至于define宏定义的其他一些知识,比如宏定义带参数这里我们就不多讲解。
5.1.3 ifdef条件编译
单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:
#ifdef 标识符
程序段1
#else
程序段2
#endif
它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。 其中#else部分也可以没有,即:
#ifdef
程序段1
#endif
5.1.4 extern外部申明
C语言中extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。这里面要注意,对于extern申明变量可以多次,但定义只有一次。在我们的代码中你会看到这样的语句:
extern uint16_t g_usart_rx_sta;
这个语句是申明g_usart_rx_sta变量在其他文件中已经定义了,在这里要使用到。所以,你肯定可以找到在某个地方有变量定义的语句:
uint16_t g_usart_rx_sta;
extern的使用比较简单,但是也会经常用到,需要掌握。
5.1.5 typedef类型别名
typedef的作用是为已有的数据类型定义一个别名,让代码更简洁、更容易阅读、便于维护。
typedef unsigned char uint8_t;
这行代码的意思是:给unsigned char类型起别名叫做uint8_t。
之后定义变量时,就可以直接写::
uint8_t a;
5.1.6 结构体
很多用户刚开始接触RA8P1的FSP驱动时,会对结构体和结构体指针感到陌生,而e²studio中大量外设初始化、配置都用到了结构体,这让学习门槛变高。其实结构体并不复杂,我们这里结合RA8P1的实际场景,讲清楚它的用法和作用。
声明结构体类型:
struct 结构体名
{
成员列表;
}变量名列表;
例如:
struct U_TYPE
{
int BaudRate
int WordLength;
}usart1, usart2;
在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:
struct 结构体名字 结构体变量列表 ;
例如:
struct U_TYPE usart1,usart2;
结构体成员变量的引用方法是:
结构体变量名字.成员名
比如要引用usart1的成员BaudRate,方法是:usart1.BaudRate;结构体指针变量定义也是一样的,跟其他变量没有啥区别。
例如:
struct U_TYPE *usart3; /* 定义结构体指针变量usart3 */
结构体指针成员变量引用方法是通过“->”符号实现,比如要访问usart3结构体指针指向的结构体的成员变量BaudRate,方法是:
usart3->BaudRate;
上面讲解了结构体和结构体指针的一些知识,其他的什么初始化这里就不多讲解了。讲到这里,有人会问,结构体到底有什么作用呢?为什么要使用结构体呢?下面我们将简单的通过一个实例回答一下这个问题。
在我们单片机程序开发过程中,经常会遇到要初始化一个外设比如串口,它的初始化状态是由几个属性来决定的,比如串口号,波特率,极性,以及模式。对于这种情况,在我们没有学习结构体的时候,我们一般的方法是:
void usart_init(uint8_t usartx, uiut32_t BaudRate, uint32_t Parity,
uint32_t Mode);
这种方式是有效的同时在一定场合是可取的。但是试想,如果有一天,我们希望往这个函数里面再传入一个/几个参数,那么势必我们需要修改这个函数的定义,重新加入新的入口参数,随着开发不断的增多,那么是不是我们就要不断的修改函数的定义呢?这是不是给我们开发带来很多的麻烦?那又怎样解决这种情况呢?
我们使用结构体参数,就可以在不改变入口参数的情况下,只需要改变结构体的成员变量,就可以达到改变入口参数的目的。
结构体就是将多个变量组合为一个有机的整体,上面的函数,usartx,BaudRate,Parity, Mode等这些参数,他们对于串口而言,是一个有机整体,都是来设置串口参数的,所以我们可以将他们通过定义一个结构体来组合在一个有机整体。e²studio中是这样定义的:
typedef struct
{
uint32_t BaudRate;
uint32_t WordLength;
uint32_t StopBits;
uint32_t Parity;
uint32_t Mode;
uint32_t HwFlowCtl;
uint32_t OverSampling;
} UART_InitTypeDef;
这样,我们在初始化串口的时候入口参数就可以是USART_InitTypeDef类型的变量或者指针变量了,于是我们可以改为:
void usart_init(UART_InitTypeDef *huart);
这样,任何时候,我们只需要修改结构体成员变量,往结构体中间加入新的成员变量,而不需要修改函数定义就可以达到修改入口参数同样的目的了。这样的好处是不用修改任何函数定义就可以达到增加变量的目的。
理解了结构体在这个例子中间的作用吗?在以后的开发过程中,如果你的变量定义过多,如果某几个变量是用来描述某一个对象,你可以考虑将这些变量定义在结构体中,这样也许可以提高你的代码的可读性。
使用结构体组合参数,可以提高代码的可读性,不会觉得变量定义混乱。当然结构体的作用就远远不止这个了,同时,e²studio中用结构体来定义外设也不仅仅只是这个作用,这里我们只是举一个例子,通过最常用的场景,让大家理解结构体的一个作用而已。后面一节我们还会讲解结构体的一些其他知识。
5.1.7 指针
指针是一个值指向地址的变量(或常量),其本质是指向一个地址,从而可以访问一片内存区域。在编写代码的时候,或多或少都要用到指针,它可以使不同代码共享同一片内存数据,也可以用作复杂的链接性的数据结构的构建,比如链表,链式二叉树等,而且,有些地方必须使用指针才能实现,比如内存管理等。
申明指针我们一般以p开头,如:
char * p_str = “This is a test!”;
这样,我们就申明了一个p_str的指针,它指向This is a test!这个字符串的首地址。我们编写如下代码:
int main(void)
{
uint8_t temp = 0X88; /* 定义变量 temp */
uint8_t *p_num = &temp; /* 定义指针p_num,指向temp的地址 */
printf("temp:0X%X\r\n", temp); /* 打印temp的值 */
printf("*p_num: 0X %X\r\n", *p_num); /* 打印*p_num的值 */
printf("p_num: 0X %X\r\n", (uint32_t)p_num); /* 打印p_num的值 */
printf("&p_num: 0X %X\r\n", (uint32_t)&p_num); /* 打印&p_num的值 */
while (1);
}
此代码的输出结果为:

图5.1.7.1 输出结果
p_num:是uint8_t类型指针,指向temp变量的地址,其值等于temp变量的地址。
*p_num:取p_num指向的地址所存储的值,即temp的值。
&p_num:取p_num指针的地址,即指针自身的地址。
以上,就是指针的简单使用和基本概念说明,指针的详细知识和使用范例大家可以百度学习,网上有非常多的资料可供参考。指针是C语言的精髓,在后续的代码中我们将会大量用到各种指针,大家务必好好学习和了解指针的使用。
5.2 RA8P1基础知识
RA8P1 是瑞萨电子基于Arm Cortex‑M85/M33内核与Ethos‑U55 NPU打造的22nm高性能 AI MCU,主频1GHz,CoreMark超7300,集成MRAM与大容量SRAM,专为边缘视觉、语音 AI 及实时控制设计,配套e² studio与E2 Lite仿真器开发。
5.2.1 双核架构
ARM公司提供内核(如 Cortex‑M85、Cortex‑M33,简称CM85、CM33,下同)授权,完整的MCU还需要搭配各类配套组件。芯片厂商瑞萨电子(Renesas)获得内核授权后,将双核架构集成到芯片硅片设计中,同时搭载Ethos‑U55 NPU硬件AI加速器,并扩展存储器、外设、I/O接口以及其他功能模块。不同品牌、不同型号的ARM架构MCU,在存储容量、存储类型、外设资源、功能配置上各有差异,以此适配不同的应用场景,形成了市面上品类丰富的ARM系列芯片。Cortex‑M85、Cortex‑M33双核架构与芯片整体的关系如图所示:

图5.3.1.1 Cortex M85& Cortex M33之间的关系图
可以看到,ARM公司提供了Cortex‑M85/M33双核内核以及统一的调试系统(包括DAP、APB/AHB‑AP、CTI/ETM/ITM/TPIU等调试与跟踪模块),构成了芯片的核心计算与调试基础。而其他部分,如系统总线、存储器(ITCM/DTCM/TCM、Cache)、安全单元(IDAU)、以及后续瑞萨电子扩展的外设(如UART、SPI、I2C、定时器、ADC等)、片上存储器(MRAM/SRAM)、I/O接口等,都由芯片制造商瑞萨电子设计开发并集成到整个系统中。这也是不同厂商基于相同ARM内核,却能推出各具特色、适配不同应用场景的MCU的根本原因。RA8P1就是瑞萨电子基于这一双核架构与调试系统,结合自身外设与存储方案打造的一款高性能AI MCU。
5.2.2 存储器地址映射
RA8P1是一个32位单片机,它可以很方便的访问4GB以内的存储空间(2^32 = 4GB),因此 Cortex‑M85/M33 内核将图中的所有结构,包括:MRAM、SRAM、TCM、外设及相关寄存器等全部组织在同一个 4GB 的线性地址空间内,我们可以通过C语言来访问这些地址空间,从而操作相关外设(读 / 写)。数据字节以小端格式(小端模式)存放在存储器中,数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。
存储器本身是没有地址信息的,我们对存储器分配地址的过程就叫存储器映射。这个分配一般由芯片厂商做好了,瑞萨电子将所有的存储器及外设资源都映射在一个4GB的地址空间上,按功能划分为多个块,从而可以通过访问对应的地址,访问具体的外设或存储器。其映射关系如图5.2.2.1所示:

图5.2.2.1 RA8P1存储器映射
上图是瑞萨RA8P1 双核ARM(CPU0:Cortex‑M85、CPU1:Cortex‑M33)+ IDAU/MSAU 安全分区的全地址空间分布图,整体按地址从低 (0x0000_0000)→高 (0xF000_0000)、横向分 3 主体(CPU0/CPU1 / 其他外设)、纵向右侧分安全属性(Secure/Non‑Secure/CPU 专属可调用非安全区)四大维度划分,下面按地址段 + 分区类型拆解用途:
一、低地址区:0x0000_0000 ~ 0x0300_0000:Code MRAM(程序主闪存区)
MRAM为磁性随机存储器,等同于MCU内置Flash,存放固件程序代码、常量数据,分两段独立Code MRAM:
-
0x00000000 ~ 0x02000000
CPU0‑ITCM²(0x0000_0000起):CPU0 (M85)的指令紧耦合内存ITCM,ITCM是M架构专用指令缓存,CPU0从此区域取指运行程序,只存指令、高速零等待读取;
CPU1‑CTCM⁴:CPU1 (M33)的常量紧耦合内存CTCM,存放CPU1的常量数据/只读代码;
右侧属性:Non‑secure callable for CPU,Secure for other bus masters
CPU可以非安全态访问这片代码,除了CPU以外的DMA/ 设总线主机,访问这片默认是安全域(Secure),需要安全权限才能读写,用于保护固件代码不被外设非法篡改。 -
0x12100000 ~ 0x12000000 第二块独立的Code MRAM
独立备用程序闪存分区,用于OTA升级备份固件、二次 bootloader、备用应用程序,安全属性默认Non‑Secure。
Extra MRAM (Option‑setting memory):0x02A0/0x02E0/0x12A0/0x12E0_0000配置字 MRAM(选项字节区),等同于传统MCU的Option Byte:
存放芯片熔丝配置、安全配置、启动配置、硬件引脚配置、读写保护开关,出厂配置 / 用户配置参数,只读为主,仅配置工具可擦写修改。
二、0x1000_0000 ~ 0x321D_4000:片上SRAM(运行内存,等同RAM)
-
ITCM/DTCM/STCM/CTCM(CPU 专属紧耦合内存,M 架构高速片内 RAM)
ITCM:Instruction TCM(指令 TCM,只跑指令);DTCM:Data TCM(数据 TCM,全局 / 局部变量);STCM/CTCM:CPU1 专属 TCM(常量 / 数据区),TCM 是 CPU 零等待高速内存,比普通 SRAM 更快,仅归属对应 CPU 内核访问
CPU0‑DTCM³/⁷:多地址别名映射(0x2001_0000、0x3001_0000),CPU0 (M85) 专用数据高速 RAM,存放运行时变量、堆栈;
CPU0‑ITCM⁵/⁶:CPU0 指令高速 TCM,可把关键代码拷贝进 ITCM 全速运行;
CPU1‑STCM⁵/⁹、CPU1‑CTCM⁴/⁸:CPU1 (M33) 专属数据 / 常量 TCM,同样多地址别名(alias),别名 = 同一块物理内存映射多个地址,方便不同安全域 / 内核寻址;
xxx alias:别名地址 = 同一块物理 TCM 内存映射到多个地址段,用来实现安全域拆分(Secure/Non‑Secure 分地址访问同一片物理 RAM)。 -
On‑chip SRAM / SRAME(0x221A_0000、0x321A_0000)
通用大容量片上 SRAM:双核共享普通运行内存,用于大缓存、DMA缓冲区、双核交互数据区、大数组缓存;SRAME=SRAM 扩展区,额外扩充RAM。
安全属性:Non‑secure callable for CPU,Secure for other bus masters:CPU能非安全读写,外设 / DMA 访问需要安全权限,防止外设越权篡改运行内存。
三、0x4000_0000 ~ 0x5050_0000:外设寄存器区Peripheral IO registers
Secure安全域分区:整段划归Secure安全域,所有片上外设(GPIO/UART/SPI/ADC/PWM 等)寄存器都在此地址;
安全规则:只有安全态 (Secure) 代码才能直接访问外设寄存器,非安全 (Non‑Secure) CPU 不能直接操作硬件外设,必须通过安全网关调用安全服务,是ARM TrustZone安全架构的典型设计;
Reserved area¹:保留地址,未集成外设,预留后续芯片改版拓展。
四、0x6000_0000 ~ 0x7000_0000:外部地址空间(外接存储 / 总线)
三段外部总线地址:
1,CS area:片选外设总线,外接 SPI / 并行总线外设(传感器、扩展 IO);
2,SDRAM area:外接 SDRAM 高速大容量运存,用于操作系统、大缓存;
3,OSPI area:八线 SPI 闪存(Octal SPI Flash),外接大容量外挂程序闪存;
4, 安全属性:Non‑Secure,默认非安全域,双核非安全代码都可访问外接器件。
5.2.3 RA8P1时钟树
RA8P采用双PLL+5路原生振荡源+分级分频+TrustZone安全时钟管控架构,面向 Cortex‑M85(CPU0)+Cortex‑M33(CPU1)双核+内置NPU架构设计,整套时钟树分为时钟源层→PLL倍频层→系统主干时钟层→外设专用时钟层→外部输出时钟五级架构,支持安全域 / 非安全域时钟寄存器隔离、振荡失效自动切换、FLL频率校准、多档分频配置、低功耗时钟冻结,可灵活适配高速运算、工业外设、LCD/以太网/USB等多场景主频需求。读者可自行参阅RA8P1数据手册第9章的“Figure 9.1 Clock generation circuit block diagram”时钟树示意图。鉴于原版框图结构较为复杂、不够直观,本文将以e2 studio的FSP时钟配置界面为基准,为大家详细讲解RA8P1的时钟树,具体如下图所示。

图5.2.3.1 RA8P1时钟树(部分截图)
1,RA8P共5个独立基准振荡源,分为外部晶振型(MOSC/SOSC)+片内RC型(HOCO/MOCO/LOCO),是全时钟树的频率基准,PLL/FLL全部依托该层时钟输入。
2,PLL1P是CPU/ICLK系统时钟首选,CPU0/1最高1GHz/250MHz主频依托PLL1P;PLL1Q/R供给 SCI/SPI/ADC等高速外设;
3,PLL2P/Q/R专供USB、I3C、以太网 SW、LCD等独立外设,不接入系统主干ICLK,实现系统与外设时钟解耦,改外设频不影响CPU主频。
4, RA8外设时钟分通用分组(PCLA~E)+外设独立多路可选时钟,SCI/SPI/OctSPI/CANFD/USB/I3C/LCD/以太网PHY等外设可脱离PCLK分组,自由从 MOS/SOS/HOCO/MOCO/PLL1 (P/Q/R)/PLL2 (P/Q/R) 任选时钟源,每个外设配独立SEL(源选择)+DIV(分频)+REQ/SRDY切换寄存器(请求就绪握手切换,防切换毛刺)。
注意:外设时钟是有频率约束规则的,不能超出或者小于某个频率范围无法正常工作。读者可以查阅第九章节的表“Table 9.2 Clock generation circuit specifications for the internal clocks (1 of 3)”,如下图所示:

图5.2.3.2 时钟产生电路的内部时钟规范(部分截图)
从上图可以看到,SCI、SPI、CANFD等外设的最高工作频率分别为480MHz、333MHz、480MHz和480MHz。若在FSP配置中设置的外设频率超出这些最大值,系统会提示配置错误警告,导致外设无法正常工作。

浙公网安备 33010602011771号