void FTM_PWM_Duty(FTMn_e ftmn, FTM_CHn_e ch, uint32 duty)
{
uint32 cv; 注:定义cv为无符号长整型,32位
uint32 mod = 0; 注:模数寄存器的初值设为0
ASSERT( (ftmn == FTM0) || ( (ftmn == FTM1 || ftmn == FTM2 ) && (ch <= FTM_CH1)) ); //检查传递进来的通道是否正确
注:ASSERT是一个断言,关于断言请看代码结尾的注释1
switch(ftmn)
{
case FTM0:
ASSERT(duty <= FTM0_PRECISON); //用断言检测 占空比是否合理
break;
case FTM1:
ASSERT(duty <= FTM1_PRECISON); //用断言检测 占空比是否合理
break;
case FTM2:
ASSERT(duty <= FTM2_PRECISON); //用断言检测 占空比是否合理
break;
default:
break;
}
//占空比 = (CnV-CNTIN)/(MOD-CNTIN+1)
do
{
mod = FTM_MOD_REG(FTMN[ftmn]); //读取 MOD 的值
}
注:关于FTM_MOD_REG请看注释2,FTMN[]请看注释3,ftmn请看注释4
while(mod == 0); //读一次,可能是0 ,需要多读取几次。
switch(ftmn)
{
case FTM0:
cv = (duty * (mod - 0 + 1)) / FTM0_PRECISON;
break;
case FTM1:
cv = (duty * (mod - 0 + 1)) / FTM1_PRECISON;
break;
case FTM2:
cv = (duty * (mod - 0 + 1)) / FTM2_PRECISON;
break; 注: duty / FTM2_PRECISON是一个大于0小于1的数
default: cv等于整个计数的数量乘以duty / FTM2_PRECISON
break;
}
// 配置FTM通道值
FTM_CnV_REG(FTMN[ftmn], ch) = cv;
} 注:然后计数值与cv进行匹配,0到cv之间是高电平
注释部分
注释1:ASSERT断言
断言就是假设你的判断是对的,这也是英文单词assert的含义。
在C语言里面断言的用法就是 “ASSERT(假设正确的判断);” ,C语言往往是把断言放在条件编译里面去用的,条件编译是一种预处理,在这个预处理中你可以设置一个断言,如果断言正确,那么就执行程序,如果断言失败,就让系统报错。就是说,设置一个合理的断言,如果你的程序里有错,编译软件在编译前就可以报错,而不是编译软件在程序运行过程中发现了某些不可预知的错误时报错,甚至是崩溃。
举个例子,这个断言中检测数组的引用是否超出了引用的范围,如下:
int a[i];int a=10;//此时你只能引用到a[9]
你可以在条件编译中断言ASSERT(i<10);如果你在主函数中存在a[10],那么条件编译中的断言就失败了,此时可以让软件报错了,主程序也就不会被执行了。
让我们跳到代码中ASSERT的底层看个究竟:
void assert_failed(char *, int);
#if defined( DEBUG )
注:这条语句意思是如果宏定义了DEBUG,开始我理解DEBUG就是DEBUG模式,下面是引用师兄的解释:
DEBUG就是一个宏标识,不一定就是DEBUG模式有时调试的时候,我也会在Release模式中加入宏定义DEBUG,目的就是输出一些调试信息。DEBUG这个宏,它就是一个工具,我需要的时候就用,不需要的时候就注释。因为发布的代码,仅仅是在DEBUG模式中用到,所以你把DEBUG这个宏当作DEBUG模式来看,也没错。(原则上我还是错的)
#defineASSERT(expr) \ 注:宏定义断言为下面的if语句,反斜杠代表下面的
if(!(expr)) \ 语句是和上面的语句一起的
assert_failed(__FILE__, __LINE__)
注:如果expr是假(断言失败),即!expr是真,则执行断言失败函数,如果expr是
真(断言成功),则不执行断言失败函数,直接进入主程序。
#else 注:未定义DEBUG,则宏定义断言为空,即开始正常执行程序
#define ASSERT(expr)
#endif
void assert_failed(char *file, int line)
{
led_init(LED0);
while (1)
{
DEBUG_PRINTF(ASSERT_FAILED_STR, file, line);
//通过串口提示断言失败
//死循环等待程序员检测为何断言失败
led_turn(LED0);
DELAY_MS(1000);
}
}
#if( defined(DEBUG) && defined(DEBUG_PRINT))
#define DEBUG_PRINTF(FORMAT,...) do{printf(FORMAT,##__VA_ARGS__);}while(0)
#else
#define DEBUG_PRINTF(FORMAT,...)
#endif
注释2:FTM_MOD_REG(base)
跳到定义处:
#define FTM_MOD_REG(base) ((base)->MOD)
这是一个宏定义,看来base是一个地址,意思是将base地址指向的MOD替换成FTM_MOD_REG(base)
注释3:FTMN[]
跳到定义处:
FTM_MemMapPtr FTMN[3] = {FTM0_BASE_PTR, FTM1_BASE_PTR, FTM2_BASE_PTR};
//定义三个指针数组保存 FTMn_e 的地址
这是一个指针数组,为什么是指针数组呢,那么就得看看FTM_MemMapPtr这个关键词是什么意思了,请看下面的底层:
typedef struct FTM_MemMap
{
uint32_t SC;
uint32_t CNT;
uint32_t MOD;
struct { uint32_t CnSC;
uint32_t CnV } CONTROLS[8];
uint32_t CNTIN;
uint32_t STATUS;
uint32_t MODE;
uint32_t SYNC;
uint32_t OUTINIT;
uint32_t OUTMASK;
uint32_t COMBINE;
uint32_t DEADTIME;
uint32_t EXTTRIG;
uint32_t POL;
uint32_t FMS;
uint32_t FILTER;
uint32_t FLTCTRL;
uint32_t QDCTRL;
uint32_t CONF;
uint32_t FLTPOL;
uint32_t SYNCONF;
uint32_t INVCTRL;
uint32_t SWOCTRL;
uint32_t PWMLOAD;
} volatile *FTM_MemMapPtr;
这个语句的意思是把FTM_MemMapPtr定义为结构体指针,其实它是上述那一大串结构体指针的一个别名,这是通过typedef这个关键字实现的。
注:typedef是存储类型的关键字,意思是它占用内存,可以发现typedef是一条语句,后面是有分号的,这一点是与宏定义不同的,宏定义是预处理,不占用内存,这就体现宏定义的优点了,宏定义用的多了,仅仅加长编译的时间,对运行是没有影响的。
下面跳入FTMN[3]数组中的FTM0_BASE_PTR:
#define FTM0_BASE_PTR ((FTM_MemMapPtr)0x40038000u)
宏定义将((FTM_MemMapPtr)0x40038000u)替换成FTM0_BASE_PTR,因为FTM0_BASE_PTR在主程序里面是一个地址,那么((FTM_MemMapPtr)0x40038000u)应该也是一个地址,0x40038000u只代表一个数,编译软件是不会把它当做地址的,但是(FTM_MemMapPtr)0x40038000u意思是把0x40038000u强制转换为地址,这样就可以用宏定义了。
下面是我对为什么要进行强制转换地址的理解:
请看下面两条语句
第一句:FTM_MemMapPtr FTMN[3] = {FTM0_BASE_PTR, FTM1_BASE_PTR, FTM2_BASE_PTR};
#define FTM0_BASE_PTR ((FTM_MemMapPtr)0x40038000u)
第二句:FTM_MemMapPtr FTMN[3] = {FTM0_BASE_PTR, FTM1_BASE_PTR, FTM2_BASE_PTR};
FTM0_BASE_PTR=0x40038000u;
其实,这两句话是一样的。
第一句中用了个宏定义,宏定义是一种预处理,此时FTM0_BASE_PTR还没有被指定类型,宏定义只是替换作用,此时编译器并不知道0x40038000u是一个地址,所以需要强制转换;
第二句用了一个赋值语句,因为编译器知道FTM0_BASE_PTR是一个地址,所以直接给它赋值就行了,而不用强制转换;
之所以不用赋值而用宏定义“仅仅是为了简化代码编写,增强可阅读性和可移植性。并不是为了省空间”,这是山外哥说的。
注释4:FTMn_e ftmn
跳到FTMn_e ftmn的定义处:
typedef enum
{
FTM0,
FTM1,
FTM2,
FTM_MAX,
} FTMn_e;
这是一个枚举,然后把这个枚举替换成FTMn_e这个别名,但是枚举里面的成员是FTM0,
FTM1, FTM2, FTM_MAX,但是这个语句中的FTMN[ftmn]是一个数组,数组后面的括号里面不应该是数字123吗,因为编译器会为枚举里面的成员自动赋值:如果是首枚举成员,则赋值为0,以此类推,所以FTMN[FTM0]就是FTMN[0]。、
注:原创博文,引用请注明出处,本博文代码源自山外提供的K60代码