Infineon英飞凌 TC275 TC27x D-Step LED闪烁例程1 Blinky_LED_1 学习笔记

引言:

大家好,我是灰,由于英飞凌官方出的视频教程都太应付,而我又不太想生啃这5000页的手册,所以我想来根据例程来逆向推导寄存器的使用,写下这篇博客也是为了自己留下一篇笔记,若有错误还请大家友好探讨。

例程下载:

首先第一篇我们以点灯为例,这里如果大家第一次使用ADS不太熟悉的话,我也带大家从头下载一下官方的例程。

首先右键空白区域,选择Import,点击左上角的File也可以找到Import:

这里我们选择AURIX Development Studio Project,然后点击Next:

在第一项里,大家可以选择是在线下载还是本地导入,我这里选的就是在线下载;第二项大家可以直接搜索关键字,我这里就输入led搜索;在列表里,大家可以根据自己的型号(如TC275 lite Kit)来勾选对应的Blinky_LED_1的例程。完毕后点击Finish。

下载完毕后就会出现在项目列表里了,如果大家之前没有项目,那么是自动Active的;没有Active的话大家就手动右键一下项目名,选择Set Active Project即可。

接着来配置一下板子的连接,大家右键项目名,然后选择Debug As中的Debug Configurations。

双击一下TASKING C/C++ Debugger:

在这里出来了对应板子的型号,就可以点Close关闭了。

在上方,锤子的标志就是编译(Build),黄色的标志是烧录(Flash),虫子的标志是调试(Debug),大家接好板子后直接点击烧录, 就会自动编译并烧录程序到板子里。

如果你的板子上对应的LED闪烁了就证明大功告成!

浏览工程:

接着我们可以看到项目中的代码文件:

其中Blinky_LED就是我们LED相关的代码,三个Main对应了三核CPU每个核,其中这次只用到了Cpu0。

然后我们来浏览一下Cpu0_Main.c文件中的代码:

#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"
#include "Blinky_LED.h"

IfxCpu_syncEvent g_cpuSyncEvent = 0;

int 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);
    
    initLED();  /* Initialize the LED port pin      */

    while(1)
    {
        blinkLED(); /* Make the LED blink           */
    }
    return (1);
}

大家可以看到,和LED有关的只有LED相关的初始化:initLED();以及while loop中的blinkLED();了。而这两个方法自然都是定义在Blinky_LED中的。

首先检查一下Blinky_LED的头文件:

#ifndef BLINKY_LED_H_
#define BLINKY_LED_H_

/*********************************************************************************************************************/
/*------------------------------------------------Function Prototypes------------------------------------------------*/
/*********************************************************************************************************************/
void initLED(void);
void blinkLED(void);

#endif /* BLINKY_LED_H_ */

可以看到英飞凌的例程还是非常简洁的,就是声明了刚才那两个方法。

我们接下来看c文件:

#include "IfxPort.h"
#include "Bsp.h"

/*********************************************************************************************************************/
/*------------------------------------------------------Macros-------------------------------------------------------*/
/*********************************************************************************************************************/
#define LED         &MODULE_P00,5                                           /* LED: Port, Pin definition            */
#define WAIT_TIME   500                                                     /* Wait time constant in milliseconds   */

/*********************************************************************************************************************/
/*---------------------------------------------Function Implementations----------------------------------------------*/
/*********************************************************************************************************************/
/* This function initializes the port pin which drives the LED */
void initLED(void)
{
    /* Initialization of the LED used in this example */
    IfxPort_setPinModeOutput(LED, IfxPort_OutputMode_pushPull, IfxPort_OutputIdx_general);

    /* Switch OFF the LED (low-level active) */
    IfxPort_setPinHigh(LED);
}

/* This function toggles the port pin and wait 500 milliseconds */
void blinkLED(void)
{
    IfxPort_togglePin(LED);                                                     /* Toggle the state of the LED      */
    waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, WAIT_TIME));    /* Wait 500 milliseconds            */
}

那我们从这一层来看的话,英飞凌对于硬件操作的库函数已经封装的很好了,基本上干干净净的,而且都加上了注释,那我们往下给它们剥开深入分析一下。

GPIO引脚:

首先是对于LED引脚的定义。

#define LED         &MODULE_P00,5

大家可以打开自己板子的原理图,可以看到LED对应的是哪个引脚。英飞凌的原理图文档比较贴心,还给我们用建模图标注好了每个元器件的准确位置,如图P00.5对应的LED1,在这里我们可以看到是低电平驱动的。

当然在正经原理图上也可以找到:

可以看到确实是接到了P00.5上,且是低电平驱动。

我们可以右键MODULE_P00跳转到对应的宏定义上:

#define MODULE_P00 /*lint --e(923)*/ (*(Ifx_P*)0xF003A000u)

