LabView with a Silicon Laboratories development kit

The devboard

I've bought the C8051F34X Development Kit (which is available at Digikey for small money) because I want to play a bit with USB devices and testing the 8051 instruction set and architecture. The Blinky sample worked without problems with the IDE. Compiling larger programs is difficult, because the Keil compiler is an evaluation version, only, which limits the maximum program size. But there is SDCC support in the IDE. I tried it, but couldn't figure out how to define aliases for registers, so I installed a later version of the ASxxxx assembler, which was used by SDCC. This version doesn't work with the IDE, because the command line arguments are different and the required output file name and format is different (looks like the Silabs IDE can't use Intel Hex for the toolchain generated files). The editor in the IDE is not very good anyway, so I switched to my favorite editor Ultraedit and used a small build.cmd script for compiling the file:

C:\Programme\ASXV4PXX\ASXMAK\VC6\EXE\AS8051.EXE -slox blinky.asm
C:\Programme\ASXV4PXX\ASXMAK\VC6\EXE\ASLINK.EXE -i blinky blinky
xcopy /y C:\SiLabs\MCU\Examples\C8051F34x\Blinky\blinky.ihx "c:\Dokumente und Einstellungen\Frank\blinky.hex"

The IDE has a nice option "Download object file" in the Debug menu, where you can specify an Intel Hex file, which will be flashed to the device. This setup works, so finally I have mastered the usual development tool quirks.

The demo program samples the ADC, which is connected to the pin on the development board with a potentiometer. It sends the average of 16 values, sampled every millisecond, every 16 ms, with the P2.0 state (a button on the devboard) in bit 7. Received bytes from the serial port are written to P2 (only for P2.2 and P2.3, the other bits are overwritten with 1 to avoid problems with the open drain GPIOs). This is the source:

.include "c8051f340.inc"

GREEN_LED1 == P2.2                      ; green LED1
GREEN_LED2 == P2.3                      ; green LED2

.define TEMP      "R2"                  ; temporary register
.define ADC_COUNT "R3"                  ; current number of accumulated ADC samples
.define ADC_SUM_L "R4"                  ; accumulated samples value, low byte
.define ADC_SUM_H "R5"                  ; accumulated samples value, high byte

        .area   CODE (ABS)

        .org    0
reset:  ljmp    start                   ; reset vector

        .org    0x02B                   ; interrupt vector for timer2
        ajmp    timer2                  

        .org    0x100
start:  anl     PCA0MD, #0xff-0x40      ; clear watchdog enable bit
        mov     OSCICN, #0x83           ; set oscillator to full speed internal clock, 12 MHz

; setup 4x clock multiplier
        mov     CLKMUL, #0x00
        mov     CLKMUL, #0x80           ; enable clock multiplier
        clr     a                       ; wait a bit for initialization
        djnz    acc, .
        orl     CLKMUL, #0xc0           ; initialize clock multiplier
cinit:  mov     a, CLKMUL
        jnb     ACC.5, cinit            ; wait for stabilization
        mov     CLKSEL, #3              ; select full 48 MHz for system clock and USB clock

; setup ports
        orl     P2MDOUT, #0x0c          ; make LEDs pin output push-pull
        orl     P2MDIN, #0x0c           ; make LEDs pin input mode digital
        setb    GREEN_LED1              ; turn on LED1
        anl     P1MDIN, #0xff-0x02      ; set P1.1 as an analog input
        
; setup ADC
        mov     ADC0CN, #0x00           ; ADC0 disabled, normal tracking
        mov     REF0CN, #0x08           ; use VDD as AD reference
        mov     AMX0P, #0x04            ; ADC0 positive input = P2.5
        mov     AMX0N, #0x1F            ; ADC0 negative input = GND (single ended mode)
        mov     ADC0CF, #0xfc           ; set SAR clock to minimum and left-justify results
        setb    AD0EN                   ; enable ADC0
        mov     ADC_COUNT, #0x10        ; initialize counter for ADC accumulation
        
; setup timer2 for an interrupt every millisecond
        mov     TMR2CN, #0x00           ; stop timer2; clear TF2; use SYSCLK/12 as timebase for T2XCLK
        mov     TMR2RLH, #0xf0          ; init reload values
        mov     TMR2RLL, #0x60
        mov     TMR2H, #0xff            ; set to reload immediately
        mov     TMR2L, #0xff
        setb    ET2                     ; enable timer2 interrupts
        setb    TR2                     ; start timer2

