STM32
直播中

风来吴山

8年用户 1454经验值
擅长:电源/新能源
私信 关注
[问答]

怎样在openmv上编写nrf24l01的发送代码Python呢

nrf24l01是如何完成通信的?
怎样在openmv上编写nrf24l01的发送代码Python呢?

回帖(1)

张兰英

2021-12-17 15:27:00
一、
无线通信模块nrf24l01采用2.4G技术,同样,蓝牙和wifi模块也是采用的2.4G技术,只是后者在技术的基础之上做了扩展,封装更高,那么我们在做通信的时候,如果只是单纯想完成两个设备之间的通信,我的建议是使用nrf24l01模块*2。之前做过蓝牙模块之间的通信,其优点在于有指定的指令集,集成度高,操作起来十分方便,但是传输速度不快,质量不可靠,实时性不高,所以需要nrf24l01。
  二、
在openmv上编写nrf24l01的发送代码,Python。
openmv是用于做图像处理的一个开源项目,将摄像头和一块stm32芯片集成在一起,通过python语言来完成对单片机的控制,以及调用内部的库函数来完成图像处理的部分内容。
首先得知道nrf24l01是如何完成通信的。
1、nrf24l01需要与单片机正常通信,这是通过spi总线来完成的,四根信号线,mosi、miso、sck、cs,注意,并不是nrf24l01上的mosi与单片机的miso连接,这里我在连线的时候是有过犹豫的,之后通过实践证明以及百度,证实了mosi与mosi相连,miso与miso相连。
nrf24l01与单片机是通过spi串行通信,但具体如何完成通信,这涉及到spi协议,网上资料还是很多的,在此是调用了openmv的库内的spi相关函数。调用方法点击跳转:

  在此我想说的是,即使调用了spi的库,仍然被一个地方卡住了,正确的使用是这样的:

CS.value(0)
NRF_SPI.send_recv(buff, buff, timeout=500)
CS.value(1)
可以看到,需要经过一次片选的完成才能正确的完成数据的通信,而不是一直片选中该模块就可以完成通信的。
  2、nrf24l01的通信,上面说到,四根信号线用在了spi上,还有两个CE、IRQ来完成模块的自身需求,通过配置模块内部的寄存器来完成数据的发送,此处应有代码。
(如需详细了解模块内部的寄存器可以仔细阅读数据手册,那上面说的很清楚,另外,此处只将nrf24l01作为发送端,接收端同理)
  def nrf_writereg(reg, dat): # NRF24L01+写寄存器

  def nrf_readreg(reg):

  def nrf_writebuf(reg, pBuf, datalen): #pBuf为TX的地址

上面这三个函数是对nrf24l01的寄存器进行的基本操作,下面上全部的代码,表示在我的设备上已正常测试通过

import sensor, image, time
import pyb
from pyb import Pin, SPI, ExtInt


# 用户配置 发送和 接收地址,频道
TX_ADDRESS = (0x34, 0x43, 0x10, 0x10, 0x01)   # 定义一个静态发送地址
RX_ADDRESS = (0x34, 0x43, 0x10, 0x10, 0x01)
CHANAL    =      40                              #频道选择
buff = bytearray(2)


def callback(line): #中断服务函数
    state = nrf_readreg(FIFO_STATUS) #读取FIFO_STATUS寄存器的值,正常为17
    #print("FIFO_STATUS = ", state)
    state = nrf_readreg(STATUS)     #读取status寄存器的值,state为发送状态,数值46: 正常发送完成, 30:重发超过次数
    #print("state = ", state)
    nrf_writereg(NRF_WRITE_REG + STATUS, state)  #清除中断标志
    global nrf_irq_tx_flag
    #if(state & RX_DR)  #接收到数据
        #不会接受到数据的,忽略该种情况
    if(state & TX_DS):  #发送完数据
        nrf_irq_tx_flag = 0
        nrf_writereg(FLUSH_TX, NOP)    #清除TX FIFO寄存器
        print("nTX_DS")
    if(state & MAX_RT):      #发送超时,达到最多重发次数标志位
        nrf_irq_tx_flag = 0                            #标记发送失败
        nrf_writereg(FLUSH_TX, NOP)                    #清除TX FIFO寄存器
        #有可能是 对方也处于 发送状态
        #放弃本次发送
        print("nMAX_RT")
    if(state & TX_FULL):    #TX FIFO 满
        print("nTX_FULL")


# 配置spi协议端口,波特率12500*1000
nrf_tx_buff = 'H+!0'
NRF_SPI = SPI(2)
# 配置nrf的CE,io输出模式,初始电平为0
CE = pyb.Pin(pyb.Pin.board.P4, pyb.Pin.OUT)
# 配置IRQ为下降沿触发中断,handler为回调函数(中断服务函数)
NRF_IRQ = pyb.ExtInt(pyb.Pin.board.P5, pyb.ExtInt.IRQ_FALLING, pyb.Pin.PULL_UP, callback)
NRF_IRQ.enable()
# CS
CS = pyb.Pin(pyb.Pin.board.P3, pyb.Pin.OUT)


