大家好,我是左工,在上一节Aurix英飞凌TC334芯片ADC采样模块配置中我们已经运用ADC模块实现了测试板的对模拟量的采集。今天我们来聊聊嵌入式控制中非常非常关键的捕获比较模块(Capture/Compare Unit,CCU)模块。

前言

CCU模块是集成在定时器模块中的核心外设,核心用于精确测量外部信号(捕获)和生成 PWM(比较)。捕获模式(Capture Mode)的原理是:检测外部信号的上升沿 / 下降沿 / 双边沿,在事件发生时锁存定时器计数值(时间戳),主要用于测量脉宽、频率、周期(如编码器、传感器信号)。比较模式(Compare Mode)工作原理是:定时器计数值与预设比较值匹配时,触发中断、翻转输出或生成 PWM,用于精确时序控制与功率器件驱动。我们今天主要使用CCU模块的比较模式调制不同占空比的PWM,从而实现对有刷电机的控制。


一、代码编写

利用文章“搭建英飞凌Aurix系列TC334芯片开源免费开发工具链”中所描述的方法创建一个名为“5_MotorControl”的工程,如下图所示。
在这里插入图片描述
工程创建完成后,我们会在导引栏中看见如下文件和文件夹。其中文件“Cpu0_Main.c”就是我们本次需要操作的文件。
在这里插入图片描述
我们双击打开该文件,找到主函数“core0_main”,TC334运行后,将首先运行该函数。我们可以看见该函数里面有一个while循环。我们将把代码写在这个while循环附近。
在这里插入图片描述
用如下代码替换上图中被红色框标出的while循环。

    GPTimer_init();
    CAN_init();
    Motor_init();
    uint8 tx_buf[8]; // 发送缓存
    while(1)
    {
        if(TimeCount>1)
        {
            TimeCount = 0;
            Motor_ctl(rx_buf[0],10);//电机控制
            tx_buf[0] = rx_buf[0];
            CAN_send(0x123,tx_buf,8); // 报文发送
        }
    }

添加完成后,我们本次实验对主函数的所有操作就结束了。下面我们来添加一些必要头文件。

#include "IfxGpt12.h"
#include "IfxSrc.h"
#include "IfxCan_Can.h"
#include "IfxCcu6.h"  //CCU6模块库头文件

函数“GPTimer_init()”的定义可以从文章Aurix英飞凌TC334芯片GPT模块定时器配置中查看。函数“CAN_init()”的定义可以从文章Aurix英飞凌TC334芯片CAN模块配置中查看。我们在主函数“core0_main()”的上面添加添加上“Motor_init()”和“Motor_ctl()”函数的具体定义以及CCU模块设定需要的变量。

