电子产业一站式赋能平台

PCB联盟网

搜索
查看: 16|回复: 0
收起左侧

单片机如何用C语言操作寄存器?

[复制链接]

596

主题

596

帖子

7435

积分

高级会员

Rank: 5Rank: 5

积分
7435
发表于 昨天 11:45 | 显示全部楼层 |阅读模式
关注+星标公众,不错过精彩内容直接来源| 瑞萨嵌入式小百科
C语言应用于嵌入式最大的特点就是可以直接操作寄存器,具有高效的特点。
本文结合瑞萨RA6M5单片机来给大家讲讲如何用C语言操作寄存器?1
存储器映射
讲述C语言操作寄存器之前,我们先来讲讲存储器映射。

寄存器与RAM、FLASH一样都是芯片内部的一种存储设备。那么,当我们需要访问它们的时候,我们需要知道它们的存储地址。

1
存储器映射表
如下图所示为RA6M5的存储器映射表,可以看到RA6M5芯片内部的存储器被映射到这一整块4G(0 ~0xFFFF FFFF)的地址空间中。我们还可以看到,除了寄存器和SRAM、Flash的地址空间区域以外,还存在着其他类型的地址空间区域,比如QSPI area和OSPI area。Reserved area表示的是保留区域,尚未用到。

0ckganup14n6405388033.png

0ckganup14n6405388033.png


2
存储器区域划分
存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射。如果给存储器再次分配一个地址就叫存储器重映射。

对于RA6M5(176 pin)芯片,其内部线性地址空间划分为如下区域:

表2:线性地址空间区域划分

sx0h3i3kaba6405388133.png

sx0h3i3kaba6405388133.png


表格中的“0x4000_0000~0x4018_0000-1”区域,也就是“0x4000_0000~0x4017_FFFF”区域,它映射到了绝大部分外设模块的寄存器。

3
外设基地址和外设寄存器地址
如下图所示:

siwftsx341q6405388233.png

siwftsx341q6405388233.png


图中①处为该外设的基地址,也就是IO端口的基地址。因为RA6M5的IO端口不止有一个,而是有16个端口(用PORTm表示,m=0~9,A,B),所以每一个端口都有一个基地址,每个端口的基地址都可以用图中的公式来计算出来。

图中②处为该外设寄存器的地址偏移,图中的寄存器为PCNTR1/PODR/PDR寄存器,而“Offset address:0x000”表示的是该寄存器相对于基地址的偏移量。

举例来说
当我们要读取PORT1的PCNTR1/PODR/PDR寄存器的值时,我们要先计算出该寄存器的地址为:(0x40080000+0x0020*1),然后再把该地址值转换为C语言的指针:(uint32_t*)(0x40080000+0x0020*1),最后再取值即可读出该寄存器的值:*((uint32_t*)(0x40080000+0x0020*1))。


需要注意的是,每一种外设模块下面都会有多个寄存器,每个寄存器都有特定的功能。对于一些功能相对复杂的外设来说,它们的寄存器数量可以达到十几个甚至几十个。以IOPORT1为例,它的基地址为:0x40080020,下表则展示了它部分的寄存器名称、寄存器地址以及相对于基地址的偏移。

表3:IOPORT1寄存器及其地址

5omls24c4pu6405388333.png

5omls24c4pu6405388333.png


注解
注:由于基地址不同,上述表格未包含PmnPFS等这些也和IOPORT1有关的寄存器。


4
外设寄存器
下图所示为外设寄存器的一般格式。

wcguxyb2z0c6405388433.png

wcguxyb2z0c6405388433.png


说明:

寄存器名称。
外设模块基地址及其寄存器偏移地址。
寄存器位表格。32位MCU的寄存器大小一般为32位(bit),占四个字节。“Bit position”为位号,指示该位处于该寄存器中的位置;“Bit field”为位域,一般不同的位域有不同的作用;“Value after reset”为复位值,指示该位的复位值。
位域功能说明。这部分为对每一个位域的功能的详细说明。
[/ol]
2
如何用C语言操作寄存器
1
C语言对寄存器的封装
前面的所有关于存储器映射的内容,最终都是为大家更好地理解如何用C语言控制读写外设寄存器做准备,因此此处是本章的重点内容。