DATA_PACKET        =     32      #一次传输最大可支持的字节数(1~32)
RX_FIFO_PACKET_NUM =     80      #接收 FIFO 的 包 数目 ( 总空间 必须要大于 一副图像的大小,否则 没法接收完 )
ADR_WIDTH          =     5       #定义地址长度(3~5)
IS_CRC16           =     1       #1表示使用 CRC16,0表示 使用CRC8 (0~1)
nrf_irq_tx_flag    =     0


# 内部配置参量
TX_ADR_WIDTH  =  ADR_WIDTH       #发射地址宽度
TX_PLOAD_WIDTH = DATA_PACKET     #发射数据通道有效数据宽度0~32Byte


RX_ADR_WIDTH  =  ADR_WIDTH       #接收地址宽度
RX_PLOAD_WIDTH=  DATA_PACKET     #接收数据通道有效数据宽度0~32Byte


# /******************************** NRF24L01+ 寄存器命令 宏定义***************************************/
# SPI(nRF24L01) commands , NRF的SPI命令宏定义,详见NRF功能使用文档
NRF_READ_REG  =  0x00    # Define read command to register
NRF_WRITE_REG =  0x20    # Define write command to register
RD_RX_PLOAD  =   0x61    # Define RX payload register address
WR_TX_PLOAD  =   0xA0    # Define TX payload register address
FLUSH_TX     =   0xE1    # Define flush TX register command
FLUSH_RX     =   0xE2    # Define flush RX register command
REUSE_TX_PL  =   0xE3    # Define reuse TX payload register command
NOP          =   0xFF    # Define No Operation, might be used to read status register


# SPI(nRF24L01) registers(addresses) ,NRF24L01 相关寄存器地址的宏定义
CONFIG   =   0x00        # 'Config' register address
EN_AA    =   0x01        # 'Enable Auto Acknowledgment' register address
EN_RXADDR =  0x02        # 'Enabled RX addresses' register address
SETUP_AW  =  0x03        # 'Setup address width' register address
SETUP_RETR=  0x04        # 'Setup Auto. Retrans' register address
RF_CH     =  0x05        # 'RF channel' register address
RF_SETUP  =  0x06        # 'RF setup' register address
STATUS    =  0x07        # 'Status' register address
OBSERVE_TX=  0x08        # 'Observe TX' register address
CD        =  0x09        # 'Carrier Detect' register address
RX_ADDR_P0=  0x0A        # 'RX address pipe0' register address
RX_ADDR_P1=  0x0B        # 'RX address pipe1' register address
RX_ADDR_P2=  0x0C        # 'RX address pipe2' register address
RX_ADDR_P3=  0x0D        # 'RX address pipe3' register address
RX_ADDR_P4=  0x0E        # 'RX address pipe4' register address
RX_ADDR_P5=  0x0F        # 'RX address pipe5' register address
TX_ADDR   =  0x10        # 'TX address' register address
RX_PW_P0  =  0x11        # 'RX payload width, pipe0' register address
RX_PW_P1  =  0x12        # 'RX payload width, pipe1' register address
RX_PW_P2  =  0x13        # 'RX payload width, pipe2' register address
RX_PW_P3  =  0x14        # 'RX payload width, pipe3' register address
RX_PW_P4  =  0x15        # 'RX payload width, pipe4' register address
RX_PW_P5  =  0x16        # 'RX payload width, pipe5' register address
FIFO_STATUS= 0x17        # 'FIFO Status Register' register address




#几个重要的状态标记
TX_FULL   =  0x01        #TX FIFO 寄存器满标志。 1 为 满,0为 不满
MAX_RT    =  0x10        #达到最大重发次数中断标志位
TX_DS     =  0x20        #发送完成中断标志位
RX_DR     =  0x40        #接收到数据中断标志位


def nrf_writereg(reg, dat):     # NRF24L01+写寄存器
    buff[0] = reg          #先发送寄存器
    buff[1] = dat          #再发送数据
    CS.value(0)
    NRF_SPI.send_recv(buff, buff, timeout=500)
    CS.value(1)
    return buff[0]


def nrf_readreg(reg):
    buff[0] = reg         #先发送寄存器
    buff[1] = 0
    CS.value(0)
    NRF_SPI.send_recv(buff, buff, timeout=500)
    CS.value(1)
    return buff[1]  #返回读取的值


def nrf_writebuf(reg, pBuf, datalen):    #pBuf为TX的地址
    buff = bytearray(datalen+1)
    buff[0] = reg
    ctr = 1
    for i in pBuf:
        buff[ctr] = i
        ctr += 1
    CS.value(0)
    NRF_SPI.send_recv(buff, buff, timeout=500)
    CS.value(1)
    return reg    #返回NRF24L01的状态


