Here is a quick tutorial of basic communication between two RFM12B’s driven with MSP430 Launchpads.
Last couple of days (10 days) I have been occupied with two FRM12B modules, trying to get them to communicate. I need them to make some kind of wireless temperature sensors. I had some simple weather station from Lidl, but it stopped working, so I said I will make one myself. This is the part for tranferring measuremensts from cold outside, into our warm home. I had short connection on receiver board and it took me a few days to realize that, so it took me much more than it should. Learn from my mistakes! I have an adapter board that fits on MSP-EXP430G2 Launchpad board. I also made small board to fit RFM12B on it. This is how everything fits together:
As you can see, the board was not properly drawn first time.
There are 5 connection between RFM12B and MSP430, plus two for power. They are connected as follows:
1 2 3 4 5 6 |
MSP430 <-> RFM12B P1.7 <-> SDO P1.6 <-> SDI P1.5 <-> SCK P1.4 <-> nSEL P1.3 <-> nIRQ |
I use Code Composer Studio 6.2.0.00050, which is FREE, because there is version 7.x out. You can get it here. You will need to register, to be able to download software. This is link to FREE license and it’s installation instructions. I will not explain how to install it, since you must be smart enough to do it if you are programming microcontrollers.
We need to start by creating new project (Project -> New CCS Project…):
Now we need to select our microcontroller (I selected MSP430G2231 since this is the one in my launchpad):
We also need to name our project. I named this one RFM12B_TX_002. RFM12 is obvious, TX is because this is the transmiter part of our project and 002, because I already have 001 and this is how I sequnentially name all my software projects. And this is what it looks after we click finish:
I used different information sources for this. Some pdf’s from internet, some code found on random sites, some code I already used on ATMEGA8 for RFM12B, some info from forums, which didn’t help a lot, since no one else had shortcut on circuit, like I did. 🙁 But finally it worked and here is code I use to do it.
This is code for main.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
/* * Created by Slemi * 7.3.2018 */ #include <msp430.h> #include "rfm12b_slemi_tx.h" #define key_press !(P1IN & 0x08) int main(void) { uint16_t tmp; WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer //set clock to 1MHz BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; /* Clock configuration. CPU is running at ~1MHz */ BCSCTL1 &= ~(XTS); // Select low frequency mode for LFXT1, high frq mode not supported on MSP430G2231 BCSCTL1 |= DIVA_3; // ACLK = 32768Hz / 8 = 4096Hz BCSCTL3 &= ~(LFXT1S0 | LFXT1S1); //32768-Hz crystal on LFXT1 rf12_port_init(); rf12_init(); //wait for RFM21B to wake up __delay_cycles(1000000); _enable_interrupt(); while(1) { rf12_write_cmd(RF_TX_ON); rf12_write_cmd(0x0000); rf12_write_data(0xAA); rf12_write_data(0xAA); rf12_write_data(0xAA); rf12_write_data(0x2D); rf12_write_data(0xD4); rf12_write_data(0x30); rf12_write_data(0x31); rf12_write_data(0x32); rf12_write_data(0x33); rf12_write_data(0x34); rf12_write_data(0x35); rf12_write_data(0x36); rf12_write_data(0x37); rf12_write_data(0x38); rf12_write_data(0x39); rf12_write_data(0x3A); rf12_write_data(0x3B); rf12_write_data(0x3C); rf12_write_data(0x3D); rf12_write_data(0x3E); rf12_write_data(0x3F); rf12_write_data(0x39); rf12_write_data(0xAA); rf12_write_data(0xAA); rf12_write_data(0xAA); rf12_write_cmd(RF_IDLE_MODE); P1OUT |= 0x01; _delay_cycles(100000); P1OUT &= 0xFE; _delay_cycles(1900000); } return 0; } void __attribute__((interrupt(PORT1_VECTOR))) PORT1_ISR(void) { if(P1IFG & BIT3) { //P1OUT |= BIT0; } P1IFG &= ~BIT3; } |
In case you try to change something, don’t forget you need to use FIFOReset();, otherwise you will get less that half the data through! Don’t ask me how I know!
We also need one other file, where the magic happens. We need to make new header file (Right click on project name -> New -> Header File):
Give it some name and click Finish (I marked wrong here, the filename should be marked):
Here is our new file opened and ready:
If you name it different you need to change include at the beginning of main.c accordingly:
Here is the code for out new file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
/* * Created by Slemi * 7.3.2018 */ #ifndef RFM12B_SLEMI_TX_H_ #define RFM12B_SLEMI_TX_H_ #include <msp430.h> #include <stdint.h> #define HW_SPI // SDI, SDO refers to msp430 device's pins, NOT RFM12B's #define SDI BIT7 // to SDO #define SDO BIT6 // to SDI #define SCK BIT5 // to SCK #define SEL BIT4 // to nSEL #define IRQ BIT3 // to nIRQ #define RED_LED BIT0 #define GRN_LED BIT6 // RF12 command codesTODO change #define RF_RX_ON 0x82DD #define RF_TX_ON 0x823D #define RF_IDLE_MODE 0x820D #define RF_SLEEP_MODE 0x8205 #define RF_WAKEUP_MODE 0x8207 #define RF_TXREG_WRITE 0xB800 #define RF_RX_FIFO_READ 0xB000 #define RF_WAKEUP_TIMER 0xE000 #define izh0_on P1OUT |= BIT0 #define izh0_off P1OUT &= ~BIT0 #define izh1_on P1OUT |= BIT1 #define izh1_off P1OUT &= ~BIT1 /*#define izh2_on P1OUT |= BIT2 #define izh2_off P1OUT &= ~BIT2*/ #define nSEL_LOW P1OUT &= ~(SEL) #define nSEL_HIGH P1OUT |= (SEL) #define SDO_HIGH P1OUT |= (SDO) #define SDO_LOW P1OUT &= ~(SDO) #define SCK_HIGH P1OUT |= (SCK) #define SCK_LOW P1OUT &= ~(SCK) void rf12_port_init(void); uint16_t rf12_write_cmd(uint16_t rf12data); void rf12_init(void); void rf12_write_data(uint16_t data); void FIFOReset(void); unsigned int rfRecv(void); unsigned char waitForData(void); void rf12_port_init(void) { P1DIR |= SCK | SDO | SEL | RED_LED | BIT1 | BIT2; //outputs P1OUT &= ~BIT2 & ~BIT1 & ~BIT0; P1DIR &= ~SDI & ~BIT3 & ~BIT2; //input //init ports P1REN |= BIT7 | BIT3 | BIT2; //Enable pull-up resistor for button/RFM12B IRQ P1OUT |= BIT7 | BIT3 | BIT2; //Set input for key high for enabling detection of keypress or IRQ P1IFG &= ~BIT3 & ~BIT2; //clear interrupt flag for bit3 of PORT1 P1IE |= BIT3 | BIT2; //enable interrupt on P1.3 #ifdef HW_SPI //enable SDI, SDO and SCLK, master mode, MSB first, output enabled, held in reset state USICTL0 = USIPE7 | USIPE6 | USIPE5 | USIMST | USIOE | USISWRST; //TODO sw solution // SMCLK / 16 USICKCTL = USIDIV_4 + USISSEL_2; //TODO sw solution // clock phase USICTL1 |= USICKPH; //TODO sw solution // USI release from reset USICTL0 &= ~USISWRST; //TODO sw solution #endif } #ifdef HW_SPI uint16_t rf12_write_cmd(uint16_t data) { nSEL_LOW; USISR = data; //load SPI data register USICTL1 &= ~USIIFG; //clear SPI interrupt USICNT = USI16B | 16;//select 16 bit data size and select 16 bits to be received/transmitted while((USICTL1 & USIIFG) != 0x01) //wait for data transmitted interrupt { } nSEL_HIGH; return USISR; } #else uint16_t rf12_write_cmd(uint16_t rf12data) { uint16_t return_data = 0; uint8_t i; nSEL_LOW; for(i = 0; i < 15; i++) { if(rf12data & 0x8000) SDO_HIGH; else SDO_LOW; SCK_HIGH; if(P1IN & SDI) return_data++; return_data = return_data << 1; SCK_LOW; rf12data = rf12data << 1; } nSEL_HIGH; return return_data; } #endif void rf12_init(void) { rf12_write_cmd(0x0000); rf12_write_cmd(0x80E8); rf12_write_cmd(RF_TX_ON); rf12_write_cmd(0xA680); rf12_write_cmd(0xC647); rf12_write_cmd(0x94A2); rf12_write_cmd(0xC2AC); rf12_write_cmd(0xCA83); rf12_write_cmd(0xCED4); rf12_write_cmd(0xC483); rf12_write_cmd(0x9855); rf12_write_cmd(0xCC77); rf12_write_cmd(0xE000); rf12_write_cmd(0xC800); rf12_write_cmd(0xC049); } void rf12_write_data(uint16_t data) { while(P1IN & BIT3) { } rf12_write_cmd(0xB800 + data); } void FIFOReset(void) { rf12_write_cmd(0xCA81); rf12_write_cmd(0xCA83); } unsigned int rfRecv(void) { unsigned int data; data = rf12_write_cmd(0xB000); return (data & 0x00FF); } unsigned char waitForData(void) { SDO_LOW; nSEL_LOW; SCK_HIGH; if(P1IN & BIT7) { SCK_LOW; SDO_HIGH; nSEL_HIGH; return 1; } else { SCK_LOW; SDO_HIGH; nSEL_HIGH; return 0; } } #endif /* RFM12B_SLEMI_TX_H_ */ |
Now all we need to do is to connect RFM12B module to microcontroller, upload our code and start it. At start, green LED should be on for about a second and after that both red and green LED should blink every two seconds, showing us when it is transmitting.
For receiver we need to create new project as before. This time I will name it RFM12B_RX_002. We also need to create new header file, I will name it rfm12b_slemi_rx. I add rx and tx at the end of files, so that I know which is which, if I have both open at the same time, when writing or editing code.
Here is our main.c code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
/* * Created by Slemi * 7.3.2018 */ #include <msp430.h> #include "rfm12b_slemi_rx.h" #define key_press !(P1IN & 0x08) volatile uint16_t podatki[16]; volatile uint16_t rx_data[16]; int main(void) { uint8_t i; uint16_t rx_data[16]; WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer //set clock to 1MHz BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; /* Clock configuration. CPU is running at ~1MHz */ BCSCTL1 &= ~(XTS); // Select low frequency mode for LFXT1, high frq mode not supported on MSP430G2231 BCSCTL1 |= DIVA_3; // ACLK = 32768Hz / 8 = 4096Hz BCSCTL3 &= ~(LFXT1S0 | LFXT1S1); //32768-Hz crystal on LFXT1 //wait for RFM21B to wake up __delay_cycles(1000000); rf12_port_init(); rf12_init(); _enable_interrupt(); rf12_write_cmd(RF_RX_ON); FIFOReset(); while(1) { for(i = 0; i < 16; i++) { while(!(waitForData())); rx_data[i] = rf12_write_cmd(0xB000) & 0x00FF; } if(rx_data[4] == 0x0034) izh0_on; _delay_cycles(100000); izh0_off; FIFOReset(); } return 0; } void __attribute__((interrupt(PORT1_VECTOR))) PORT1_ISR(void) { if(P1IFG & BIT3) { P1IFG &= ~BIT3; //clear interrupt flag } if(P1IFG & BIT2) { P1OUT |= BIT1; P1IFG &= ~BIT2; //clear interrupt flag } } |
And here is code for rfm12b_slemi_rx.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
/* * Created by Slemi * 7.3.2018 */ #ifndef RFM12B_SLEMI_RX_H_ #define RFM12B_SLEMI_RX_H_ #include <msp430.h> #include <stdint.h> #define HW_SPI // SDI, SDO refers to msp430 device's pins, NOT RFM12B's #define SDI BIT7 // to SDO #define SDO BIT6 // to SDI #define SCK BIT5 // to SCK #define SEL BIT4 // to nSEL #define IRQ BIT3 // to nIRQ #define RED_LED BIT0 #define GRN_LED BIT6 // RF12 command codesTODO change #define RF_RX_ON 0x82DD #define RF_TX_ON 0x823D #define RF_IDLE_MODE 0x820D #define RF_SLEEP_MODE 0x8205 #define RF_WAKEUP_MODE 0x8207 #define RF_TXREG_WRITE 0xB800 #define RF_RX_FIFO_READ 0xB000 #define RF_WAKEUP_TIMER 0xE000 #define izh0_on P1OUT |= BIT0 #define izh0_off P1OUT &= ~BIT0 #define izh1_on P1OUT |= BIT1 #define izh1_off P1OUT &= ~BIT1 /*#define izh2_on P1OUT |= BIT2 #define izh2_off P1OUT &= ~BIT2*/ #define nSEL_LOW P1OUT &= ~(SEL) #define nSEL_HIGH P1OUT |= (SEL) #define SDO_HIGH P1OUT |= (SDO) #define SDO_LOW P1OUT &= ~(SDO) #define SCK_HIGH P1OUT |= (SCK) #define SCK_LOW P1OUT &= ~(SCK) void rf12_port_init(void); uint16_t rf12_write_cmd(uint16_t rf12data); void rf12_init(void); void rf12_write_data(uint16_t data); void FIFOReset(void); unsigned int rfRecv(void); unsigned char waitForData(void); void rf12_port_init(void) { P1DIR |= SCK | SDO | SEL | RED_LED | BIT1 | BIT2; //outputs P1OUT &= ~BIT2 & ~BIT1 & ~BIT0; P1DIR &= ~SDI & ~BIT3 & ~BIT2; //input //init ports P1REN |= BIT7 | BIT3 | BIT2; //Enable pull-up resistor for button/RFM12B IRQ P1OUT |= BIT7 | BIT3 | BIT2; //Set input for key high for enabling detection of keypress or IRQ P1IFG &= ~BIT3 & ~BIT2; //clear interrupt flag for bit3 of PORT1 P1IE |= BIT3 | BIT2; //enable interrupt on P1.3 #ifdef HW_SPI //enable SDI, SDO and SCLK, master mode, MSB first, output enabled, held in reset state USICTL0 = USIPE7 | USIPE6 | USIPE5 | USIMST | USIOE | USISWRST; //TODO sw solution // SMCLK / 16 USICKCTL = USIDIV_4 + USISSEL_2; //TODO sw solution // clock phase USICTL1 |= USICKPH; //TODO sw solution // USI release from reset USICTL0 &= ~USISWRST; //TODO sw solution #endif } #ifdef HW_SPI uint16_t rf12_write_cmd(uint16_t data) { nSEL_LOW; USISR = data; //load SPI data register USICTL1 &= ~USIIFG; //clear SPI interrupt USICNT = USI16B | 16;//select 16 bit data size and select 16 bits to be received/transmitted while((USICTL1 & USIIFG) != 0x01) //wait for data transmitted interrupt { } nSEL_HIGH; return USISR; } #else uint16_t rf12_write_cmd(uint16_t rf12data) { uint16_t return_data = 0; uint8_t i; nSEL_LOW; for(i = 0; i < 15; i++) { if(rf12data & 0x8000) SDO_HIGH; else SDO_LOW; SCK_HIGH; if(P1IN & SDI) return_data++; return_data = return_data << 1; SCK_LOW; rf12data = rf12data << 1; } nSEL_HIGH; return return_data; } #endif void rf12_init(void) { rf12_write_cmd(0x0000); rf12_write_cmd(0x80E8); rf12_write_cmd(RF_RX_ON); rf12_write_cmd(0xA680); rf12_write_cmd(0xC647); rf12_write_cmd(0x94A2); rf12_write_cmd(0xC2AC); rf12_write_cmd(0xCA83); rf12_write_cmd(0xCED4); rf12_write_cmd(0xC483); rf12_write_cmd(0x9855); rf12_write_cmd(0xCC77); rf12_write_cmd(0xE000); rf12_write_cmd(0xC800); rf12_write_cmd(0xC049); } void rf12_write_data(uint16_t data) { while(P1IN & BIT3) { } rf12_write_cmd(0xB800 + data); } void FIFOReset(void) { rf12_write_cmd(0xCA81); rf12_write_cmd(0xCA83); } unsigned int rfRecv(void) { unsigned int data; data = rf12_write_cmd(0xB000); return (data & 0x00FF); } unsigned char waitForData(void) { SDO_LOW; nSEL_LOW; SCK_HIGH; if(P1IN & BIT7) { SCK_LOW; SDO_HIGH; nSEL_HIGH; return 1; } else { SCK_LOW; SDO_HIGH; nSEL_HIGH; return 0; } } #endif /* RFM12B_SLEMI_RX_H_ */ |
After running it, it should blink at the same rate as transmitter. I hope it works in first try for you!
This is just bare project, just to get started. If we need some transmission checking, we should at least add checksum, or even better two way transmission, so that we can check back if data was sent ok, or we need to retransmit. But this is for another tutorial…
Thank you for reading!
Gregor