STM32理论 ——通信

文章目录

1. 数据通信介绍

1.1 并行/串行通信

  • 电子数据通信一般有两种通信方式:并行通信与串行通信;
    • 并行通信
      • 数据各个位(bit)同时传输;

      如果有 8根通信线,就可以同时传输 8bits;

      • 优:速度快;
      • 缺:占用引脚资源多;
    • 串行通信
      • 数据按位(bit)顺序传输;

      可以占用 1个引脚,逐个位传输;

      • 优:占用引脚资源少;
      • 缺:速度慢;

1.2 TTL 电平

  • TTL:TTL 一般是从单片机或者芯片中发出的电平,高电平为 5V(51单片机)或 3.3V(stm32)
  • TTL 电平规定
    • 在逻辑电平中逻辑高电平一般为VCC ,一般为3.3V 或 5V;
    • 数字电路中,由 TTL 电子元器件组成电路的电平是个电压范围,规定:

输出:高电平 >= 2.4V,输出低电平 <= 0.4V,中间则为不稳定区;
输入:高电平 >= 2.0V,输入低电平 <= 0.8V,中间则为不稳定区;

1.2.1 USB 转TTL

USB 转 TTL 模块的作用就是把电平转换到双方都能识别的电平并进行正常通信;

单片机通信接口的电平逻辑和 PC 机通信接口的电平逻辑不同,PC 机上的通信接口有 USB 接口,相应电平逻辑遵照 USB 原则;还有 DB9 接口(九针口),相应电平逻辑遵照 RS-232 原则;

单片机上的串行通信通过单片机的 RXD、TXD、VCC、GND 四个引脚,相应电平逻辑遵照 TTL 原则;

USB 是一种串口(串口是一大类接口,包括但不仅限于 RS232),它拥有复杂的通讯协议,支持热插拔,并可以以非常快的速度传输数据。串口,是指 RS232 串口,这是一种几乎没有协议的传输接口,可以直接收发数据

常见的USB 转串口芯片有 CP2102、PL2303、FT232、CH340 等;

2. USART串口通信(STM32H7系列)

USART(Universal synchronous asynchronous receiver transmitter,通用同步异步收发器),经常使用串口是异步串口,简称 UART.

2.1 串口的硬件框图

在这里插入图片描述

  • IRQ Interface 中断接口:用于实现中断方式的串口唤醒 usart_wkup 和串口的相关中断 usart_it.
  • DMA Interface DMA 接口:实现串口发送 usart_tx_dma 和接收 usart_rx_dma 的 DMA 方式。
  • COM Contronller 串口控制器:串口相关的寄存器基本都在这部分。
  • TxFIFO 和 RxFIFO:串口的发送和接收都支持了硬件 FIFO 功能。
  • TX 和 RX 引脚的互换功能:发送偏移寄存器(TX Shift Reg)和接收偏移寄存器(RX Shift Reg)与 TX 引脚,RX 引脚之间弄了个交叉连接,这里的意思是支持了引脚互换功能,这样在设计 PCB 的时候就可以比较随性了,接反了也没有关系。
  • 发送过程经过的寄存器:依次是 USART_TDR -> TxFIFO ->Tx Shift Reg 偏移寄存器 –> TX 或者 RX 引脚。
  • 接收经过的寄存器:依次是 TX 或者 RX 引脚-> Rx Shift Reg 偏移寄存器->RxFIFO –>USART_RDR。
  • 两个时钟 usart_pclk 和 usart_ker_ck:这两个时钟是独立的,作用如下:
    • usart_pclk:用于为外设总线提供时钟。
    • usart_ker_ck:串口外设的时钟源。

2.2 串口的基本功能特性

只列举了最常用的功能特性;

  • 任意波特率:硬件采用分数波特率发生器系统,可以设置任意的波特率,最高达 4.5Mbits/s.
  • 可编程数据字长度:支持 7bit,8bit 和 9bit.
  • 可配置的停止位:支持 1 或 2 个停止位。
  • 发送器和接收器可以单独使能:比如 GPS 应用只需要串口接收,那么发送的 GPIO 就可以节省出来用作其他功能。
  • 检测标志和中断
    • 接收缓冲器满,可产生中断。串口中断服务程序据此判断是否接收到数据。
    • 发送缓冲器空,可产生中断。串口中断服务程序据此启动发送下一个数据。
    • 传输结束标志,可产生中断。用于 RS485 通信,等最后一个字节发送完毕后,需要控制 RS485收发器芯片切换为接收模式。

2.3 串口的自适应波特率

  • 应用场合
    • 系统的通信速度未知。
    • 系统使用相对低精度的时钟源,并且该机制能够在没有测量时钟偏差的情况下获得正确的波特率。
  • 测量范围
    • 8 倍过采样的情况下,测量速度范围是 usart_ker_ck_pres/65535 到 usart_ker_ck_pres/8。最
      高串口速度是 100MHz / 8 = 12.5Mbps。
    • 16 倍过采样的情况下,速度范围是 usart_ker_ck_pres/65535 到 usart_ker_ck_pres/16。最高
      串口速度是 100MHz / 16 = 6.25Mbsp。

注:usart_ker_ck_pres 在不做串口分频的情况下,是 100MHz。

  • 测量方法
    根据不同的字符特征,支持四种自适应方法。自适应波特率在同步数据接收期间会测量多次,而且每次测量都会跟前一次做比较。
    当前支持四种字符样式进行识别,识别成功后会将中断状态寄存的 ABRF 位置 1,其中模式 2 发送几次 0x7F 基本都可以适应成功,相对好用,模式 3 跟模式 2 差不多,而模式 0 检测起始位的持续时间和模式 1 检测起始位以及第 1 个 bit 的数据位持续时间,这两种模式不好用。

【STM32H743实验例程】实验15:STM32H743串口自适应波特率

2.4 串口的数据帧格式

在这里插入图片描述
串口支持的帧格式如图(M 和 PCE 都是 USART_CR1 寄存器的位,其中 M 位用于控制帧长度,PCE用于使能奇偶校验位):特别注意奇偶校验位,用户在配置的时候可以选择奇校验和偶校验,校验位是占据的最高位。比如选择 M=00,PCE=1,即 7bit 的数据位。

  • 关于奇偶校验

    • 串口发送数据:如果发送的 7bit 数据是 111 0011,这个里面有奇数个 1,那么选择偶校验的情况下,校验位 = 1,凑够偶数个 1,而选择奇校验的情况下,校验位 = 0,因为已经是奇数个 1。校验位不需要用户去计算,是硬件自动生成的。
    • 串口接收数据:根据用户设置的奇校验或者偶校验类型,串口硬件会对接收到的数据做校验,如果失败,USART_ISR寄存器的 PE 位会被置 1。如果使能了对应的中断 PEIE,那么失败的时候还会产生中断。
  • 实际数据发送时,一帧数据中数据位的先后顺序:
    图一,不带奇偶校验位的数据帧格式:
    在这里插入图片描述
    图二,带奇偶校验位的数据帧格式:在这里插入图片描述

  • 起始位:在通信双方没有通信时(通信空闲状态),数据流起始位为1(或0),当双方开始通信时,起始位发生改变,表示通信开始

  • 数据位:8或 9位,有效数据位为 8位,第 9位可配置为奇偶校验位;

  • 奇偶校验位:用于校验数据传输是否出错,提高数据传输的准确率;

  • 停止位:与起始位同理,表示通信结束;

  • 波特率:通信双方约定好的码元符号传输速率;

2.5 同步串口和异步串口的区别

  • 异步通信(UART) 是按约定的波特率逐个字符传输的。每传输一个字符就用起始位来进行收、发双方的同步,不会因收发双方的时钟频率的偏差导致数据传输错误。这种传输方式利用每一帧的起、止信号来建立发送与接收之间的同步。

异步通信的特点是:每帧内部各位均采用固定的时间间隔,而帧与帧之间的间隔是随机的。接收机完全靠每一帧的起始位和停止位来识别字符是正在进行传输还是传输结束。

  • 同步通信(USRT) 的发送和接收双方要保持完全的同步,因此要求接收和发送设备必须使用同一时钟。优点是可以实现高速度、大容量的数据传送;缺点是要求发生时钟和接收时钟保持严格同步,同时硬件复杂。典型的应用有SPI、IIC 通信接口。

同步通信在硬件上一般最少需要两根线,即时钟线和数据线,如IIC总线有SDL(串行时钟线)、SDA(串行数据线),有的时候还需要一个GND(公共参考地)。

可以说,不管是异步通信还是同步通信都需要进行同步,只是异步通信通过传送字符内的起始位来进行同步,而同步通信采用共用外部时钟来进行同步。所以,可以说前者是自同步,后者是外同步。

在这里插入图片描述
在这里插入图片描述

RS232:因为PC 与IC的电平不兼容,中间需加电平转换器;

2.6 单工,半双工和全双工通讯

  • 单工:在一个单工的串行通讯系统中,一般至少有两根线(信号线和地线),数据传送只有一个方向,例如可以使用单工数据传送将数据从一个简单的数据监测系统传送到 PC 上。
  • 半双工:在半双工串行通信系统中,一般同样要求至少有两根线。这里的数据传送是双向的。然而,同一个时刻只能为一个方向。如可以使用半双工通讯机制发送信息到嵌入式模块(来设置参数,比如采样率)。此外,在其他时候,可以使用这个种连接将嵌入式装置上的数据下载到 PC 中。
  • 全双工:在一个全双工的串行通信系统中,一般要求至少有三根线(信号线 A,信号线 B 和地线)。信号线 A 将传输一个方向上的数据,同时信号线 B 传送另一个方向上的数据。
    在这里插入图片描述

2.7 串口的HAL库应用(串口的初始化流程)

一些比较高级的型号芯片会有HAL库,如STM32H7系列;

  • 第 1 步:定义 UART_HandleTypeDef 类型串口结构体变量,比如 UART_HandleTypeDef huart。
  • 第 2 步:使用函数 HAL_UART_MspInit 初始化串口底层,不限制一定要用此函数里面初始化,用户也可以自己实现。
    • 使能串口时钟。
    • 引脚配置。
      • a、使能串口所使用的 GPIO 时钟。
      • b、配置 GPIO 的复用模式。
    • 如果使用中断方式函数 HAL_UART_Transmit_IT 和 HAL_UART_Receive_IT 需要做如下配置。
      • a、配置串口中断优先级。
      • b、使能串口中断。
    • 串口中断的开关是通过函数__HAL_UART_ENABLE_IT() 和 __HAL_UART_DISABLE_IT()来实现,这两个函数被嵌套到串口的发送和接收函数中调用。
    • 如果使用 DMA 方式函数 HAL_UART_Transmit_DMA 和 HAL_UART_Receive_DMA 需要做如下配置。
      • a、声明串口的发送和接收 DMA 结构体变量,注意发送和接收是独立的,如果都使用,那就都需要配置。
      • b、使能 DMA 接口时钟。
      • c、配置串口的发送和接收 DMA 结构体变量。
      • d、配置 DMA 发送和接收通道。
      • e、关联 DMA 和串口的句柄。
      • f、配置发送 DMA 和接收 DMA 的传输完成中断和中断优先级。
  • 第 3 步:配置串口的波特率,位长,停止位,奇偶校验位,流控制和发送接收模式。
  • 第 4 步:如果需要,可以编程高级特性,比如 TX/RX 交换引脚,自动波特率检测。通过第 1 步串口结构体变量 huart 的结构体成员 AdvancedInit 来设置。
  • 第 5 步:串口初始化调用的函数 HAL_UART_Init 初始化。

2.7.1 串口寄存器结构体USART_TypeDef

USART 相关的寄存器是通过 HAL 库中的结构体 USART_TypeDef 定义的,在 stm32h743xx.h 中可以找到这个类型定义:

typedef struct
{
	__IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x00 */
	__IO uint32_t CR2; /*!< USART Control register 2, Address offset: 0x04 */
	__IO uint32_t CR3; /*!< USART Control register 3, Address offset: 0x08 */
	__IO uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x0C */
	__IO uint16_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x10 */
	uint16_t RESERVED2; /*!< Reserved, 0x12 */
	__IO uint32_t RTOR; /*!< USART Receiver Time Out register, Address offset: 0x14 */
	__IO uint16_t RQR; /*!< USART Request register, Address offset: 0x18 */
	uint16_t RESERVED3; /*!< Reserved, 0x1A */
	__IO uint32_t ISR; /*!< USART Interrupt and status register, Address offset: 0x1C */
	__IO uint32_t ICR; /*!< USART Interrupt flag Clear register, Address offset: 0x20 */
	__IO uint16_t RDR; /*!< USART Receive Data register, Address offset: 0x24 */
	uint16_t RESERVED4; /*!< Reserved, 0x26 */
	__IO uint16_t TDR; /*!< USART Transmit Data register, Address offset: 0x28 */
	uint16_t RESERVED5; /*!< Reserved, 0x2A */
	__IO uint32_t PRESC; /*!< USART clock Prescaler register, Address offset: 0x2C */
} USART_TypeDef;

其中__IO 表示 volatile, 这是标准 C 语言中的一个修饰字,表示这个变量是非易失性的,编译器不要将其优化
掉。core_m7.h 文件定义了这个宏:

#define __O volatile /*!< Defines 'write only' permissions */
#define __IO volatile /*!< Defines 'read / write' permissions */
  • USART1、USART2 … UART8 的定义,在 stm32h743xx.h 文件中:
#define PERIPH_BASE ((uint32_t)0x40000000)
#define D2_APB1PERIPH_BASE PERIPH_BASE
#define D2_APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define USART1_BASE (D2_APB2PERIPH_BASE + 0x1000)
#define USART2_BASE (D2_APB1PERIPH_BASE + 0x4400)
#define USART3_BASE (D2_APB1PERIPH_BASE + 0x4800)
#define UART4_BASE (D2_APB1PERIPH_BASE + 0x4C00)
#define UART5_BASE (D2_APB1PERIPH_BASE + 0x5000)
#define USART6_BASE (D2_APB2PERIPH_BASE + 0x1400)
#define UART7_BASE (D2_APB1PERIPH_BASE + 0x7800)
#define UART8_BASE (D2_APB1PERIPH_BASE + 0x7C00)
#define USART1 ((USART_TypeDef *) USART1_BASE) <----- 展开这个宏,(USART_TypeDef *) 0x40011000
#define USART2 ((USART_TypeDef *) USART2_BASE)
#define USART3 ((USART_TypeDef *) USART3_BASE)
#define UART4 ((USART_TypeDef *) UART4_BASE)
#define UART5 ((USART_TypeDef *) UART5_BASE)
#define USART6 ((USART_TypeDef *) USART6_BASE)
#define UART7 ((USART_TypeDef *) UART7_BASE)
#define UART8 ((USART_TypeDef *) UART8_BASE)

这样访问 USART1 的 CR1 寄存器可以采用这种形式:USART1->CR1 = 0.

2.7.2 串口句柄结构体 UART_HandleTypeDef

HAL 库在 USART_TypeDef 的基础上封装了一个结构体 UART_HandleTypeDef,定义如下:

typedef struct
{
	USART_TypeDef *Instance; /*!< UART registers base address */
	UART_InitTypeDef Init; /*!< UART communication parameters */
	UART_AdvFeatureInitTypeDef AdvancedInit; /*!< UART Advanced Features initialization parameters */
	uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */
	uint16_t TxXferSize; /*!< UART Tx Transfer size */
	__IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
	uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
	uint16_t RxXferSize; /*!< UART Rx Transfer size */
	__IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */
	uint16_t Mask; /*!< UART Rx RDR register mask */
	DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */
	DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */
	HAL_LockTypeDef Lock; /*!< Locking object */
	__IO HAL_UART_StateTypeDef gState; /*!< UART state information related to global Handle management
	and also related to Tx operations.
	This parameter can be a value of @ref HAL_UART_StateTypeDef */
	__IO HAL_UART_StateTypeDef RxState; /*!< UART state information related to Rx operations.
	This parameter can be a value of @ref HAL_UART_StateTypeDef */
	__IO uint32_t ErrorCode; /*!< UART Error code */
}UART_HandleTypeDef;
  • *USART_TypeDef Instance:这个参数是寄存器的例化,方便操作寄存器,比如使能串口的发送空中断。SET_BIT(huart->Instance->CR1, USART_CR1_TXEIE).
  • UART_InitTypeDef Init:这个参数是接触最多的,用于配置串口的基本参数,像波特率、奇偶校验、停止位等。
    UART_InitTypeDef 结构体的定义如下:
typedef struct
{
	uint32_t BaudRate; /* 波特率 */
	uint32_t WordLength; /* 数据位长度 */
	uint32_t StopBits; /* 停止位 */
	uint32_t Parity; /* 奇偶校验位 */
	uint32_t Mode; /* 发送模式和接收模式使能 */
	uint32_t HwFlowCtl; /* 硬件流控制 */
	uint32_t OverSampling; /* 过采样,可以选择 8 倍和 16 倍过采样 */
	uint32_t Prescaler; /* 串口分频 */
	uint32_t FIFOMode; /* 串口 FIFO 使能 */
	uint32_t TXFIFOThreshold; /* 发送 FIFO 的阀值 */
	uint32_t RXFIFOThreshold; /* 接收 FIFO 的阀值 */
}UART_InitTypeDef;
  • UART_AdvFeatureInitTypeDef AdvancedInit:这个参数用于配置串口的高级特性。具体支持的功能参数如下:
typedef struct
{
	uint32_t AdvFeatureInit; /* 初始化的高级特性类别 */
	uint32_t TxPinLevelInvert; /* Tx 引脚电平翻转 */
	uint32_t RxPinLevelInvert; /* Rx 引脚电平翻转 */
	uint32_t DataInvert; /* 数据逻辑电平翻转 */
	uint32_t Swap; /* Tx 和 Rx 引脚交换 */
	uint32_t OverrunDisable; /* 接收超时检测禁止 */
	uint32_t DMADisableonRxError; /* 接收出错,禁止 DMA */
	uint32_t AutoBaudRateEnable; /* 自适应波特率使能 */
	uint32_t AutoBaudRateMode; /* 自适应波特率的四种检测模式选择 */
	uint32_t MSBFirst; /* 发送或者接收数据时,高位在前 */
} UART_AdvFeatureInitTypeDef;

配置串口参数,其实就是配置结构体 UART_HandleTypeDef 的成员。比如下面配置为波特率 115200,8
个数据位,无奇偶校验,1 个停止位。