/************************电机控制定义************************/
/*直流有刷电机初始化函数*/
void Motor_init(void)
{
    /*端口部分*/
    IfxPort_setPinMode(&MODULE_P00,2,IfxPort_Mode_outputPushPullAlt7);  //将P00.2复用为输出7,对应CCU61的COUT60
    IfxPort_setPinMode(&MODULE_P00,4,IfxPort_Mode_outputPushPullAlt7);  //将P00.4复用为输出7,对应CCU61的COUT61
    IfxPort_setPinMode(&MODULE_P00,6,IfxPort_Mode_outputPushPullAlt7);  //将P00.6复用为输出7,对应CCU61的COUT62
    IfxPort_setPinMode(&MODULE_P00,1,IfxPort_Mode_outputPushPullAlt7);  //将P00.1复用为输出7,对应CCU61的CC60
    IfxPort_setPinMode(&MODULE_P00,3,IfxPort_Mode_outputPushPullAlt7);  //将P00.3复用为输出7,对应CCU61的CC61
    IfxPort_setPinMode(&MODULE_P00,5,IfxPort_Mode_outputPushPullAlt7);  //将P00.5复用为输出7,对应CCU61的CC62
    IfxPort_setPinMode(&MODULE_P00,0,IfxPort_Mode_outputPushPullGeneral);  //将P00.0设置为普通输出,对应于电机驱动芯片的使能控制
    IfxPort_setPinState(&MODULE_P00,0,IfxPort_State_high);  //使能电机驱动芯片
    /*CCU6部分*/
    IfxCcu6_enableModule(&MODULE_CCU61);  //使能CCU61模块
    /*配置T12定时器的时基参数*/
    IfxCcu6_enableTimer(&MODULE_CCU61,IfxCcu6_TimerId_t12);  //使能CCU6的T12定时器,以便对它进行操作
    IfxCcu6_setInputClockFrequency(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_TimerInputClock_fcc6By4);  //CCU6使用SPB总线时钟作为时钟源,因此其模块频率为100MHz,经4分频得到25MHz的T12计数频率
    IfxCcu6_setT12CountMode(&MODULE_CCU61,IfxCcu6_T12CountMode_centerAligned);  //将T12的计数模式设为中心对齐模式
    IfxCcu6_setT12PeriodValue(&MODULE_CCU61,1250-1);  //周期值决定T12的输出频率,也即PWM的频率,设定为1250分频,则输出频率=25M/1.25K=20KHz,考虑到中心对齐的计数模式,那么输出的PWM就具有2分频特性,所以最终的输出频率为10KHz
    IfxCcu6_setT12CounterValue(&MODULE_CCU61,0);  //计数器先初始化为0
    /*配置T12通道模式*/
    IfxCcu6_setT12ChannelMode(&MODULE_CCU61,IfxCcu6_T12Channel_0,IfxCcu6_T12ChannelMode_compareMode);  //CC60通道设为比较模式,产生PWM波
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_0,0);  //比较值先设为0,产生0%的占空比
    IfxCcu6_setT12ChannelMode(&MODULE_CCU61,IfxCcu6_T12Channel_1,IfxCcu6_T12ChannelMode_compareMode);  //CC61通道设为比较模式,产生PWM波
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_1,0);  //比较值先设为0,产生0%的占空比
    IfxCcu6_setT12ChannelMode(&MODULE_CCU61,IfxCcu6_T12Channel_2,IfxCcu6_T12ChannelMode_compareMode);  //CC62通道设为比较模式,产生PWM波
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_2,0);  //比较值先设为0,产生0%的占空比
    /*配置CC60通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cc0,FALSE);  //通道的默认电平为低电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cc0,TRUE);  //默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cc0);  //先禁止CC60的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置COUT60通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cout0,TRUE);  //通道的默认电平为高电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cout0,TRUE);  //默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cout0);  //先禁止COUT0的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置CC61通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cc1,FALSE);  //通道的默认电平为低电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cc1,TRUE);  //的默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cc1);  //先禁止CC61的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置COUT61通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cout1,TRUE);  //通道的默认电平为高电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cout1,TRUE);  //默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cout1);  //先禁止COUT1的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置CC62通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cc2,FALSE);  //通道的默认电平为低电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cc2,TRUE);  //的默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cc2);  //先禁止CC62的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置COUT62通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cout2,TRUE);  //通道的默认电平为高电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cout2,TRUE);  //默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cout2);  //先禁止COUT2的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*启动定时器*/
    IfxCcu6_startTimer(&MODULE_CCU61,TRUE,FALSE);
}
/*直流有刷电机控制函数,控制电机的旋转方向及停止,占空比调节动作快慢,占空比范围0-100,对应0%-100%,调节精度1%*/
void Motor_ctl(uint8 ctl_dir,uint8 duty)
{
    uint16 period_val = 0;
    uint16 cmp_val = 0;
    uint16 cc60_cmp_val = 0;  //左半桥比较值
    uint16 cc61_cmp_val = 0;  //中半桥比较值
    uint16 cc62_cmp_val = 0;  //右半桥比较直
    /*计算PWM*/
    period_val = MODULE_CCU61.T12PR.B.T12PV + 1;  //获取T12的周期寄存器值
    cmp_val = (uint16)(((float)duty/100.0f) * (float)period_val);
    /*设置各通道输出*/
    switch(ctl_dir)
    {
        case 1:  //电机正转,COUT60控制左上桥臂,CC61控制右下桥臂导通,使电机电压左正右负,电流由左向右
        {
            MODULE_CCU61.MODCTR.B.T12MODEN = 0x09;  //左下管导通,中上管导通,右半桥关闭
            cc60_cmp_val = cmp_val;     //左半桥下管PWM调制
            cc61_cmp_val = period_val;  //中半桥上管常通
            cc62_cmp_val = 0;           //右半桥无PWM输出
            break;
        }
        case 2:  //电机反转,CC60控制左下桥臂,COUT61控制右上桥臂导通,使电机电压左负右正,电流由右向左
        {
            MODULE_CCU61.MODCTR.B.T12MODEN = 0x06;  //左上管导通,中下管导通,右半桥关闭
            cc60_cmp_val = period_val;  //左半桥上管常通
            cc61_cmp_val = cmp_val;     //中半桥下管PWM调制
            cc62_cmp_val = 0;           //右半桥无PWM输出
            break;
        }
        default://停止,下两个桥臂导通,利用电机的电感特性阻碍电机运动,使电机停止
        {
            MODULE_CCU61.MODCTR.B.T12MODEN = 0x15;  //开启所有下管,关闭所有上管
            cc60_cmp_val = period_val;  //左半桥PWM输出100%,最大能耗制动
            cc61_cmp_val = period_val;  //中半桥PWM输出100%,最大能耗制动
            cc62_cmp_val = period_val;  //右半桥PWM输出100%,最大能耗制动
            break;
        }
    }
    /*写入PWM比较值*/
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_0,cc60_cmp_val);  //写入CC60通道的新比较值
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_1,cc61_cmp_val);  //写入CC61通道的新比较值
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_2,cc62_cmp_val);  //写入CC62通道的新比较值
    /*更新实际寄存器*/
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,1,0);  //启动一次阴影传输以更新到实际寄存器
}

