pixpop - tech

experiments, projects, miscellany

Sunday, January 23, 2011

Syntax highlighting for CNC G-Code files in Emacs

Here's a snippet of Emacs Lisp code that enables syntax highlighting for G-Code, the language used by CNC machines for machining under computer control. This highlighting is based on generic-mode, which is a kind of catch-all mode that Emacs uses for highlighting of all files with relatively simple syntax. It allows for highlighting of comments, keywords, and anything else that can be matched by a regular expression.

To use it, insert the following code into your .emacs file. I've selected '.ngc' as the file suffix for G-Code files, but you can make it anything you want if your system uses some other suffix. I chose '.ngc' because that's what's used by EMC which is the CNC software I'm using for my Laser machine.
(require 'generic-x)

(define-generic-mode gcode-generic-mode
  '(("(" . ")"))
  (apply 'append 
         (mapcar #'(lambda (s) (list (upcase s) (downcase s) (capitalize s)))
                 '("sub" "endsub" "if" "do" "while" "endwhile" "call" "endif" 
                   "sqrt" "return" "mod" "eq" "ne" "gt" "ge" "lt" "le" "and" 
                   "or" "xor" "atan" "abs" "acos" "asin" "cos" "exp" 
                   "fix" "fup" "round" "ln" "sin" "tan" "repeat" "endrepeat")))
  '(("\\(#<_?[A-Za-z0-9_]+>\\)" (1 font-lock-type-face))
    ("\\([NnGgMmFfSsTtOo]\\)" (1 font-lock-function-name-face))
    ("\\([XxYyZzAaBbCcUuVvWwIiJjKkPpQqRr]\\)" (1 font-lock-string-face))
    ("\\([\-+]?[0-9]*\\.[0-9]+\\)" (1 font-lock-constant-face))
    ("\\(#[0-9]+\\)" (1 font-lock-type-face))
    ("\\([0-9]+\\)" (1 font-lock-constant-face)))
  '("\\.ngc\\'")
  nil
  "Generic mode for g-code files.")

Here's a screen-cap that gives an idea of how it looks in action. I took this example from section 3.4 of the EMC Wiki "OWord" chapter.




HTML generated by org-mode 6.33x in emacs 23


Saturday, January 22, 2011

Setting multiple text properties in elisp

Recently I was creating some simple elisp functions to make journal entries, and wanted to be able to define a keybinding that would set some combination of text properties on the selected text, or on text subsequently entered.

For some reason, this didn't seem to work in any obvious (at least to me) manner, but eventually with help from gnu.emacs.help I found a method that works. The function to be used is (put-text-property start end prop value).

The name of this function implies that it sets only a single text property. This is more or less correct, as the property I want to set is the "face" property. This property controls many aspects of the appearance of thetext, but the ones I was interested in were bold, italic, and foreground color, and it is not at all clear from the emacs documentation how to set more than one of these.

The answer is that if the property you are setting is the "face" property, you can supply as list as the value, and the face will be set to a combination of the values in the list.

Here are some examples of how to do this (tested on Emacs 23). These calls set properties on the entire line containing point.

;; Italic only 
(put-text-property (line-beginning-position) (line-end-position) 
  'face 'italic) 

;; Blue only: 
(put-text-property (line-beginning-position) (line-end-position) 
  'face '(:foreground "blue")) 

;; Blue and bold: 
(put-text-property (line-beginning-position) (line-end-position) 
  'face '((:foreground "blue") bold)) 

;; Blue and italic: 
(put-text-property (line-beginning-position) (line-end-position) 
  'face '((:foreground "blue") italic)) 

;; Bold, italic, and red: 
(put-text-property (line-beginning-position) (line-end-position) 
  'face '(bold italic (:foreground "red"))) 

Thursday, February 04, 2010

Using a PIC microcontroller to control a CO2 laser

I recently acquired a nice Synrad CO2 laser for my laser cutter project. This laser has its power controlled by an opto-isolated PWM input. Synrad recommends a carrier frequency of 5 kHz. In addition they have a minimum duty cycle requirement, which they claim will possibly damage the laser if violated. The minimum duty cycle is 0.5%, which means 1.0 µs pulses at 5 kHz. They call these pulses 'Tickle Pulses'.