UART_HandleTypeDef UartHandle;
/* USART3 工作在 UART 模式 */
/* 配置如下:
- 数据位 = 8 Bits
- 停止位 = 1 bit
- 奇偶校验位 = 无
- 波特率 = 115200bsp
- 硬件流控制 (RTS 和 CTS 信号) */
UartHandle.Instance = USART3;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if(HAL_UART_Init(&UartHandle) != HAL_OK)
{
	Error_Handler();
}

2.7.3 串口的底层配置(GPIO、时钟、中断等)

如下面配置串口 1,使用引脚 PA9 和 PA10。

/* 串口 1 的 GPIO PA9, PA10 */
#define USART1_CLK_ENABLE() __HAL_RCC_USART1_CLK_ENABLE()
#define USART1_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define USART1_TX_GPIO_PORT GPIOA
#define USART1_TX_PIN GPIO_PIN_9
#define USART1_TX_AF GPIO_AF7_USART1
/*
*********************************************************************************************************
* 函 数 名: InitHardUart
* 功能说明: 配置串口的硬件参数和底层
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void InitHardUart(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;
#if UART1_FIFO_EN == 1 /* 串口 1 */
	/* 使能 GPIO TX/RX 时钟 */
	USART1_TX_GPIO_CLK_ENABLE();
	USART1_RX_GPIO_CLK_ENABLE();
	/* 使能 USARTx 时钟 */
	USART1_CLK_ENABLE();
	/* 配置 TX 引脚 */
	GPIO_InitStruct.Pin = USART1_TX_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate = USART1_TX_AF;
	HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStruct);
	/* 配置 RX 引脚 */
	GPIO_InitStruct.Pin = USART1_RX_PIN;
	GPIO_InitStruct.Alternate = USART1_RX_AF;
	HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStruct);
	/* 配置 NVIC the NVIC for UART */
	HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);
	HAL_NVIC_EnableIRQ(USART1_IRQn);
	/* 配置波特率、奇偶校验 */
	bsp_SetUartParam(USART1, UART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX);
	SET_BIT(USART1->ICR, USART_ICR_TCCF); /* 清除 TC 发送完成标志 */
	SET_BIT(USART1->RQR, USART_RQR_RXFRQ); /* 清除 RXNE 接收标志 */
	SET_BIT(USART1->CR1, USART_CR1_RXNEIE);/* 使能 PE. RX 接受中断 */
#endif
}

串口发送和接收引脚的复用模式选择已经被 HAL 库定义好,放在了stm32h7xx_hal_gpio_ex.h文件里面。比如串口 1 有两个复用

#define GPIO_AF4_USART1 ((uint8_t)0x04) /* USART1 Alternate Function mapping */
#define GPIO_AF7_USART1 ((uint8_t)0x07) /* USART1 Alternate Function mapping */

具体使用那个,要看数据手册,比如我们这里使用引脚 PA9 和 PA10,对应的复用如下:
在这里插入图片描述
那么使用 GPIO_AF7_USART1 即可。

  • 根据情况要清除 TC 发送完成标志和 RXNE 接收数据标志,因为这两个标志位在使能了串口后就已经置位,所以当用户使用了 TC 或者 RX 中断后,就会进入一次中断服务程序,这点要特别注意。
  • HAL 库有个自己的底层初始化回调函数 HAL_UART_MspInit,是弱定义的,用户可以在其它的 C 文件里面实现,并将相对的底层初始化在里面实现。当用户调用 HAL_UART_Init 后,会在此函数里面调 用 HAL_UART_MspInit , 对 应 的 底 层 复 位 函 数 HAL_UART_MspDeInit 是 在 函 数HAL_UART_DeInit 里面被调用的。
    当然,用户也可以自己初始化,不限制必须在两个函数里面实现。

2.7.4 串口的状态标志清除问题

2.8 USART串口通信应用 — RS485

[UART] 两篇不错的中文版RS485文档,值得一读

EIA 一开始将 **RS(Recommended Standard)**作为标准的前缀,不过后来为了便于识别标准的来源,已将 RS 改为 EIA/TIA。电子工业联盟(EIA)已结束运作,此标准目前是电信行业协会(TIA)维护,名称为 TIA-485,但工程师仍继续用 RS-485 来称呼此协议。

2.8.1 背景知识

随着企业信息化的需要,企业在仪表选型时其中的一个必要条件就是要具有联网通讯接口。最初是数据模拟信号输出简单过程量,后来仪表接口是 RS232 接口,这种接口可以实现点对点的通信方式,但这种方式不能实现联网功能。随后出现的RS485 解决了这个问题。
RS485隶属于 OSI 模型物理层的电气特性规定为 2 线、半双工、平衡传输线多点通信的标准,是由电信行业协会(TIA)及电子工业联盟(EIA)联合发布的标准。可以在有电子噪声的环境下进行长距离有效率的通信。在线性多点总线的配置下,可以在一个网络上有多个接收器。因此适用在工业环境中。

2.8.2 电气特性

  • 数据最高传输速率为 10Mbsp,最大的通信距离约为 1219m,传输速率与传输距离成反比,在100Kb/S 的传输速率下,才可以达到最大的通信距离,如果需传输更长的距离,需要加 485 中继器。RS-485总线一般最大支持 32 个节点,如果使用特制的 485 芯片,可以达到 128 个或者 256 个节点,最大的可以支持到 400 个节点。
  • 接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗干扰噪声性好。
  • RS485 的逻辑状态:以TI公司对RS485的逻辑定义为例:
    A 表示非反向输出 non-inverting output,B 表示反向输出 inverting output。
    当 VA > VB 的时候表示逻辑状态 0,被称为 ON。
    当 VA < VB 的时候表示逻辑状态 1,被称为 OFF。
    在这里插入图片描述
    对应实际IC原理图如下:其中DE 发送使能,D 是发送数据端,RE 是接收使能,R 是接收数据端
    在这里插入图片描述
    当用户在 D(Driver)引脚输入逻辑高电平时,将在 485 总线上实现逻辑状态 0,即 ON 状态。接收端 R(Receiver)将收到逻辑高电平。
    当用户在 D(Driver)引脚输入逻辑低电平时,将在 485 总线上实现逻辑状态 1,即 OFF 状态。接收端 R(Receiver)将收到逻辑低电平。
    在这里插入图片描述
    在这里插入图片描述
    发送状态下,越变大于|±1.5V |可以有效表示逻辑状态 1 和逻辑状态 0:
    接收状态下,大于|±200mv|可以有效表示逻辑状态 1 和逻辑状态 0:

2.8.3 硬件设计

STM32H743XIH6 最多可以支持 8 个独立的串口。其中串口 4 和串口 5 和 SDIO 的 GPIO 是共用的,也就是说,如果要用到 SD 卡,那么串口 4 和串口 5 将不能使用。串口 7 和 SPI3 共用,串口 8 和 RGB 硬件接口共用。 串口功能可以分配到不同的 GPIO。常用的管脚分配如下:
在这里插入图片描述

3 USART串口通信(STM32F1系列)

3.1 串口初始化一般步骤

  1. 串口时钟使能,GPIO 时钟使能,若用到端口复用,还需使能复用功能时钟。
  2. 串口复位。
  3. GPIO 端口设置。
  4. 串口参数初始化。
  5. 开启中断并且初始化 NVIC(如果需要开启中断的话)。
  6. 使能串口。
  7. 编写中断处理函数。

3.1.1 关于串口复位

当与STM32串口相连的外设出现异常时,可通过串口复位来实现对该外设的复位,复位后重新配置该外设以让其重新工作,串口复位功能在函数USART_DeInit()中完成。

void USART_DeInit(USART_TypeDef* USARTx);//串口复位
// 例程:复位串口1
USART_DeInit(USART1); 

3.1.2 关于数据的发送与接收

STM32 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR (发送数据寄存器)RDR(接收数据寄存器)

  • 当MCU需要发送数据时,内核或 DMA 外设(一种数据传输方式)把数据从内存写入到 TDR 后,发送控制器将适时地自动把数据从 TDR 加载到发送移位寄存器,然后通过串口线 Tx,把数据一位一位地发送出去,在数据从 TDR 转移到移位寄存器时,会产生发送寄存器TDR 已空事件 TXE,当数据从移位寄存器全部发送出去时,会产生数据发送完成事件 TC,这些事件可以在状态寄存器中查询到。
  • 而接收数据则是一个逆过程,数据从串口线 Rx 一位一位地输入到接收移位寄存器,然后自动地转移到RDR,最后用内核指令或 DMA读取到内存中。

STM32 库函数操作 USART_DR 寄存器发送、接收数据的函数是:

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

在这里插入图片描述

3.1.3 关于串口状态

串口的状态可以通过状态寄存器 USART_SR 读取。USART_SR 的各位描述如图
在这里插入图片描述

  • RXNE(读数据寄存器非空):当该位被置 1 时,表示已经有数据被串口接收到了,并且可以进行读取。这时要做的就是尽快去读取 USART_DR,通过读 USART_DR 可将该位清零,也可以通过向该位写 0,直接手动清零。
  • TC(发送完成):当该位被置位的时候,表示 USART_DR 内的数据已经被发送完成了。如果设置了这个位的中断,就会有中断产生。该位有两种清零方式:
  1. 读 USART_SR,写USART_DR.
  2. 直接向该位写 0.

其他位可查看官方参考手册

// 读取串口状态
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)//应用:读取串口1的RXNE寄存器(读取串口是否接收到数据)
USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
//应用:读取串口1的TC寄存器(读取数据发送是否完成)
USART_GetFlagStatus(USART1, USART_FLAG_TC);

3.1.4 关于串口中断

如果串口需要用到中断的功能,就要配置和开启中断。在库中配置中断用到的函数为void USART_ITConfig(xxx),它的第二个入口参数指明中断的类型,即指明串口在什么时候产生中断。

// 配置中断
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT,FunctionalState NewState)
//指明串口1在接收到数据时产生中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//指明串口1在数据发送完成时产生中断
USART_ITConfig(USART1,USART_IT_TC,ENABLE);

使能串口中断后,查询中断状态和串口中断类型要用到函数ITStatus USART_GetITStatus(xxx)

//获取串口中断类型和状态
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
//判断USART1是否完成数据发送
USART_GetITStatus(USART1, USART_IT_TC)

3.1.5 关于串口GPIO引脚的配置

以配置串口1为全双工模式为例,USART1_TX引脚需要配置成推挽复用输出模式,而USART1_RX需要配置为浮空输入或上拉输入模式。而要配置成半双工同步模式,USART1_RX则不需要配置。
在这里插入图片描述

关于串口GPIO引脚的配置在官方参考手册中可查到。

3.1.6 关于波特率

波特率,即每秒钟传送的码元符号的个数(根据不同的调制方式,在一个码元符号上负载bit位的个数不同),它是对符号传输速率的一种度量,用单位时间内载波调制状态改变的次数来表示,单位是波特每秒或符号每秒 Baud,symbol/s1 Baud/s即指每秒传输1个符号,它与比特率(单位:比特每秒 bps)的关系是 比特率=波特率*单个调制状态对应的二进制位数

3.2 核心代码

以下应用到的函数和定义都引自官方文件 stm32f10x_usart.hstm32f10x_usart.c

3.2.1 串口参数初始化

// 以初始化uart1为例,使用的引脚是PA9和PA10
void uart_init(u32 bound)
{
	// 定义结构体变量
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	// 串口时钟使能,GPIO 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA 时钟
	// 串口复位
	USART_DeInit(USART1); //复位串口 1
	// GPIO 端口设置
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // TX PA.9
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.9
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX PA.10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.10
	// 串口参数初始化
	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); //初始化串口
	#if EN_USART1_RX //如果使能了接收
	// 配置中断控制器 NVIC
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
	NVIC_Init(&NVIC_InitStructure); //中断优先级初始化
	// 开启中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口的RXNE中断
	#endif
	// 使能串口
	USART_Cmd(USART1, ENABLE); //使能串口
}

3.2.2 串口中断服务函数

/***************************************************************************
** 函数名称   :   USART3_IRQHandler
** 功能描述   :  	串口3 中断服务函数
** 输入变量   :   无
** 返 回 值   :  	无
** 最后修改人 :   xxx
** 最后更新日期:  20210319
** 说    明   :		无
***************************************************************************/
void USART3_IRQHandler(void)                	//串口3中断服务程序
{
	u8 Res;
#ifdef OS_TICKS_PER_SEC	 	//如果时钟节拍数定义了,说明要使用ucosII了.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		Res =USART_ReceiveData(USART3);//(USART3->DR);	//读取接收到的数据
	
	if((USART_RX_STA&0x8000)==0)//接收未完成
		{
		if(USART_RX_STA&0x4000)//接收到了0x0d
			{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
			}
		else //还没收到0X0D
			{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
						USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
						USART_RX_STA++;
						if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
			}
		}   		 
	 } 
#ifdef OS_TICKS_PER_SEC	 	//如果时钟节拍数定义了,说明要使用ucosII了.
	OSIntExit();  											 
#endif
} 

3.2.3 串口应用例程

注:USART_RX_STA是正点原子自定义的寄存器,它定义在正点原子编写的usart.c文件中,寄存器的位定义为:
bit7,接收完成标志
bit6,接收到回车0x0d
bit5~0,接收到的有效字节数
回车0x0d,换行0x0A

#include "sys.h"
#include "usart.h"

int main(void)
{
	u8 t;
	u8 len;
	u16 times=0;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置 NVIC 中断分组 2
	uart_init(115200); //串口初始化波特率为 115200
	
	while(1)
	{
		if(USART_RX_STA&0x80) //如果串口完成数据接收
		{ 
			len=USART_RX_STA&0x3f; //得到此次接收到的数据长度
			printf("\r\n 您发送的消息为:\r\n\r\n");
			for(t=0;t<len;t++)
			{ 
				USART_SendData(USART1, USART_RX_BUF[t]); //向串口 1 发送数据
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET); //等待发送结束
			}
			printf("\r\n"); //插入回车换行
			USART_RX_STA=0; //寄存器清零
		}else
		{ 
			times++;
			if(times%5000==0)
			{ 
				printf("@_@\r\n");
			}
			if(times%200==0)printf("请输入数据,以回车键结束\n");
		}
	}
}

3.3 串口的硬件框图

详见中文参考手册

  • 数据接收:数据通过RX 引脚接收 - 编解码模块 - 数据逐位被接收位移寄存器接收 - 接收数据寄存器 - CPU 读数据;
    在这里插入图片描述
  • 数据发送:CPU 写数据到发送数据寄存器 - 数据逐位发送到发送位移寄存器 - 数据按波特率发送到TX 引脚;
    在这里插入图片描述
  • 波特率发生器:接收和发送数据的波特率来自于同一个波特率发生器,其中STM32F10x 系列的串口 1时钟来源于 f_PCLK1时钟,串口 2~4来源于f_PCLK2时钟;
    在这里插入图片描述

3.4 常用串口寄存器

更多详见官方参考手册
置位=1
复位=清零=0
寄存器中所有的 “保留” 都表示被强制清零。

3.4.1 状态寄存器(USART_SR)

  • Status register,可读取一些串口当前状态的标志位;
    在这里插入图片描述
    • RXNE(Read data register not empty,读数据寄存器非空):当该位被置位时,表示已经有数据被串口接收到了,并且可以进行读取。这时要做的就是尽快去读取 USART_DR,通过读 USART_DR 可将该位清零,也可以通过向该位写 0,直接手动清零。
    • TC( Transmission complete,发送完成):当该位被置位的时候,表示 USART_DR 内的数据已经被发送完成了。如果设置了这个位的中断,就会有中断产生。该位有两种清零方式:
    1. 先读 USART_SR,再写USART_DR.
    2. 直接向该位写 0.

3.4.2 数据寄存器(USART_DR)

Data register,相当于一个数据缓冲器,CPU 要往外发数据,向它发送,要读数据,向它读取;
在这里插入图片描述

  • DR(Data value,数据值):包含了发送或接收的数据。它兼具读和写的功能,由两个寄存器组成,一个给发送用(TDR,发送寄存器),一个给接收用(RDR,接收寄存器)。

3.4.3 波特率寄存器(USART_BRR)

  • Baud rate register,波特率的配置方法如下;
    在这里插入图片描述

3.4.4 控制寄存器 1(USART_CR1)

Control register 1,主要控制串口接收/发送、中断使能;
在这里插入图片描述

  • UE(USART enable,USART使能):当该位被清零,则在当前字节传输完成后USART的分频器和输出停止工作,以减少功耗。
  • M(字长,Word length):定义数据字的长度,
    • 0:一个起始位,8个数据位,n个停止位;
    • 1:一个起始位,9个数据位,n个停止位。

在数据传输过程中(发送或者接收时),不允许修改该位 。

  • PS(Parity selection,校验选择 ):选择采用偶校验还是奇校验,

    • 0:偶校验;
    • 1:奇校验。
  • TCIE(Transmission complete interrupt enable,发送完成中断使能 ):用于选择串口中断的类型,当该位被置位时,USART_SR中的TC为’1’时,产生USART中断。

  • RXNEIE(RXNE interrupt enable,接收缓冲区非空中断使能 ):用于选择串口中断的类型,当该位被置位时,USART_SR中的ORE或者RXNE为’1’时,产生USART中断。

  • TE(Transmitter enable,发送使能 ):当该位被置位时,使能串口发送。

  • RE (Receiver enable,接收使能):当该位被置位时,使能串口接收,并开始搜寻RX引脚上的起始位。

3.4.5 控制寄存器 2(USART_CR2)

在这里插入图片描述

  • STOP (STOP bits,停止位):设置停止位位数,
    • 00:1个停止位;
    • 01:0.5个停止位;
    • 10:2个停止位;
    • 11:1.5个停止位;

注:UART4和UART5不能用0.5停止位和1.5停止位。

  • CLKEN (Clock enable,时钟使能) :使能CK引脚
    • 0:禁止CK引脚;
    • 1:使能CK引脚

3.5 串口应用 - RS232与RS485

