Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

How fast can you toggle an I/O pin?

Status
Not open for further replies.

Nigel Goodwin

Super Moderator
Most Helpful Member
I came across this video on YouTube today, it popped up as a suggestion as it often does, and I was interested to see his results.


In particular because I'm currently in the process of converting an Arduino library, and was considering using subroutines to replicate the Arduino pinMode(), digitalWrite() and digitalRead() functions, rather than rewriting the code not to use them. This also gives you the huge advantage that it's trivial to switch to different pins dynamically if required - something you can't do if it's fixed in the source code. My feeling however, was that it would be MUCH slower than direct PIC code - so I was interested to see that the exact same applies to the Arduino as well.

So I wrote the following simple code, using an 18F27K40 using the internal clock at 64MHz, on an existing board, commenting out each pair in turn.

C:
    while(1)
    {
        //LATAbits.LATA6 = 1;   //4MHz
        //LATAbits.LATA6 = 0;
        digitalWrite(10, 1);    //150KHz
        digitalWrite(10, 0);
    }

As commented, the straight PIC code using LAT gave me a 4MHz output, and the digitalWrite() routines only 150KHz, this using the Free version of the XC8 compiler.

The digitalWrite() routine actually came from an example program at http://mplabxpress.microchip.com/mplabcloud/example

Incidentally, if you remove the test for the State value been 0 or 1, it increases speed to 160KHz or so.

C:
void    digitalWrite(byte Pin, byte State)
{
    if ((State != 0)&&(State!=1))
    {
        printf("digitalWrite() invalid state %d\r\n",State);
        return;
    }
    
    switch (Pin) {
        case  2:    // RA0
            LATAbits.LATA0 = State;
            break;
        case  3:    // RA1
            LATAbits.LATA1 = State;
            break;
        case  4:    // RA2
            LATAbits.LATA2 = State;
            break;
        case  5:    // RA3
            LATAbits.LATA3 = State;
            break;
        case  6:    // RA4
            LATAbits.LATA4 = State;
            break;
        case  7:    // RA5
            LATAbits.LATA5 = State;
            break;
        case 10:    // RA6
            LATAbits.LATA6 = State;
            break;
        case  9:    // RA7
            LATAbits.LATA7 = State;
            break;
        case 11:    // RC0
            LATCbits.LATC0 = State;
            break;
        case 12:    // RC1
            LATCbits.LATC1 = State;
            break;
        case 13:    // RC2
            LATCbits.LATC2 = State;
            break;
        case 14:    // RC3
            LATCbits.LATC3 = State;
            break;
        case 15:    // RC4
            LATCbits.LATC4 = State;
            break;
        case 16:    // RC5
            LATCbits.LATC5 = State;
            break;
        case 17:    // RC6
            LATCbits.LATC6 = State;
            break;
        case 18:    // RC7
            LATCbits.LATC7 = State;
            break;
        case 21:    // RB0
            LATBbits.LATB0 = State;
            break;
        case 22:    // RB1
            LATBbits.LATB1 = State;
            break;
        case 23:    // RB2
            LATBbits.LATB2 = State;
            break;
        case 24:    // RB3
            LATBbits.LATB3 = State;
            break;
        case 25:    // RB4
            LATBbits.LATB4 = State;
            break;
        case 26:    // RB5
            LATBbits.LATB5 = State;
            break;
        case  1:    // xpress board MCLR
        case  8:    // xpress board VSS
        case 19:    // xpress board VSS
        case 20:    // xpress board VDD
        case 27:    // xpress board ICSPCLK/RB6
        case 28:    // xpress board ICSPDAT/RB7           
        default:
            printf("digitalWrite() invalid pin %d\r\n",Pin);
            break;
    }
}
 
Can you use a precompiler macro to write constants in to the code, rather than the runtime switch block?
It would be limited to passing constants to the function for the pin then, though..

You could also redefine the PIN constants so you can use groups of bits directly from the pin value to set the port and bit numbers, or at least pick up a bitmask from a table of constants.

Or define all possible port address and pin values bitmasks in a look up table and set them directly.

You can definitely save a few cycles in your existing routine using something like
if ((State & ~1)
{
printf("digitalWrite() invalid state %d\r\n",State);
return;
}
 
I've tested SRAM to LAT DMA on the K42. You can get toggles every 250ns moving alternating bits from a 16 byte uint8_t array in background without no CPU load. It's not super fast but could be useful for background bit-banging.
A snip of the test code.
C:
uint8_t port_data[16]={255,0,255,0,255,0,255,0,255,0,255,0,255,0,255,0};

// some of the setup code, the rest of the setup code is in the MCC generated DMA files.
/*
* channel 2 DMA
*/
void init_port(void)
{
    DMA2CON1bits.DMODE = 0;
    DMA2CON1bits.DSTP = 0;
    DMA2CON1bits.SMODE = 1;
    DMA2CON1bits.SMR = 0;
    DMA2CON1bits.SSTP = 0;
    DMA2DSA = 0x3FBB; // LATB
    DMA2SSA = (uint32_t) port_data;
    DMA2CON0bits.DGO = 0;
}

/*
* uses DMA channel 2 for transfers
*/
void send_port_data_dma(void)
{
    DMA2CON0bits.EN = 0; /* disable DMA to change source count */
    DMA2SSZ = 16;
    DMA2DSZ = 16;
    DMA2CON0bits.EN = 1; /* enable DMA */
    DMA2CON0bits.DMA2SIRQEN = 1; /* start DMA trigger, a 20ms timer trigger for each each 16 byte transfer  */
}

            /*
             * DMA I/O testing
             */
            init_port();
            send_port_data_dma();
 
Last edited:
Of course calling the "digitalWrite" function is slower than direct write to register. The "digitalWrite" function is designed to be thread-safe and there is a lot of overhead. It disables interrupts, saves the execution context and pushes given parameters to stack.. then writes the pin changes to the right register... then.. when leaving the function there is exit code that loads the execution context back from stack so that program can continue from the point where it was before the function call.
 
Last edited:
Code:
Loop
    comf portA,f
    goto Loop

It is still surprisingly slow in that (for a PIC), each ON cycle takes three instructions (GOTO is a two instruction command), each OFF cycle takes 3 instructions (6 total for an on/off cycle) and each instruction takes 4 clock cycles so the minimum toggle frequency for this will be f(osc)/24.
 
Agreed, but I don't see any other way around it, unless you're only interested in average speed:

Code:
Loop
    comf portA,f
    comf portA,f
    comf portA,f
    comf portA,f
    comf portA,f
    comf portA,f
    comf portA,f
    comf portA,f
    goto Loop

Now you get 8 toggles in 10 cycles.
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top