Tag Archives: 802.15.4

MRF24J40 driver for STM32

A while ago I put together a C driver for the MRF24J40 802.15.4 modules from microchip. I had been using them as a cheap alternative to xbees in some AVR projects. As I’ve been moving on to STM32 parts for hobby projects, (more power, cheaper) I started off porting my driver code from AVR over to cortex m3.

This has been quite an experience. The arm toolchain experience, and particularly the libc support and general documentation is completely different to, say, avr-libc (AVR-libc is a great project, reallly solid)

However, with lots of learning, and lots of mistakes, I’ve got it all working. It’s a first cut, but the basic features are there. There’s no magic for DMA, and you have to do a lot of the pin setup yourself, but this is Cortex M3, there’s so many pins you could be using for this! I’ve tried to reduce the required function calls as much as possible, but there’s still room for improvement.

    // Required by the user code
    extern void mrf_select(void);  // Chip select, if necessary
    extern void mrf_deselect(void);  // chip deselect, if necessary
    extern uint8_t spi_tx(uint8_t cData);
    extern void _delay_ms(int);  // only used at init time.

This still needs to move to a clear sub project, currently it’s a separate code base to the AVR code.

Get the code now!

More to come, as it gets tidied up and put into use!

Updated MRF24J40 library code, now easier to use

I’ve just substantially updated my library code for dealing with Microchip’s MRF24J40 modules on AVR microcontrollers, making it much much easier to use. It’s now based on callbacks for handling rx/tx packets, so you don’t need to know anywhere near as much about the module’s internals anymore. The tradeoff is a bit more ram usage.

mrf_reset and mrf_init now take parameters to ports and pins, so you no longer need to mess around with defines in the library headers any more.

#include "lib_mrf24j.h"
    mrf_reset(&PORTB, PINB5);   // reset pin
    mrf_init(&PORTB, PINB0);  // chip select pin

You no longer need to manually work with the interrupts, you just need to set up the ISR for the pin you’ve chosen to connect to the MRF24J40’s INT pin, for example, for my case, using INT0. (Remember to add a EIMSK |= _BV(INT0); somewhere too!)

ISR(INT0_vect) {
    mrf_interrupt_handler();
}

The interrupt handler takes care of reading in received rf data, and keeping it in a local buffer. It always contains the most recent packet. To do something with the received data, call mrf_check_flags() pretty often. It takes two function pointers as parameters, one which will be called for rx, and one for tx. Here’s a snippet from my main().

    // set the pan id to use
    mrf_pan_write(0xcafe);
    // set our address
    mrf_address16_write(0x6001);
    while (1) {
        // Call this pretty often, at least as often as you expect to be receiving packets
        mrf_check_flags(&handle_rx, &handle_tx);
        if (time_to_send()) {
            mrf_send16(0x4202, 4, "abcd");
        }
    }

And here’s some example rx/tx callback handlers.

void handle_rx(mrf_rx_info_t *rxinfo, uint8_t *rx_buffer) {
    printf_P(PSTR("Received a packet: %d bytes long\n"), rxinfo->frame_length);
    printf_P(PSTR("Packet data:\n"));
    for (int i = 0; i <= rxinfo->frame_length; i++) {
        printf("%x", rx_buffer[i]);
    }
    printf_P(PSTR("\nLQI/RSSI=%d/%d\n"), rxinfo->lqi, rxinfo->rssi);
}
 
void handle_tx(mrf_tx_info_t *txinfo) {
    if (txinfo->tx_ok) {
        printf_P(PSTR("TX went ok, got ack\n"));
    } else {
        printf_P(PSTR("TX failed after %d retries\n"), txinfo->retries);
    }
}

The library code is available here, and also on github (lib_mrf24j)

Complete demo code for a module connected to a PJRC Teensy 2.0 board is here and also on github (You’ll probably have to edit paths in the Makefile)

MRF24J40 with AVR SPI (atmega32u4) part3

Update 2011 Oct 6 See Christopher’s comments below! Specifically, line 172 might need to be removed, if you’re using this with a pure mrf24j40 network, or a network that doesn’t contain xbee series 1 devices!
Update 2011 May 29 The sample code and library have been substantially upgraded. See my newer post for the details

And now I have TX working too. The microchip datasheet is _reallly_ sparse on details, and you have to go and get a copy of the actual IEEE 802.15.4 spec (either 2003, or 2006, we’re only concerned with the specification of the MAC header)

I had some problems trying to reconcile bit ordering between the IEEE spec (MSB rightmost, most of the time) and the Microhip docs (MSB leftmost, most of the time) but from there it pretty much just worked. I have yet to try out acknowledgements, I was trying to keep it pretty simple, but working code is working code.

Except… The microchip docs say that you write out one byte with the header length, one byte with the frame length, (header plus packet body) then the header, then the packet body. No gaps anywhere. Try as I might, I had to leave a two byte gap after the header, and before the packet data, when writing to the MRF24J40’s tx normal fifo. (And allow for this in the “length” fields)

I thought I had the addressing modes wrong, and my first two bytes of packet data were being used as some sort of header field I didn’t understand, but I tried out 4 different addressing modes, and in all cases, the data on the received side passed all checksums, and was only consistent with there being a _gap_ in the fifo.

Strange, but workable, as long as you know it’s there. It still makes me feel uncomfortable though.

For reference, I’m using 16 bit (short) addressing for both source and destination, PAN ID header compression (only destination PAN ID is sent) with no security.

The library code is over at github and allows sending packets to existing series one xbee listeners just like this…

mrf_reset();
mrf_init();
// set our PAN ID
mrf_pan_write(0xcafe);
// set our local source address
mrf_address16_write(0x6001);
// send something to a given node
mrf_send16(0x4202, 4, "abcd");

MRF24J40 with AVR SPI (atmega32u4) part2

Update 2011 May 29 The sample code and library have been substantially upgraded. See my newer post for the details.

Good progress last night and today. I get an interrupt for each received packet, working in both promiscuous mode and normal mode.

A fair bit of code to get here, compared to using xbees, but SPI is much much faster, and you don’t need to dick around with baud rates and getting clock timing perfect. UARTs are terrible. From the datasheet, and the little bit of information you can find on the web, it seems that these modules have some rather odd silicon errata. It seems the original datasheet was completely wrong. This is the only thing that I can think of to explain why you need to set up so many registers just to get it to work. The defaults in the silicon are all worthless. Oh well :) It works :)

Note to self: be careful to use mrf_read_long() when you want to use a long address, the address spaces overlap, so using mrf_read_short works, just doesn’t return anything useful.

Working code to get this far, with an AVR host is over at github

MRF24J40 with AVR SPI (atmega32u4) part1

Ouch, this took a lot longer to get started than I thought. SPI is meant to be easy right? All my SPI reads from the MRF24J were returning 0xff.

Turns out that, even with the /SS pin disconnected, if you don’t explicitly set it as an output pin in the DDR register, the AVR falls out of SPI mode if it ever goes low.

So, even though the pin is not connected, (I’m using a general IO pin to do chip select on the radio module) nothing worked until I explicitly made it an output.

And now, presto, I can read data from the MRF24J properly now! Now we can finally move on to the rest of this bring up.

(The MRF24J40 is an 802.15.4 module, with a SPI interface. It’s about 6€, vs about 20€ for xbees, and is on a standard 2.54mm pin spacing, instead of xbee’s 2mm spacing)