串口是一种接口标准,其规定了物理层接口的电气标准,常用的典型的串行通信标准接口有RS232RS485,他们定义了通信电压和阻抗等,但没有定义软件协议;

  1. RS232

    • 优点:推出时间早,因为大多数设备对传输速率、抗干扰能力和传输距离要求不高,所以它是应用最广泛的串口通信标准;支持全双工通信,收发同步;
    • 缺点:定义的信号电平较高(+/-12V),容易损坏接口电路芯片;传输速度低,异步传输时波特率为 20Kbps;容易产生共模干扰,抗噪声干扰能力若;传输距离有限,一般只能传输 50米左右;
  2. RS485

    • 优点:接口电平低(以两线电压差为 +(2~6)V 表示1,以两线电压差为 -(2~6)V 表示0),不易损坏接口芯片;传输速率高,10米内最高传输速率可达 35Mbps,1200米内传输速率可达 100Kbps;抗干扰能力强,采用平衡驱动器和差分接收器的组合,抗共模干扰能力强,即抗噪声干扰性能好;传输距离远,支持节点多,总线可达 1200米,最大支持 32个节点;优点多,应用也逐渐广泛;
    • 缺点:只支持半双工通信,即发的时候不能接,接时不能收;

3.6 串口应用之串口监听

串口监听就是用一个串口去读取并显示另一个串口接收到的信息,最直接的应用方法就是把一个串口的printf函数加到另一个串口的接收中断服务函数中;

3.7 Mac 系统下串口退格问题修复

  • 问题:在使用Mac 系统终端软件进行串口调试下,会出现下图按下退格(Backspace)后光标退格而内容不删除的问题;
    在这里插入图片描述
  • 解决方法:(以串口2为例)在串口中断服务函数中,监测是否有退格符\b 的输入,若有,则发送退格的同时进行内容删除,内容删除的实现方式是以空格填充后在进行退格:
void USART2_IRQHandler(void)                	//串口2中断服务程序
{
    u8 Res;
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)    读取接收中断标志位后自动清零接收中断标志位
    {
        Res =USART_ReceiveData(USART2);//(USART1->DR);	//读取接收到的数据    读取该数据可以自动将该寄存器清零

        if(Res=='\b') // 若有退格符输入
        {
            if(Uart.flag2_see==1)
            {
                if(Uart.rec2_id>0)
                {
                    Send2('\b'); // Send2 函数必须包含串口发送完成再做下一步动作的功能
                    Send2(' ');
                    Send2('\b');
                    Uart.rec2_id--;
                }
            }
        }
        else if(Res=='\r')
        {
            Uart.string2[Uart.rec2_id]='\0';
            if(Uart.flag2_see==1) {
                Send2('\r');
                Send2('\n');
            }
            Uart.Uart2_flag=1;
        }
        else if(Res=='\n')
        {
//					Uart.string[Uart.rec1_id]='\0';
//					if(Uart.flag1_see==1){Send1('\r');Send1('\n');}
//					Uart.Uart1_flag=1;
        }
        else
        {
            if(Uart.rec2_id<35)Uart.string2[Uart.rec2_id++]=Res;
            if(Uart.flag2_see==1)USART_SendData(USART2,Res);

        }

    }
}

修改后效果如下:
在这里插入图片描述

4. RS485通信

RS485和RS232都是串口通信的应用;

4.1 RS485通信控制步进电机

与用脉冲控制的步进电机不同,RS485通信的步进电机所有参数配置通过程序向电机驱动器发特定的指令完成参数配置而不是通过驱动器拨码,RS485通信的步进电机驱动器上的拨码功能一般用于设置波特率;

函数索引:
void motorInit(void);
char motorGoHome(void);
void motorLoadAllParameters(void);
void motorMovePluse(int32_t pluse);
char motorSetAndSavePulse(char *string);
void motorSetSpeed(int32_t sp);
char motorSetAndSaveSpeed(char *string);
char motorSaveCurrentPosition(char *string);
void motorTX(char *data,char length);
char motorReadOneByte(char *motorCommand,char length);
void motorWriteOneData(char *motorCommand,char length,uint16_t data);
void motorWriteTwoData(char *motorCommand,char length,uint32_t data,char symbol);
/**********************************************************************************************************
*	函 数 名: motorInit
*	功能说明: 初始化电机参数,检测电机通信状态
*	形    参:无
*	返 回 值: 无
**********************************************************************************************************/
char YAK_Motor_Command1[8] = {0x01,0x03,0x00,0x00,0x00,0x01,0x84,0x0a};//电机指令定义的一个例子
void motorInit(void)
{
   char array_length;//定义字符串长度变量
			 /*
		     检测电机通信状态,读取驱动器型号代号
		   */
			 array_length = sizeof(YAK_Motor_Command1);		
			 memset(Uart.string,0,sizeof(Uart.string)); // 串口缓冲区清零
			 Uart.rec1_id = 0;
		   motorWriteCommand(YAK_Motor_Command1,array_length); //读取驱动器型号代号
		   delay_ms(20); 
	//		 printf("string is %X\r\n",Uart.string);//串口回显查看指令内容
		 
			 if(Uart.string[1]==0x03) // 若电机通信成功,则返回0x0301或0x0302
       {
//				  printf("Motor connected successfully\r\n");
					/*
						设置细分数 - 3200pulse/rev
					*/
//					YAK_Motor_Command35[4] = 0x00;
//					YAK_Motor_Command35[5] = 0x04;
//					calculateCRC(YAK_Motor_Command35,6); // 计算CRC校验码
//					YAK_Motor_Command35[6] = Variable.CRC_H;
//					YAK_Motor_Command35[7] = Variable.CRC_L;
   				array_length = sizeof(YAK_Motor_Command35);
					memset(Uart.string,0,sizeof(Uart.string));
					Uart.rec1_id = 0;
					motorWriteCommand(YAK_Motor_Command35,array_length); //设置电机细分,3200pu/rev
					delay_ms(100);

					/*
						设置锁机电流 - 半流
					*/
//					YAK_Motor_Command36[4] = 0x00;
//					YAK_Motor_Command36[5] = 0x01;
//					calculateCRC(YAK_Motor_Command36,6); // 计算CRC校验码
//					YAK_Motor_Command36[6] = Variable.CRC_H;
//					YAK_Motor_Command36[7] = Variable.CRC_L;
					array_length = sizeof(YAK_Motor_Command36);
					memset(Uart.string,0,sizeof(Uart.string));
					Uart.rec1_id = 0;
					motorWriteCommand(YAK_Motor_Command36,array_length); //设置电机锁机电流,半流
					delay_ms(100);
					
					/*
						设置最大运行电流 - 2.3A
					*/
//					YAK_Motor_Command37[4] = 0x00;
//					YAK_Motor_Command37[5] = 0x06;
//					calculateCRC(YAK_Motor_Command37,6); // 计算CRC校验码
//					YAK_Motor_Command37[6] = Variable.CRC_H;
//					YAK_Motor_Command37[7] = Variable.CRC_L;
					array_length = sizeof(YAK_Motor_Command37);
					memset(Uart.string,0,sizeof(Uart.string));
					Uart.rec1_id = 0;
					motorWriteCommand(YAK_Motor_Command37,array_length); //设置电机运行电流 - 2.3A(峰值3.3A)
					delay_ms(100);
					
					/*
						电机使能
					*/
					array_length = sizeof(YAK_Motor_Command22);
					memset(Uart.string,0,sizeof(Uart.string));
					Uart.rec1_id = 0;
					motorWriteCommand(YAK_Motor_Command22,array_length); //电机使能
					delay_ms(100);
					
					/*
						保存所有参数到EEPROM
					*/
					memset(Uart.string,0,sizeof(Uart.string));
					Uart.rec1_id = 0;
					array_length = sizeof(YAK_Motor_Command21);
					motorWriteCommand(YAK_Motor_Command21,array_length); 保存所有参数到EEPROM
					delay_ms(500);
					delay_ms(500);
					delay_ms(300);
					if(Uart.string[6]==0X78)//数据保存成功
					{
							AT24C02_WriteOneByte(9,0,2); // 输入参数:Rom内的地址,数据,通道
							AT24C02_WriteOneByte(10,0,2);   // hall电机自动识别标志位
							printf("Motor Parameters Save Pass\r\n");
					}
					else 
					{
						  printf("Motor Parameters Save Fail\r\n");
					}
					
					return;
			 }
			 printf("Motor initialization pass\r\n");
			 delay_ms(10);		      	 
		 /*
		   若自动检测电机失败,则尝试手动强制识别HALL电机厂商
		 */
		 Variable.hall_motor_vendor_flag = AT24C02_ReadOneByte(9,2);	 	 
		 AT24C02_WriteOneByte(10,1,2);    // hall电机厂商强制设置标志位	 
}
/**********************************************************************************************************
*	函 数 名: motorGoHome
*	功能说明: 电机回原点
*	形    参:无
*	返 回 值: 电机回原点结果,0-回原点失败,1-回原点成功
**********************************************************************************************************/
char motorGoHome(void)
{
	uint32_t TimeOut = 0;
	//motorWriteCommand(YAK_Motor_Command43,8); // 回原点模式:反向限位+原点模式
	motorWriteCommand(YAK_Motor_Command48,8); // 回原点模式:正向限位+原点模式
	motorWriteCommand(YAK_Motor_Command44,8); // 回原点加减速时间
	motorWriteCommand(YAK_Motor_Command45,8); //回原点查询速度
	motorWriteCommand(YAK_Motor_Command46,8); //回原点速度
	motorWriteCommand(YAK_Motor_Command47,8); //回原点使能
	while(1)
	{
			TimeOut++;
			memset(Uart.string,0,sizeof(Uart.string)); // 清除串口接收缓冲区
			Uart.rec1_id = 0;
		  if(LIGHT_CURTAIN_SENSOR == 1)//光栅检测与急停
			{
				motorWriteCommand(YAK_Motor_Command25,8);//电机急停
				printf("Safety Light Curtains Trigger!\r\n");
				printf("Motor Go Home Fail!\r\n");
				return 0;
			} 
			motorWriteCommand(YAK_Motor_Command41,8);//查询电机运行状态
			delay_ms(100);
			if(Uart.string[4]&0x03)
			{
				printf("Motor Go Home Pass\r\n");
				return 1;
			}
			if(TimeOut==100)//回原点超时
			{
					printf("Motor Go Home Fail!\r\n");
				 return 0;
			}			
	}
}
/**********************************************************************************************************
*	函 数 名: motorLoadallParameters
*	功能说明: 读取EEPROM中保存的电机位置和速度值,并打印出来
*	形    参:无
*	返 回 值: 无
**********************************************************************************************************/
void motorLoadAllParameters(void)
{
		// 读EEPROM中保存的电机位置值
		Point.temp[3] = AT24C02_ReadOneByte(81,2);
		Point.temp[2] = AT24C02_ReadOneByte(82,2);
		Point.temp[1] = AT24C02_ReadOneByte(83,2);
		Point.temp[0] = AT24C02_ReadOneByte(84,2);

		// 读EEPROM中保存的电机速度值
		Speed.temp[1] = AT24C02_ReadOneByte(85,2);
		Speed.temp[0] = AT24C02_ReadOneByte(86,2);
	
//		Lev_lig = AT24C02_ReadOneByte(88,2);
		
		printf("Positicn:%d\r\n",Point.point);
		printf("MotorSpeed:%d\r\n",Speed.MotorSpeed);
//		printf("Lev_lig:%d\r\n",Lev_lig);
}
/**********************************************************************************************************
*	函 数 名: motorMovePluse
*	功能说明: 电机走指定脉冲数
*	形    参:无
*	返 回 值: 无
**********************************************************************************************************/
void motorMovePluse(int32_t pluse)
{
		// 逐位读取脉冲数到指令
		YAK_Motor_Command30[7]  = (uint8_t)((pluse>>24)&0x00ff);
		YAK_Motor_Command30[8]  = (uint8_t)((pluse>>16)&0x00ff);
		YAK_Motor_Command30[9]  = (uint8_t)((pluse>>8)&0x00ff);
		YAK_Motor_Command30[10]  = (uint8_t)((pluse)&0x00ff);

		Motor_GET_CRC(YAK_Motor_Command30,11); // 计算CRC校验码
		YAK_Motor_Command30[11] = CRC_H;
		YAK_Motor_Command30[12] = CRC_L;
		MOTOR_Write(YAK_Motor_Command30,13);  // 向电机发送指令 - 以位置模式运行总脉冲数
		MOTOR_Write(YAK_Motor_Command26,8);	// 向电机发送指令 - 以相对位置模式启动
}
/**********************************************************************************************************
*	函 数 名: motorSetAndSavePulse
*	功能说明: 设置并保存从串口设置的电机脉冲数
*	形    参:string:串口输入的字符串
*	返 回 值: 0-代码运行成功,1-代码运行失败
**********************************************************************************************************/
char motorSetAndSavePulse(char *string)
{
	int32_t pluse;			
	char  *p  = 0;
	char  *s = &string[5];
	
	//从串口中提取脉冲数
	if(strncmp(string,"MOVE ",5)==0)
	{
		pluse = strtol(s,&p,10);
		printf("Move:%d\r\n",pluse);

		motorSetSpeed(Speed.MotorSpeed); // 设置速度值
		motorMovePluse(pluse); // 向电机发送运动脉冲
//		Point.point += pluse;
//		printf("Cur_Pluse=%d\r\n",Point.point);
		return 0;
	}
	return 1;
}
/**********************************************************************************************************
*	函 数 名: motorSetSpeed
*	功能说明: 设置电机运动速度
*	形    参:sp:速度值
*	返 回 值: 无
**********************************************************************************************************/
void motorSetSpeed(int32_t sp)
{
	  // 逐位读取速度值到指令
		YAK_Motor_Command31[4]  = (uint8_t)((sp>>8)&0x00ff);
		YAK_Motor_Command31[5]  = (uint8_t)((sp)&0x00ff);
	
		Motor_GET_CRC(YAK_Motor_Command31,6); // 计算CRC校验码
		YAK_Motor_Command31[6] = Variable.CRC_H;
		YAK_Motor_Command31[7] = Variable.CRC_L;
		MOTOR_Write(YAK_Motor_Command31,8); // 向电机发送指令 - 设置电机最大速度
}


/**********************************************************************************************************
*	函 数 名: motorSetAndSaveSpeed
*	功能说明: 设置并保存从串口设置的电机速度值
*	形    参:string:串口输入的字符串
*	返 回 值: 0-代码运行成功,1-代码运行失败
**********************************************************************************************************/
char motorSetAndSaveSpeed(char *string)
{
	int32_t speed;			
	char  *p  = 0;
	char *s = &string[5];
	
	//从串口中提取速度值
	if(strncmp(string,"SPEED ",6)==0)
	{
		speed = strtol(s,&p,10);
		printf("MotorSpeed:%d\r\n",speed);

		motorSetSpeed(speed);//设置电机速度值
		//保存电机速度值到EEPROM
		Speed.MotorSpeed = speed;
		AT24C02_WriteOneByte(85,Speed.temp[1],2);delay_ms(5);	
		AT24C02_WriteOneByte(86,Speed.temp[0],2);delay_ms(5);		
		return 0;
	}
	return 1;
}
/**********************************************************************************************************
*	函 数 名: motorSaveCurrentPosition
*	功能说明: 保存电机当前位置,即保存电机固定的脉冲数
*	形    参:string:串口输入的字符串
*	返 回 值: 0-代码运行成功,1-代码运行失败
**********************************************************************************************************/
char motorSaveCurrentPosition(char *string)
{
		if(strncmp(string,"SAVE POSITION",10)==0)
		{
			memset(Uart.string,0,sizeof(Uart.string)); //清除UART接收缓存区
			Uart.rec1_id = 0;
			MOTOR_Write(YAK_Motor_Command7,8); // 读取驱动器当前位置高字节,单次读取两个字节
			Point.temp[3] = Uart.string[3];
			Point.temp[2] = Uart.string[4];
			Point.temp[1] = Uart.string[5];
			Point.temp[0] = Uart.string[6];
			printf("Motor Positicn=%d\r\n",Point.point);
			
			// 保存位置值到EEPROM
			AT24C02_WriteOneByte(81,Point.temp[3],2);delay_ms(5);	
			AT24C02_WriteOneByte(82,Point.temp[2],2);delay_ms(5);		
			AT24C02_WriteOneByte(83,Point.temp[1],2);delay_ms(5);		
			AT24C02_WriteOneByte(84,Point.temp[0],2);delay_ms(5);		
			return 0;
		}
		return 1;
}
/**********************************************************************************************************
*	函 数 名: motorTX
*	功能说明: 向电机逐位发送指令
*	形    参:data:数据,length:数据长度
*	返 回 值: 无
**********************************************************************************************************/
void motorTX(char *data,char length)
{
  u8 j;
	RS485_TX_EN = 1;//使能485TX
	for(j=0;j<length;j++)
	{
	  Send1(data[j]);//串口1发送单个字符
	}
	RS485_TX_EN = 0;//使失能485TX
}
/**********************************************************************************************************
*	函 数 名: motorReadOneByte
*	功能说明: 从电机读一个字节数据
*	形    参:motorCommand:电机指令,length:指令长度
*	返 回 值: data:读取得到的数据
**********************************************************************************************************/
char motorReadOneByte(char *motorCommand,char length)
{
  uint16_t data; 
	memset(Uart.string,0,sizeof(Uart.string));
	Uart.rec1_id = 0;
	motorTX(motorCommand,length);delay_ms(10);//向电机发送指令
  data = Uart.string[4];//读取电机返回的内容
	return data;	 
}
/**********************************************************************************************************
*	函 数 名: motorWriteOneData
*	功能说明: 向电机写一个数据
*	形    参:motorCommand:电机指令,length:指令长度,data:要写入的数据内容
*	返 回 值: 无
**********************************************************************************************************/
void motorWriteOneData(char *motorCommand,char length,uint16_t data)
{
  uint16_t a,b;
	a = data%65536/256;
	b = data%65536%256;
	motorCommand[4] = a;
	motorCommand[5] = b;
	Motor_GET_CRC(motorCommand,6);
	motorCommand[6] = CRC_H;
	motorCommand[7] = CRC_L;
	memset(Uart.string,0,sizeof(Uart.string));Uart.rec1_id = 0;	//串口处理
	motorTX(motorCommand,length);
	delay_ms(5);
}
/**********************************************************************************************************
*	函 数 名: motorWriteTwoData
*	功能说明: 向电机写两个数据
*	形    参:motorCommand:电机指令,length:指令长度,data:要写入的数据内容,symbol:正负值符号
*	返 回 值: 无
**********************************************************************************************************/
void motorWriteTwoData(char *motorCommand,char length,uint32_t data,char symbol)
{
  uint16_t a,b,c,d;
	uint32_t  abs_value;
	if(symbol=='+')
	{
		a = data/65536/256;
		b = data/65536%256;
		c = data%65536/256;
		d = data%65536%256;
	}
	else if(symbol=='-')
	{
		abs_value = ~data+1;
	  a = abs_value/65536/256;
		b = abs_value/65536%256;
		c = abs_value%65536/256;
		d = abs_value%65536%256;
	}
	motorCommand[7] = a;
	motorCommand[8] = b;
	motorCommand[9] = c;
	motorCommand[10] = d;
	Motor_GET_CRC(YAK_Motor_Command30,11);
	motorCommand[11] = Variable.CRC_H;
	motorCommand[12] = Variable.CRC_L;
	memset(Uart.string,0,sizeof(Uart.string));Uart.rec1_id = 0;		
	motorTX(motorCommand,length);
	delay_ms(5);	
}