这个定义是在IfxPort_reg.h中,从这个头文件的名字我们也不难看出,它定义了所有IO口寄存器的宏定义。

我们在手册的第13章(GPIO)第8页中,可以看到P00组IO口的首末地址,可以看到这个地址对应上了:

但是宏定义中并没有对我们5号口的地址进行计算,仅仅是用逗号隔开了,那我们可以推测出,这个就是在初始化函数里进行计算了。

GPIO初始化:

接下来我们就看看这个初始化函数:

void initLED(void)
{
    /* Initialization of the LED used in this example */
    IfxPort_setPinModeOutput(LED, IfxPort_OutputMode_pushPull, IfxPort_OutputIdx_general);

    /* Switch OFF the LED (low-level active) */
    IfxPort_setPinHigh(LED);
}

总共就两步,第一步是设置好IO口(也就是Pin脚)的输出模式,第二步是设置好IO口的默认输出电平。

我们查看一下IfxPort_setPinModeOutput的定义:

IFX_INLINE void IfxPort_setPinModeOutput(Ifx_P *port, uint8 pinIndex, IfxPort_OutputMode mode, IfxPort_OutputIdx index)
{
    IfxPort_setPinMode(port, pinIndex, (IfxPort_Mode)(index | mode));
}

不出所料,前两项就是port的基地址,第二项就是Pin脚的标号。这两项可以采用宏定义写到一起。而这也是一个内联函数,应该就是单纯为了方便用户操作多包装了一层,区分开输出配置和输入配置。

第三个参数是IfxPort_OutputMode类型的变量,可以看到是一个枚举:

typedef enum
{
    IfxPort_OutputMode_pushPull      = 0x10U << 3,
        IfxPort_OutputMode_openDrain = 0x18U << 3,
        IfxPort_OutputMode_none      = 0
} IfxPort_OutputMode;

在这里可以定义为推挽输出(push pull)或者开漏输出(open drain)。在这里我没有在手册里找到这一块的原理图,但是大家应该都知道这两者的区别,即推挽输出指IO口可以直接输出高电平或低电平,而开漏输出只能输出低电平和高阻态(也就是相当于断路)。在例程里选择的是推挽输出模式,应该是为了考虑到所有板子的情况,推挽输出通用性更强一些(比如你的LED接法和官方是相反的)。

第四个参数IfxPort_OutputIdx类型自然也是一个枚举:

typedef enum
{
    IfxPort_OutputIdx_general  = 0x10U << 3,
        IfxPort_OutputIdx_alt1 = 0x11U << 3,
        IfxPort_OutputIdx_alt2 = 0x12U << 3,
        IfxPort_OutputIdx_alt3 = 0x13U << 3,
        IfxPort_OutputIdx_alt4 = 0x14U << 3,
        IfxPort_OutputIdx_alt5 = 0x15U << 3,
        IfxPort_OutputIdx_alt6 = 0x16U << 3,
        IfxPort_OutputIdx_alt7 = 0x17U << 3
} IfxPort_OutputIdx;

这里即是配置Port的复用模式,在手册图13-1里我们可以看到支持多种复用模式:

手册上关于这段的描述是:
If the on-chip peripheral units use the pin for output signals, the alternate output lines ALT1 to ALT7 can be switched via the multiplexer to the output driver.
这里的意思应该是,IO口的其他复用功能会通过其他外设直连的方式输出,具体的使用方式在其他外设的章节中,如果我们只在GPIO寄存器中设置是没有效果的。


第三、第四这两个参数均定义在IfxPort.h中,也就是GPIO配置相关的头文件。

目前为止,我们使用到了三个寄存器配置相关数据,第一个是P00对应的基地址0xF003A000;第二个是设置推挽输出模式用到的IfxPort_OutputMode_pushPull = 0x10U << 3,也就是0x80;第三个是设置IO口为通用模式用到的IfxPort_OutputIdx_general = 0x10U << 3,一样也是0x80。

而这些参数(包括前面的5号端口)会一起给到下面这个函数:

IfxPort_setPinMode(port, pinIndex, (IfxPort_Mode)(index | mode));

其中前两个参数就是P00对应的基地址0xF003A000和P00.5对应的端口号5,第三个参数是将前面的后两个参数取或再转换成IFxPort_Mode类型,大家可以看看这个类型是什么:

