翻译+转载作品:使用Microchip PIC12F683单片机构建自己的简单激光投影仪 2010 年4 月13 日,作者:rwb ,隶属于Microcontroller 。
8引脚PIC12F683单片机是Microchip 8位单片机系列中最小的成员之一,但具有强大的外设,例如ADC和PWM功能。这使得这个微型微控制器适用于控制直流电动机的速度。为了演示PIC12F683的功能并使本教程更具吸引力,我决定使用PIC12F683微控制器通过廉价的钥匙串激光笔生成简单而又引人入胜的激光表演。 在许多娱乐俱乐部或公园中显示的激光灯基本都使用两种方法。第一个是在观众身上发射激光束,第二个是在屏幕上显示激光绘图图案。在本教程中,我们将使用微型Microchip PIC12F683微控制器构建激光投影仪,在屏幕上显示呼吸描记器图案。 制作呼吸描记器激光投影仪的原理是至少使用两个直流电机,并在其上安装反射镜,然后这些反射镜会将激光束从一个直流电机反射镜偏转到第二个直流电机反射镜,最后偏转到屏幕。通过控制每个直流电动机的旋转速度,我们可以在屏幕上生成引人入胜的激光呼吸描记器图案,如下图所示。 控制直流电动机速度的最佳方法是使用PWM(脉冲波调制)信号来驱动直流电动机,并且由于我们要手动更改直流电动机速度,因此我们需要使用微调端口或电位计来控制每个直流电动机。直流电动机的速度。嗯,这听起来像是微控制器的一项适当工作,但我们可以使用这种8引脚微型PIC12F683微控制器来完成此任务吗? 从数据表中您会注意到,Microchip PIC12F683单片机只有一个PWM输出(CCP1)和四个ADC输入通道(AN0,AN1,AN2和AN3)。由于我们需要两个PWM输出,因此在本教程中,我将向您展示如何基于PIC12F683单片机TIMER0外设生成PWM信号,而不是使用内置在PWM外设中的PIC12F683单片机。以下是激光投影仪项目的完整电子原理图。 好吧,在我们进一步介绍细节之前;让我们列出完成此激光投影仪项目所需的支持外围设备:
- 热胶枪
- 钥匙串激光笔或任何可用的激光笔
- 3xAA,4.5伏电池座,用于为激光指示器供电,请使用与激光指示器相同的电压速率。
- 从废弃的PS2双电击操纵杆中取出两个直流电机
- 来自田宫赛车的两个玩具车轮胎
- 镜子的CD / DVD,用厨房剪刀将CD / DVD切成直径约38毫米的两个圆形镜子
- 一些用于固定直流电动机的玩具塑料积木
- 面包板
- 激光投影仪的底座使用硬板或丙烯酸树脂
- 双面胶带
以下是我用于制作此激光投影仪项目的电子零件和软件开发工具:
- 电阻器:330(3),1K(5)和10K(1)
- Trimport:10K(2)
- 电容:100nF(2)和10nF(1)
- 一个100uH电感器
- 两个1N4148二极管
- 两个蓝色和一个红色发光二极管(LED)
- 两个2N2222A晶体管
- 一个迷你按钮开关
- 一个Microchip PIC12F683单片机
- Microchip MPLAB v8.46 IDE(集成开发环境)
- Microchip宏汇编器MPASMWIN.exe v5.35,mplink.exe v4.35
- PIC10 / 12/16 MCU的HI-TECH C编译器(精简模式)V9.70
- Microchip PICKit3编程器(固件套件版本:01.25.20)
该项目的目标是为持续教训我以前发布博客介绍PIC汇编语言部分-1和介绍PIC汇编语言部分-2,所以我用的第2部分呈现相同的PIC12F683板,你可以降负荷两者的以EagleCAD格式设计的电子原理图和PCB布局。该激光投影仪项目的另一个有趣特征是:除了PIC汇编代码外,我还为C语言爱好者提供了该项目的C语言版本,并使用HI-TECH C编译器进行了编译(最近HI-TECH软件已被Microchip收购)。此C语言版本可用于学习以及嵌入式系统编程语言比较。 在该项目中,我还使用了一个新的Microchip PICKit3编程器,但是您当然可以使用Microchip PICKit2编程器将十六进制代码下载到PIC12F683单片机闪存中。 以下是PIC汇编语言中的Laser Projector代码: ;******************************************************************************
; File Name : laserlight.asm
; Version : 1.0
; Description : Laser Light ShowProject
; Author : RWB
; Target : Microchip PIC12F683Microcontroller
; Compiler : Microchip Assembler(MPASMWIN.exe v5.35, mplink.exe v4.35)
; IDE : Microchip MPLAB IDEv8.46
; Programmer : PICKit3 (FirmwareSuite Version: 01.25.20)
; Last Updated : 01 April 2010
; *****************************************************************************
#include <p12F683.inc>
__config (_INTRC_OSC_NOCLKOUT &_WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _IESO_OFF &_FCMEN_OFF)
#define MAX_TMR0 0xFB
#define MAX_COUNT .200
#define MAX_DEBOUNCE 0x0A
#define MAX_TBLINDEX 0x0A
; Define variables used
cblock 0x20
Delay:2 ; Define two registersfor the Delay and Delay + 1
mode ; Operation Mode
pwm_count ; Hold the Main PWM Counter
pwm_m1 ; Hold the PWMwidth for Motor 1
pwm_m2 ; Hold the PWM width forMotor 2
keycount ; Debounce Count
tableindex ; Table Index for Auto PWM
endc
; Define variable use for storing STATUSand WREG register
cblock 0x70 ; Useunbanked RAM, available both in Bank0 and Bank1
saved_w
saved_status
endc
; Start the Light show Assembler Code here
org 0x00 ; Wealways start at flash address 0
goto Main ; Jump toMain
org 0x04 ; 0x04:Start PIC Interrupt Address
PIC_ISR: ; Start the PIC InterruptService Routine
movwf saved_w ; Save Working Register
movf STATUS,w ; Save Status Register
movwf saved_status
; Check the TIMER0 Interrupt here
btfss INTCON,T0IF
goto ExitISR ; If (T0IF != 1) then Exit ISR
bcf STATUS,RP0 ; Select Registers at Bank 0
incf pwm_count ; pwm_count++
movlw MAX_COUNT
subwf pwm_count,w ; if (pwm_count < MAX_COUNT) thenCheckPWM
btfss STATUS,C ; else clear GP1 and GP2
goto CheckPWM
bcf GPIO,GP1 ; GPIO1=0
bcf GPIO,GP2 ; GPIO2=0
goto ExitPWM
CheckPWM:
movf pwm_m1,w
subwf pwm_count,w
btfsc STATUS,Z ; if (pwm_count == pwm_m1) then Set GP1
bsf GPIO,GP1 ; Set GP1 Bit
CheckM2:
movf pwm_m2,w
subwf pwm_count,w
btfsc STATUS,Z ; if (pwm_count == pwm_m2) then Set GP2
bsf GPIO,GP2 ; Set GP2 bit
ExitPWM:
bcf INTCON,T0IF ; clear the TIMER0 interrupt flag
movlw MAX_TMR0
movwf TMR0 ; TMR0 = MAX_TMR0
ExitISR:
movf saved_status,w
movwf STATUS ; Restore STATUS Register
swapf saved_w,f
swapf saved_w,w ; Restore W Register
retfie ; Returnfrom Interrupt
Main:
bsf STATUS,RP0 ; Select Registers at Bank 1
movlw 0x70
movwf OSCCON ; Set the internal clock speed to 8MHz
movlw 0x39 ; GP1 and GP2 Output, GP0,GP3,GP4and GP5 as Input
movwf TRISIO ; TRISIO = 0x39
bcf STATUS,RP0 ; Select Registers at Bank 0
movlw 0x07
movwf CMCON0 ; Turn off Comparator (GP0, GP1, GP2)
clrf GPIO
; Now we Set the ADC Peripheral
bsf STATUS,RP0 ; Select Registers at Bank 1
movlw 0x79 ; Set AN0 (GP0) and AN3 (GP4) asAnalog Input
movwf ANSEL ; Using the Internal Clock(FRC)
; Now we set the TIMER0 Peripheral
; TIMER0 Period = 1/FSOC x 4 x Prescale xTMR0
movlw 0x00 ; Use TIMER0 Prescaler 1:2, InternalClock
movwf OPTION_REG ; OPTION_REG = 0x00
bcf STATUS,RP0 ; Select Registers at Bank 0
movlw MAX_TMR0
movwf TMR0 ; TMR0=MAX_TMR0
; Initial the variables used
clrf mode ; Default mode = 0, Light Show Off
clrf pwm_count ; pwm_count = 0
clrf pwm_m1 ; pwm_m1 = 0
clrf pwm_m2 ; pwm_m2 = 0
clrf keycount ; keycount = 0
clrf tableindex ; tableindex = 0
; Activate the Interrupt
bsf INTCON,GIE ; Enable Global Interrupt
MainLoop:
btfsc GPIO,GP5 ; Now we check the Button
goto CheckMode ; if (GP5 != 0) goto CheckMode
movlw 0x01
addwf keycount ; keycount=keycount + 1
movf keycount,w
sublw MAX_DEBOUNCE
btfss STATUS,C ; if (keycount > MAX_DEBOUNCE) gotoKeyPressed
goto KeyPressed
goto CheckMode ; else CheckMode
KeyPressed:
clrf keycount ; keycount=0
incf mode ; mode++
movlw 0x03
subwf mode,w ; W = mode - 0x03
btfsc STATUS,C ; if (mode >= 0x03)
clrf mode ; mode=0;
movlw 0x01 ; else check the mode
subwf mode,w
btfss STATUS,C ; if (mode >= 0x01) goto TurnOn
goto TurnOff ; else goto TurnOff
goto TurnOn
TurnOff:
bcf INTCON,T0IE ; Disable TIMER0 Interrupt
clrf pwm_count ; pwm_count = 0
clrf pwm_m1 ; pwm_m1 = 0
clrf pwm_m2 ; pwm_m2 = 0
bcf GPIO,GP1
bcf GPIO,GP2
movlw .250
call DelayMs ; DelayMs(250)
movlw .250
call DelayMs ; DelayMs(250)
goto CheckMode
TurnOn:
bsf INTCON,T0IE ; Enable TIMER0 Interrupt
CheckMode:
movlw 0x01
subwf mode,w
btfss STATUS,Z ; if (mode == 1) goto ShowMode1
goto CheckMode2
goto ShowMode1
CheckMode2:
movlw 0x02
subwf mode,w
btfss STATUS,Z ; if (mode == 2) goto ShowMode2
goto KeepLoop
goto ShowMode2
ShowMode1: ; Used ADC for PWM
movlw B'00000001' ; Left Justify and turn on the ADCperipheral, channel 0 (AN0)
movwf ADCON0 ; Vreff=Vdd
bsf ADCON0,GO ; Start the ADC Conversion on channel 0(AN0)
btfss ADCON0,GO ; while(GO == 1)
goto $-1 ; Keep Loop
call Delay1ms
movlw B'00000001' ; Left Justify and turn on the ADCperipheral, channel 0 (AN0)
movwf ADCON0 ; Vreff=Vdd
bsf ADCON0,GO ; Start the ADC Conversion on channel 0(AN0)
btfss ADCON0,GO ; while(GO == 1)
goto $-1 ; Keep Loop
movf ADRESH,w ; Conversion Done, Read ADRESH
movwf pwm_m1 ; pwm_m1 = ADRESH
call Delay1ms
movlw B'00001101' ; Left Justify and turn on the ADCperipheral, channel 3 (AN3)
movwf ADCON0 ; Vreff=Vdd
bsf ADCON0,GO ; Start the ADC Conversion on channel 3(AN3)
btfss ADCON0,GO ; while(GO == 1)
goto $-1 ; Keep Test
call Delay1ms
movlw B'00001101' ; Left Justify and turn on the ADCperipheral, channel 3 (AN3)
movwf ADCON0 ; Vreff=Vdd
bsf ADCON0,GO ; Start the ADC Conversion on channel 3(AN3)
btfss ADCON0,GO ; while(GO == 1)
goto $-1 ; Keep Test
movf ADRESH,w ; Conversion Done, Read ADRESH
movwf pwm_m2 ; pwm_m2 = ADRESH
call Delay1ms
goto KeepLoop
ShowMode2: ; Used Predefined Value forPWM
movf tableindex,w
call tablepwm1 ; Call tablepwm1
movwf pwm_m1 ; Assigned it to pwm_m1
movlw .30
call DelayMs ; DelayMs(30)
movf tableindex,w
call tablepwm2 ; Call tablepwm2
movwf pwm_m2 ; Assigned it to pwm_m2
movlw .30
call DelayMs ; DelayMs(30)
incf tableindex ; tableindex++
movlw MAX_TBLINDEX
subwf tableindex,w
btfss STATUS,C ; if (tableindex >= 0x0A) thentableindex = 0
goto KeepLoop
clrf tableindex ; tableindex = 0
KeepLoop:
goto MainLoop ; Goto MainLoop
; Predefined value table for Automatic PWM
tablepwm1:
addwf PCL,f
retlw 0x10
retlw 0x5A
retlw 0x9A
retlw 0x20
retlw 0x40
retlw 0x8A
retlw 0x82
retlw 0x30
retlw 0x58
retlw 0xAA
tablepwm2:
addwf PCL,f
retlw 0x70
retlw 0x8A
retlw 0x2A
retlw 0x30
retlw 0x1C
retlw 0x2A
retlw 0x4B
retlw 0xA0
retlw 0x18
retlw 0x2A
;----------------- DelayMs: MillisecondDelay Subroutine ----------------------
; Paramater: WREG = delay amount in milisecond,max: 255 millisecond
DelayMs:
movwf Delay + 1
DelayLoop:
call Delay1ms
decfsz Delay + 1,f ; Decrease Delay + 1, If zero skip thenext instruction
goto DelayLoop ; Not zero goto DelayLoop
return ; return to the caller
;----------------- Delay1ms: 1 ms DelaySubroutine ---------------------------
Delay1ms: ; Total Delay: 1998 x 0.5us~ 1 ms
movlw 0x99
movwf Delay
DelayLoop1:
decfsz Delay,f ; Decrease Delay, If zero skip thenext instruction
goto DelayLoop1
DelayLoop2:
decfsz Delay,f ; Decrease Delay, If zero skip thenext instruction
goto DelayLoop2 ; Not zero goto DelayLoop2
DelayLoop3:
decfsz Delay,f ; Decrease Delay, If zero skip thenext instruction
goto DelayLoop3 ; Not zero goto DelayLoop2
return ; Returnto the caller
end
; EOF: laserlight.asm
The following is the Laser ProjectorProject code in C Language version:
//***************************************************************************
// File Name : laserlight.c
// Version : 1.0
// Description : Laser Light ShowProject
// Author : RWB
// Target : Microchip PIC12F683Microcontroller
// Compiler : HI-TECH CPIC10/12/16 MCUs (Lite Mode) V9.70
// IDE : Microchip MPLAB IDEv8.46
// Programmer : PICKit3 (FirmwareSuite Version: 01.25.20)
// Last Updated : 03 April 2010
// ***************************************************************************
#include <pic.h>
/* PIC Configuration Bit:
** INTIO - Using Internal RC NoClock
** WDTDIS - Wacthdog Timer Disable
** PWRTEN - Power Up Timer Enable
** MCLRDIS - Master Clear Disable
** UNPROTECT - Code Un-Protect
** UNPROTECT - Data EEPROM Read Un-Protect
** BORDIS - Borwn Out DetectDisable
** IESODIS - Internal ExternalSwitch Over Mode Disable
** FCMDIS - Monitor Clock FailSafe Disable
*/
__CONFIG(INTIO & WDTDIS & PWRTEN& MCLRDIS & UNPROTECT \
& UNPROTECT & BORDIS & IESODIS & FCMDIS);
// Using Internal Clock of 8 MHz
#define FOSC 8000000L
#define MAX_COUNT 200
#define MAX_TMR0 0xFB
#define MAX_DEBOUNCE 0x0A
#define MAX_TBLINDEX 0x0A
unsigned char pwm_count=0;
unsigned char pwm_m1=0;
unsigned char pwm_m2=0;
unsigned chartablepwm1[10]={0x10,0x5A,0x9A,0x20,0x40,0x8A,0x82,0x30,0x58,0xAA};
unsigned chartablepwm2[10]={0x70,0x8A,0x2A,0x30,0x1C,0x2A,0x4B,0xA0,0x18,0x2A};
unsigned char tableindex=0;
/* The Delay Function */
#define delay_us(x){ unsigned char us; \
us = (x)/(12000000/FOSC)|1; \
while(--us != 0) continue; }
void delay_ms(unsigned int ms)
{
unsigned char i;
do{
i= 4;
do {
delay_us(164);
}while(--i);
}while(--ms);
}
static void interrupt isr(void)
{
if(T0IF) { // TIMER0 Interrupt Flag
pwm_count++; // PWM CountIncrement
if (pwm_count >= MAX_COUNT) {
pwm_count=0;
GPIO1=0; // Turn off GP1
GPIO2=0; // Turn off GP2
}
if (pwm_count == pwm_m1) {
GPIO1=1; // Turn On GP1
}
if (pwm_count == pwm_m2) {
GPIO2=1; // Turn On GP2
}
TMR0 = MAX_TMR0; // InitialValue for TIMER0 Interrupt
T0IF = 0; // Clear TIMER0 interrupt flag
}
}
void main(void)
{
unsigned char mode,keycount;
OSCCON=0x70; // Select 8MHz internal clock
/*Initial Port Used */
TRISIO = 0x39; // GP1 andGP2 Output, GP0,GP3,GP4 and GP5 as Input
CMCON0 = 0x07; // Turn offComparator (GP0, GP1, GP2)
GPIO = 0x00; // Turn Offall IO
/*Init ADC Peripheral */
ANSEL = 0x79; // Set AN0(GP0) and AN3 (GP4) as Analog Input, Internal Clock
/*Init TIMER0: TIMER0 Period = 1/FSOC x 4 x Prescale x TMR0*/
OPTION = 0b00000000; // 1:2 Prescale
TMR0=MAX_TMR0 ;
/*Init Variable Used */
pwm_count=0;
pwm_m1=0;
pwm_m2=0;
mode=0;
keycount=0;
tableindex=0;
GIE=1; // Enable Global Interrupt
for(;;) {
// Display the LED
if (GPIO5 == 0) {
keycount++;
if (keycount > MAX_DEBOUNCE) {
keycount=0;
mode = mode + 1;
if (mode > 2) mode = 0;
if (mode >= 0x01) {
T0IE = 1; // Enable TIMER0 Interrupt on Overflow
} else {
T0IE = 0; // Disable TIMER0 Intterupt on Overflow
pwm_count=0;
pwm_m1=0;
pwm_m2=0;
GPIO1=0; // Turn offGP1
GPIO2=0; // Turn offGP2
delay_ms(500);
}
}
}
if (mode == 1) {
/* Read the ADC here */
ADCON0=0b00000001; // selectleft justify result. ADC port channel AN0
GODONE=1; // initiate conversion on thechannel 0
while(GODONE) continue; // Waitfor ldr_left conversion done
pwm_m1=ADRESH; // Read 8bits MSB, Ignore 2 bits LSB in ADRESL
delay_ms(1);
/* Read the ADC here */
ADCON0=0b00001101; // selectleft justify result. ADC port channel AN3
GODONE=1; // initiate conversion on the channel4
while(GODONE) continue; // Waitfor ldr_left conversion done
pwm_m2=ADRESH; // Read 8bits MSB, Ignore 2 bits LSB in ADRESL
delay_ms(1);
}
if (mode == 2) {
pwm_m1=tablepwm1[tableindex];
delay_ms(10);
pwm_m2=tablepwm2[tableindex];
delay_ms(10);
tableindex++;
if (tableindex >= MAX_TBLINDEX)
tableindex = 0;
}
}
}
/* EOF: laserlight.c */
产生PWM输出的简单方法是利用PIC12F683单片机的PWM内置功能,但是由于PIC12F683仅支持一个PWM输出,因此我们需要找到一种方法来模仿固件中的这种外设行为。大多数微控制器PWM外设都使用TIMER外设来产生脉冲。在PIC12F683中,PWM发生器使用TIMER2生成PWM输出(CCP1),如下图所示:
当TIMER2开始计数时,TMR2寄存器(TIMER2计数器寄存器)的值会不断与PR2和CCPR1L寄存器进行比较。当TMR2等于CCPR1L值时,CCP1输出将被复位;而当TMR2等于PR2值时,它将设置CCP1输出。因此,通过如上图所示更改CCPR1L寄存器的值,我们可以更改PWM占空比(脉冲宽度)。您可以在我以前的博客上阅读有关使用PIC单片机PWM外设的原理的更多信息。H桥Microchip PIC单片机PWM电机控制器。
使用相同的原理,我在Microchcip PIC12F683微控制器TIMER0外设的固件中制作了PWM发生器,如下图所示:
file:///C:/Users/User1/AppData/Local/Temp/msohtmlclip1/01/clip_image022.jpg
TIMER0基本PWM发生器的基本原理是使用TIMER0溢出中断来增加我们的PWM计数器变量pwm_count,如果该变量达到MAX_COUNT(200),则我们将复位GP1和GP2输出引脚。如果不同,则将其与PWM占空比控制变量pwm_m1和pwm_m2进行比较,如果相等,则只需设置GP1或GP2输出引脚即可。现在,使用该原理,我们可以轻松地基于PIC12F683单片机TIMER0外设生成高效的PWM信号。
file:///C:/Users/User1/AppData/Local/Temp/msohtmlclip1/01/clip_image024.jpg
现在看一下实现PIC12F683单片机TIMER0外设基本PWM发生器的PIC汇编代码:
; Checkthe TIMER0 Interrupt here
btfss INTCON,T0IF
goto ExitISR ; If (T0IF != 1)then Exit ISR
bcf STATUS,RP0 ; Select Registersat Bank 0
incf pwm_count ; pwm_count++
movlw MAX_COUNT
subwf pwm_count,w ; if (pwm_count< MAX_COUNT) then CheckPWM
btfss STATUS,C ; else clear GP1and GP2
goto CheckPWM
bcf GPIO,GP1 ; GPIO1=0
bcf GPIO,GP2 ; GPIO2=0
goto ExitPWM
该例程首先通过检查INTCON寄存器上的T0IF位来检查该中断是否由TIMER0外设产生,如果此中断来自TIMER0,则T0IF位将被置1(逻辑“ 1 ”),然后我们继续增加pwm_count变量,并如果达到MAX_COUNT,我们将重置GP1和GP2输出引脚。接下来,如果pwm_count尚未达到MAX_COUNT,则将其值与pwm_m1和pwm_m2变量进行比较,如以下代码所示:
heckPWM:
movf pwm_m1,w
subwf pwm_count,w
btfsc STATUS,Z ; if (pwm_count == pwm_m1) then Set GP1
bsf GPIO,GP1 ; Set GP1 Bit
CheckM2:
movf pwm_m2,w
subwf pwm_count,w
btfsc STATUS,Z ; if (pwm_count == pwm_m2) then Set GP2
bsf GPIO,GP2 ; Set GP2 bit
当pwm_count等于pwm_m1或pwm_m2时,我们分别将GP1和GP2输出引脚设置为“逻辑1 ” 。
要计算PWM周期,首先我们必须使用以下公式计算TIMER0周期:
TIMER0 Period = 1/Fosc x 4 x Prescale x (TMR0 + 1)
在本教程中,我们使用1:2预分频比,TMR0 =251(0xFB)和8 MHz内部振荡器,因此TIMER0将在以下时间中断:
TIMER0 Period = 1/8000000 x 4 x 2 x (256 – 251)= 0.000005 second
每次TIMER0中断(TMR0溢出)时,我们都会增加pwm_count变量,直到达到MAX_COUNT(200),然后将GP1和GP2的每个输出引脚复位。因此,大约PWM周期可以计算如下:
PWM Period = MAX_COUNT x 0.000005 seconds = 200 x0.000005 = 0.001 second
PWM Frequency = 1 / T = 1/0.001 = 1000 Hz = 1KHz
改变直流电动机的速度
该激光投影仪项目具有两种模式,用于控制两个直流电动机的速度,第一个模式是使用微调端口(VR1和VR2),在该模式下,我们使用PIC12F683单片机读取由这些微调端口(分压器电路)产生的电压电平。数字转换器(ADC)外围设备,然后应用这些ADC值来更改每个直流电动机速度。第二种方法是使用存储在查找表中的固定值,然后分配这些值以更改每个直流电动机速度。
Microchip PIC12F683单片机具有四个可用的ADC通道。在此项目中,我们使用通道0(AN0)和通道4(AN3)。通过选择ADCON0寄存器(ADC控制寄存器)上的requireADC通道并选择左对齐结果(ADFM = 0),我们可以读取ADRESH寄存器中的VR1和VR2调整端口产生的电压值,并将这些值分配给pwm_m1和pwm_m2变量,如以下PIC汇编代码所示:
ShowMode1: ; Used ADC for PWM
movlw B'00000001' ; Left Justify and turn on the ADCperipheral, channel 0 (AN0)
movwf ADCON0 ; Vreff=Vdd
bsf ADCON0,GO ; Start the ADC Conversion on channel 0(AN0)
btfss ADCON0,GO ; while(GO == 1)
goto $-1 ; Keep Loop
call Delay1ms
movlw B'00000001' ; Left Justify and turn on the ADCperipheral, channel 0 (AN0)
movwf ADCON0 ; Vreff=Vdd
bsf ADCON0,GO ; Start the ADC Conversion on channel 0 (AN0)
btfss ADCON0,GO ; while(GO == 1)
goto $-1 ; Keep Loop
movf ADRESH,w ; Conversion Done, Read ADRESH
movwf pwm_m1 ; pwm_m1 = ADRESH
call Delay1ms
movlw B'00001101' ; Left Justify and turn on the ADCperipheral, channel 3 (AN3)
movwf ADCON0 ; Vreff=Vdd
bsf ADCON0,GO ; Start the ADC Conversion on channel 3(AN3)
btfss ADCON0,GO ; while(GO == 1)
goto $-1 ; Keep Test
call Delay1ms
movlw B'00001101' ; Left Justify and turn on the ADCperipheral, channel 3 (AN3)
movwf ADCON0 ; Vreff=Vdd
bsf ADCON0,GO ; Start the ADC Conversion on channel 3(AN3)
btfss ADCON0,GO ; while(GO == 1)
goto $-1 ; Keep Test
movf ADRESH,w ; Conversion Done, Read ADRESH
movwf pwm_m2 ; pwm_m2 = ADRESH
call Delay1ms
goto KeepLoop
有关使用PICADC外设的更多信息,请阅读我以前发布的博客PIC模数转换器C编程。
接下来是自动模式,该模式将为PIC程序闪存上的每个PWM值分配预定值。通过使用PIC汇编器“ retlw k ”(在W寄存器上返回文字值k )指令,我们可以轻松地检索这些值。以下PIC汇编代码显示了我们如何执行此操作:
ShowMode2: ; Used Predefined Value forPWM
movf tableindex,w
call tablepwm1 ; Call tablepwm1
movwf pwm_m1 ; Assigned it to pwm_m1
movlw .30
call DelayMs ; DelayMs(30)
movf tableindex,w
call tablepwm2 ; Call tablepwm2
movwf pwm_m2 ; Assigned it to pwm_m2
movlw .30
call DelayMs ; DelayMs(30)
incf tableindex ; tableindex++
movlw MAX_TBLINDEX
subwf tableindex,w
btfss STATUS,C ; if (tableindex >= 0x0A) thentableindex = 0
goto KeepLoop
clrf tableindex ; tableindex = 0
...
...
; Predefined value table for Automatic PWM
tablepwm1:
addwf PCL,f
retlw 0x10
retlw 0x5A
retlw 0x9A
retlw 0x20
retlw 0x40
retlw 0x8A
retlw 0x82
retlw 0x30
retlw 0x58
retlw 0xAA
tablepwm2:
addwf PCL,f
retlw 0x70
retlw 0x8A
retlw 0x2A
retlw 0x30
retlw 0x1C
retlw 0x2A
retlw 0x4B
retlw 0xA0
retlw 0x18
retlw 0x2A
此处的原理是修改PCL(程序计数器加载)寄存器。所述PCL与所述微控制器PIC12F683程序计数器至少显著字节和在一起PCLATH寄存器形成PIC12F683微控制器13位宽程序计数器。“ goto ”命令行为可以通过操纵PCL内容来实现,例如:
movlw 0x01
call tablepwm2
nop
tablepwm2:
addwf PCL,f
retlw 0x70
retlw 0x8A
retlw 0x2A
当PIC单片机运行“ 调用tablepwm ”指令时,它将立即将程序计数器(PCL)更改为“ tablepwm2 ”标签所指的程序地址,即“ addwfPCL,f ”指令。当PIC微控制器执行该指令时,它将W寄存器(0x01)的内容添加到PCL寄存器中,并且PIC微控制器立即跳转到“ PCL+ 0x01 ”程序地址。接着,它会执行“ RETLW0x8A ”指令,它简单地返回到与主叫方0x8A的值Wˉˉ寄存器。
因此,通过增加tableindex(查找表指针)变量,我们可以轻松地在pwm_m1和pwm_m2变量中的每个变量上分配预定值。
PIC组装的基本条件
特别是对于初学者来说,理解PIC汇编指令中令人困惑的部分之一是汇编代码中的“ 如何实现if条件 ”。在此激光投影仪项目的PIC汇编代码中,您注意到它使用了很多“ 如果条件 ”以使其起作用。PIC汇编程序的基本“ if condition ”可以使用以下第一个模板代码来构建:
movf var_1,w ; w = var_1
subwf var_2,w ; w = var_2 - var_1
btfss STATUS,C ; if (var_2 >= var_1)goto label_2
goto label_1 ; else goto label_1
goto label_2
第一个代码(movfvar_1,w)指示PIC微控制器将var_1的内容放入W(工作)寄存器。第二个代码(subwfvar_2,w)指示PIC微控制器用W寄存器的内容减去var_2变量的内容,并将结果放入W寄存器中,或者我们可以记为:
W = var_2-var_1
第三码(BTFSS - b它吨 EST ˚F寄存器,小号硖如果小号等)指示PIC微控制器,如果检查Ç在(进位)位STATUS寄存器被设置(逻辑“ 1 “),如果将它设置然后跳到一条指令(PIC单片机实际上会自动将下一条指令gotolabel_1替换为nop指令),并执行“ goto label_2 ”代码。如果C位清零(逻辑“ 0 ”),它将执行“ goto label_1 ”代码。
如果var_2变量的值大于或等于var_1变量,则STATUS寄存器中的C(进位)位将始终置1(逻辑“ 1 ”)。接下来,以下代码显示了第二个“ if condition ”模板:
movf var_1,w ; w = var_1
subwf var_2,w ; w = var_2 - var_1
btfss STATUS,Z ; if (var_2 == var_1)goto label_2
goto label_1 ; else goto label_1
goto label_2
这一次,我们将研究Ž(零)在比特STATUS寄存器,该Ž(零)位将总是设置(逻辑“ 1 “)时,VAR_2值等于VAR_1值。最后,以下代码显示了第三个“ ifcondition ”模板:
movlw 0x08 ; w = 0x08
subwf var_1,w ; w = var_1 - 0x08
btfss STATUS,C ; if (var_1 >= 0x08)goto label_2
goto label_1 ; else goto label_1
goto label_2
第三个模板只是第一个模板的变体,这次我们将字面值0x08加载到W寄存器中,并将其与var_1变量进行比较。通过仅使用这三个“ ifcondition ”模板,您可以轻松地在代码中实现多种类型的“ ifcondition ”。关键要理解和执行PIC汇编“ 如果条件 ”在你的代码指令成功地,始终如一地使用这些模板,一旦你了解比你能修改或用结合起来BTFSC(b它牛逼 EST ˚F寄存器,小号基普如果Çlear)(bit test f register, skipif clear)指令或影响STATUS寄存器中C和Z位的任何PIC汇编程序指令,使您的汇编代码更高效。
为了使PIC汇编代码更易于理解,在此激光投影仪教程中,我在代码中添加了很多注释,您还可以将其与为该项目提供的等效C语言代码进行比较。C语言代码使用与PIC汇编代码中相同的变量名,以便于比较。
PIC汇编程序代码内部
激光投影仪项目使用PIC12F683单片机TIMER0和ADC外设来控制每个直流电动机的速度。以下说明用于初始化PIC12F683外设
Main:
bsf STATUS,RP0 ; Select Registers at Bank 1
movlw 0x70
movwf OSCCON ; Set the internal clock speed to 8MHz
movlw 0x39 ; GP1 and GP2 Output, GP0,GP3,GP4and GP5 as Input
movwf TRISIO ; TRISIO = 0x39
bcf STATUS,RP0 ; Select Registers at Bank 0
movlw 0x07
movwf CMCON0 ; Turn off Comparator (GP0, GP1, GP2)
clrf GPIO
; Now we Set the ADC Peripheral
bsf STATUS,RP0 ; Select Registers at Bank 1
movlw 0x79 ; Set AN0 (GP0) and AN3 (GP4) as Analog Input
movwf ANSEL ; Using the Internal Clock(FRC)
; Now we set the TIMER0 Peripheral
; TIMER0 Period = 1/FSOC x 4 x Prescale x TMR0
movlw 0x00 ; Use TIMER0 Prescaler 1:2,Internal Clock
movwf OPTION_REG ; OPTION_REG = 0x00
bcf STATUS,RP0 ; Select Registers at Bank 0
movlw MAX_TMR0
movwf TMR0 ; TMR0=MAX_TMR0
; Initial the variables used
clrf mode ; Default mode = 0, Light Show Off
clrf pwm_count ; pwm_count = 0
clrf pwm_m1 ; pwm_m1 = 0
clrf pwm_m2 ; pwm_m2 = 0
clrf keycount ; keycount = 0
clrf tableindex ; tableindex = 0
; Activate the Interrupt
bsf INTCON,GIE ; Enable Global Interrupt
第一条指令是将OSCCON(振荡器控制)寄存器设置为使用8MHz内部时钟,然后我们将TRISIO(三态I / O控制器缓冲器)寄存器设置为输入和输出端口,并关闭CMCON0上的比较器外设(比较器控制)寄存器。复位GPIO(通用I / O)寄存器后,我们继续设置ADC外设的ANSEL(模拟选择)寄存器和TIMER0外设的OPTION_REG,并通过清除它们来初始化程序中使用的所有变量。 在进入无限循环之前,我们立即激活INTCON(中断控制)寄存器中的GIE(全局中断使能)位,以便当TMR0(TIMER0计数器)寄存器溢出(大于255)时发生TIMER0中断。 在无限循环内(MainLoop标签),我们不断检查模式变量。该模式值被用于确定所述程序的行为。当模式值为0时,程序将简单地连续循环;当模式值为1时,程序将使用ADC值来控制每个直流电动机速度;而当模式值为2时,程序将使用外观-上表来控制PWM信号(自动模式)。 PIC汇编代码和C代码比较 由于在此项目中,我将PIC汇编语言和C语言用于固件;因此,比较两个十六进制程序的大小会很有趣。以下是由MicrochipPIC汇编器和HI-TECH C PRO(精简模式)编译器生成的HEX代码。 HI-TECH C PRO编译器生成的HEX代码在“ 精简模式 ” 下为356字(623字节),但是,如果在“ PRO模式 ” 下编译C代码,则其大小将减小40%或约为214字( 374.5字节),另一方面,Microchip宏汇编器为HEX代码生成180个字(315字节)。现在您可以看到,如果我们使用专业的C语言编译器,它将生成非常小的HEX代码,几乎与汇编语言生成的HEX代码相等。 下载十六进制代码 在编译并模拟了代码之后,是时候使用MicrochipPICKit3编程器下载代码了,将PICKit3ICSP(微电路在线串行编程)端口连接到PIC12F683单片机引脚,然后从Programmer菜单中选择Programmer-> PicKit3,然后选择Programmer ->程序菜单,将您的十六进制代码下载到PIC12F683单片机闪存中。
原文链接:
游客,本帖隐藏的内容需要积分高于 1 才可浏览,您当前积分为 0
|