Pretty good weekend effort

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
For quick and easy adjustments, I included a Bourns PEC12R incremental encoder in my design.

I've never used this product before, but now that I have, I'll say this: it is the single most retarded incremental encoder I have ever had the displeasure of using.

The part has 24 mechanical detents throughout the 360° turn of the shaft. The detents are pretty solid -- it is nearly impossible to leave the shaft out of alignment with a detent. This is a good thing.

Here's where it gets crazy: if you properly decode the quadrature output, you get 4 counts per detent! Why not one? It's not like you can use the 4 counts -- the shaft doesn't stop mid-detent (unless you really, really try).

So, you've got to interpret: 4 quadrature counts == 1 real count. Maybe I am missing something, but it seems pretty stupid to have 24 positions representing 96 counts.

I will allow this as an explanation: I suppose if you are not interest in direction, one could just use a single A or B output to produce 24 rising (or falling) edges per revolution. But why use an encoder, then?

For fun, here's my port B IOC interrupt code to drive the encoder:

Code:
;*****************************************
;** ENCINT -- Encoder Interrupt routine **
;*****************************************

encint    swapf    portb,w        ;capture new encoder value (bits 4:5 -> 0:1)
    andlw    b'00000011'    ;mask encoder inputs
    bcf    intcon,rbif    ;clear interrupt

    rlncf    _enclst,f    ;shift old values
    rlncf    _enclst,f
    iorwf    _enclst,w    ;merge in new
    andlw    b'1111'        ;modulo 16
    movwf    _enclst        ;save as new last

    cjump    _enclst

    bra    eina        ;no action
    bra    eidec        ;decrement
    bra    eiinc        ;increment
    bra     eina        ;no action

    bra    eiinc        ;increment
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eidec        ;decrement

    bra    eidec        ;decrement
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eiinc        ;increment

    bra    eina        ;no action
    bra    eiinc        ;increment
    bra    eidec        ;decrement
    bra     eina        ;no action

eidec    decf    _encinc,f    ;decrement encoder counts
    bra    eiupd        ;and indicate update

eiinc    incf    _encinc,f    ;increment encoder counts

eiupd    bsf    _encrdy        ;indicate new encoder data ready

eina    goto    intdone        ;and get out
 

BillB3857

Joined Feb 28, 2009
2,573
Just a thought on the 4 counts per detent thing.. The manufacturer can produce several different models using the same optical system and simply install a different detent mechanism. Anything to save $0.20!
 

cmartinez

Joined Jan 17, 2007
8,768
For quick and easy adjustments, I included a Bourns PEC12R incremental encoder in my design.

I've never used this product before, but now that I have, I'll say this: it is the single most retarded incremental encoder I have ever had the displeasure of using.

The part has 24 mechanical detents throughout the 360° turn of the shaft. The detents are pretty solid -- it is nearly impossible to leave the shaft out of alignment with a detent. This is a good thing.

Here's where it gets crazy: if you properly decode the quadrature output, you get 4 counts per detent! Why not one? It's not like you can use the 4 counts -- the shaft doesn't stop mid-detent (unless you really, really try).

So, you've got to interpret: 4 quadrature counts == 1 real count. Maybe I am missing something, but it seems pretty stupid to have 24 positions representing 96 counts.

I will allow this as an explanation: I suppose if you are not interest in direction, one could just use a single A or B output to produce 24 rising (or falling) edges per revolution. But why use an encoder, then?

For fun, here's my port B IOC interrupt code to drive the encoder:

Code:
;*****************************************
;** ENCINT -- Encoder Interrupt routine **
;*****************************************

