pic 24 code to output an int to a pin - Page 1
Forum section: electronics / general

author: snarkysparky
author_rank: Frequent Contributor
timestamp: March 02, 2017, 02:41:20 am
content: The goal is to create a function that will output a 16 bit integer to a digital pin for debugging with an oscilloscope.For a pic24.Code:void int2scope( unsigned int testval){unsigned int bitpnt = 1<<15;unsigned int seq;unsigned int LATBShadow;unsigned int index = 0;LATBShadow = LATB; // store current stateseq = LATBShadow | ScopeBitMask; // start bitdo{index++;if(bitpnt&testval)seq = LATBShadow | ScopeBitMask;elseseq = LATBShadow & ~ScopeBitMask;bitpnt >>=1;}while(bitpnt);index++;seq = LATBShadow | ScopeBitMask; // stop bitLATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;LATB = seq;}Any suggestions for improvement ??Thanks

author: helius
author_rank: Super Contributor
timestamp: March 02, 2017, 03:01:45 am
content: You only output 12 bits, not 16.It looks like you are trying to set the output bits as fast as possible, so you hard-coded them instead of doing it in a loop; but just reading from that array is likely to take even more time, unless the compiler can overlap it with writes to LATB.Generally this kind of problem requires that you understand what the compiler can and can't do. For instance, if the MCU has instructions that can write a single bit into an IO reg then all the precomputation of shadow values etc is pointless.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: March 02, 2017, 03:49:57 am
content: Well my main goal was to insure that both conditions ( true or not ) took the exact same cycles so that it was easy to decode the data using the cursors on my scope. Yes I did only mean for this to be used for 12 bits. It's to debug A/D converter inputs. But easily enough expandable to the full 16 bits.I coded it this way so that the on and off times would be exactly one cycle. I don't have to worry about testing and branching effects on the cycle time. It's important to be able to read the values off a scope capture.Shouldn't the compiler be able to hard code the addresses assigned to the array and then call them for setting the LATB words.From my perspective the compiler should put the array on the stack and then pop these values off with single cycle instructions and write to LATB. But then I don't know much about this stuff.

author: BrianHG
author_rank: Super Contributor
timestamp: March 02, 2017, 06:11:50 am
content: Now, this can be written a bit better, but, moving bits out in 1 clock cycle in 'C' I would think is impossible since I can't think of a way to do so in assembly. Maybe 2 cycles, prestoring 12 varibles, using a movf, then a movwf line by line.Now, I would sacrifice one additional cycle just so that I only affect 1 pin on port B and don't have to generate the 12 or 16 bytes ahead of time. To do this, I would only use #asm, btfsc and bcf/bsf to your IO pin in your C code, you'll get 3 instructions per bit coming out of the pin and a reference low output gap between consecutive high bits so you can see the spacing on you scope. Here is how I would go about this: Now, when I say comes out fast, I mean with a 40Mhz pic, this will come out at 3.3333 megabits/sec + my fat setup pulse. You need not worry about the C compiler altering the timing ever & since I only bit-check a 2 byte integer low & high memory points directly. It's been awhile since I've added assembly in line into C routines with MPLab, so I hope I got everything OK.(Note, you need to have set testval_h & testval_l at the beginning of you C code, they can be ram or directly pointing to your ADC registers)Code:#asmbsf PORTB,0 ; I'm using B0 in this testbsf PORTB,1 ; Optional trigger for scope on a second channelbcf PORTB,1bcf PORTB,0 ; Initial 4 cpu cycle high for scope to trigger, using the scope to only trigger on x ns high.btfsc testval_h,3bsf PORTB,0bcf PORTB,0btfsc testval_h,2bsf PORTB,0bcf PORTB,0btfsc testval_h,1bsf PORTB,0bcf PORTB,0btfsc testval_h,0bsf PORTB,0bcf PORTB,0btfsc testval_l,7bsf PORTB,0bcf PORTB,0btfsc testval_l,6bsf PORTB,0bcf PORTB,0btfsc testval_l,5bsf PORTB,0bcf PORTB,0btfsc testval_l,4bsf PORTB,0bcf PORTB,0btfsc testval_l,3bsf PORTB,0bcf PORTB,0btfsc testval_l,2bsf PORTB,0bcf PORTB,0btfsc testval_l,1bsf PORTB,0bcf PORTB,0btfsc testval_l,0bsf PORTB,0bcf PORTB,0#endasmUsing the equivalent movf seq(x),0 , then movwf LATB, will give you a 2 CPU cycle per bit, but, all of port B will be affected and you need to prep and store all of the seq(x) which would take a mound of many more CPU cycles ahead of time. You can also add a NOP inbetween my previous bsf & bcf to make everything an even 4 cpu cycles & fatter high bits. You can also change those NOPs to an alternating bsf PORTB,1 and bcf PORTB,1 to generate a high and low latch clock for a second channel on you scope.The nature of my code which makes the fat header & a blank 0 between bits makes the output easy to decode visually on a single trace of your oscilloscope display.

author: BrianHG
author_rank: Super Contributor
timestamp: March 02, 2017, 06:22:21 am
content: Another example with 4 cpu cycles per byte + a true data latch clock on PORTB,1.Code:#asmmovlw b'11111100'btfsc testval_h,3bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_h,2bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_h,1bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_h,0bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_l,7bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_l,6bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_l,5bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_l,4bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_l,3bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_l,2bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_l,1bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1btfsc testval_l,0bsf PORTB,0bsf PORTB,1 ; Trigger and clock pulse on PORTB,1andwf PORTB,1 ; Clear PORTB bits 0&1#endasm

author: JPortici
author_rank: Super Contributor
timestamp: March 02, 2017, 06:36:38 am
content: this would be the perfect use case for UART, which you are not using because..?otherwise i like the code on reply #3. (is the asm correct? wouldn't it be bset, bclr and btsc?)

author: BrianHG
author_rank: Super Contributor
timestamp: March 02, 2017, 09:15:02 am
content: Woops, 'PIC24' has a 16 bit core, I was using the 8 bit core opcode. Yes, you need to use the enhanced 16bit opcodes, now, you would only need to point to a 16 bit int memory space instead of the high and low byte bit tests. In fact, if you are using a 16 bit port, all you need to do is move your 16 bit word to that port, then in assembly, do 12 rotates to the right right on the latch or port, then you will get at pin bit 0, 1 bit per cpu clock. This would be the only way to get 10 megabits/sec from a 40Mhz cpu. You can only do 8 bits at a time like this with the 8 bit PICs. There would be a 2 cycle hump when switching from the high to the low byte in that case. Note that all the IO pins would sequence like mad doing it like this.Since I am not aware of the behavior of the 'latchB' register, ''maybe'', if you port b on the chip has only a few bits, but the latch register is complete 16 bits, loading your 16 bit data into the latch and rotating the latch 12 time ''might'' still actually work. this is something you would have to test. You might also consider changing the unused bits of port B to inputs since the latch register shouldn't be affected by the input data.In fact, before loading the latch for the port, I would rotate the data to the left by 1, fill the end with 0 and bit 14 with 0, then feed that int to the port and do 14 right rotations on the latch. This way your output will start with a low and end with a low.

author: JPortici
author_rank: Super Contributor
timestamp: March 02, 2017, 12:12:04 pm
content: Quote from: BrianHG on March 02, 2017, 09:15:02 amfrom what i remember the LAT register is not bit complete. each unimplemented bit is actually zero (i did a simillar thing a long time ago)the fastest way to shift out a word is your code with the appropriate opcodesif one wanted a fixed bit rate it should look likeCode:BTSC WREG,#0BSET LATB,#0BTSS WREG,#0BCLR LATB,#0for each bitsuggestion: first load the word into one accumulator. some instructions are not available in GP ram and certain families of PIC24 need two clock cycles each time they have to access GP RAM.. those also need more than 2 cycles to excecute a branch... check the programmer's manual!

author: snarkysparky
author_rank: Frequent Contributor
timestamp: March 02, 2017, 12:56:31 pm
content: btfsc testval_h,0bsf PORTB,0bcf PORTB,0doesn't this always clear B0, if the branch is taken or not.I would use the UART but I didn't want to have to juggle combining two bytes in my head while debugging.

author: cv007
author_rank: Frequent Contributor
timestamp: March 02, 2017, 04:53:58 pm
content: This is untested and could be wrong, but how about something like so-void int2scope( uint16_t val){for( int8_t i = 11; i>=0; i--){ //only 12 bits (11-0)uint8_t v = val>>i; //get desired bit into bit0 of v_LATB1 = 1; //1 1 - 'start' bit_LATB1 = v; //1 0 - data_LATB1 = v; //1 0_LATB1 = 0; //0 0 - always leave pin as 0if(!(i & 3)){ NOP; NOP; NOP; NOP; } //create gap between nibbles}}A 1000... is a 0 (short length high), a 1110... is a 1 (long length high) , with a 'space' between each nibble to make it more readable. Should be able to mentally convert the nibbles to hex somewhat easily. I would imagine if using the original idea it would get difficult when there are lots of consecutive zeros or ones. With something like this, the timing really doesn't matter as you are just looking for short/long 1's.Just a thought.

author: BrianHG
author_rank: Super Contributor
timestamp: March 03, 2017, 01:11:55 am
content: Quote from: snarkysparky on March 02, 2017, 12:56:31 pmYes it does. Remember, according to the initial post, you want to visually see and capture the data on 1 channel of the scope as fast as possible, not create a valid com signal. The forced clearing of port b,0 makes a nice set of evenly spaced high pulses for the 1s, which you can visually evaluate the 12 bit chain of data.The uart doesn't support 12 or 16 consecutive bits without start and stop bits.The newer post above with the bit-test for the setting and clearing, where the data is set then might be cleared with a skew makes it visually more difficult to decode the bits.I've used this with a simple scopes & then thin high pulses make visualizing the individual bit easy to read by eye as I can place the pulses right on the horizontal grid spacing even if your CPU clock doesn't fit on a particular grid spacing. If you want fatter high pulses, just add a NOP between the bsf & bcf. You can also change this to a bit width modulation like this:........bsf PORTB,0btfss integer,bit#bcf PORTB,0nopbcf PORTB,0nop........Ok, this one is 6 cpu clocks per bit, but, always get an initial high per bit. But now, the width of that high is fat for a 1, thin for a 0. This is also useful for the 1 bit bus communications of the 1 wire full RGB color LED com port with slower CPU speeds.

author: JPortici
author_rank: Super Contributor
timestamp: March 03, 2017, 07:05:12 am
content: Quote from: snarkysparky on March 02, 2017, 12:56:31 pmwith realterm and probably other terminal emulators you can decide how to interpret the acquired data. there is also an uint16_t options which will combine the two bytes for you

author: snarkysparky
author_rank: Frequent Contributor
timestamp: March 03, 2017, 01:32:39 pm
content: bsf PORTB,0btfss integer,bit#bcf PORTB,0nopbcf PORTB,0nopI like it.. I was thinking of changing to fat and thin pulses already to make it more readable. I think I will try to trick my C compiler ( CCS ) into emitting this code. It has intrinsic bit_test() and bit_set()thanks

author: @rt
author_rank: Super Contributor
timestamp: March 03, 2017, 01:48:19 pm
content: If consistency was the issue rather than speed, was there a reason not to bang it out?Code:int outword = something;count = 0;while (count < 12) {LATB.0 = outword;outword = outword >> 1;count++;} // countor if you wanted the bitmaskCode:int outword = something;count = 0;while (count < 12) {LATB.0 = outword & 0x0001;outword = outword >> 1;count++;} // count

author: snarkysparky
author_rank: Frequent Contributor
timestamp: March 03, 2017, 02:03:48 pm
content: LATB.0 = outword;Just asking.. Isn't this assigning an integer to a bit value, how does that work.Code:void int2scope2(unsigned int testval){bit_set(LATB,ScopeBit);if(!bit_test(testval,15))bit_clear(LATB,ScopeBit);bit_clear(LATB,ScopeBit);delay_cycles(1);....the compiler emitted this.................... void int2scope2(unsigned int testval).................... {.................... bit_set(LATB,ScopeBit);0218: BSET.B 2CD.0.................... if(!bit_test(testval,15))bit_clear(LATB,ScopeBit);021A: BTSC.B 819.7021C: BRA 220021E: BCLR.B 2CD.0.................... bit_clear(LATB,ScopeBit);0220: BCLR.B 2CD.0.................... delay_cycles(1);0222: NOPWhich looks like56 cycles either way.

author: Buriedcode
author_rank: Super Contributor
timestamp: March 03, 2017, 03:01:27 pm
content: I've seen UART mentioned, but... what about SPI? The PIC24's SPI module has a 16-bit mode. This will spit out 16 bits/an int happily. The clock will be helpful too because viewing serial data on an oscilloscope is tedious when you have long runs of 1's or 0's.I'm probably missing something here, but seems like an awful lot of replies for someone who just wants to output a16-bit value out a pin...

author: @rt
author_rank: Super Contributor
timestamp: March 03, 2017, 04:36:21 pm
content: LS bit of the int doesn’t write to the port pin?It should be ok then to compare two integers:Code:int mask;int sendint;OUT_LAT = 0;for (mask = 0x01; mask; mask <<= 1) {if (sendint & mask) {OUT_LAT = 1;} else {OUT_LAT = 0;}}or are you saying it takes longer to check one bit state than the other?

author: BrianHG
author_rank: Super Contributor
timestamp: March 04, 2017, 03:45:24 am
content: Quote from: Buriedcode on March 03, 2017, 03:01:27 pmThe nice thing about snarkysparky using the intrinsic C code bit-tests and bit-set/clear is that he can use ANY free IO pin from ANY pic to probe from. I believe the #asm & #endasm in the middle of your C code should also still work if you want to force things to always be dead on.Remember, this appears to be for testing, not as a main function.

author: Lunasix
author_rank: Regular Contributor
timestamp: March 04, 2017, 02:11:24 pm
content: You can use SPI. If you want to send more bits than possible with the peripheral maximum size (some has fifo), simply use DMA, which will then send all words for you. So, you can send 1kbits if you want, with few line code and in one time.

author: Buriedcode
author_rank: Super Contributor
timestamp: March 04, 2017, 02:40:57 pm
content: Quote from: BrianHG on March 04, 2017, 03:45:24 amI appreciate that using a generic routine, rather than relying on specific hardware, is preferable, and it really is quite simple to spit out bits serially... I suggested the SPI because as far as I am aware, all PIC24's have an SPI peripheral, and most have peripheral pin select, allowing the SPI MOSI to be on (almost) any pin. It can also run very quickly, or can be slowed down easily. Perhaps the biggest advantage is that it can run in the background.I noticed the OP mention outputting PPM, or PWM to indicate a bit 0, or bit 1 for debugging - similar to cheap RF and IR remote's. That is quite a good idea for 'eyeballing' serial data, it is remarkably easier to read since every pulse is a bit, and its width determines its value.I'm still unsure of the end-application of this. If its just debugging, then surely terminal would be better than looking at values on a scope? A fast UART with DMA with set 'sentences' and debug memory locations uses minimal CPU, and is human-readable on any terminal. It uses one pin - and with PPS, that can be any IO - and generally makes debugging a breeze. If this was for a PIC10F202 or an Attiny I might understand the whole bit-banging thing but for a peripheral-rich device like the PIC24 (be it F, H or E) debugging by 'looking at waveforms on a scope to read out an int' seems like an awkward way of doing it. Again, I must be missing something

author: Lunasix
author_rank: Regular Contributor
timestamp: March 04, 2017, 02:56:23 pm
content: If the goal is to output a readable value, and if there is no need to show this on a scope among other signals, you can send an ascii string, with as many chars as you want, so with readable text, with a small amount of code and and in one time if you use uart with DMA.