4.2 硬件应用 - RS485收发器 - SP3485

SP3485 datasheet
图中A、B 为总线接口,用于连接 485总线,RO 为接收输出端,DI 为发送数据接收端,RE 为接收使能信号(低电平有效),DE 为发送使能信号(高电平有效);

实际应用时,RO、DI 接MCU IO引脚,RE、DE连一起接到某器件控制引脚,实现 485半双工特性;

在这里插入图片描述

5.SPI通信

SPI(Serial Peripheral interface —— 串行外围设备接口)是一种高速、全双工、同步串行的通信协议,最多只占用四根线(三线式或四线式):

  • MISO(SDI) :Master In Slave Out,主设备数据输入,从设备数据输出。
  • MOSI(SDO):Master Out Slave In,主设备数据输出,从设备数据输入。
  • SCLK :Serial Clock,时钟信号,由主设备产生。
  • CS (SS):Chip Select/Slave Select,从设备片选信号,由主设备控制,实现一主多从;

SPI有主、从两种模式,通常由一个主模块和一个或多个从模块组成(不支持多主机),主模块选择一个从模块进行同步通信,从而完成数据的交换。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起,当存在多个从设备时,通过各自的片选信号进行管理。

  • 一主一从:
    在这里插入图片描述
  • 一主多从:
    在这里插入图片描述
  • SPI 通信协议:
    1. 主设备发起通信开始信号,将CS/SS拉低,启动通信;
    2. 主设备通过发送时钟信号,来告诉从设备进行写数据或者读数据操作(采集时机可能是时钟信号的上升沿(从低到高)或下降沿(从高到低),因为SPI有四种模式,具体看芯片手册),它将立即读取数据线上的信号,这样就得到了一位数据(1bit),以此依次读/写全部数据位;
    3. 通信完成后,主设备发起通信结束信号,将CS/SS拉高,结束通信;

例如,下图示例中简单模拟SPI通信流程,主机拉低NSS片选信号,启动通信,并且产生时钟信号,上升沿触发边沿信号,主机在MOSI线路一位一位发送数据0X53,在MISO线路一位一位接收数据0X46,如下图所示:
在这里插入图片描述

  • SPI 通信的优缺点:
    • 优点:
      1. 无起始位和停止位,因此数据位可以连续传输而不会被中断;
      2. 没有像I2C 这样复杂的从设备寻址系统,从站不需要唯一地址
      3. 数据传输速率比I2C 更高(几乎快两倍);
      4. 分离的MISO 和MOSI 信号线,因此可以同时发送和接收数据;
      5. 极其灵活的数据传输,不限于8位,它可以是任意大小的字;
      6. 非常简单的硬件结构。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同)。
    • 缺点
      1. 使用四根信号线(I2C和UART使用两根信号线);
      2. 无法确认是否已成功接收数据(I2C拥有此功能);
      3. 没有任何形式的错误检查,如UART中的奇偶校验位;
      4. 只允许一个主设备;
      5. 没有硬件从机应答信号(主机可能在不知情的情况下无处发送);
      6. 没有定义硬件级别的错误检查协议;
      7. 与RS-232和CAN总线相比,只能支持非常短的距离;

参考:
CSDN - 一文搞懂SPI通信协议

5.1 W25Q64存储器应用

  • 硬件连接:
    在这里插入图片描述
  • 核心代码:
// 代码索引:
void SPI2_Init(void);
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler);
u8 SPI2_ReadWriteByte(u8 TxData);
void SPI_Flash_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);
void SPI_Flash_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
#include "spi.h"
//将MCU SPI2配置成主机模式

//对 SPI2 的初始化
void SPI2_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	SPI_InitTypeDef SPI_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//GPIOB 时钟使能
	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_InitStructure.SPI_Mode = SPI_Mode_Master; //设置 SPI 工作模式:设置 SPI为主机模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8 位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//选择了串行时钟的稳态:时钟悬空高
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由硬件管理
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频 256
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure); //根据指定的参数初始化外设 SPI2 寄存器
	
	SPI_Cmd(SPI2, ENABLE); //使能 SPI2 外设
	SPI2_ReadWriteByte(0xff); //启动传输
} 

//SPI 速度设置函数
//SpeedSet://SPI_BaudRatePrescaler_256 256 分频 (SPI 281.25K@sys 72M)
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); 
} 

//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
	u8 retry=0;
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //等待发送区为空
	{
		retry++;
		if(retry>200)return 0;
	} 
	SPI_I2S_SendData(SPI2, TxData); //通过外设 SPIx 发送一个数据
	retry=0;
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //等待接收完一个 byte
	{ 
		retry++;
		if(retry>200)return 0;
	}
	return SPI_I2S_ReceiveData(SPI2); //返回通过 SPIx 最近接收的数据
}

//读取 SPI FLASH W25Q64中的特定数据
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大 65535)
void SPI_Flash_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead) 
{ 
	u16 i; 
	SPI_FLASH_CS=0; //使能器件 
	 SPI2_ReadWriteByte(W25X_ReadData); //发送读取命令 
	 SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址 
	 SPI2_ReadWriteByte((u8)((ReadAddr)>>8)); 
	 SPI2_ReadWriteByte((u8)ReadAddr); 
	 for(i=0;i<NumByteToRead;i++)
	{ 
		 pBuffer[i]=SPI2_ReadWriteByte(0XFF); //循环读数 
	 }
	SPI_FLASH_CS=1; //失能器件 
}

//写 SPI FLASH W25Q64中的特定数据
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大 65535) 
u8 SPI_FLASH_BUFFER[4096];
void SPI_Flash_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) 
{ 
	u32 secpos;
	u16 secoff;
	u16 secremain; 
	u16 i; 
	u8 * SPI_FLASH_BUF; 
	 SPI_FLASH_BUF=SPI_FLASH_BUFFER; 
	secpos=WriteAddr/4096; //扇区地址 0~511 for w25x16
	secoff=WriteAddr%4096; //在扇区内的偏移
	secremain=4096-secoff; //扇区剩余空间大小 
	//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite); //测试用
	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于 4096 个字节
	while(1) 
	{
		SPI_Flash_Read(SPI_FLASH_BUF,secpos*4096,4096); //读出整个扇区的内容
		for(i=0;i<secremain;i++) //校验数据
		{ 
			if(SPI_FLASH_BUF[secoff+i]!=0XFF)break; //需要擦除 
		}
		if(i<secremain) //需要擦除
		{ 
			SPI_Flash_Erase_Sector(secpos); //擦除这个扇区
			for(i=0;i<secremain;i++) //复制
			{
				SPI_FLASH_BUF[i+secoff]=pBuffer[i]; 
			}
			SPI_Flash_Write_NoCheck(SPI_FLASH_BUF,secpos*4096,4096);
			//写入整个扇区 
		}
		else SPI_Flash_Write_NoCheck(pBuffer,WriteAddr,secremain);
		//写已经擦除了的,直接写入扇区剩余区间. 
		if(NumByteToWrite==secremain)break;//写入结束了
		else//写入未结束
		{ 
			secpos++; //扇区地址增 1 secoff=0; //偏移位置为 0
			pBuffer+=secremain; //指针偏移
			WriteAddr+=secremain; //写地址偏移 
			NumByteToWrite-=secremain; //字节数递减
			if(NumByteToWrite>4096)secremain=4096; //下一个扇区还是写不完
			else secremain=NumByteToWrite; //下一个扇区可以写完了
		}
	};
}

5.2 W5500以太网芯片应用

  • 源文件:
/**********************************************************************************
 * 文件名  :W5500.c
 * 描述    :W5500 驱动函数库         
 * 库版本  :ST_v3.5

 * 淘宝    :http://yixindianzikeji.taobao.com/
**********************************************************************************/

#include "stm32f10x.h"
#include "stm32f10x_spi.h"				
#include "W5500.h"	
#include "IIC.h"
#include "string.h"
#include "USART.h"
#include "main.h"

static u32 CpuID[3];  
static u32 mac_Code; 
u8 ID0,ID1,ID2,ID3;

u16 GoalIP = 02;   //目标IP
u16 LocalIP = 11;  //本机IP
u8 W5500_Link = 0; //用于判断网线是否插上 ,插上为0xBF

/***************----- 网络参数变量定义 -----***************/
unsigned char Gateway_IP[4];//网关IP地址 
unsigned char Sub_Mask[4];	//子网掩码 
unsigned char Phy_Addr[6];	//物理地址(MAC) 
unsigned char IP_Addr[4];	//本机IP地址 

unsigned char S0_Port[2];	//端口0的端口号(5000) 
unsigned char S0_DIP[4];	//端口0目的IP地址 
unsigned char S0_DPort[2];	//端口0目的端口号(6000) 

unsigned char UDP_DIPR[4];	//UDP(广播)模式,目的主机IP地址
unsigned char UDP_DPORT[2];	//UDP(广播)模式,目的主机端口号

/***************----- 端口的运行模式 -----***************/
unsigned char S0_Mode =3;	//端口0的运行模式,0:TCP服务器模式,1:TCP客户端模式,2:UDP(广播)模式
#define TCP_SERVER	0x00	//TCP服务器模式
#define TCP_CLIENT	0x01	//TCP客户端模式 
#define UDP_MODE	0x02	//UDP(广播)模式 

/***************----- 端口的运行状态 -----***************/
unsigned char S0_State =0;	//端口0状态记录,1:端口完成初始化,2端口完成连接(可以正常传输数据) 
#define S_INIT		0x01	//端口完成初始化 
#define S_CONN		0x02	//端口完成连接,可以正常传输数据 

/***************----- 端口收发数据的状态 -----***************/
unsigned char S0_Data;		//端口0接收和发送数据的状态,1:端口接收到数据,2:端口发送数据完成 
#define S_RECEIVE	 0x01	//端口接收到一个数据包 
#define S_TRANSMITOK 0x02	//端口发送一个数据包完成 

/***************----- 端口数据缓冲区 -----***************/
unsigned char Rx_Buffer[Buffer_Len];	//端口接收数据缓冲区 
unsigned char Tx_Buffer[Buffer_Len];	//端口发送数据缓冲区 
unsigned char Net_Buffer[255];          //网口处理数据时数据暂存
unsigned short SocketCount1 = 0,SocketCount2 = 0; //buf使用长度计数
unsigned char W5500_Interrupt;	//W5500中断标志(0:无中断,1:有中断)

/*******************************************************************************
* 函数名  : Delay
* 描述    : 延时函数(ms)
* 输入    : d:延时系数,单位为毫秒
* 输出    : 无
* 返回    : 无 
* 说明    : 
*******************************************************************************/
void Delay(unsigned int ms)
{
	uint16_t d;

	while(ms--)
	{
		d=3990;
		while(d--);
	}
}

/*******************************************************************************
* 函数名  : Load_Net_Parameters
* 描述    : 装载网络参数
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : 网关、掩码、物理地址、本机IP地址、端口号、目的IP地址、目的端口号、端口工作模式
*******************************************************************************/
void Load_Net_Parameters(void)
{
	
	Gateway_IP[0] = 192;//加载网关参数
	Gateway_IP[1] = 168;
	Gateway_IP[2] = 1;
	Gateway_IP[3] = 1;
	
	Sub_Mask[0]=255;//加载子网掩码
	Sub_Mask[1]=255;
	Sub_Mask[2]=255;
	Sub_Mask[3]=0;

	/***固定W5500的MAC后两位为:00:01
	将ID0-ID3依次作为MAC前四位***/
	Phy_Addr[0]=0x0c;//加载物理地址 //第一位必须为偶数
	Phy_Addr[1]=ID0;
	Phy_Addr[2]=ID1;
	Phy_Addr[3]=ID2;
	Phy_Addr[4]=0x00;
	Phy_Addr[5]=0x01;

	IP_Addr[0]=192;//加载本机IP地址
	IP_Addr[1]=168;
	IP_Addr[2]=1;
	IP_Addr[3]=LocalIP;


	S0_Port[0] = 0x13;//加载端口0的端口号5000 
	S0_Port[1] = 0x88;

	S0_DIP[0]=192;//加载端口0的目的IP地址
	S0_DIP[1]=168;
	S0_DIP[2]=1;
	S0_DIP[3]=GoalIP;

	S0_DPort[0] = 0x17;//加载端口0的目的端口号6000
	S0_DPort[1] = 0x70;

	S0_Mode=TCP_SERVER;//加载端口0的工作模式,TCP客户端模式

}

/*******************************************************************************
* 函数名  : W5500_GPIO_Configuration
* 描述    : W5500 GPIO初始化配置
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void W5500_GPIO_Configuration(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	EXTI_InitTypeDef  EXTI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;	

	/* W5500_RST引脚初始化配置 */
	GPIO_InitStructure.GPIO_Pin  = W5500_RST;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(W5500_RST_PORT, &GPIO_InitStructure);
	GPIO_ResetBits(W5500_RST_PORT, W5500_RST);
	
	/* W5500_INT引脚初始化配置 */	
	GPIO_InitStructure.GPIO_Pin = W5500_INT;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(W5500_INT_PORT, &GPIO_InitStructure);
		
	/* Connect EXTI Line6 to PC1 */
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource6);

	/* PA1 as W5500 interrupt input */
	EXTI_InitStructure.EXTI_Line = EXTI_Line6;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);
	
	/* Enable the EXTI1 Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

/*******************************************************************************
* 函数名  : EXTI4_IRQHandler
* 描述    : 中断线4中断服务函数(W5500 INT引脚中断服务函数)
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line6) != RESET)
	{
		EXTI_ClearITPendingBit(EXTI_Line6);
		W5500_Interrupt=1;
	}
}

/*******************************************************************************
* 函数名  : SPI_Configuration
* 描述    : W5500 SPI初始化配置(STM32 SPI2)
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void SPI_Configuration(void)
{
	GPIO_InitTypeDef 	GPIO_InitStructure;
	SPI_InitTypeDef   SPI_InitStructure;	   

  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
	
	/* 初始化SCK、MISO、MOSI引脚 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13| GPIO_Pin_15|GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	
	/* 初始化CS引脚 */
	GPIO_InitStructure.GPIO_Pin = W5500_SCS;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_Init(W5500_SCS_PORT, &GPIO_InitStructure);
	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS);

	/* 初始化配置STM32 SPI2 */
	
	SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;	//SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode=SPI_Mode_Master;							//设置为主SPI
	SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;						//SPI发送接收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_2;	//波特率预分频值为4
	SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;					//数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial=7;								//CRC多项式为7
	SPI_Init(SPI2,&SPI_InitStructure);									//根据SPI_InitStruct中指定的参数初始化外设SPI2寄存器

	SPI_Cmd(SPI2,ENABLE);	//STM32使能SPI2

		
}

/*******************************************************************************
* 函数名  : SPI2_Send_Byte
* 描述    : SPI2发送1个字节数据
* 输入    : dat:待发送的数据
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void SPI2_Send_Byte(unsigned char dat)
{
	SPI_I2S_SendData(SPI2,dat);//写1个字节数据
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);//等待数据寄存器空
}

/*******************************************************************************
* 函数名  : SPI2_Send_Short
* 描述    : SPI2发送2个字节数据(16位)
* 输入    : dat:待发送的16位数据
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void SPI2_Send_Short(unsigned short dat)
{
	SPI2_Send_Byte(dat/256);//写数据高位 相当于右移8位
	SPI2_Send_Byte(dat);	//写数据低位
}

/*******************************************************************************
* 函数名  : Write_W5500_1Byte
* 描述    : 通过SPI2向指定地址寄存器写1个字节数据
* 输入    : reg:16位寄存器地址,dat:待写入的数据
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void Write_W5500_1Byte(unsigned short reg, unsigned char dat)
{
	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平

	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(FDM1|RWB_WRITE|COMMON_R);//通过SPI2写控制字节,1个字节数据长度,写数据,选择通用寄存器
	SPI2_Send_Byte(dat);//写1个字节数据

	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平
}

/*******************************************************************************
* 函数名  : Write_W5500_2Byte
* 描述    : 通过SPI2向指定地址寄存器写2个字节数据
* 输入    : reg:16位寄存器地址,dat:16位待写入的数据(2个字节)
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void Write_W5500_2Byte(unsigned short reg, unsigned short dat)
{
	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平
		
	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(FDM2|RWB_WRITE|COMMON_R);//通过SPI2写控制字节,2个字节数据长度,写数据,选择通用寄存器
	SPI2_Send_Short(dat);//写16位数据

	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平
}

/*******************************************************************************
* 函数名  : Write_W5500_nByte
* 描述    : 通过SPI2向指定地址寄存器写n个字节数据
* 输入    : reg:16位寄存器地址,*dat_ptr:待写入数据缓冲区指针,size:待写入的数据长度
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void Write_W5500_nByte(unsigned short reg, unsigned char *dat_ptr, unsigned short size)
{
	unsigned short i;

	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平	
		
	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(VDM|RWB_WRITE|COMMON_R);//通过SPI2写控制字节,N个字节数据长度,写数据,选择通用寄存器

	for(i=0;i<size;i++)//循环将缓冲区的size个字节数据写入W5500
	{
		SPI2_Send_Byte(*dat_ptr++);//写一个字节数据
	}

	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平
}

/*******************************************************************************
* 函数名  : Write_W5500_SOCK_1Byte
* 描述    : 通过SPI2向指定端口寄存器写1个字节数据
* 输入    : s:端口号,reg:16位寄存器地址,dat:待写入的数据
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void Write_W5500_SOCK_1Byte(SOCKET s, unsigned short reg, unsigned char dat)
{
	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平	
		
	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(FDM1|RWB_WRITE|(s*0x20+0x08));//通过SPI2写控制字节,1个字节数据长度,写数据,选择端口s的寄存器
	SPI2_Send_Byte(dat);//写1个字节数据
	
	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平
}

/*******************************************************************************
* 函数名  : Write_W5500_SOCK_2Byte
* 描述    : 通过SPI2向指定端口寄存器写2个字节数据
* 输入    : s:端口号,reg:16位寄存器地址,dat:16位待写入的数据(2个字节)
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void Write_W5500_SOCK_2Byte(SOCKET s, unsigned short reg, unsigned short dat)
{
	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平
			
	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(FDM2|RWB_WRITE|(s*0x20+0x08));//通过SPI2写控制字节,2个字节数据长度,写数据,选择端口s的寄存器
	SPI2_Send_Short(dat);//写16位数据

	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平
}

/*******************************************************************************
* 函数名  : Write_W5500_SOCK_4Byte
* 描述    : 通过SPI2向指定端口寄存器写4个字节数据
* 输入    : s:端口号,reg:16位寄存器地址,*dat_ptr:待写入的4个字节缓冲区指针
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void Write_W5500_SOCK_4Byte(SOCKET s, unsigned short reg, unsigned char *dat_ptr)
{
	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平
			
	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(FDM4|RWB_WRITE|(s*0x20+0x08));//通过SPI2写控制字节,4个字节数据长度,写数据,选择端口s的寄存器

	SPI2_Send_Byte(*dat_ptr++);//写第1个字节数据
	SPI2_Send_Byte(*dat_ptr++);//写第2个字节数据
	SPI2_Send_Byte(*dat_ptr++);//写第3个字节数据
	SPI2_Send_Byte(*dat_ptr++);//写第4个字节数据

	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平
}

/*******************************************************************************
* 函数名  : Read_W5500_1Byte
* 描述    : 读W5500指定地址寄存器的1个字节数据
* 输入    : reg:16位寄存器地址
* 输出    : 无
* 返回值  : 读取到寄存器的1个字节数据
* 说明    : 无
*******************************************************************************/
unsigned char Read_W5500_1Byte(unsigned short reg)
{
	unsigned char i;

	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平
			
	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(FDM1|RWB_READ|COMMON_R);//通过SPI2写控制字节,1个字节数据长度,读数据,选择通用寄存器

	i=SPI_I2S_ReceiveData(SPI2);
	SPI2_Send_Byte(0x00);//发送一个哑数据
	i=SPI_I2S_ReceiveData(SPI2);//读取1个字节数据

	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为高电平
	return i;//返回读取到的寄存器数据

}

