• AVR Freaks

Helpful ReplyHot!mixed C and assembly port manipulation

Page: 123 > Showing page 1 of 3
Author
bitdoctor
Starting Member
  • Total Posts : 24
  • Reward points : 0
  • Joined: 2019/10/11 06:10:09
  • Location: 0
  • Status: offline
2019/10/14 11:36:47 (permalink)
0

mixed C and assembly port manipulation

I am interfacing a PIC16F1512 to a string of neopixels. I have MPLAB 5.20 and I'm using the XC8 compiler. To make this work, a requirement is to produce 250ns and 750ns pulses on an output pin - I've succeeded in doing this by writing to LATA. The first successful test program compiled and was able to toggle the output pin in one clock cycle with the CPU running at 16MHz. I have a scope so I can verify the output - the following sequence did in fact produce the 250ns pulse:
 LATA = 0x01;
 LATA = 0x00;

Subsequent versions that attempted to use this approach failed, as the compiler didn't produce efficient code (for this purpose). I researched inline assembly in order to fix this problem. I did consult the XC8 manual section on mixed language programming and tried some things I saw there but got lots of syntax errors and warnings (you will ask what they were, but please allow me to explain my solution before we get into that). Desperate to get something working, I actually copied the code directly from the assembly listing of the compiled working version, and embedded it in a new version. Thus I ended up with this working function:
void sendByte(unsigned char byte) {

    // Neopixel wants bit in highest-to-lowest order
    // so send highest bit (bit #7 in an 8-bit byte since they start at 0)
    for (unsigned char bitmask = 0x80; bitmask != 0; bitmask = bitmask >> 1) {

        if (bitmask & byte) {
            // send logic one = 750ns pulse
            asm("MOVLW 1");
            asm("MOVLB 2");
            asm("MOVWF 12");
            asm("NOP");
            asm("NOP");
            asm("CLRF 12");

        } else {
            // send logic zero = 250ns pulse
            asm("MOVLW 1");
            asm("MOVLB 2");
            asm("MOVWF 12");
            asm("CLRF 12");
        }
    }
}

I have the PIC datasheet and do understand bank registers and so on. I am new to PIC but not to computers so I do have a good idea what those instructions mean and how they accomplish the task. My question is, how can I replace the hard coded constants (like "12") with proper abstract references to the registers in question? For example, I tried BANKSEL and got warnings, I got warnings about LATA exceeding the address size so I tried to use BANKMASK but that generated a syntax error. Is it impossible to use those macros with this form of expression of assembly language? What is the proper way to go about this?
 
Oh, and... anything I do has to be FAST. If you take too much time between bit transmissions it counts as a data latch command and then you are not able to communicate all the bits properly.
post edited by bitdoctor - 2019/10/14 11:41:47
#1
1and0
Access is Denied
  • Total Posts : 9900
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 11:53:56 (permalink)
0
Try this:

        if (bitmask & byte) {
            // send logic one = 750ns pulse
            LATAbits.LATA0 = 1;
            NOP();
            NOP();
            LATAbits.LATA0 = 0;
        } else {
            // send logic zero = 250ns pulse
            LATAbits.LATA0 = 1;
            LATAbits.LATA0 = 0;
        }

#2
Gort2015
Klaatu Barada Nikto
  • Total Posts : 3315
  • Reward points : 0
  • Joined: 2015/04/30 10:49:57
  • Location: 0
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 12:24:02 (permalink)
0
Add volatile to asm statements, this ensures that each instruction follows the next.
 
Best to use 1and0's example, the asm you used will not get you a speed gain plus the mix of, "for, if and else" defeats the object.
 

