常见通信协议
单端通信的双方需要共地,将GND连在一起
USART:USART(通用同步 / 异步收发传输器)是 UART(通用异步收发传输器)的增强版本,UART 只支持异步通信,而 USART 既支持异步也支持同步通信。以下是常见引脚及其意义:
- TX(Transmit)/TXD
- 功能:数据发送引脚,用于将微控制器内部的数据发送到外部设备。在异步通信中,数据以串行方式一位一位地通过该引脚输出。例如,当 STM32 要将传感器采集的数据发送给计算机时,数据就从 TX 引脚输出。
- 电平特性:通常为 TTL 电平,如果要与计算机等设备通信,可能需要通过 USB 转 TTL 模块将 TTL 电平转换为 RS - 232 电平。
- RX(Receive)/RXD
- 功能:数据接收引脚,用于接收外部设备发送过来的数据。外部设备的数据以串行方式通过该引脚输入到微控制器内部。比如计算机向 STM32 发送控制指令时,指令数据就从 RX 引脚进入 STM32。
- 电平特性:同样为 TTL 电平,接收时也需要考虑与外部设备电平的匹配问题。
- SCLK(仅 USART 同步模式使用)
- 功能:同步时钟信号引脚,在同步通信模式下,主设备通过该引脚向从设备提供时钟信号,以确保数据传输的同步性。主设备和从设备根据这个时钟信号来协调数据的发送和接收。
- 电平特性:与系统的逻辑电平一致,一般为 TTL 电平。
I2C:I2C(Inter - Integrated Circuit)是一种串行通信协议,使用两根线进行通信
- SCL(Serial Clock Line)
- 功能:串行时钟线,主设备通过该引脚向从设备提供时钟信号,控制数据传输的节奏。主设备在 SCL 线上产生时钟脉冲,在时钟脉冲的控制下,数据在 SDA 线上进行传输。
- 电平特性:开漏输出,需要外接上拉电阻。
- SDA(Serial Data Line)
- 功能:串行数据线,用于在主设备和从设备之间传输数据。在通信过程中,数据的发送和接收都通过这根线进行。主设备可以向从设备发送数据,也可以从从设备读取数据。
- 电平特性:开漏输出,需要外接上拉电阻到电源。上拉电阻的作用是在总线空闲时将总线拉高,保证信号的正常传输。
- 通信特点:数据传输是双向的,但同一时刻只能在一个方向上传输数据,属于半双工通信。
SPI:SPI(Serial Peripheral Interface)是一种高速、全双工、同步的串行通信协议,通常使用四根线进行通信,各引脚意义如下:
- SCK(Serial Clock)
- 功能:串行时钟线,由主设备产生时钟信号,为数据传输提供同步时钟。主设备和从设备根据这个时钟信号来同步数据的发送和接收。时钟信号的频率决定了数据传输的速度。
- 电平特性:与系统的逻辑电平一致,一般为 TTL 电平。
- MOSI(Master Output Slave Input)
- 功能:主设备输出、从设备输入引脚。主设备通过该引脚向从设备发送数据。在通信过程中,主设备将数据一位一位地通过 MOSI 线发送给从设备。
- 电平特性:与系统的逻辑电平一致,一般为 TTL 电平。
- MISO(Master Input Slave Output)
- 功能:主设备输入、从设备输出引脚。从设备通过该引脚向主设备返回数据。主设备在发送数据的同时,可以通过 MISO 线接收从设备返回的数据,实现全双工通信。
- 电平特性:与系统的逻辑电平一致,一般为 TTL 电平。
- SS(Slave Select)/CS (Chip Select)
- 功能:从设备选择引脚(ss),也称为片选引脚(cs)。主设备通过该引脚选择要通信的从设备。当 SS /CS引脚为低电平时,表示选中对应的从设备,主设备可以与该从设备进行通信;当 SS /CS引脚为高电平时,表示未选中该从设备。
- 电平特性:低电平有效,与系统的逻辑电平一致,一般为 TTL 电平。在一个 SPI 总线上可以连接多个从设备,通过不同的 SS 引脚来选择不同的从设备进行通信。
CAN:CAN 是一种串行通信协议,最初为汽车电子领域设计,用于高效、可靠地在多个电子控制单元(ECU)之间进行数据通信。如今,它在工业自动化、船舶、医疗设备等领域也有广泛应用。
- CAN_H 和 CAN_L
- 功能:CAN 总线采用差分信号传输数据,CAN_H 和 CAN_L 是一对差分信号线。数据通过这两根线之间的电压差来表示,这样可以有效抵抗电磁干扰,提高通信的可靠性和传输距离。例如在汽车的复杂电磁环境中,差分信号能保证数据准确传输。
- 电平特性:CAN_H 和 CAN_L 的电压差决定了总线的逻辑状态。隐性状态(逻辑 1)时,CAN_H 和 CAN_L 的电压差接近 0V;显性状态(逻辑 0)时,CAN_H 的电压比 CAN_L 高约 2V 左右。
- VCC
- 功能:为 CAN 收发器提供电源。确保收发器正常工作,不同的 CAN 收发器对电源电压的要求可能不同,常见的有 3.3V 或 5V。
- 电平特性:电压值需符合收发器的规格要求,要保证电源的稳定性,否则可能影响通信质量。
- GND
- 功能:接地引脚,为整个 CAN 通信系统提供参考电位,保证信号的稳定传输。如果接地不良,可能会引入噪声干扰,导致通信错误。
USB:USB 是一种广泛应用的通用串行总线标准,具有高速、易用、支持热插拔等特点,用于连接计算机和各种外部设备,如鼠标、键盘、打印机、移动硬盘等。
- VCC
- 功能:为 USB 设备提供电源。USB 接口通常可以提供 5V 的电源电压,为一些小型设备(如 USB 风扇、USB 小台灯等)供电。
- 电平特性:标准电压为 5V,电压波动范围一般在规定的公差范围内,以保证设备的正常工作。
- GND
- 功能:接地引脚,为 USB 通信提供参考电位,确保信号的稳定传输。同时,它也起到了电气安全的作用,防止设备因静电等原因损坏。
- D+ 和 D - 正端(Data Positive,DP)和负端(Data Negative,DM)
- 功能:USB 采用差分信号进行数据传输,D+ 和 D - 是一对差分信号线。数据通过这两根线之间的电压差来表示。在 USB 1.1 和 USB 2.0 中,高速模式下通过 D+ 和 D - 的电压变化来传输数据;在 USB 3.0 及以上标准中,除了 D+ 和 D - 外,还增加了额外的信号线以提高传输速率。
- 电平特性:在 USB 2.0 全速模式下,逻辑 0 和逻辑 1 由 D+ 和 D - 之间的电压差来区分。在空闲状态下,D+ 和 D - 都处于低电平;在数据传输时,通过不同的电压组合来表示不同的逻辑状态。
- ID(仅适用于 USB OTG 设备)
- 功能:用于区分 USB OTG(On - The - Go)设备的工作模式。当 ID 引脚接地时,设备作为设备(Device)模式工作;当 ID 引脚悬空时,设备作为主机(Host)模式工作。这使得 USB OTG 设备可以在不同的场景下灵活切换角色。
通信协议原理学习
(一)USART 与 UART
1. 原理
- UART(Universal Asynchronous Receiver/Transmitter):异步串行通信方式,通信双方无需共同时钟信号。数据以帧为单位传输,一帧包含起始位、数据位、校验位(可选)和停止位。起始位为低电平,通知接收方开始接收;数据位通常为 5 - 8 位;校验位用于检测传输错误;停止位为高电平,标志一帧结束。
- USART(Universal Synchronous/Asynchronous Receiver/Transmitter):是 UART 的增强版本,既支持异步通信(同 UART),还支持同步通信。同步通信时需额外的时钟信号来同步数据传输。
波特率(Baud Rate)
- 定义:表示每秒传输的符号(通常是二进制位)数量,用于衡量数据传输的速率。
- 常见取值:常见的有 9600、115200、4800、19200 等。在实际应用中,要依据通信需求和设备性能来选择。例如,蓝牙模块常使用 9600 波特率进行低速数据传输;而一些高速数据采集设备可能采用 115200 波特率以实现快速数据传输。
- 影响:波特率越高,数据传输速度越快,但对硬件处理能力和通信线路质量要求也越高。若设置过高,可能导致数据传输错误增多;设置过低则会使传输效率低下。
起始位(Start Bit)
- 定义:位于每个数据帧的开头,是一个逻辑 0 信号,用于通知接收方一个新的数据帧即将开始传输。
- 特点:起始位的宽度通常为 1 位时间,即一个波特率周期。在空闲状态下,串口线处于高电平,当发送方要发送数据时,先将串口线拉低至低电平,持续一个波特率周期,以此作为起始信号。
- 作用:接收方通过检测到起始位的下降沿,来同步自己的接收时钟,并开始接收后续的数据位,确保数据接收的准确性和同步性。
数据位(Data Bits)
- 定义:是每个数据帧中实际传输的有效数据部分,其位数决定了一次能传输的信息量。
- 常见取值:有 5 位、6 位、7 位、8 位等。8 位数据位最为常用,可表示一个字节的数据,能方便地与大多数计算机系统和设备进行数据交互。例如,在 ASCII 码字符传输中,通常使用 7 位数据位来表示 128 个不同的字符,若需要传输扩展 ASCII 码或其他 8 位编码的数据,则使用 8 位数据位。
- 影响:数据位的选择会影响数据的表示范围和传输效率。位数越多,能表示的状态就越多,但传输相同数量的数据所需的时间也越长。
校验位(Parity Bit)
- 定义:用于检测数据在传输过程中是否出现错误。发送方根据数据位的内容计算出校验位并添加到数据帧中,接收方则按相同规则重新计算校验位并与接收到的校验位进行比对,不一致则表示数据可能出错。
- 常见取值:常见的校验方式有无校验(None)、奇校验(Odd)、偶校验(Even)、标记校验(Mark)和空格校验(Space)。无校验即不使用校验位;奇校验要求数据位和校验位中 1 的总数为奇数;偶校验要求数据位和校验位中 1 的总数为偶数;标记校验固定将校验位设为 1;空格校验固定将校验位设为 0。
- 影响:使用校验位可提高数据传输的可靠性,但会增加传输开销,因为校验位也需占用传输时间和带宽。在对可靠性要求高的场景,如金融交易、航空航天等领域,常使用校验位;对速度要求高、对错误容忍度相对较高的场景,如一些实时视频流传输,可能选择无校验。
停止位(Stop Bit)
- 定义:位于数据帧的末尾,用于表示一个数据帧的结束。发送方在传输完数据位和校验位(若有)后,发送停止位信号,告知接收方一帧数据传输完毕。
- 常见取值:常见的停止位设置有 1 位、1.5 位和 2 位。1 位停止位应用最为广泛,能在保证数据传输可靠性的同时,提高传输效率。例如,在大多数常规的串口通信设备中,如单片机与电脑之间的通信,通常采用 1 位停止位。
- 影响:停止位位数越多,数据传输的可靠性越高,但传输效率会降低,因为停止位不携带有效数据,过多的停止位会增加传输时间。在对实时性要求不高、对数据准确性要求较高的场合,可以适当增加停止位的位数。
实际有四个寄存器,但是程序上只用操控一个DR寄存器(程序自动分别读接受寄存器,写发送寄存器)
2. 应用场景
常用于设备与计算机之间的数据传输、传感器数据采集上传等,如温湿度传感器通过 UART 将数据发送给 STM32。
代码
#include "stm32f10x.h"
// USART1初始化函数
void USART1_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能GPIOA和USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// 配置USART1 Tx (PA9)为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART1 Rx (PA10)为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART1配置
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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);
// 使能USART1接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 使能USART1
USART_Cmd(USART1, ENABLE);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
// 发送一个字符
void USART1_SendChar(char ch) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, (uint8_t)ch);
}
// 发送字符串
void USART1_SendString(char* str) {
while (*str) {
USART1_SendChar(*str++);
}
}
// 串口中断服务函数
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
char ch = USART_ReceiveData(USART1);
USART1_SendChar(ch); // 回显接收到的字符
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
int main(void) {
USART1_Init();
USART1_SendString("Hello, USART Communication!\r\n");
while (1) {
// 主循环可以处理其他任务
}
}
(二)I2C(Inter - Integrated Circuit)
1. 原理
- 两线式串行通信协议,使用 SDA(串行数据线)和 SCL(串行时钟线)。采用半双工通信,同一时刻数据只能单向传输。
- 总线上可挂载多个主设备和从设备,每个从设备有唯一的 7 位或 10 位地址。通信由主设备发起,主设备通过 SCL 提供时钟信号,在时钟控制下通过 SDA 发送和接收数据。通信包含起始条件、地址帧、数据帧和停止条件。
- 主机
- 功能:主机是 I2C 总线的控制者,负责发起和终止数据传输,以及决定数据传输的方向和速率。它通过向从机发送地址和控制信号来选择要通信的从机,并在总线上传输数据。
- 操作过程:主机首先会发送一个起始信号,以表明数据传输的开始。然后,主机发送一个从机地址字节,其中包含了要通信的从机的 7 位地址以及一个读写位,用于指示接下来是读操作还是写操作。在发送完从机地址后,主机可以根据需要发送或接收数据字节,并在数据传输完成后发送一个停止信号,以结束本次通信。
- 从机
- 功能:从机是 I2C 总线上接收主机控制信号和数据的设备。它会监听总线上的信号,当接收到与自己地址匹配的地址字节时,从机就会根据读写位的指示来执行相应的操作,即要么接收主机发送的数据,要么向主机发送自己的数据。
- 操作过程:从机在接收到起始信号和地址字节后,会将接收到的地址与自己的地址进行比较。如果地址匹配,从机就会发送一个应答信号(ACK)给主机,以表明它已准备好进行数据传输。然后,根据读写位的指示,从机要么接收主机发送的数据字节,并在接收完成后发送应答信号;要么将自己的数据字节发送给主机,同时等待主机的应答信号。如果从机在数据传输过程中出现错误或者无法继续传输数据,它可以发送一个非应答信号(NACK)给主机,通知主机数据传输出现问题。
2. 应用场景
适用于连接多个低速外设,如连接多个传感器(如加速度计、陀螺仪)、EEPROM 存储器等。
STM32的I2C外设
代码
#include "stm32f10x.h"
#include <stdio.h>
// 初始化I2C1
void I2C1_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
// 使能GPIOB和I2C1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// 配置I2C1的SCL(PB6)和SDA(PB7)引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 配置I2C1
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 400000; // 400kHz
I2C_Init(I2C1, &I2C_InitStructure);
// 使能I2C1
I2C_Cmd(I2C1, ENABLE);
}
// I2C主发送函数
void I2C_MasterSendData(I2C_TypeDef* I2Cx, uint8_t slaveAddr, uint8_t* pBuffer, uint8_t NumByteToWrite)
{
// 产生起始信号
I2C_GenerateSTART(I2Cx, ENABLE);
// 等待起始信号发送完成
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
// 发送从设备地址(写模式)
I2C_Send7bitAddress(I2Cx, slaveAddr, I2C_Direction_Transmitter);
// 等待地址发送完成
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送数据
for (uint8_t i = 0; i < NumByteToWrite; i++)
{
I2C_SendData(I2Cx, pBuffer[i]);
// 等待数据发送完成
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}
// 产生停止信号
I2C_GenerateSTOP(I2Cx, ENABLE);
}
// I2C主接收函数
void I2C_MasterReceiveData(I2C_TypeDef* I2Cx, uint8_t slaveAddr, uint8_t* pBuffer, uint8_t NumByteToRead)
{
// 产生起始信号
I2C_GenerateSTART(I2Cx, ENABLE);
// 等待起始信号发送完成
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
// 发送从设备地址(读模式)
I2C_Send7bitAddress(I2Cx, slaveAddr, I2C_Direction_Receiver);
if (NumByteToRead == 1)
{
// 关闭应答
I2C_AcknowledgeConfig(I2Cx, DISABLE);
// 等待地址发送完成
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
// 产生停止信号
I2C_GenerateSTOP(I2Cx, ENABLE);
// 等待接收数据
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));
// 读取数据
*pBuffer = I2C_ReceiveData(I2Cx);
}
else
{
// 等待地址发送完成
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
for (uint8_t i = 0; i < NumByteToRead - 1; i++)
{
// 等待接收数据
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));
// 读取数据
pBuffer[i] = I2C_ReceiveData(I2Cx);
}
// 关闭应答
I2C_AcknowledgeConfig(I2Cx, DISABLE);
// 产生停止信号
I2C_GenerateSTOP(I2Cx, ENABLE);
// 等待接收最后一个数据
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));
pBuffer[NumByteToRead - 1] = I2C_ReceiveData(I2Cx);
}
// 重新使能应答
I2C_AcknowledgeConfig(I2Cx, ENABLE);
}
int main(void)
{
uint8_t sendBuffer[3] = {0x01, 0x02, 0x03};
uint8_t receiveBuffer[3];
uint8_t slaveAddress = 0xA0; // 从设备地址
// 初始化I2C1
I2C1_Configuration();
// 发送数据
I2C_MasterSendData(I2C1, slaveAddress, sendBuffer, 3);
// 接收数据
I2C_MasterReceiveData(I2C1, slaveAddress, receiveBuffer, 3);
while (1)
{
// 主循环
}
}
(三)SPI(Serial Peripheral Interface)
1. 原理
- 高速、全双工、同步的串行通信协议。需要四根线:SCK(串行时钟线)、MOSI(主输出从输入线)、MISO(主输入从输出线)和 SS(从设备选择线)。
- 主设备通过 SCK 提供时钟信号,在时钟控制下,主设备通过 MOSI 向从设备发送数据,从设备通过 MISO 向主设备返回数据。SS 线用于选择要通信的从设备,低电平有效。
SPI(Serial Peripheral Interface)是一种高速、全双工、同步的通信总线,有四种工作模式,这些模式由时钟极性(CPOL)和时钟相位(CPHA)两个参数来决定。以下为你详细介绍这四种模式:
1. 时钟极性(CPOL)
- CPOL = 0:时钟空闲状态为低电平。
- CPOL = 1:时钟空闲状态为高电平。
2. 时钟相位(CPHA)
- CPHA = 0:数据在时钟的第一个边沿被采样。
- CPHA = 1:数据在时钟的第二个边沿被采样。
3. 四种模式详解
模式 0(CPOL = 0,CPHA = 0)
- 时钟空闲状态为低电平。
- 数据在时钟的第一个边沿被采样(移入),在第二个边沿改变(移除)
指定地址写
指定地址读
模式 1(CPOL = 0,CPHA = 1)
- 时钟空闲状态为低电平。
- 数据在时钟的在第一个边沿改变(移除),第二个边沿被采样(移入)。
模式 2(CPOL = 1,CPHA = 0)
- 时钟空闲状态为高电平。
- 数据在时钟的第一个边沿被采样,在第二个边沿改变。
模式 3(CPOL = 1,CPHA = 1)
- 时钟空闲状态为高电平。
- 数据在时钟的第一个边沿被采样,在第二个边沿改变。
2. 应用场景
常用于高速数据传输场景,如与 SD 卡、FLASH 存储器进行数据交互,以及显示驱动(如 OLED 显示屏)。
代码
#include "stm32f10x.h"
// 初始化SPI引脚
void SPI_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 使能SPI1和GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置SPI1 SCK (PA5), MOSI (PA7)为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置SPI1 MISO (PA6)为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
// 初始化SPI,模式0(CPOL = 0,CPHA = 0)
void SPI_Mode0_Init(void) {
SPI_InitTypeDef SPI_InitStructure;
// 使能SPI1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
// 初始化SPI,模式1(CPOL = 0,CPHA = 1)
void SPI_Mode1_Init(void) {
SPI_InitTypeDef SPI_InitStructure;
// 使能SPI1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
// 初始化SPI,模式2(CPOL = 1,CPHA = 0)
void SPI_Mode2_Init(void) {
SPI_InitTypeDef SPI_InitStructure;
// 使能SPI1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
// 初始化SPI,模式3(CPOL = 1,CPHA = 1)
void SPI_Mode3_Init(void) {
SPI_InitTypeDef SPI_InitStructure;
// 使能SPI1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
// SPI发送并接收一个字节数据
uint8_t SPI_SendReceiveByte(uint8_t data) {
// 等待发送缓冲区为空
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
// 发送数据
SPI_I2S_SendData(SPI1, data);
// 等待接收缓冲区非空
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
// 返回接收到的数据
return SPI_I2S_ReceiveData(SPI1);
}
int main(void) {
uint8_t sendData = 0xAA;
uint8_t receivedData;
// 初始化SPI引脚
SPI_GPIO_Init();
// 选择要使用的SPI模式,这里以模式0为例
SPI_Mode0_Init();
while (1) {
// 发送并接收数据
receivedData = SPI_SendReceiveByte(sendData);
// 可以在这里处理接收到的数据
// 例如通过串口打印等操作
}
}