encint    swapf    portb,w        ;capture new encoder value (bits 4:5 -> 0:1)
    andlw    b'00000011'    ;mask encoder inputs
    bcf    intcon,rbif    ;clear interrupt

    rlncf    _enclst,f    ;shift old values
    rlncf    _enclst,f
    iorwf    _enclst,w    ;merge in new
    andlw    b'1111'        ;modulo 16
    movwf    _enclst        ;save as new last

    cjump    _enclst

    bra    eina        ;no action
    bra    eidec        ;decrement
    bra    eiinc        ;increment
    bra     eina        ;no action

    bra    eiinc        ;increment
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eidec        ;decrement

    bra    eidec        ;decrement
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eiinc        ;increment

    bra    eina        ;no action
    bra    eiinc        ;increment
    bra    eidec        ;decrement
    bra     eina        ;no action

eidec    decf    _encinc,f    ;decrement encoder counts
    bra    eiupd        ;and indicate update

eiinc    incf    _encinc,f    ;increment encoder counts

eiupd    bsf    _encrdy        ;indicate new encoder data ready

eina    goto    intdone        ;and get out
Can the detent mechanism be removed?
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
Just a thought on the 4 counts per detent thing.. The manufacturer can produce several different models using the same optical system and simply install a different detent mechanism. Anything to save $0.20!
Not optical, mechanical.

Actually, I discovered that I can use the extra counts to add digital hysteresis so that a slight knock of the shaft doesn't produce spurious counts. I like it now.
 

cmartinez

Joined Jan 17, 2007
8,768
Not optical, mechanical.

Actually, I discovered that I can use the extra counts to add digital hysteresis so that a slight knock of the shaft doesn't produce spurious counts. I like it now.
Funny ... you posted your displeasure with this thing at 3:00 a.m. ... and this morning you've fallen in love with it ... that's why I never argue with my wife after dark ... :D
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
Funny ... you posted your displeasure with this thing at 3:00 a.m. ... and this morning you've fallen in love with it ... that's why I never argue with my wife after dark ... :D
This is why I tend to give engineers the benefit of the doubt when it seems they've done something stupid. In this case, I was tired and grumpy from coding and debugging all day. I recognized the brilliance after a good night's sleep.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
18F65K22: Is there a typo in the datasheet?

Selection_055.png

SSPM = b'1010' does not work (BF never sets after a write to SSP1BUF), but b'0001' does. Anyone experience this?

Also, I've discovered using MOVFF reg,SSP1BUF does not seem to clear the BF flag, but MOVWF SSP1BUF does.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
Here's my incremental encoder ISR including hysteresis:

Code:
;*****************************************
;** ENCINT -- Encoder Interrupt routine **
;*****************************************

encint    swapf    portb,w        ;capture new encoder value (bits 4:5 -> 0:1)
    andlw    b'00000011'    ;mask encoder inputs
    bcf    intcon,rbif    ;clear interrupt

    rlncf    _enclst,f    ;shift old values
    rlncf    _enclst,f
    iorwf    _enclst,w    ;merge in new
    andlw    b'1111'        ;modulo 16
    movwf    _enclst        ;save as new last

    cjump    _enclst

    bra    eina        ;no action
    bra    eidec        ;decrement
    bra    eiinc        ;increment
    bra     eina        ;no action

    bra    eiinc        ;increment
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eidec        ;decrement

    bra    eidec        ;decrement
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eiinc        ;increment

    bra    eina        ;no action
    bra    eiinc        ;increment
    bra    eidec        ;decrement
    bra     eina        ;no action

eidec    decf    _encinc0,f    ;decrement encoder counts
    bra    eiupd        ;and indicate update

eiinc    incf    _encinc0,f    ;increment encoder counts

eiupd    bbs    _encinc0,7,eiuneg     ;result negative?

eiupos    bbc    _encinc0,2,eina    ;need 4 counts per detent

    incf    _encinc,f    ;4 counts == 1 detent
    bsf    _encup        ;indicate up count
    bra    eiex

eiuneg    movlw    b'11'        ;need 4 counts per detent
    andwf    _encinc0,w
    bnz    eina

    decf    _encinc,f
    bsf    _encdn        ;indicate down count

eiex    clrf    _encinc0    ;clear counter for next 0
    bsf    _encrdy        ;indicate new encoder data ready