MPLab X playing up, bug in your code? Nevermind, Star Trek:Discovery will be with us soon.
https://www.youtube.com/watch?v=Iu1qa8N2ID0
+ ST:Continues, "What Ships are Made for", Q's back.
#3
ric
Super Member
  • Total Posts : 24282
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
Re: mixed C and assembly port manipulation 2019/10/14 12:25:42 (permalink)
+1 (1)
bitdoctor
I have the PIC datasheet and do understand bank registers and so on. I am new to PIC but not to computers so I do have a good idea what those instructions mean and how they accomplish the task. My question is, how can I replace the hard coded constants (like "12") with proper abstract references to the registers in question? For example, I tried BANKSEL and got warnings, I got warnings about LATA exceeding the address size so I tried to use BANKMASK but that generated a syntax error. Is it impossible to use those macros with this form of expression of assembly language? What is the proper way to go about this?

As ever, we need to see the exact code you tried, and the exact error/warning you received.
Context is everything.
 
Do understand that going from a constant value to a variable value requires different instructions on a PIC, so you do have to understand the assembly instructions.
 
There are dozens of threads on this board talking about driving neopixels, and which bits of the datasheet are critical and which bits can be stretched. Try 1and0's solution first.
 
Personally, I've done the same thing using an SPI peripheral to send the bits for me, quite successfully. (It took one SPI byte transmission to transfer each neopixel bit)

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#4
NKurzman
A Guy on the Net
  • Total Posts : 17942
  • Reward points : 0
  • Joined: 2008/01/16 19:33:48
  • Location: 0
  • Status: online
Re: mixed C and assembly port manipulation 2019/10/14 12:43:23 (permalink) ☄ Helpfulby bitdoctor 2019/10/14 15:04:30
0
XC8 Free or Pro Version?  It is Possible that the Pro my work.
I was able to get it to work @ 32 Mhz with the Free Version.
Also note the Data sheet is a little Of  and the WS2812 and WS2812B are a little different.
This is a Good Reference:
https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/
#5
bitdoctor
Starting Member
  • Total Posts : 24
  • Reward points : 0
  • Joined: 2019/10/11 06:10:09
  • Location: 0
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 13:15:45 (permalink)
0
1and0
Try this:

        if (bitmask & byte) {
            // send logic one = 750ns pulse
            LATAbits.LATA0 = 1;
            NOP();
            NOP();
            LATAbits.LATA0 = 0;
        } else {
            // send logic zero = 250ns pulse
            LATAbits.LATA0 = 1;
            LATAbits.LATA0 = 0;
        }



Okay, I compiled the same program, substituting the code above. It illustrates the problem quite well - if you look at the listing, you can see that the compiler has disturbed the timing with optimization - it observes that both branches of the if-else have a common end, and merges them with a GOTO. This is typical of many of the attempts I made. I checked the optimization level and found that it is set to "0", but this obviously does not guarantee a literal interpretation of the source.
 
