离大谱,没 commit 就回滚了,居然得重写
原本,没有怎么啰嗦的,没想到,第三次写,就越写越啰嗦了,诸位见谅
# 说明
本文硬件基于 LaunchXL-F28379D 和 逻辑分析仪,软件基于 CCS 10.4 版本, 帮助文档为 C2000 的例程和官方文档
在此说明,这一系列的文章主要是记录我学习思考的过程,可能会啰里啰唆
个人观点,授人以鱼不如授人以渔,最后会给出完整的代码和工程的链接
# 逻辑框图
一般来说,一个处理器要配置引脚都会有两种方式:寄存器和库函数。
但无论哪一种都需要去理解硬件的架构,虽然这幅框图有些复杂,但是大致的可以看出,下半部分是用于配置输出模式的。从寄存器的名称 也可以看出一些寄存器是用于控制输出值的。例如, GPySET
、 GPyDAT
等。
但是要知道如何配置,还是需要去阅读文档的寄存器部分和其他部分的讲解的注意事项
对应文档地址链接,可惜转跳不到对应章节
# 第一步: GPIO 工作模式配置
在文档的 8.2 节中可以找到 GPIO 各种模式配置的概要和说明
从文档中可以得知,要将 引脚设置为 通用输入输出模式,需要配置 GPyMUXn
和 GPyGMUXn
.
但这里并没有给出 个寄存器 需要如何配置。不过文档最后一句给出了,默认情况下,引脚处于 通用输入输出模式
翻阅文档后,找到了 复用表
可以看出 当 GPyMUXn
为 0 时, GPyGMUXn
的值与模式配置无关.
所以我们得到了第一步,将 GPyMUXn
设置为 0
但,表格中 GPyMUXn
的 y
和 n
分别代表什么呢,还是得去查找一下寄存器表
在找寄存器表的时候,发现 GPIO 的寄存器分为两大类, GpioCtrlReg
和 GpioDataReg
, 分别用于配置 GPIO 和 控制引脚输出值
很显然 GPyMUXn
属于控制寄存器,所以很轻松的就找到了 对应的寄存器
从图中我们可以知道, GPyMUXn
的 y
代表 GPIO 的分组,不同的芯片引脚数不同,具体多少个分组得看芯片
n
则是 1 或 2, 看地址差值为 2 , 然后从之前的复用表中得到一个引脚占 2 个数据位,而 TMS320 是地址总线和数据总线的位宽为 16, 所以得 2*16/2 = 16, 一个 GPxMUXn
控制 16 个引脚。这只是写文章的时候突然想到的,事实上我们可以直接看后面的说明,GPAMUX1 控制 0~15, 一看就知道是 16 个 (捂脸)
事实上知道这些就可以去配置 GPIO 的工作模式了,但是问了预防万一,还是去看一眼寄存器表为好
事实上,也没有什么要注意的.
至此,我们就得到了 GPIO 配置的第一步:将 GpioCtrlReg.GPyMUxn
的对应引脚设置为 0.
# 第二步:GPIO 输入输出配置
继续阅读文档,会发现 2, 3 步基本没什么用 (对输出模式而言), ODR 寄存器是在输入模式时配置的,且默认是关闭的,所以直接到第 4 步,也是 8.2 节最后有用的地方了,配置 引脚 的输入输出方向
从文档中可以知道,默认情况下 GPIO 的引脚 是输入的。并且在改变 引脚 为输出模式时,建议先写入要输出的值到 数据寄存器中,输出模式下默认输出的电平都为低电平.
个人觉得官方,让我们先设置输出值是为了防止,配置为输出模式时默认的低电平导致,外部设备出现奇怪的反应。咱们现在是空载所以输出啥电平没啥区别.
然后又到了翻看寄存器的时候了,还是去之前的 GpioCtrReg
寄存器表中查找
不得不说 TI 的 Go 和 返回总表 做的是真的让人舒心
从寄存器数码中可以得知 当 GPyDIR 为 1 时,引脚为输出模式
至此,我们得到了第二步: GpioCtrlReg.GPADIR
对应的引脚置位为 1
# 第三步:输出模式配置
到了这里,8.2 节没有继续说明和单纯的输出模式相关的信息了,但是根据我们之前的配置经验可以知道 还有输出模式需要配置
输出模式分为开漏和推挽输出,问题是这个寄存器叫啥呢,这就需要从框图中查看了,从框图中可以看出 这是一个名为 GPyODR
的寄存器。这就要再次开始翻找 GpioCtrlReg
的寄存器列表
由于 TI 的人性化设计,还是很快就能找到的,从文档中可知 当 GPyODR 为 0 的时候,为正常输出.
至此我们就得到了配置引脚为输出的第三步:将 GpioCtrlReg.GPyODR
对应的 引脚位复位
# 小结
到此我们就知道了,引脚配置为输出模式所需要的步骤:
- 配置引脚为 通用输入输出模式
- 设置引脚输出值
- 配置引脚为 输出模式
- 配置引脚为 推挽输出
接下来用两种方式来实现 GPIO 配置
这里假设 我们要将 GPIO24 配置为输出模式并输出 5 个方波
不过需要注意的是导入的空文件工程需要不同可能会导致 库函数不同,直接操作寄存的方式也不同
从 C2000Ware_4_01_00_00\driverlib
路径下导入的工程,寄存器操作会麻烦很多,但是库函数资料多
而从 C2000Ware_4_01_00_00\device_support
路径下导入则是寄存器操作更加贴近手册、便于操作,库函数体系自成一家
简单查看一下源文件就看可以知道两个工程的风格相差甚远 (说实话,两种库的底层构建都给我极大的启发)
下面我会用两个库分别实现寄存器版本和库函数版本的操作
# device_support
# 寄存器版本
这个当初我是直接翻看,源码看明白的,大致讲一下思路
文档中给出了两个寄存器的名称,然后我直接输进去然后出现自动补全,然后就没有然后了
不过,在输入寄存器名称的时候,我跳转到源码查看了一下
找到了对应的结构体,翻阅了一下成员,发现与文档相符然后就直接操作了, GpioDataReg
也是同样的道理
根据上面的小结我们可以得到一下代码
/** | |
* @b 寄存器配制引脚 | |
*/ | |
void Reg_GpioCfg(void) | |
{ | |
EALLOW; | |
GpioDataRegs.GPADAT.bit.GPIO24 = 0; // 预设 pin 输出电平 | |
GpioCtrlRegs.GPAMUX2.bit.GPIO24 = 0; // 设置 pin 为通用输入输出模式 | |
GpioCtrlRegs.GPADIR.bit.GPIO24 = 1; // 设置 pin 为输出 | |
GpioCtrlRegs.GPAODR.bit.GPIO24 = 0; // 设置 pin 为推挽输出 | |
EDIS; | |
} |
EALLOW
是用于解除写保护, EDIS
是用于恢复写保护的,这个在寄存器表中可以得到
接下去我们要实 IO24 输出 5 个高电平,这个就比较简单啦,无非就是写个随机延时 + 上 for 循环,用 GPADAT
或者 GPASET
和 GPACLEAR
以及 GPATOGGLE
来操作
这里,我选择用 GPATOGGLE
直接操作,能少些好多行代码
/** | |
* @b 测试函数:产生 5 个方波 | |
*/ | |
void test_function(void) | |
{ | |
for (volatile int i = 0; i < 10; i++) | |
{ | |
GpioDataRegs.GPATOGGLE.bit.GPIO24 = 1; // 翻转电平 | |
// 随机延时 | |
int j = 1000; | |
while (j--) | |
; | |
} | |
} |
# 库函数版本
由于没找到相关的文档,就只能直接读源码了
直接翻阅 Gpio.c
文件,就可以看到和 GPIO 配置相关的代码了
一顿翻看函数注释后就可以知道 GPIO_SetupPinMux
和 GPIO_SetupPinOptions
可以完成对 引脚的配置GPIO_SetupPinMux
是用于 将 引脚设置位 通用 I/O 模式, GPIO_SetupPinOptions
则是用于选择 输入输出和输出模式
由此可以得到最终代码
/** | |
* @b 函数配置 引脚 | |
*/ | |
void Fun_GpioCfg(void) | |
{ | |
GPIO_SetupPinMux(24, GPIO_MUX_CPU1, 0); // 31 脚配置为 GPIO | |
GPIO_SetupPinOptions(24, GPIO_OUTPUT, GPIO_PULLUP); // 31 脚配置为 推挽输出 | |
} |
# driverlib
由于这个的寄存器版本,会涉及到阅读库函数,所以先讲库函数
# 库函数版本
翻阅文档在 8.9.4 小结可以看到对应的 操作函数
当然也可以去官网下载 F2837xD_DriverLib_Users_Guide.pdf
, 这个文档 分模块的介绍了各个 API 和该模块对应的结构体
或者直接查看 device.h
头文件中包含的 gpio.h
文件,里面对各个函数的用途及其参数进行了详细的说明
途径很多,就我的习惯而言,会先从文档中获取相关函数,然后再去代码注解中去配置函数如何使用,等实现效果后再回头分析 这个函数的底层的实现
所以,这我也按这种方式介绍.
首先,第一步需要操作 GPyMUXn
, 从文档的 8.9.4 节可知,需要查看 GPIO_setPinConfig
,
查看源码得到,这个函数只需要 将要配置的引脚 的引脚号传入即可 GPIO_setPinConfig(24);
第二步,需要操作设置 引脚要输出 的值,我想输出 低电平,默认就是低电平,所以不需要配置
不过需要一开始输出高电平则,从文档中可知 可以使用 GPIO_writePin
和 GPIO_setPortPins
来将电平拉高
查看源码注释可知配置为高电平的方式 GPIO_writePin(24, 1)
或 GPIO_setPortPins(GPIO_PORT_A, (uint32_t)1 << 24)
第三步,将 引脚 配置为输出模式,从文档得 需要操作 GPIO_setDirectionMode
查看源码可知,函数需要配置两个参数:引脚号和输出模式。从注释中看可以知道,输出模式有宏定义,所以可以直接套用宏定义
得到源码 GPIO_setDirectionMode(24, GPIO_DIR_MODE_OUT);
最后一步,配置引脚的输出模式,从文档中可知,需要操作 GPIO_setPadConfig
查看源码后返现,这个函数需要两个参数:引脚号和输入 | 输出模式引脚状态
有意思的是,这个函数 可以进行 输出模式的推挽开漏配置 和 输入模式的上拉和下拉配置.
通过 宏来进行区分,当前引脚需要配置什么.(真就没有防护)
得到代码 GPIO_setPadConfig(24, GPIO_PIN_TYPE_STD);
最后得到对应的源码如下
/** | |
* @b 初始化 pin 为输出模式 | |
*/ | |
void Fun_setupOutputPin(uint32_t pin) | |
{ | |
GPIO_setPinConfig(pin); // 配置为 通用输入输出模式 | |
GPIO_setDirectionMode(pin, GPIO_DIR_MODE_OUT); // 配置为 输出模式 | |
GPIO_setPadConfig(pin, GPIO_PIN_TYPE_STD); // 配置为 推挽输出 | |
} |
# 寄存器版本
这个库里的寄存器就没有特定的结构体了,有的是大量的枚举和强制转换,比较考验指针操作和对寄存器的熟悉程度
所以我们就需要去看一下库函数是如何实现的
从源码中可以看出,这的寄存器操作是通过 GPIO 的基地址强转为 指针,通过访问数组的方式来实现修改值
相关的宏 分布在 gpio.h
和 hw_gpio.h
以及 hw_memmap.h
中定义,具体情况自行查看
这里就直接贴出源码,因为纯粹的翻看寄存器 和 算内存偏差实在是没什么可以说的了
注释里已经写的很清楚了
/** | |
* @b 初始化 pin 为输出模式 | |
*/ | |
void Reg_setupOutputPin(uint32_t pin) | |
{ | |
uint32_t pinMask = 0; // 其他 1 位寄存器掩码 | |
uint32_t muxPinMask = 0; // MUX 寄存器掩码 | |
volatile uint32_t *gpioCtrlReg = NULL; // 指向 gpioCtrlReg 的 指针 | |
// 定位 GPIOA 所有寄存器的首地址,GPIO_CTRL_REGS_STEP 是寄存器步长,一组 GPIO 寄存器的长度 | |
gpioCtrlReg = (uint32_t*) ((uintptr_t) GPIOCTRL_BASE | |
+ (pin / 32) * GPIO_CTRL_REGS_STEP); | |
// 要被置位的引脚掩码计算 | |
pinMask = (uint32_t) 1U << (pin % 32); | |
// 一个 MUX 管理 16 个引脚,所以 对 16 取余数 | |
// 乘 2 是因为一个引脚对应两个位,每过一个引脚要 移两位 | |
muxPinMask = ((uint32_t) 3U << ((pin % 16) * 2)); | |
EALLOW; | |
// 设置为输出模式 | |
gpioCtrlReg[GPIO_GPxDIR_INDEX] |= pinMask; | |
// 设置为推挽输出 | |
gpioCtrlReg[GPIO_GPxODR_INDEX] &= ~pinMask; | |
// 设置位 通用输出模式 | |
if ((int)(pin % 32) - 16 >= 0) // 判断引脚属于 MUX1 还是 MUX2 | |
{// 为什么 + 1 而不是 +2 我也没弄懂,挠头,等弄懂再更新 | |
gpioCtrlReg[GPIO_GPxMUX_INDEX + 1] &= ~muxPinMask; | |
} | |
else | |
{ | |
gpioCtrlReg[GPIO_GPxMUX_INDEX] &= ~muxPinMask; | |
} | |
EDIS; | |
} |
# 代码实现
# device_support
使用 C2000Ware_4_01_00_00\device_support
空工程 导入 实现的源码
#include "F28x_Project.h" | |
void Fun_GpioCfg(void); | |
void Reg_GpioCfg(void); | |
void test_function(void); | |
void main(void) | |
{ | |
InitSysCtrl(); // 初始化系统配置,(例如配置时钟,关闭看门狗...) | |
InitGpio(); // 初始化 GPIO 模块 | |
// 关中断 | |
DINT; | |
InitPieCtrl(); // 初始化中断控制器 | |
InitPieVectTable(); // 初始化中断向量表 | |
// 开中断 | |
EINT; | |
// 初始化 GPIO, 二选一即可 | |
Reg_GpioCfg(); | |
//Fun_GpioCfg(); | |
test_function(); | |
while (1) | |
{ | |
} | |
} | |
/** | |
* @b 测试函数:产生 5 个方波 | |
*/ | |
void test_function(void) | |
{ | |
for (volatile int i = 0; i < 10; i++) | |
{ | |
GpioDataRegs.GPATOGGLE.bit.GPIO24 = 1; | |
int j = 1000; | |
while (j--) | |
; | |
} | |
} | |
/** | |
* @b 函数配置 引脚 | |
*/ | |
void Fun_GpioCfg(void) | |
{ | |
GPIO_SetupPinMux(24, GPIO_MUX_CPU1, 0); // 31 脚配置为 GPIO | |
GPIO_SetupPinOptions(24, GPIO_OUTPUT, GPIO_PULLUP); // 31 脚配置为 推挽输出 | |
} | |
/** | |
* @b 寄存器配制引脚 | |
*/ | |
void Reg_GpioCfg(void) | |
{ | |
EALLOW; | |
GpioCtrlRegs.GPAMUX2.bit.GPIO24 = 0; | |
GpioCtrlRegs.GPADIR.bit.GPIO24 = 1; | |
GpioCtrlRegs.GPAODR.bit.GPIO24 = 0; | |
EDIS; | |
} |
# driverlib
#include "driverlib.h" | |
#include "device.h" | |
void Reg_setupOutputPin(uint32_t pin); | |
void Fun_setupOutputPin(uint32_t pin); | |
void test_function(void); | |
// | |
// Main | |
// | |
void main(void) | |
{ | |
// 系统初始化 | |
Device_init(); | |
// Reg_setupOutputPin(24); | |
Fun_setupOutputPin(24); | |
// 测试函数 | |
test_function(); | |
while (1) | |
; | |
} | |
/** | |
* @b 初始化 pin 为输出模式 | |
*/ | |
void Fun_setupOutputPin(uint32_t pin) | |
{ | |
GPIO_setPinConfig(pin); // 配置为 通用输入输出模式 | |
GPIO_setDirectionMode(pin, GPIO_DIR_MODE_OUT); // 配置为 输出模式 | |
GPIO_setPadConfig(pin, GPIO_PIN_TYPE_STD); // 配置为 推挽输出 | |
} | |
/** | |
* @b 初始化 pin 为输出模式 | |
*/ | |
void Reg_setupOutputPin(uint32_t pin) | |
{ | |
uint32_t pinMask = 0; // 其他 1 位寄存器掩码 | |
uint32_t muxPinMask = 0; // MUX 寄存器掩码 | |
volatile uint32_t *gpioCtrlReg = NULL; // 指向 gpioCtrlReg 的 指针 | |
// 定位 GPIOA 所有寄存器的首地址,GPIO_CTRL_REGS_STEP 是寄存器步长,一组 GPIO 寄存器的长度 | |
gpioCtrlReg = (uint32_t*) ((uintptr_t) GPIOCTRL_BASE | |
+ (pin / 32) * GPIO_CTRL_REGS_STEP); | |
// 要被置位的引脚掩码计算 | |
pinMask = (uint32_t) 1U << (pin % 32); | |
// 一个 MUX 管理 16 个引脚,所以 对 16 取余数 | |
// 乘 2 是因为一个引脚对应两个位,每过一个引脚要 移两位 | |
muxPinMask = ((uint32_t) 3U << ((pin % 16) * 2)); | |
EALLOW; | |
// 设置为输出模式 | |
gpioCtrlReg[GPIO_GPxDIR_INDEX] |= pinMask; | |
// 设置为推挽输出 | |
gpioCtrlReg[GPIO_GPxODR_INDEX] &= ~pinMask; | |
// 设置位 通用输出模式 | |
if ((int)(pin % 32) - 16 >= 0) // 判断引脚属于 MUX1 还是 MUX2 | |
{// 为什么 + 1 而不是 +2 我也没弄懂,挠头,等弄懂再更新 | |
gpioCtrlReg[GPIO_GPxMUX_INDEX + 1] &= ~muxPinMask; | |
} | |
else | |
{ | |
gpioCtrlReg[GPIO_GPxMUX_INDEX] &= ~muxPinMask; | |
} | |
EDIS; | |
} | |
/** | |
* @b 测试函数:产生 6 个方波 | |
*/ | |
void test_function(void) | |
{ | |
uint32_t pin = 24; | |
uint32_t pinMask = 0; | |
volatile uint32_t *gpioDataRegs = NULL; | |
gpioDataRegs = (uint32_t*) ((uintptr_t) GPIODATA_BASE + (pin / 32) * 0x8); | |
pinMask = (uint32_t) 1U << (pin % 32); | |
for (volatile int i = 0; i < 12; i++) | |
{ | |
gpioDataRegs[GPIO_GPxTOGGLE_INDEX] |= pinMask; | |
int j = 1000; | |
while (j--) | |
; | |
} | |
} |
# 现象
产生 6 个方波
# 工程链接
由于 device_support 移动后依赖有点难修复,所以工程文件中只有 driverlib 的工程链接
🐱(⬅️点我)
大道五十,天衍四十九,人遁其一!