[菜农助学交流] 第四批 笔记四—PWM初始化程序完全解析

[复制链接]
 楼主| zxcscm 发表于 2012-2-7 13:25 | 显示全部楼层 |阅读模式
本帖最后由 zxcscm 于 2012-2-7 13:35 编辑

园地里有很多关于PWM的例子,但是只看这些例子只能知其然不知所以然。索性逐字逐句地分析每条语句,更能深入的理解pwm的配置过程,并可举一反三地理解其他外设的配置
要具备的C语言基础知识:宏定义definetypedef 结构体,结构体指针,位域

先看整个程序工程:https://bbs.21ic.com/viewthread.php?tid=271140&highlight=pwm

(一)、DrvPWM_SelectClockSource(DRVPWM_TIMER0,DRVPWM_HCLK);
//选择系统时钟作为PWM的时钟

该函数传递了两个参数:DRVPWM_TIMER0,DRVPWM_HCLK

利用keil 的便捷查看:


找到




知道了传递的参数是0x002,接下来看看函数是怎么操作的
同样在函数名上点右键:




打开该函数:

  1. void DrvPWM_SelectClockSource(uint8_t u8Timer, uint8_t
  2. u8ClockSourceSelector)

  3. {

  4.        switch (u8Timer & 0x07)

  5.        {

  6.               case DRVPWM_TIMER0:

  7.               case DRVPWM_TIMER1:

  8.                      SYSCLK->CLKSEL1.PWM01_S
  9. = u8ClockSourceSelector;                           

  10.                      break;

  11.               case DRVPWM_TIMER2:

  12.               case DRVPWM_TIMER3:

  13.                      SYSCLK->CLKSEL1.PWM23_S
  14. = u8ClockSourceSelector;

  15.                      break;            

  16.               case DRVPWM_TIMER4:

  17.               case DRVPWM_TIMER5:

  18.                      SYSCLK->CLKSEL2.PWM45_S
  19. = u8ClockSourceSelector;                           

  20.                      break;

  21.               case DRVPWM_TIMER6:

  22.               case DRVPWM_TIMER7:

  23.                      SYSCLK->CLKSEL2.PWM67_S
  24. = u8ClockSourceSelector;

  25.                      break;     

  26.        }

  27. }


由前面传递的参数0x002,可知道函数执行
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定义

  1. typedef struct

  2. {

  3.     SYSCLK_PWRCON_T    PWRCON;

  4.     SYSCLK_AHBCLK_T    AHBCLK;

  5.     SYSCLK_APBCLK_T    APBCLK;

  6.     SYSCLK_CLKSTATUS_T CLKSTATUS;

  7.     SYSCLK_CLKSEL0_T   CLKSEL0;

  8.     SYSCLK_CLKSEL1_T   CLKSEL1;

  9.     SYSCLK_CLKDIV_T    CLKDIV;

  10.     SYSCLK_CLKSEL2_T   CLKSEL2;

  11.        SYSCLK_PLLCON_T    PLLCON;

  12.        SYSCLK_FRQDIV_T    FRQDIV;

  13.    

  14. } 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_0200SYSCLK_T结构体指针。

其次:SYSCLK->CLKSEL1.PWM01_S
1.指向SYSCLK_T成员CLKSEL1的地址,该偏移地址为0x14
下面看一下该地址是怎么得来的
先要了解下M0的存储结构,从手册可看到M0的每个寄存器为32位(bit),即占4字节(byte
在结构体SYSCLK_TCLKSEL1被定义为SYSCLK_CLKSEL1_T
2.SYSCLK_CLKSEL1_T又是一个结构体类型,定义如下



  1. typedef struct

  2. {

  3.     __IO uint32_t  WDT_S:2;

  4.     __IO uint32_t  ADC_S:2;

  5.     __I  uint32_t
  6. RESERVE1:4;

  7.     __IO uint32_t  TMR0_S:3;

  8.     __I  uint32_t
  9. RESERVE2:1;

  10.     __IO uint32_t  TMR1_S:3;

  11.     __I  uint32_t
  12. RESERVE3:1;

  13.     __IO uint32_t  TMR2_S:3;

  14.     __I  uint32_t
  15. RESERVE4:1;

  16.     __IO uint32_t  TMR3_S:3;

  17.     __I  uint32_t
  18. RESERVE5:1;

  19.     __IO uint32_t  UART_S:2;

  20.     __IO uint32_t  CAN_S:2;

  21.     __IO uint32_t  PWM01_S:2;

  22.     __IO uint32_t  PWM23_S:2;

  23. } SYSCLK_CLKSEL1_T;

可以看到,该结构体是有别于其他的结构体类型,里面的成员是位域的形式,这就说明SYSCLK_CLKSEL1_T32位的结构体,其成员则指具体的某一位。
所以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+0x042829位,这两位就是 时钟源选择控制寄存器1CLKSEL1)中的 PWM1 PWM0的时钟源选择.

所以SYSCLK->CLKSEL1.PWM01_S= u8ClockSourceSelector;向时钟源选择控制寄存器1CLKSEL1)的第2928位写入二进制数10,选择HCLK作为PWM0的时钟源




(二)、DrvGPIO_InitFunction(E_FUNC_PWM01);     
//  设置GPIO口的PWM01功能

该函数的原型就不列了,只看函数会用到的语句,如下图



首先来看传递的具体参数&SYS->GPAMFP,有了前面的了解现在能猜到,这个是某个寄存器的地址
#define SYS                 ((GCR_T*) GCR_BASE)
1SYSGCR_T结构体类型指针,指向首地址GCR_BASE



得出GCR_BASE=0x50000000
2SYS->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的第1312位置1
由手册(P104)知是将端口PA13PA12设置为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_0200SYSCLK_T结构体指针,其成员APBCLK的偏移地址是0x08.
对照手册P91找到CLK_BA  
P161找到APBCLK  确定APBCLK的地址:0x5000_0208   
P166  因此该句实现:使能所有pwm时钟
第二句、&SYS->IPRSTC2
SYS是指向地址0x5000_0000GCR_T结构体类型指针,其成员IPRSTC2的偏移地址是0x0C
对照手册P98 确定地址和功能:向地址0x5000_000C2122位写入1,复位PWM模块
第三句、向地址0x5000_000C2122位写入0 PWM模块正常工作

分析完这三个函数,后面的程序也就无需分析也能知道函数内部是具体怎么操作硬件的了。

参考:
1PWM例程https://bbs.21ic.com/viewthread.php?tid=271140&highlight=pwm
2】新塘官方BSP例程:NUC100SeriesBSP_v1.05.002
3Timer应用之理解C语言位域:https://bbs.21ic.com/icview-268846-1-1.html
4NUC1XX参考手册




完整文件

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
xyz549040622 发表于 2012-2-7 13:54 | 显示全部楼层
很详细,拜读了,顶
raja21 发表于 2012-7-3 17:08 | 显示全部楼层
很详细,谢谢楼主!
ljp98 发表于 2012-7-11 08:56 | 显示全部楼层
楼主真细心。顶楼主。
lee9888 发表于 2012-7-11 15:29 | 显示全部楼层
我也是用示波器边调边改才出来想要的效果
luabc 发表于 2012-7-21 17:12 | 显示全部楼层
似乎看不懂
nomorehest 发表于 2013-2-24 01:06 | 显示全部楼层
楼主太用心了, 感谢了.
a437916817 发表于 2013-4-9 15:54 | 显示全部楼层
多谢分享
您需要登录后才可以回帖 登录 | 注册

本版积分规则

5

主题

628

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部