精华内容
下载资源
问答
  • 数据传输类型与通信方式

    千次阅读 2021-06-01 16:20:29
    数据在计算机中是以离散的二进制数字信号表示,但是在数据通信过程中,它是以数字信号方式还是以模拟信号方式表示,主要取决于选用的通信信道所允许传输的信号类型。 如果通信信道不允许直接传输计算机所产生的数字...

    数据传输类型

    数据在计算机中是以离散的二进制数字信号表示,但是在数据通信过程中,它是以数字信号方式还是以模拟信号方式表示,主要取决于选用的通信信道所允许传输的信号类型。

    如果通信信道不允许直接传输计算机所产生的数字信号,则需要在发送端将数字信号变换成模拟信号,在接收端再将模拟信号还原成数字信号,这个过程称为调制解调。

    如果通信信道允许直接传输计算机所产生的数字信号,为了很好地解决收发双方的同步及具体实现中的技术问题,有时也需要将数字信号进行适当的波形变换。

    因此数据传输类型可分为模拟通信、数字通信和数据通信。

    如果信源产生的是模拟数据并以模拟信号传输,则称为模拟通信;如果信源产生的是模拟数据但以数字信号形式传输,则称为数字通信;如果信源产生的是数字数据,则既可以采用模拟信号传输,也可采有数字信号传输,一般称为数据通信。

    在这里插入图片描述

    数据通信方式

    数据通信方式从不同的角度可以有不同的划分方式。

    按照数据传输方向与时间的关系可分为单工通信、半双工通信和全双工通信;

    按照数据传输中采用的同步技术可分为同步传输和异步传输;

    按照数据通信中使用的信道数量可分为串行通信和并行通信;

    按照传输信号是否调制可分为基带传输和频带传输。

    (1)单工通信与双工通信

    根据数据通信双方是否能实现双向通信及如何实现双向通信,数据通信可以分为单工通信、半双工通信与全双工通信。

    单工通信是只支持数据在一个方向上传输,在任何时候都不能改变信号传送方向的通信,又称为单向通信。如无线电广播和电视广播都是单工通信。

    半双工通信是允许数据在两个方向上传输,但在同一时刻,只允许数据在一个方向上传输的通信。即数据可以双向传送,但是必须是交替进行,它实际上是一种可切换方向的单工通信。这种方式一般用于计算机网络的非主干线路中。

    全双工通信是允许数据同时在两个方向上传输的通信,又称为双向同时通信,即通信的双方可以同时发送和接收数据。

    (2)异步传输与同步传输

    在数据通信过程中,通信双方要正确地交换数据,必须保持协同工作。数据接收方要根据发送方所发送的每个码元(比特)的起止时刻和传输速率进行数据的接收,否则,收发双方之间就会产生不一致。

    即使开始时刻单个码元(比特)的收发偏差非常小,随着时间的不断累积,收发偏差也会不断增大,直到出现数据接收错误。

    为此,要保持数据发送方和数据接收方的同步。

    同步是指要求通信的收发双方在时间基准上保持一致。

    数据通信的同步技术包括字符同步(Character Synchronous)和位同步(Bit Synchronous)。

    按照数据通信中采用的同步技术不同可分为异步传输(Asynchronous Transmission)和同步传输(Synchronous Transmission)。

    异步传输是字符同步方式,又称起止式同步。

    在异步传输中,在每个要传送的字符码(7或8位)前面,都要加上一个字符起始始位,用以表示字符码的开始,在字符码或字符校验码之后加上1个、1.5个或2个终止位,用以表示该字符的结束。接收方根据起始位和终止位判断每个字符的开始和结束,从而保证通信双方的同步。在这种同步方式下,即使发送方和接收方的时钟有微小偏差,但由于每次都重新检测字符起始位的开始,而且每个字符码的位数较短,因此不会产生大的时钟误差累积,从而保证了半符码发送和接收的同步性。异步传输方式实现比较容易,但是它在每个字符码前后添加了起始位和终止位,其数据传输效率较低。

    同步传输是一次传输若干个字符码或若干个二进制位组成的数据块。在该数据块发送之前,先发送一个同步字符SYN(01101000)或一个同步字节(01111110)。接收方检测该同步字符或同步字节,做好接收数据的准备。在同步字符或同步字节之后,可以连续发送任意长的字符或数据块,在数据发送完成后,再利用同步字符或同步字节告知接收方整个发送过程结束。

    (3)串行通信与并行通信

    按照通信过程中所占用的信道数量,数据通信可以分为串行通信与并行通信。

    串行通信是指构成字符的二进制代码在一条信道上以位(码元)为单位,按时间顺序逐位传输的方式。按位发送,逐位接收,同时需要确认还原字符,因此需采取同步措施以保证收发双方同步。串行通信速度虽慢,但只需一条传输信道,投资小,易于实现,是数据传输采用的主要传输方式,也是计算机通信采取的一种主要方式。

    并行通信是指数据以成组的方式,在多条并行信道上同时进行传输。常用的就是将一个字符代码的若干位二进制码,分别在几个并行信道上同时传输。例如,采用8位代码的字符,可以用8个信道并行传输,一次传送一个字符,因此收发双方不存在字符的同步问题,不需要加“起”“止”信号或者其他信号来实现收发双方的字符同步,这是并行传输的一个主要优点。但是,并行通信必须有并行信道,这带来了设备上或实施条件的限制。

    (4)基带传输与频带传输

    按照传输信号是否调制可分为基带传输和频带传输。

    基带传输是指数字信号不加任何改变直接在信道中进行传输的过程。

    数字信号是用高、低电平表示比特“0”和比特“1”的矩形脉冲信号。

    这种矩形脉冲信号所固有的频带称为基本频带(基带),因此数字信号也称为数字基带信号。数字基带信号没有经过调制,它所占据的频带一般是从直流或低频开始。通常,发送端在进行基带传输之前,需要对信源发送的数字信号进行编码;在接收端,对接收到的数字信号进行解码,以恢复原始数据。基带传输实现简单、成本低,得到了广泛应用。

    频带传输是指数字信号经过调制后在信道中传输的过程。

    信号调制的目的是使信号能够更好地适应传输通道的频率特性,以减少信号失真;另外,数字信号经调制处理后能够克服基带信号占用频带过宽的缺点,从而提高线路的利用率。在接收端,需要使用专门的解调设备对调制后的信号进行解调。频带传输中最典型的设备是调制解调器。

    计算机网络中主要采用全双工通信、串行通信、异步传输,局域网中采用基带传输,远距离传输一般采用频带传输。

    数据通信的主要性能指标

    影响数据通信性能的因素有很多,其性能指标主要有以下几个方面。
    ● 有效性:指消息的传输速度。有效性指标是衡量系统传输能力的主要指标,通常从数据传输速率、带宽、吞吐量、时延、往返时间等方面来考虑。
    ● 可靠性:指消息的传输质量。
    ● 适应性:指环境使用条件。
    ● 标准性:指元件的标准性、互换性。
    ● 经济性:指成本的高低。
    ● 使用维修:指是否方便。

    展开全文
  • 数据传输技术

    千次阅读 2018-12-09 16:21:06
    数据在通信信道上的各种传输方式及其所采用的技术。

    紫色代表一级目录
    粉红代表二级目录
    蓝色代表三级目录
    红色代表关键字
    橙色代表说明

    数据通信系统的基本结构
      在这里插入图片描述
    数据传输方式
      模拟信号发送(模拟信道)
      在这里插入图片描述
      数字信号发送(数字信道)
      在这里插入图片描述
        数字信号发送的特点
          抗干扰能力强
          可实现高质量的远距离通信
          能适应各种通信业务
          高保密通信
          高保密通信
          通信设备的集成化和微型化(比之模拟传输设备便宜)
        数字信号发送的缺点
          占用的频带宽
    数据编码技术
      数字数据的数字传输(基带传输)
        基带:基本频带,指传输变换前所占用的频带,是原始信号所固有的频带。
        基带传输:在传输时直接使用基带信号。
          基带传输是一种最简单最基本的传输方式,一般用低电平表示“0”,高电平表示“1”。
          适用范围:低速和高速的各种情况。
          限制:因基带信号所占的频率成分很宽,所以对传输线路有一定的要求。
      基带传输常用的几种编码方式
        不归零制码(NRZ:Non-Return to Zero)
          原理:用两种不同的电平分别表示二进制信息“0”和“1”,低电平表示“0”,高电平表示“1”。
        曼彻斯特码(Manchester)
          原理:每一位中间都有一个跳变,从低跳到高表示“0”,从高跳到低表示“1”。
        差分曼彻斯特码(Differential Manchester)
          原理:每一位中间都有一个跳变,每位开始时有跳变表示“0”,无跳变表示“1”。位中间跳变表示时钟,位前跳变表示数据。
        在这里插入图片描述
      数字数据的模拟传输(频带传输)
        频带传输:指在一定频率范围内的线路上,进行载波传输,称为频带传输。用基带信号对载波进行调制,使其变为适合于线路传送的信号。
        调制(Modulation):基带脉冲对载波信号的某些参量进行控制,使这些参量随基带脉冲变化。
        解调(Demodulation):调制的反变换。
        调制解调器MODEM(modulation-demodulation)
        根据载波在这里插入图片描述的三个特性:幅度、频率、相位,产生常用的三种调制技术
          幅移键控法 Amplitude-shift keying (ASK)
          在这里插入图片描述
          A(t) 取不同的值表示不同的信息码。
          频移键控法 Frequency-shift keying (FSK)
          在这里插入图片描述
          在这里插入图片描述 取不同的值表示不同的信息码。
          相移键控法 Phase-shift keying (PSK)
          在这里插入图片描述
          在这里插入图片描述取不同的值表示不同的信息码。
          在这里插入图片描述
      数据编码技术(脉冲代码调制)
        解决模拟信号数字化问题,也称为脉冲代码调制PCM(Pulse Code Modulation)。具体过程分为采样、量化、编码
        采样:以高于两倍最高有效信号频率的速率对信号进行采样的话,那么,这些采样值就包含了原始信号的全部信息。
        量化:采样后,需用二进制代码序列来表示模拟采样值,该数字化过程称为量化。
        编码:量化可分为2n个等级,每个采样值近似地用n位二进制数来表示,这一过程称为编码。
    多路复用技术
      频分复用 FDM
      波分复用 WDM
      时分复用 TDM
      码分复用 CDM

    通信线路的通信方式
      连接方式
        点 — 点方式
        多点方式
      通信方式
        单工通信方式
          信息只能单向传输,监视信号可回送。
          在这里插入图片描述
        半双工通信方式
          信息可以双向传输,但在某一时刻只能单向传输。
          在这里插入图片描述
        全双工通信方式
          信息可以同时双向传输,一般采用四线式结构。
          在这里插入图片描述
    数据同步方式
      目的:接收方必须知道每一位信号的开始及其宽度,以便正确的采样接收(位同步),同时要求对各个字符正确识别(字符同步),以及对各个数据包正确识别(数据包同步)。
      位同步
        外同步法
          接收端的时钟定时信号是由发送端送过来的。
          发送端在发送数据之前,先向接收端发出一串同步的时钟。
          接收端:首先校准同步,然后用校准后产生的同步信号作为定时信号接收数据信息。
        自同步法
          从数据信息波形的本身提取时钟信号。(比如曼切斯特编码
      常用的实现同步的两种技术
        异步通信
        同步通信-基于字符
        基于位的同步通信

    展开全文
  • 一文教你学会使用NRF24L01,了解NRF24L01,nRF24L01实现不定长数据串口透传(串口无线通信),一块板子的串口A可以和另一块板子的串口B通信,让你更深入STM32的学习...后续还可能会推出nRF24L01用DMA方式实现传输......

    nRF24L01配置

    写在前,如果你认真一步一步跟着本文走,相信你会清楚nRF24L01的使用,会有想不到的收获,看十篇相关文章,不如认认真真读本文!
    本文介绍如何使用nRF24L01实现不定长数据串口透传,换句话讲就是学习如何配置使用nRF24L01。STM32使用nRF24L01的文章很多,但要找到符合我心意的太难了!我要实现的是串口A收到的数据通过nRF24L01传输,由另一块板子的串口B输出。当然这很简单,但是我想要结合串口DMA和SPI DMA。功能是实现了,不过本文先讲讲最基本的nRF24L01的使用。如果本文写得好,可以参与评论或点个赞,希望大家多多支持,谢谢!说说本文关键词吧,为你节省时间!

    测试平台:STM32F103C8T6
    库版本:官方库3.5版本
    关键词:不定长数据 (定长也只是其中一种);双向传输;串口透传;中断方式
    代码下载地址: 基于NRF24L01实现串口透传(不定长数据无线串口双向通信).zip (点击链接跳转)
                            (https://download.csdn.net/download/weixin_44524484/12345526)

    一、引脚说明

    序号引脚功能
    1GND接地(0V)
    2VCC电源(1.9V~3.6V),超过3.6V将会烧毁模块。推荐电压3.3V左右。
    3CE芯片的模式控制线。在 CSN 为低的情况下,CE 协同NRF24L01 的CONFIG 寄存器共同决定NRF24L01 的状态
    4CSN芯片(SPI)的片选线,CSN 为低电平芯片工作
    5SCK为芯片控制的时钟线(SPI时钟)
    6MOSISPI控制数据线,主输出 、从输入
    7MISOSPI控制数据线,主输入 、从输出
    8IRQ中断信号引脚,中断时变为低电平。引脚会在以下三种情况变低:Tx FIFO 发完数据并且收到ACK(使能ACK情况下)、Rx FIFO 收到数据达到最大重发次数

    二、工作方式

    nRF24L01工作模式由芯片的模式控制线CE和配置寄存器CONFIG内部PWR_UP、PRIM_RX位共同控制。

    NRF24L01所处模式PWR_UP位状态PRIM_RX位状态CE引脚电平FIFO 寄存器状态
    接收模式111-
    发送模式101数据在TX FIFO 寄存器中
    发送模式101→0停留在发送模式,直至数据发送完
    待机模式II101TX FIFO 为空
    待机模式I1-0无数据传输
    掉电模式0---

    三、相关寄存器介绍

    1、配置寄存器(配置NRF24L01一些工作方式)

    寄存器地址:0x00       名称:CONFIG(配置寄存器)

    第7位第6位(MASK_RX_DR)第5位(MASK_TX_DS)第4位(MASK_MAX_RT)第3位(EN_CRC)第2位(CRCO)第1位(PWR_UP)第0位(PRIM_RX)
    保留(未用)可屏蔽中断RX_RD。‘1’:IRQ 引脚不显示RX_RD中断;‘0’:RX_RD 中断产生时IRQ 引脚电平为低可屏蔽中断TX_DS。‘1’:IRQ 引脚不显示TX_DS 中断;‘0’:TX_DS 中断产生时IRQ 引脚电平为低可屏蔽中断MAX_RT。‘1’:IRQ 引脚不显示TX_DS 中断;‘0’:MAX_RT 中断产生时IRQ 引脚电平为低CRC使能。如果EN_AA 中任意一位为高则EN_CRC 强迫为高。CRC模式。‘0’:8 位CRC 校验;‘1’:16 位CRC 校验NRF24L01上电掉电模式设置位。‘1’:上电;‘0’:掉电NRF24L01接收、发射模式设置位。‘1’:接收模式;;‘0’:发射模式

    2、状态寄存器(反应NRF24L01当前工作状态)

    寄存器地址:0x07       名称:STATUS(状态寄存器)

    第7位第6位(RX_DR)第5位(TX_DS)第4位(MAX_RT)第3~1位(RX_P_NO)第0位(TX_FULL)
    保留(未用)接收数据中断位。当接收到有效数据后置 ‘1’ 。 写 ‘1’ 清除中断数据发送完成中断。当数据发送完成后产生中断。如果工作在自动应答模式下,只有当接收到应答信号后此位置 ‘1’ 。写 ‘1’ 清除中断达到最多次重发中断。写 ‘1’ 清除中断。如果MAX_RT中断产生则必须清除后系统才能进行通讯接收数据通道号 位。‘000-101’:数据通道号、110:未使用、111:RX FIFO 寄存器为空TX FIFO 寄存器满标志。 ‘1’ :TX FIFO 寄存器满。‘0’ :TX FIFO 寄存器未满,有可用空间。

    3、NRF24L01寄存器操作命令及地址(宏定义)

    动态数据的三条重要宏定义:
    #define R_RX_PL_WID 0x60 //读取接收数据长度
    #define DYNPD 0x1C //动态长度地址;bit7~ 6,只允许’00’;bit5~ 0,分别对应使能动态有效数据长度数据通道5 ~ 0
    #define FEATURE 0x1D //功能寄存器地址;bit7~ 3,只允许’0000’;bit2,使能动态有效数据长度;bit1,使能ACK有效数据;bit0,使能’W_TX_PAYLOAD_NOACK’命令

    //NRF24L01寄存器操作命令
    #define NRF_READ_REG    0x00  //读配置寄存器,低5位为寄存器地址
    #define NRF_WRITE_REG   0x20  //写配置寄存器,低5位为寄存器地址
    #define R_RX_PL_WID     0x60  //读取接收数据长度
    #define RD_RX_PLOAD     0x61  //读RX有效数据,1~32字节
    #define WR_TX_PLOAD     0xA0  //写TX有效数据,1~32字节
    #define FLUSH_TX        0xE1  //清除TX FIFO寄存器.发射模式下用
    #define FLUSH_RX        0xE2  //清除RX FIFO寄存器.接收模式下用
    #define REUSE_TX_PL     0xE3  //重新使用上一包数据,CE为高,数据包被不断发送.
    #define NOP             0xFF  //空操作,可以用来读状态寄存器
    
    //SPI(NRF24L01)寄存器地址
    #define CONFIG          0x00  //配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选择;bit2:CRC模式;bit3:CRC使能;
                                  //bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使能;bit6:中断RX_DR使能
    #define EN_AA           0x01  //使能自动应答功能  bit0~5,对应通道0~5
    #define EN_RXADDR       0x02  //接收地址允许,bit0~5,对应通道0~5
    #define SETUP_AW        0x03  //设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字节;
    #define SETUP_RETR      0x04  //建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时 250*x+86us
    #define RF_CH           0x05  //RF通道,bit6:0,工作通道频率;
    #define RF_SETUP        0x06  //RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功率;bit0:低噪声放大器增益
    #define STATUS          0x07  //状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,达到最多次重发
                                  //bit5:数据发送完成中断;bit6:接收数据中断;
    #define MAX_TX  		0x10  //达到最大发送次数中断
    #define TX_OK   		0x20  //TX发送完成中断
    #define RX_OK 		  	0x40  //接收到数据中断
    
    #define OBSERVE_TX      0x08  //发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
    #define CD              0x09  //载波检测寄存器,bit0,载波检测;
    #define RX_ADDR_P0      0x0A  //数据通道0接收地址,最大长度5个字节,低字节在前
    #define RX_ADDR_P1      0x0B  //数据通道1接收地址,最大长度5个字节,低字节在前
    #define RX_ADDR_P2      0x0C  //数据通道2接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
    #define RX_ADDR_P3      0x0D  //数据通道3接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
    #define RX_ADDR_P4      0x0E  //数据通道4接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
    #define RX_ADDR_P5      0x0F  //数据通道5接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
    #define TX_ADDR         0x10  //发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址相等
    #define RX_PW_P0        0x11  //接收数据通道0有效数据宽度(1~32字节),设置为0则非法
    #define RX_PW_P1        0x12  //接收数据通道1有效数据宽度(1~32字节),设置为0则非法
    #define RX_PW_P2        0x13  //接收数据通道2有效数据宽度(1~32字节),设置为0则非法
    #define RX_PW_P3        0x14  //接收数据通道3有效数据宽度(1~32字节),设置为0则非法
    #define RX_PW_P4        0x15  //接收数据通道4有效数据宽度(1~32字节),设置为0则非法
    #define RX_PW_P5        0x16  //接收数据通道5有效数据宽度(1~32字节),设置为0则非法
    #define NRF_FIFO_STATUS 0x17  //FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留
                                  //bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环;
    #define DYNPD        	0x1C  //动态长度地址;bit7~6,只允许'00';bit5~0,分别对应使能动态有效数据长度数据通道5~0
    #define FEATURE        	0x1D  //功能寄存器地址;bit7~3,只允许'0000';bit2,使能动态有效数据长度;
    							  //bit1,使能ACK有效数据;bit0,使能'W_TX_PAYLOAD_NOACK'命令
    

    相关的寄存器上述也有解释,使用到的寄存器我在后文也有讲解,如需了解可点击此处跳转查看

    四、配置步骤

    1、SPI配置

    nRF24L01使用的通信方式是SPI,所以先预配置SPI2,这里的SPI2配置仅仅预配置,时钟极性CPOL和时钟相位CPHA还未根据nRF24L01的时序图作修改

    //SPI2初始化
    void SPI2_Init(void)
    {
     	GPIO_InitTypeDef GPIO_InitStructure;
     	SPI_InitTypeDef  SPI_InitStructure;
    
    	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );					//PORTB时钟使能 
    	RCC_APB1PeriphClockCmd(	RCC_APB1Periph_SPI2,  ENABLE );					//SPI2时钟使能 	
     
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  						//PB13/14/15复用推挽输出 
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);									//初始化GPIOB
    
     	GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);  				//PB13/14/15上拉
    
    	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 		//设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
    	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;							//设置SPI工作模式:设置为主SPI
    	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;						//设置SPI的数据大小:SPI发送接收8位帧结构
    	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;								//串行同步时钟的空闲状态为高电平
    	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;							//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;								//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
    	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;	//定义波特率预分频的值:波特率预分频值为256
    	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;						//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
    	SPI_InitStructure.SPI_CRCPolynomial = 7;								//CRC值计算的多项式
    	SPI_Init(SPI2, &SPI_InitStructure);  									//根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
     
    	SPI_Cmd(SPI2, ENABLE); 													//使能SPI外设
    }
    

    为了方便调整SPI2的通信速度,故有以下SPI2速度设置函数:

    //SPI 速度设置函数
    //SpeedSet:
    //SPI_BaudRatePrescaler_2   2分频   
    //SPI_BaudRatePrescaler_8   8分频   
    //SPI_BaudRatePrescaler_16  16分频  
    //SPI_BaudRatePrescaler_256 256分频 
    void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
    {
    	assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
    	SPI2->CR1&=0XFFC7;
    	SPI2->CR1|=SPI_BaudRatePrescaler;	//设置SPI2速度 
    	SPI_Cmd(SPI2,ENABLE);
    } 
    

    SPI2的读写函数如下:

    //SPIx 读写一个字节
    //TxData:要写入的字节
    //返回值:读取到的字节
    u8 SPI2_ReadWriteByte(u8 TxData)
    {		
    	u8 retry=0;				 	
    	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
    	{
    		retry++;
    		if(retry>200)return 0;		//超时返回
    	}			  
    	SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
    	retry=0;
    
    	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
    	{
    		retry++;
    		if(retry>200) return 0;		  //超时返回
    	}	  						    
    	return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据					    
    }
    

    至此,SPI2预配置完毕!但要使STM32F103与nRF24L01通信还需修改配置SPI2的时钟极性CPOL和时钟相位CPHA,除此,还需配置CE、CSN、IRQ引脚

    2、nRF24L01管脚初始化

    前面我们已经完成了SPI2的基本配置,对于SPI的配置到处都有例程,直接拿来用即可,也可以把我上面的代码复制到你的“SPI.c”文件中,然后把函数添加进“.h”文件中以便调用。要注意的是,如果你使用SPI1要修改为SPI1的配置。配置好后只需调用SPI2_Init();即可对SPI2进行初始化。
    前面只配置了CLK、MISO、MOSI,NSS信号由是软件(使用SSI位)控制,所以还要配置CSN、CE、IRQ管脚,下面是nRF24L01模块的IO配置。

    相应管脚连接如下:(可自行修改)

    管脚GPIO口
    CEPC8
    CSNPC7
    IRQPC6

    (1)首先先在“24l01.h”头文件中把前面的寄存器命令和地址添加进去,再补充以下一些宏定义。

    //24L01操作线
    #define NRF24L01_CE   	PCout(8) //24L01片选信号
    #define NRF24L01_CSN  	PCout(7) //SPI片选信号	   
    #define NRF24L01_IRQ 	PCin(6)  //IRQ主机数据输入
    //24L01发送接收数据宽度定义
    #define TX_ADR_WIDTH    4   	 //4字节的地址宽度
    #define RX_ADR_WIDTH    4   	 //4字节的地址宽度
    #define TX_PLOAD_WIDTH  32  	 //32字节的用户数据宽度
    #define RX_PLOAD_WIDTH  32  	 //32字节的用户数据宽度
    //频道选择 							   	   
    #define CHANAL 			0x50	 //通信频道
    

    如果想问PCout()、PCin()是什么?其实也不是什么,就是一个“sys.h”里的宏定义,实现51类似的GPIO控制功能。
    “sys.h”里的宏定义我这里也给出:

    //位带操作,实现51类似的GPIO控制功能
    //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
    //IO口操作宏定义
    #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
    #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
    #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
    //IO口地址映射
    #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
    #define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
    #define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
    #define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
    #define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
    #define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
    #define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    
    
    #define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
    #define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
    #define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
    #define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
    #define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
    #define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
    #define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
     
    //IO口操作,只对单一的IO口!
    //确保n的值小于16!
    #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
    #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 
    
    #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
    #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 
    
    #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
    #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 
    
    #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
    #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 
    
    #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
    #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入
    
    #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
    #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入
    
    #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
    #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入
    

    要注意的是,这样宏定义后就是方便置 ‘1’ 或复 ‘0’

    • NRF24L01_CE=1; 等价于 PCout(8)=1; 等价于 GPIO_SetBits(GPIOC,GPIO_Pin_8);
    • NRF24L01_CE=0; 等价于 PCout(8)=0; 等价于 GPIO_ResetBits(GPIOC,GPIO_Pin_8);
    • NRF24L01_IRQ 等价于 PCin(6) 就是读取PC6的电平状态

    在“24l01.c”里还需定义以下全局常量(发送及接收地址,其实两地址一样),目的是为了方便后面的调用。

    const u8 TX_ADDRESS[TX_ADR_WIDTH] = {0x4c,0x45,0x44,0x52}; 	//发送地址
    const u8 RX_ADDRESS[RX_ADR_WIDTH] = {0x4c,0x45,0x44,0x52};	//接收地址
    

    接下来正式配置nRF24L01的管脚,以下是NRF24L01的IO口初始化函数。

    //初始化24L01的IO口
    void NRF24L01_Init_IO(void)
    { 	
    	GPIO_InitTypeDef GPIO_InitStructure;
        SPI_InitTypeDef  SPI_InitStructure;
    
    	//使能GPIOC端口时钟  //PC8-CE  PC7-CSN  PC6-IRQ
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);			//使能复用
      	
    	/*配置SPI_NRF_SPI的CE引脚,和SPI_NRF_SPI的 CSN 引脚*/
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 	 		//推挽输出
     	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8;		//PC7 8 推挽 	  
     	GPIO_Init(GPIOC, &GPIO_InitStructure);						//初始化指定IO
        GPIO_ResetBits(GPIOC,GPIO_Pin_7|GPIO_Pin_8);				//PC7,8下拉
      
    	/*配置SPI_NRF_SPI的IRQ引脚*/
    	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;   
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 				//PC6 输入  
    	GPIO_Init(GPIOC, &GPIO_InitStructure);	
    
        SPI2_Init();    			//初始化SPI2
    
    	SPI_Cmd(SPI2, DISABLE); 	// SPI外设不使能
    
    	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //SPI设置为双线双向全双工
    	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;						//SPI主机
        SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;					//发送接收8位帧结构
    	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;							//时钟悬空低
    	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;						//数据捕获于第1个时钟沿
    	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;							//NSS信号由软件控制
    	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;	//定义波特率预分频的值:波特率预分频值为16
    	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;					//数据传输从MSB位开始
    	SPI_InitStructure.SPI_CRCPolynomial = 7;							//CRC值计算的多项式
    	SPI_Init(SPI2, &SPI_InitStructure);  								//根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
     
    	SPI_Cmd(SPI2, ENABLE); 							//使能SPI外设
    	SPI2_SetSpeed(SPI_BaudRatePrescaler_4);			//设置时钟4分频,我这测试可用,如果错误建议使用SPI_BaudRatePrescaler_8
    	NRF24L01_CE=0; 									//使能24L01
    	NRF24L01_CSN=1;									//SPI片选取消
    }
    

    上述代码中需注意的是SPI频率,nRF24L01模块最高频率可达10MHz,但是建议不要超过8MHz,SPI2用的时钟是36MHz,所以我使用SPI2_SetSpeed(SPI_BaudRatePrescaler_4);函数进行4分频,这里是9MHz。SPI1用的系统时钟是72MHz,应该进行8分频,根据实际情况作修改。
    到此,NRF24L01模块的管脚基本配置完成,要提醒的是本人使用的是中断方式,所以还应该初始化外部中断,这个我放到后面写。

    3、nRF24L01读写函数

    上述已经完成nRF24L01的GPIO配置,但要想和nRF24L01进行正常的通讯,还需要写四个与读写相关函数,其实就是对SPI读写函数“再包装”。我们进行SPI通信的时候还要对CSN片选信号进行拉低,而不是直接一个SPI2_ReadWriteByte();函数就可以进行SPI通信,和串口的两根线通信有些区别。接下来我们需要完成两个读写nRF24L01寄存器函数(其实就是字节的SPI读写)以及nRF24L01与STM32之间的两个SPI收发数据函数(其实就是字节的SPI读写)。

    //SPI写寄存器
    //reg:指定寄存器地址
    //value:写入的值
    u8 NRF24L01_Write_Reg(u8 reg, u8 value)
    {
    	u8 status;
    	while(!PCin(7));					//等待上一次24L01收发数据完成
      	NRF24L01_CSN = 0;                 	//使能SPI传输
      	status = SPI2_ReadWriteByte(reg);	//发送寄存器号 
      	SPI2_ReadWriteByte(value);      	//写入寄存器的值
      	NRF24L01_CSN = 1;                 	//禁止SPI传输	   
      	return(status);       				//返回状态值
    }
    
    //读取SPI寄存器值
    //reg:要读的寄存器
    u8 NRF24L01_Read_Reg(u8 reg)
    {
    	u8 reg_val;
    	while(!PCin(7));					//等待上一次24L01收发数据完成
     	NRF24L01_CSN = 0;          			//使能SPI传输		
      	SPI2_ReadWriteByte(reg);   			//发送寄存器号
     	reg_val = SPI2_ReadWriteByte(0XFF);	//读取寄存器内容
     	NRF24L01_CSN = 1;          			//禁止SPI传输		    
     	return(reg_val);           			//返回状态值
    }	
    
    //在指定位置读出指定长度的数据
    //reg:寄存器(位置)
    //*pBuf:数据指针
    //len:数据长度
    //返回值,此次读到的状态寄存器值 
    u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len)
    {
    	u8 status, u8_ctr=u8_ctr;
    	while(!PCin(7));							//等待上一次24L01收发数据完成
      	NRF24L01_CSN = 0;           				//使能SPI传输
      	status = SPI2_ReadWriteByte(reg);			//发送寄存器值(位置),并读取状态值
     	for(u8_ctr=0; u8_ctr<len; u8_ctr++)
     		pBuf[u8_ctr] = SPI2_ReadWriteByte(0XFF);//读出数据
    	NRF24L01_CSN = 1;       					//关闭SPI传输
    	return status;        						//返回读到的状态值
    }
    
    //在指定位置写指定长度的数据
    //reg:寄存器(位置)
    //*pBuf:数据指针
    //len:数据长度
    //返回值,此次读到的状态寄存器值
    u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
    {
    	u8 status,u8_ctr=u8_ctr;
    	while(!PCin(7));						//等待上一次24L01收发数据完成
     	NRF24L01_CSN = 0;          				//使能SPI传输
     	status = SPI2_ReadWriteByte(reg);		//发送寄存器值(位置),并读取状态值
     	for(u8_ctr=0; u8_ctr<len; u8_ctr++)
      		SPI2_ReadWriteByte(*pBuf++); 		//写入数据
    	NRF24L01_CSN = 1;       				//关闭SPI传输
    	return status;          				//返回读到的状态值
    }
    
    

    虽然上述代码都有注释,但我还是简单介绍一下。
    与网上其他例程不同的是我加了句while(!PCin(7));文章最前面我有说过,我最后要实现DMA方式的,如果上一次数据还没传输完成,那再次传输数据必然出错。所以这里用了个最简单地方法,每次数据传送完成都需要拉高CSN,那我只需要在拉低CSN之前保证CSN为高电平状态即可,故当PC7为低电平(CSN为低)时,等待CSN为高再拉低CSN。这里先了解一下while(!PCin(7));的意义,当然,本次暂时不介绍DMA方式,可以暂且注释这句也是OK的。

    要注意的是这四个函数使用的时候,发送寄存器号并不是单纯地发送【寄存器地址】,而是【操作指令 + 寄存器地址】在发送完寄存器号后都会返回一个status值,返回的这个值就是前面介绍的STATUS寄存器的内容。

    提示一下,SPI是通过移位寄存器实现数据传输,所以发一个字节数据就会收一个字节数据。故发送数据接直接发送数据,接收数据就发送0XFF,寄存器会返回要读取的数据。四个函数实现的操作主要是先拉低CSN–发送寄存器号–发送数据/接收数据–拉高CSN,具体参考如下数据手册的SPI读写时序。
    nRF24L01的SPI读写时序图

    4、nRF24L01模式初始化

    给代码之前,先简单了解下nRF24L01的接收模式和发送模式。前面已经给过nRF24L01的工作方式,忘记的回到文章前面去看看。这里我列出采用ShockBurst 模式的接收模式和发送模式的初始化配置过程,熟悉了 nRF24L01 以后可以采用别的通信方式。

    Rx 模式初始化过程:

    初始化步骤24L01相关寄存器
    1)写Rx 节点的地址RX_ADDR_P0
    2)使能AUTOACK EN_AA
    3)使能PIPE 0EN_RXADDR
    4)选择通信频率RF_CH
    5)选择通道0 有效数据宽度Rx_Pw_P0
    6)配置发射参数(低噪放大器增益、发射功率、无线速率)RF_SETUP
    7)配置24L01 的基本参数以及切换工作模式 CONFIG

    Tx 模式初始化过程:

    初始化步骤24L01相关寄存器
    1)写Tx 节点的地址 TX_ADDR
    2)写Rx 节点的地址(主要是为了使能Auto Ack)RX_ADDR_P0
    3)使能AUTOACK EN_AA
    4)使能PIPE 0EN_RXADDR
    5)配置自动重发次数 SETUP_RETR
    6)选择通信频率RF_CH
    7 ) 选择通道0 有效数据宽度Rx_Pw_P0
    8)配置发射参数(低噪放大器增益、发射功率、无线速率)RF_SETUP
    9)配置24L01 的基本参数以及切换工作模式 CONFIG

    通过上面很容易发现,发送模式和接收模式的配置很相似,只有几句有区别(红色标出)。所以我要做的就是能省事就省事,有些寄存器操作一次即可,没必要发送模式(Tx)和接收模式(Rx)相互切换的时候重新配置相同的步骤。所以我便封装了一个nRF24L01的模式初始化函数。

    //该函数初始化NRF24L01
    void NRF24L01_Init(void)
    {  
    	NRF24L01_CE = 0;	
    	NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0, (u8*)RX_ADDRESS, RX_ADR_WIDTH);	//写RX节点地址
    	NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR, (u8*)TX_ADDRESS,TX_ADR_WIDTH);		//写TX节点地址
    	NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA, 0x00);									//关闭通道0的自动应答    
    	NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR, 0x01);								//使能通道0的接收地址 
    	NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR, 0x00);								//不重发
    	NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_AW, 0x02);								//地址宽度4
    	NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH, CHANAL);	  							//设置RF通信频率			    
    	NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP, 0x0f);								//设置TX发射参数,0db增益,2Mbps,低噪声增益开启
    	NRF24L01_Write_Reg(NRF_WRITE_REG+DYNPD, 0x01);	    							//使能动态长度0
    	NRF24L01_Write_Reg(NRF_WRITE_REG+FEATURE, 0x04);	 							//使能动态长度
    	NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS, 0xff); 								//清除状态标志
    	NRF24L01_RX_Mode();																//初始化为接收模式
    }
    
    

    相关寄存器配置在前面头文件中的宏定义中也有解释,下面我还是详细列出使用到的寄存器及其相应位功能。由于个人对照数据手册翻译,不一定翻译正确,但不影响理解和使用。
    注意:两个比较重要的寄存器(状态寄存器配置寄存器)在前面已经介绍。更具体请自行查看数据手册。

    寄存器地址:0x01       名称:EN_AA(自动应答功能寄存器)

    第7~6位(Reserved)第5位(ENAA_P5)第4位(ENAA_P4)第3位(ENAA_P3)第2位(ENAA_P2)第1位(ENAA_P1)第0位(ENAA_P0)
    保留(未用),仅允许’00’数据通道5自动应答允许。
    ‘1’ :允许;
    ‘0’ :不允许
    数据通道4自动应答允许。
    ‘1’ :允许;
    ‘0’ :不允许
    数据通道3自动应答允许。
    ‘1’ :允许;
    ‘0’ :不允许
    数据通道2自动应答允许。
    ‘1’ :允许;
    ‘0’ :不允许
    数据通道1自动应答允许。
    ‘1’ :允许;
    ‘0’ :不允许
    数据通道0自动应答允许。
    ‘1’ :允许;
    ‘0’ :不允许

    寄存器地址:0x02       名称:EN_RXADDR(接收地址允许寄存器)

    第7~6位(Reserved)第5位(ERX_P5)第4位(ERX_P4)第3位(ERX_P3)第2位(ERX_P2)第1位(ERX_P1)第0位(ERX_P0)
    保留(未用),仅允许’00’接收数据通道5允许。
    ‘1’ :允许;
    ‘0’ :不允许
    接收数据通道4允许。
    ‘1’ :允许;
    ‘0’ :不允许
    接收数据通道3允许。
    ‘1’ :允许;
    ‘0’ :不允许
    接收数据通道2允许。
    ‘1’ :允许;
    ‘0’ :不允许
    接收数据通道1允许。
    ‘1’ :允许;
    ‘0’ :不允许
    接收数据通道0允许。
    ‘1’ :允许;
    ‘0’ :不允许

    寄存器地址:0x03       名称:SETUP_AW(地址宽度设置寄存器)

    第7~2位(Reserved)第1~0位(AW)
    保留(未用),仅允许’000000’接收/发射地址宽度设置位。
    ‘00’:无效;         
    ‘01’:3字节宽度;
    ‘10’:4字节宽度;
    ‘11’:5字节宽度   

    寄存器地址:0x04       名称:SETUP_RETR(自动重发配置寄存器)

    第7~4位(ARD)第3~0位(ARC)
    自动重发延时设置位。
    ‘0000’:等待250+86us;
    ‘0001’:等待500+86us:
    ‘0010’:等待750+86us;
    ……
    ‘1111’:等待4000+86us;
    (延时时间是指一包数据发送完成到下一包数据开始发射之间的时间间隔)
    自动重发数值设置位。
    ‘0000’:禁止自动重发;
    ‘0001’:自动重发 1 次;
    ……
    ‘1111’:自动重发15次

    寄存器地址:0x05       名称:RF_CH(工作频率设置寄存器)

    第7位(Reserved)第6~0位(RF_CH)
    保留(未用),仅允许’0’7位值可以设置,128个频段,实际是125个频段,可以任意设定但是接收与发送必须一致。

    寄存器地址:0x06       名称:RF_SETUP(发射参数设置寄存器)

    第7~5位(Reserved)第4位(PLL_LOCK)第3位(PLL_LOCK)第2~1位(RF_PWR)第0位(LNA_HCURR)
    保留(未用),仅允许’000’PLL_LOCK允许,仅应用于测试模式,默认‘0’数据传输速率设置位。
    ‘0’:1Mbps
    ‘1’:2Mbps
    发射功率设置。
    ‘00’:-18dBm
    ‘01’:-12dBm
    ‘10’:-6dBm  
    ‘11’:0dBm   
    低噪声放大增益,默认‘1’

    寄存器地址:0x1C       名称:DYNPD(动态长度允许寄存器)

    第7~6位(Reserved)第5位(DPL_P5)第4位(DPL_P4)第3位(DPL_P3)第2位(DPL_P2)第1位(DPL_P1)第0位(DPL_P0)
    保留(未用),仅允许’00’动态有效数据长度数据通道5允许位
    ’1’ :允许;
    ‘0’ :不允许
    动态有效数据长度数据通道4允许位
    ’1’ :允许;
    ‘0’ :不允许
    动态有效数据长度数据通道3允许位
    ’1’ :允许;
    ‘0’ :不允许
    动态有效数据长度数据通道2允许位
    ’1’ :允许;
    ‘0’ :不允许
    动态有效数据长度数据通道1允许位
    ’1’ :允许;
    ‘0’ :不允许
    动态有效数据长度数据通道0允许位
    ’1’ :允许;
    ‘0’ :不允许

    寄存器地址:0x1D       名称:FEATURE(功能寄存器)

    第7~3位(Reserved)第2位(EN_DPL)第1位(EN_ACK_PAY)第0位(EN_DYN_ACK)
    保留(未用),仅允许’00000’动态有效数据长度允许位
    ’1’ :允许;
    ‘0’ :不允许
    使能ACK有效数据
    ’1’ :允许;
    ‘0’:不允许
    使能’W_TX_PAYLOAD_NOACK’命令
    ’1’ :允许;
    ‘0’:不允许

    至此,nRF24L01的初始化函数已完成。聪明的人应该会问为什么初始化完成没有拉高CE,其实并不是没有拉高CE,可以看到最后一句 NRF24L01_RX_Mode(); 初始化为接收模式,配置完相应的模式后就会拉高CE,这个函数后面会介绍。nRF24L01模块默认初始化为接收模式,当要发送数据的时候再转为发送模式。上述代码已经写得很详细了,学习过程中只需要对照流程把相应的寄存器配置好即可。

    5、接收模式配置

    对照“Rx模式初始化过程”,可以发现只剩下步骤“配置24L01 的基本参数以及切换工作模式”。在待机模式下,若PRIM_RX = 1且CE = 1,会进入接收模式,接收数据该步骤主要就是先清空 RX FIFO 寄存器,再配置CONFIG寄存器,简简单单,直接上代码。

    //该函数初始化NRF24L01到RX模式
    //当CE变高后,即进入RX模式,并可以接收数据了		   
    void NRF24L01_RX_Mode(void)
    {
    	NRF24L01_CE = 0;
    	NRF24L01_Write_Reg(FLUSH_RX, 0xff);				//清接收FIFO
    	NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG, 0x0f);	//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式 
    	NRF24L01_CE = 1; 								//CE为高,进入接收模式 
    }
    
    • FLUSH_RX:清除 RX FIFO 寄存器,应用于接收模式下。写0XFF清除。在传输应答信号过程中不应执行此指令。也就是说,若传输应答信号过程中执行此指令的话将使得应答信号不能被完整的传输。
    • 在待机模式下,若PRIM_RX = 1且CE = 1,会进入接收模式,接收数据。PRIM_RX 位在CONFIG(配置寄存器)的第0位中配置。详细回到前面查看配置寄存器
    • 拉低CE是为了切换工作模式。如果为发送模式或待机模式II,此时CE为高,只有拉低才能对寄存器进行操作,配置完再拉高。

    6、发送模式配置

    在待机模式下,若PRIM_RX = 0、CE = 1且TX FIFO寄存器非空,会进入发射模式,发送数据。根据前文“Tx模式初始化过程”,和接收模式类似,我们需要清空 TX FIFO 寄存器,再配置CONFIG寄存器。需要注意的是,配置发送模式必须写一次发送地址

    //该函数初始化NRF24L01到TX模式		   
    //CE为高大于10us,则启动发送.	 
    void NRF24L01_TX_Mode(void)
    {
    	NRF24L01_CE = 0;
    	NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR, (u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址
    	NRF24L01_Write_Reg(FLUSH_TX, 0xff);										//清发送FIFO	
        NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG, 0x0e);		//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
    }
    
    • FLUSH_TX:清除 TX FIFO 寄存器,应用于发射模式下。写0XFF清除。
    • 在待机模式下,若PRIM_RX = 0、CE = 1且TX FIFO寄存器非空,会进入发射模式,发送数据。PRIM_RX 位在CONFIG(配置寄存器)的第0位中配置。详细回到前面查看配置寄存器
    • 拉低CE是为了切换工作模式。如果为接收模式,此时CE为高,只有拉低才能对寄存器进行操作,配置完并且等发送数据的长度以字节计数从 MCU 写入TX FIFO 寄存器后再拉高10us就会进入发送模式。

    7、双向通信

    双向通信的方法:A、B两个nRF24L01模块默认都处于接收模式下,当A模块要发送数据的时候就切换为发送模式,当发完数据就马上切换为接收模式。B模块的工作方式也一样,这样就能实现双向通信。
    一种特殊情况:如果两个模块都处于发送模式,这样就没有接收端了。这种现象我们可以称为碰撞现象,其实就是数据丢失了,两个端都不会接收到数据。不过由于在空中传输时间很短,如果不是两个模块同时频繁发送数据,一般情况下不会出现。不过作死就不一样了,你硬要设置两个模块频繁同时发送,我悄悄测试了两个模块每间隔1ms同时发送数据,确实会出现碰撞现象。
    两种不完全解决办法:(因为不管怎样都有可能会发生,只能尽量避免,不能完全解决)
    (一)本文介绍的是采用ShockBurst 模式,你可以配置成增强型ShockBurst 模式,好处就是可以重发丢失数据包和产生应答信号。此模式下由于空中传输时间很短,极大的降低了无线传输中的碰撞现象。配置方法就是将前文nRF24L01初始化配置部分的配置参数修改一下,使能自应答,设置重发次数。虽然这种模式下还是挺容易发生(这里的容易还是作死的情况下测试的)碰撞现象。
    (二)采用检测信号通道占用+延时发送的形式。具体可自行了解,这里不再讲解。
    nRF24L01+如何检测信道被占用-RSSI寄存器实战分享
    nRF24L01+组网方式及防撞(防冲突)机制的实战分享

    8、检测nRF24L01连接状态

    完成了模式的配置,现已经可以在接收模式(Rx)和发送模式(Tx)之间进行切换了。不过为了方便检查nRF24L01有没有跟STM32正常连接,还是写个检测函数比较好。

    //检测24L01是否存在
    //返回值:0,成功;1,失败	
    u8 NRF24L01_Check(void)
    {
    	u8 buf[5] = {0XA5,0XA5,0XA5,0XA5,0XA5};
    	u8 buf1[5]=	{0};
    	u8 i;
    	NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR, buf, 5);	//写入5个字节的地址
    	NRF24L01_Read_Buf(TX_ADDR,buf1, 5); 				//读出写入的地址
    		for(i=0; i<5; i++) if(buf1[i] != 0XA5) break;	//判断读出地址是否是写入地址
    	if(i != 5) return 1;								//检测24L01错误	
    	return 0;		 									//检测到24L01
    }
    

    9、nRF24L01发送数据

    模式都已配置完成,万事具备,只欠发送和接收函数。发送函数很简单,拉低CE—通过SPI向nRF24L01的发送数据寄存器写数据—切换到发送模式(拉高CE10us)。当发送完数据后,此时状态寄存器的 TX_DS 位置高,IRQ 引脚产生中断。

    //发送数据
    //*data_buffer:数据指针
    //Nb_bytes:数据长度
    void NRF_Send_Data(u8 *data_buffer, u8 Nb_bytes)
    {
    	NRF24L01_CE = 0;
    	NRF24L01_TX_Mode();										//切换至发送模式
      	NRF24L01_Write_Buf(WR_TX_PLOAD, data_buffer, Nb_bytes);	//向发送寄存器写数据
      	NRF24L01_CE = 1;
    	delay_us(10);											//延时10us
    }
    

    要注意的是,如果 CE 置低,则系统进入待机模式 I。如果不设置 CE 为低,则系统会发送 TX FIFO 寄存器中下一包数据。如果 TX FIFO 寄存器为空并且 CE 为高则系统进入待机模式 II 。如果系统在待机模式 II,当 CE 置低后系统立即进入待机模式 I。

    10、nRF24L01接收数据

    nRF24L01处在接收模式下,会检测空中信息,接收到有效的数据包(地址匹配、CRC 检验正确),数据存储在 RX_FIFO 中,同时状态寄存器的 RX_DR 位置高,并产生中断,随后 MCU 可将接收到的数据从 RX FIFO 寄存器中读出。

    读取接收数据步骤:(因为是动态长度数据,需先得数据长度)
    1. 读R_RX_PL_WID寄存器得到接收到的数据长度rf_len;
    2. 通过SPI读取RD_RX_PLOAD寄存器,把接收到的数据存的数组rf_rxbuf中。

    	rf_len = NRF24L01_Read_Reg(R_RX_PL_WID);			//读寄存器得到接收到的数据长度
    	NRF24L01_Read_Buf(RD_RX_PLOAD, rf_rxbuf, rf_len);	//把接收到的数据存的数组rf_rxbuf中
    

    因为接收数据是MCU在外部中断(由IRQ 引脚产生的中断)中读取的,两条代码所以就不封装成函数了,产生接收中断的时候就这样使用来读取接收数据。

    11、外部中断6配置(IRQ中断)

    因为使用的是中断方式,所以还需要配置外部中断,我这里使用的是外部中断6
    nRF24L01 的中断引脚(IRQ)为低电平触发,当状态寄存器中TX_DS(数据发送完成中断位)、RX_DR(接收数据中断位) 或MAX_RT(达到最多次重发中断位)为高时触发中断。当MCU 给中断源写‘1’时,中断引脚被禁止。可屏蔽中断可以被IRQ 中断屏蔽。通过设置可屏蔽中断位为高,则中断响应被禁止。默认状态下所有的中断源是被禁止的。

    
    u8 rf_rxbuf[RX_PLOAD_WIDTH];	//接收数据组(32位)
    u8 rf_flag;						//接收标志
    u8 rf_len;						//接收长度
    
    void EXTIX_Init(void)
    { 
    	EXTI_InitTypeDef EXTI_InitStructure;
    	NVIC_InitTypeDef NVIC_InitStructure;
    
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	//使能复用功能时钟
    
    	//nRF24L01 NVIC配置
    	NVIC_InitStructure.NVIC_IRQChannel=EXTI9_5_IRQn;		//打开EXTI9_5的全局中断
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;		//响应优先级为3
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;	//抢占优先级为3
    	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;			//使能
    	NVIC_Init(&NVIC_InitStructure);							//NVIC初始化
    	
    	//将GPIO管脚与外部中断线连接
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource6);//选择GPIOC_Pin6管脚用作外部中断线路
    	
    	//nRF24L01 EXIT配置
    	EXTI_InitStructure.EXTI_Line=EXTI_Line6;				//外部中断通道6
    	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;		//中断模式
    	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;	//下降沿触发
    	EXTI_InitStructure.EXTI_LineCmd=ENABLE;					//使能
    	EXTI_Init(&EXTI_InitStructure);							//EXTI初始化
    	EXTI_ClearITPendingBit(EXTI_Line6);						//清除中断线6
    }
    
    //外部中断6服务程序 
    void EXTI9_5_IRQHandler(void)
    {
    	u8 status;//,i;
    	//判断是否是线路6引起的中断
    	if(EXTI_GetITStatus(EXTI_Line6) != RESET)					//RF有中断了           
    	{
    		if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6) == 0)
    		{ 
    		  status = NRF24L01_Read_Reg(NRF_READ_REG+STATUS);		//读状态寄存器完了判断是哪种中断状态           
    	      if(status & 0x40)				//bit6:数据接收中断                             
    	      {
    			rf_len = NRF24L01_Read_Reg(R_RX_PL_WID);			//读寄存器得到接收到的数据长度
    	        NRF24L01_Read_Buf(RD_RX_PLOAD, rf_rxbuf, rf_len);	//把接收到的数据存的数组rf_rxbuf中  
    	        rf_flag = 1;				//RF接收标志位置1
    	        for(i=0; i<rf_len; i++)
    			{
    				while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
    				USART_SendData(USART1, rf_rxbuf[i]);
    			}
    			//printf("%s\r\n",rf_rxbuf);	//串口1输出接收数据
    			rf_len = 0;					//接收长度清零
    			rf_flag = 0;				//接收标志位清零
    	      }
    	      else if(status &0x20)			//TX发送完成中断
    		  {
    	        NRF24L01_RX_Mode();			//转为接收模式                                                 
    	      }
    	      NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS, status);		//清除状态标志        
    	    }        
    	    EXTI_ClearITPendingBit(EXTI_Line6);                   	//清除中断线6
      } 
    }
    

    需要注意的是,这里我没有开启MAX_RT中断位,所以没写相应的处理代码;对于TX_DS(数据发送完成中断位),发送完数据后我们需要转入接收模式,以保证能够双向通信。而对于printf(); 其实是串口1输出,已经重定义过了,不懂没关系,百度一下,你就知道,当然后面我也会给出。由于我在测试的时候发现用printf("%s\r\n",rf_rxbuf);输出时,第一次输出的数据会出现数据输出不完整,所以我便用了个循环让串口1输出。至此,从nRF24L01接收到数据传输给MCU再由串口1输出这个传输方向已完成。

    12、串口1(USART1)配置

    串口USART的配置也是初学STM32必须的,所以这里没必要讲太多。先给出printf重定义部分代码。两种方式,一种是不需要选择“Use MicroLIB”,一种是选择使用“Use MicroLIB”。“Use MicroLIB”选项在“魔术棒”—target里选择。

    // 	 
    //如果使用ucos,则包括下面的头文件即可.
    #if SYSTEM_SUPPORT_OS
    #include "includes.h"					//ucos 使用	  
    #endif
    //
    //加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
    #if 1
    #pragma import(__use_no_semihosting)             
    //标准库需要的支持函数                 
    struct __FILE 
    { 
    	int handle; 
    
    }; 
    
    FILE __stdout;       
    //定义_sys_exit()以避免使用半主机模式    
    _sys_exit(int x) 
    { 
    	x = x; 
    } 
    //重定义fputc函数 
    int fputc(int ch, FILE *f)
    {      
    	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
        USART1->DR = (u8) ch;      
    	return ch;
    }
    #endif 
    
    /*使用microLib的方法*/
     /* 
    int fputc(int ch, FILE *f)
    {
    	USART_SendData(USART1, (uint8_t) ch);
    
    	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}	
       
        return ch;
    }
    int GetKey (void)  { 
    
        while (!(USART1->SR & USART_FLAG_RXNE));
    
        return ((int)(USART1->DR & 0x1FF));
    }
    */
    

    串口1这里我先定义了3个u8变量,和外部中断一样。要注意的是我声明了使用外部函数extern void NRF_Send_Data(u8 *data_buffer, u8 Nb_bytes);。因为不是USART1.c里的函数,所以要进行声明。

    u8 rxd1_buf[32];			//接收数组
    u8 rxd1_len;				//接收数据长度
    u8 rxd1_flag;				//接收标志
    
    extern void NRF_Send_Data(u8	*data_buffer, u8 Nb_bytes);		//发送数据
    
    //串口1初始化
    void uart_init(u32 bound)
    {
      	//GPIO端口设置
    	GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    	NVIC_InitTypeDef NVIC_InitStructure;
    	 
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);		//使能USART1,GPIOA时钟
    		
    	//USART1_TX   GPIOA.9
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 											//PA.9
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;										//复用推挽输出
      	GPIO_Init(GPIOA, &GPIO_InitStructure);												//初始化GPIOA.9
       
      	//USART1_RX	  GPIOA.10初始化
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;											//PA10
      	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;								//浮空输入
      	GPIO_Init(GPIOA, &GPIO_InitStructure);												//初始化GPIOA.10  
    
      	//Usart1 NVIC 配置
     	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;							//抢占优先级3
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;									//子优先级2
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;										//IRQ通道使能
    	NVIC_Init(&NVIC_InitStructure);														//根据指定的参数初始化VIC寄存器
      
       	//USART 初始化设置
    	USART_InitStructure.USART_BaudRate = bound;											//串口波特率
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;							//字长为8位数据格式
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;								//一个停止位
    	USART_InitStructure.USART_Parity = USART_Parity_No;									//无奇偶校验位
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		//无硬件数据流控制
    	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;						//收发模式
    
      	USART_Init(USART1, &USART_InitStructure); 											//初始化串口1
     	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);										//开启串口接受中断
    	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);										//开启检测串口空闲状态中断
    	USART_Cmd(USART1, ENABLE);                    										//使能串口1 
    }
    
    //串口1中断服务程序
    void USART1_IRQHandler(void)                	
    {
    	u8 clear = clear;	//这样定义避免弹出clear定义了没使用的警告
    	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)		//接收中断
    	{
    		rxd1_buf[rxd1_len++] = USART1->DR;						//把接收到的数据存到rxd1_buf数组
      }
    	else if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)	//串口线空闲了说明一包数据接收完了
    	{
    		clear = USART1->SR;										//读寄存器就等于清寄存器
    		clear = USART1->DR;										//读寄存器就等于清寄存器
    		rxd1_flag = 1;											//串口接收标志位置1
    		NRF_Send_Data(rxd1_buf, rxd1_len);						//NRF24L01发送数据
    		rxd1_len = 0;											//接收数据长度置0
    		rxd1_flag = 0;											//串口接收标志位置0
    	}
    	USART_ClearITPendingBit(USART1,USART_IT_ORE);				//清除USART1_ORE标志位
    }
    

    至此,从串口1接收到数据传输给MCU再由nRF24L01发送这个传输方向已完成。整个串口透传传输也全部完成。

    13、延时函数

    延时函数不是我主要介绍的内容,但却是很常用,怕有些人不知道怎么写或者怎么实现较准确的延时,我这里便提供正点原子的代码。

    (1)delay.c

    #include "delay.h"
    // 	 
    //如果需要使用OS,则包括下面的头文件即可.
    #if SYSTEM_SUPPORT_OS
    #include "includes.h"					//ucos 使用	  
    #endif
    /
    
    static u8  fac_us=0;							//us延时倍乘数			   
    static u16 fac_ms=0;							//ms延时倍乘数,在ucos下,代表每个节拍的ms数
    	
    #if SYSTEM_SUPPORT_OS							//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
    //当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
    //首先是3个宏定义:
    //    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
    //delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
    // delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
    //然后是3个函数:
    //  delay_osschedlock:用于锁定OS任务调度,禁止调度
    //delay_osschedunlock:用于解锁OS任务调度,重新开启调度
    //    delay_ostimedly:用于OS延时,可以引起任务调度.
    
    //本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
    //支持UCOSII
    #ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII				
    #define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
    #define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
    #define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
    #endif
    
    //支持UCOSIII
    #ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII	
    #define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
    #define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
    #define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
    #endif
    
    
    //us级延时时,关闭任务调度(防止打断us级延迟)
    void delay_osschedlock(void)
    {
    #ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
    	OS_ERR err; 
    	OSSchedLock(&err);							//UCOSIII的方式,禁止调度,防止打断us延时
    #else											//否则UCOSII
    	OSSchedLock();								//UCOSII的方式,禁止调度,防止打断us延时
    #endif
    }
    
    //us级延时时,恢复任务调度
    void delay_osschedunlock(void)
    {	
    #ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
    	OS_ERR err; 
    	OSSchedUnlock(&err);						//UCOSIII的方式,恢复调度
    #else											//否则UCOSII
    	OSSchedUnlock();							//UCOSII的方式,恢复调度
    #endif
    }
    
    //调用OS自带的延时函数延时
    //ticks:延时的节拍数
    void delay_ostimedly(u32 ticks)
    {
    #ifdef CPU_CFG_CRITICAL_METHOD
    	OS_ERR err; 
    	OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);	//UCOSIII延时采用周期模式
    #else
    	OSTimeDly(ticks);							//UCOSII延时
    #endif 
    }
     
    //systick中断服务函数,使用ucos时用到
    void SysTick_Handler(void)
    {	
    	if(delay_osrunning==1)						//OS开始跑了,才执行正常的调度处理
    	{
    		OSIntEnter();							//进入中断
    		OSTimeTick();       					//调用ucos的时钟服务程序               
    		OSIntExit();       	 					//触发任务切换软中断
    	}
    }
    #endif
    
    			   
    //初始化延迟函数
    //当使用OS的时候,此函数会初始化OS的时钟节拍
    //SYSTICK的时钟固定为HCLK时钟的1/8
    //SYSCLK:系统时钟
    void delay_init()
    {
    #if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
    	u32 reload;
    #endif
    	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
    	fac_us=SystemCoreClock/8000000;				//为系统时钟的1/8  
    #if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
    	reload=SystemCoreClock/8000000;				//每秒钟的计数次数 单位为K	   
    	reload*=1000000/delay_ostickspersec;		//根据delay_ostickspersec设定溢出时间
    												//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右	
    	fac_ms=1000/delay_ostickspersec;			//代表OS可以延时的最少单位	   
    
    	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
    	SysTick->LOAD=reload; 						//每1/delay_ostickspersec秒中断一次	
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    
    
    #else
    	fac_ms=(u16)fac_us*1000;					//非OS下,代表每个ms需要的systick时钟数   
    #endif
    }								    
    
    #if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
    //延时nus
    //nus为要延时的us数.		    								   
    void delay_us(u32 nus)
    {		
    	u32 ticks;
    	u32 told,tnow,tcnt=0;
    	u32 reload=SysTick->LOAD;					//LOAD的值	    	 
    	ticks=nus*fac_us; 							//需要的节拍数	  		 
    	tcnt=0;
    	delay_osschedlock();						//阻止OS调度,防止打断us延时
    	told=SysTick->VAL;        					//刚进入时的计数器值
    	while(1)
    	{
    		tnow=SysTick->VAL;	
    		if(tnow!=told)
    		{	    
    			if(tnow<told)tcnt+=told-tnow;		//这里注意一下SYSTICK是一个递减的计数器就可以了.
    			else tcnt+=reload-tnow+told;	    
    			told=tnow;
    			if(tcnt>=ticks)break;				//时间超过/等于要延迟的时间,则退出.
    		}  
    	};
    	delay_osschedunlock();						//恢复OS调度									    
    }
    //延时nms
    //nms:要延时的ms数
    void delay_ms(u16 nms)
    {	
    	if(delay_osrunning&&delay_osintnesting==0)	//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    
    	{		 
    		if(nms>=fac_ms)							//延时的时间大于OS的最少时间周期 
    		{ 
       			delay_ostimedly(nms/fac_ms);		//OS延时
    		}
    		nms%=fac_ms;							//OS已经无法提供这么小的延时了,采用普通方式延时    
    	}
    	delay_us((u32)(nms*1000));					//普通方式延时  
    }
    #else //不用OS时
    //延时nus
    //nus为要延时的us数.		    								   
    void delay_us(u32 nus)
    {		
    	u32 temp;	    	 
    	SysTick->LOAD=nus*fac_us; 					//时间加载	  		 
    	SysTick->VAL=0x00;        					//清空计数器
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数	  
    	do
    	{
    		temp=SysTick->CTRL;
    	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
    	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
    	SysTick->VAL =0X00;      					 //清空计数器	 
    }
    //延时nms
    //注意nms的范围
    //SysTick->LOAD为24位寄存器,所以,最大延时为:
    //nms<=0xffffff*8*1000/SYSCLK
    //SYSCLK单位为Hz,nms单位为ms
    //对72M条件下,nms<=1864 
    void delay_ms(u16 nms)
    {	 		  	  
    	u32 temp;		   
    	SysTick->LOAD=(u32)nms*fac_ms;				//时间加载(SysTick->LOAD为24bit)
    	SysTick->VAL =0x00;							//清空计数器
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数  
    	do
    	{
    		temp=SysTick->CTRL;
    	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
    	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
    	SysTick->VAL =0X00;       					//清空计数器	  	    
    } 
    #endif 
    
    

    (2)delay.h

    #ifndef __DELAY_H
    #define __DELAY_H 			   
    #include "sys.h" 
    
    void delay_init(void);
    void delay_ms(u16 nms);
    void delay_us(u32 nus);
    
    #endif
    

    五、主函数部分

    前面的配置就是整个基于NRF24L01串口透传的流程,要提醒一下,我使用的中断比较多,所以一定要理清楚中断的抢占优先级和响应优先级,后期要是介绍如何实现DMA方式的时候会有更多的中断,所以头脑一定要清晰。主函数部分主要就是引入头文件,以及一些初始化。

    int main(void)
    {
    	//u8 i;
    	delay_init();	    	 		//延时函数初始化	  
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
    	uart_init(115200);	 			//串口初始化为115200
    	NRF24L01_Init_IO();    			//初始化NRF24L01的IO
     	NRF24L01_Init();    			//初始化NRF24L01 
    	EXTIX_Init();					//外部中断初始化
    	while(NRF24L01_Check())			//如果检测不到2401串口就间隔1s打印提示信息
    	{
    		printf("NRF24L01 Error\r\n");
    		delay_ms(1000);
    	}	
    	printf("NRF24L01 SUCCESS\r\n");
    	
    	while (1)
      	{
    		/*
    		if(rxd1_flag == 1) 
    		{
    			NRF_Send_Data(rxd1_buf, rxd1_len);
    			rxd1_len = 0;
    			rxd1_flag = 0;
    		}
    		if(rf_flag == 1)
    		{
    	   		for(i=0; i<rf_len; i++)
    			{
    				while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
    				USART_SendData(USART1, rf_rxbuf[i]);
    			}
    			rf_len = 0;
    			rf_flag = 0;
    		}
      		*/
    	}
    }
    

    聪明人会发现,我在while循环里注释了一些代码,这些代码是用于查询方式的,查询方式下一定要注意回到串口接收中断外部中断6修改中断标志位

    六、啰嗦一下

    写了这么多,就是希望大家学习nRF24L01的时候能少走一些弯路,少跳进一些坑。本人也是第一次写博文,如有错误之处欢迎指出!也欢迎大家一起学习!本文介绍的我个人认为还是非常详细的,尽管有些代码是我在学习过程中复制其他人的代码,但是最后都是经过自己的理解和思考。学习的过程就是Ctrl+CCtrl+V,但是一定要转变成自己的知识,这也是我边给代码,边讲解配置和使用的初衷,所以我并不是像其他人一样直接上代码,结果只知道复制、粘贴,错误后不知道错在哪。最后说一下,如果有需要我可以上传代码给大家,如果本文效果好,后期我会写如何实现串口DMA—SPI DMA方式实现串口透传,相信大家百度nRF24L01 SPI DMA使用的时候查不到什么东西,对于nRF24L01如何实现SPI DMA传输也找不到什么文章,当然大佬请无视。

    展开全文
  • 尽管比按字节(byte)传输的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。 串口通信中比较重要的参数包括波特率、数据位、停止位及校验位,通讯双方需要约定一致的数据格式才能正常收发...

    一、串口通信简介

    串口通信,顾名思义也就是利用串行接口进行通信。串行接口指串口按位(bit)发送和接收字节。尽管比按字节(byte)传输的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。

    串口通信中比较重要的参数包括波特率、数据位、停止位及校验位,通讯双方需要约定一致的数据格式才能正常收发数据。串行通讯根据通信双方的分工和信号传输方向可以进一步分为单工、半双工和全双工三种。在串口通信中,常用的协议包括RS-232、RS-422和RS-485。它们的主要区别在于其各自的电平范围不相同。

    二、串行通信的传输方向

    数据通信中,数据在线路上的传送方式(方向)可以分为:单工通信、半双工通信和全双工通信三种。

    1 单工(Simplex Communication )

    单工模式通信使用一根传输线,其数据传输是单向的,仅能沿一个方向,不能实现反向传输,即通信双方发送端和接收端的身份是固定的。通信双方中,一方固定为发送端,一方则固定为接收端。

    • 例子:早期的电视,广播,打印机

    2 半双工(Half Duplex Communication)

    半双工模式通信一般使用一根(或一对)传输线,数据可以沿两个方向传输,既可以发送数据又可以接收数据,但不能同时进行发送和接收,同一时刻只允许单方向传送。因此又被称为双向交替通信。数据传输允许数据在两个方向上传输,但是,在任何时刻只能由其中的一方发送数据,另一方接收数据。

    半双工模式收发两端都有发送器和接收器,通过收/发开关转接到通信线上。半双工通信中每端需有一个收发切换电子开关,若要改变传输方向,需由开关进行切换,通过切换来决定数据向哪个方向传输。由于要频繁切换信道方向,会产生时间延迟,故传输效率低些,但可以节约传输线路。半双工方式适用于终端与终端之间的会话式通信。

    • 例子:对讲机,RS485

    3 全双工(Full Duplex Transmission)

    全双工模式通信指数据由两根不同的数据线(可能还需要控制线、状态线、地线)传送,可以同时进行双向传输。即数据的发送和接收分流,通信双方都能在同一时刻进行发送和接收操作。从功能角度方面讲,全双工通信相当于两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。因此,通信系统的每一端都设置了发送器和接收器,来控制数据同时在两个方向上传送。

    与半双工模式相比,全双工可同时进行数据收发,且无需进行方向的切换,没有切换操作所产生的时间延迟。 显然,在其它参数都一样的情况下,全双工比半双工传输速度要快,信息传输效率要高。这对那些不能有时间延误的交互式应用(例如远程监测和控制系统)十分有利。

    • 例子:手机通话,RS422,RS232

    三、串行通信的错误校验

    在通信过程中往往要对数据传送的正确与否进行校验。校验是保证准确无误传输数据的关键。常用的校验方法有奇偶校验、代码和校验及循环冗余码校验。

    (1)奇偶校验

    在发送数据时,数据位尾随的1位为奇偶校验位(1或0)。

    (2)常用算法校验

    代码和校验是发送方将所发数据块求和(或各字节异或),产生一个字节的校验字符(校验和)附加到数据块末尾。

    (3)循环冗余校验

    这种校验是通过某种数学运算实现有效信息与校验位之间的循环校验,常用于对存储区的完整性、磁盘信息的传输校验等。

    1 奇偶校验

    奇偶校验指的是在发送数据时,利用数据中“1”的个数是奇数或偶数作为检测错误的标志。通常在数据位后面设置1位奇偶校验位(1或0),用它使这组代码中“1”的个数为奇数或偶数。奇校验时,数据中“1”的个数与校验位“1”的个数之和应为奇数;偶校验时,数据中“1”的个数与校验位“1”的个数之和应为偶数。接收学符时,接收端对数据位“1”的个数进行校验,若发现发送端结果与不一致,则说明传输数据过程中出现了错误。此时接收端可以向发送端发送请求,要求重新发送一遍数据。

    • 奇校验:此时奇偶校验位的作用就是保证所有数据位加奇偶校验位的所有比特位中值为1的比特位的个数为奇数。
      若数据位中共有奇数个值为1的比特位,则此时奇偶校验位的值为0。
      若数据位中共有偶数个值为1的比特位,则此时奇偶校验位的值为1。
    • 偶校验:此时奇偶校验位的作用就是保证所有数据位加奇偶校验位的所有比特位中值为1的比特位的个数为偶数。
      若数据位中一共有奇数个值为1的比特位,则此时奇偶校验位的值为1。
      若数据位中一共有偶数个值为1的比特位,则此时奇偶校验位的值为0。

    例子

    假设传输的数据位为01001100,如果是奇校验,则奇校验位为0(确保总共有奇数个1);如果是偶校验,则偶校验位为1(要确保总共有偶数个1)。

    优缺点

    奇偶校验的缺点很明显,首先,它对错误的检测概率大约只有50%。因为只有奇数个数据位发生变化能检测到,如果偶数个数据位发生变化则无能为力了。另外,每传输一个字节都要附加一位校验位,对传输效率有较大影响。因此,在高速数据通讯中很少采用奇偶校验。奇偶校验可以发现错误,但不能纠正错误,也就是说它只能告诉你出错了,但不能告诉你怎么出错了,一旦发现错误,只能重发。

    奇偶校验优点也很明显,它很简单,因此可以用硬件来实现,这样可以减少软件的负担。因此,奇偶校验也被广泛的应用着。

    2 常用算法校验

    常用算法校验是指发送端将所发数据块进行累加和校验异或校验,在数据块末尾附加一个字节的校验字符。接收端接收数据,同时对数据块(除校验字节外)进行不进位求和字节异或,将所得的结果与发送端的“校验和”进行比较,相符则无差错,否则即认为传送过程中出现了差错。

    例子-累加和校验
    累加和校验:
    要传输的数据为:01H、55H、D3H
    则进行不进位累加的校验和字节为:29H,即01H+55H=56H,56H+D3H=129H,舍去进位1,得29H。
    这里 29H 就是前三个字节的累加校验和。接收端收到全部数据后对前三个数据进行同样的累加计算,如果累加和与最后一个字节相同的话就认为传输的数据没有错误。

    异或就是对数据逐一异或计算(异或结果与下一个数据异或)。即接收端将所有字节(一般是两个16进制的字符)按位异或后,得到校验码后与发送端异或码的字符进行比较。相等即认为通信无错误,不相等则认为通信出错。

    优缺点

    累加和校验由于实现起来非常简单,也被广泛的采用。虽然其检错率优于奇偶校验,但这种校验方式的检错能力也较为一般,例如累加的其中一个字节多1,另一个字节少1,累加和不变,将原本是错误的通讯数据误判为正确数据。异或校验同理。累加和校验、异或校验也不能纠正错误。

    3 循环冗余校验

    这种校验是通过某种数学运算实现有效信息与校验位之间的循环校验,常用于对磁盘信息的传输、存储区的完整性校验等。这种校验方法纠错能力强,广泛应用于同步通信中。

    循环冗余码校验(Cyclical Redundancy Check, CRC)是利用除法和余数的原理来做错误侦测的。实际应用时,发送装置计算出CRC值并随数据一同发送给接收端RX,RX对收到的数据重新计算CRC并与收到的CRC值相比较,若两个CRC值不同,则说明数据通信出现了错误,该数据包应该舍弃不用。
    在远距离数据通讯中,为确保高效而无差错的传送数据,必须对数据进行校验控制,而CRC是对一个传送数据块进行校验,是一种非常高效的差错控制方法。目前,主流的CRC可以分为以下几个标准:CRC-12码;CRC-16码;CRC-CCITT码;CRC-32码。
    CRC-12码通常用来传送6-bit字符串。CRC-16及CRC-CCITT码则用来传送8-bit字符,其中CRC-16为美国采用,而CRC-CCITT为欧洲国家所采用。CRC-32码用途有限。
    在数据存储和数据通信领域,CRC无处不在:著名的通信协议X.25的FCS(帧检错序列)采用的是CRC/CCITT,ARJ/LHA等压缩工具软件采用的是CRC32,磁盘驱动器读写采用的日式CRC16,通常用到的图像存储格式GIF/TIFF等也是采用CRC作为检错手段的。

    四、传输速率与传输距离

    数据传输速率指通信线上传输信息的速度,有比特率和波特率两种表示方法。比特率也称为信号速率,是指单位时间内所传送的二进制位代码的有效位数,以每秒多少比特计算,即bit/s;波特率是指调制速率,是脉冲信号经过调制后的传输速率,以波特(Baud)为单位,通常用于表示调制器之间传输信号的速率。

    1 传输速率

    比特率:每秒传输的二进制位数,也称为信号速率,单位为比特每秒(bit/s,bps)。

    波特率:每秒传输的码元符号的个数(码元传输速率),也称为调制速率,单位是波特(B)。它是对符号传输速率的一种度量,它用单位时间内载波调制状态改变的次数来表示,1波特即指每秒传输1个符号。通过不同的调制方法可以在一个符号上负载多个比特信息。

    比特率与波特的关系
    即比特率在数值上和波特率有这样的关系:

    I = S ∗ l o g 2 N I=S*{log_2{N}} I=Slog2N

    其中I为比特率,S为波特率,N为每个符号承载的信息量(一个脉冲信号所表示的有效状态),而 l o g 2 N {log_2{N}} log2N以比特为单位。即波特率与比特率的关系:比特率=波特率*单个调制状态对应的二进制位数。
    一个以X波特传送信号的线路,其传送二进制数据的速率不一定是X比特/秒,因为每个码元符号需要通过几个比特来表示,所以运送一个符号等于运送了几个比特。在二进制中脉冲(二电平)只有两种状态0或1,即 n=“2”,也就是说,信号速率与调制速率是一致的。如果使用多电平脉冲信号传输信息,信号速率与调制速率就不一致了。例如,若使用0、1、2、3、4、5、6、7共8个电平级,则需要,即3个比特来表示一个信号值,因而这种条件下比特率将是波特率的3倍。(当用二进制位表示一个码元时与比特率相等)

    例如假设数据传送速率为120符号/秒(symbol/s)(也就是波特率为120Baud),又假设每一个符号为八相调制(单个调制状态对应3个二进制位),则其传送的比特率为(120symbol/s) * (3bit/symbol)=360bps。只有在每个符号只代表一个比特信息的情况、或一些简单的调制方式下,例如基带二进制信号调制方式等,波特率与比特率才在数值上相等。 具体而言, 两相调制(单个调制状态对应1个二进制位)的比特率等于波特率;四相调制(单个调制状态对应2个二进制位)的比特率为波特率的两倍;八相调制(单个调制状态对应3个二进制位)的比特率为波特率的三倍,依次类推。

    在串行通信中,单个调制状态对应的1个二进制位,因此比特率和波特率往往相同。可以用”波特率”来描述数据的传输速率,即每秒钟传送的二进制位数。它是衡量串行数据速度快慢的重要指标。典型的“波特率”是1200,4800,9600,14400,19200,28800,38400,57600,115200,230400,460800,921600等。有时也用”位周期”来表示传输速率,位周期是波特率的倒数。

    举例:RS485/RS232
    假设目前“波特率”为9600,指每秒传送9600个码元符号,则此RS485/RS232的传信率计算为 :

    I = S ∗ l o g 2 N = 9600 ∗ l o g 2 2 I=S*{log_2{N}}=9600*log_2{2} I=Slog2N=9600log22=9600bit/s

    通信线上所传输的字符数据(代码)是逐位传送的,1个字符由若干位组成,每一位即是一个码元。因此每秒钟所传输的字符数(字符速率)和波特率是两种概念。常有人把RS232的N误以为是每个“符号”(symbol)所夹带的信息量,但实际上每个“位元”(bit)即为一个“符号”(symbol)。

    在串行通信中,所说的传输速率是指波特率,而不是指字符速率。
    如在异步串行通信中,传输速率是9600b,而每个字符格式包含10位(1个起始位、1个停止位、0个校验位、8个数据位),这时每秒钟传送的字符数:9600/(1+8+0+1)=960个。

    2 传输距离与传输速率的关系

    串行通信直接传送串行信息位流的最大距离与传输速率和传输线的电气特性有关。通信速率和通信距离这两个方面是相互制约的,降低通信速率,可以提高通信距离。

    串行通信中,数据位信号流在信号线上传输时,要引起畸变,畸变的大小与以下因素有关:

    波特率——信号线的特征(频带范围)
    传输距离——信号的性质及大小(电平高低,电流大小)
    当畸变较大时,接收方出现误码。
    在规定的误码率下,当波特率、信号线、信号的性质及大小一定时,串行通信的传输距离就一定。为了加大传输距离,必须加调制解调器。

    当传输线使用每0.3m(约1英尺)有50PF电容的非平衡屏蔽双绞线时,传输距离随传输速率的增加而减小。当比特率超过1000 bps时,最大传输距离迅速下降,如9600 bps时最大距离下降到只有76m(约250英尺)。

    串口通讯的距离

    经实测,液晶显示屏控制 系统的RS232串行口在通讯波特率为28800bit/s时能够稳定传输达300米以上(传输介质为1箱五类线);当通讯距离大于 300米时,选择RS485通讯接口的液晶显示屏控制系统,此时只须在计算机的RS232串口端加配一个RS232/485转换器即可。

    115200bps最好的距离在30-50米之间(和线、232芯片有关),再远就有误码啦。15米还是很容易超的。232谁也不敢用300米的。

    传输电缆长度

    由RS-232C标准规定在码元畸变小于4%的情况下,传输电缆长度应为50英尺,其实这个 4%的码元畸变是很保守的,在实际应用中,约有99%的用户是按码元畸变10-20%的范围工作的,所以实际使用中最大距离会远超过50英尺。

    传输距离

    由设备可提供端口的不同,故数据传输距离也不同。普通的RS232是常见的设备端口,其连接距离只有15米左右,如果连线设备距离相当远,则无法在使用RS232。采用RS424的设备,它的连接距离可达1000米。但当多个设备都是远距离时,给每个设备拉一条线会相当不方便,于是RS485便成为首选。RS485接口支持多个设备同时挂在一根导线上,它的总连线距离也可达1000米,而且一路上所有的设备都可以连接其上,相当方便。但它有一个限制:必须是半双工通信方式,即在同一时刻只能有一个设备进行数据发送,而其他设备只能接收。要保证这个条件必须依靠软件。

    3、发送/接收时钟

    在串行传输过程中,二进制数据序列是以数字信号波形的形式出现的,如何对这些数字波形定时发送出去或接收进来,以及如何对发/收双方之间的数据传输进行同步控制的问题就引出了发送/接收时钟的应用。

    在发送数据时,发送器在发送时钟(下降沿)作用下将发送移位寄存器的数据按串行移位输出;在接收数据时,接收器在接收时钟(上升沿)作用下对来自通信线上串行数据,按位串行移入移位寄存器。可见,发送/接收时钟是对数字波形的每一位进行移位操作,因此,从这个意义上来讲,发送/接收时钟又可叫做移位始终脉冲。另外,从数据传输过程中,收方进行同步检测的角度来看,接收时钟成为收方保证正确接收数据的重要工具。为此,接收器采用比波特率更高频率的时钟来提高定位采样的分辨能力和抗干扰能力。
      
    发送/接收时钟频率与波特率的关系:发/收时钟频率 =n*(发/收波特率 )

    4、波特率因子

    在波特率指定后,输入移位寄存器 / 输出移位寄存器在接收时钟 / 发送时钟控制下,按指定的波特率速度进行移位。一般几个时钟脉冲移位一次。要求:接收时钟/ 发送时钟是波特率的 16 、 32 或 64 倍。波特率因子就是发送/接收 1 个数据( 1 个数据位)所需要的时钟脉冲个数,其单位是个/位。如波特率因子为 16 ,则16 个时钟脉冲移位 1 次。 例:波特率 =9600bps ,波特率因子 =32 ,则接收时钟和发送时钟频率 =9600 × 32=297200Hz 。

    参考:
    【1】https://baike.baidu.com/item/%E6%B3%A2%E7%89%B9%E7%8E%87/2153185?fr=aladdin#reference-[3]-119333-wrap
    【2】https://baike.baidu.com/item/%E7%A0%81%E5%85%83/10525003
    【3】http://mayer.spaces.eepw.com.cn/articles/article/item/59707

    展开全文
  • 计算机三类总线的都是双向的吗?

    千次阅读 2021-07-22 01:16:39
    数据总线和控制总线是双向的,地址总线是单向是只能CPU到I/O口。“地址总线AB”是专门用来传送地址的,由于地址只能从CPU传向外部存储器或I/O端口,所以地址总线总是单向三态的,这与数据总线不同。地址总线的位数...
  • NB-IOT的数据传输流程和双工模式

    千次阅读 2020-06-13 16:38:29
    本文章主要整理了一下NB-IOT的数据传输以及双工模式 其实开始的时候主要目的是想了解一下在NB进入PSM之后云端下发的数据暂时存在了什么地方?没有找到确切的答案。目前个人认为是存在核心网或者基站中,那么最多能存...
  • 相当于TCP的稳定性来说,UDP因为其数据传输的不可靠性,所以用在某些特定的场合,如直播、广播消息、视频音频流处理等不太需要校验数据完整性的场合。 UDP相对TCP协议而言,其特点就是简洁,它删除了在TCP协议中为了...
  • 而且可以通过抓包软件修改响应数据返回给客户端,这样一来,客户端实际上接收到的数据并不是服务端给我的源数据,而是被第三者修改过的数据,如此一来,数据传输的安全就很有必要了。 那如何保证数据传输的安全呢? ...
  • 计算机考试题库精简版

    千次阅读 2021-06-19 01:16:55
    第一章一、判断共38题(共计38分)第1题:在RAM中保存的数据在系统断电之后即丢失.Y第2题:磁盘读写数据方式是顺序的.N第3题:某计算机系统的字长为16位,指的是它具有计算16位十进制数的能力.N第4题:因为都是外存储器,...
  • UDP实现可靠数据传输

    千次阅读 2016-09-24 18:21:56
    UDP没有Delievery Garuantee,也没有顺序保证,所以如果你要求你的数据发送与接受既要高效,又要保证有序,收包确认等,你就需要在UDP协议上构建自己...下面分别介绍三种使用UDP进行可靠数据传输的协议 RUDP RTP
  • 先通过http(或者https)请求然后经过socket的方法在经过tcp/udp协议(四次握手)在经过IP(数据传输以及指定IP)然后在反向返回给服务器。 TCP和UDP的区别 HTTP和HTTPS的区别  1.https协议需要到CA申请证书。 ...
  • rpc官方称为 远程过程调用 。我这里理解为远程函数调用,即一个本机程序调用另一个机器的程序中的某个函数。因不是同一机器调用,故需要远程访问操作。 与远程过程调用相反的则是“近程过程调用”(哈哈,自己乱起的...
  • 为了防止各种假冒攻击,在执行真正的数据访问操作之前,要在客户和数据库服务器之间进行双向身份验证,比如数据库系统服务器与服务器之间进行数据传输时,都需要验证身份。 通过数字证书来进行身份验证。签名者用秘密...
  • 客户端与服务器的数据传输

    万次阅读 多人点赞 2018-06-29 15:53:21
    Socket实质上提供了进程通信的端点,网络上的两个程序通过一个双向的通讯链路实现数据的交换,这个双向链路的一端称为一个Socket。 ServerSocket类常用方法如下: O了,讲了这么多理论原理的东西,我来讲讲编码方面...
  • 局域网文件传输方式分析

    万次阅读 2012-06-14 17:50:51
    在C/S结构的软件开发过程中通常被限于局域网范围内, 常常会遇到需要传输数据文件、音视频文件、升级文件到服务器端或者客户端。每个工作站点都是通过交换机或者集线器、路由器等设备相互连接在一起。由于需要传输的...
  • 数据本身的安全包括数据保密,数据完整性验证,数据双向认证等。数据防护安全包括磁盘阵列,数据备份,异地容灾等。App安全问题主要包括: App代码安全,包括代码混淆,加密或者app加壳。 App数据存储安全,主要指...
  • 总线和控制总线分别用于传输数据,数据地址和控制信号. 总线是一种内部结构,是CPU,内存,输入和输出设备传输信息的公共通道. 主机的每个部分通过总线连接,外部设备通过相应的接口电路连接到总线,从而形成计算机...
  • 流媒体及流媒体传输协议简介

    千次阅读 多人点赞 2019-06-01 22:26:10
    流媒体(streaming media):是指将一连串的媒体数据压缩后,经过网上分段发送数据,在网上即时传输影音以供观赏的一种技术与过程,此技术使得数据包得以像流水一样发送;如果不使用此技术,就必须在使用前下载整个...
  • USB四种传输模式

    千次阅读 2017-12-02 15:16:57
     追求数据完整性,CRC校验,故该种传输方式,虽然可以做大数据的传输,但是并不适合实时传输; Interrupt:中断传输 也是实时传输,对数据准确性有一定保证; ISO:同步传输  追求实时
  • 我来学网络——三种数据通信方式

    千次阅读 2018-12-17 11:14:10
    单工通信只支持数据在一个方向上传输,又称为单向通信。如无线电广播和电视广播都是单工通信。 半双工通信允许数据在两个方向上传输,但在同一时刻,只允许数据在一个方向上传输,它实际上是一种可切换方向的单工...
  • 数据传输更安全

    千次阅读 2018-04-22 12:05:53
    在阅读RabbitMQ数据传输安全的章节时,提到了ssl协议,用了很大篇幅介绍使用openssl生成一些列秘钥和证书,如果没有相关基础,会不太好理解,本篇就来总结下数据安全相关的概念以及浏览器HTTPS的应用。 通过介绍,...
  • 这一周做了一个计算机网络的实验,名字叫 可靠数据传输协议-GBN协议的设计与实现 感觉自己做的很认真,实现的效果也不错,就把自己的过程与结果记录一下 对于这个实验,实验要求上说实现SR协议是加分项,而SR协议又是以...
  • SSL/TLS单向认证和双向认证介绍

    千次阅读 2020-06-19 15:05:20
    为了便于理解SSL/TLS的单向认证和双向认证执行流程,这里先介绍一些术语。 1. 散列函数(Hash function):又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字”指纹”的方法。散列函数把消息或数据压缩成...
  • TCP/IP第四层--传输层TCP数据报文详解

    万次阅读 2019-01-28 19:47:48
    TCP/IP第四层--传输层TCP数据报文详解
  • 陈永庭,饿了么框架工具部高级架构师,主要负责MySQL异地双向数据复制,支撑饿了么异地多活项目。曾就职于WebEx、Cisco、腾讯等公司。 今天我主要分享饿了么多活的底层数据实施,会和大家介绍在整个多活的设计和...
  • 知识点: ...2.总线:连接多个部件的信息传输线,是各部件共享的传输介质。 3.总线分为:片内总线,系统总线,通信总线 4.片内总线:芯片内部的总线。如在CPU芯片内部,寄存器与寄存器之间,寄存器与A...
  • 【计算机网络】谢希仁笔记 数据链路层

    千次阅读 多人点赞 2019-03-20 21:25:12
    1.数据链路使用的信道 数据链路层使用的信道主要有以下两种类型: 点对点信道。这种信道使用一对一的点对点通信方式。 广播信道。这种信道使用一对多的广播通信方式,因此过程比较复杂。广播信道上连接的主机很多...
  • 史上最全的数据链路层基础知识详解

    千次阅读 多人点赞 2020-03-14 16:45:53
    三个基本问题2.1封装成帧2.1.1基本概念2.1.2常用成帧方法2.2透明传输2.2.1解决透明传输问题2.2.2用字节/字符填充法解决透明传输问题2.2.3字节填充的标志字节法的特点.2.2.4字节填充的首尾定界法2.2.5零比特填充法2.3...
  • 2017-02-10 可靠数据传输原理、可靠数据传输协议、自动重传请求协议、停等协议、冗余分组、比特交替协议、滑动窗口协议 《计算机网络-自顶向下方法》(原书第6版) 3.4 可靠数据传输原理  可靠数据...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 52,160
精华内容 20,864
关键字:

双向传输数据的方式称为