def nrf_link_check():           #检测NRF24L01+与MCU是否正常连接
    NRF_CHECH_DATA = 0xd2       #此值为校验数据时使用,可修改为其他值


    buff = bytearray(6)
    buff[0] = NRF_WRITE_REG + TX_ADDR
    buff[1] = NRF_CHECH_DATA
    buff[2] = NRF_CHECH_DATA
    buff[3] = NRF_CHECH_DATA
    buff[4] = NRF_CHECH_DATA
    buff[5] = NRF_CHECH_DATA
    CS.value(0)
    NRF_SPI.send_recv(buff, buff)
    CS.value(1)


    buff[0] = TX_ADDR
    CS.value(0)
    NRF_SPI.send_recv(buff, buff)
    CS.value(1)


    #比较
    for i in (buff[1], buff[2], buff[3], buff[4], buff[5]):
        if i != NRF_CHECH_DATA:
            return 0        #MCU与NRF不正常连接
    return 1                #MCU与NRF成功连接


def nrf_init():
    ## 配置nrf的寄存器
    # CE置低,即将开始配置
    NRF_SPI.init(SPI.MASTER, baudrate=12500000,polarity=0, phase=0, bits=8, firstbit=SPI.MSB, ti=False, crc=None)
    CE.value(0)
    nrf_writereg(NRF_WRITE_REG + SETUP_AW, ADR_WIDTH - 2)          #设置地址长度为 TX_ADR_WIDTH
    nrf_writereg(NRF_WRITE_REG + RF_CH, CHANAL)                    #设置RF通道为CHANAL
    nrf_writereg(NRF_WRITE_REG + RF_SETUP, 0x0f)                   #设置TX发射参数,0db增益,2Mbps,低噪声增益开启
    nrf_writereg(NRF_WRITE_REG + EN_AA, 0x01)                      #使能通道0的自动应答
    nrf_writereg(NRF_WRITE_REG + EN_RXADDR, 0x01)                  #使能通道0的接收地址
    #RX模式配置
    #nrf_writebuf(NRF_WRITE_REG + RX_ADDR_P0, RX_ADDRESS, RX_ADR_WIDTH)           #写RX节点地址
    nrf_writereg(NRF_WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH)         #选择通道0的有效数据宽度
    #nrf_writereg(NRF_WRITE_REG + CONFIG, 0x0B | (IS_CRC16 << 2));       #配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式


    #TX模式配置
    nrf_writebuf(NRF_WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH)           #写TX节点地址
    nrf_writereg(NRF_WRITE_REG + SETUP_RETR, 0x00)                 #设置自动重发间隔时间:250us + 86us;最大自动重发次数:15次
    nrf_writereg(FLUSH_TX, NOP)                                    #清除TX FIFO寄存器
    # CE置高,配置完成
    CE.value(1)
    time.sleep(1)
    return nrf_link_check()


def nrf_tx(txbuf, datalen):     #NRF24L01+数据发送
    if(txbuf == None or datalen == 0):
        return 0
    global nrf_irq_tx_flag
    if(nrf_irq_tx_flag == 0):   #上一包发送完之后才能发送下一个包
        #默认每次发送一个32byte的包
        nrf_irq_tx_flag = 1
        #需要 先发送一次数据包后才能 中断发送
        CE.value(0)    #ce为低,进入待机模式1
        nrf_writebuf(NRF_WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH)           #写TX节点地址
        nrf_writebuf(NRF_WRITE_REG + RX_ADDR_P0, RX_ADDRESS, RX_ADR_WIDTH) #设置RX节点地址 ,主要为了使能ACK!!!
        nrf_writereg(NRF_WRITE_REG + CONFIG, 0x0A | (IS_CRC16 << 2)) #配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,发射模式,开启所有中断
        nrf_writebuf(WR_TX_PLOAD, txbuf, datalen)   #写数据到TX BUF 最大 32个字节
        CE.value(1)   #CE为高,txbuf非空,发送数据包
        i = 0x0fff
        while(i != 0):
            i -= 1
        #print(nrf_readreg(STATUS))
        return 1
    else:
        return 0


while(nrf_init() == 0):
    print('nrf not link openmv')
print('nrf link openmv')
print(NRF_SPI)
while(True):
    time.sleep(1000)
    nrf_tx(nrf_tx_buff.encode('utf-8'), DATA_PACKET)
三、头疼的坑,勿入
1、需要先测试nrf24l01与单片机是否通过spi正常通信(串行),这里有个坑1之前提到过就是片选CS的问题
2、坑2:引脚irq触发中断,其中中断服务函数必须带一个参数line,即使函数里并没用到这个参数,也不用传入参数,但必须这么申明,即def callback(line):
3、坑3:中断服务函数里面需要清除nrf的状态标志寄存器state,清除的方法:从state读到什么状态就往寄存器写什么状态即完成清除。
nrf_writereg(NRF_WRITE_REG + STATUS, state) #清除中断标志
4、坑4:在写入需要发送的数据之前,之前,之前,必须再次写TX、RX节点地址,虽然在初始化里面已经做过这项工作了,目的是:主要为了使能ACK!!!
  四、
调试告一段落,过程中,虽多次心态崩了,但还是有收获的。
上面的代码是最大众化的,有了上面的基础,到后来的发送模式,多通道通信也就不在话下了,已测试通过,在此就不在赘述了。
–end
举报

更多回帖

发帖
×
20
完善资料,
赚取积分