; setup UART0        
        orl     P0MDOUT, #0x10          ; enable UTX as push-pull output
        mov     XBR0, #0x01             ; enable UART on P0.4(TX) and P0.5(RX)                     
        mov     SCON0, #0x10            ; 8 bit, ignore stop bit, rx enabled, 9th bits are zeros, clear RI0 and TI0 bits
        mov     TH1, #0x30
        mov     TL1, #0x30
        orl     CKCON, #8               ; T1M = 1 (timer1 UART baud rate generator uses system clock)
        mov     TMOD, #0x20             ; timer 1 in 8-bit autoreload
        setb    TR1                     ; start timer1
        setb    TI0                     ; Indicate TX0 ready
        
        mov     XBR1, #0x40             ; enable Crossbar
        setb    EA                      ; enable global interrupts

        ajmp    .                       ; wait forever


; timer2 interrupt handler
timer2: clr     TF2H                    ; clear timer2 interrupt flag

        lcall   sample
        dec     ADC_COUNT
        mov     a, ADC_COUNT
        jnz     testA                   ; skip ADC output, until 16 values accumulated
        mov     ADC_COUNT, #0x10        ; reinitialize counter for ADC accumulation
        lcall   avg                     ; get average ADC value in accu
        anl     a, #0xfe                ; discard lsb
        rr      a
        jb      P2.0, notSet            ; test button
        orl     a, #0x80                ; set msb, if button is pressed
notSet: lcall   emit                    ; transfer to RS232
        
testA:  lcall   avail                   ; test if RS232 data is available
        jnb     ACC.0, iend
        mov     a, SBUF0                ; get byte
        orl     a, #0xf3                ; set 1 for all input pins
        mov     P2, a                   ; save in port2
        
iend:   reti                            ; return from interrupt


; sends a byte to RS232 and waits until transfered
emit:   mov     SBUF0, a                ; send byte
emit2:  mov     a, SCON0
        jnb     ACC.1, emit2            ; wait until transfered
        clr     SCON0+1                 ; clear transmit bit
        ret


; tests, if a byte is available from RS232
; return: accu=1, if available, 0 otherwise
avail:  mov     a, SCON0
        anl     a, #1
        clr     SCON0+0
        ret

; sample one ADC value and accumulate it in r5/r6
sample: setb    AD0BUSY                 ; start AD conversion
waitAD: mov     a, AD0BUSY              ; wait until AD conversion is finished
        jb      ACC.0, waitAD
        mov     a, ADC0H
        add     a, ADC_SUM_L
        mov     ADC_SUM_L, a
        jnc     s2
        inc     ADC_SUM_H
s2:     ret

; get the average of 16 accumulated ADC values from ADC_SUM and reinitialize it
; return: accu=average
avg:    mov     TEMP, #4
shift:  clr     C                       ; clear carry
        mov     a, ADC_SUM_H            ; shift ADC_COUNT_L/ADC_COUNT_H one bit right
        rrc     a
        mov     ADC_SUM_H, a
        mov     a, ADC_SUM_L
        rrc     a
        mov     ADC_SUM_L, a
        djnz    TEMP, shift             ; shift four times = divide by 16

        mov     a, ADC_SUM_L            ; return result in accu
        mov     ADC_SUM_L, #0           ; reinitialize ADC_SUM
        mov     ADC_SUM_H, #0
        ret

I like the instruction set, the bit-set and bit-clear operations are very handy. And many instructions of the C8051F34X chips are executed in one ow two clock cycles, which means you have up to 48 MIPS speed (but with some Silabs parts the system clock is limited to 25 MIPS). No external clock is required, the internal 12 MHz clock can be divided and multiplied up to 48 MHz, if the accuracy from 11.82 MHz to 12.18 MHz is sufficient.

A first LabView test

Some time ago there was a full version of LabView in a German computer magazin DVD for personal use. I've seen some expensive PCI cards for integration with LabView, with ADC, GPIOs etc., but for many tasks the multiplexed ADCs and many GPIOs of one Silabs chip, wired with RS232 to the PC, is all you need to control and measure some external hardware, so this was a good test for me to try out LabView, because the graphical, schematic-like programming environment looks very user friendly and easier for electronic engineers than writing some text programs. And after reading some tutorials, finally I managed to create my first Virtual Instrument. Once you have learned the basics, it is very easy to create your own instruments. To do all the minor format conversions, numerical constants etc. with symbols needs getting used to, but the same is true for writing programs with a text editor. I don't know how it scales for bigger projects (but you can encapsulate functionalities inside user defined symbols, which should help), but this small project looks very nice and clear, and it is fun to work with.

This is the GUI of the virtual instrument:

The pointer of the round instrument is updated very fast when tuning the potentiometer and there is nearly no CPU usage.

The flow diagram for this instrument:

You can download the instrument. Maybe I'll buy the commercial version and use it more often instead of writing text programs. For virtual instruments it is more natural to just draw it instead of writing lots of text for implementing it.


10. Februar 2008, Frank Buß