/*******************************************************************************
* 函数名  : Read_W5500_SOCK_1Byte
* 描述    : 读W5500指定端口寄存器的1个字节数据
* 输入    : s:端口号,reg:16位寄存器地址
* 输出    : 无
* 返回值  : 读取到寄存器的1个字节数据
* 说明    : 无
*******************************************************************************/
unsigned char Read_W5500_SOCK_1Byte(SOCKET s, unsigned short reg)
{
	unsigned char i;

	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平
			
	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(FDM1|RWB_READ|(s*0x20+0x08));//通过SPI2写控制字节,1个字节数据长度,读数据,选择端口s的寄存器

	i=SPI_I2S_ReceiveData(SPI2);
	SPI2_Send_Byte(0x00);//发送一个哑数据
	i=SPI_I2S_ReceiveData(SPI2);//读取1个字节数据

	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为高电平
	return i;//返回读取到的寄存器数据
}

/*******************************************************************************
* 函数名  : Read_W5500_SOCK_2Byte
* 描述    : 读W5500指定端口寄存器的2个字节数据
* 输入    : s:端口号,reg:16位寄存器地址
* 输出    : 无
* 返回值  : 读取到寄存器的2个字节数据(16位)
* 说明    : 无
*******************************************************************************/
unsigned short Read_W5500_SOCK_2Byte(SOCKET s, unsigned short reg)
{
	unsigned short i;

	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平
			
	SPI2_Send_Short(reg);//通过SPI2写16位寄存器地址
	SPI2_Send_Byte(FDM2|RWB_READ|(s*0x20+0x08));//通过SPI2写控制字节,2个字节数据长度,读数据,选择端口s的寄存器

	i=SPI_I2S_ReceiveData(SPI2);
	SPI2_Send_Byte(0x00);//发送一个哑数据
	i=SPI_I2S_ReceiveData(SPI2);//读取高位数据
	SPI2_Send_Byte(0x00);//发送一个哑数据
	i*=256;
	i+=SPI_I2S_ReceiveData(SPI2);//读取低位数据

	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为高电平
	return i;//返回读取到的寄存器数据
}

/*******************************************************************************
* 函数名  : Read_SOCK_Data_Buffer
* 描述    : 从W5500接收数据缓冲区中读取数据
* 输入    : s:端口号,*dat_ptr:数据保存缓冲区指针
* 输出    : 无
* 返回值  : 读取到的数据长度,rx_size个字节
* 说明    : 无
*******************************************************************************/
unsigned short Read_SOCK_Data_Buffer(SOCKET s, unsigned char *dat_ptr)
{
	unsigned short rx_size;
	unsigned short offset, offset1;
	unsigned short i;
	unsigned char j;

	rx_size=Read_W5500_SOCK_2Byte(s,Sn_RX_RSR);
	if(rx_size==0) return 0;//没接收到数据则返回
	if(rx_size>1460) rx_size=1460;

	offset=Read_W5500_SOCK_2Byte(s,Sn_RX_RD);//缓存数据中的首地址
	offset1=offset;
	offset&=(S_RX_SIZE-1);//计算实际的物理地址

	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平

	SPI2_Send_Short(offset);//写16位地址
	SPI2_Send_Byte(VDM|RWB_READ|(s*0x20+0x18));//写控制字节,N个字节数据长度,读数据,选择端口s的寄存器
	j=SPI_I2S_ReceiveData(SPI2);
	
	if((offset+rx_size)<S_RX_SIZE)//如果最大地址未超过W5500接收缓冲区寄存器的最大地址
	{
		for(i=0;i<rx_size;i++)//循环读取rx_size个字节数据
		{
			SPI2_Send_Byte(0x00);//发送一个哑数据
			j=SPI_I2S_ReceiveData(SPI2);//读取1个字节数据
			*dat_ptr=j;//将读取到的数据保存到数据保存缓冲区
			dat_ptr++;//数据保存缓冲区指针地址自增1
		}
	}
	else//如果最大地址超过W5500接收缓冲区寄存器的最大地址
	{
		offset=S_RX_SIZE-offset;
		for(i=0;i<offset;i++)//循环读取出前offset个字节数据
		{
			SPI2_Send_Byte(0x00);//发送一个哑数据
			j=SPI_I2S_ReceiveData(SPI2);//读取1个字节数据
			*dat_ptr=j;//将读取到的数据保存到数据保存缓冲区
			dat_ptr++;//数据保存缓冲区指针地址自增1
		}
		GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平

		GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平

		SPI2_Send_Short(0x00);//写16位地址
		SPI2_Send_Byte(VDM|RWB_READ|(s*0x20+0x18));//写控制字节,N个字节数据长度,读数据,选择端口s的寄存器
		j=SPI_I2S_ReceiveData(SPI2);

		for(;i<rx_size;i++)//循环读取后rx_size-offset个字节数据
		{
			SPI2_Send_Byte(0x00);//发送一个哑数据
			j=SPI_I2S_ReceiveData(SPI2);//读取1个字节数据
			*dat_ptr=j;//将读取到的数据保存到数据保存缓冲区
			dat_ptr++;//数据保存缓冲区指针地址自增1
		}
	}
	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平

	offset1+=rx_size;//更新实际物理地址,即下次读取接收到的数据的起始地址
	Write_W5500_SOCK_2Byte(s, Sn_RX_RD, offset1);
	Write_W5500_SOCK_1Byte(s, Sn_CR, RECV);//发送启动接收命令
	return rx_size;//返回接收到数据的长度
}

/*******************************************************************************
* 函数名  : Write_SOCK_Data_Buffer
* 描述    : 将数据写入W5500的数据发送缓冲区
* 输入    : s:端口号,*dat_ptr:数据保存缓冲区指针,size:待写入数据的长度
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void Write_SOCK_Data_Buffer(SOCKET s, unsigned char *dat_ptr, unsigned short size)
{
	unsigned short offset,offset1;
	unsigned short i;

	//如果是UDP模式,可以在此设置目的主机的IP和端口号
	if((Read_W5500_SOCK_1Byte(s,Sn_MR)&0x0f) != SOCK_UDP)//如果Socket打开失败
	{		
		Write_W5500_SOCK_4Byte(s, Sn_DIPR, UDP_DIPR);//设置目的主机IP  		
		Write_W5500_SOCK_2Byte(s, Sn_DPORTR, UDP_DPORT[0]*256+UDP_DPORT[1]);//设置目的主机端口号				
	}

	offset=Read_W5500_SOCK_2Byte(s,Sn_TX_WR);
	offset1=offset;
	offset&=(S_TX_SIZE-1);//计算实际的物理地址

	GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平

	SPI2_Send_Short(offset);//写16位地址
	SPI2_Send_Byte(VDM|RWB_WRITE|(s*0x20+0x10));//写控制字节,N个字节数据长度,写数据,选择端口s的寄存器

	if((offset+size)<S_TX_SIZE)//如果最大地址未超过W5500发送缓冲区寄存器的最大地址
	{
		for(i=0;i<size;i++)//循环写入size个字节数据
		{
			SPI2_Send_Byte(*dat_ptr++);//写入一个字节的数据		
		}
	}
	else//如果最大地址超过W5500发送缓冲区寄存器的最大地址
	{
		offset=S_TX_SIZE-offset;
		for(i=0;i<offset;i++)//循环写入前offset个字节数据
		{
			SPI2_Send_Byte(*dat_ptr++);//写入一个字节的数据
		}
		GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平

		GPIO_ResetBits(W5500_SCS_PORT, W5500_SCS);//置W5500的SCS为低电平

		SPI2_Send_Short(0x00);//写16位地址
		SPI2_Send_Byte(VDM|RWB_WRITE|(s*0x20+0x10));//写控制字节,N个字节数据长度,写数据,选择端口s的寄存器

		for(;i<size;i++)//循环写入size-offset个字节数据
		{
			SPI2_Send_Byte(*dat_ptr++);//写入一个字节的数据
		}
	}
	GPIO_SetBits(W5500_SCS_PORT, W5500_SCS); //置W5500的SCS为高电平

	offset1+=size;//更新实际物理地址,即下次写待发送数据到发送数据缓冲区的起始地址
	Write_W5500_SOCK_2Byte(s, Sn_TX_WR, offset1);
	Write_W5500_SOCK_1Byte(s, Sn_CR, SEND);//发送启动发送命令				
}

/*******************************************************************************
* 函数名  : W5500_Hardware_Reset
* 描述    : 硬件复位W5500
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : W5500的复位引脚保持低电平至少500us以上,才能重围W5500
*******************************************************************************/
void W5500_Hardware_Reset(void)
{

	GPIO_ResetBits(W5500_RST_PORT, W5500_RST);//复位引脚拉低
	Delay(50);
	GPIO_SetBits(W5500_RST_PORT, W5500_RST);//复位引脚拉高
	Delay(200);
//	while((W5500_Link = Read_W5500_1Byte(PHYCFGR)&LINK)==0);//等待以太网连接完成

}

/*******************************************************************************
* 函数名  : W5500_Init
* 描述    : 初始化W5500寄存器函数
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : 在使用W5500之前,先对W5500初始化
*******************************************************************************/
void W5500_Init(void)
{
	u8 i=0;

	Write_W5500_1Byte(MR, RST);//软件复位W5500,置1有效,复位后自动清0
	Delay(10);//延时10ms,自己定义该函数

	//设置网关(Gateway)的IP地址,Gateway_IP为4字节unsigned char数组,自己定义 
	//使用网关可以使通信突破子网的局限,通过网关可以访问到其它子网或进入Internet
	Write_W5500_nByte(GAR, Gateway_IP, 4);
			
	//设置子网掩码(MASK)值,SUB_MASK为4字节unsigned char数组,自己定义
	//子网掩码用于子网运算
	Write_W5500_nByte(SUBR,Sub_Mask,4);		
	
	//设置物理地址,PHY_ADDR为6字节unsigned char数组,自己定义,用于唯一标识网络设备的物理地址值
	//该地址值需要到IEEE申请,按照OUI的规定,前3个字节为厂商代码,后三个字节为产品序号
	//如果自己定义物理地址,注意第一个字节必须为偶数
	Write_W5500_nByte(SHAR,Phy_Addr,6);		

	//设置本机的IP地址,IP_ADDR为4字节unsigned char数组,自己定义
	//注意,网关IP必须与本机IP属于同一个子网,否则本机将无法找到网关
	Write_W5500_nByte(SIPR,IP_Addr,4);		
	
	//设置发送缓冲区和接收缓冲区的大小,参考W5500数据手册(所有Socket发送/接收缓存的总大小不能超过16kb)
	for(i=0;i<8;i++)
	{
		Write_W5500_SOCK_1Byte(i,Sn_RXBUF_SIZE, 0x02);//Socket Rx memory size=2k
		Write_W5500_SOCK_1Byte(i,Sn_TXBUF_SIZE, 0x02);//Socket Tx mempry size=2k
	}

	//设置重试时间,默认为2000(200ms) 
	//每一单位数值为100微秒,初始化时值设为2000(0x07D0),等于200毫秒
	Write_W5500_2Byte(RTR, 0x07d0);

	//设置重试次数,默认为8次 
	//如果重发的次数超过设定值,则产生超时中断(相关的端口中断寄存器中的Sn_IR 超时位(TIMEOUT)置“1”)
	Write_W5500_1Byte(RCR,8);

	//启动中断,参考W5500数据手册确定自己需要的中断类型
	//IMR_CONFLICT是IP地址冲突异常中断,IMR_UNREACH是UDP通信时,地址无法到达的异常中断
	//其它是Socket事件中断,根据需要添加
	Write_W5500_1Byte(IMR,IM_IR7 | IM_IR6);
	Write_W5500_1Byte(SIMR,S0_IMR);
	Write_W5500_SOCK_1Byte(0, Sn_IMR, IMR_SENDOK | IMR_TIMEOUT | IMR_RECV | IMR_DISCON | IMR_CON);
}

/*******************************************************************************
* 函数名  : Detect_Gateway
* 描述    : 检查网关服务器
* 输入    : 无
* 输出    : 无
* 返回值  : 成功返回TRUE(0xFF),失败返回FALSE(0x00)
* 说明    : 无
*******************************************************************************/
unsigned char Detect_Gateway(void)
{
	unsigned char ip_adde[4];
	ip_adde[0]=IP_Addr[0]+1;
	ip_adde[1]=IP_Addr[1]+1;
	ip_adde[2]=IP_Addr[2]+1;
	ip_adde[3]=IP_Addr[3]+1;

	//检查网关及获取网关的物理地址
	Write_W5500_SOCK_4Byte(0,Sn_DIPR,ip_adde);//向目的地址寄存器写入与本机IP不同的IP值
	Write_W5500_SOCK_1Byte(0,Sn_MR,MR_TCP);//设置socket为TCP模式
	Write_W5500_SOCK_1Byte(0,Sn_CR,OPEN);//打开Socket	
	Delay(5);//延时5ms 	
	
	if(Read_W5500_SOCK_1Byte(0,Sn_SR) != SOCK_INIT)//如果socket打开失败,
	{
		Write_W5500_SOCK_1Byte(0,Sn_CR,CLOSE);//打开不成功,关闭Socket
		return FALSE;//返回FALSE(0x00)
	}

	Write_W5500_SOCK_1Byte(0,Sn_CR,CONNECT);//设置Socket为Connect模式,Sn_CR变为SOCK_INT之后,用户才可以使用CONNECT命令					

	do
	{
		u8 j=0;
		j=Read_W5500_SOCK_1Byte(0,Sn_IR);//读取Socket0中断标志寄存器
		if(j!=0)
		Write_W5500_SOCK_1Byte(0,Sn_IR,j); //清除Socket0中断标志寄存器,对应位写1清除
		Delay(5);//延时5ms 
		if((j&IR_TIMEOUT) == IR_TIMEOUT)   //连接超时
		{
			return FALSE;	
		}
		else if(Read_W5500_SOCK_1Byte(0,Sn_DHAR) != 0xff)
		{
			Write_W5500_SOCK_1Byte(0,Sn_CR,CLOSE);//关闭Socket
			return TRUE;							
		}
	}while(1);
}

/*******************************************************************************
* 函数名  : Socket_Init
* 描述    : 指定Socket(0~7)初始化
* 输入    : s:待初始化的端口
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void Socket_Init(SOCKET s)
{
	//设置分片长度,参考W5500数据手册,该值可以不修改	
	Write_W5500_SOCK_2Byte(0, Sn_MSSR, 1460);//最大分片字节数=1460(0x5b4)
	//设置指定端口
	switch(s)
	{
		case 0:
			//设置端口0的端口号
			Write_W5500_SOCK_2Byte(0, Sn_PORT, S0_Port[0]*256+S0_Port[1]); //(相当于S0_Port[0]<<8)+S0_Port[1]
			//设置端口0目的(远程)端口号
			Write_W5500_SOCK_2Byte(0, Sn_DPORTR, S0_DPort[0]*256+S0_DPort[1]);
			//设置端口0目的(远程)IP地址
			Write_W5500_SOCK_4Byte(0, Sn_DIPR, S0_DIP);			
			
			break;

		case 1:
			break;

		case 2:
			break;

		case 3:
			break;

		case 4:
			break;

		case 5:
			break;

		case 6:
			break;

		case 7:
			break;

		default:
			break;
	}
}

/*******************************************************************************
* 函数名  : Socket_Connect
* 描述    : 设置指定Socket(0~7)为客户端与远程服务器连接
* 输入    : s:待设定的端口
* 输出    : 无
* 返回值  : 成功返回TRUE(0xFF),失败返回FALSE(0x00)
* 说明    : 当本机Socket工作在客户端模式时,引用该程序,与远程服务器建立连接
*			如果启动连接后出现超时中断,则与服务器连接失败,需要重新调用该程序连接
*			该程序每调用一次,就与服务器产生一次连接
*******************************************************************************/
unsigned char Socket_Connect(SOCKET s)
{
	Write_W5500_SOCK_1Byte(s,Sn_MR,MR_TCP);//设置socket为TCP模式
	Write_W5500_SOCK_1Byte(s,Sn_CR,OPEN);//打开Socket
	Delay(5);//延时5ms
	if(Read_W5500_SOCK_1Byte(s,Sn_SR)!=SOCK_INIT)//如果socket打开失败
	{
		Write_W5500_SOCK_1Byte(s,Sn_CR,CLOSE);//打开不成功,关闭Socket
		return FALSE;//返回FALSE(0x00)
	}
	Write_W5500_SOCK_1Byte(s,Sn_CR,CONNECT);//设置Socket为Connect模式
	return TRUE;//返回TRUE,设置成功
}

