RT-Thread通信管理

RTT串行通信实现

  • 一、UART设备
    • 1.1 UART简介
    • 1.2 访问串口设备
      • 1.2.1 查找串口设备
      • 1.2.2 打开串口设备
      • 1.2.3 控制串口设备
      • 1.2.4 数据发送与接受
      • 1.2.5 关闭串口设备
  • 2、SPI设备
    • 2.1 SPI简介
    • 2.2 挂载SPI设备
    • 2.3 配置SPI/QSPI设备
      • 2.3.1 配置SPI设备
      • 2.3.2 配置QSPI设备
    • 2.4 访问SPI设备
    • 2.5 访问QSPI设备
  • 3、I2C设备
    • 3.1 I2C简介
    • 3.2 访问IIC设备
      • 3.2.1 查找I2C设备
      • 3.2.2 数据传输

处理器与外部设备的通信根据数据传输方式可以分为:

  • 并行通信
    数据各个位同时传输,速度快、占用引脚资源多。
  • 串行通信
    数据各个位顺序传输,速度慢、占用引脚资源少。

根据数据传输方向的能力,又可分为:

  • 单工
    数据传输只支持数据在一个方向上传输。
  • 半双工
    允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,实际上是一种切换方向的单工通信。
  • 全双工
    允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。

根据是否带时钟同步信号又可分为:

  • 同步通信
    带时钟同步信号传输,例如SPI、I2C。
  • 异步通信
    不带时钟同步信号,例如单总线、UART。

常见的串行通信接口对比如下:
RT-Thread通信管理_第1张图片

一、UART设备

1.1 UART简介

UART(Universal Asynchronous Receiver/Transmitter)通用异步收发传输器,UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。是在应用程序开发过程中使用频率最高的数据总线。
UART 串口的特点是将数据一位一位地顺序传送,只要 2 根传输线就可以实现双向通信,一根线发送数据的同时用另一根线接收数据。UART 串口通信有几个重要的参数,分别是波特率、起始位、数据位、停止位和奇偶检验位,数据格式如下图所示:
RT-Thread通信管理_第2张图片

起始位:表示数据传输的开始,电平逻辑为 “0” 。
数据位:可能值有 5、6、7、8、9,表示传输几个 bit 位数据。一般取值为 8,因为一个 ASCII 字符值为 8 位。
奇偶校验位:用于接收方对接收到的数据进行校验,校验 “1” 的位数为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性,使用时不需要此位也可以。
停止位: 表示一帧数据的结束。电平逻辑为 “1”。
波特率:串口通信时的速率,它用单位时间内传输的二进制代码的有效位(bit)数来表示,其单位为每秒比特数 bit/s(bps)。常见的波特率值有 4800、9600、14400、38400、115200等。

1.2 访问串口设备

应用程序通过 RT-Thread提供的 I/O 设备管理接口来访问串口硬件,相关接口如下所示:

函数 描述
rt_device_find() 查找设备
rt_device_open() 打开设备
rt_device_read() 读取数据
rt_device_write() 写入数据
rt_device_control() 控制设备
rt_device_set_rx_indicate() 设置接收回调函数
rt_device_set_tx_complete() 设置发送完成回调函数
rt_device_close() 关闭设备

1.2.1 查找串口设备

应用程序根据串口设备名称获取设备句柄,进而可以操作串口设备,查找设备函数如下所示:

rt_device_t rt_device_find(const char* name);
RT-Thread通信管理_第3张图片

1.2.2 打开串口设备

通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备:

rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
RT-Thread通信管理_第4张图片

oflags参数支持下列取值 (可以采用或的方式支持多种取值):

#define RT_DEVICE_FLAG_STREAM       0x040     /* 流模式      */
/* 接收模式参数 */
#define RT_DEVICE_FLAG_INT_RX       0x100     /* 中断接收模式 */
#define RT_DEVICE_FLAG_DMA_RX       0x200     /* DMA 接收模式 */
/* 发送模式参数 */
#define RT_DEVICE_FLAG_INT_TX       0x400     /* 中断发送模式 */
#define RT_DEVICE_FLAG_DMA_TX       0x800     /* DMA 发送模式 */

