从STM32最小系统入门嵌入式的学习记录(USART,I2C,SPI通信)

常见通信协议

单端通信的双方需要共地,将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);

        // 可以在这里处理接收到的数据
        // 例如通过串口打印等操作
    }
}    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值