本帖最后由 zxcscm 于 2012-2-7 13:35 编辑
园地里有很多关于PWM的例子,但是只看这些例子只能知其然不知所以然。索性逐字逐句地分析每条语句,更能深入的理解pwm的配置过程,并可举一反三地理解其他外设的配置
要具备的C语言基础知识:宏定义define、typedef ,结构体,结构体指针,位域
先看整个程序工程:https://bbs.21ic.com/viewthread.php?tid=271140&highlight=pwm
(一)、DrvPWM_SelectClockSource(DRVPWM_TIMER0,DRVPWM_HCLK);
//选择系统时钟作为PWM的时钟
该函数传递了两个参数:DRVPWM_TIMER0,DRVPWM_HCLK
利用keil 的便捷查看:
找到
知道了传递的参数是0x00和2,接下来看看函数是怎么操作的
同样在函数名上点右键:
打开该函数:
void DrvPWM_SelectClockSource(uint8_t u8Timer, uint8_t
u8ClockSourceSelector)
{
switch (u8Timer & 0x07)
{
case DRVPWM_TIMER0:
case DRVPWM_TIMER1:
SYSCLK->CLKSEL1.PWM01_S
= u8ClockSourceSelector;
break;
case DRVPWM_TIMER2:
case DRVPWM_TIMER3:
SYSCLK->CLKSEL1.PWM23_S
= u8ClockSourceSelector;
break;
case DRVPWM_TIMER4:
case DRVPWM_TIMER5:
SYSCLK->CLKSEL2.PWM45_S
= u8ClockSourceSelector;
break;
case DRVPWM_TIMER6:
case DRVPWM_TIMER7:
SYSCLK->CLKSEL2.PWM67_S
= u8ClockSourceSelector;
break;
}
}
由前面传递的参数0x00和2,可知道函数执行
SYSCLK->CLKSEL1.PWM01_S = u8ClockSourceSelector;
关键的SYSCLK->CLKSEL1.PWM01_S卡住了,主要因为对结构体的理解不够,只是在以前看书时略知一二,正好趁此机会深入了解
没有现成的书可参考,在网上找到该贴https://bbs.21ic.com/icview-268846-1-1.html,给了我很大的提示
首先SYSCLK是什么
#define SYSCLK ((SYSCLK_T *) SYSCLK_BASE)
这里定义为SYSCLK_T类型的指针,其值为SYSCLK_BASE
1. SYSCLK_T定义
typedef struct
{
SYSCLK_PWRCON_T PWRCON;
SYSCLK_AHBCLK_T AHBCLK;
SYSCLK_APBCLK_T APBCLK;
SYSCLK_CLKSTATUS_T CLKSTATUS;
SYSCLK_CLKSEL0_T CLKSEL0;
SYSCLK_CLKSEL1_T CLKSEL1;
SYSCLK_CLKDIV_T CLKDIV;
SYSCLK_CLKSEL2_T CLKSEL2;
SYSCLK_PLLCON_T PLLCON;
SYSCLK_FRQDIV_T FRQDIV;
} SYSCLK_T;
即SYSCLK_T等同于一个结构体类型,可用于定义结构体变量。
2. SYSCLK_BASE的定义
#define SYSCLK_BASE (AHB_BASE + 0x00200)
又#define AHB_BASE (( uint32_t)0x50000000)
所以,SYSCLK_BASE代表的是物理地址0x5000_0200,由NUC1XX参考手册(5.2.1 P91)得知该地址是时钟控制寄存器的首地址
综上:SYSCLK代表指向地址0x5000_0200的SYSCLK_T结构体指针。
其次:SYSCLK->CLKSEL1.PWM01_S
1.指向SYSCLK_T成员CLKSEL1的地址,该偏移地址为0x14
下面看一下该地址是怎么得来的
先要了解下M0的存储结构,从手册可看到M0的每个寄存器为32位(bit),即占4字节(byte)
在结构体SYSCLK_T中CLKSEL1被定义为SYSCLK_CLKSEL1_T
2.SYSCLK_CLKSEL1_T又是一个结构体类型,定义如下
typedef struct
{
__IO uint32_t WDT_S:2;
__IO uint32_t ADC_S:2;
__I uint32_t
RESERVE1:4;
__IO uint32_t TMR0_S:3;
__I uint32_t
RESERVE2:1;
__IO uint32_t TMR1_S:3;
__I uint32_t
RESERVE3:1;
__IO uint32_t TMR2_S:3;
__I uint32_t
RESERVE4:1;
__IO uint32_t TMR3_S:3;
__I uint32_t
RESERVE5:1;
__IO uint32_t UART_S:2;
__IO uint32_t CAN_S:2;
__IO uint32_t PWM01_S:2;
__IO uint32_t PWM23_S:2;
} SYSCLK_CLKSEL1_T;
可以看到,该结构体是有别于其他的结构体类型,里面的成员是位域的形式,这就说明SYSCLK_CLKSEL1_T是32位的结构体,其成员则指具体的某一位。
所以SYSCLK_T的每个成员占32位即4字节,从而可将其各个偏移地址列出来
typedef struct
{
SYSCLK_PWRCON_T PWRCON; 0x00
SYSCLK_AHBCLK_T AHBCLK; 0x04
SYSCLK_APBCLK_T APBCLK; 0x08
SYSCLK_CLKSTATUS_T CLKSTATUS; 0x0C
SYSCLK_CLKSEL0_T CLKSEL0; 0x10
SYSCLK_CLKSEL1_T CLKSEL1; 0x14
SYSCLK_CLKDIV_T CLKDIV; 0x18
SYSCLK_CLKSEL2_T CLKSEL2; 0x1C
SYSCLK_PLLCON_T PLLCON; 0x20
SYSCLK_FRQDIV_T FRQDIV; 0x24
} SYSCLK_T;
到这里即可算出SYSCLK->CLKSEL1.PWM01_S的具体地址了
0x5000_0200+0x04第28、29位,这两位就是 时钟源选择控制寄存器1(CLKSEL1)中的 PWM1 与 PWM0的时钟源选择.
所以SYSCLK->CLKSEL1.PWM01_S= u8ClockSourceSelector;是向时钟源选择控制寄存器1(CLKSEL1)的第29、28位写入二进制数10,选择HCLK作为PWM0的时钟源
(二)、DrvGPIO_InitFunction(E_FUNC_PWM01);
// 设置GPIO口的PWM01功能
该函数的原型就不列了,只看函数会用到的语句,如下图
首先来看传递的具体参数&SYS->GPAMFP,有了前面的了解现在能猜到,这个是某个寄存器的地址
#define SYS ((GCR_T*) GCR_BASE)
1、SYS是GCR_T结构体类型指针,指向首地址GCR_BASE
得出GCR_BASE=0x50000000
2、SYS->GPAMFP
找出GPAMFP的偏移地址
typedef struct
{
GCR_PDID_T PDID; 0x00
GCR_RSTSRC_T RSTSRC; 0x04
GCR_IPRSTC1_T IPRSTC1; 0x08
GCR_IPRSTC2_T IPRSTC2; 0x0C
GCR_CPR_T CPR; 0x10
uint32_t RESERVE0; 0x14
GCR_BODCR_T BODCR; 0x18
GCR_TEMPCR_T TEMPCR; 0x1C
uint32_t RESERVE1; 0x20
GCR_PORCR_T PORCR; 0x24
uint32_t RESERVE2[2]; 0x28 注意:此处是个数组
GCR_GPAMFP_T GPAMFP; 0x30
GCR_GPBMFP_T GPBMFP; 0x34
GCR_GPCMFP_T GPCMFP; 0x38
GCR_GPDMFP_T GPDMFP; 0x3C
GCR_GPEMFP_T GPEMFP; 0x40
uint32_t RESERVE3[3]; 0x44
GCR_ALTMFP_T ALTMFP; 0x50
uint32_t RESERVE4[43]; 0x54
GCR_REGLOCK_T REGLOCK; 0x100
uint32_t RESERVE5[3]; 0x104
GCR_RCADJ_T RCADJ; 0x110
} GCR_T;
因此:&SYS->GPAMFP所指向的地址是:0x50000000+0x30
由手册知这个地址是:多功能GPIOA控制寄存器((GPA_MFP)
再回过来看看outpw(&SYS->GPAMFP,inpw(&SYS->GPAMFP) | (0x3<<12))的定义
大意是将value赋给port所指向的地址,因此outpw(&SYS->GPAMFP,inpw(&SYS->GPAMFP) | (0x3<<12))就是将&SYS->GPAMFP的第13、12位置1
由手册(P104)知是将端口PA13和PA12设置为PWM
函数DrvGPIO_InitFunction();内的其他语句也是实现类似的给某个寄存器赋值,设置端口功能
(三)、DrvPWM_Open();
//使能PWM时钟并复位
定义:
/*Description: Enable PWM engine clock and reset PWM */
/* */
/*---------------------------------------------------------------------------------------------------------*/
void DrvPWM_Open(void)
{
outp32(&SYSCLK->APBCLK,inp32(&SYSCLK->APBCLK) | 0x00F00000);//第一句
outp32(&SYS->IPRSTC2,inp32(&SYS->IPRSTC2) | 0x00300000); //第二句
outp32(&SYS->IPRSTC2,inp32(&SYS->IPRSTC2) & ~0x00300000); //第三句
}
简单的分析每句的功能
第一句、找出&SYSCLK->APBCLK对应地址,
在前面已知道SYSCLK代表指向地址0x5000_0200的SYSCLK_T结构体指针,其成员APBCLK的偏移地址是0x08.
对照手册P91找到CLK_BA
P161找到APBCLK 确定APBCLK的地址:0x5000_0208
P166 因此该句实现:使能所有pwm时钟
第二句、&SYS->IPRSTC2
SYS是指向地址0x5000_0000的GCR_T结构体类型指针,其成员IPRSTC2的偏移地址是0x0C
对照手册P98 确定地址和功能:向地址0x5000_000C的21、22位写入1,复位PWM模块
第三句、向地址0x5000_000C的21、22位写入0, PWM模块正常工作
分析完这三个函数,后面的程序也就无需分析也能知道函数内部是具体怎么操作硬件的了。
参考:
【1】PWM例程https://bbs.21ic.com/viewthread.php?tid=271140&highlight=pwm
【2】新塘官方BSP例程:NUC100SeriesBSP_v1.05.002
【3】Timer应用之理解C语言位域:https://bbs.21ic.com/icview-268846-1-1.html
【4】NUC1XX参考手册
完整文件 |