LATAbits.LATA0 = 1;
   826 07B4 0022 movlb 2 ; select bank2
   827 07B5 140C bsf 12,0 ;volatile
   828
   829 ;main.c: 95: __nop();
   830 07B6 0000 nop
   831
   832 ;main.c: 96: __nop();
   833 07B7 0000 nop
   834
   835 ;main.c: 97: LATAbits.LATA0 = 0;
   836 07B8 0022 movlb 2 ; select bank2
   837 07B9 2FBC goto L1
   838 07BA l25:
   839 ;main.c: 109: } else {
   840
   841
   842 ;main.c: 110: LATAbits.LATA0 = 1;
   843 07BA 0022 movlb 2 ; select bank2
   844 07BB 140C bsf 12,0 ;volatile
   845 07BC L1:
   846
   847 ;main.c: 111: LATAbits.LATA0 = 0;
   848 07BC 100C bcf 12,0 ;volatile

 
I did search for posts on this general subject. It didn't yield much that I can use in my present situation. I'm also aware that there are better ways than bit banging, such as SPI. But for my application, I don't need it because it is only a small subsystem of a larger task, which only has to do this one job and there is lots of CPU time to burn. I can actually waste a few microseconds between pulses and not worry about it, because the number of LEDs is small (6 maximum). The pulse leading to falling edge timing itself is what is really critical. I'm using this as a reference:
https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
 
Hence my question became, "how do I do the assembly right?", not "how can I make it work using only C?". I didn't make that choice, it was kind of forced on my by the compiler.
#6
1and0
Access is Denied
  • Total Posts : 9900
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 14:30:00 (permalink)
0
So make the end "uncommon", like adding a NOP().
#7
bitdoctor
Starting Member
  • Total Posts : 24
  • Reward points : 0
  • Joined: 2019/10/11 06:10:09
  • Location: 0
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 14:40:49 (permalink)
0
NKurzman
XC8 Free or Pro Version?  It is Possible that the Pro my work.
I was able to get it to work @ 32 Mhz with the Free Version.
Also note the Data sheet is a little Of  and the WS2812 and WS2812B are a little different.
This is a Good Reference:
https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/


I am using the XC8 Free version. If you are running at 32 MHz then you have a different chip - I think 20 MHz is the maximum for the '1512 and that would require an external oscillator. Yes, it's a good point about the Neo differences. In fact, I am expecting to support not only the WS2812B but also the SK6812. I have it running perfectly with the SK6812 now... AFAIK it is difficult to obtain real data sheets on some of the parts that are coming out of China, so I'm depending on failure limit testing to determine compliance with the baseline spec. Thanks for the link.
#8
ric
Super Member
  • Total Posts : 24282
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
Re: mixed C and assembly port manipulation 2019/10/14 14:49:04 (permalink) ☄ Helpfulby bitdoctor 2019/10/14 15:03:49
0
Regarding your original question, inline assemble in XC8 v2.xx took a real backward step in an attempt to achieve better compliance with C standards. Here's how to make it compile using real register names.
void sendByte(unsigned char byte)
{
    // Neopixel wants bit in highest-to-lowest order
    // so send highest bit (bit #7 in an 8-bit byte since they start at 0)
    for (unsigned char bitmask = 0x80; bitmask != 0; bitmask = bitmask >> 1) {

        if (bitmask & byte) {
            // send logic one = 750ns pulse
            asm("MOVLW 1");
            asm("BANKSEL LATA");
            asm("MOVWF " ___mkstr(BANKMASK(LATA)));
            asm("NOP");
            asm("NOP");
            asm("CLRF " ___mkstr(BANKMASK(LATA)));

        } else {
            // send logic zero = 250ns pulse
            asm("MOVLW 1");
            asm("BANKSEL LATA");
            asm("MOVWF " ___mkstr(BANKMASK(LATA)));
            asm("CLRF " ___mkstr(BANKMASK(LATA)));
        }
    }
}

The messy details are explained on page 160 of the XC8 User Guide.
Do you really have to write the entire LATA, or just bit 0?
If it's just the one bit, you could use:
void sendByte2(unsigned char byte)
{
    // Neopixel wants bit in highest-to-lowest order
    // so send highest bit (bit #7 in an 8-bit byte since they start at 0)
    for (unsigned char bitmask = 0x80; bitmask != 0; bitmask = bitmask >> 1) {

        if (bitmask & byte) {
            // send logic one = 750ns pulse
            asm("BANKSEL LATA");
            asm("BSF " ___mkstr(BANKMASK(LATA))",0");
            asm("NOP");
            asm("NOP");
            asm("BCF " ___mkstr(BANKMASK(LATA))",0");

        } else {
            // send logic zero = 250ns pulse
            asm("BANKSEL LATA");
            asm("BSF " ___mkstr(BANKMASK(LATA))",0");
            asm("BCF " ___mkstr(BANKMASK(LATA))",0");
        }
    }
}


or possibly make it a bit more readable
void sendByte3(unsigned char byte)
{
    // Neopixel wants bit in highest-to-lowest order
    // so send highest bit (bit #7 in an 8-bit byte since they start at 0)
    for (unsigned char bitmask = 0x80; bitmask != 0; bitmask = bitmask >> 1) {

        if (bitmask & byte) {
            // send logic one = 750ns pulse
            asm("BANKSEL LATA");
            asm("BSF LATA & 0x7f,0");
            asm("NOP");
            asm("NOP");
            asm("BCF LATA & 0x7f,0");

        } else {
            // send logic zero = 250ns pulse
            asm("BANKSEL LATA");
            asm("BSF LATA & 0x7f,0");
            asm("BCF LATA & 0x7f,0");
        }
    }
}

post edited by ric - 2019/10/14 14:51:07

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#9
bitdoctor
Starting Member
  • Total Posts : 24
  • Reward points : 0
  • Joined: 2019/10/11 06:10:09
  • Location: 0
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 14:51:03 (permalink)
0
By the way, I found out empirically that you can not use the "relaxed timing" of the data latch delay, as promoted by both the author of the link I provided and the one @nkurzman provided - 6us and 9us respectively, with the SK6812's. So if you want to have a universal driver, you absolutely have to respect the 50us spec.
#10
bitdoctor
Starting Member
  • Total Posts : 24
  • Reward points : 0
  • Joined: 2019/10/11 06:10:09
  • Location: 0
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 14:56:47 (permalink)
0
ric
Do you really have to write the entire LATA, or just bit 0?

I only have to write one bit. Do I understand correctly now that the BSF/BCF instructions will allow me to do that in a single cycle? My thoughts were that I could use the remaining bits for input if I couldn't assign them to outputs as would be advantageous. If I can also use some port bits as output that would be sweet.
#11
ric
Super Member
  • Total Posts : 24282
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
Re: mixed C and assembly port manipulation 2019/10/14 15:00:20 (permalink)
0
bitdoctor
I am using the XC8 Free version. If you are running at 32 MHz then you have a different chip - I think 20 MHz is the maximum for the '1512 and that would require an external oscillator.



Interesting. Most of the "Enhanced mid-range" PICs support a 32MHz internal oscillator, but as you say, the whole PIC16F15xx range seem to only go up to 16MHz internally.
Was there any particular reason to use a PIC16F1512?
 

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#12
ric
Super Member
  • Total Posts : 24282
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
Re: mixed C and assembly port manipulation 2019/10/14 15:01:13 (permalink)
0
bitdoctor
...
I only have to write one bit. Do I understand correctly now that the BSF/BCF instructions will allow me to do that in a single cycle?

Yes. So long as the correct bank is already selected.
 
 

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#13
bitdoctor
Starting Member
  • Total Posts : 24
  • Reward points : 0
  • Joined: 2019/10/11 06:10:09
  • Location: 0
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 15:18:07 (permalink)
+1 (1)
ric
Was there any particular reason to use a PIC16F1512?

Yes, it's the chip on a board I'm trying to write third party software for. I think the manufacturer of that board might be interested that it can support Neopixels so it is important to at least demo it on that chip.
 
Also - this is my first "jailbreak" from the Arduino IDE and ecosystem. I'm also preparing to dig into STM32Cube later to support STM micros. If I just wanted LEDs to blink, I would be content with the Adafruit library that I used with the AVR parts. So this project is also a learning exercise - working with a feature limited processor isn't such a bad thing for that... :)
 