I needed a way to generate these pulses, as my laser did not come with the appropriate Synrad controller. I decided to program a PIC micro to perform this task, and like the Synrad controller allow me to set the power level with a potentiometer. I used a pic18f1220 for the task. This is my favorite microcontroller, and I've used it in many projects. I didn't need super high accuracy, so I opted to use the internal 8 MHz oscillator and no crystal.

First, to set the PWM frequency. I used timer 2 as the PWM clock. When using the 8MHz internal clock in the PIC, the basic instruction clock is 2 MHz. I set timer 2 to use that as it's clock source, with a /4 prescaler to give 500 kHz. Then I set the timer 2 period register to 99, meaning divide by 100, to get 5 kHz. So timer 2 repeatedly countes from 0 thru 99, resetting itself every 200 µs. The PWM works by comparing this timer to another register (CCPR1L, CCP1CON). It turns the PWM output on when timer 2 resets, and turns if off when timer 2 equals the CCPR1L value. Since timer 2 never exceeds a count of 99, setting the PWM duty cycle to any value greater than 99 will cause 100% duty cycle.

Now, the a/d convertor has 10 bits of resolution, giving values from 0 thru 1023. Since I need 0..99, I throw away the lo 3 bits of the a/d convertor value. This gives me a range of 0..127. To get full use of my potentiometer, I put a resistor in series with it so that when set to full scale, I get an a/d value of about 99. Then I added some math so that whatever the a/d value is, the PWM duty cycle will never provide pulses less than 1 µs.

Next, I added a pushbutton, connected to RA5. RA5 is normally the reset input, so I disabled the reset input and used the internal reset circuit instead. I also connected an LED to RA2. I setup the code for the LED so it blinks at a few Hz to indicate that tickle pulses are being generated. If I press the 'fire' button, the code uses the value from the potentiometer to set the PWM duty cycle, and the LED turns on continuously.

This works well. When the button is not pressed, the 'lase' indicator on the back of the laser lights up dimly, and there's no output from the laser. When I press the button, the 'lase' indicator lights up with brightness proportional to the duty cycle. When the duty cycle gets above a certain threshold, the laser action begins and you can hear a 5 kHz tone coming from it. The laser has an auxiliary DC output, so I used that to power the TPG. So as soon as the laser receives power, it also starts getting tickle pulses on its PWM input. The power supply has an enable input, so I can use that to switch power to both the laser and the TPG.

Ultimately, I'll use EMC to control this laser, and will connect a signal from it to the signal on RA5 so that the EMC G-Code can turn the laser on and off. Note that while this is intended for use with a Synrad laser, it should work fine with any of the Chinese disposable CO2 tubes that have a PWM input on their power supply. Here's the PIC source code for those interested:

;
;   tickle.asm
;
;   Generate tickle pulses for Synrad laser
;
        list            st=on   ;dump the symbol table
        list            n=9999  ;lines per page
        radix           dec
        processor       18f1220
        #include        <P18F1220.INC>
        #include        utility.inc

        ;noexpand
        list

    __CONFIG    _CONFIG1H, _IESO_ON_1H & _FSCM_OFF_1H & _INTIO1_OSC_1H
    __CONFIG    _CONFIG2L, _PWRT_ON_2L & _BOR_OFF_2L 
    __CONFIG    _CONFIG2H, _WDT_OFF_2H & _WDTPS_32K_2H
    __CONFIG    _CONFIG3H,  _MCLRE_OFF_3H
    __CONFIG    _CONFIG4L,  _DEBUG_OFF_4L & _LVP_OFF_4L & _STVR_OFF_4L
    __CONFIG    _CONFIG5L,  _CP0_OFF_5L & _CP1_OFF_5L
    __CONFIG    _CONFIG5H,  _CPB_OFF_5H & _CPD_OFF_5H
    __CONFIG    _CONFIG6L,  _WRT0_OFF_6L & _WRT1_OFF_6L
    __CONFIG    _CONFIG6H,  _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
    __CONFIG    _CONFIG7L,  _EBTR0_OFF_7L & _EBTR1_OFF_7L
    __CONFIG    _CONFIG7H,  _EBTRB_OFF_7H