typedef enum
{
    IfxPort_Mode_inputNoPullDevice      = 0,      /**< \brief Input, No pull device connected. */
    IfxPort_Mode_inputPullDown          = 8U,     /**< \brief Input, pull-down device connected. */
    IfxPort_Mode_inputPullUp            = 0x10U,  /**< \brief Input, pull-up device connected. */
    IfxPort_Mode_outputPushPullGeneral  = 0x80U,  /**< \brief Push-pull, General-purpose output */
    IfxPort_Mode_outputPushPullAlt1     = 0x88U,  /**< \brief Push-pull, Alternate output function 1. */
    IfxPort_Mode_outputPushPullAlt2     = 0x90U,  /**< \brief Push-pull, Alternate output function 2. */
    IfxPort_Mode_outputPushPullAlt3     = 0x98U,  /**< \brief Push-pull, Alternate output function 3. */
    IfxPort_Mode_outputPushPullAlt4     = 0xA0U,  /**< \brief Push-pull, Alternate output function 4. */
    IfxPort_Mode_outputPushPullAlt5     = 0xA8U,  /**< \brief Push-pull, Alternate output function 5. */
    IfxPort_Mode_outputPushPullAlt6     = 0xB0U,  /**< \brief Push-pull, Alternate output function 6. */
    IfxPort_Mode_outputPushPullAlt7     = 0xB8U,  /**< \brief Push-pull, Alternate output function 7. */
    IfxPort_Mode_outputOpenDrainGeneral = 0xC0U,  /**< \brief Open-drain, General-purpose output. */
    IfxPort_Mode_outputOpenDrainAlt1    = 0xC8U,  /**< \brief Open-drain, Alternate output function 1. */
    IfxPort_Mode_outputOpenDrainAlt2    = 0xD0U,  /**< \brief Open-drain, Alternate output function 2. */
    IfxPort_Mode_outputOpenDrainAlt3    = 0xD8U,  /**< \brief Open-drain, Alternate output function 3. */
    IfxPort_Mode_outputOpenDrainAlt4    = 0xE0U,  /**< \brief Open-drain, Alternate output function 4. */
    IfxPort_Mode_outputOpenDrainAlt5    = 0xE8U,  /**< \brief Open-drain, Alternate output function 5. */
    IfxPort_Mode_outputOpenDrainAlt6    = 0xF0U,  /**< \brief Open-drain, Alternate output function 6. */
    IfxPort_Mode_outputOpenDrainAlt7    = 0xF8U   /**< \brief Open-drain, Alternate output function 7. */
} IfxPort_Mode;

实际上就是输入和输出配置、模式配置以及复用配置的整合,大家可以看到0x80 | 0x80 = 0x80,而0x80就是对应的通用推挽输出。

下面我们再来打开这个函数继续看:

void IfxPort_setPinMode(Ifx_P *port, uint8 pinIndex, IfxPort_Mode mode)
{
    volatile Ifx_P_IOCR0 *iocr      = &(port->IOCR0);
    uint8                 iocrIndex = (pinIndex / 4);
    uint8                 shift     = (pinIndex & 0x3U) * 8;

    if (port == &MODULE_P40)
    {
        uint16 passwd = IfxScuWdt_getCpuWatchdogPassword();
        IfxScuWdt_clearCpuEndinit(passwd);
        port->PDISC.U &= ~(1 << pinIndex);
        IfxScuWdt_setCpuEndinit(passwd);
    }

    __ldmst(&iocr[iocrIndex].U, (0xFFUL << shift), (mode << shift));
}

可以看到首先获取了P00的IOCR0寄存器地址(也是IOCR寄存器组的基地址),大家可以在手册13-14页(也就是第十三章的第十四页,以后都会这样简写,不是第十三到第十四页哦)看到:

意思是每个IOCR寄存器都对应了四个端口,这也是为什么代码中紧接着求了端口号除以四,就是判断该端口处于第几个IOCR,比如我们5号端口就求出来1,对应IOCR4:

uint8                 iocrIndex = (pinIndex / 4);

再下一行则是求寄存器内部的偏移,大家可以看到每8个bit对应着一个端口,那我们是5号,可以代入进去求出来偏移量就是8:

uint8                 shift     = (pinIndex & 0x3U) * 8;

这里大家看手册可以看到,IOCR寄存器有两种初始状态,

在寄存器的描述中,我们可以看到,基于HWCFG[6]这一位的不同,会导致初始值的不同。当HWCFG[6]为1时,IOCR的初始值(除了P33.8和P40)为1010 1010, 反之则全部为0000 0000。

在第四章我们可以进一步搜到相关的描述:

在4-7中可以看到,这一位的默认值是1。

这一块手册里描述的比较跳跃,我也不确定是不是这样(如果有懂行的朋友还请留下你宝贵的评论!),但是也没太大关系,因为我们不会使用默认值来配置寄存器。

回到代码我们再看下一段:

if (port == &MODULE_P40)
    {
        uint16 passwd = IfxScuWdt_getCpuWatchdogPassword();
        IfxScuWdt_clearCpuEndinit(passwd);
        port->PDISC.U &= ~(1 << pinIndex);
        IfxScuWdt_setCpuEndinit(passwd);
    }

