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.
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:
Over here I made another question with more info about this issue.