3
\$\begingroup\$

I want to generate 16 impulses, one for the freq. of 32 kHz and other for 64 kHz. I'm using a PIC16F887 and the freq. of the μC is 1 MHz, which mean 1 instruction cycle = 4 μs.

All I want is for 32 kHz, to generate 16 impulses , where one impulse has 16 μs ( 4 cycles High + 4 cycles Low), and for 64 kHz, same 16 impulses, where one impulse has 8 μs (2 cycles High + 2 cycles Low).

The problem is I've tried so many options by adding a variable which count to 16, but it added additional instruction cycles, especially for the Low part, where RB7 = 0, and I don't want to let the code in this form.

I would highly appreciate help in this situation, advices, code written, where should I change?

enter image description here

 #include <htc.h> #define _XTAL_FREQ 1000000 unsigned char trigger ; void main(void) { TRISB=0b00000001; //RB0 input ANSELH=0; // pini digitali IOCB=0b00000001 ; //selectie pin RB0 interupt on change INTCON=0b10001000; // b7 GIE=1 activ. globala intreruperi // b3 RBIE=1 activ. intrerupere PORTB // b0 RBIF=0 fanion instr. PORTB //GIE=1 ;RBIE=1;RBIF=0; while(1) { if(trigger==32) { RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; RB7=1; RB7=1; RB7=1; RB7=1; RB7=0; RB7=0; RB7=0; RB7=0; } if(trigger==64) { RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; RB7=1; RB7=1; RB7=0; RB7=0; } trigger=0; } } void interrupt my_isr(void) { if(RBIF==1 && RBIE==1) { if(RB0==0) trigger=32; if(RB0==1) trigger=64; RBIF=0; } } 
\$\endgroup\$
2
  • 5
    \$\begingroup\$ Your probably better off dropping to assembly for this if you need to bit bang it. \$\endgroup\$ Commented Dec 28, 2024 at 16:31
  • \$\begingroup\$ Use on-chip hardware peripheral timers instead. Counting cycles is a brittle and tedious solution, plus it also keeps the CPU 100% busy for no good reason. \$\endgroup\$ Commented Jan 8 at 8:30

2 Answers 2

8
\$\begingroup\$

Use the hardware timers to do the pulses.

Use assembly language to fine-tune the cycle count.

Use USART or SPI modules to generate the pulses.

Use higher clock frequency to have more granularity.

\$\endgroup\$
6
\$\begingroup\$

@Justme's answer gives the optimal solution if you don't want to write out all 16 pulses directly - use a hardware timer to generate the pulses, or increase the clock frequency.


Let's explore why it's not possible to generate the 64kHz pulses with a loop in software alone with a 1MHz clock (250kHz instruction rate)

In order to form a loop, you need to have a loop variable which counts down the number of cycles remaining, and if there are more to complete, jump back to the start of the loop. You also need an instruction to modify the output register.

For the most compact code you would also need logic to select whether the output is being set or clear, which would add further instructions. We will start with that example for interests sake.

Take the following C code:

uint8_t cnt; for (cnt = (2*16); cnt > 0; cnt--) { // Counting down is more efficient! if (RB7) { RB7 = 0; // Clear if set } else { RB7 = 1; // Set if clear } } 

Let's take a stab at writing this in assembly based directly on the way the C code is written above, though ensuring cycle accuracy for low-high and high-low transitions. We would likely get the following pseudo-assembly:

 // Counter initialisation MOVLW 32 // Load W register with literal 32 MOVWF cnt // Copy W into cnt register // Begin loop start_loop: // Pin state checking BTFSC 0x6, 7 // Check if RB7 is set GOTO clear_bit // If it is set, jump to "clear_bit" NOP // Nop needed to ensure cycle accuracy // Set bit logic BSF 0x6, 7 // Set the bit GOTO check_loop // Jump to loop checking logic // Clear bit logic clear_bit: BCF 0x6, 7 // Clear the bit NOP // Nops needed to ensure cycle accuracy NOP // Loop check logic check_loop DECFSZ cnt, 1 // Decrement counter and check if zero GOTO start_loop // If not zero, repeat loop // End of loop 

Notice that this requires quite a lot of extra instructions to handle the loop and state checking logic.

From the PIC16F Instruction Set document, we can get information about each of these instructions and how long they take. I've added screenshots at the end of this answer for the relevant instructions.

Each path through the loop above can be calculated to be 9 instruction cycles. You have a maximum of 2 cycles, so clearly this is not going to work.

With a bit of optimisation effort (directly writing in assembly rather than in C), then by duplicating the loop checking logic we can actually further reduce this to take only 7 cycles as shown below. But that is still not fast enough for either 32kHz or 64kHz pulse trains.

 // Counter initialisation MOVLW 32 // Load W register with literal 32 MOVWF cnt // Copy W into cnt register // Begin loop start_loop: // Pin state checking BTFSC 0x6, 7 // Check if RB7 is set GOTO clear_bit // If it is set, jump to "clear_bit" NOP // Nop needed to ensure cycle accuracy // Set bit logic BSF 0x6, 7 // Set the bit // Set state loop check logic DECFSZ cnt, 1 // Decrement counter and check if zero GOTO start_loop // If not zero, repeat loop GOTO end_loop // Exit the loop otherwise // Clear bit logic clear_bit: BCF 0x6, 7 // Clear the bit // Clear state loop check logic DECFSZ cnt, 1 // Decrement counter and check if zero GOTO start_loop // If not zero, repeat loop end_loop: //End of loop 

Ok, so let's simplify things. The following C code removes the state check and does each high-low in the same loop:

uint8_t cnt; for (cnt = 16; cnt > 0; cnt--) { // Counting down is more efficient! RB7 = 1; // Add some NOPs here to make pulse symetric RB7 = 0; } 

Again, translating into assembly in an optimal way, we would likely get the following pseudo-assembly:

 // Counter initialisation MOVLW 16 // Load W register with literal 16 MOVWF cnt // Copy W into cnt register // Begin loop start_loop: BSF 0x6, 7 // Set the bit NOP // Nops needed to ensure cycle accuracy NOP NOP BCF 0x6, 7 // Clear the bit // Loop check logic DECFSZ cnt, 1 // Decrement counter and check if zero GOTO start_loop // If not zero, repeat loop //End of loop 

Calculating the number of instructions here, we see the loop checking is 3 cycles for the low state, hence we add 3 NOP instructions for the high state.

This will give a total of 4 instruction cycles for both high and low states. Interestingly this is actually sufficiently fast for the 32kHz pulse train. However it is still too slow for the 64kHz pulse train.

If you were to double the PIC clock rate from 1MHz to 2MHz, the above assembly code would also be fast enough to generate your 64kHz pulses.

However if you are stuck with 1MHz for your clock rate, then clearly it is not possible to use a loop for your 64kHz pulse train, and as a result you are left with your unrolled loop code. In assembly, this would look like:

BSF 0x6, 7 // Set the bit NOP // Stay high for 2 cycles BCF 0x6, 7 // Clear the bit NOP // Stay low for 2 cycles ... // Repeat (Copy and paste) 16 times 

BCF Instruction

BTFSC Instruction

DECFSZ Instruction

GOTO Instruction

\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.