串口数据接收和发送数据的模式分为 3 种:中断模式轮询模式DMA 模式。在使用的时候,这 3 种模式只能选其一,若串口的打开参数 oflags 没有指定使用中断模式或者 DMA 模式,则默认使用轮询模式
DMA(Direct Memory Access)即直接存储器访问。 DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过 DMA 控制器为 RAM 与 I/O 设备开辟一条直接传送数据的通路,这就节省了 CPU 的资源来做其他操作。使用 DMA 传输可以连续获取或发送一段信息而不占用中断或延时,在通信频繁或有大段信息要传输时非常有用。

1.2.3 控制串口设备

通过控制接口,应用程序可以对串口设备进行配置,如波特率、数据位、校验位、接收缓冲区大小、停止位等参数的修改。控制函数如下所示:

rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
RT-Thread通信管理_第5张图片 控制参数结构体 struct serial_configure 原型如下:
struct serial_configure
{
    rt_uint32_t baud_rate;            /* 波特率 */
    rt_uint32_t data_bits    :4;      /* 数据位 */
    rt_uint32_t stop_bits    :2;      /* 停止位 */
    rt_uint32_t parity       :2;      /* 奇偶校验位 */
    rt_uint32_t bit_order    :1;      /* 高位在前或者低位在前 */
    rt_uint32_t invert       :1;      /* 模式 */
    rt_uint32_t bufsz        :16;     /* 接收数据缓冲区大小,无法动态更改 */
    rt_uint32_t reserved     :4;      /* 保留位 */
};

RT-Thread 提供的配置参数可取值为如下宏定义:

/* 波特率可取值 */
#define BAUD_RATE_2400                  2400
#define BAUD_RATE_4800                  4800
#define BAUD_RATE_9600                  9600
#define BAUD_RATE_19200                 19200
#define BAUD_RATE_38400                 38400
#define BAUD_RATE_57600                 57600
#define BAUD_RATE_115200                115200
#define BAUD_RATE_230400                230400
#define BAUD_RATE_460800                460800
#define BAUD_RATE_921600                921600
#define BAUD_RATE_2000000               2000000
#define BAUD_RATE_3000000               3000000
/* 数据位可取值 */
#define DATA_BITS_5                     5
#define DATA_BITS_6                     6
#define DATA_BITS_7                     7
#define DATA_BITS_8                     8
#define DATA_BITS_9                     9
/* 停止位可取值 */
#define STOP_BITS_1                     0
#define STOP_BITS_2                     1
#define STOP_BITS_3                     2
#define STOP_BITS_4                     3
/* 极性位可取值 */
#define PARITY_NONE                     0
#define PARITY_ODD                      1
#define PARITY_EVEN                     2
/* 高低位顺序可取值 */
#define BIT_ORDER_LSB                   0
#define BIT_ORDER_MSB                   1
/* 模式可取值 */
#define NRZ_NORMAL                      0     /* normal mode */
#define NRZ_INVERTED                    1     /* inverted mode */
/* 接收数据缓冲区默认大小 */
#define RT_SERIAL_RB_BUFSZ              64

接收缓冲区:当串口使用中断接收模式打开时,串口驱动框架会根据 RT_SERIAL_RB_BUFSZ大小开辟一块缓冲区用于保存接收到的数据,底层驱动接收到一个数据,都会在中断服务程序里面将数据放入缓冲区。
RT-Thread 提供的默认串口配置如下:

#define RT_SERIAL_CONFIG_DEFAULT           \
{                                          \
    BAUD_RATE_115200, /* 115200 bits/s */  \
    DATA_BITS_8,      /* 8 databits */     \
    STOP_BITS_1,      /* 1 stopbit */      \
    PARITY_NONE,      /* No parity  */     \
    BIT_ORDER_LSB,    /* LSB first sent */ \
    NRZ_NORMAL,       /* Normal mode */    \
    RT_SERIAL_RB_BUFSZ, /* Buffer size */  \
    0                                      \
}

1.2.4 数据发送与接受

  1. 发送数据

将缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的大小是 size,调用以下函数:

rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
RT-Thread通信管理_第6张图片
  1. 设置发送完成回调函数

回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用,函数设置如下:

rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
RT-Thread通信管理_第7张图片