现在所有代码编写完毕,整个Cpu0_Main.c的定义如下所示。

#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"
#include "IfxGpt12.h"
#include "IfxSrc.h"
#include "IfxCan_Can.h"
#include "IfxCcu6.h"  //CCU6模块库头文件,用于配置CCU6模块
IFX_ALIGN(4) IfxCpu_syncEvent g_cpuSyncEvent = 0;
/************************GPT定时模块定义************************/
uint8 TimeCount = 0;
void GPTimer_init(void)
{
    /*端口初始化*/
    IfxPort_setPinMode(&MODULE_P02,5,IfxPort_Mode_outputPushPullGeneral );  //P02.5配置为输入模式
    /*GPT120配置*/
    IfxGpt12_enableModule(&MODULE_GPT120);  //使能GPT120模块
    IfxGpt12_setGpt1BlockPrescaler(&MODULE_GPT120,IfxGpt12_Gpt1BlockPrescaler_8);  //GPT模块预分频,使用SPB总线时钟为100MHz
    /*配置T3定时器用于产生周期性中断*/
    IfxGpt12_T3_setMode(&MODULE_GPT120,IfxGpt12_Mode_timer);  //设置T3定时器的模式为定时器模式
    IfxGpt12_T3_setDirectionSource(&MODULE_GPT120,IfxGpt12_TimerDirectionSource_internal);  //内部控制计数方向
    IfxGpt12_T3_setTimerDirection(&MODULE_GPT120,IfxGpt12_TimerDirection_up);  //T3的计数方向为向上计数
    IfxGpt12_T3_setTimerPrescaler(&MODULE_GPT120,IfxGpt12_TimerInputPrescaler_128);  //定时器计数时钟为模块时钟的128分频
    IfxGpt12_T3_setTimerValue(&MODULE_GPT120,0xFFFF-50000+1);  //计数50000个脉冲即会发生溢出事件
    /*配置T3的溢出中断*/
    IfxSrc_init(&SRC_GPT120T3,IfxSrc_Tos_cpu0,10);   //配置GPT120模块的T3定时器溢出中断服务,中断优先级设为10
    IfxSrc_enable(&SRC_GPT120T3);  //使能GPT120模块的T3定时器溢出中断服务
    /*启动定时器*/
    IfxGpt12_T3_run(&MODULE_GPT120,IfxGpt12_TimerRun_start);  //启动T3
}
IFX_INTERRUPT(GPTimer_isr,0,10);  //声明定时中断服务函数
void GPTimer_isr(void)
{
     TimeCount++;
}
/************************CAN模块定义************************/
volatile uint16 rx_id = 0;  //存储接收报文ID
volatile uint8 rx_len = 0;  //存储接收报文负载数据长度
volatile uint8 rx_buf[8] = {0};  //报文接收数据
/*CAN模块初始化*/
IfxCan_Can can_handle = {0};  //CAN模块的操作句柄
IfxCan_Can_Node node0_handle = {0};  //CAN第0节点的操作句柄
/*CAN模块初始化函数*/
void CAN_init(void)
{
     /*模块配置*/
    IfxCan_Can_Config can_config = {0};  //模块配置参数结构体变量
    IfxCan_Can_initModuleConfig(&can_config,&MODULE_CAN0);  //填充CAN模块配置参数结构体
    IfxCan_Can_initModule(&can_handle,&can_config);  //初始化CAN模块
    /*端口配置*/
    IfxCan_Node_initTxPin(&IfxCan_TXD00_P20_8_OUT,IfxPort_OutputMode_pushPull,IfxPort_PadDriver_cmosAutomotiveSpeed1);
    IfxCan_Node_initRxPin(&MODULE_CAN0.N[IfxCan_NodeId_0],&IfxCan_RXD00B_P20_7_IN,IfxPort_InputMode_noPullDevice,IfxPort_PadDriver_cmosAutomotiveSpeed1);
    /*节点0配置*/
    IfxCan_Can_NodeConfig node_config = {0};
    IfxCan_Can_initNodeConfig(&node_config,&can_handle);  //填充节点默认配置
    node_config.nodeId = IfxCan_NodeId_0;  //要配置的节点为第0号节点
    node_config.frame.type = IfxCan_FrameType_transmitAndReceive;  //允许节点能接收和发送数据帧
    node_config.baudRate.baudrate = 500000;//波特率
    /*节点接收中断*/
    node_config.interruptConfig.messageStoredToDedicatedRxBufferEnabled = TRUE;  //使能接收成功中断
    node_config.interruptConfig.reint.interruptLine = IfxCan_InterruptLine_0;  //中断信号映射到0号中断线
    node_config.interruptConfig.reint.priority = 11;  //接收成功中断的优先级设为11
    IfxCan_Can_initNode(&node0_handle,&node_config);  //完成节点配置
    /*节点0过滤配置*/
    IfxCan_Filter node0_filter0 = {0};
    node0_filter0.number = 0;  //配置第0号过滤器
    node0_filter0.elementConfiguration = IfxCan_FilterElementConfiguration_storeInRxBuffer;  //过滤通过存到指定缓冲区
    node0_filter0.id1 = 0x010;  //在指定缓冲区模式下,配置id1为过滤id
    node0_filter0.rxBufferOffset = IfxCan_RxBufferId_0;  //过滤完成后存入0号接收缓冲区
    IfxCan_Can_setStandardFilter(&node0_handle,&node0_filter0);  //完成过滤器配置
}
/*根据报文ID发出报文,返回已完成发送的数据长度*/
void CAN_send(uint16 id,uint8 *buf,uint32 len)
{
    IfxCan_Message tx_msg = {0};
    IfxCan_Can_initMessage(&tx_msg);  //向消息结构体填充初始参数
    tx_msg.bufferNumber = IfxCan_TxBufferId_0;  //使用第0号发送缓冲区发送
    tx_msg.frameMode = IfxCan_FrameMode_standard;  //此为标准CAN协议帧
    tx_msg.dataLengthCode = len;  //设定要发送的报文的负载数据长度
    tx_msg.messageId = id;  //设置本次报文的id
    tx_msg.messageIdLength = IfxCan_MessageIdLength_standard;  //本次报文ID长度为11位的ID
    IfxCan_Can_sendMessage(&node0_handle,&tx_msg,(uint32 *)buf);  //传入负载数据启动发送
}
/*CAN模块的接收中断处理函数*/
IFX_INTERRUPT(CAN_rx_isr,0,11);  //声明CAN接收中断服务函数
void CAN_rx_isr(void)
{
    IfxCan_Message rx_msg = {0};
    uint32 data_buf[2] = {0};  //存数据的临时缓冲区
    IfxCan_Node_clearInterruptFlag(node0_handle.node,IfxCan_Interrupt_messageStoredToDedicatedRxBuffer);  //清除接收完成中断
    rx_msg.bufferNumber = IfxCan_RxBufferId_0;  //指定获取第0号接收缓冲区
    IfxCan_Can_readMessage(&node0_handle,&rx_msg,data_buf);  //读取消息,将数据存入缓冲区便于程序处理
    rx_id = (uint16)rx_msg.messageId;  //将接收到的报文ID保存下来
    rx_len = (uint8)rx_msg.dataLengthCode;  //获取接收到的数据长度
    /*取出接收到的报文数据*/
    for(uint32 cnt = 0; cnt < rx_len; cnt ++)
    {
        rx_buf[cnt] = ((uint8 *)data_buf)[cnt];  //以字节来读取数据
    }
}
/************************电机控制定义************************/
/*直流有刷电机初始化函数*/
void Motor_init(void)
{
    /*端口部分*/
    IfxPort_setPinMode(&MODULE_P00,2,IfxPort_Mode_outputPushPullAlt7);  //将P00.2复用为输出7,对应CCU61的COUT60
    IfxPort_setPinMode(&MODULE_P00,4,IfxPort_Mode_outputPushPullAlt7);  //将P00.4复用为输出7,对应CCU61的COUT61
    IfxPort_setPinMode(&MODULE_P00,6,IfxPort_Mode_outputPushPullAlt7);  //将P00.6复用为输出7,对应CCU61的COUT62
    IfxPort_setPinMode(&MODULE_P00,1,IfxPort_Mode_outputPushPullAlt7);  //将P00.1复用为输出7,对应CCU61的CC60
    IfxPort_setPinMode(&MODULE_P00,3,IfxPort_Mode_outputPushPullAlt7);  //将P00.3复用为输出7,对应CCU61的CC61
    IfxPort_setPinMode(&MODULE_P00,5,IfxPort_Mode_outputPushPullAlt7);  //将P00.5复用为输出7,对应CCU61的CC62
    IfxPort_setPinMode(&MODULE_P00,0,IfxPort_Mode_outputPushPullGeneral);  //将P00.0设置为普通输出,对应于电机驱动芯片的使能控制
    IfxPort_setPinState(&MODULE_P00,0,IfxPort_State_high);  //使能电机驱动芯片
    /*CCU6部分*/
    IfxCcu6_enableModule(&MODULE_CCU61);  //使能CCU61模块
    /*配置T12定时器的时基参数*/
    IfxCcu6_enableTimer(&MODULE_CCU61,IfxCcu6_TimerId_t12);  //使能CCU6的T12定时器,以便对它进行操作
    IfxCcu6_setInputClockFrequency(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_TimerInputClock_fcc6By4);  //CCU6使用SPB总线时钟作为时钟源,因此其模块频率为100MHz,经4分频得到25MHz的T12计数频率
    IfxCcu6_setT12CountMode(&MODULE_CCU61,IfxCcu6_T12CountMode_centerAligned);  //将T12的计数模式设为中心对齐模式
    IfxCcu6_setT12PeriodValue(&MODULE_CCU61,1250-1);  //周期值决定T12的输出频率,也即PWM的频率,设定为1250分频,则输出频率=25M/1.25K=20KHz,考虑到中心对齐的计数模式,那么输出的PWM就具有2分频特性,所以最终的输出频率为10KHz
    IfxCcu6_setT12CounterValue(&MODULE_CCU61,0);  //计数器先初始化为0
    /*配置T12通道模式*/
    IfxCcu6_setT12ChannelMode(&MODULE_CCU61,IfxCcu6_T12Channel_0,IfxCcu6_T12ChannelMode_compareMode);  //CC60通道设为比较模式,产生PWM波
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_0,0);  //比较值先设为0,产生0%的占空比
    IfxCcu6_setT12ChannelMode(&MODULE_CCU61,IfxCcu6_T12Channel_1,IfxCcu6_T12ChannelMode_compareMode);  //CC61通道设为比较模式,产生PWM波
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_1,0);  //比较值先设为0,产生0%的占空比
    IfxCcu6_setT12ChannelMode(&MODULE_CCU61,IfxCcu6_T12Channel_2,IfxCcu6_T12ChannelMode_compareMode);  //CC62通道设为比较模式,产生PWM波
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_2,0);  //比较值先设为0,产生0%的占空比
    /*配置CC60通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cc0,FALSE);  //通道的默认电平为低电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cc0,TRUE);  //默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cc0);  //先禁止CC60的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置COUT60通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cout0,TRUE);  //通道的默认电平为高电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cout0,TRUE);  //默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cout0);  //先禁止COUT0的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置CC61通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cc1,FALSE);  //通道的默认电平为低电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cc1,TRUE);  //的默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cc1);  //先禁止CC61的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置COUT61通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cout1,TRUE);  //通道的默认电平为高电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cout1,TRUE);  //默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cout1);  //先禁止COUT1的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置CC62通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cc2,FALSE);  //通道的默认电平为低电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cc2,TRUE);  //的默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cc2);  //先禁止CC62的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*配置COUT62通道参数*/
    IfxCcu6_setOutputPassiveLevel(&MODULE_CCU61,IfxCcu6_ChannelOut_cout2,TRUE);  //通道的默认电平为高电平
    IfxCcu6_setOutputPassiveState(&MODULE_CCU61,IfxCcu6_ChannelOut_cout2,TRUE);  //默认电平激活条件为计数器大于比较值时激活,大于比较值输出低电平,小于等于比较值输出高电平
    IfxCcu6_disableModulationOutput(&MODULE_CCU61,IfxCcu6_TimerId_t12,IfxCcu6_ChannelOut_cout2);  //先禁止COUT2的通道输出
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,TRUE,FALSE);  //启动一次阴影传输以更新到实际寄存器
    /*启动定时器*/
    IfxCcu6_startTimer(&MODULE_CCU61,TRUE,FALSE);
}
/*直流有刷电机控制函数,控制电机的旋转方向及停止,占空比调节动作快慢,占空比范围0-100,对应0%-100%,调节精度1%*/
void Motor_ctl(uint8 ctl_dir,uint8 duty)
{
    uint16 period_val = 0;
    uint16 cmp_val = 0;
    uint16 cc60_cmp_val = 0;  //左半桥比较值
    uint16 cc61_cmp_val = 0;  //中半桥比较值
    uint16 cc62_cmp_val = 0;  //右半桥比较直
    /*计算PWM*/
    period_val = MODULE_CCU61.T12PR.B.T12PV + 1;  //获取T12的周期寄存器值
    cmp_val = (uint16)(((float)duty/100.0f) * (float)period_val);
    /*设置各通道输出*/
    switch(ctl_dir)
    {
        case 1:  //电机正转,COUT60控制左上桥臂,CC61控制右下桥臂导通,使电机电压左正右负,电流由左向右
        {
            MODULE_CCU61.MODCTR.B.T12MODEN = 0x09;  //左下管导通,中上管导通,右半桥关闭
            cc60_cmp_val = cmp_val;     //左半桥下管PWM调制
            cc61_cmp_val = period_val;  //中半桥上管常通
            cc62_cmp_val = 0;           //右半桥无PWM输出
            break;
        }
        case 2:  //电机反转,CC60控制左下桥臂,COUT61控制右上桥臂导通,使电机电压左负右正,电流由右向左
        {
            MODULE_CCU61.MODCTR.B.T12MODEN = 0x06;  //左上管导通,中下管导通,右半桥关闭
            cc60_cmp_val = period_val;  //左半桥上管常通
            cc61_cmp_val = cmp_val;     //中半桥下管PWM调制
            cc62_cmp_val = 0;           //右半桥无PWM输出
            break;
        }
        default://停止,下两个桥臂导通,利用电机的电感特性阻碍电机运动,使电机停止
        {
            MODULE_CCU61.MODCTR.B.T12MODEN = 0x15;  //开启所有下管,关闭所有上管
            cc60_cmp_val = period_val;  //左半桥PWM输出100%,最大能耗制动
            cc61_cmp_val = period_val;  //中半桥PWM输出100%,最大能耗制动
            cc62_cmp_val = period_val;  //右半桥PWM输出100%,最大能耗制动
            break;
        }
    }
    /*写入PWM比较值*/
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_0,cc60_cmp_val);  //写入CC60通道的新比较值
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_1,cc61_cmp_val);  //写入CC61通道的新比较值
    IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_2,cc62_cmp_val);  //写入CC62通道的新比较值
    /*更新实际寄存器*/
    IfxCcu6_enableShadowTransfer(&MODULE_CCU61,1,0);  //启动一次阴影传输以更新到实际寄存器
}
void core0_main(void)
{
    IfxCpu_enableInterrupts();

    /* !!WATCHDOG0 AND SAFETY WATCHDOG ARE DISABLED HERE!!
     * Enable the watchdogs and service them periodically if it is required
     */
    IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
    IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());

    /* Wait for CPU sync event */
    IfxCpu_emitEvent(&g_cpuSyncEvent);
    IfxCpu_waitEvent(&g_cpuSyncEvent, 1);
    GPTimer_init();
    CAN_init();
    Motor_init();
    uint8 tx_buf[8]; // 发送缓存
    while(1)
    {
        if(TimeCount>1)
        {
            TimeCount = 0;
            Motor_ctl(rx_buf[0],10);//电机控制
            tx_buf[0] = rx_buf[0];
            CAN_send(0x123,tx_buf,8); // 报文发送
        }
    }
}

