3.4
如何用C语言操作寄存器
3.4.1
C语言对寄存器的封装
前面的所有关于存储器映射的内容,最终都是为大家更好地理解如何用C语言控制读写外设寄存器做准备,因此此处是本章的重点内容。
3.4.1.1
外设模块基地址定义
在编程上为了方便理解和记忆,我们要把外设模块基地址以相应的宏定义起来,外设基地址都以它们的名字作为宏名的组成部分。以下是IO端口外设基地址的宏定义。
列表1:代码清单3‑1 IOPORT外设基地址宏定义
左右滑动查看完整内容
/* 外设基地址 */ #defineR_PORT0_BASE 0x40080000 #defineR_PORT1_BASE 0x40080020 #defineR_PORT2_BASE 0x40080040 #defineR_PORT3_BASE 0x40080060 #defineR_PORT4_BASE 0x40080080 #defineR_PORT5_BASE 0x400800A0 #defineR_PORT6_BASE 0x400800C0 #defineR_PORT7_BASE 0x400800E0 #defineR_PORT8_BASE 0x40080100 #defineR_PORT9_BASE 0x40080120 #defineR_PORT10_BASE 0x40080140 #defineR_PORT11_BASE 0x40080160 #defineR_PFS_BASE 0x40080800 #defineR_PMISC_BASE 0x40080D00
3.4.1.2
寄存器结构体定义
由于寄存器的数量是非常之多的,如果每个寄存器都用像*((uint32_t*)(0x40080000+0x0020*1))这样的方式去访问的话,会显得很繁琐、很麻烦。为了更方便地访问寄存器,我们会借助C语言结构体的特性去定义寄存器和寄存器位域,这是通用的做法。
列表2:代码清单3‑2使用结构体封装外设寄存器
左右滑动查看完整内容
// 注:关于输入输出端口的声明 /* C 语言: IO definitions (access restrictions to peripheral registers) */ //#define __I volatile const /*!< Defines 'read only'␣ , →permissions */ //#define __O volatile /*!< Defines 'write only'␣ , →permissions */ //#define __IO volatile /*!< Defines 'read / write'␣ , →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 /*!< (@ 0x40040000) R_PORT0␣ , →Structure */ { union { union { __IOM uint32_t PCNTR1; /*!< (@ 0x00000000) Port Control␣ , →Register 1 */ struct { __IOM uint32_t PDR : 16; /*!< [15..0] Pmn Direction(引脚 Pmn 方向)*/ __IOM uint32_t PODR : 16; /*!< [31..16] Pmn Output Data(引脚 Pmn 输出数据)*/ } PCNTR1_b; }; /* ... 代码过长省略 ... */ }; union { union { __IM uint32_t PCNTR2; /*!< (@ 0x00000004) Port Control␣ , →Register 2 */ struct { __IM uint32_t PIDR : 16; /*!< [15..0] Pmn Input Data(引脚 Pmn 输入数据)*/ __IM uint32_t EIDR : 16; /*!< [31..16] Pmn Event Input Data (引脚 Pmn 事件输入数据)*/ } PCNTR2_b; }; /* ... 代码过长省略 ... */ }; union { union { __OM uint32_t PCNTR3; /*!< (@ 0x00000008) Port Control␣ , →Register 3 */ struct { __OM uint32_t POSR : 16; /*!< [15..0] Pmn Output Set(引脚 Pmn 输出置位)*/ __OM uint32_t PORR : 16; /*!< [31..16] Pmn Output Reset(引脚 Pmn 输出复位)*/ } PCNTR3_b; }; /* ... 代码过长省略 ... */ }; union { union { __IOM uint32_t PCNTR4; /*!< (@ 0x0000000C) Port Control␣, →Register 4 */ struct { __IOM uint32_t EOSR : 16; /*!< [15..0] Pmn Event Output Set (引脚 Pmn 事件输出置位)*/ __IOM uint32_t EORR : 16; /*!< [31..16] Pmn Event Output␣ →Reset(引脚 Pmn 事件输出复位)*/ } PCNTR4_b; }; /* ... 代码过长省略 ... */ }; } R_PORT0_Type; /*!< Size = 16 (0x10) */
3.4.1.3
外设模块寄存器定义
我们在上一步已经定义好了R_PORT0_Type类型的结构体,它包含了IOPORT的寄存器定义。接下来使用宏定义来表示结构体指针,指针指向IOPORT外设的每个端口的寄存器首地址。
列表3:代码清单3‑3寄存器定义
#defineR_PORT0 ((R_PORT0_Type *) R_PORT0_BASE) #defineR_PORT1 ((R_PORT0_Type *) R_PORT1_BASE) #defineR_PORT2 ((R_PORT0_Type *) R_PORT2_BASE) #defineR_PORT3 ((R_PORT0_Type *) R_PORT3_BASE) #defineR_PORT4 ((R_PORT0_Type *) R_PORT4_BASE) #defineR_PORT5 ((R_PORT0_Type *) R_PORT5_BASE) #defineR_PORT6 ((R_PORT0_Type *) R_PORT6_BASE) #defineR_PORT7 ((R_PORT0_Type *) R_PORT7_BASE) #defineR_PORT8 ((R_PORT0_Type *) R_PORT8_BASE) #defineR_PORT9 ((R_PORT0_Type *) R_PORT9_BASE) #defineR_PORT10 ((R_PORT0_Type *) R_PORT10_BASE)
这样便大功告成了,我们就可以使用这些宏来访问各个IO端口的每一个寄存器了。
3.4.2
修改寄存器操作的本质:读-改-写
有了以上的对IOPORT这个外设模块的寄存器的定义,我们便完成了“C语言对寄存器的封装”这个步骤,接下来我们便可以使用C语言对寄存器进行各种操作了。
对寄存器进行操作可以是忽略寄存器原本的值,而直接覆盖写入新的值;但是更为一般的操作是根据原本的寄存器值进行修改,即:先读出寄存器原本的值,然后修改该值,最后重新写入到寄存器里面,让新的值生效。
接下来将介绍修改寄存器的几种通用方法。
3.4.2.1
清零寄存器上的某N个位
使用C语言的按位与“&”运算符可以将位进行清零。
列表4:代码清单3‑4位清零:按位与&
//清零某个位 R_PORT0->PODR &= ~(1u<<0); //清零 PODR 寄存器的第 0 位 R_PORT0->PODR &= ~(1u<<6); //清零 PODR 寄存器的第 6 位 //清零多个位 R_PORT0->PODR &= ~(3u<<0); //清零 PODR 寄存器的第 0,1 位 R_PORT0->PODR &= ~(3u<<6); //清零 PODR 寄存器的第 6,7 位
3.4.2.2
对寄存器上的某N个位进行置位
使用C语言的按位或“|”运算符可以将位进行置一。
列表5:代码清单3‑5位置位:按位或|
//置位某个位 R_PORT0->PODR |= 1u<<0; //PODR 寄存器的第 0 位置 1 R_PORT0->PODR |= 1u<<6; //PODR 寄存器的第 6 位置 1 //置位多个位 R_PORT0->PODR |= 3u<<0; //PODR 寄存器的第 0,1 位置 1 R_PORT0->PODR |= 3u<<6; //PODR 寄存器的第 6,7 位置 1
3.4.2.3
对寄存器上的某N个位进行取反
使用C语言的按位异或“^”运算符可以将位进行取反。
列表6:代码清单3‑6位取反:按位异或^
//取反某个位 R_PORT0->PODR ^= 1u<<0; //取反 PODR 寄存器的第 0 位 R_PORT0->PODR ^= 1u<<6; //取反 PODR 寄存器的第 6 位 //取反多个位 R_PORT0->PODR ^= 3u<<0; //取反 PODR 寄存器的第 0,1 位 R_PORT0->PODR ^= 3u<<6; //取反 PODR 寄存器的第 6,7 位