调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由设备驱动程序回调这个函数并把发送完成的数据块地址 buffer 作为参数传递给上层应用。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。

  1. 设置接收回调函数

当串口收到数据时,通过如下函数来设置数据接收指示,通知上层应用线程有数据到达 :

rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
RT-Thread通信管理_第8张图片

若串口以中断接收模式打开,当串口接收到一个数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在 size 参数里,把串口设备句柄放在 dev 参数里供调用者获取;若串口以 DMA 接收模式打开,当 DMA 完成一批数据的接收后会调用此回调函数。

  1. 接收数据
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
RT-Thread通信管理_第9张图片

1.2.5 关闭串口设备

当应用程序完成串口操作后,可以关闭串口设备,关闭设备接口和打开设备接口需配对使用。通过如下函数完成:

rt_err_t rt_device_close(rt_device_t dev);
RT-Thread通信管理_第10张图片

2、SPI设备

2.1 SPI简介

SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步通信总线,常用于短距离通讯,主要应用于 EEPROM、FLASH、实时时钟、AD 转换器、还有数字信号处理器和数字信号解码器之间。SPI 一般使用 4 根线通信,如下图所示:
RT-Thread通信管理_第11张图片
MOSI –主机输出 / 从机输入数据线(SPI Bus Master Output/Slave Input)。
MISO –主机输入 / 从机输出数据线(SPI Bus Master Input/Slave Output)。
SCLK –串行时钟线(Serial Clock),主设备输出时钟信号至从设备。
CS –从设备选择线 (Chip select)。也叫 SS、CSB、CSN、EN 等,主设备输出片选信号至从设备。
SPI 以主从方式工作,通常有一个主设备和一个或多个从设备。通信由主设备发起,主设备通过 CS 选择要通信的从设备,然后通过 SCLK 给从设备提供时钟信号,数据通过 MOSI 输出给从设备,同时通过 MISO 接收从设备发送的数据。
如下图所示芯片有 2 个 SPI 控制器,SPI 控制器对应 SPI 主设备,每个 SPI 控制器可以连接多个 SPI 从设备。挂载在同一个 SPI 控制器上的从设备共享 3 个信号引脚:SCK、MISO、MOSI,但每个从设备的 CS 引脚是独立的。
RT-Thread通信管理_第12张图片
主设备通过控制 CS 引脚对从设备进行片选,一般为低电平有效。任何时刻,一个 SPI 主设备上只有一个 CS 引脚处于有效状态,与该有效 CS 引脚连接的从设备此时可以与主设备通信。
从设备的时钟由主设备通过 SCLK 提供,MOSI、MISO 则基于此脉冲完成数据传输。SPI 的工作时序模式由 CPOL(Clock Polarity,时钟极性)和 CPHA(Clock Phase,时钟相位)之间的相位关系决定,CPOL 表示时钟信号的初始电平的状态,CPOL 为 0 表示时钟信号初始状态为低电平,为 1 表示时钟信号的初始电平是高电平。CPHA 表示在哪个时钟沿采样数据,CPHA 为 0 表示在首个时钟变化沿采样数据,而 CPHA 为 1 则表示在第二个时钟变化沿采样数据。
QSPI 是 Queued SPI 的简写,是 Motorola 公司推出的 SPI 接口的扩展,比 SPI 应用更加广泛。在 SPI 协议的基础上,Motorola 公司对其功能进行了增强,增加了队列传输机制,推出了队列串行外围接口协议(即 QSPI 协议)。使用该接口,用户可以一次性传输包含多达 16 个 8 位或 16 位数据的传输队列。一旦传输启动,直到传输结束,都不需要 CPU 干预,极大的提高了传输效率。与 SPI 相比,QSPI 的最大结构特点是以 80 字节的 RAM 代替了 SPI 的发送和接收数据寄存器。

2.2 挂载SPI设备

SPI 驱动会注册 SPI 总线,SPI 设备需要挂载到已经注册好的 SPI 总线上:

rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
                                  const char           *name,
                                  const char           *bus_name,
                                  void                 *user_data)

一般 SPI 总线命名原则为 spix, SPI 设备命名原则为 spixy ,如 spi10 表示挂载在 spi1 总线上的 0 号设备。user_data 一般为 SPI 设备的 CS 引脚指针,进行数据传输时 SPI 控制器会操作此引脚进行片选。
若使用 rt-thread/bsp/stm32 目录下的 BSP 则可以使用下面的函数挂载 SPI 设备到总线:

rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_TypeDef* cs_gpiox, uint16_t cs_gpio_pin);

2.3 配置SPI/QSPI设备

2.3.1 配置SPI设备

挂载 SPI 设备到 SPI 总线后需要配置 SPI 设备的传输参数:

rt_err_t rt_spi_configure(struct rt_spi_device *device,
                          struct rt_spi_configuration *cfg)

此函数会保存 cfg 指向的配置参数到 SPI 设备 device 的控制块里,当传输数据时会使用此配置参数,struct rt_spi_configuration 原型如下:

struct rt_spi_configuration
{
    rt_uint8_t mode;        /* 模式 */
    rt_uint8_t data_width;  /* 数据宽度,可取8位、16位、32位 */
    rt_uint16_t reserved;   /* 保留 */
    rt_uint32_t max_hz;     /* 最大频率 */
};

模式: 包含 MSB/LSB、主从模式、 时序模式等,可取宏组合如下:

/* 设置数据传输顺序是MSB位在前还是LSB位在前 */
#define RT_SPI_LSB      (0<<2)                        /* bit[2]: 0-LSB */
#define RT_SPI_MSB      (1<<2)                        /* bit[2]: 1-MSB */

/* 设置SPI的主从模式 */
#define RT_SPI_MASTER   (0<<3)                        /* SPI master device */
#define RT_SPI_SLAVE    (1<<3)                        /* SPI slave device */

/* 设置时钟极性和时钟相位 */
#define RT_SPI_MODE_0   (0 | 0)                       /* CPOL = 0, CPHA = 0 */
#define RT_SPI_MODE_1   (0 | RT_SPI_CPHA)             /* CPOL = 0, CPHA = 1 */
#define RT_SPI_MODE_2   (RT_SPI_CPOL | 0)             /* CPOL = 1, CPHA = 0 */
#define RT_SPI_MODE_3   (RT_SPI_CPOL | RT_SPI_CPHA)   /* CPOL = 1, CPHA = 1 */

#define RT_SPI_CS_HIGH  (1<<4)                        /* Chipselect active high */
#define RT_SPI_NO_CS    (1<<5)                        /* No chipselect */
#define RT_SPI_3WIRE    (1<<6)                        /* SI/SO pin shared */
#define RT_SPI_READY    (1<<7)                        /* Slave pulls low to pause */

数据宽度: 根据 SPI 主设备及 SPI 从设备可发送及接收的数据宽度格式设置为8位、16位或者32位。
最大频率: 设置数据传输的波特率,同样根据 SPI 主设备及 SPI 从设备工作的波特率范围设置。

2.3.2 配置QSPI设备

配置 QSPI 设备的传输参数可使用如下函数:

rt_err_t rt_qspi_configure(struct rt_qspi_device *device, struct rt_qspi_configuration *cfg);

struct rt_qspi_configuration 原型如下:

struct rt_qspi_configuration
{
    struct rt_spi_configuration parent;     /* 继承自 SPI 设备配置参数 */
    rt_uint32_t medium_size;                /* 介质大小 */
    rt_uint8_t ddr_mode;                   /* 双倍速率模式 */
    rt_uint8_t qspi_dl_width ;             /* QSPI 总线位宽,单线模式 1 位、双线模式 2 位,4 线模式 4 位 */
};

2.4 访问SPI设备

在 RT-Thread 中将 SPI 主机虚拟为 SPI 总线设备,应用程序使用 SPI 设备管理接口来访问 SPI 从机器件,主要接口如下所示:

函数 描述
rt_device_find() 根据 SPI 设备名称查找设备获取设备句柄
rt_spi_transfer_message() 自定义传输数据
rt_spi_transfer() 传输一次数据
rt_spi_send() 发送一次数据
rt_spi_recv() 接受一次数据
rt_spi_send_then_send() 连续两次发送
rt_spi_send_then_recv() 先发送后接

2.5 访问QSPI设备

QSPI 的数据传输接口如下所示:

函数 描述
rt_qspi_transfer_message() 传输数据
rt_qspi_send_then_recv() 先发送后接收
rt_qspi_send() 发送一次数据

