[nRF52] Here is tested and working "proprietary" radio demo code

Discussion and questions about boards that can run MicroPython but don't have a dedicated forum.
Target audience: Everyone interested in running MicroPython on other hardware.
User avatar
WhiteHare
Posts: 129
Joined: Thu Oct 04, 2018 4:00 am

[nRF52] Here is tested and working "proprietary" radio demo code

Post by WhiteHare » Sun Oct 21, 2018 12:53 pm

It is written entirely in micropython, and it works on both nRF52832 and nRF52840. In brief: the transmitter sends a packet with a different one-byte payload about once every second, and the receiver displays the content of the payload as well as an ongoing count of the number of packets received.

In this case "proprietary" simply means Nordic's proprietary radio mode. Anyone can use it. It's very similar to the frames sent by the nRF24L01+. It does not rely on bluetooth.

Here is the receiver code:

Code: Select all

# This program for an nRF52832 receives a packet with a one byte payload 

from micropython import const
import machine  # so can peek and poke different registers on the nRF5x
import uctypes
import utime

radioBuffer = bytearray(100)  # allocate IO buffer for use by nRF5x radio
radioBuffer_address = uctypes.addressof(radioBuffer)

my_prefixAddress = const(0xAA)
my_baseAddress = const(0xDEADBEEF)

NRF_POWER = const(0x40000000)
DCDCEN = const(0x578)
NRF_POWER___DCDCEN = const(NRF_POWER + DCDCEN)

NRF_CLOCK = const(0x40000000)
TASKS_HFCLKSTART = const(0)
EVENTS_HFCLKSTARTED = const(0x100)
NRF_CLOCK___TASKS_HFCLKSTART = const(NRF_CLOCK + TASKS_HFCLKSTART)
NRF_CLOCK___EVENTS_HFCLKSTARTED = const(NRF_CLOCK + EVENTS_HFCLKSTARTED)

NRF_RADIO = const(0x40001000)
BASE0 = const(0x51C)
PREFIX0 = const(0x524)
FREQUENCY = const(0x508)
PCNF1 = const(0x518)
PCNF0 = const(0x514)
MODE = const(0x510)
MODECNF0 = const(0x650)
CRCCNF = const(0x534)
PACKETPTR = const(0x504)
RXADDRESSES = const(0x530)
TXPOWER = const(0x50C)
TASKS_DISABLE = const(0x010)
STATE = const(0x550)
TASKS_TXEN = const(0)
TASKS_RXEN = const(0x004)
EVENTS_READY = const(0x100)
TASKS_START = const(0x008)
NRF_RADIO___BASE0 = const(NRF_RADIO + BASE0)
NRF_RADIO___PREFIX0 = const(NRF_RADIO + PREFIX0)
NRF_RADIO___FREQUENCY = const(NRF_RADIO + FREQUENCY)
NRF_RADIO___PCNF1 = const(NRF_RADIO + PCNF1)
NRF_RADIO___PCNF0 = const(NRF_RADIO + PCNF0)
NRF_RADIO___MODE = const(NRF_RADIO + MODE)
NRF_RADIO___MODECNF0 = const(NRF_RADIO + MODECNF0)
NRF_RADIO___CRCCNF = const(NRF_RADIO + CRCCNF)
NRF_RADIO___PACKETPTR = const(NRF_RADIO + PACKETPTR)
NRF_RADIO___RXADDRESSES = const(NRF_RADIO + RXADDRESSES)
NRF_RADIO___TXPOWER = const(NRF_RADIO + TXPOWER)
NRF_RADIO___TASKS_DISABLE = const(NRF_RADIO + TASKS_DISABLE)
NRF_RADIO___STATE = const(NRF_RADIO + STATE)
NRF_RADIO___TASKS_TXEN = const(NRF_RADIO + TASKS_TXEN)
NRF_RADIO___TASKS_RXEN = const(NRF_RADIO + TASKS_RXEN)
NRF_RADIO___EVENTS_READY = const(NRF_RADIO + EVENTS_READY)
NRF_RADIO___TASKS_START = const(NRF_RADIO + TASKS_START)

def initializeSerialOutput():
    print("Starting...")

def initializeHardware():  # enable the DCDC voltage regulator
    machine.mem32[NRF_POWER___DCDCEN] = 1  # NRF_POWER->DCDCEN=1;   
    
def initializeClocks():    # activate the high frequency crystal oscillator
    # NRF_CLOCK->TASKS_HFCLKSTART=1;  
    machine.mem32[NRF_CLOCK___TASKS_HFCLKSTART] = 1 
    # wait until high frequency clock start is confirmed
    # while (NRF_CLOCK->EVENTS_HFCLKSTARTED==0) {};  
    while (machine.mem32[NRF_CLOCK___EVENTS_HFCLKSTARTED] == 0):
        True
        
