原文出自枯叶之碟的博客
一.输出比较初始化 步骤: 1.复用端口映射为OCx 例如:RPA0Rbits.RPA0R=0b0101; 即RPA0引脚作为外设OC1使用 见附一 2.OCM<2:0>:输出比较模式选择位 例如:OC1CON=0X06; //输出比较端口1配置为PWM故障禁止模式。 3.OC32<5>:32 位比较模式位 例如:OC1CONCLR=0X010; //将第五位清零,设置为16位单定时器模式。 4.OCTSEL<3>:输出比较定时器选择位 例如:OC1CONSET=0X08; //将第三位设置为一,选择定时器3为基时钟 5.定时器使能初始化 例如:OpenTimer3(T1_ON|T1_SOURCE_INT|T1_PS_1_1,pwmn); Pwmn周期数={ FB外设/pwmfp频率 } - 1; 6.ON<15>:输出比较外设使能位 例如:OC1CONSET=0X8000; //将第15位置一,输出比较使能。 二.中断触发条件 单比较模式 ?? 比较匹配事件强制OCx 引脚为高电平;该引脚的初始状态为低电平。在发生单比较匹配事件 时,产生中断。 ?? 比较匹配事件强制OCx 引脚为低电平;该引脚的初始状态为高电平。在发生单比较匹配事件时,产生中断。 ?? 比较匹配事件使OCx 引脚电平翻转。翻转事件是连续的,且每次翻转事件都会产生一次中断。 双比较模式 当OCx引脚被驱动为低电平(单脉冲的下降沿)时,相应通道的中断标志OCxIF会置为有效。 PWM模式 TyIF 中断标志在每个PWM 周期边界处置为有效。 当使能了具有故障保护输入模式的PWM 时,必须通过将相应的TRIS SFR 位置1 以将OCFx 故 障引脚配置为输入。选择PWM 故障模式时, OCFx 故障输入引脚不会自动配置为输入。 三.计算各项值 所需的PWM通过写入OCxRS 寄存器来指定PWM 占空比。可以在任何时候写OCxRS 寄存器,但是在PRy和TMRy 发生匹配(即周期结束)前占空比值不会被锁存到OCxR 中。 PWM 周期 = [(PR 1) ?? TPB ?? (TMR 预分频值)] 或T=PR 1/(Fpb/PS) PWM 频率 = 1/[PWM 周期] 最大PWM 分辨率:在一个PWM周期内有n个时基(PR),为2的x次方,分辨率为x。 以频率为52.08 kHz为例 FPB = 10 MHz Timer2 预分频比设置: 1:1 1/52.08 kHz = (PR2 1) ?? TPB ?? (Timer2 预分频值)=(PR2 1)/(FPB/Timer2预分频) 19.20 us = (PR2 1) ?? 0.1 us ?? (1) PR2 = 191 确定可用于52.08 kHz PWM 频率和10 MHz 外设总线时钟速率的占空比的最大分辨率。 1/52.08 kHz = 2^PWM 分辨率?? 1/10 MHz ?? 1 19.20 us = 2^PWM 分辨率?? 100 ns ?? 1 192 = 2^PWM 分辨率 log10(192) = (PWM 分辨率) ?? log10(2) PWM 分辨率 = 7.6 位 四.拓展 #用PWM绘制新波形 峰峰值最大为PR定时器数。 每个PWM周期为一个样本 所需波形频率: 所需频率=1/(n个样本*PWM周期) 每个样本波形的幅度值(占空比*PR): 波形/n,将n个值列入数组表格,OCxRS引用。 每个PWM周期输出通过RC滤波电路转换为模拟信号,约为一条幅值为 高电平*占空比 的直线, 通过改变占空比控制赋值y轴,再通过控制周期数控制产生的x轴 正弦波波形产生,占空比计算值 OC1RS=偏移量 振幅*SIN(2*pi/周期样本值),但如此短的时间无法 来的及计算sin,最好制成表格数组引用。 #发出声音 绘制相应的频率可发出相应的音高(do,re,me…),再调整波形幅度决定音色(不同的乐器)。 举例说明 一. 用PWM制作呼吸灯效果 #include #pragma config FPLLIDIV = DIV_2 // PLL Input Divider (2x Divider) #pragma config FPLLMUL = MUL_24 #pragma config FPLLODIV = DIV_2 #pragma config FPBDIV = DIV_1 #pragma config FNOSC = FRCPLL #pragma config FUSBIDIO = OFF #pragma config FWDTEN = OFF #pragma config JTAGEN = OFF int pwm1,pwmn,pwmfp,count,pwm_g; void PWMinint() { OC1CON=0; //关闭 初始都为零,定时器二,16位模式 OC1CON=0x06; //PWM无故障模式 OC1R=10000; //初始占空比为10000 OC1RS=10000; pwmn=48000000/pwmfp-1; PWM周期数 OpenTimer2(T2_ON|T2_SOURCE_INT|T2_PS_1_1,pwmn); //初始定时器2 OC1CONSET=0x8000; //开启输出比较使能 } void __ISR(_TIMER_2_VECTOR,ipl3) Timer2hander(void) //中断 { mT2ClearIntFlag(); if(pwm_g==0) count ; //当count越大亮度越低反之详见电路图 else count--; if(count>4410) pwm_g=1; if(count==0) pwm_g=0; OC1RS=count*pwmn/4410; //4410/4410=1s一翻转 } int main() { RPB7Rbits.RPB7R=0b0101; pwmfp=4410; PWMinint(); mT2SetIntPriority(3); mT2IntEnable(1); INTEnableSystemMultiVectoredInt(); while(1); } 二.按键实时控制PWM #include // Configuration Bit settings // SYSCLK = 48 MHz (8MHz Crystal / FPLLIDIV * FPLLMUL / FPLLODIV) // PBCLK = 48 MHz (SYSCLK / FPBDIV) // Primary Osc w/PLL (XT ,HS ,EC PLL) // WDT OFF #pragma config FPLLMUL = MUL_24, FPLLIDIV = DIV_2, FPLLODIV = DIV_2, FWDTEN = OFF #pragma config POSCMOD = OFF, FNOSC = FRCPLL, FPBDIV = DIV_1,FSOSCEN = OFF #pragma config FUSBIDIO = OFF //FUSBIDIO????? #pragma config FVBUSONIO = OFF #pragma config JTAGEN = OFF //JTAG disable #pragma config CP = OFF #pragma config DEBUG = ON // Period needed for timer 1 to trigger an interrupt every 0.1 second // (48MHz PBCLK / 1 = 48000000KHz Timer 1 clock) #define PERIOD 48000 //48000/48000000 = 0.001s = 1ms #define BTN_DELAY 5 //2*1=2ms #define SYS_FREQ (48000000L) //???????? typedef enum //PRO_Status { SHOW_PERIOD = 0, SHOW_DUTY, SHOW_SET_PERIOD, SHOW_SET_DUTY, SET_PERIOD, SET_DUTY }PRO_STATUS; PRO_STATUS g_status=SHOW_PERIOD; UINT16 g_period=40,g_duty=20; //初始周期40,占空比20 UINT16 g_set_period=40,g_set_duty=20; int g_led_cnt=0,g_led_flag=0,g_btn_cnt=0,g_btn_flag=0,flag=0; //显示于按键标志,用于周期性定时处理LED显示和按键 unsigned char Led_lib[] = { 0x42, 0xf3, 0x86, 0xa2, 0x33, 0x2a, 0x0a, 0xf2, 0x02, 0x22, //0-9 0x40, 0xf1, 0x84, 0xa0, 0x31, 0x28, 0x08, 0xf0, 0x00, 0x20, //0.-9. 0x1e, 0x2a, 0x0e, 0x0f, 0xbf, 0x23, 0x9b, 0x8b}; //FSEt-yno //LED字库SPI初始化 void SpiInitDevice() { // 8 bits/char, input data sampled at end of data output time SpiOpenFlags oFlags = SPI_OPEN_MSTEN | SPI_OPEN_CKP_HIGH | SPI_OPEN_MODE8 | SPI_OPEN_ON; PORTSetPinsDigitalOut(IOPORT_B, BIT_9);//作为锁存,1锁存,0开放 PPSOutput(2, RPB8, SDO2); // Set RB8 pin as output for SDO2 // Open SPI module, use SPI channel 2, use flags set above, Divide Fpb by 6 SpiChnOpen(2, oFlags, 6); } void SpiDoBurst(unsigned char *pBuff, unsigned char Len) { if (pBuff) { unsigned int i; PORTClearBits(IOPORT_B, BIT_9); for (i = 0; i < Len; i ) { SpiChnPutC(2, pBuff[ i]); } PORTSetBits(IOPORT_B, BIT_9); } } //LED初始化 void Led() { static unsigned char ledBuff[4] = {0x00, 0x00, 0x00, 0x00}; static int led = 0; int i,n; SpiDoBurst(ledBuff, 4); //LED显示 4,1,2,3 switch(g_status) { case SHOW_PERIOD: led=g_period; ledBuff[3]=0b00010110;//Led_lib[1]; break; case SHOW_DUTY: led=g_duty; ledBuff[3]=0b01001110;//Led_lib[2]; break; case SHOW_SET_PERIOD: led=g_set_period; ledBuff[3]=0b00010100;//Led_lib[3]; break; case SHOW_SET_DUTY: led=g_set_duty; ledBuff[3]=0b01001100;//Led_lib[4]; break; case SET_PERIOD: led=g_set_period; ledBuff[3]=0b00010100;//Led_lib[3]; break; case SET_DUTY: led=g_set_duty; ledBuff[3]=0b01001100;//Led_lib[4]; break; } i=led/100; i=i; ledBuff[0]=Led_lib[ i]; led=led0; i=led/10; ledBuff[1]=Led_lib[ i]; i=led; ledBuff[2]=Led_lib[ i]; n ; if(n=2) flag=1; } void pwminit() { RPB13Rbits.RPB13R=0b0101;//外设端口映射为OC4 OC4CON=0; OC4R=0; OC4RS=0; OC4CON=0X06; //PWM无故障模式 OpenTimer2(T2_ON|T2_SOURCE_INT|T2_PS_1_1,g_period); //基定时器初始 OC4CONSET=0X8000; //使能OC4 // mT2SetIntPriority(1); // mT2IntEnable(1); } //void __ISR(_TIMER_2_VECTOR,ipl1) Timer2(void) //{ // // mT2ClearIntFlag(); // OC4RS=g_duty; //若由输出比较的基定时器2刷新值,则会随着PWM周期的太小刷新过快,会与定时器1的中断多次冲突造成定时器1无法正常工作。 // PR2=g_period; //} void Timer1Init() { // Timer1@1ms OpenTimer1(T1_ON | T1_SOURCE_INT | T1_PS_1_1, PERIOD); // Set up the timer interrupt with a priority of 2 INTEnable(INT_T1, INT_ENABLED); INTSetVectorPriority(INT_TIMER_1_VECTOR, INT_PRIORITY_LEVEL_5); INTSetVectorSubPriority(INT_TIMER_1_VECTOR, INT_SUB_PRIORITY_LEVEL_0); } //??1???? void __ISR(_TIMER_1_VECTOR, ipl5) Timer1Handler(void) { // Clear the interrupt flag INTClearFlag(INT_T1); OC4RS=g_duty; PR2=g_period;//由定时器1统一更新周期和占空比 g_led_cnt ; if(g_led_cnt > 100) //0.1s { g_led_cnt = 0; g_led_flag = 1; } g_btn_cnt ; if(g_btn_cnt > 5) //5ms { g_btn_cnt = 0; g_btn_flag = 1; } } //设为数字端口,当有足够电压改变1与0的转换才有信号 void BtnInit() { ANSELAbits.ANSA0 = 0; ANSELAbits.ANSA1 = 0; // ANSELBbits.ANSB3 = 0; ANSELBbits.ANSB14 = 0; } //???? void Button(void) { static int btn0=0,btn1=0,btn2=0,btn3=0,n=0; if(PORTAbits.RA0 == 0) //sel { btn0 ; if(btn0 == BTN_DELAY) { switch(g_status) { case SHOW_PERIOD: g_status=SHOW_SET_PERIOD; break; case SHOW_DUTY: g_status=SHOW_SET_DUTY; break; case SHOW_SET_PERIOD: g_status=SHOW_PERIOD; break; case SHOW_SET_DUTY: g_status=SHOW_DUTY; break; } // if (g_status>=SHOW_SET_PERIOD) // { // g_status=SHOW_PERIOD; // OC4CONCLR=0X8000; // } // else // { // g_status=SHOW_SET_PERIOD; // g_set_period=g_period; // g_set_duty=g_duty; // // } } } else btn0 = 0; if(PORTAbits.RA1 == 0) // { btn1 ; if(btn1 == BTN_DELAY) { switch(g_status) { case SHOW_PERIOD: g_status=SHOW_DUTY; break; case SHOW_DUTY: g_status=SHOW_PERIOD; break; case SHOW_SET_PERIOD: if (g_set_period<999) g_set_period ; g_period=g_set_period; break; // g_status=SHOW_SET_DUTY; // break; case SHOW_SET_DUTY: if (g_set_duty<100) g_set_duty ; g_duty=g_set_duty; break; // g_status=SHOW_SET_PERIOD; // break; // case SET_PERIOD: // case SET_DUTY: } } } else btn1 = 0; if(PORTBbits.RB14 == 0) //- { btn2 ; if(btn2 == BTN_DELAY) { switch(g_status) { case SHOW_PERIOD: g_status=SHOW_DUTY; break; case SHOW_DUTY: g_status=SHOW_PERIOD; break; case SHOW_SET_PERIOD: if (g_set_period>0) g_set_period--; g_period=g_set_period; break; // g_status=SHOW_SET_DUTY; // break; case SHOW_SET_DUTY: if (g_set_duty>0) g_set_duty--; g_duty=g_set_duty; break; // g_status=SHOW_SET_PERIOD; // break; // case SET_PERIOD: // if (g_set_period>0) g_set_period--; // break; // case SET_DUTY: // if (g_set_duty>0) g_set_duty--; // break; } } } else btn2 = 0; // if(PORTBbits.RB3 == 0) //enter // { // btn3 ; // if(btn3 == BTN_DELAY) // { // switch(g_status) // { // case SHOW_PERIOD: // OC1CONCLR=0X8000; // break; // case SHOW_DUTY: // OC1CONCLR=0X8000; // break; // case SHOW_SET_PERIOD: // g_status=SET_PERIOD; // OC1CONCLR=0X8000; // break; // case SHOW_SET_DUTY: // g_status=SET_DUTY; // OC1CONCLR=0X8000; // break; // case SET_PERIOD: // g_period=g_set_period; // g_status=SHOW_PERIOD; // while(!flag); // flag=0; // pwminit(); // break; // case SET_DUTY: // g_duty=g_set_duty; // g_status=SHOW_PERIOD; // while(!flag); // flag=0; // pwminit(); // break; // } // } // } // else // btn3 = 0; } int main(int argc, char** argv) { int task=0; SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE); INTDisableInterrupts(); INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR); SpiInitDevice(); BtnInit(); Timer1Init(); pwminit(); INTEnableInterrupts(); while(1) { switch(task) { case 0: if(g_led_flag > 0) { g_led_flag = 0; Led(); } break; case 1: if(g_btn_flag > 0) { g_btn_flag = 0; Button(); } default: break; } task ; if(task > 1) task = 0; } return (EXIT_SUCCESS); } 附一: 表11-2: 输出引脚选择
例如:RPA0Rbits.RPA0R=0b0101;或 PPSOutput(1,RPA0,OC1); 第1组 即RPA0引脚作为OC1使用
例如:RPB15Rbits.RPB15R=0b0011; 或 PPSOutput(1,RPB15,SS1); 输入引脚映射选择
INT4Rbits.INT4R=0B0001; 或 PPSInput(3,SDI2,RPB6) 第3组 //将外部中断4映射给RPB3 SDI1Rbits.SDI1R=0B0110; 或 PPSInput(2,SDI1,RPC8) 第2组 //数据输入1口映射给RPC8 附二 初始化相关位定义 bit 15 ON:输出比较外设使能位 1 = 使能输出比较外设。 0 = 禁止输出比较外设,不会消耗电流。允许进行SFR 修改。该寄存器中其他位的状态不会受该位置1 或清零影响。 注: 使用1:1 PBCLK 分频比时,在清零模块ON 位的指令之后,用户的软件不应立即在SYSCLK 周期中读/ 写外设的SFR。 bit 14 FRZ:调试异常模式冻结位 1 = 在CPU 进入调试异常模式时停止工作 0 = 在CPU 进入调试异常模式时继续工作 注: FRZ 仅在调试异常模式下可写,在正常模式下强制为0。 bit 13 SIDL:IDLE (空闲)模式停止位 1 = 在CPU 进入IDLE (空闲)模式时停止工作 0 = 在IDLE (空闲)模式下继续工作 bit 12-6 保留:写入0 ;忽略读操作 bit 5 OC32:32 位比较模式位 1 = OCxR<31:0> 和/ 或OCxRS<31:0> 用于与32 位定时器源进行比较 0 = OCxR<15:0> 和OCxRS<15:0> 用于与16 位定时器源进行比较 bit 4 OCFLT:PWM 故障条件状态位(1) 1 = 发生了PWM 故障条件(仅可用硬件清零) 0 = 未发生PWM 故障条件 注: 仅当OCM<2:0> = 111 时,才使用该位。 bit 3 OCTSEL:输出比较定时器选择位 1 = Timer3 作为该OCMP 模块的时钟源 0 = Timer2 作为该OCMP 模块的时钟源 关于输出比较模块可用的特定时基,请参见器件数据手册。 bit 2-0 OCM<2:0>:输出比较模式选择位 111 = OCx 处于PWM 模式;故障引脚使能 110 = OCx 处于PWM 模式;故障引脚禁止 101 = 初始化OCx 引脚为低电平;在OCx 引脚上产生连续输出脉冲 100 = 初始化OCx 引脚为低电平;在OCx 引脚上产生单输出脉冲 011 = 比较事件使OCx 引脚电平翻转 010 = 初始化OCx 引脚为高电平;比较事件强制OCx 引脚为低电平 001 = 初始化OCx 引脚为低电平;比较事件强制OCx 引脚为高电平 000 = 输出比较外设被禁止,但会继续消耗电流 转载请说明出处谢谢 |