I did look for a pin compatible IC but the only one that came close that I could find, was the PIC16F886 which has even less horsepower...
post edited by bitdoctor - 2019/10/14 15:21:26
#14
ric
Super Member
  • Total Posts : 24282
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
Re: mixed C and assembly port manipulation 2019/10/14 15:40:09 (permalink)
+1 (1)
bitdoctor
I did look for a pin compatible IC but the only one that came close that I could find, was the PIC16F886 which has even less horsepower...

What features are you looking for?
If you look on MAPS at https://www.microchip.com/maps/microcontroller.aspx
there are lots of 28 pin PIC16F devices.
The PIC16F1713 is a bit of a step up, only a few cents more, twice the memory.
The PIC16F18856 is a big step up, and only fractionally more expensive than the PIC16F1713.
 

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#15
1and0
Access is Denied
  • Total Posts : 9900
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 16:20:38 (permalink)
+2 (2)
If you want to go all the way symbolically ;)
  asm("BCF " ___mkstr(BANKMASK(LATA)) "," ___mkstr(_LATA_LATA0_POSN));

 
But this way would be better:
        if (bitmask & byte) {
            // send logic one = 750ns pulse
            LATAbits.LATA0 = 1;
            LATAbits.LATA0 = 1;
            LATAbits.LATA0 = 1;
            LATAbits.LATA0 = 0;
        } else {
            // send logic zero = 250ns pulse
            LATAbits.LATA0 = 1;
            LATAbits.LATA0 = 0;
            NOP();
        }