二、代码解释

相比于前面文章Aurix英飞凌TC334芯片ADC采样模块配置中CAN模块的配置,CCU模块的配置稍微复杂了一点。主要是因为CCU模块涉及到多个端口按照时序输出。好消息是模块初始化中绝大部分代码不需要修改,只关注几个重要的配置即可。下面我们就对这段代码就行简单的分析和讲解。

IfxPort_setPinMode(&MODULE_P00,2,IfxPort_Mode_outputPushPullAlt7);  //将P00.2复用为输出7,对应CCU61的COUT60
IfxPort_setPinMode(&MODULE_P00,4,IfxPort_Mode_outputPushPullAlt7);  //将P00.4复用为输出7,对应CCU61的COUT61
IfxPort_setPinMode(&MODULE_P00,6,IfxPort_Mode_outputPushPullAlt7);  //将P00.6复用为输出7,对应CCU61的COUT62
IfxPort_setPinMode(&MODULE_P00,1,IfxPort_Mode_outputPushPullAlt7);  //将P00.1复用为输出7,对应CCU61的CC60
IfxPort_setPinMode(&MODULE_P00,3,IfxPort_Mode_outputPushPullAlt7);  //将P00.3复用为输出7,对应CCU61的CC61
IfxPort_setPinMode(&MODULE_P00,5,IfxPort_Mode_outputPushPullAlt7);  //将P00.5复用为输出7,对应CCU61的CC62