def initializeRadio():
    # print this node's address in hexadecimal
    print("My address is 0x{:02X}".format(my_prefixAddress) + "{:08X}".format(my_baseAddress))
          
    machine.mem32[NRF_RADIO___BASE0] = my_baseAddress
    machine.mem32[NRF_RADIO___PREFIX0] = my_prefixAddress
    
    # value must be between 0 and 100
    machine.mem32[NRF_RADIO___FREQUENCY] = 98  # 2498Mhz.  
    
    # base address is 4 bytes long (possible range is 2 to 4) and 
    # max size of payload is 1,and 1 bytes of static length payload
    machine.mem32[NRF_RADIO___PCNF1] = 0x40101
    # S0,LENGTH, and S1 are all zero bits long.
    machine.mem32[NRF_RADIO___PCNF0] = 0
    
    machine.mem32[NRF_RADIO___MODE] = 1  # set 2Mbps datarate.
    machine.mem32[NRF_RADIO___MODECNF0] = 1  # enable fast ramp-up of radio from DISABLED state.
    
    machine.mem32[NRF_RADIO___CRCCNF] = 3  # CRC will be 3 bytes and is computed including the address field
    machine.mem32[NRF_RADIO___PACKETPTR] = radioBuffer_address  # pointer to the payload in radioBuffer
    
    machine.mem32[NRF_RADIO___RXADDRESSES] = 1  # receive on logical address 0.  Not important for transmitting.
    machine.mem32[NRF_RADIO___TXPOWER] = 4  # set to 4db transmit power, which is the maximum. max for nRF52840 is 8db
    
    machine.mem32[NRF_RADIO___TASKS_DISABLE] = 1  # DISABLE the radio to establish a known state.
    while (machine.mem32[NRF_RADIO___STATE] != 0):  # wait until radio is DISABLED (i.e. STATE=0);
        True
        
    machine.mem32[NRF_RADIO___TASKS_RXEN] = 1  # turn on the radio receiver and shift into RXIDLE.
    while (machine.mem32[NRF_RADIO___EVENTS_READY] == 0):  # Busy-wait.  After event READY, radio shall be in state RXIDLE.
        True
        
    machine.mem32[NRF_RADIO___TASKS_START] = 1  # Move from RXIDLE mode into RX mode.

def start():
    # Main setup    
    initializeSerialOutput()
    initializeHardware()
    initializeClocks()
    initializeRadio() 

    # Main loop
    packetCounter = 0
    while (True):
        if (machine.mem32[NRF_RADIO___STATE] != 3):  # if radio no longer in RX state, then it must have received a packet
            packetCounter = packetCounter + 1
            payload = ((machine.mem32[radioBuffer_address]) % 256)
            print(packetCounter, "Payload received:", payload)
            machine.mem32[NRF_RADIO___TASKS_START] = 1  # Move from RXIDLE mode into RX mode to receive another packet
and here is the transmitter code:

Code: Select all

# This program for an nRF52840 sends a one byte payload in a 
# packet once every second.

from micropython import const
import machine  # so can peek and poke different registers on the nRF5x
import uctypes
import utime

radioBuffer = bytearray(100)  # allocate IO buffer for use by nRF5x radio
radioBuffer_address = uctypes.addressof(radioBuffer)

target_prefixAddress = const(0xAA)
target_baseAddress = const(0xDEADBEEF)

NRF_POWER = const(0x40000000)
DCDCEN = const(0x578)
NRF_POWER___DCDCEN = const(NRF_POWER + DCDCEN)

NRF_CLOCK = const(0x40000000)
TASKS_HFCLKSTART = const(0)
EVENTS_HFCLKSTARTED = const(0x100)
NRF_CLOCK___TASKS_HFCLKSTART = const(NRF_CLOCK + TASKS_HFCLKSTART)
NRF_CLOCK___EVENTS_HFCLKSTARTED = const(NRF_CLOCK + EVENTS_HFCLKSTARTED)

