3、GPIO与中断系统:GPIO复用功能配置、外部中断触发机制、PIE中断控制器详解
各位做电机控制的同行,今天咱们聊聊C2000的GPIO和中断系统。说实话,这两个东西是嵌入式开发的"基本功",但也是很多人容易翻车的地方。我见过不少工程师,算法写得飞起,结果GPIO配置错了,电机死活转不起来。嗯,咱们今天就把这块彻底讲透。
3.1 GPIO复用功能配置——别让引脚"打架"
C2000的GPIO引脚,说白了就是"多面手"。一个物理引脚,可能同时挂着PWM输出、CAP捕获、SPI通信、普通IO等功能。你想想看,如果不做选择,这些功能就会"打架"。
我个人习惯,拿到新板子第一件事就是打开datasheet,翻到引脚功能表。别偷懒,这一步省不了。
3.1.1 GPIO复用寄存器
每个GPIO引脚都有对应的复用选择寄存器,主要这几个:
| 寄存器 | 功能 | 位宽 |
|---|---|---|
| GPxMUX1/2 | 选择外设功能(0=GPIO,1=外设1,2=外设2,3=外设3) | 每引脚2位 |
| GPxDIR | 方向控制(0=输入,1=输出) | 每引脚1位 |
| GPxPUD | 内部上拉使能(0=使能,1=禁用) | 每引脚1位 |
| GPxQSELn | 输入量化选择(同步/采样窗口) | 每引脚2位 |
这里有个坑,我提醒一下:GPxMUX的配置必须在GPIO功能使能之前完成。我曾经在项目里先开了外设时钟,再配MUX,结果引脚死活不出波形。查了两天才发现是顺序问题。
3.1.2 实战配置示例
以F280049为例,把GPIO0配成EPWM1A输出:
// 第一步:先关外设时钟(安全操作)
ClkCfgRegs.PERCLKEN.bit.EPWM1 = 0;
// 第二步:配置GPIO复用
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 1; // 选择外设功能1(EPWM1A)
// 第三步:配置方向(外设模式下,方向由外设控制)
// 但建议显式设置为输出
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;
// 第四步:禁用内部上拉(PWM输出不需要上拉)
GpioCtrlRegs.GPAPUD.bit.GPIO0 = 1;
// 第五步:配置量化(PWM输出建议用异步模式)
GpioCtrlRegs.GPAQSEL1.bit.GPIO0 = 3; // 异步模式
// 第六步:开外设时钟
ClkCfgRegs.PERCLKEN.bit.EPWM1 = 1;
3.2 外部中断触发机制——抓住信号的"脉搏"
电机控制里,外部中断用得最多的场景就是:编码器信号捕获、霍尔传感器跳变、过流保护触发。说白了,就是需要CPU在特定事件发生时"立刻响应"。
C2000的每个GPIO都可以配置为外部中断源,但实际是通过XINT1-XINT7这7个中断通道来路由的。你想想看,32个引脚抢7个通道,怎么分配?
3.2.1 中断触发方式
| 触发方式 | 寄存器配置 | 适用场景 |
|---|---|---|
| 上升沿触发 | XINTnCR.POLARITY = 0 | 编码器Z信号、霍尔U相上升 |
| 下降沿触发 | XINTnCR.POLARITY = 1 | 霍尔V相下降、故障信号 |
| 双边沿触发 | XINTnCR.POLARITY = 2 | 霍尔传感器换相、脉冲捕获 |
| 软件触发 | XINTnCR.POLARITY = 3 | 调试用,手动模拟中断 |
我记得有一次做永磁同步电机的初始位置检测,需要捕获编码器的Z脉冲。用的就是双边沿触发,结果发现中断响应时间不稳定。后来查了手册才知道,双边沿触发模式下,硬件会插入一个同步延迟。嗯,这个细节很容易被忽略。
3.2.2 配置步骤
// 以XINT1为例,配置GPIO12为下降沿触发
// 1. 使能GPIO输入
GpioCtrlRegs.GPBDIR.bit.GPIO12 = 0; // 输入
GpioCtrlRegs.GPBPUD.bit.GPIO12 = 0; // 使能上拉
// 2. 将GPIO12映射到XINT1
InputXbarRegs.INPUT1SELECT = 12; // XINT1 <- GPIO12
// 3. 配置触发极性
XintRegs.XINT1CR.bit.POLARITY = 1; // 下降沿
// 4. 使能中断
XintRegs.XINT1CR.bit.ENABLE = 1;
// 5. 在PIE中使能XINT1
PieCtrlRegs.PIEIER1.bit.INTx4 = 1; // XINT1在PIE组1第4通道
3.3 PIE中断控制器详解——别让中断"打架"
C2000的中断系统,核心就是PIE(外设中断扩展控制器)。为什么需要它?因为C28x内核只有16个中断线(INT1-INT16),但外设可能有上百个中断源。PIE就是那个"调度员"。
说白了,PIE把96个中断源分成12组(每组8个),然后映射到INT1-INT12上。CPU只需要处理12个中断线,具体是哪个外设触发的,由PIE来告诉CPU。
3.3.1 PIE中断层次结构
3.3.2 PIE配置核心步骤
配置PIE中断,我总结了一个"四步法":
- 使能PIE模块:PieCtrlRegs.PIECTRL.bit.ENPIE = 1;
- 使能对应PIE组:PieCtrlRegs.PIEIERx.bit.INTxy = 1;
- 使能CPU中断:IER |= M_INTx;
- 全局中断使能:EINT;
这里有个细节,很多人会忽略:PIEACK应答位。CPU响应完中断后,必须手动清除PIEACK,否则该组后续的中断进不来。
3.3.3 中断优先级与嵌套
C2000的PIE中断优先级是固定的:INT1优先级最高,INT16最低。同一组内,通道1优先级高于通道8。
但在电机控制里,我建议不要轻易开中断嵌套。为什么?因为PWM中断、ADC中断、故障保护中断这些,优先级一旦乱套,时序就全崩了。
我个人习惯的做法是:
- 把最紧急的故障保护中断放在INT1(比如过流、过压)
- PWM周期中断放在INT3
- ADC转换完成中断放在INT4
- 通信中断(SCI、SPI)放在INT6以后
3.4 综合实战:霍尔传感器中断配置
咱们来个完整的例子。三相霍尔传感器,三个信号分别接GPIO12、13、14,用XINT1、XINT2、XINT3捕获,实现换相触发。
// 霍尔传感器中断配置
void Hall_Interrupt_Init(void)
{
// 1. 配置GPIO为输入
GpioCtrlRegs.GPBDIR.bit.GPIO12 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO13 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO14 = 0;
// 使能内部上拉(霍尔信号开漏输出)
GpioCtrlRegs.GPBPUD.bit.GPIO12 = 0;
GpioCtrlRegs.GPBPUD.bit.GPIO13 = 0;
GpioCtrlRegs.GPBPUD.bit.GPIO14 = 0;
// 2. 映射到XINT通道
InputXbarRegs.INPUT1SELECT = 12; // GPIO12 -> XINT1
InputXbarRegs.INPUT2SELECT = 13; // GPIO13 -> XINT2
InputXbarRegs.INPUT3SELECT = 14; // GPIO14 -> XINT3
// 3. 配置双边沿触发(霍尔换相需要捕获上升和下降)
XintRegs.XINT1CR.bit.POLARITY = 2;
XintRegs.XINT2CR.bit.POLARITY = 2;
XintRegs.XINT3CR.bit.POLARITY = 2;
// 4. 使能XINT
XintRegs.XINT1CR.bit.ENABLE = 1;
XintRegs.XINT2CR.bit.ENABLE = 1;
XintRegs.XINT3CR.bit.ENABLE = 1;
// 5. PIE配置
// XINT1在PIE组1第4通道
// XINT2在PIE组1第5通道
// XINT3在PIE组1第6通道
PieCtrlRegs.PIEIER1.bit.INTx4 = 1;
PieCtrlRegs.PIEIER1.bit.INTx5 = 1;
PieCtrlRegs.PIEIER1.bit.INTx6 = 1;
// 6. CPU中断使能
IER |= M_INT1;
// 7. 全局中断
EINT;
}
// 中断服务函数
interrupt void xint1_isr(void)
{
// 读取霍尔状态
uint16_t hall_state = (GpioDataRegs.GPBDAT.bit.GPIO12 & 0x01) |
((GpioDataRegs.GPBDAT.bit.GPIO13 & 0x01) << 1) |
((GpioDataRegs.GPBDAT.bit.GPIO14 & 0x01) << 2);
// 根据霍尔状态查表换相
Hall_Commutation(hall_state);
// 清中断标志
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}
好了,GPIO和中断系统就讲到这里。这些东西看着简单,但实际项目里80%的"疑难杂症"都出在这块。我的建议是:每配置一个中断,都写一个测试函数验证。别等到整机调试时才发现中断没触发,那时候定位问题就难了。