这几行代码是设置电机控制的端口。驱动一个有刷电机,使用下图所示的H桥电路即可实现。Q1和Q4通,Q2和Q3断,电机正转;Q1和Q4断,Q2和Q3通,电机反转;Q1和Q3断,Q2和Q4通,电机制动。因此一般情况配置四个端口就可以实现功能。由于CCU模块和测试板均具备可生成3路PWM的资源,可以用于控制BLDC(直流无刷)电机,所以本实例中就将三路六个端口全部进行了配置。实际上我们只使用了其中两路四个端口。
在这里插入图片描述
配置所需的端口可从电路原理图上寻找。
在这里插入图片描述

 IfxPort_setPinMode(&MODULE_P00,0,IfxPort_Mode_outputPushPullGeneral);
 IfxPort_setPinState(&MODULE_P00,0,IfxPort_State_high); 

上面这行代码就使能电机驱动芯片。因为英飞凌单片机的驱动能力很弱,只能提供很小的电流,有时可能无法顺利实现MOS管的通断,所以我们使用了电机驱动芯片增大其驱动能力。电机驱动芯片要想工作,需要将P00.0端口置为高电平。

IfxCcu6_setT12CountMode(&MODULE_CCU61,IfxCcu6_T12CountMode_centerAligned);  //将T12的计数模式设为中心对齐模式

