2
\$\begingroup\$

I'm trying to interface a 433 MHz receiver module with a PIC16F628A, running at 4 MHz from the INTOSC. The idea is that using a generic 433 MHz remote, I could pair it to the uC and toggle some outputs depending on what button was pressed.

Given that this uC already has a board made, and pins were not assigned properly, I'm not able to take advantage of any of the "most useful" modules (USART, IOC, RB0/INT) for reading the serial bit stream that the remote sends.

As of now, the receive pin of the RF module (A2ABTAE-D2) is connected to RA4.

So far, I tried using timer2 (since timer0 and timer1 are already in use) to check the status of RA4, and add one to HighTime (ht) or LowTime (lt). This is because the bitstream of the remote I'm using, represents a logic "0" as a short high pulse followed by a long low pulse, and a logic "1" as a long high pulse followed by a short low pulse. Because of this, to get the logic value of one bit, I have to at least, sample the receive pin 4 times in the period of one bit to be able to differentiate a "long" or a "short" pulse.

Because I plan on using this code with different remotes, the "long" and "short" times have to be adjusted for each remote, and this is where the problems begin.

Here is my code so far:

#include <xc.h>
#include "config.h"

#define SYNCq 15
uint32_t buffer;
uint32_t key = 0b0101010101010101001100000;
uint8_t data = 0, bitd = 0;
uint16_t ht, lt, bitp = 0;

void delay(uint16_t time);

void main(void) {
    TRISA = 0xFF;
    TRISB = 0x00;
    PORTA = 0x00;
    PORTB = 0x00;
    
    INTCON = 0xC0;
    PIE1 = 0x02;
    T2CON = 0x00;
    PR2 = 50;
    while (1){
        // "debug leds"
        PORTBbits.RB0 = T2CONbits.TMR2ON;   
        PORTBbits.RB1 = PIR1bits.TMR2IF;
        PORTBbits.RB2 = data;
        PORTBbits.RB3 = PORTAbits.RA4;
        // theyre here just for monitoring the status of registers
        if (PORTAbits.RA4 && !data)         // when a high pulse is detected, it means that a new transmission has begun. Start timer2 to begin the sync process
            data = T2CONbits.TMR2ON = 1;
        if (data == SYNCq){                    // once data reaches SYNCq, it means that the sync time is done
            T2CONbits.TMR2ON = 0;
            PORTBbits.RB4 = !PORTBbits.RB4; // ch2
            delay((bitp/SYNCq)*50);
        }
    }
    return;
}

void __interrupt() isr() {
    if (PIR1bits.TMR2IF){
        if (data < SYNCq){                 // 0 < data < SYNCq means sync time
            if(PORTAbits.RA4){                      // depending on the state of RA4, a 1 is added to ht or lt
                ht++;
                if (lt)                             // because every transmission/bit is started by a high pulse,  
                    bitd = 1;                       // if lt has a value assigned and this is executed again, it means 
            } else {                                // that a full bit has been sent, and the period of the bit is complete
                lt++;
            }
            if(bitd){                               // once the period of the bit is completed, values of ht, 
                bitp = bitp + ht + lt;              // lt and bitp are added for later averaging
                data++;                             // this part is repeated SYNCq times (not for any reason in particular)
                ht = lt = bitd = 0;
                PORTBbits.RB5 = !PORTBbits.RB5;     // ch3
            }
        }
        PIR1bits.TMR2IF = 0;
    }
}

void delay(uint16_t time){
    for (uint16_t i = 0; i >= time; i++){
        __delay_us(1);
    }
}

To be able to receive any data correctly, I have to know how long it takes for a "bit" to be completely sent (that is, one high pulse followed by a low pulse). That's why I first have to "sync" the uC and then I can start decoding data.

enter image description here

As you can see in this graph, the output at RB4 is a pulse of 876 μs, where instead, it should be a pulse of around 670 μs (matching the bit period). Now, one "debug" feature is the output at RB5. It (should) only change state when a full "bit period" has passed while in the sync time. And luckily it does. Watching it on the photo might be hard, but with the raw data I can see that the pulses (high or low) generated at RB5 are all around 650 to 700 μs (because the timer2 interrupts every 50 μs), and adding them all and averaging them, I get really close to the actual bit period.

Now, I would like to know if anyone sees anything wrong with my code and why is it not performing as expected.

Also, as a side question, I would like to know if there are faster ways to do what I've written as of now (for example: faster ways to read the pin status, or reduce the time it takes for all the code in the interruption to be executed, etc) and if there are any other options or ways of receiving the bitstream that any RF remote can send.

EDIT

Thanks to @thebusybee for the idea and @velvel for the pseudo code, I was able to determine that it was impossible to do what I wanted with the remote I was using. After trying for a long time, I decided that my only option was to find a slower remote. And I did: HT6P20B. Not only I have the advantage of having a datasheet, but it also is many many times slower than what I was working with previously. The "bit period" is easily determined by the oscilation frecuency of the IC and the timing graphs. I calculater that the bit period is exactly 1ms long. Thanks to that, I feel pretty sure that I will be able to recive the bit stream.

After some hours of work, I got this code:

#include <xc.h>
#include "config.h"
#include <stdio.h>
#include <string.h>

uint8_t status, statusp = 1, count = 0, done;
int16_t statusc = 0;
char buffer[32] = {0};
char buff[60] = {0};

void UARTinit(const long, const unsigned char);
void UARTsendString(const char *, const unsigned char);

