本程序用位控方式来实现10位的软件PWM

;转载自 http://martin.21ic.org

****************************************************************************
; 本程序用位控方式来实现10位的软件PWM(适合移植到其他没有硬件PWM的PIC中) *
; 电压输入脚在GP0,10位AD转换转换的结果改变PWM的占空比 *
; 程序编写:Martin 转载请注明出处 http://martin.21ic.org *
; ****************************************************************************
#include p12f675.inc ;包含MPLAB预定义的头文件
__CONFIG _CPD_OFF & _CP_OFF & _BODEN_ON & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT
;设定配置字信息,关代码数据保护,掉电复位使能,内部复位,
;上电复位延时使能,关看门狗,内部振荡无时钟输出
;*************************************************
;* 寄存器定义及RAM分配 *
;*************************************************
ERRORLEVEL -302 ; 编译输出结果中不要显示message

cblock 0x20 ; 通用变量定义从该地址开始
W_TEMP ; 中断服务程序用来临时保存W和STATUS
STATUS_TEMP
FLAGS ; 程序要使用的状态标志寄存器
PWM_HIGH:2 ; 新的PWM高电平时间,保留两个字节
PWM_LOW:2 ; 新的PWM低电平时间,保留两个字节
PWM_HIGH_CURRENT:2 ; 当前PWM的高电平时间锁存
PWM_LOW_CURRENT:2 ; 当前的PWM低电平时间锁存
COUNTER ; AD采样次数计数
adsum:2 ; 16位的AD累加结果,用于平均运算,两个字节
temp:2 ; 用于数学运算的临时变量,两个字节
endc ; 结束cblock变量定义

;************************************************
;I/O引脚初始化 *
;以及 A/D 初始化 *
;************************************************
;GP0 = 模拟电压输入
;GP1 = 没有使用
;GP2 = 没有使用
;GP3 = 只能作为输入,连接10K下拉电阻到地,ICSP烧录会使用该引脚
;GP4 = PWM输出引脚
;GP5 = 没有使用
;代换定义
#define PWM_OUTPUT GPIO,4
#define LOAD_PWM FLAGS,0
#define CALCULATE_AD FLAGS,1
#define PWM_TICKS .1024 ; PWM时间片的个数,如果PWM的分辨率是10位,这个数就是2的10次方,.1024代表十进制的1024
#define TEST_PWM_HIGH .512 ; 用于测试的50%占空比,高电平时间等于PWM时间片的一半
#define TEST_PWM_LOW PWM_TICKS-TEST_PWM_HIGH;低电平时间等于PWM时间片减高电平时间
#define ISR_DELAY .16 ; 从Timer1溢出到进入中断重新装载Timer1值的延时,可通过软件模拟来确定该值
#define NUM_AD_SAMPLES .16 ; AD采样次数
#define AD_TIME_INTERVAL 0xFF - (.245) ; AD时间间隔,10个cycle的循环

sub_const_16 macro const_input, input, output;定义双字节常数减变量的宏
movlw low const_input
movwf temp
movlw high const_input
movwf temp+1
movf input,w
subwf temp,w
movwf output
movf input+1,w
btfss STATUS,C
incf input+1,w
subwf temp+1,w
movwf output+1
endm

add_const_16 macro const_input, input, output;定义双字节常数加变量的宏
movlw low const_input
movwf temp
movlw high const_input
movwf temp+1
movf input,w
addwf temp,w
movwf output
movf input+1,w
btfsc STATUS,C
incf input+1,w
addwf temp+1,w
movwf output+1
endm

org 0x0 ; 芯片复位入口地址0x00
goto startup ; Goto到startup代码处

;****************************************
;中断服务子程序 *
;****************************************
org 0004h ; 中断入口地址0x0004
save_context
movwf W_TEMP ; 保存W寄存器
swapf STATUS,W ; 保存STATUS寄存器到W,使用SWAPF是为了不影响标志位
bcf STATUS,RP0 ; 选择Bank 0
movwf STATUS_TEMP ; 把STATUS寄存器保存到临时寄存器
bcf PIR1,TMR1IF ; 清掉Timer1溢出标志位
movlw b'00010000' ; 把W的第四位置高,其他都是零
xorwf GPIO,F ; 和GPIO异或,也就是把PWM输出脚的电平反转
load_pwm
btfss PWM_OUTPUT ; 测试如果PWM输出是高电平的话,跳过一条指令来装载PWM高电平时间,否则装载PWM低电平时间
goto load_pwm_low ; 如果PWM输出是低电平,那么装载低电平时间
load_pwm_high
movf PWM_HIGH_CURRENT+1,W ; 预装载的PWM高时间到Timer1
movwf TMR1H
movf PWM_HIGH_CURRENT,W
movwf TMR1L
btfsc LOAD_PWM ; 检查主循环main()有没有更新PWM的高低时间
goto skip_load_pwm_value ; 如果没有更新的高低时间值,跳过PWM_HIGH和PWM_LOW的重新装载
load_new_pwm
movf PWM_HIGH+1,W ; 把新的PWM_HIGH时间装载到当前高时间寄存器
movwf PWM_HIGH_CURRENT+1
movf PWM_HIGH,W
movwf PWM_HIGH_CURRENT
movf PWM_LOW+1,W ; 把新的PWM_LOW时间装载到当前低时间寄存器
movwf PWM_LOW_CURRENT+1
movf PWM_LOW,W
movwf PWM_LOW_CURRENT
skip_load_pwm_value
bsf CALCULATE_AD ; 置位计算A/D标志位来给主循环main()代码使用
bsf LOAD_PWM ; 置位标志位来指示可以为新的PWM周期装载新的高电平时间值
goto restore_context