eina    goto    intdone        ;and get out
And the polling routine (called from the main program loop):

Code:
;*****************************
;** POLLENC -- Poll Encoder **
;*****************************

pollenc    movlw    b'00001111'    ;preclear static flags
    andwf    encflag,f

    retbc    _encrdy        ;new encoder data ready?

;capture encoder increments since last check into temp0

    bcf    intcon,rbie    ;disable further interrupts

    movff    _encinc,temp0    ;capture increment
    movff    encflag,temp1    ;capture volatile flags

    clrw
    movff    wreg,_encinc    ;clear volatile increments
    clrf    encflag        ;and volatile flags for next interrupt

    bsf    intcon,rbie     ;reinable int
  
;add signed 8 bit increments into enccnt

    movfw    temp0        ;get increment/decrement
    addwf    enccnt,f    ;update new counts

;set static flags

    swapf    temp1,w        ;duplicate low nibble to high
    andlw    b'11110000'
    iorwf    encflag        ;set static flags      

    return            ;and get out
The encoder flag register is defined thusly:

Code:
#define  _encrdy   encflag,0        ;1=Optical Encoder data ready (volatile)
#define  _encup    encflag,1        ;1=encoder up
#define  _encdn    encflag,2        ;1=encoder down
;#define           encflag,3        ;
#define  encrdy    encflag,4        ;1=Optical Encoder data ready (static)
#define  encup     encflag,5        ;1=encoder up
#define  encdn     encflag,6        ;1=encoder down
;#define           encflag,7        ;
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
So far, everything on my new board is working perfectly.

I'm already up to 14,785 lines of assembly code. Some would say this is an unmanageable amount of .asm code (C is soooo much better, donchya know!), but my modular approach to code construction -- and extremely effective and efficient cooperative multitasking -- makes coding and maintenance quick and easy.

Here is my top level .asm file which controls the build and starts execution:

Code:
;#define TESTCODE            ;uncomment for instant jump to testxxx for sim
;#define NOSWITCH            ;uncomment for instant on
;#define NOCODEPROTECT            ;uncomment for no code protection
;#define NOAUTOPOWER            ;uncomment for no auto power off

;0x7DC0-0x7FFF used by ICD (picf65k22)
;0x3FF8-0x3FFF used by configuration words

;    radix        dec        ;decimal radix (on command line)
;    list        n=0,st=off    ;exclude symbol table
    list        n=0,st=on    ;include symbol table
;    errorlevel     -311        ;exclude assembler messages

;Included Modules

    include    "buildoptions.inc"    ;get build configuration options
    include "frvg.inc"        ;general framework program definitions
    include    "error.inc"        ;error definitions
    include    "floatlib.inc"        ;floating point library definitions
    include "eeprom.inc"        ;eeprom library definitions
    include    "keypad.inc"        ;keypad definitions
    include "dsspi.inc"        ;SPI interface
    include "ads1242.inc"        ;ADS1242 definitions
    include "xxxxxx.inc"        ;xxxxxx definitions (secret!)
    include "ad5683.inc"        ;ADS5683 definitions
    include "ad.inc"        ;On-chip A/D definitions
    include "led.inc"        ;LED driver
    include "encoder.inc"        ;Incremental Encoder
    include "lcd.inc"        ;Character LCD definitions
    include "bluetooth.inc"        ;bluetooth definitions

;Reset Vector

    org    H'0000'

    nop                ;required for ICD
    movlb    defbank            ;set bank 1 ram as default
    goto    start            ;goto program start vector

