分享

如何在PIC单片机的GPIO引脚上生成PWM信号

 共同成长888 2020-05-28

在这里插入图片描述
PWM信号生成是每个嵌入式工程师工具库中的重要工具,它们非常适用于控制伺服电机位置,在转换器/逆变器中切换少量电源电子集成电路等许多应用,甚至用于简单的LED亮度控制。在pic 微控制器中, pwm 信号可以通过设置所需的寄存器使用比较、捕获和 pwm (ccp) 模块生成。

如果我们使用CCP模块,PIC16F877A 只能在引脚RC1和RC2产生PWM信号,由此我们可能会遇到需要更多引脚来实现PWM功能的情况。例如,我想控制6个RC伺服电机,CCP模块是不行的。于是在这种情况下,我们可以使用定时器模块对GPIO引脚进行编程以产生PWM信号,这样我们就可以产生尽可能多的PWM信号。还可以考虑其他硬件技术,比如使用多路复用器,但是为什么要在通过编程可以实现同样的目标时,还去考虑其他硬件。因此,在本教程中,我们将学习如何将PIC GPIO引脚转换为PWM引脚,并进行测试,我们将在proteus上使用数字示波器进行仿真,同时使用PWM信号控制伺服电机的位置,并调节电位器改变其占空比。

什么是PWM信号?

在我们详细介绍之前,让我们先了解一下PWM信号是什么。 脉冲宽度调制(PWM) 是一种数字信号,最常用于控制电路。该信号在预定义的时间和速度中设置为高(5v)和低(0v)。信号保持高电平的称为“接通时间” ,信号保持低电平的称为“断开时间”。 如下所述,PWM有两个重要参数:

PWM占空比

PWM信号保持高电平(导通时间)的时间百分比称为占空比。如果信号始终为ON,则它处于100%占空比,如果它始终处于关闭状态,则占空比为0%。

Duty Cycle =Turn ON time/ (Turn ON time + Turn OFF time)

在这里插入图片描述

PWM的频率

PWM信号的频率决定PWM完成一个周期的速度。一个周期完成PWM信号的ON和OFF,如上图所示。在我们的教程中,我们将设置5KHz的频率。

计算PWM的占空比

要在GPIO引脚上产生PWM信号,我们必须简单地将其打开和关闭一段预定义的时间。但它并不像听起来那么简单。这个打开和关闭时间应该对每个周期都是准确的,因此我们根本不能使用延迟功能,因此我们使用定时器模块定时中断。此外,我们还要考虑占空比和我们生成的PWM信号的频率。程序中使用以下变量名来定义参数。

变量名

PWM_Frequency
PWM信号频率
T_TOTAL
PWM的一个完整周期总时间
T_ON
PWM信号的打开时间
T_OFF
PWM信号的关机时间
DUTY_CYCLE
PWM信号占空比

所以现在,让我们做数学。

这是标准公式,其中频率只是时间的倒数。频率值必须由用户根据应用要求来决定和设置。

T_TOTAL = (1/PWM_Frequency)

当用户更改占空比值时,我们的程序应自动调整T_ON时间和T_OFF时间。因此,上述公式可用于 根据Duty_Cycle和T_TOTAL的值计算T_ON。

T_ON = (Duty_Cycle*T_TOTAL)/100

由于一个完整周期的PWM信号的总时间将是导通时间和关断时间的总和。我们可以 计算关闭时间T_OFF, 如上所示。

T_OFF = T_TOTAL – T_ON

鉴于这些公式,我们可以开始编程PIC单片机。该程序涉及PIC定时器模块 和PIC ADC模块 ,根据POT的ADC值,根据变化的占空比创建PWM信号。

编程PIC在GPIO引脚上生成PWM

在本节中,让我们了解程序的实际编写方式。像所有程序一样,我们首先设置配置位。我已经使用了memory views选项为我设置它。

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)

#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)

#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)

#pragma config BOREN = ON      // Brown-out Reset Enable bit (BOR enabled)

#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)

#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)

#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)

#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// #pragma config statements should precede project file includes.

// Use project enums instead of #define for ON and OFF.

#include <xc.h>