load_pwm_low
movf PWM_LOW_CURRENT+1,W ; 预装载的PWM低时间到Timer1
movwf TMR1H
movf PWM_LOW_CURRENT,W
movwf TMR1L
goto restore_context

restore_context
swapf STATUS_TEMP,W ; 读出先前保存的STATUS到W,SWAPF对应保护现场时的
movwf STATUS ; 恢复STATUS寄存器
swapf W_TEMP,F ; 恢复W寄存器
swapf W_TEMP,W
retfie ; 从中断返回

startup:
clrf GPIO ; 初始化GPIO端口寄存器为零
bsf STATUS, RP0 ; 选择Bank 1
call 0x3FF ; 读内部振荡出厂校准字,返回值在W寄存器内
movwf OSCCAL ; 内部振荡器校准
movlw b'11000001' ; 设置GP<0>为输入,所有其他口(包括未用到的)为输出
movwf TRISIO
movlw 01h ; 使能Timer1中断
movwf PIE1
movlw b'00010001' ; AD采用FOSC/8 时钟,GP2是模拟口,其他口为数字IO
movwf ANSEL
movlw b'00001000' ; Timer 0设置为使用内部指令周期,无预分频
movwf OPTION_REG
bcf STATUS, RP0 ; 选择Bank0
movlw b'10000001' ; A/D是右对齐格式,Vdd为参考,GP0作为AD输入口
movwf ADCON0
movlw b'00000111' ; 关掉内部比较器,让比较器的输入都为数字口
movwf CMCON
movlw 0C0h ; 使能周边外设中断和全局中断
movwf INTCON
movlw b'00000001' ; Timer1是1:1预分频,内部时钟源,振荡频率/4,不带门控
movwf T1CON

;****************************************
;主循环MAIN() *
;****************************************
main:
movlw high (0xFFFF-TEST_PWM_HIGH+ISR_DELAY) ; 只是好玩,初始化PWM_HIGH和PWM_LOW到50%占空比
movwf PWM_HIGH+1
movlw low (0xFFFF-TEST_PWM_HIGH+ISR_DELAY)
movwf PWM_HIGH
movlw high (0xFFFF-TEST_PWM_HIGH+ISR_DELAY)
movwf PWM_HIGH+1
movlw low (0xFFFF-TEST_PWM_HIGH+ISR_DELAY)
movwf PWM_HIGH
clrf FLAGS ; 清FLAGS标志寄存器来让PWM_CURRENT被更新一次
wait_pwm_dc
btfss CALCULATE_AD ; 如果是到了重新计算AD值来改变PWM占空比的时候,跳过一条指令
goto wait_pwm_dc
bcf CALCULATE_AD ; 清计算A/D标志位
calculate_pwm_dc
movlw NUM_AD_SAMPLES ; 用预定义的AD采样次数初始化COUNTER
movwf COUNTER
clrf adsum ; 清A/D累加和 / 平均值 寄存器
clrf adsum+1
loop_adc_conversion
btfss INTCON,T0IF ; 等待Timer0的溢出
goto loop_adc_conversion
movlw AD_TIME_INTERVAL ; 重新装载Timer0初值
movwf TMR0
bcf INTCON,T0IF ; 清Timer0中断标志位,注:Timer0中断并未使能
bsf STATUS,RP0 ; 选择bank1
movf ADRESL,W ; 把AD转换值低字节装入W寄存器
addwf adsum,F ; 把AD转换值低字节加到sum寄存器
btfsc STATUS,C ; 如果没有进位跳过一条指令
incf adsum+1,F ; 如果有进位把sum高字节加一
bcf STATUS,RP0 ; 选择Bank0
movf ADRESH,W ; 把AD转换值的高字节送到W寄存器
addwf adsum+1,F ; 把AD转换值的高字节加到sum高字节
bsf ADCON0,GO_DONE ; 开始下一次转换
decfsz COUNTER,F ; 转换次数寄存器减一,当转换次数结束时跳过一条指令
goto loop_adc_conversion ; 如果转换次数没有结束,跳回循环
; 如果结束了NUM_AD_SAMPLES次数的转换,开始计算新的PWM值
rrf adsum+1,F ; adsum双字节除2
rrf adsum,F
rrf adsum+1,F ; adsum双字节除2
rrf adsum,F
rrf adsum+1,F ; adsum双字节除2
rrf adsum,F
rrf adsum+1,F ; adsum双字节除2
rrf adsum,F
movlw 0x3 ; 屏蔽高字节的高6位,防止C里的1移入了高位
andwf adsum+1, F

wait_pwm_load
btfss LOAD_PWM ; 测试LOAD_PWM位,如果为1,装载PWM值,否则循环等待当前PWM周期结束
goto wait_pwm_load
update_b
; PWM高时间为 0xFFFF - Duty Cycle + ISR_DELAY
sub_const_16 0xffff,adsum,PWM_HIGH
add_const_16 ISR_DELAY,PWM_HIGH,PWM_HIGH
btfss PWM_HIGH+1,7 ; 如果没有超过PWM最小或最大值的话,跳过一条指令
goto wait_pwm_dc
; PWM低时间为 0xFFFF - PWM_TICKS + Duty Cycle + ISR_DELAY
add_const_16 (0xFFFF-PWM_TICKS+ISR_DELAY),adsum,PWM_LOW
btfsc PWM_LOW +1,7 ; 如果超过PWM最小或最大时间,跳过一条指令
bcf LOAD_PWM ; 结束装载PWM,清LOAD_PWM标志位,以供下次中断时使用
goto wait_pwm_dc

end ; 程序结束符


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值