/*******************************************************************************
* 函数名  : Socket_Listen
* 描述    : 设置指定Socket(0~7)作为服务器等待远程主机的连接
* 输入    : s:待设定的端口
* 输出    : 无
* 返回值  : 成功返回TRUE(0xFF),失败返回FALSE(0x00)
* 说明    : 当本机Socket工作在服务器模式时,引用该程序,等等远程主机的连接
*			该程序只调用一次,就使W5500设置为服务器模式
*******************************************************************************/
unsigned char Socket_Listen(SOCKET s)
{
	Write_W5500_SOCK_1Byte(s,Sn_MR,MR_TCP);//设置socket为TCP模式 
	Write_W5500_SOCK_1Byte(s,Sn_CR,OPEN);//打开Socket	
	Delay(5);//延时5ms
	if(Read_W5500_SOCK_1Byte(s,Sn_SR)!=SOCK_INIT)//如果socket打开失败
	{
		Write_W5500_SOCK_1Byte(s,Sn_CR,CLOSE);//打开不成功,关闭Socket
		return FALSE;//返回FALSE(0x00)
	}	
	Write_W5500_SOCK_1Byte(s,Sn_CR,LISTEN);//设置Socket为侦听模式	
	Delay(5);//延时5ms
	if(Read_W5500_SOCK_1Byte(s,Sn_SR)!=SOCK_LISTEN)//如果socket设置失败
	{
		Write_W5500_SOCK_1Byte(s,Sn_CR,CLOSE);//设置不成功,关闭Socket
		return FALSE;//返回FALSE(0x00)
	}

	return TRUE;

	//至此完成了Socket的打开和设置侦听工作,至于远程客户端是否与它建立连接,则需要等待Socket中断,
	//以判断Socket的连接是否成功。参考W5500数据手册的Socket中断状态
	//在服务器侦听模式不需要设置目的IP和目的端口号
}

/*******************************************************************************
* 函数名  : Socket_UDP
* 描述    : 设置指定Socket(0~7)为UDP模式
* 输入    : s:待设定的端口
* 输出    : 无
* 返回值  : 成功返回TRUE(0xFF),失败返回FALSE(0x00)
* 说明    : 如果Socket工作在UDP模式,引用该程序,在UDP模式下,Socket通信不需要建立连接
*			该程序只调用一次,就使W5500设置为UDP模式
*******************************************************************************/
unsigned char Socket_UDP(SOCKET s)
{
	Write_W5500_SOCK_1Byte(s,Sn_MR,MR_UDP);//设置Socket为UDP模式*/
	Write_W5500_SOCK_1Byte(s,Sn_CR,OPEN);//打开Socket*/
	Delay(5);//延时5ms
	if(Read_W5500_SOCK_1Byte(s,Sn_SR)!=SOCK_UDP)//如果Socket打开失败
	{
		Write_W5500_SOCK_1Byte(s,Sn_CR,CLOSE);//打开不成功,关闭Socket
		return FALSE;//返回FALSE(0x00)
	}
	else
		return TRUE;

	//至此完成了Socket的打开和UDP模式设置,在这种模式下它不需要与远程主机建立连接
	//因为Socket不需要建立连接,所以在发送数据前都可以设置目的主机IP和目的Socket的端口号
	//如果目的主机IP和目的Socket的端口号是固定的,在运行过程中没有改变,那么也可以在这里设置
}

/*******************************************************************************
* 函数名  : W5500_Interrupt_Process
* 描述    : W5500中断处理程序框架
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void W5500_Interrupt_Process(void)
{
	unsigned char i,j;

IntDispose:
	W5500_Interrupt=0;//清零中断标志
	i = Read_W5500_1Byte(IR);//读取中断标志寄存器
	Write_W5500_1Byte(IR, (i&0xf0));//回写清除中断标志

	if((i & CONFLICT) == CONFLICT)//IP地址冲突异常处理
	{
		 //自己添加代码
	}

	if((i & UNREACH) == UNREACH)//UDP模式下地址无法到达异常处理
	{
		//自己添加代码
	}

	i=Read_W5500_1Byte(SIR);//读取端口中断标志寄存器	
	if((i & S0_INT) == S0_INT)//Socket0事件处理 
	{
		j=Read_W5500_SOCK_1Byte(0,Sn_IR);//读取Socket0中断标志寄存器
		Write_W5500_SOCK_1Byte(0,Sn_IR,j);
		if(j&IR_CON)//在TCP模式下,Socket0成功连接 
		{
			S0_State|=S_CONN;//网络连接状态0x02,端口完成连接,可以正常传输数据
		}
		if(j&IR_DISCON)//在TCP模式下Socket断开连接处理
		{
			Write_W5500_SOCK_1Byte(0,Sn_CR,CLOSE);//关闭端口,等待重新打开连接 
			Socket_Init(0);		//指定Socket(0~7)初始化,初始化端口0
			S0_State=0;//网络连接状态0x00,端口连接失败
		}
		if(j&IR_SEND_OK)//Socket0数据发送完成,可以再次启动S_tx_process()函数发送数据 
		{
			S0_Data|=S_TRANSMITOK;//端口发送一个数据包完成 
		}
		if(j&IR_RECV)//Socket接收到数据,可以启动S_rx_process()函数 
		{
			S0_Data|=S_RECEIVE;//端口接收到一个数据包
		}
		if(j&IR_TIMEOUT)//Socket连接或数据传输超时处理 
		{
			Write_W5500_SOCK_1Byte(0,Sn_CR,CLOSE);// 关闭端口,等待重新打开连接 
			S0_State=0;//网络连接状态0x00,端口连接失败
		}
	}

	if(Read_W5500_1Byte(SIR) != 0) 
		goto IntDispose;
}

/*******************************************************************************
* 函数名  : W5500_Initialization
* 描述    : W5500初始货配置
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : 无
*******************************************************************************/
void W5500_Initialization(void)
{
	W5500_GPIO_Configuration();	//W5500 GPIO初始化配置
	SPI_Configuration();	         //初始化SPI}
	GetCPU_ID(); 				 //读CPU 的ID号用作MAC地址
//	printf("%x %x %x\r\n",ID0,ID1,ID2);
	Load_Net_Parameters();	   	//装载网络参数	
	W5500_Hardware_Reset();	   	//硬件复位W5500
	W5500_Init();		            //初始化W5500寄存器函数
	Detect_Gateway();	            //检查网关服务器 
	Socket_Init(0);		         //指定Socket(0~7)初始化,初始化端口0
}

/*******************************************************************************
* 函数名  : W5500_Socket_Set
* 描述    : W5500端口初始化配置
* 输入    : 无
* 输出    : 无
* 返回值  : 无
* 说明    : 分别设置4个端口,根据端口工作模式,将端口置于TCP服务器、TCP客户端或UDP模式.
*			从端口状态字节Socket_State可以判断端口的工作情况
*******************************************************************************/
void W5500_Socket_Set(void)
{
	if(S0_State==0)//端口0初始化配置
	{
		if(S0_Mode==TCP_SERVER)//TCP服务器模式 
		{
			if(Socket_Listen(0)==TRUE)
				S0_State=S_INIT;
			else
				S0_State=0;
		}
		else if(S0_Mode==TCP_CLIENT)//TCP客户端模式 
		{
			if(Socket_Connect(0)==TRUE)
				S0_State=S_INIT;
			else
				S0_State=0;
		}
		else//UDP模式 
		{
			if(Socket_UDP(0)==TRUE)
				S0_State=S_INIT|S_CONN;
			else
				S0_State=0;
		}
	}
}


/***************************************************************************
;** 函数名称:  void GetCPU_ID(void) 
;** 功能描述:   读stm32内部96位ID,用作MAC地址
;** 输入参数:
;** 返 回 值:
;** 全局变量:
;** 调用模块:
;** 作   者:   ChenLin
;** 日   期:   2018-8-2
;** 修改原因:
;** 说    明:   基地址:0x1FFF F7E8
;***************************************************************************/
void GetCPU_ID(void)  
{  
	CpuID[0]=*(vu32*)(0x1ffff7e8);
	CpuID[1]=*(vu32*)(0x1ffff7ec);
	CpuID[2]=*(vu32*)(0x1ffff7f0); 
    mac_Code = (CpuID[0]>>1)+(CpuID[1]>>2)+(CpuID[2]>>3);  
    ID0=mac_Code&0x000000FF;
    ID1=(mac_Code&0x0000FF00)>>8;
    ID2=(mac_Code&0x00FF0000)>>16;
    ID3=(mac_Code&0xFF000000)>>24;
	
}



/***************************************************************************
;** 函数名称:  void W5500_Data_Handle()
;** 功能描述:   W5500数据处理
;** 输入参数:
;** 返 回 值:
;** 全局变量:
;** 调用模块:
;** 作   者:   ChenLin
;** 日   期:   2018-8-2
;** 修改原因:
;** 说    明:   
;***************************************************************************/
void W5500_Data_Handle()
{
	W5500_Socket_Set();//W5500端口初始化配置
	if(W5500_Interrupt)//处理W5500中断		
	{
		W5500_Interrupt_Process();//W5500中断处理程序框架
	}
	if((S0_Data & S_RECEIVE) == S_RECEIVE)//如果Socket0接收到数据
	{
		S0_Data&=~S_RECEIVE;
	//	Process_Socket_Data(0);//W5500接收并发送接收到的数据
	}
}



/***************************************************************************
;** 函数名称: u8 SocketDataHandle(char *str,u8 tx_en)
;** 功能描述:     将w5500 数据合并后一起发送
;** 输入参数:
;** 返 回 值:  1或0
;** 全局变量:  str 要发送的数据,tx_en:发送使能
;** 调用模块:
;** 作   者:   ChenLin
;** 日   期:   2018-8-2
;** 修改原因:
;** 说    明:   
;***************************************************************************/
static u8 TempBufer[1024];   //数据处理用buf
static u16 temp_cnt = 0;
u8 SocketDataHandle(char *str,u8 tx_en)
{
	u8 error = 0;
	u16 i = 0,len = 0;
	
	len = strlen(str);
	
	if((temp_cnt+len) >= 1024)
	{
		temp_cnt = 0;
		error = 1; //内存有溢出
	}	
	for(i = 0;i < len;i++)
	{
		TempBufer[temp_cnt+i] = str[i];
	}
	temp_cnt += len;
	if(tx_en == 1)
	{
		Write_SOCK_Data_Buffer(0, TempBufer,temp_cnt);	
		temp_cnt = 0;
	}

	return error;
	
}


  • 头文件
#ifndef	_W5500_H_
#define	_W5500_H_

/***************** Common Register *****************/
#define MR		0x0000             //模式寄存器
	#define RST		0x80
	#define WOL		0x20
	#define PB		0x10
	#define PPP		0x08
	#define FARP	0x02

#define GAR		0x0001            //网关IP地址寄存器
#define SUBR	0x0005            //子网掩码寄存器
#define SHAR	0x0009            //源mac地址寄存器
#define SIPR	0x000f            //源IP地址寄存器

#define INTLEVEL	0x0013        //低电平中断定时器寄存器
#define IR		0x0015            //中断寄存器
	#define CONFLICT	0x80
	#define UNREACH		0x40
	#define PPPOE		0x20
	#define MP			0x10

#define IMR		0x0016            //中断屏蔽寄存器
	#define IM_IR7		0x80
	#define IM_IR6		0x40
	#define IM_IR5		0x20
	#define IM_IR4		0x10

#define SIR		0x0017            //Socket中断寄存器
	#define S7_INT		0x80
	#define S6_INT		0x40
	#define S5_INT		0x20
	#define S4_INT		0x10
	#define S3_INT		0x08
	#define S2_INT		0x04
	#define S1_INT		0x02
	#define S0_INT		0x01

#define SIMR	0x0018           //Socket中断屏蔽寄存器
	#define S7_IMR		0x80
	#define S6_IMR		0x40
	#define S5_IMR		0x20
	#define S4_IMR		0x10
	#define S3_IMR		0x08
	#define S2_IMR		0x04
	#define S1_IMR		0x02
	#define S0_IMR		0x01

#define RTR		0x0019          //重试时间值寄存器
#define RCR		0x001b          //重试计数寄存器
 
#define PTIMER	0x001c          //PPP连接控制协议请求定时寄存器
#define PMAGIC	0x001d          //PPP连接控制协议幻数寄存器
#define PHA		0x001e          //PPPoE模式下目标MAC寄存器
#define PSID	0x0024          //PPPoE模式下会话ID寄存器
#define PMRU	0x0026          //PPPoE模式下最大接收单元

#define UIPR	0x0028          //无法抵达IP地址寄存器
#define UPORT	0x002c          //无法抵达端口寄存器

#define PHYCFGR	0x002e          //W5550 PHY 配置寄存器
	#define RST_PHY		0x80
	#define OPMODE		0x40
	#define DPX			0x04
	#define SPD			0x02
	#define LINK		0x01

#define VERR	0x0039          //W5550芯片版本寄存器

/********************* Socket Register *******************/
#define Sn_MR		0x0000               //Socketn模式寄存器
	#define MULTI_MFEN		0x80
	#define BCASTB			0x40
	#define	ND_MC_MMB		0x20
	#define UCASTB_MIP6B	0x10
	#define MR_CLOSE		0x00
	#define MR_TCP		0x01
	#define MR_UDP		0x02
	#define MR_MACRAW		0x04

#define Sn_CR		0x0001               //Socketn配置寄存器
	#define OPEN		0x01
	#define LISTEN		0x02
	#define CONNECT		0x04
	#define DISCON		0x08
	#define CLOSE		0x10
	#define SEND		0x20
	#define SEND_MAC	0x21
	#define SEND_KEEP	0x22
	#define RECV		0x40

#define Sn_IR		0x0002             //Socketn中断寄存器
	#define IR_SEND_OK		0x10
	#define IR_TIMEOUT		0x08
	#define IR_RECV			0x04
	#define IR_DISCON		0x02
	#define IR_CON			0x01

#define Sn_SR		0x0003             //Socketn状态寄存器
	#define SOCK_CLOSED		0x00
	#define SOCK_INIT		0x13
	#define SOCK_LISTEN		0x14
	#define SOCK_ESTABLISHED	0x17
	#define SOCK_CLOSE_WAIT		0x1c
	#define SOCK_UDP		0x22
	#define SOCK_MACRAW		0x02

	#define SOCK_SYNSEND	0x15
	#define SOCK_SYNRECV	0x16
	#define SOCK_FIN_WAI	0x18
	#define SOCK_CLOSING	0x1a
	#define SOCK_TIME_WAIT	0x1b
	#define SOCK_LAST_ACK	0x1d

#define Sn_PORT		0x0004             //Socketn源端口寄存器
#define Sn_DHAR	   	0x0006             //Socketn目的MAC地址寄存器
#define Sn_DIPR		0x000c             //Socketn目的IP地址寄存器
#define Sn_DPORTR	0x0010             //Socketn目的端口寄存器

#define Sn_MSSR		0x0012             //Socketn-th最大分段寄存器
#define Sn_TOS		0x0015             //Socketn IP服务类型寄存器
#define Sn_TTL		0x0016             //Socketn 生存时间寄存器

#define Sn_RXBUF_SIZE	0x001e         //Socketn接收缓存大小寄存器
#define Sn_TXBUF_SIZE	0x001f         //Socketn发送缓存大小寄存器
#define Sn_TX_FSR	0x0020             //Socketn空闲发送缓存寄存器
#define Sn_TX_RD	0x0022             //Socketn发送读指针寄存器
#define Sn_TX_WR	0x0024             //Socketn发送写指针寄存器
#define Sn_RX_RSR	0x0026             //Socketn空闲接收缓存寄存器
#define Sn_RX_RD	0x0028             //Socketn接收读指针寄存器
#define Sn_RX_WR	0x002a             //Socketn接收写指针寄存器  

#define Sn_IMR		0x002c             //Socketn中断屏蔽寄存器
	#define IMR_SENDOK	0x10 
	#define IMR_TIMEOUT	0x08
	#define IMR_RECV	0x04
	#define IMR_DISCON	0x02
	#define IMR_CON		0x01

#define Sn_FRAG		0x002d            //Socketn分段寄存器
#define Sn_KPALVTR	0x002f            //在线时间寄存器

/*******************************************************************/
/************************ SPI Control Byte *************************/
/*******************************************************************/
/* Operation mode bits */
#define VDM		0x00                //可变数据长度
#define FDM1	0x01                //1byte数据长度 
#define	FDM2	0x02                //2byte数据长度  
#define FDM4	0x03                //4byte数据长度 

/* Read_Write control bit */
#define RWB_READ	0x00            //读操作
#define RWB_WRITE	0x04            //写操作

/* Block select bits */
#define COMMON_R	0x00

/* Socket 0 */
#define S0_REG		0x08
#define S0_TX_BUF	0x10
#define S0_RX_BUF	0x18

/* Socket 1 */
#define S1_REG		0x28
#define S1_TX_BUF	0x30
#define S1_RX_BUF	0x38

/* Socket 2 */
#define S2_REG		0x48
#define S2_TX_BUF	0x50
#define S2_RX_BUF	0x58

/* Socket 3 */
#define S3_REG		0x68
#define S3_TX_BUF	0x70
#define S3_RX_BUF	0x78

/* Socket 4 */
#define S4_REG		0x88
#define S4_TX_BUF	0x90
#define S4_RX_BUF	0x98

/* Socket 5 */
#define S5_REG		0xa8
#define S5_TX_BUF	0xb0
#define S5_RX_BUF	0xb8

/* Socket 6 */
#define S6_REG		0xc8
#define S6_TX_BUF	0xd0
#define S6_RX_BUF	0xd8

/* Socket 7 */
#define S7_REG		0xe8
#define S7_TX_BUF	0xf0
#define S7_RX_BUF	0xf8