;Support Libraries

    include "interrupts.asm"    ;interrupt handlers
    include "gensub.asm"        ;general subroutines
    include "systim.asm"        ;system timer
    include "floatlib.asm"        ;floating point library
    include "calculations.asm"    ;numeric calculations
    include "syscalls.asm"        ;system calls
    include "init.asm"        ;initialization
    include "main.asm"        ;main program
    include "states.asm"        ;state processing
    include "power.asm"        ;power processing
    include    "keypad.asm"        ;keypad processing
    include "dsspi.asm"        ;SPI interface
    include "ads1242.asm"        ;ADS1242 processing
    include "xxxxx.asm"        ;xxxxx processing (secret!)
    include "ad5683.asm"        ;DS5683 procesing
    include "ad.asm"        ;on-chip A/D converter
    include "sigpro.asm"        ;signal processing
    include "calibration.asm"    ;calibration routines
    include "statistics.asm"    ;statistics
    include    "eeprom.asm"        ;for on-chip eeprom
    include "error.asm"        ;error processing
    include "led.asm"        ;LED driver
    include "encoder.asm"        ;Optical Encoder driver
    include "lcd.asm"
    include "bluetooth.asm"        ;for Bluetooth

    end
And this is the "main" program loop. Each call in the loop is non-blocking, and only uses as much CPU time as -- and if -- necessary to complete its job:

Code:
;******************
;** Reset Vector **
;******************

start    clrf    stkptr            ;clear stack pointer
    call    startup            ;(init.asm)

;*** TEST CODE LOOP ***

#ifdef TESTCODE
simlp    call    testxxx            ;infinite loop for partial code simulation
    goto    simlp
#endif

;*** END TEST CODE LOOP ***

;*******************************
;** MAIN -- Main Program Loop **
;*******************************

main    call    polltim            ;poll system timer       (systim.asm)
    call    pollkey            ;poll keypad            (keypad.asm)
    call    pollenc            ;poll incremental encoder    (encoder.asm)
    call    prokey            ;process keypresses        (keypad.asm)
    call    proenc            ;process encoder changes    (encoder.asm)
    call    prosig            ;process analog signal chain    (sigpro.asm)
    call    pollda            ;poll d/a converter        (ad5683.asm)
    call    prostate        ;process states            (states.asm)
    call    pollled            ;poll LEDs            (led.asm)
    call    propwr            ;process power management    (power.asm)
    call    polllcd            ;poll lcd device        (lcd.asm/lcdcb.asm)
    call    pollbt            ;poll bluetooth transceiver     (bluetooth.asm)
    call    proee            ;process EERAM            (eeprom.asm)

    bra    main            ;and do it all over again

;******** END MAIN ********
As a side note -- and yet another complaint! -- please, management, make it possible to line up the tabs properly when displaying .asm code. The current setup makes my code look crappy. It is so much more beautiful in real life.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
My character LCD driver is awesome. It is based on a library I wrote around 10 years ago, and updated for this app. Some nice features:

-- Completely non-blocking. Doesn't stall waiting for the BUSY flag to clear after writing command/data.
-- Auto initialization and power off -- the app need not be aware the LCD is even present. It takes care of itself.
-- Digital contrast control -- nice feature of the Newhaven NHD-C0216...
-- Multiple language support -- makes internationalization a snap!
-- Full support for a hierarchical menu system, including editable menu items.
-- Seamless time-based message flashing, swapping and message replacement.
-- Automatically retrieves arbitrary data and formats as necessary (including ints and floats to ASCII with arbitrary field sizes, precision, zero blanking, and punctuation) and only when the display requires repainting.
-- Integrates seamlessly with the keypad/encoder drivers to cycle through various modes, adjustments, and menu actualization.

There's a lot more, but those are the main points.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
I wasn't completely happy with my hysteretic encoder code in post #50. The code enforced 4 full counts (1 detent up or down) before producing an up or down count to the app. The encoder is kinda sloppy, so sometimes you get 3 counts, and sometimes 5, for each detent. It worked, but sometimes you'd need to jog the shaft a bit to realize the count.

So I modified the code further. Now, +/- 3 counts produce an output count, while still maintaining an average of four minor counts to a major count. It works *much* better -- and is now pretty much immune to the sloppiness of the encoder.

Only the ISR has changed. Here it is:

Code:
;*****************************************
;** ENCINT -- Encoder Interrupt routine **
;*****************************************