1.1
外设模块基地址定义
在编程上为了方便理解和记忆,我们要把外设模块基地址以相应的宏定义起来,外设基地址都以它们的名字作为宏名的组成部分。以下是IO端口外设基地址的宏定义。

列表1:代码清单3?1 IOPORT外设基地址宏定义
左右滑动查看完整内容
  • /* 外设基地址 */#define R_PORT0_BASE 0x40080000#define R_PORT1_BASE 0x40080020#define R_PORT2_BASE 0x40080040#define R_PORT3_BASE 0x40080060#define R_PORT4_BASE 0x40080080#define R_PORT5_BASE 0x400800A0#define R_PORT6_BASE 0x400800C0#define R_PORT7_BASE 0x400800E0#define R_PORT8_BASE 0x40080100#define R_PORT9_BASE 0x40080120#define R_PORT10_BASE 0x40080140#define R_PORT11_BASE 0x40080160#define R_PFS_BASE 0x40080800#define R_PMISC_BASE 0x40080D00
    1.2
    寄存器结构体定义
    由于寄存器的数量是非常之多的,如果每个寄存器都用像*((uint32_t*)(0x40080000+0x0020*1))这样的方式去访问的话,会显得很繁琐、很麻烦。为了更方便地访问寄存器,我们会借助C语言结构体的特性去定义寄存器和寄存器位域,这是通用的做法。

    列表2:代码清单3?2使用结构体封装外设寄存器
    左右滑动查看完整内容
  • // 注:关于输入输出端口的声明/* C 语言: IO definitions (access restrictions to peripheral registers) *///#define __I volatile const /*!,→permissions *///#define __O volatile /*!,→permissions *///#define __IO volatile /*!,→permissions */
    /* 下面的宏定义用于结构体成员 *//* following defines should be used for structure members *///#define __IM volatile const /*! Defines 'read only'?,→structure member permissions *///#define __OM volatile /*! Defines 'write only'?,→structure member permissions *///#define __IOM volatile /*! Defines 'read / write'?,→structure member permissions */
    //typedef unsigned char uint8_t;//typedef unsigned short int uint16_t; /* 无符号 16 位整型变量 *///typedef unsigned int uint32_t; /* 无符号 32 位整型变量 */
    /*** @brief I/O Ports (R_PORT0)*/typedefstruct /*!,→Structure */{union{union{__IOM uint32_t PCNTR1; /*!,→Register 1 */
    struct{__IOM uint32_t PDR : 16; /*!Pmn 方向)*/__IOM uint32_t PODR : 16; /*!Pmn 输出数据)*/} PCNTR1_b;};/* ... 代码过长省略 ... */};
    union{union{__IM uint32_t PCNTR2; /*!,→Register 2 */
    struct{__IM uint32_t PIDR : 16; /*!Pmn 输入数据)*/__IM uint32_t EIDR : 16; /*!(引脚 Pmn 事件输入数据)*/} PCNTR2_b;};
    /* ... 代码过长省略 ... */};
    union{
    union{__OM uint32_t PCNTR3; /*!,→Register 3 */struct{__OM uint32_t POSR : 16; /*!Pmn 输出置位)*/__OM uint32_t PORR : 16; /*!Pmn 输出复位)*/} PCNTR3_b;};
    /* ... 代码过长省略 ... */};
    union{union{__IOM uint32_t PCNTR4; /*!→Register 4 */
    struct{__IOM uint32_t EOSR : 16; /*!(引脚 Pmn 事件输出置位)*/__IOM uint32_t EORR : 16; /*!→Reset(引脚 Pmn 事件输出复位)*/} PCNTR4_b;};
    /* ... 代码过长省略 ... */};} R_PORT0_Type; /*!
    1.3
    外设模块寄存器定义
    我们在上一步已经定义好了R_PORT0_Type类型的结构体,它包含了IOPORT的寄存器定义。接下来使用宏定义来表示结构体指针,指针指向IOPORT外设的每个端口的寄存器首地址。

    列表3:代码清单3?3寄存器定义
    左右滑动查看完整内容
  • #define R_PORT0 ((R_PORT0_Type *) R_PORT0_BASE)#define R_PORT1 ((R_PORT0_Type *) R_PORT1_BASE)#define R_PORT2 ((R_PORT0_Type *) R_PORT2_BASE)#define R_PORT3 ((R_PORT0_Type *) R_PORT3_BASE)#define R_PORT4 ((R_PORT0_Type *) R_PORT4_BASE)#define R_PORT5 ((R_PORT0_Type *) R_PORT5_BASE)#define R_PORT6 ((R_PORT0_Type *) R_PORT6_BASE)#define R_PORT7 ((R_PORT0_Type *) R_PORT7_BASE)#define R_PORT8 ((R_PORT0_Type *) R_PORT8_BASE)#define R_PORT9 ((R_PORT0_Type *) R_PORT9_BASE)#define R_PORT10 ((R_PORT0_Type *) R_PORT10_BASE)
    这样便大功告成了,我们就可以使用这些宏来访问各个IO端口的每一个寄存器了。

    2
    修改寄存器操作的本质:读-改-写
    有了以上的对IOPORT这个外设模块的寄存器的定义,我们便完成了“C语言对寄存器的封装”这个步骤,接下来我们便可以使用C语言对寄存器进行各种操作了。

    对寄存器进行操作可以是忽略寄存器原本的值,而直接覆盖写入新的值;但是更为一般的操作是根据原本的寄存器值进行修改,即:先读出寄存器原本的值,然后修改该值,最后重新写入到寄存器里面,让新的值生效。

    接下来将介绍修改寄存器的几种通用方法。

    2.1
    清零寄存器上的某N个位
    使用C语言的按位与“&”运算符可以将位进行清零。

    列表4:代码清单3?4位清零:按位与&
    左右滑动查看完整内容
  • //清零某个位R_PORT0->PODR &= ~(1u0); //清零 PODR 寄存器的第 0 位R_PORT0->PODR &= ~(1u6); //清零 PODR 寄存器的第 6 位
    //清零多个位R_PORT0->PODR &= ~(3u0); //清零 PODR 寄存器的第 0,1 位R_PORT0->PODR &= ~(3u6); //清零 PODR 寄存器的第 6,7 位
    2.2
    对寄存器上的某N个位进行置位
    使用C语言的按位或“|”运算符可以将位进行置一。

    列表5:代码清单3?5位置位:按位或|
    左右滑动查看完整内容
  • //置位某个位R_PORT0->PODR |= 1u0; //PODR 寄存器的第 0 位置 1R_PORT0->PODR |= 1u6; //PODR 寄存器的第 6 位置 1
    //置位多个位R_PORT0->PODR |= 3u0; //PODR 寄存器的第 0,1 位置 1R_PORT0->PODR |= 3u6; //PODR 寄存器的第 6,7 位置 1
    2.3
    对寄存器上的某N个位进行取反
    使用C语言的按位异或“^”运算符可以将位进行取反。

    列表6:代码清单3?6位取反:按位异或^
    左右滑动查看完整内容
  • //取反某个位R_PORT0->PODR ^= 1u0; //取反 PODR 寄存器的第 0 位R_PORT0->PODR ^= 1u6; //取反 PODR 寄存器的第 6 位
    //取反多个位R_PORT0->PODR ^= 3u0; //取反 PODR 寄存器的第 0,1 位R_PORT0->PODR ^= 3u6; //取反 PODR 寄存器的第 6,7 位

    t5ml2be24wu6405388533.jpg

    t5ml2be24wu6405388533.jpg

    Cortex-M85(RA8)单片机中 TrustZone 的含义和作用是什么?

    se45esypsd06405388634.jpg

    se45esypsd06405388634.jpg

    MCU使用编程软件设置安全边界

    kt2e44zggwp6405388734.jpg

    kt2e44zggwp6405388734.jpg

    双核处理器BOOT启动流程
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表