8位微处理器实践(1) - 彩色LED控制器

这一篇研究怎样使用PIC控制彩色LED。

彩色LED可以使用4引脚的RGB共阴发光二极管。 阴极接Vss,RGB三个阳极经过限流电阻分别接三个GPIO引脚。PIC的GPIO可以输出25mA电流,足够直接驱动LED,无需额外的晶体管。

我希望实现色彩循环渐变的效果,因此我把循环周期等分为三个相位,对于某一种颜色:

  1. 亮度从0增加到最大
  2. 亮度从最大减小到0
  3. 亮度保持为0

红、绿、蓝三个颜色各错开一个相位,这样就可以产生各种颜色的组合。

相位图

LED亮度的控制,通常采用脉冲宽度调制(PWM)的办法:在很短的周期内,输出一段高电平(亮)和低电平(灭)。根据高电平所占时间(占空比)不同,人眼观察到的是不同的亮度。

这个项目我使用一片PIC16F15214。它有8个引脚,我使用其中3个作为输出。它有4个PWM外设,因此PWM的控制可以完全由硬件完成。

原理图

这个项目比较简单,可以用PIC汇编实现。在MPLAB X中,将编译器设为pic-as,并创建一个主文件main.S

配置字

在汇编文件的最开头,应当设置PIC的配置字 (Configuration words)。MPLAB X提供了一个图形界面 (Production→Set Configuration Bits),当编译器是pic-as时,可以生成相应的汇编代码。当编译器是XC8时,则生成C代码。

; PIC16F15214 Configuration Bit Settings

; Assembly source line config statements

; CONFIG1
  CONFIG  FEXTOSC = OFF         ; External Oscillator Mode Selection bits (Oscillator not enabled)
  CONFIG  RSTOSC = HFINTOSC_1MHZ; Power-up Default Value for COSC bits (HFINTOSC (1 MHz))
  CONFIG  CLKOUTEN = OFF        ; Clock Out Enable bit (CLKOUT function is disabled; I/O function on RA4)
  CONFIG  VDDAR = HI            ; VDD Range Analog Calibration Selection bit (Internal analog systems are calibrated for operation between VDD = 2.3V - 5.5V)

; CONFIG2
  CONFIG  MCLRE = EXTMCLR       ; Master Clear Enable bit (If LVP = 0, MCLR pin is MCLR; If LVP = 1, RA3 pin function is MCLR)
  CONFIG  PWRTS = PWRT_OFF      ; Power-up Timer Selection bits (PWRT is disabled)
  CONFIG  WDTE = OFF            ; WDT Operating Mode bits (WDT disabled; SEN is ignored)
  CONFIG  BOREN = ON            ; Brown-out Reset Enable bits (Brown-out Reset Enabled, SBOREN bit is ignored)
  CONFIG  BORV = LO             ; Brown-out Reset Voltage Selection bit (Brown-out Reset Voltage (VBOR) set to 1.9V)
  CONFIG  PPS1WAY = ON          ; PPSLOCKED One-Way Set Enable bit (The PPSLOCKED bit can be set once after an unlocking sequence is executed; once PPSLOCKED is set, all future changes to PPS registers are prevented)
  CONFIG  STVREN = ON           ; Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a reset)

; CONFIG3

; CONFIG4
  CONFIG  BBSIZE = BB512        ; Boot Block Size Selection bits (512 words boot block size)
  CONFIG  BBEN = OFF            ; Boot Block Enable bit (Boot Block is disabled)
  CONFIG  SAFEN = OFF           ; SAF Enable bit (SAF is disabled)
  CONFIG  WRTAPP = OFF          ; Application Block Write Protection bit (Application Block is not write-protected)
  CONFIG  WRTB = OFF            ; Boot Block Write Protection bit (Boot Block is not write-protected)
  CONFIG  WRTC = OFF            ; Configuration Registers Write Protection bit (Configuration Registers are not write-protected)
  CONFIG  WRTSAF = OFF          ; Storage Area Flash (SAF) Write Protection bit (SAF is not write-protected)
  CONFIG  LVP = ON              ; Low Voltage Programming Enable bit (Low Voltage programming enabled. MCLR/Vpp pin function is MCLR. MCLRE Configuration bit is ignored.)

; CONFIG5
  CONFIG  CP = OFF              ; User Program Flash Memory Code Protection bit (User Program Flash Memory code protection is disabled)

// config statements should precede project file includes.
#include <xc.inc>