encint    swapf    portb,w        ;capture new encoder value (bits 4:5 -> 0:1)
    andlw    b'00000011'    ;mask encoder inputs
    bcf    intcon,rbif    ;clear interrupt

    rlncf    _enclst,f    ;shift old values
    rlncf    _enclst,f
    iorwf    _enclst,w    ;merge in new
    andlw    b'1111'        ;modulo 16
    movwf    _enclst        ;save as new last

    cjump    _enclst

    bra    eina        ;no action
    bra    eidec        ;decrement
    bra    eiinc        ;increment
    bra     eina        ;no action

    bra    eiinc        ;increment
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eidec        ;decrement

    bra    eidec        ;decrement
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eiinc        ;increment

    bra    eina        ;no action
    bra    eiinc        ;increment
    bra    eidec        ;decrement
    bra     eina        ;no action

eidec    decf    _enccnt,f    ;decrement encoder counts
    bra    eiupd        ;and indicate update

eiinc    incf    _enccnt,f    ;increment encoder counts

eiupd    cjump    _enccnt        ;process hysteretic action

    bra    eist0
    bra    eist1
    bra    eina
    bra    eist3
    bra    eina
    bra    eist5
    bra    eina
    bra    eist7
    bra    eist0

eist0    bcf    _encbup
    bcf    _encbdn
    movlf    _enccnt,4
    bra    eina

eist1    bsf    _encbdn
    bra    eidn

eist3    bbc    _encbdn,eina
    bcf    _encbdn
    bra    eiup

eist5    bbc    _encbup,eina
    bcf    _encbup
    bra    eidn

eist7    bsf    _encbup

eiup    incf    _encinc,f
    bsf    _encup
    bra    eiex

eidn    decf    _encinc,f
    bsf    _encdn

eiex    bsf    _encrdy        ;indicate new encoder data ready

eina    goto    intdone        ;and get out
Note that I am using the jump tables for speed, not code size.
 
Last edited:

cmartinez

Joined Jan 17, 2007
8,768
I wasn't completely happy with my hysteretic encoder code in post #50. The code enforced 4 full counts (1 detent up or down) before producing an up or down count to the app. The encoder is kinda sloppy, so sometimes you get 3 counts, and sometimes 5, for each detent. It worked, but sometimes you'd need to jog the shaft a bit to realize the count.

So I modified the code further. Now, +/- 3 counts produce an output count, while still maintaining an average of four minor counts to a major count. It works *much* better -- and is now pretty much immune to the sloppiness of the encoder.

Only the ISR has changed. Here it is:

Code:
;*****************************************
;** ENCINT -- Encoder Interrupt routine **
;*****************************************

encint    swapf    portb,w        ;capture new encoder value (bits 4:5 -> 0:1)
    andlw    b'00000011'    ;mask encoder inputs
    bcf    intcon,rbif    ;clear interrupt

    rlncf    _enclst,f    ;shift old values
    rlncf    _enclst,f
    iorwf    _enclst,w    ;merge in new
    andlw    b'1111'        ;modulo 16
    movwf    _enclst        ;save as new last

    cjump    _enclst

    bra    eina        ;no action
    bra    eidec        ;decrement
    bra    eiinc        ;increment
    bra     eina        ;no action

    bra    eiinc        ;increment
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eidec        ;decrement

    bra    eidec        ;decrement
    bra     eina        ;no action
    bra     eina        ;no action
    bra    eiinc        ;increment

    bra    eina        ;no action
    bra    eiinc        ;increment
    bra    eidec        ;decrement
    bra     eina        ;no action

eidec    decf    _enccnt,f    ;decrement encoder counts
    bra    eiupd        ;and indicate update

eiinc    incf    _enccnt,f    ;increment encoder counts

eiupd    cjump    _enccnt        ;process hysteretic action

    bra    eist0
    bra    eist1
    bra    eina
    bra    eist3
    bra    eina
    bra    eist5
    bra    eina
    bra    eist7
    bra    eist0