这一行代码的主要作用是将定时器设置为中心对齐模式。中心对齐模式是相对于边缘对齐模式而言的。中心对齐模式下,如果定时器达到最大值后,不会置零而是会由最大值逐个递减。

IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_0,0);  //比较值先设为0,产生0%的占空比

这一行代码的主要作用是设定比较值。当定时器的值与比较值相等的时候,我们就说这时发生了一次“比较匹配”。当比较匹配发生时,我们前面配置的几个端口会出现一次电平翻转。因此我们可以通过更改不同的比较值,从而调制出不同占空比的PWM。通过设置定时器的初始值,可以调制出不同频率的PWM。

MODULE_CCU61.MODCTR.B.T12MODEN = 0x09;  //左下管导通,中上管导通,右半桥关闭

上面这行代码是控制四个端口的通断,只有导通的端口才会输出PWM。

IfxCcu6_setT12CompareValue(&MODULE_CCU61,IfxCcu6_T12Channel_0,cc60_cmp_val);  //写入CC60通道的新比较值

上面这行代码是通过设置比较值,实现PWM占空比变化。PWM的占空比越大,电机被驱动的时间就越长,累计的速度会更高。我们可以看见,这段程序的主要作用是周期性的获取CAN报文发送过来的电机旋向指令,然后控制电机做出响应的动作,最后通过CAN报文把电机的旋向发送出去。程序的结构如下图所示。
在这里插入图片描述