引脚映射

xc.inc包含了寄存器定义。为了程序的可读性,我自己也给一些寄存器定义别名:

R_PPS   equ RA2PPS
G_PPS   equ RA4PPS
B_PPS   equ RA5PPS
R_PR    equ CCPR1H
G_PR    equ CCPR2H
B_PR    equ PWM3DCH

我用RA2RA4RA5引脚分别控制红、绿、蓝输出,它们的PWM分别由CCP1CCP2PWM3控制。

    psect main_code,class=CODE,delta=2
    global _main

_main:
    ; initialize pins
    banksel ANSELA
    movlw   0xca
    andwf   ANSELA,f    ; R2,R4,R5 are digital..
    banksel TRISA
    andwf   TRISA,f     ; ..output pins

    banksel R_PPS   ; Output PPS regs are in the same bank
    movlw   1
    movwf   R_PPS   ; R_PIN uses CCP1
    movlw   2
    movwf   G_PPS   ; G_PIN uses CCP2
    movlw   3
    movwf   B_PPS   ; B_PIN uses PWM3

在pic-as中,所有汇编代码必须指定其所在的程度段 (psect)。链接程序 (linker) 将为这些 psect 分配地址。这和MPASM不同,MPASM里所有代码都使用绝对地址。使用psect的好处是方便整合C和汇编代码,但也给写纯汇编代码增添了一些麻烦。

初始化

设置好引脚后,下一步是初始化定时器和PWM。

    ; initialize timer2 - PWM clock source
    banksel T2CON
    movlw   5
    movwf   T2CLKCON    ; TMR2 uses MFINTOSC - 500kHz
    movlw   0x00        ; PSYNC=0,CPOL=0,CSYNC=0,MODE=0 (normal timer)
    movwf   T2HLT
    movlw   255
    movwf   T2PR        ; period = 256 clock period
    movlw   0x90
    movwf   T2CON       ; ON, 1:2 prescaler, 1:1 postscaler
    ; PWM frequency is about 977Hz

    ; initialize PWMs
    banksel CCP1CON
    movlw   0x9c
    movwf   CCP1CON     ; EN, FMT=1 (left aligned), MODE=PWM
    movwf   CCP2CON     ; same
    movlw   0x80
    movwf   PWM3CON     ; EN, POL=normal

这些设置比较枯燥,需要参考厂商发布的数据表 (datasheet)。如果用C开发,那么也可以用Microchip提供的代码生成器MCC (Microchip Code Configurator)

这里结果是把Timer2设置为大约1kHz的定时器作为PWM的时钟。占空比由x_PR/T2PR确定。简单地说,要控制亮度,只需要写一个0-255的数值进x_PR寄存器,而无需担心PWM波形是怎么生成的——硬件PWM模块负责这一部分。

PIC汇编的一个缺点是访问寄存器之前必须选择正确的内存bank。如果遗漏了这个指令(手写汇编时很容易发生),那么访问的是别的寄存器,代码也就无法正常工作。

主程序

主程序相对来说比较直接,就是上文描述的三个相位:

    ; initialize RGB output
    banksel R_PR
    movlw   255
    movwf   R_PR
    clrf    G_PR
    clrf    B_PR

    ; mainloop
phase_A:    ; R\ G/ B_
    call    delay
    incf    G_PR
    decfsz  R_PR
    goto    phase_A
phase_B:    ; R_ G\ B/
    call    delay
    incf    B_PR
    decfsz  G_PR
    goto    phase_B
phase_C:    ; R/ G_ B\.
    call    delay
    incf    R_PR
    decfsz  B_PR
    goto    phase_C
    goto    phase_A

delay是一个延时程序,可以控制颜色变化的速度。可以用一个简单的计数器循环实现,代码我就省略不写了。

重置向量

用C开发时,main函数是程序的入口。实际上,重置向量 (reset vector) 才是真正的入口——这是微控制器在通电时或者重置时跳转到的地址。在PIC上,重置向量是0x0,并且只能放4条指令(因为0x4是中断向量)。所以,一般在重置向量的位置只放跳转指令,跳转到真正的入口。C编译器在幕后做了这些事情,而用汇编的时候则需要自己手动做。

    psect resetVec,class=CODE,delta=2
_resetVec:
    pagesel _main 
    goto    _main

在MPLAB X中,需要手动添加链接器选项-presetVec=0h,告诉链接器把这个psect放在这个地址。


分享