然后我们 提到在硬件中使用的时钟频率,这里我的硬件使用20MHz晶振,你可以根据你的硬件输入值。 接下来是PWM信号的频率值。 由于我的目标是控制一个需要PWM频率为50Hz的RC伺服电机,我已将0.05KHz设置为频率值,您也可以根据您的应用要求更改此值。

#define _XTAL_FREQ 20000000

#define PWM_Frequency 0.05 // in KHz (50Hz)

现在,我们有频率值,我们可以使用上面讨论的公式, 计算出T_TOTAL, 结果除以10以后,得到以毫秒为单位的时间值。在我的情况下,T_TOTAL的值将是2毫秒。

int T_TOTAL = (1/PWM_Frequency)/10; //calculate Total Time from frequency (in milli sec)) //2msec

接下来,我们初始化ADC模块以读取电位计的位置。现在让我们检查一下main函数。

在主函数内部,我们配置定时器模块。这里我将Timer模块配置为每0.1ms溢出一次。可以使用下面的公式计算时间的值。

RegValue = 256-((Delay * Fosc)/(Prescalar*4))  delay in sec and Fosc in hz

在我的情况下,延迟为0.0001秒(0.1ms),预分频为64,Fosc为20MHz,我的寄存器(TMR0)的值应该是248,所以配置看起来像这样

/*****Port Configuration for Timer ******/    
OPTION_REG = 0b00000101;  // Timer0 with external freq and 64 as prescalar // Also Enables PULL UPs    

TMR0=248;       // Load the time value for 0.0001s; delayValue can be between 0-256 only    

TMR0IE=1;       //Enable timer interrupt bit in PIE1 register    

GIE=1;          //Enable Global Interrupt    

PEIE=1;         //Enable the Peripheral Interrupt    

/***********______***********/

我们必须设置输入和输出配置。这里我们使用AN0引脚读取ADC值,使用PORTD引脚输出PWM信号。因此,将它们作为输出引脚启动,并使用下面的代码行使它们变低。

/*****Port Configuration for I/O ******/    
TRISD = 0x00; //Instruct the MCU that all pins on PORT D are output    

PORTD=0x00; //Initialize all pins to 0    
/***********______***********/

在while无线循环中,我们要计算占空比中T_ON的时间。导通时间 和占空比根据POT的位置变化,因此我们在 while 循环内重复执行,如下所示。0.0976是必须乘以1024得到100并且计算T_ON的值,我们将它乘以10以得到毫秒的值。

while(1)    

      {      

      POT_val = (ADC_Read(0)); //Read the value of POT using ADC      

            Duty_cycle = (POT_val * 0.0976); //Map 0 to 1024 to 0 to 100      

            T_ON = ((Duty_cycle * T_TOTAL)*10 / 100); //Calculate On Time using formulae unit in milli seconds      

             __delay_ms(100);      

         }

由于定时器每隔0.1ms设置为溢出, 定时器中断服务程序ISR将每0.1ms调用一次。在服务程序中,我们使用一个名为count的变量,每0.1ms递增一次。这样我们就可以跟踪时间。

if(TMR0IF==1) // Timer flag has been triggered due to timer overflow -> set to overflow for every 0.1ms    

{        

    TMR0 = 248;     //Load the timer Value        

    TMR0IF=0;       // Clear timer interrupt flag        

     count++; //Count increments for every 0.1ms -> count/10 will give value of count in ms    

 }

最后,可以根据T_ON和T_OFF的值切换GPIO引脚。我们有 count 变量,以毫秒为单位跟踪时间。因此我们使用该变量来检查时间是否小于导通时间,如果是,那么我们将GPIO引脚保持打开,否则我们将其关闭,并保持关闭直到新周期开始。这可以通过将其与一个PWM周期的总时间进行比较来完成。相同的代码如下所示

if (count <= (T_ON) ) //If time less than on time        

         RD1=1; //Turn on GPIO    

    else        

          RD1=0; //Else turn off GPIO    

     if (count >= (T_TOTAL*10) ) //Keep it turned off until a new cycle starts        

          count=0;

电路图