三、下载调试

程序编写完毕后,我们点击任务栏中的编译按钮。
在这里插入图片描述
编译完成后,在输出窗口可以看见“编译完成,无错误,无警告”的字样。
在这里插入图片描述
然后我们通过Type-C将测试板与你的电脑相连。
在这里插入图片描述
点击任务栏中的下载按钮。
在这里插入图片描述
下载完成后,在输出窗口可以看见“下载成功”的字样。
在这里插入图片描述
将测试板与有刷电机相连,将测试板的CAN_H(红色)和CAN_L(蓝色)接出来通过DB9接头与CAN卡相连。将CAN卡通过USB接口与电脑相连。如下图所示。
在这里插入图片描述
个人电脑的上位机我们使用TSMaster的CAN报文收发功能。
在这里插入图片描述
配置好TSMaster后,测试板已经在周期性的发送ID为0x123的报文了。同时我们也能看见有刷电机转动。

电机控制

在没有接到ID为0x10的指令报文前,电机是不转动的。当接收到ID0x10的第0字节为1时,电机正转;当接收到ID0x10的第0字节为0时,电机停止;当接收到ID0x10的第0字节为2时,电机反转。同时ID为0x123的报文也在周期性的外发电机旋向状态。至此我们完成了使用TC334芯片实现了有刷电机的控制。


总结

今天我们介绍了如何运用TC334芯片CCU模块实现有刷电机控制。我们到目前为止一直介绍的都是芯片的基础配置。后续我们将继续介绍该芯片的进阶应用,使用该芯片实现CCP协议,XCP协议,UDS协议,J1939协议和RTOS。敬请收藏关注,不迷路。

如需测试板,请点击。
https://store.weixin.qq.com/shop/b/feHVuMfOAmRCoHk?entrance_id=h5

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