NRF_RADIO = const(0x40001000)
BASE0 = const(0x51C)
PREFIX0 = const(0x524)
FREQUENCY = const(0x508)
PCNF1 = const(0x518)
PCNF0 = const(0x514)
MODE = const(0x510)
MODECNF0 = const(0x650)
CRCCNF = const(0x534)
PACKETPTR = const(0x504)
RXADDRESSES = const(0x530)
TXPOWER = const(0x50C)
TASKS_DISABLE = const(0x010)
STATE = const(0x550)
TASKS_TXEN = const(0)
EVENTS_READY = const(0x100)
TASKS_START = const(0x008)
NRF_RADIO___BASE0 = const(NRF_RADIO + BASE0)
NRF_RADIO___PREFIX0 = const(NRF_RADIO + PREFIX0)
NRF_RADIO___FREQUENCY = const(NRF_RADIO + FREQUENCY)
NRF_RADIO___PCNF1 = const(NRF_RADIO + PCNF1)
NRF_RADIO___PCNF0 = const(NRF_RADIO + PCNF0)
NRF_RADIO___MODE = const(NRF_RADIO + MODE)
NRF_RADIO___MODECNF0 = const(NRF_RADIO + MODECNF0)
NRF_RADIO___CRCCNF = const(NRF_RADIO + CRCCNF)
NRF_RADIO___PACKETPTR = const(NRF_RADIO + PACKETPTR)
NRF_RADIO___RXADDRESSES = const(NRF_RADIO + RXADDRESSES)
NRF_RADIO___TXPOWER = const(NRF_RADIO + TXPOWER)
NRF_RADIO___TASKS_DISABLE = const(NRF_RADIO + TASKS_DISABLE)
NRF_RADIO___STATE = const(NRF_RADIO + STATE)
NRF_RADIO___TASKS_TXEN = const(NRF_RADIO + TASKS_TXEN)
NRF_RADIO___EVENTS_READY = const(NRF_RADIO + EVENTS_READY)
NRF_RADIO___TASKS_START = const(NRF_RADIO + TASKS_START)

def initializeSerialOutput():
    print("Starting...")

def initializeHardware():  # enable the DCDC voltage regulator
    machine.mem32[NRF_POWER___DCDCEN] = 1  # NRF_POWER->DCDCEN=1;   
    
def initializeClocks():    # activate the high frequency crystal oscillator
    # NRF_CLOCK->TASKS_HFCLKSTART=1;  
    machine.mem32[NRF_CLOCK___TASKS_HFCLKSTART] = 1 
    # wait until high frequency clock start is confirmed
    # while (NRF_CLOCK->EVENTS_HFCLKSTARTED==0) {};  
    while (machine.mem32[NRF_CLOCK___EVENTS_HFCLKSTARTED] == 0):
        True
        
def initializeRadio():
    # print target address in hexadecimal
    print("Target address is 0x{:02X}".format(target_prefixAddress)
          + "{:08X}".format(target_baseAddress))
          
    machine.mem32[NRF_RADIO___BASE0] = target_baseAddress
    machine.mem32[NRF_RADIO___PREFIX0] = target_prefixAddress
    
    # value must be between 0 and 100
    machine.mem32[NRF_RADIO___FREQUENCY] = 98  # 2498Mhz.  
    
    # base address is 4 bytes long (possible range is 2 to 4) and 
    # max size of payload is 1,and 1 bytes of static length payload
    machine.mem32[NRF_RADIO___PCNF1] = 0x40101
    # S0,LENGTH, and S1 are all zero bits long.
    machine.mem32[NRF_RADIO___PCNF0] = 0
    
    machine.mem32[NRF_RADIO___MODE] = 1  # set 2Mbps datarate.
    machine.mem32[NRF_RADIO___MODECNF0] = 1  # enable fast ramp-up of radio from DISABLED state.
    
    machine.mem32[NRF_RADIO___CRCCNF] = 3  # CRC will be 3 bytes and is computed including the address field
    machine.mem32[NRF_RADIO___PACKETPTR] = radioBuffer_address  # pointer to the payload in radioBuffer
    
    machine.mem32[NRF_RADIO___RXADDRESSES] = 1  # receive on logical address 0.  Not important for transmitting.
    machine.mem32[NRF_RADIO___TXPOWER] = 4  # set to 4db transmit power, which is the maximum. max for nRF52840 is 8db
    
    machine.mem32[NRF_RADIO___TASKS_DISABLE] = 1  # DISABLE the radio to establish a known state.
    while (machine.mem32[NRF_RADIO___STATE] != 0):  # wait until radio is DISABLED (i.e. STATE=0);
        True
        
    machine.mem32[NRF_RADIO___TASKS_TXEN] = 1  # turn on the radio transmitter and shift into TXIDLE.
    while (machine.mem32[NRF_RADIO___EVENTS_READY] == 0):  # Busy-wait.  After event READY, radio shall be in state TXIDLE.
        True
        
    machine.mem32[const(NRF_RADIO___TASKS_START)] = 1  # Move from TXIDLE mode into TX mode.

def start():
    # Main setup    
    initializeSerialOutput()
    initializeHardware()
    initializeClocks()
    initializeRadio() 

    # Main loop
    while (True):
        if (machine.mem32[NRF_RADIO___STATE] != 11):  # if radio no longer in TX state, then it must have sent a packet
            utime.sleep_ms(1000)  # wait one second before sending next packet
            machine.mem32[radioBuffer_address] = ((machine.mem32[radioBuffer_address] + 1) % 256)  # increment the payload value to send something different
            machine.mem32[NRF_RADIO___TASKS_START] = 1  # Move from TXIDLE mode into TX mode to transmit another packet