#define TRUE	0xff
#define FALSE	0x00

#define S_RX_SIZE	2048	/*定义Socket接收缓冲区的大小,可以根据W5500_RMSR的设置修改 */
#define S_TX_SIZE	2048  	/*定义Socket发送缓冲区的大小,可以根据W5500_TMSR的设置修改 */

/***************----- W5500 GPIO定义 -----***************/
#define W5500_SCS		GPIO_Pin_12	//定义W5500的CS引脚	 
#define W5500_SCS_PORT	GPIOB
	
#define W5500_RST		GPIO_Pin_7	//定义W5500的RST引脚
#define W5500_RST_PORT	GPIOC

#define W5500_INT		GPIO_Pin_6	//定义W5500的INT引脚
#define W5500_INT_PORT	GPIOC

/***************----- 网络参数变量定义 -----***************/
extern unsigned char Gateway_IP[4];	//网关IP地址 
extern unsigned char Sub_Mask[4];	//子网掩码 
extern unsigned char Phy_Addr[6];	//物理地址(MAC) 
extern unsigned char IP_Addr[4];	//本机IP地址 

extern unsigned char S0_Port[2];	//端口0的端口号(5000) 
extern unsigned char S0_DIP[4];		//端口0目的IP地址 
extern unsigned char S0_DPort[2];	//端口0目的端口号(6000) 

extern unsigned char UDP_DIPR[4];	//UDP(广播)模式,目的主机IP地址
extern unsigned char UDP_DPORT[2];	//UDP(广播)模式,目的主机端口号

/***************----- 端口的运行模式 -----***************/
extern unsigned char S0_Mode;	//端口0的运行模式,0:TCP服务器模式,1:TCP客户端模式,2:UDP(广播)模式
#define TCP_SERVER		0x00	//TCP服务器模式
#define TCP_CLIENT		0x01	//TCP客户端模式 
#define UDP_MODE		0x02	//UDP(广播)模式 

/***************----- 端口的运行状态 -----***************/
extern unsigned char S0_State;	//端口0状态记录,1:端口完成初始化,2端口完成连接(可以正常传输数据) 
#define S_INIT			0x01	//端口完成初始化 
#define S_CONN			0x02	//端口完成连接,可以正常传输数据 

/***************----- 端口收发数据的状态 -----***************/
extern unsigned char S0_Data;		//端口0接收和发送数据的状态,1:端口接收到数据,2:端口发送数据完成 
#define S_RECEIVE		0x01		//端口接收到一个数据包 
#define S_TRANSMITOK	0x02		//端口发送一个数据包完成 

/***************----- 端口数据缓冲区 -----***************/
#define  Buffer_Len  2048
extern unsigned char Rx_Buffer[Buffer_Len];	//端口接收数据缓冲区 
extern unsigned char Tx_Buffer[Buffer_Len];	//端口发送数据缓冲区 
extern unsigned char Net_Buffer[255];
extern unsigned short SocketCount1,SocketCount2; //buf使用长度计数
extern unsigned char W5500_Interrupt;	//W5500中断标志(0:无中断,1:有中断)
typedef unsigned char SOCKET;			//自定义端口号数据类型

/****IP地址****/
extern unsigned short GoalIP;   //上档IP
extern unsigned short LocalIP;  //本机IP
extern unsigned char W5500_Link;

extern unsigned char ID0,ID1,ID2,ID3;
extern void Delay(unsigned int d);//延时函数(ms)
extern void SPI_Configuration(void);//W5500 SPI初始化配置(STM32 SPI1)
extern void W5500_Hardware_Reset(void);//硬件复位W5500
extern void W5500_Init(void);//初始化W5500寄存器函数
extern unsigned char Detect_Gateway(void);//检查网关服务器
extern void Socket_Init(SOCKET s);//指定Socket(0~7)初始化
extern unsigned char Socket_Connect(SOCKET s);//设置指定Socket(0~7)为客户端与远程服务器连接
extern unsigned char Socket_Listen(SOCKET s);//设置指定Socket(0~7)作为服务器等待远程主机的连接
extern unsigned char Socket_UDP(SOCKET s);//设置指定Socket(0~7)为UDP模式
extern unsigned short Read_SOCK_Data_Buffer(SOCKET s, unsigned char *dat_ptr);//指定Socket(0~7)接收数据处理
extern void Write_SOCK_Data_Buffer(SOCKET s, unsigned char *dat_ptr, unsigned short size); //指定Socket(0~7)发送数据处理
extern void W5500_Interrupt_Process(void);//W5500中断处理程序框架
extern void Load_Net_Parameters(void);
extern void W5500_Initialization(void);
extern void W5500_Socket_Set(void);
extern void GetCPU_ID(void);
unsigned char  SocketDataHandle(char *str,unsigned char tx_en);
#endif

5.3 MCP41010数字电位器应用

// 代码索引:
void delay_Xus(uint16_t us);
void MCP41010_IO_init(void);
void MCP41010_Write_Data(u8 command,u8 value);
// IO宏定义
#define MCP41010_CS_1      GPIO_SetBits(GPIOB,GPIO_Pin_6);
#define MCP41010_SI_1      GPIO_SetBits(GPIOE,GPIO_Pin_0);
#define MCP41010_SCK_1     GPIO_SetBits(GPIOB,GPIO_Pin_8);
#define MCP41010_CS_0      GPIO_ResetBits(GPIOB,GPIO_Pin_6);
#define MCP41010_SI_0      GPIO_ResetBits(GPIOE,GPIO_Pin_0);
#define MCP41010_SCK_0     GPIO_ResetBits(GPIOB,GPIO_Pin_8);


void delay_Xus(uint16_t us)
{
	uint16_t d;
	
	while(us--)
	{
		d=6;
		while(d--);
	}
}
/********************************************************************************
** 函数名称   :   MCP41010_IO_init
** 功能描述   :   MCP41010 IO初始化
** 输入变量   :   无
** 返 回 值   :  	无
** 最后修改人 :   xxx
** 最后更新日期:  20210727
** 说    明   :	 无
********************************************************************************/
void MCP41010_IO_init(void)
{
	u8 MCP41010Data = 0;
    GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOE,ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_8;   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;     
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;             //推挽输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;               //上拉
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;			
	GPIO_Init(GPIOB,&GPIO_InitStructure); 
//	GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_8);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;      
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;             //推挽输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;               //上拉
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;			
	GPIO_Init(GPIOE,&GPIO_InitStructure); 
	
	MCP41010Data = AT24CXX_ReadOneByte(MCP41010_Addr);
//	Uart4_Printf("MCP41010 init %d Pass\r\n@_@",MCP41010Data);
	MCP41010_Write_Data(0X11,MCP41010Data); // 默认阻值为最小
}

/********************************************************************************
** 函数名称   :   MCP41010_Write_Data
** 功能描述   :   MCP41010写数据
** 输入变量   :   
									1. command:命令字节指令
									2. value  :数据字节指令
** 返 回 值   :  	无
** 最后修改人 :   xxx
** 最后更新日期:  20210727
** 说    明   :	 无
********************************************************************************/
void MCP41010_Write_Data(u8 command,u8 value)
{
	u8 t;
	MCP41010_CS_0; // 片选信号
  delay_Xus(5);	
	MCP41010_SCK_0;
//	delay_Xus(5);
	for(t=0;t<8;t++) // 写入命令字节
  {              	        
	  if(((command&0x80)>>7)==1){MCP41010_SI_1;}  	  
	  else {MCP41010_SI_0;}	  
	  command<<=1;       
	  MCP41010_SCK_0;
	  delay_Xus(5); 
	  MCP41010_SCK_1;
	  delay_Xus(5);
  }
	for(t=0;t<8;t++) // 写入数据字节
  {              	        
	  if(((value&0x80)>>7)==1){MCP41010_SI_1;}  	  
	  else {MCP41010_SI_0;} 	  
	  value<<=1;       
	  MCP41010_SCK_0;
	  delay_Xus(5); 
	  MCP41010_SCK_1;
	  delay_Xus(5);
  }
//	delay_Xus(5);
  MCP41010_CS_1;  
}

5.4 DAC8411直流电压输出DAC

// 代码索引:

// 宏定义
// SCLK 置高低电平
#define DAC8411_SCLK_H	GPIO_SetBits(GPIOD,GPIO_Pin_6)
#define DAC8411_SCLK_L	GPIO_ResetBits(GPIOD,GPIO_Pin_6)
// SYNC 置高低电平
#define DAC8411_SYNC_H	GPIO_SetBits(GPIOB,GPIO_Pin_8)
#define DAC8411_SYNC_L	GPIO_ResetBits(GPIOB,GPIO_Pin_8)
// DIN 置高低电平
#define DAC8411_DIN_H	GPIO_SetBits(GPIOD,GPIO_Pin_5)
#define DAC8411_DIN_L	GPIO_ResetBits(GPIOD,GPIO_Pin_5)

#define Normal 	00
#define OUT_1K	01
#define OUT_100K	10
#define OUT_HIZ		11

else if(strncmp("SET POWER VOL:",ptr,14)==0) {SET_POWER_VOL(ptr);return ;} // 单位mV
/********************************************************
功能:初始化DAC8411 SPI IO与时钟
********************************************************/
void DAC8411_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; 	
	// 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOA,ENABLE); 
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;     		
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6;     		
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOD, &GPIO_InitStructure);
	
	// 以下是debug 代码,debug spi io 是否配置成功
	//DAC8411_SYNC_H;
	//DAC8411_SYNC_L;
	
	//DAC8411_SCLK_H;
	//DAC8411_SCLK_L;
	
	//DAC8411_DIN_H;
	//DAC8411_DIN_L;
}


/********************************************************
功能:提取参数
********************************************************/
void SET_POWER_VOL(char *ptr)
{
	char Str_K[8];
	float SET_VOL;
	strncpy(Str_K,ptr+14,strlen(ptr)-14);
	SET_VOL=atof(Str_K);
	POWER_SET_VOL(SET_VOL);
	Uart_Printf(UART4,"SET POWER=%.3f mV PASS\r\n",SET_VOL);
}

/********************************************************
功能:转换计算
********************************************************/
void POWER_SET_VOL(float Vol)
{
	DAC8411((u16)(Vol/5000*65536));
}

/********************************************************
功能:数据处理、向DAC8411 写入数据
********************************************************/
void DAC8411(u16 dat)
{
	u32 DAC_Data=0;
	u8 mode;
	u8 i;

	mode=Normal;

	DAC_Data=(u32)dat; // 将16位数据dat转换为32位无符号整数
	DAC_Data=DAC_Data<<14; // 左移14位,与DAC8411的18位串行接口对应。DAC8411需要18位数据,其中前两位用于选择工作模式,后16位用于表示模拟电压值。
	switch(mode)
	{
		case Normal:
			DAC_Data&=0x3fffffff; // 前2位清零
			break;
		case OUT_1K:
			DAC_Data|=0x40000000;
			DAC_Data&=0x7fffffff;
			break;
		case OUT_100K:
			DAC_Data|=0x80000000;
			DAC_Data&=0xbfffffff;
			break;
		case OUT_HIZ:
			DAC_Data|=0xC0000000;
//			DAC_Data&=0xbfffffff;
			break;
		default: break;
	}
//	DAC_Data = 0x20000000; // debug 代码
	DAC8411_SYNC_H;
	DAC8411_SCLK_H;
	DAC8411_DIN_H;
	
	DAC8411_SYNC_L; // Cs 线拉低,开始通信
	for(i=0;i<18;i++) // 只发送前18位有效信号
	{
		if((DAC_Data<<i)&0x80000000)
			DAC8411_DIN_H;
		else
			DAC8411_DIN_L;
		
		DAC8411_SCLK_H;
		delay_us(5);
		DAC8411_SCLK_L;
		delay_us(5);
	}
	DAC8411_SYNC_H;  // Cs 线拉高,结束通信
	DAC8411_SCLK_H;
	DAC8411_DIN_H;
}

6.IIC 通信

下述使用1 表示逻辑高电平,0 表示逻辑低电平;

6.1 综述

IIC 总线(IIC bus):(Inter-Integrated Circuit bus - 内部集成电路总线),一种两线式串行总线,由数据线 SDA时钟 SCL组成; 一个I2C总线上只允许一个I2C主设备(即CPU),可以允许并接很多I2C从设备,这些设备必须具备唯一的地址以便于区分。


I2C 总线在传送数据过程中共有三种类型信号:开始信号、结束信号和应答信号:

  • 传输启动信号:当SCL 为1,SDA 从1到0,即产生一个启动条件;
  • 传输结束信号:当SCL 稳定为1,SDA 从0到1,即产生一个结束条件;
  • 应答信号ACK 与NACK:在收到每个字节word 后,接收方必须向发送方发送应答ACK 信号以表示其已成功收到字节数据;应答在SCL 的第8个完整时钟的下降沿开始释放SDA 线,然后在第9个完整时钟内,SDA 通过保持逻辑0 来表示应答ACK 信号;当主机想要表示已接收到目标数据并打算终止通讯时,主机就会在第9个完整时钟内,通过控制SDA 保持逻辑1 来表示无应答NACK 信号;

  • IIC 通讯时序图
    在这里插入图片描述
  • 在启动信号和结束信号之间的信号传输过程,可以把时钟线SCL 理解为红绿灯,低电平是绿灯,高电平是红灯,而数据线SDA 传输的每一个数据则相当于一辆汽车,高电平是A,低电平是B。当绿灯亮了的时候,汽车就可以过去,只不过这里的交通规则是每亮一次绿灯,只能通过一辆汽车。所以,IIC通讯的过程就是红绿灯交替闪烁(也就是时钟线输出时钟脉冲),汽车跟着一辆一辆的过去,过去的是A,就是传输了一个“1”,过去的B,就是传输了一个“0”,连续传输8次,就可以组成一个8位的二进制数,也就是一个字节的数据,反复这个过程就能实现两个设备之间的通讯。

  • SCL: 时钟线是由主设备输出,到从设备输入的,以单片机为例,单片机的IO口要给SCL引脚输出一个方波脉冲,因为IIC设备支持的最大通讯频率一般都是400kHz,也就是说一个时钟周期(一个高电平加一个低电平为一个周期)不能小于2.5us . 单片机输出时钟的时候一定要注意高低电平延时的时间,延时的时间越长,通讯的速率越慢。另外,时钟线不会一直输出脉冲,只会在需要通讯的时候输出,并且要遵循一定的规则。需要通讯的时候时钟线先要输出一个“起始信号”告诉从设备我要开始通讯了,其实就是电平由高到低跳变,但是这个高电平的持续时间不能太短,具体最少要多少时间需要看芯片手册。然后再根据固定的时间输出高低脉冲,直到到了要停止通讯的时候,时钟线要输出一个“结束信号”告诉从设备我不通讯了,其实就是电平一直拉高。
  • SDA:而数据线传输的数据是双向的,以单片机与BH1750通信为例,首先,单片机要先发一个器件地址(器件地址是7位的),再发送一个读写位(0表示是写入,1表示读取),器件地址和读写位加起来刚好是一个字节,然后BH1750会给单片机回一个应答位,意思就是“我收到了”。然后单片机就可以接着发送数据了,每次都是以1个字节为间隔发。收也是类似的,只是把单片机发数据改成收数据。(注:器件地址是用来区分从设备的,因为有时候同一根时钟线和数据线可能会连接多个从设备,也就是说主设备发送的数据所有的从设备都可以收到,所以主设备要先发送一个器件地址,告诉所有的从设备我是给哪个设备发命令,其他设备收到了也不要执行)。

以OPT3001低功耗环境光传感器的IIC数据读写操作为例,如下图,

  • 数据写入过程

    1. 最左边先是有一个“Start by Master”,也就是单片机先给一个“起始信号”;
    2. 然后后面接着传输了8位数据(1 0 0 0 1 A1 A0 R/W)。其中,“1 0 0 0 1 A1 A0”是器件地址,因为这里的器件地址有4个可选,所以用了A1和A0表示,“R/W”是读写位,这里是写入,所以这里的R/W应该是一个“0”;
    3. 接着是“ACK byOPT3001”,这是从设备给主设备发的应答,就是说“你发的数据我收到了,你可以接着发了”;
    4. 然后接下来的RA7-RA0是寄存器地址(因为寄存器不止一个所以要先发地址,告诉它你接下来要把数据存到哪里);
    5. 再后面的D15-D0是两个字节的数据(这些数据就是存到前面发的那个地址的寄存器里面);
    6. 重复上述4、5 步可写入多个寄存器数据;
    7. “Stop by Master”,也就是单片机发起“停止信号”;
  • 数据读取过程:读取的过程和写入类似;

  1. 先是“起始信号”;
  2. 再是器件地址+读写位;
  3. 接着是应答;
  4. 再是寄存器地址;
  5. 接着是应答;
  6. 然后开始接收数据(单片机的SDA引脚IO口要从输出改成输入),D15-D0是接收到两个字节的数据;
  7. 应答,“ACK by Master”是单片机给OPT3001发的应答。(只要是接收的一方都要发应答,不应答的话通讯就会结束,比如读取的第二个字节后面的“No ACK by Master”)

在这里插入图片描述


为了避免设备间硬件冲突,CPU配置的SCL和SDA输出均为开漏输出模式,因此必须外接上拉电阻才能输出高电平。上拉电阻越大越省电,但是信号的上升沿越平滑,这将限制I2C通信的速率。上拉电阻越小,上升沿越陡,通信速率可以更高,但是低电平输出时的功耗越大。因此需要选择合适的上拉电阻阻值以便在功耗和速度之间寻求一个平衡点。一般取1-10K欧均可。

6.2 核心代码

  • IIC IO口初始化

其中SCL配置为输出引脚,SDA根据数据收发状态配置成输入/输出引脚,配置函数由SDA_IN()SDA_OUT()完成。

函数索引:
void IIC_Init(void);
void IIC_Start(void);
void IIC_Stop(void);
u8 IIC_Wait_Ack(void);
void IIC_Ack(void);
void IIC_NAck(void);
void IIC_Send_Byte(u8 txd);
u8 IIC_Read_Byte(unsigned char ack);
void IIC_Write_Data(u16 RomAddress,u8 data);
u8 IIC_Read_Data(u16 RomAddress);
void SDA_IN(void)  ;
void SDA_OUT(void) ;
void Set_IIC_SDA(void);
void Reset_IIC_SDA(void);
void Set_IIC_SCL(void);
void Reset_IIC_SCL(void);
u8 READ_SDA(void);
u8 READ_SCL(void);
  • IIC IO 初始化