这里判断了一下端口是否属于P40,如果是的话会把这个端口的PDISC置为0,那我们看一下手册里对于PDISC的描述:

可以看到,对于P40来说,可以选择数字输入或者模拟输入,并且使用PDISC寄存器来选择输入模式。这里手册写的很疑惑,0代表function X,1代表function Y?难道x和y是前者后者的意思?但是在tc3xx的手册里,明确说明了0代表数字、1代表模拟(不会真是前者后者的意思吧):

当修改这个寄存器时,又对SCU的看门狗寄存器进行了操作,我们未来学习SCU时再对这里的寄存器进行分析。总之我们知道,把P40当做GPIO来使用时,就设为数字模式即可。

初始化的最后,就是对IOCR进行赋值操作:

__ldmst(&iocr[iocrIndex].U, (0xFFUL << shift), (mode << shift));

 可以看到,第一个参数首先选择了IOCR的第二组寄存器——IOCR4,接着将0xFF左移8位到我们要修改的PC5上,最后在这个位置填入我们要写的0x80(当然也要左移8位后写进去)。

大家继续深入这个函数的话,可以看到最终的汇编操作:

IFX_INLINE void Ifx__ldmstAsm(volatile void *addr, uint32 mask, uint32 data)
{
    __asm("\tmov d3, %1 \n"
          "\tmov d2, %2 \n"
          "\tldmst [%0],e2"
          ::"a"(addr), "d"(mask), "d"(data):"d2", "d3");

}

那我们看参数也可以看出来,三个参数分别就是地址、掩码、数据。 

我们再来看看IOCR4寄存器:

可以看到,我们把红框的几位写入了1 0 0 0 0 0 0 0,也就是0x80。

后面三位是保留项,只看前五位的话可以查表知道,10000B对应的就是通用推挽输出,到此为止初始化也结束了。

GPIO操控:

回到初始化代码,我们对其进行了置高电平的操作:

IfxPort_setPinHigh(LED);

再次剥开它,可以看到与前面一样,也是对一个通用的函数进行封装成不同专用功能的内联函数,前两个参数就是P00的地址和5,第三位已经是帮我们填好了的高电平:

IFX_INLINE void IfxPort_setPinHigh(Ifx_P *port, uint8 pinIndex)
{
    IfxPort_setPinState(port, pinIndex, IfxPort_State_high);
}

 这里也可以看一下对应的枚举,high就是0x01:

typedef enum
{
    IfxPort_State_notChanged = (0 << 16) | (0 << 0),  /**< \brief Ifx_P pin is left unchanged. */
    IfxPort_State_high       = (0 << 16) | (1U << 0), /**< \brief Ifx_P pin is set to high. */
    IfxPort_State_low        = (1U << 16) | (0 << 0), /**< \brief Ifx_P pin is set to low. */
    IfxPort_State_toggled    = (1U << 16) | (1U << 0) /**< \brief Ifx_P pin is toggled. */
} IfxPort_State;

 再下一层,就是对P00的OMR寄存器操作了,把0x01左移5位:

IFX_INLINE void IfxPort_setPinState(Ifx_P *port, uint8 pinIndex, IfxPort_State action)
{
    port->OMR.U = action << pinIndex;
}

那我们继续看手册:

 

这个寄存器的功能不代表IO值, 而是可以通过填写端口对应的PCL和PS位来进行调整。比如我们要填写的就是PCL5和PS5。那刚才我们也都知道了,要写入的是0x01。那就是PCL=0,PS=1,我们看下面的表:

可以看到,对应的就是set(置高电平)。

那同理的,我们可以看LED闪烁的代码:

IfxPort_togglePin(LED);

进入内部,可以看到和刚才的置高电平的函数一样,不过是把第三个参数换成toggled了:

IFX_INLINE void IfxPort_togglePin(Ifx_P *port, uint8 pinIndex)
{
    IfxPort_setPinState(port, pinIndex, IfxPort_State_toggled);
}

 前面也查过了toggled对应的值是多少:

IfxPort_State_toggled    = (1U << 16) | (1U << 0) /**< \brief Ifx_P pin is toggled. */

为了更好地理解寄存器要填入的值,大家可以不对其进行计算,而是单纯地把它看成“高16位置1,低16位置1”。这样是不是可以更清晰地看出来,PCL5和PS5分别都是1和1了:

那再看上面的表,也可以对应出toggled操作了:

 

到此为止,所有的关于点灯的操作都已经学习完毕,代码中剩余的关于看门狗与延时函数的内容,就留到后面单独章节进行学习~

英飞凌的学习资料在网上实在太少,如果有需要指正的地方还请大家多多指点!