void main(void) {
    TRISA = 0xFF;
    TRISB = 0x00;
    PORTA = 0x00;
    PORTB = 0x00;

    INTCON = 0xC0;
    PIE1 = 0x02;
    T2CON = 0x00;
    PR2 = 111;
    UARTinit(9600, 1);
    UARTsendString("PIC OK\n\r", 20);
    while (1) {
        if (PORTAbits.RA4 && !T2CONbits.TMR2ON) { // when a high pulse is detected, it means that a new transmission has begun. Start timer2 to begin the sync process
            T2CONbits.TMR2ON = 1;
        }
        if (done && T2CONbits.TMR2ON) {
            status = count = done = T2CONbits.TMR2ON = 0;
            statusc = 0;
            statusp = 1;
            sprintf(buff, "0b%s\n\r", buffer);
            UARTsendString(buff, sizeof buff);
            memset(buffer, 0, sizeof buffer);
        }
    }
    return;
}

void __interrupt() isr() {
    if (PIR1bits.TMR2IF) {
        status = PORTAbits.RA4;
        if (status) {
            if (!statusp) {
                if (statusc > 0) { //0
                    PORTBbits.RB4 = 0;
                    buffer[count] = 48;
                } else { //1
                    PORTBbits.RB4 = 1;
                    buffer[count] = 49;
                }
                statusc = 0;
                count++;
            }
            statusc--;
            PORTBbits.RB3 = 0;
        } else {
            statusc++;
            PORTBbits.RB3 = 1;
        }
        if (statusc > 45){
            done = 1;
        }
        statusp = status;
        PIR1bits.TMR2IF = 0;
        PORTBbits.RB5 = !PORTBbits.RB5;
    }
}

As you can see, I did some modifications and added some other debug stuff because the way I was "debugging" the variables was completely useless.

Now, as is, the code works really good at reading only the first bit stream (the remote repeats the same code 6 or 7 times). Only the last bit gets lost, but it dosent really matter because is considered as an "anti-code period" by the datasheet. The issue is that if I remove the last line of the interrupt (PORTBbits.RB5 = !PORTBbits.RB5;) EVERYTHING stops working. And I really mean it. The bit stream gets corrupted, the edge detection stops working and the end detection (done) triggers before the bit stream ends. The thing is that I need that line to be gone, since in the full code (wich I still havent tested any of what I did on) I use that pin for other things, and there are no spare pins. It would seem like the issue has to do with the overall time it takes for the interrupt to complete, but it wouldnt make any sense because a simpe PORTX = X (if i'm not mistaken) takes only 4 cycles, and if the INTOSC is at 4MHz that means that PORTX = X takes only 4uS. What is even more weird is that in theory, after the interrupt ocurrs (again, if I'm not mistaken), that is, TMR2 == PR2, it "automatically" clears TMR2 and starts counting again even if the interrupt vector isn't completely executed yet. I dont know. I attach these two images of the outputs i have when the PORTBbits.RB5 = !PORTBbits.RB5; line is on the interrupt and when its not: enter image description here enter image description here Over here I made another question with more info about this issue.

\$\endgroup\$
2
  • \$\begingroup\$ Could you use one of the top 4 bits of PORTB instead of RA4 to input the receiver serial stream? Doing so allows you to interrupt-on-change. This feature allows a tight interrupt service routine to decode those serial pulses. In a slightly more capable PIC, my serial stream is compacted into a few bytes with 15 instructions (assembler, not C). I'm decoding SONY SIRC IR-remote codes, not these. Your interrupt service might be roughly twice as long. \$\endgroup\$
    – glen_geek
    Jul 19 at 2:37
  • 1
    \$\begingroup\$ @glen_geek, as I said, there is already a PCB made for this IC, and there is no other way of connecting the receive pin to any of the pins with useful peripherals \$\endgroup\$
    – fpp
    Jul 19 at 12:53

2 Answers 2

2
\$\begingroup\$

Without any hardware change your only serious option is to sample RA4, right. There are multiple alternatives for the evaluation of the samples.

One possible solution is this:

  • Detect the rising edge. This is "just" a matter of saving the level of the last sample and a comparison.
  • Between the rising edges, use a single counter, which decrements on high level and increments on low level.
  • On each rising edge, use the most significant bit of the counter as the resulting bit of the past period. Then clear the counter.

Pseudo code:


bool curr_ra4;
static bool prev_ra4 = true;
static int16_t ra4_ctr = 0;

curr_ra4 = PORTAbits.RA4; // capture current state

if (!prev_ra4 && curr_ra4) // RA4 rising edge detection
{
    if (ra4_ctr > 0)
    {
        // received a "0" bit -> do stuff
    }
    else
    {
        // received a "1" bit -> do stuff
    }

    ra4_ctr = 0; // reset RA4 counter
}

if (curr_ra4)
{
    ra4_ctr--; // decrement on high level
}
else
{
    ra4_ctr++; // increment on low level
}

prev_ra4 = curr_ra4; // latch previous state for edge detection
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks for the suggestion. it has been like 4 hours that i've been banging my head to the desk trying to figure out why it wouldnt work. I think i'm hitting the roof on what this uC can do at 4MHz... I've got good results, but as soon as i add three lines on the interrupt to handle when to stop writing data, everithing gets out of time and eventually the data reception hangs because the rising edge has already passed \$\endgroup\$
    – fpp
    Jul 19 at 20:39
  • 1
    \$\begingroup\$ @fpp In cases like this I look into the generated machine code to check for bottle necks. Some of your new lines might produce a lot overhead. At 4 MHz system clock and 50 µs interrupt period there are 200 clocks per interrupt. Some of them need to be assigned to the main loop... -- Anyway, this is another example why thoughtless assignment of port pins leads to -erm- sub-optimal solutions. :-D \$\endgroup\$ Jul 19 at 20:59
1
\$\begingroup\$

I forgot to add CMCON = 0x07 to turn off the comparators. That did the trick.

The issue has to do with problems with the read-modify-write (RMW) behaviour of BSF/BCF instructions on PORTx registers in old PIC16F devices.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.