;
;   Declare RAM locations
;
            udata   0

duty_lo res     1               ;PWM Duty cycle ls bits
duty_hi res     1               ;PWM duty cycle ms bits

a_d_lo  res     1               ;A/D convertor ls bits
a_d_hi  res     1               ;A/D convertor ms bits


blink   res     1


duty_min_lo     equ 0x80
duty_min_hi     equ 0x00
    
;
;       Reset and interrupt vectors
;
        org     0                   ; Reset vector: 8 bytes
        goto    main


main    movlb   0                   ;initialize bank select reg
        
        rcall   init
        clrf    LATA

idle
    btfss   INTCON, TMR0IF      ;Timer expired?
    bra     idle_10             ;Branch if not
    bcf     INTCON, TMR0IF      ;Reset the flag
    incf    blink,f
    
idle_10    
    btfss   PORTA, RA5          ;Is the button pressed?
    bra     fire                ;Branch if so

    movlwf  duty_min_lo, duty_lo
    movlwf  duty_min_hi, duty_hi
    rcall   set_pwm_duty        ;Set minimum duty cycle for tickle pulse

    btfsc   blink,0             ;Should LED be on ?
    bra     idle_20             ;Branch if so
    bcf     LATA, RA2           ;Else turn LED off
    bra     idle
idle_20
    bsf     LATA, RA2           ;Turn the LED on
    bra     idle

;;
;; The fire button is pressed. If the a/d conversion is done, read the value
;; and set the pwm duty cycle. Otherwise, use the most recent a/d value.
;; Then force the LED on. 
;; 
fire    
    btfsc   ADCON0, GO_DONE     ;Is A/D conversion complete?
    bra     a_d_busy            ;Branch if not

a_d_done
    movff   ADRESL, a_d_lo
    movff   ADRESH, a_d_hi
    bsf     ADCON0, GO_DONE     ;Start another conversion
    
    movfw   a_d_lo
    rrcf    a_d_hi,f            ;Divide by 2
    rrcf    WREG,w
    andlw   0xc0
    movwf   a_d_lo

    tstfsz  a_d_hi
    bra     a_d_busy
    btfsc   a_d_lo, 7
    bra     a_d_busy
    movlwf  0x80, a_d_lo        ;Clamp value at 1 usec

a_d_busy
    movff   a_d_lo, duty_lo
    movff   a_d_hi, duty_hi
    rcall   set_pwm_duty

    bsf     LATA, RA2          ;Force the LED on
    bra     idle
        
        
init 
;;
;; Initialize clock frequency
;;
        movlwf  0x72, OSCCON    ;Internal 8 Mhz clock
    
;
;   Initialize Port pins as inputs or outputs
;
        movlwf  0x21, TRISA            ;RA0 is analog input. RA5 is pushbutton in.
        clrf    PORTA
        movlwf  0x0, TRISB             ;Set port B direction bits as described
        clrf    PORTB  
;
;   initialize A/D convertor
;
        movlwf  0x7e, ADCON1            ;Make only channel 0 an Analog input
        movlwf  0x3e, ADCON2            ;Setup A/D timing, and left justify.
        movlwf  0x01, ADCON0            ;Turn A/D convertor on
        bsf     ADCON0, 1               ;Start conversion

;
;   Initialize Timer 2 for PWM
;   
        movlwf  99, PR2                 ;Setup PWM period for 5 kHz
        movlwf  0x05, T2CON             ;Timer 2 on, /4 Prescaler

        call    set_pwm_duty

;
;   Initialize Timer 0 for LED blink
;
        movlwf  0xc7, T0CON              ;Divide instruction clock by 256    
        return                                    

    
set_pwm_duty
        movff       duty_hi, CCPR1L     ;Set ms 8 bits of duty cycle
        rrncf       duty_lo, w,1        
        rrncf       WREG, w
        andlw       0x30             
        iorlw       0x0c
        movwf       CCP1CON             ;Write PWM mode and 2 ls bits 
        return

    
        end