3、I2C设备

3.1 I2C简介

I2C(Inter Integrated Circuit,即IIC)总线是 PHILIPS 公司开发的一种半双工、双向二线制同步串行总线。I2C 总线传输数据时只需两根信号线,一根是双向数据线 SDA(serial data),另一根是双向时钟线 SCL(serial clock)。SPI 总线有两根线分别用于主从设备之间接收数据和发送数据,而 I2C 总线只使用一根线进行数据收发。
I2C 和 SPI 一样以主从方式工作,不同于 SPI 一主多从的结构,它允许同时有多个主设备存在,每个连接到总线上的器件都有唯一的地址,主设备启动数据传输并产生时钟信号,从设备被主设备寻址,同一时刻只允许有一个主设备。如下图所示:
RT-Thread通信管理_第13张图片

下图所示为 I2C 总线主要的数据传输格式:
RT-Thread通信管理_第14张图片
当总线空闲时,SDA 和 SCL 都处于高电平状态,当主机要和某个从机通讯时,会先发送一个开始条件,然后发送从机地址和读写控制位,接下来传输数据(主机发送或者接收数据),数据传输结束时主机会发送停止条件。传输的每个字节为8位,高位在前,低位在后。数据传输过程中的不同名词详解如下所示:
开始条件: SCL 为高电平时,主机将 SDA 拉低,表示数据传输即将开始。
从机地址: 主机发送的第一个字节为从机地址,高 7 位为地址,最低位为 R/W 读写控制位,1 表示读操作,0 表示写操作。
应答信号: 每传输完成一个字节的数据,接收方就需要回复一个 ACK(acknowledge)。写数据时由从机发送 ACK,读数据时由主机发送 ACK。当主机读到最后一个字节数据时,可发送 NACK(Not acknowledge)然后跟停止条件。
数据: 从机地址发送完后可能会发送一些指令,依从机而定,然后开始传输数据,由主机或者从机发送,每个数据为 8 位,数据的字节数没有限制。
重复开始条件: 在一次通信过程中,主机可能需要和不同的从机传输数据或者需要切换读写操作时,主机可以再发送一个开始条件。
停止条件: 在 SDA 为低电平时,主机将 SCL 拉高并保持高电平,然后在将 SDA 拉高,表示传输结束。

3.2 访问IIC设备

RT-Thread 将 I2C 主机虚拟为 I2C总线设备,I2C 从机通过 I2C 设备接口和 I2C 总线通讯,相关接口如下所示:

函数 描述
rt_device_find() 根据 I2C 总线设备名称查找设备获取设备句柄
rt_i2c_transfer() 传输数据

3.2.1 查找I2C设备

查找I2C设备的函数如下所示:

rt_device_t rt_device_find(const char* name);
RT-Thread通信管理_第15张图片

3.2.2 数据传输

获取到I2C总线设备句柄就可以使用rt_i2c_transfer()进行数据传输,函数原型如下所示:

rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
                          struct rt_i2c_msg         msgs[],
                          rt_uint32_t               num);
RT-Thread通信管理_第16张图片

I2C 消息数据结构原型如下:

struct rt_i2c_msg
{
    rt_uint16_t addr;    /* 从机地址,支持 7 位和 10 位二进制地址,需查看不同设备的数据手册 */
    rt_uint16_t flags;   /* 读、写标志等 */
    rt_uint16_t len;     /* 读写数据字节数 */
    rt_uint8_t  *buf;    /* 读写数据缓冲区指针 */
}

标志 flags 可取值为以下宏定义,根据需要可以与其他宏使用位运算 “|” 组合起来使用:

#define RT_I2C_WR              0x0000        /* 写标志 */
#define RT_I2C_RD              (1u << 0)     /* 读标志 */
#define RT_I2C_ADDR_10BIT      (1u << 2)     /* 10 位地址模式 */
#define RT_I2C_NO_START        (1u << 4)     /* 无开始条件 */
#define RT_I2C_IGNORE_NACK     (1u << 5)     /* 忽视 NACK */
#define RT_I2C_NO_READ_ACK     (1u << 6)     /* 读的时候不发送 ACK */

你可能感兴趣的:(RT,Thread学习)