#16
ric
Super Member
  • Total Posts : 24282
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
Re: mixed C and assembly port manipulation 2019/10/14 16:29:32 (permalink)
0
I wonder if this construct would work.
        if (bitmask & byte) {
            // send logic one = 750ns pulse
            LATAbits.LATA0 = 1;
            LATAbits.LATA0 = 1;
    }
        // send logic zero = 250ns pulse
        LATAbits.LATA0 = 1
        LATAbits.LATA0 = 0;


I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#17
1and0
Access is Denied
  • Total Posts : 9900
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 16:43:06 (permalink)
0
ric
I wonder if this construct would work.
        if (bitmask & byte) {
            // send logic one = 750ns pulse
            LATAbits.LATA0 = 1;
            LATAbits.LATA0 = 1;
 
    }
        // send logic zero = 250ns pulse
        LATAbits.LATA0 = 1
        LATAbits.LATA0 = 0;


Nah, XC8 Free Mode inserts a MOVLB before the last LATA0=1.
 
If you're brave enough to risk it, this works:
        if (bitmask & byte) {
            // send logic one = 750ns pulse
            LATAbits.LATA0 = 1; 
    }
        // send logic zero = 250ns pulse
        LATAbits.LATA0 = 1
        LATAbits.LATA0 = 0;

#18
ric
Super Member
  • Total Posts : 24282
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
Re: mixed C and assembly port manipulation 2019/10/14 17:05:56 (permalink)
0
or maybe:
    for (unsigned char bitmask = 0x80; bitmask != 0; bitmask = bitmask >> 1) {
        WREG = (bitmask & byte);
        asm("BANKSEL LATA");
        asm("ANDLW 0xff");    //set zero flag if zero
        asm("BTFSS STATUS,2");    //skip if was zero
        asm("BSF LATA & 0x7f,0");  //only executed if W was non-zero
        asm("NOP");
        asm("BSF LATA & 0x7f,0");
        asm("BCF LATA & 0x7f,0");
    }

If you're brave, you can leave out the "ANDLW 0xff" as the Zero flag will already be correctly set by what the compiler has just done.
 

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#19
1and0
Access is Denied
  • Total Posts : 9900
  • Reward points : 0
  • Joined: 2007/05/06 12:03:20
  • Location: Harry's Gray Matter
  • Status: offline
Re: mixed C and assembly port manipulation 2019/10/14 17:25:15 (permalink)
0
Nice!  I'll see you and raise you:
void sendByte(uint8_t byte)
{
    uint8_t i = 8;
    do {
        asm("banksel sendByte@byte");
        asm("rlf     sendByte@byte");  // shift bit 7 to carry bit
        asm("banksel LATA         ");
        asm("btfsc   STATUS,0     ");  // if (carry bit == 1)
        asm("bsf     LATA & 0x7F,0");  //   750ns pulse
        asm("nop                  ");  // else
        asm("bsf     LATA & 0x7F,0");  //   250ns pulse
        asm("bcf     LATA & 0x7F,0");
    } while (--i);
}

 
Edit: Comment is added as requested.
post edited by 1and0 - 2019/10/14 19:34:09
#20
Page: 123 > Showing page 1 of 3
Jump to:
© 2019 APG vNext Commercial Version 4.5