eist0    bcf    _encbup
    bcf    _encbdn
    movlf    _enccnt,4
    bra    eina

eist1    bsf    _encbdn
    bra    eidn

eist3    bbc    _encbdn,eina
    bcf    _encbdn
    bra    eiup

eist5    bbc    _encbup,eina
    bcf    _encbup
    bra    eiup

eist7    bsf    _encbup

eiup    incf    _encinc,f
    bsf    _encup
    bra    eiex

eidn    decf    _encinc,f
    bsf    _encdn

eiex    bsf    _encrdy        ;indicate new encoder data ready

eina    goto    intdone        ;and get out
Note that I am using the jump tables for speed, not code size.
You're right, your beautiful code is severely disfigured by the sucky tab display on this page.
 

nsaspook

Joined Aug 27, 2009
16,330
So far, everything on my new board is working perfectly.

I'm already up to 14,785 lines of assembly code. Some would say this is an unmanageable amount of .asm code (C is soooo much better, donchya know!), but my modular approach to code construction -- and extremely effective and efficient cooperative multitasking -- makes coding and maintenance quick and easy.
Any sufficiently designed assembly coding system will eventually become a high level language so many of us start with a HLL in the beginning.:D
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
Any sufficiently designed assembly coding system will eventually become a high level language so many of us start with a HLL in the beginning.:D
If I ever discover a desire to work twice as hard to generate twice the code footprint running at half the speed, I'll switch to C.

Maybe when I retire and have time to waste.
 

nsaspook

Joined Aug 27, 2009
16,330
If I ever discover a desire to work twice as hard to generate twice the code footprint running at half the speed, I'll switch to C.

Maybe when I retire and have time to waste.
Like you said before, we code in different worlds, with different objectives. I'm just about finished re-hosting the initial machine low-level drivers (full interrupt driven with DMA I/O transfers on some devices) of a MIPS32 version of the riot-os on a PIC32 dev board. This is using the MIPS32 bare-metal compiler on Linux to cross-compile the C code and link it into a compatible hex file using IPE to flash the board. There is no PLIB, no Harmony or Microchip framework to use, only C (with a small amount of assembly), SFR defines in the device header and the riot-os API. The objective is different. What's needed on this project is to make robust, generalized and easily understandable fast code that can be reused and ported. My first test application for the new host board OS with a completely different C compiler (the mips GCC port) and CPU was the C language PIC24 RN4020 project app I posted here. After a small amount of rework (hours) on the BLE project device I/O model to match the current OS api it runs even better on the new platform.

https://github.com/nsaspook/RIOT/tree/PIC32MZEF/examples/rn4020_riot/test.X

No time was wasted. The original C application programming time was well spent when porting the code to a machine with different and better hardware features.


Device driver example:
C:
typedef struct PIC32_SPI_tag {
volatile uint32_t *regs;
uint8_t *in;
size_t len;
volatile int32_t complete;
} PIC32_SPI_T;

typedef struct PIC32_DMA_tag {
volatile uint32_t *regs;
} PIC32_DMA_T;

static PIC32_SPI_T pic_spi[SPI_NUMOF + 1];
static mutex_t locks[SPI_NUMOF + 1];
static PIC32_DMA_T pic_dma[DMA_NUMOF];

#define DCHxCON(U) ((U).regs[0x00 / 4])
#define DCHxCONCLR(U) ((U).regs[0x04 / 4])
#define DCHxCONSET(U) ((U).regs[0x08 / 4])
#define DCHxECON(U) ((U).regs[0x10 / 4])
#define DCHxECONCLR(U) ((U).regs[0x14 / 4])
#define DCHxECONSET(U) ((U).regs[0x18 / 4])
#define DCHxINT(U) ((U).regs[0x20 / 4])
#define DCHxINTCLR(U) ((U).regs[0x24 / 4])
#define DCHxINTSET(U) ((U).regs[0x28 / 4])
#define DCHxSSA(U) ((U).regs[0x30 / 4])
#define DCHxDSA(U) ((U).regs[0x40 / 4])
#define DCHxSSIZ(U) ((U).regs[0x50 / 4])
#define DCHxDSIZ(U) ((U).regs[0x60 / 4])
#define DCHxSPTR(U) ((U).regs[0x70 / 4])
#define DCHxDPTR(U) ((U).regs[0x80 / 4])
#define DCHxCSIZ(U) ((U).regs[0x90 / 4])
#define DCHxCPTR(U) ((U).regs[0xA0 / 4])
#define DCHxDAT(U) ((U).regs[0xB0 / 4])
#define DMA_REGS_SPACING (&DCH1CON - &DCH0CON)