电路图非常简单,只需用振荡器为PIC供电,并将电位器连接到引脚AN0和伺服电机连接到引脚RD1,我们就可以使用GPIO引脚获取PWM信号,我选择RD1只是随机的。电位器和伺服电机均由5V供电,由7805调节,如下图所示。
在这里插入图片描述

模拟

为了模拟项目,我使用了我的proteus软件。构建下面显示的电路并将代码链接到模块并运行它。根据我们的程序, 您应该在RD1 GPIO 引脚上获得PWM 信号, PWM 的占空比应该根据电位器的位置进行控制。下面的GIF 显示了PWM 信号和伺服电机在通过电位器改变ADC值时的响应情况。

Simulation-for-Generating-PWM-signals-on-GPIO-pins-of-PIC-Microcontroller.gif在这里插入图片描述

使用PIC单片机控制伺服电机的硬件设置

我的完整硬件设置如下所示,对于那些关注我的教程的人来说,这个板应该看起来很熟悉,它与我迄今为止在我所有教程中使用的板相同。

上传程序并更改电位计,您应该看到伺服根据电位计的位置改变而改变位置。

完整代码

/*
* File:   PIC_GPIO_PWM.c
* Author: Aswinth
*
* Created on 17 October, 2018, 11:59 AM
*/
// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include <xc.h>
#define _XTAL_FREQ 20000000
#define PWM_Frequency 0.05 // in KHz (50Hz)
//TIMER0    8-bit  with 64-bit Prescalar
//$$RegValue = 256-((Delay * Fosc)/(Prescalar*4))  delay in sec and Fosc in hz  ->Substitute value of Delay for calculating RegValue
int POT_val; //variable to store value from ADC
int count; //timer variable
int T_TOTAL = (1/PWM_Frequency)/10; //calculate Total Time from frequency (in milli sec)) //2msec
int T_ON=0; //value of on time
int Duty_cycle; //Duty cycle value
void ADC_Initialize() //Prepare the ADC module
{
 ADCON0 = 0b01000001; //ADC ON and Fosc/16 is selected
 ADCON1 = 0b11000000; // Internal reference voltage is selected
}
unsigned int ADC_Read(unsigned char channel) //Read from ADC
{
 ADCON0 &= 0x11000101; //Clearing the Channel Selection Bits
 ADCON0 |= channel<<3; //Setting the required Bits
 __delay_ms(2); //Acquisition time to charge hold capacitor
 GO_nDONE = 1; //Initializes A/D Conversion
 while(GO_nDONE); //Wait for A/D Conversion to complete
 return ((ADRESH<<8)+ADRESL); //Returns Result
}
void interrupt timer_isr()
{  
   if(TMR0IF==1) // Timer flag has been triggered due to timer overflow -> set to overflow for every 0.1ms
   {
       TMR0 = 248;     //Load the timer Value
       TMR0IF=0;       // Clear timer interrupt flag
       count++; //Count increments for every 0.1ms -> count/10 will give value of count in ms
   }
   
   if (count <= (T_ON) )
       RD1=1;
   else
       RD1=0;
   
   if (count >= (T_TOTAL*10) )
       count=0;
}
void main()
{    
/*****Port Configuration for Timer ******/
   OPTION_REG = 0b00000101;  // Timer0 with external freq and 64 as prescalar // Also Enables PULL UPs
   TMR0=248;       // Load the time value for 0.0001s; delayValue can be between 0-256 only
   TMR0IE=1;      //Enable timer interrupt bit in PIE1 register
   GIE=1;          //Enable Global Interrupt
   PEIE=1;         //Enable the Peripheral Interrupt
   /***********______***********/  
   /*****Port Configuration for I/O ******/
   TRISD = 0x00; //Instruct the MCU that all pins on PORT D are output
   PORTD=0x00; //Initialize all pins to 0
   /***********______***********/  
   
   ADC_Initialize();
   while(1)
   {
      POT_val = (ADC_Read(0)); //Read the value of POT using ADC
     
      Duty_cycle = (POT_val * 0.0976); //Map 0 to 1024 to 0 to 100
     
      T_ON = ((Duty_cycle * T_TOTAL)*10 / 100); //Calculate On Time using formulae unit in milli seconds
      __delay_ms(100);    
     
   }
   
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多