Load the code into a file named main.py. I used ampy to put the file onto the board (in my case I used an nRF52840-DK as the transmitter and an nRF52832-DK as the receiver). Then, reset the board, which loads the code. Then open a putty window (or similar serial terminal) to the board and type

Code: Select all

start()
at the ">>>" REPL prompt to execute the code. You could, of course, have it start itself, but then I found it difficult to regain control over the board if I did it that way (e.g. to upload a new program). This way you regain control by just resetting the board.

Presently the transmitter is sending packets faster than one a second (viewtopic.php?f=12&t=5417), but for demo purposes that doesn't matter. The receiver has no problem keeping up with it.

With this as a starting point, you can easily extend the code to do whatever you want.

Enjoy!
Last edited by WhiteHare on Mon Oct 22, 2018 5:21 am, edited 2 times in total.

User avatar
marfis
Posts: 186
Joined: Fri Oct 31, 2014 10:29 am
Location: Zurich / Switzerland

Re: [nRF2] Here is tested and working "proprietary" radio demo code

Post by marfis » Sun Oct 21, 2018 3:51 pm

that is great, thanks!

Do you mind sharing this as a git gist or even as a git repo so that scanning through the code and making comments is easier?

User avatar
WhiteHare
Posts: 129
Joined: Thu Oct 04, 2018 4:00 am

Re: [nRF2] Here is tested and working "proprietary" radio demo code

Post by WhiteHare » Sun Oct 21, 2018 4:35 pm

Note: you will need to fix the machine module as explained here by dhylands: viewtopic.php?f=12&t=5377
to get the code to work.

User avatar
WhiteHare
Posts: 129
Joined: Thu Oct 04, 2018 4:00 am

Re: [nRF2] Here is tested and working "proprietary" radio demo code

Post by WhiteHare » Sun Oct 21, 2018 5:03 pm

marfis wrote:
Sun Oct 21, 2018 3:51 pm
that is great, thanks!

Do you mind sharing this as a git gist or even as a git repo so that scanning through the code and making comments is easier?
Done: https://github.com/rabbithat/micropython_nRF52840

User avatar
marfis
Posts: 186
Joined: Fri Oct 31, 2014 10:29 am
Location: Zurich / Switzerland

Re: [nRF2] Here is tested and working "proprietary" radio demo code

Post by marfis » Mon Oct 22, 2018 4:46 am

I‘m wondering how the softdevice coorporate with this... do you include it in your flash output at all?

And just a small thing: please rephrase the title of this thread so it reads correctly („nRF52“) - makes it a lot easier to search.

User avatar
WhiteHare
Posts: 129
Joined: Thu Oct 04, 2018 4:00 am

Re: [nRF52] Here is tested and working "proprietary" radio demo code

Post by WhiteHare » Mon Oct 22, 2018 5:23 am

It doesn't rely upon nor use softdevice.

kak
Posts: 22
Joined: Fri Oct 05, 2018 11:35 am

Re: [nRF52] Here is tested and working "proprietary" radio demo code

Post by kak » Mon Oct 22, 2018 5:32 pm

Great.
This is the Enhanced Shockburst protocol, I assume?

Now we only have to port MySensors to MicroPython.
Piece of cake :D

User avatar
WhiteHare
Posts: 129
Joined: Thu Oct 04, 2018 4:00 am

Re: [nRF52] Here is tested and working "proprietary" radio demo code

Post by WhiteHare » Mon Oct 22, 2018 7:43 pm

Not sure. I'm not personally all that interested in backward comparability, so I'll leave that answer for those that are.

I've now got code working where I can transmit separate individual strings of up to 255 characters in length. As many as I want to.

My goal in all this is doing OTA micropython code updates, and I think a first pass at that will be happening soon. Since all the code is written in micropython, once I get that working it means I'll even be able to update the radio code OTA as well, which I think is pretty cool. 8-)

User avatar
WhiteHare
Posts: 129
Joined: Thu Oct 04, 2018 4:00 am

Re: [nRF52] Here is tested and working "proprietary" radio demo code

Post by WhiteHare » Tue Oct 23, 2018 12:04 am

I created a new, separate repository for the string sending/receiving code: https://github.com/rabbithat/Micropython_nRF52840_send

I've tested it, and it works.

User avatar
marfis
Posts: 186
Joined: Fri Oct 31, 2014 10:29 am
Location: Zurich / Switzerland

Re: [nRF52] Here is tested and working "proprietary" radio demo code

Post by marfis » Tue Oct 23, 2018 4:16 am

It doesn't rely upon nor use softdevice.
What I meant to ask was that *if* the softdevice is included (in case you want to use standard Btle functionality too), doesn‘t it interfere with your code (because you are directly manipulating hw registers)?

Post Reply