static void Init_Dma_Chan(uint8_t chan, uint32_t irq_num, volatile unsigned int * SourceDma, volatile unsigned int * DestDma)
{
assert(chan < DMA_NUMOF);

pic_dma[chan].regs = (volatile uint32_t *)(&DCH0CON + (chan * DMA_REGS_SPACING));

IEC4CLR = _IEC4_DMA0IE_MASK << chan; /* Disable the DMA interrupt. */
IFS4CLR = _IFS4_DMA0IF_MASK << chan; /* Clear the DMA interrupt flag. */
DCRCCON = 0;
DMACONSET = _DMACON_ON_MASK; /* Enable the DMA module. */
DCHxCON(pic_dma[chan]) = 0;
DCHxECON(pic_dma[chan]) = 0;
DCHxINT(pic_dma[chan]) = 0;
DCHxSSA(pic_dma[chan]) = KVA_TO_PA(SourceDma); /* Source start address. */
DCHxDSA(pic_dma[chan]) = KVA_TO_PA(DestDma); /* Destination start address. */
DCHxSSIZ(pic_dma[chan]) = 1; /* Source bytes. */
DCHxDSIZ(pic_dma[chan]) = 1; /* Destination bytes. */
DCHxCSIZ(pic_dma[chan]) = 1; /* Bytes to transfer per event. */
DCHxECON(pic_dma[chan]) = irq_num << _DCH0ECON_CHSIRQ_POSITION; /* cell trigger interrupt */
DCHxECONSET(pic_dma[chan]) = _DCH0ECON_SIRQEN_MASK; /* Start cell transfer if an interrupt matching CHSIRQ occurs */
DCHxINTSET(pic_dma[chan]) = _DCH0INT_CHBCIE_MASK; /* enable Channel block transfer complete interrupt. */

/*
* set vector priority and receiver DMA trigger enables for the board hardware configuration
*/
switch (chan) {
case 0:
IPC33bits.DMA0IP = SPIxPRI_SW0; /* DMA interrupt priority. */
IPC33bits.DMA0IS = SPIXSUBPRI_SW0; /* DMA sub-priority. */
IEC4SET = _IEC4_DMA0IE_MASK; /* DMA interrupt enable for rx dma */
break;
case 1:
IPC33bits.DMA1IP = SPIxPRI_SW0;
IPC33bits.DMA1IS = SPIXSUBPRI_SW0;
break;
case 2:
IPC34bits.DMA2IP = SPIxPRI_SW0;
IPC34bits.DMA2IS = SPIXSUBPRI_SW0;
IEC4SET = _IEC4_DMA2IE_MASK;
break;
case 3:
IPC34bits.DMA3IP = SPIxPRI_SW0;
IPC34bits.DMA3IS = SPIXSUBPRI_SW0;
break;
default:
break;
}
}

static void Trigger_Bus_DMA_Tx2(size_t len, uint32_t physSourceDma)
{
DCH3SSA = physSourceDma;
DCH3SSIZbits.CHSSIZ = len;
DCH3CONbits.CHEN = 1;
}