/********************************************************************************
** 函数名称   :   IIC_Init
** 功能描述   :  IIC IO 初始化
** 输入变量   :   无
** 返 回 值   :  	无
** 最后修改人 :   xxx
** 最后更新日期:  xxx
** 说    明   :	 这里假设使用PB10引脚作为SCL、PB11引脚作为SDA,在初始化阶段,SDA引脚可任意配置成输入/输出模式
********************************************************************************/
void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; // 定义结构体变量
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//IO 时钟使能
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 GPIO
	
	GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11); //PB10,PB11 输出高
}
  • 产生传输开始信号
/********************************************************************************
** 函数名称   :   IIC_Start
** 功能描述   :  产生IIC 传输启动信号
** 输入变量   :   无
** 返 回 值   :  	无
** 最后修改人 :   xxx
** 最后更新日期:  xxx
** 说    明   :	 无
********************************************************************************/
void IIC_Start(void)
{
	SDA_OUT(); // SDA 引脚设置为输出
	Set_IIC_SDA(); // SDA 设置为1
	Set_IIC_SCL();// SCL 设置为1
	delay_us(4);
	Reset_IIC_SDA(); // SDA 设置为0 
	delay_us(4);
	Reset_IIC_SCL(); // SCL 设置为0
}
  • 产生传输终止信号
/********************************************************************************
** 函数名称   :   IIC_Stop
** 功能描述   :  产生IIC 传输终止信号
** 输入变量   :   无
** 返 回 值   :  	无
** 最后修改人 :   xxx
** 最后更新日期:  xxx
** 说    明   :	 无
********************************************************************************/
void IIC_Stop(void)
{
	SDA_OUT(); // SDA 引脚设置为输出
	Reset_IIC_SCL(); // SCL 设置为0
	Reset_IIC_SDA(); // SDA 设置为0 
	delay_us(4);
	Set_IIC_SCL(); // SDA 设置为1
	Set_IIC_SDA(); // SCL 设置为1
	delay_us(4); 
}
  • 等待ACK应答信号
/********************************************************************************
** 函数名称   :   IIC_Wait_Ack
** 功能描述   :  等待ACK应答信号
** 输入变量   :   无
** 返 回 值   :   1,接收应答失败
			      0,接收应答成功
** 最后修改人 :   xxx
** 最后更新日期:  xxx
** 说    明   :	 无
********************************************************************************/

u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_IN(); //SDA 引脚设置为输入 
	Set_IIC_SDA();delay_us(1); 
	Set_IIC_SCL();delay_us(1);
	while(READ_SDA()==1)
	{ 	ucErrTime++;
		if(ucErrTime>250)
		{ 	IIC_Stop();
			return 1;
		}
	}
	Reset_IIC_SCL(); //时钟输出 0 
	return 0; 
}
  • 产生ACK应答信号
/********************************************************************************
** 函数名称   :   IIC_Ack
** 功能描述   :  产生 ACK 应答信号
** 输入变量   :   无
** 返 回 值   :   无
** 最后修改人 :   xxx
** 最后更新日期:  xxx
** 说    明   :	 无
********************************************************************************/
void IIC_Ack(void)
{   Reset_IIC_SCL();
	SDA_OUT();
	Reset_IIC_SDA();
	delay_us(2);
	Set_IIC_SCL();
	delay_us(2);
	Reset_IIC_SDA();
}
  • 不产生ACK应答信号
/********************************************************************************
** 函数名称   :   IIC_NAck
** 功能描述   :  产生 NACK 不应答信号
** 输入变量   :   无
** 返 回 值   :   无
** 最后修改人 :   xxx
** 最后更新日期:  xxx
** 说    明   :	 无
********************************************************************************/
void IIC_NAck(void)
{ 
	Reset_IIC_SDA();
	SDA_OUT();
	Set_IIC_SDA();
	delay_us(2);
	Set_IIC_SCL();
	delay_us(2);
	Reset_IIC_SCL();
}
  • 发送/读取一个字节数据
//返回从机应答情况
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
	u8 t; 
	SDA_OUT(); 
	 Reset_IIC_SCL();//拉低时钟开始数据传输
	 for(t=0;t<8;t++)
	 { 	IIC_SDA=(txd&0x80)>>7; //IIC_SDA就是IIC的SDA引脚
		txd<<=1; 
		delay_us(2); //对 TEA5767 这三个延时都是必须的
		Set_IIC_SCL();
		delay_us(2); 
		Reset_IIC_SCL();
		delay_us(2);
	 }
}

// ack=1 时,发送 ACK,ack=0,发送 nACK
u8 IIC_Read_Byte(unsigned char ack)
{ 
	unsigned char i,receive=0;
	SDA_IN(); //SDA 设置为输入
	 for(i=0;i<8;i++ )
	{ 
		 Reset_IIC_SCL(); 
		 delay_us(2);
		 Set_IIC_SCL();
		 receive<<=1;
		 if(READ_SDA()==1)receive++; 
		 delay_us(1); 
	 }
	 if (!ack)
	 	IIC_NAck(); //发送 nACK
	 else
	 	IIC_Ack(); //发送 ACK
		 return receive;
}
  • 发送/接收一组数据
void IIC_Write_Data(u16 RomAddress,u8 data)
{
	u8 flag=0;
	IIC_Channel = 1;
	do
	{
		IIC_Start();
		IIC_WRITE_BYTE(WriteDeviceIIC);	 //写操作  
		if(IIC_Recelve_Ack()==0)  		 //判断ACK
		{ 
//	      IIC_WRITE_BYTE((unsigned char)(RomAddress>>8));	  //写H地址 (地址为16位时用到)  
//		  if(IIC_Recelve_Ack()==0)  //判断ACK
	  	  {
			IIC_WRITE_BYTE((unsigned char)RomAddress);	  //写L地址
			if(IIC_Recelve_Ack()==0)  //判断ACK
			{
				IIC_WRITE_BYTE(data);
			    if(IIC_Recelve_Ack()==0)  //判断ACK
			    {
					flag=0;
				}
			    else
			    {
					flag=1;
				}
			}
			else
			{
				flag=1;
			}			
		  }	
//		  else
//		  {flag=1;}
	    }
		else
		{
			flag=1;
		}
		IIC_Stop();      //停止
		delay_Nus(250);
	}while(flag==1);	
	delay_Nms(1);
}

u8 IIC_Read_Data(u16 RomAddress)
{
	unsigned char flag,data=0;
    IIC_Channel = 1;
	do
	{
		IIC_Start();
		
		IIC_WRITE_BYTE(WriteDeviceIIC);	 //写操作 
		if(IIC_Recelve_Ack()==0)  		//判断ACK
		{
//			IIC_WRITE_BYTE((unsigned char)(RomAddress>>8));		//写H地址
//			if(IIC_Recelve_Ack()==0)  //判断ACK
			{
				IIC_WRITE_BYTE((u8)RomAddress);		//写L地址
				if(IIC_Recelve_Ack()==0)  //判断ACK
				{
					IIC_Start();
					IIC_WRITE_BYTE(ReadDeviceIIC);   //读操作
					if(IIC_Recelve_Ack()==0)  //判断ACK
					{
						
						data=IIC_Read_Byte(0);
						flag=0;				
					}
					else
					{flag=1;}  
				}
				else
				{flag=1;} 	
			}
//			else
//			{flag=1;}
		}
		else
	    {flag=1;}	
		IIC_Stop();   //停止
		delay_Nus(100);
	}while(flag==1);
	return data;
}
  • 配置SDA工作在接收模式
// 假设使用了两个通道的IIC,这两个通道的SDA引脚分别是MCU的PB7和PA4,这两个引脚分别是24Cxx的SDA引脚、PCA9555的SDA引脚
void SDA_IN(void)  
{	
	GPIO_InitTypeDef GPIO_InitStructure;  //定义结构体变量
	switch(IIC_Channel)
	{
		case 1:
		{
			 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;      //SDA			
			 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	   //上拉输入
			 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
			 GPIO_Init(GPIOB, &GPIO_InitStructure);		 //PB 
		}break;

		case 2: 
		{
			 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;      //SDA			
			 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;	 //上拉输入
			 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
			 GPIO_Init(GPIOA, &GPIO_InitStructure);		     //PB 
		}break;
  • 配置SDA工作在发送模式
//假设使用了两个通道的IIC,这两个通道的SDA引脚分别是PB7和PA4
void SDA_OUT(void) 
{	
	GPIO_InitTypeDef GPIO_InitStructure;  //定义结构体变量
	
	switch(IIC_Channel)
	{
		case 1: 
		{
			GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;   //SDA			
			GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   //推挽输出
			GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
			GPIO_Init(GPIOB, &GPIO_InitStructure);		 //PB 
						
		}break;
		
		case 2: 
		{
			GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;   //SDA			
			GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   //推挽输出
			GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
			GPIO_Init(GPIOA, &GPIO_InitStructure);		 //PB 
		}break;
  • 置位与复位SDA引脚
void Set_IIC_SDA(void)
{
	switch(IIC_Channel)
	{
		case 1:GPIO_SetBits(GPIOB,GPIO_Pin_7);break; 
		case 2:GPIO_SetBits(GPIOA,GPIO_Pin_4);break;	 
	}
}
void Reset_IIC_SDA(void)
{
	switch(IIC_Channel)
	{
		case 1:GPIO_ResetBits(GPIOB,GPIO_Pin_7);break; 
		case 2:GPIO_ResetBits(GPIOA,GPIO_Pin_4);break;	
	}		
}
  • 置位与复位SCL引脚
void Set_IIC_SCL(void)
{
	switch(IIC_Channel)
	{
		case 1:GPIO_SetBits(GPIOB,GPIO_Pin_6);break;
		case 2:GPIO_SetBits(GPIOA,GPIO_Pin_3);break;	
	}
}
void Reset_IIC_SCL(void)
{
	switch(IIC_Channel)
	{
		case 1:GPIO_ResetBits(GPIOB,GPIO_Pin_6);break;
		case 2:GPIO_ResetBits(GPIOA,GPIO_Pin_3);break;	
	}
}
  • 读SDA/SCL一位数据
u8 READ_SDA(void)
{
	u8 flag=0;
	switch(IIC_Channel)
	{
		case 1:flag=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7);break;
		case 2:flag=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4);break;		
	}
	return flag;
}
u8 READ_SCL(void)
{
	u8 flag=0;
	switch(IIC_Channel)
	{
		case 1:flag=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6);break;
		case 2:flag=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3);break;		
	}
	return flag;
}

3.7 IIC应用案例之PCA9554A - IO扩展器

该IC通过IIC总线控制多个IO端口,从而实现IO扩展功能;

函数索引:
void PCA9554Init(u8 address,u8 mode);
void PCA9554WriteData(u8 deviceAddress,u8 data);
u8 PCA9554ReadData(u8 address);
void PCA9554OutputControl(u8 channel,u8 statue);
/**********************************************************************************************************
*	函 数 名: PCA9554Init
*	功能说明: PCA9554A初始化,配置PCA9554工作模式
*	形    参:
					address:设备地址
					mode:工作模式,0-输出模式,1-输入模式
*	返 回 值: 无
**********************************************************************************************************/
void PCA9554Init(u8 address,u8 mode)
{
	u8 wait_flag=0;
	u8 wait_time=0;
//	IIC_Channel = 2;
	do
	{
		IIC_Start();
		IIC_WRITE_BYTE(address);
		if(IIC_Recelve_Ack()==0)
		{
			IIC_WRITE_BYTE(0x03);
			if(IIC_Recelve_Ack()==0)
			{
				if(mode)
				IIC_WRITE_BYTE(0xFF);        
				else
				IIC_WRITE_BYTE(0x00);        			
				if(IIC_Recelve_Ack()==0)
					wait_flag=0;
				else
					wait_flag=1;
			}
			else
				wait_flag=1;
		}
		else
			wait_flag=1;
		wait_time++;
	}while(wait_flag==1&&wait_time<200);
	
	IIC_Stop();
	delay_ms(1);
}
/**********************************************************************************************************
*	函 数 名: PCA9554WriteData
*	功能说明: PCA9554A数据写入
*	形    参:
					deviceAddress:设备地址
					data:要写入的数据
*	返 回 值: 无
**********************************************************************************************************/
void PCA9554WriteData(u8 deviceAddress,u8 data)
{
	u8 wait_flag=0;
	u8 wait_time=0;
	IIC_Channel = 2;
	do
	{
		IIC_Start();
		IIC_WRITE_BYTE(deviceAddress);
		if(IIC_Recelve_Ack()==0)
		{
			IIC_WRITE_BYTE(0x01);
			if(IIC_Recelve_Ack()==0)
			{
				IIC_WRITE_BYTE(data);
				if(IIC_Recelve_Ack()==0)
					wait_flag=0;
				else
					wait_flag=1;
			}
			else
				wait_flag=1;
		}
		else
			wait_flag=1;
		wait_time++;
	}while(wait_flag==1&&wait_time<200);
	
	IIC_Stop();
	delay_ms(1);
}

/**********************************************************************************************************
*	函 数 名: PCA9554ReadData
*	功能说明: PCA9554A数据读取
*	形    参:
					address:地址
*	返 回 值: PCA9554Data:读取的数据
**********************************************************************************************************/
u8 PCA9554ReadData(u8 address)
{
	u8 wait_flag=0;
	u8 wait_time=0;
	u8 PCA9554Data;
	IIC_Channel = 2;
	do
	{
		IIC_Start();
		IIC_WRITE_BYTE(address);
		if(IIC_Recelve_Ack()==0)
		{
			IIC_WRITE_BYTE(0x00);
			if(IIC_Recelve_Ack()==0)
			{
				IIC_Start();
				IIC_WRITE_BYTE(address+1);

				if(IIC_Recelve_Ack()==0)
				{
					PCA9554Data=IIC_Read_Byte(0);
					wait_flag=0;
				}
				else
					wait_flag=1;
			}
			else
				wait_flag=1;
		}
		else
			wait_flag=1;
		wait_time++;
	}while(wait_flag==1&&wait_time<200);

	IIC_Stop();
	delay_ms(1);
	
	return PCA9554Data;
}
/**********************************************************************************************************
*	函 数 名: PCA9554OutputControl
*	功能说明: PCA9554输出控制
*	形    参:
					channel:输出端口
					statue:输出口状态
*	返 回 值: 无
**********************************************************************************************************/
void PCA9554OutputControl(u8 channel,u8 statue)
{
	u8	SetValue;
	u8  value1;	
	value1 = PCA9554ReadData(PCA9554_4); 	
	switch(channel)
	{
		case 1:{
							if(statue == OFF) //OFF=0
							{
								SetValue = 0x01;
							}
							else if(statue == ON)//ON=1
							{
								SetValue = 0xFE;						
							}
							break;
					 }
		case 2:{
							if(statue == OFF) //OFF
							{
								SetValue = 0x02;
							}
							else if(statue == ON)
							{
								SetValue = 0xFD;						
							}
							break;
					 }
		case 3:{
							if(statue == OFF) //OFF
							{
								SetValue = 0x04;
							}
							else if(statue == ON)
							{
								SetValue = 0xFB;						
							}
							break;
					 }
		case 4:{
							if(statue == OFF) //OFF
							{
								SetValue = 0x08;
							}
							else if(statue == ON)
							{
								SetValue = 0xF7;						
							}
							break;
					 }
		case 5:{
							if(statue == OFF) //OFF
							{
							SetValue = 0x10;
							}
							else if(statue == ON)
							{
							SetValue = 0xEF;						
							}
							break;
					 }
		case 6:{
							if(statue == OFF) //OFF
							{
								SetValue = 0x20;
							}
							else if(statue == ON)
							{
								SetValue = 0xDF;						
							}
							break;
					 }
		case 7:{
							if(statue == OFF) //OFF
							{
								SetValue = 0x40;
							}
							else if(statue == ON)
							{
								SetValue = 0xBF;						
							}
							break;
						}
		 case 8:{
							if(statue == OFF) //OFF
							{
								SetValue = 0x80;
							}
							else if(statue == ON)
							{
								SetValue = 0x7F;						
							}
							break;
						}
		default: break;
	}
		delay_ms(2);
	if(statue == ON)
		PCA9554WriteData(PCA9554_4,(value1 & SetValue));  
	else if(statue == OFF)
		PCA9554WriteData(PCA9554_4,(value1 | SetValue));  
		delay_ms(2);
		return;	
}	

3.8 IIC 时钟频率与数据传输速率

  • 时钟频率(Hz):SCL 线上的频率就叫时钟频率,
  • 数据传输速率(bit/s):一个SCL 时钟周期只能传输1bit 数据(并且只能在SCL 的低电平期间进行SDA 数据电平的变化),假如SCL 的时钟频率为100KHz,即1s 内能产生100K 个SCL 时钟周期,即1s 内可以传输100Kbit 的数据。此时,可知时钟频率100KHz 与数据传输率100Kbit/s 是对应的;
  • 常用的IIC 时钟频率: IIC传输位速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s;也可以理解为时钟频率在标准模式下可达100kHz,快速模式下可达400kHz,高速模式下可达3.4MHz;

例:下面计算一下100KHz的时钟速率对应的一个周期的时间为多少:
T= 1/f = 1/100 000 = 0.00001S = 10us;
下图是调试IIC过程中,读数据时逻辑分析仪记录的波形:
在这里插入图片描述

参考:jackailson - IIC 时钟频率与数据传输速率的理解

3.9 IIC 波形调试问题

  1. SCL 上升时间tr 大于datesheet 标准:上升时间大,则意味着与标准相比,上升的较为平缓,驱动能力弱。这种问题往往是上拉电阻阻值选择不合理导致的。负载本身含有电容,如果上拉电阻的选取过大,RC延时电路,进而导致上升变得平缓。

    • 解决办法:减小上拉电阻;软件调节驱动能力。
  2. SDA SCL波形整体略微被抬高,低电平很难接近0V,甚至超过0.4V:这类问题,存在两种可能性:
    A:测试时,示波器探头未进行校准,导致测试时,出现这类问题。
    B:上拉电阻选取过小导致驱动电流过大。

    • 解决办法:校准示波器;适当的减小上拉电阻阻值。
  3. SDA信号出现毛刺

    • 解决办法:PCB走线包地;SDA信号串小电阻。

*. 参考


  1. 动图:秒懂各种常用通信协议原理
  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Truffle7电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值