static void Trigger_Bus_DMA_Rx2(size_t len, uint32_t physDestDma)
{
IEC4CLR = _IEC4_SPI2RXIE_MASK; /* disable SPI2RX interrupt */
SPI2CONbits.SRXISEL = 1; /* not empty */
IFS4CLR = _IFS4_SPI2RXIF_MASK; /* clear SPI2RX flag */
DCH2DSA = physDestDma;
DCH2DSIZbits.CHDSIZ = len;
DCH2CONbits.CHEN = 1; /* Channel enable. */
}

static inline void _spi_transfer_bytes_async(spi_t bus, spi_cs_t cs, bool cont,
const void *out, void *in, size_t len)
{
const uint8_t *out_buffer = (const uint8_t *) out;
uint8_t dma_able = 8; /* default to NO DMA to trigger default method */

(void) cs;
(void) cont;

/* set input buffer params for the non-dma isr mode */
pic_spi[bus].in = in;
pic_spi[bus].len = len;
pic_spi[bus].complete = false;

/* check of we have both buffers */
if (out && in)
dma_able = 0;

/* Translate a kernel (KSEG) virtual address to a physical address. */
switch (bus + dma_able) {
case 1:
Trigger_Bus_DMA_Rx1(len, KVA_TO_PA(in));
Trigger_Bus_DMA_Tx1(len, KVA_TO_PA(out_buffer));
break;
case 2:
Trigger_Bus_DMA_Rx2(len, KVA_TO_PA(in));
Trigger_Bus_DMA_Tx2(len, KVA_TO_PA(out_buffer));
break;
default: /* non-dma mode */
while (len--) {
if (out_buffer) {
SPIxBUF(pic_spi[bus]) = *out_buffer++;
/* Wait until TX FIFO is empty */
while ((SPIxSTAT(pic_spi[bus]) & _SPI1STAT_SPITBF_MASK)) {
}
} else {
SPIxBUF(pic_spi[bus]) = 0;
/* Wait until TX FIFO is empty */
while ((SPIxSTAT(pic_spi[bus]) & _SPI1STAT_SPITBF_MASK)) {
}
}
}
}
}

Driver CPU processing time for two SPI 18 byte data-stream channels send trace #1 and receive #2 with a 10MHz SCK.


Receive ISR usage from DMA interrupt.
 
Last edited:

Thread Starter

joeyd999

Joined Jun 6, 2011
6,305
Like you said before, we code in different worlds, with different objectives
Yes. And let's be clear: I've never argued that C doesn't have its place. The project you described is exemplary.

My focus is on small MCUs, mostly PIC18 (and prior PIC16). The simple fact that these parts lack a hardware stack for passing arguments and allocating memory for local variables makes C a kludge. This is a major part of the excess overhead required for C code in these devices. Even using the PIC18 extended instruction set (so as to more efficiently support a software stack) is a compromise as it consumes hardware resources that are useful to me (i.e. FSR2).

Face it: the 8 bit PIC architecture was never designed to support a high-level language. OTOH, PICs were designed (originally) to be fast, low power, and cheap. When they first launched in the early 90's, there was nothing like them.

I still use them because they are fast (for a given clock speed) and low power (again, for a given clock speed). They are a bit pricey compared to some of the modern alternatives, though. I also still use them because of the huge library of code I've developed over the past (almost) 30! years.

But, if I were to use C, three things happen:

1) The code gets larger. This dictates a larger RAM/ROM footprint and therefore increased cost.
2) The code gets slower. This dictates a faster clock and therefore higher power.
3) My libraries get dumped. Simply. Not. Worth. It.

As I said many times before, I am quite proficient in C and C++. I understand the language(s), how it's compiled, and what the resulting object code looks like. But it's simply not a fit for what I do.

At the end of the day, I really don't care what language anyone uses for their own projects. In fact, bad code is generally related to the skill of the programmer, not the language they use. And, regardless of language, there are far more bad programmers than there are good ones -- they (the bad ones) shouldn't be allowed to have an opinion.

What I don't like is that there are those that insist there is no place for .asm in embedded processing, when I can demonstrate conclusively that there are cases where there is no place for C.

 
Last edited:
Top