STM32_9(USART串口)

串口通信

  • 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
  • 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力

 

硬件电路

  • 简单双向串口通信有两根通信线(发送端TX和接收端RX)
  • TX与RX要交叉连接
  • 当只需单向的数据传输时,可以只接一根通信线
  • 当电平标准不一致时,需要加电平转换芯片

一个设备使用TX发送高低电平,另一个设备使用RX接收高低电平。

因为STM32是3.3V,所以线路对地是3.3V,就代表发送了逻辑1,线路对地为0V,就代表了发送逻辑0。

 

串口参数及时序

  • 波特率:串口通信的速率(串口一般使用异步通信,需要双方约定一个通信速率)
  • 起始位:标志一个数据帧的开始,固定为低电平
  • 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
  • 校验位:用于数据验证,根据数据位计算得来(奇校验:加入需要传输的字节为0000 1111,则在最后一位补一个1为 5个1为奇数,如果传输的字节为0000 0111,则最后一位补一个0,3个1为奇数,最后在接收放验证个数是不是奇数。偶校验同理,但奇偶校验只能保证一定程度上的数据校验)
  • 停止位:用于数据帧间隔,固定为高电平

串口中每一个字节都装载在一个数据帧里,每个数据帧都由起始位,数据位,停止位组成。左图数据位有8位,代表一个字节8位,右图数据位有9位,最后一个为奇偶校验位。

没用工作的时候都是空闲为高电位,开始工作的时候有一个起始位为低电平,产生下降沿,来告诉接收设备,我要发数据了。同理,一个字节数据发送完成后,必须要有一个停止位。

 

USART

  • USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
  • USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
  • 自带波特率发生器,最高达4.5Mbits/s
  • 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
  • 可选校验位(无校验/奇校验/偶校验)
  • 支持同步模式、硬件流控制(在硬件电路上会多出一根线,如果B没准备好接收,就置高电平,如果准备好了就置低电平,A会根据电平来发送数据)、DMA、智能卡、IrDA、LIN
  • STM32F103C8T6 USART资源: USART1、 USART2、 USART3

 

USART框图

发送数据寄存器:排队等待打饭;发送移位寄存器:正在打饭;

接收移位寄存器:拿到了饭;接收数据寄存器:开始吃饭。

 

USART基本结构

当数据由数据寄存器转到移位寄存器时,会置一个TXE的标志位,判断这个标志位就可以知道是否可以写入下一个数据;

同理,移位寄存器转到数据寄存器的时,会置一个RXNE的标志位,检查这个标志位就可以知道是否收到数据了。

 

数据帧

停止位就是停止的位数,一般常用于一位停止位

 

起始位侦测

当输入电路侦测到一个数据帧的起始位后,就会以波特率的帧率,连续采样一帧数据。同时,从起始位开始,采样位置就要对其到位的正中间,只要第一位对齐了,后面就肯定对齐。

首先输入的部分电路对采样时钟进行了细分,它会以波特率16倍频率进行采样,也就是一位的时间里,进行16次采样。

最开始,空闲状态高电平采样一直为1,在某个位置突然采样到0,那么说明在这两次采样之间出现了下降沿,如果没有任何噪声,就应该是起始位。在起始位进行连续16次采样。

 

数据采样

  从1到16是一个数据位的时间长度,在一个数据位,有16个采样时钟,由于起始位侦测到已经对齐了采样的时钟,所有就在8、9、10次开始采样,为了保证数据准确性性,是连续采样3次。由于噪声的影响,它是由2:1规则来,2次为1则为1,两次为0则为0。这种情况下,噪声标志位NE也会置1,告诉我,虽然我收到数据了,但有噪声,需要考虑使用。

 

波特率发生器(寄存器)

  • 发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
  • 计算公式:波特率 = fPCLK2/1 / (16 * DIV)    (为什么有16,因为一个数据位,有16个采样时钟)
  • 如果是使用库函数,库函数自动帮我们算好BRR

 

HEX数据包

数据包作用:把一个个单独的数据打包起来,方便进行多字节的数据通信。

比如:陀螺仪传感器需要用串口发送数据STM32

固定包长,含包头包尾

这里定义0xFF为包头,0xFE为包尾

 

可变包长,含包头包尾

如果载荷会出现包头包尾重复的情况,最好选择固定包长,避免接收错误;如果载荷不会和包头包尾重复,可以选择可变包长。

 

文本数据包

固定包长,含包头包尾

这里以@作为包头,以\r\n作为包尾

可变包长,含包头包尾

 

HEX数据包流程图

这里可以利用状态机。先定义3个状态,第一个状态是等待包头,第二个状态是接收数据,第三个状态时等待包尾。

最开始S=0,收到一个数据进入中断,根据S=0进入第一个状态的程序,判断数据是不是包头FF,如果时FF,则代表收到包头,之后置S=1,退出中断结束。这样下次进中断,就可以根据S=1进行接收数据程序。那在第一个状态,如果收到的不是FF,证明数据包没有对齐, 所以包头仍为0。下次进中断还是进入包头,直到出现FF,才进入下一状态。如果出现了FF,就可以转移到接收数据状态,这时再收到数据,就直接把它存在数组中,另外再用一个变量,记录收了多少个数据,如果没有收够4个数据,就一直是接收状态,如果收够了,就置S=2,下次中断时,就可以进去下一个状态。最后一个等待包尾,判断是不是FE,如果是则置S=0,回到最初的状态开始下一次轮回,如果不是就一直等待FE。

 

文本数据包流程图

这个与上面数据包流程图类似,但这个是可变包长,需要在S=1时接收数据并且等待包尾,需要时刻监视是不是该收包尾。

 

建议

一般情况下,HEX数据包一般多用于传输各种传感器的每个独立数据,比如陀螺仪的X,Y,Z轴数据,温湿度数据等。那文本数据包一般可以利用发送文字到串口,实现相应功能。

 

代码部分

串口发送和接收

#include "Bsp_Serial.h"

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

void Serial_Init(void)
{

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);          // 1.时钟配置
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;                            // 2.配置GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                 // 在手册中推荐使用复用推挽
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          

    USART_InitTypeDef USART_InitStructure;                          // 3.配置USART      
    USART_InitStructure.USART_BaudRate = 9600;                      // 波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;     // 硬件流控制
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 同时开启发送和接收
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 奇偶校验位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 停止位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 传输位长
    USART_Init(USART1, &USART_InitStructure);

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                  // 开启串口中断

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                 // 设置中断组
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

    USART_Cmd(USART1, ENABLE);                                      // 4.开启串口1

}

/* 发送一个字节 */
void Serial_SendByte(uint8_t Byte)                                  // 这里只传8位,用uint_8可以。要传9位的话就得是uint_16的类型了
{
    USART_SendData(USART1, Byte);
    while ((USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET));
}

/* 发送一个数组 */
void Serial_SendArray(uint8_t *Array, uint32_t Length)
{
    for (uint16_t i = 0; i < Length; i++)
    {
        Serial_SendByte(Array[i]);
    }
}

/* 发送字符串 */
void Serial_SentString(char *String)
{
    for (uint8_t i = 0; String[i] != '\0'; i++)
    {
        Serial_SendByte(String[i]);
    }
}

/* 次方 */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1;
    while (Y --)
    {
        Result *= X;
    }
    return Result;
}

/* 发送十进制数字 */
void Serial_SentNumer(uint32_t Number, uint8_t Length)
{
    for (uint8_t i = 0; i < Length; i++)
    {
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
    }
    
}

/* 移植printf到串口 fputc是printf的底层函数,利用这个函数重定位到串口 */
int fputc(int ch, FILE *f)
{
    Serial_SendByte(ch);
    return ch;
}

/* 封装sprintf */
void Serial_Printf(char *format, ...)
{
    char String[100];
    va_list arg;                    // va_list是一个类型名,arg是变量名
    va_start(arg, format);          // va_start是从format位置开始接收参数表,放在arg里
    vsprintf(String, format, arg);  // 打印位置是String,格式化字符串是format,参数表是arg
    va_end(arg);                    // 释放参数表 
    Serial_SentString(String);
}

/* 检测RXNE标志位 */
/*
void Serial_RXNE_Flag(uint8_t RxData)
{
    if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
    {
        RxData = USART_ReceiveData(USART1);
        OLED_ShowHexNum(1, 1, RxData, 2);
    }
}
*/

/* 中断接收封装 */
uint8_t Serial_GetRxFlag(void)
{
    if (Serial_RxFlag == 1)
    {
        Serial_RxFlag = 0;
        return 1;
    }
    return 0;
}

/* 中断变量封装 */
uint8_t Serial_GetRxData(void)
{
    return Serial_RxData;
}

/* USART1中断函数 */
void USART1_IRQHandler(void)
{
    if (USART_GetITStatus(USART1, USART_IT_RXNE) == 1)
    {
        Serial_RxData = USART_ReceiveData(USART1);
        Serial_RxFlag = 1;
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

 

串口发送HEX数据包

#include "Bsp_Serial.h"

// uint8_t Serial_RxData;
uint8_t Serial_RxPacket[4];
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxFlag = 0;

void Serial_Init(void)
{

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);          // 1.时钟配置
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;                            // 2.配置GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                 // 在手册中推荐使用复用推挽
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          

    USART_InitTypeDef USART_InitStructure;                          // 3.配置USART      
    USART_InitStructure.USART_BaudRate = 9600;                      // 波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;     // 硬件流控制
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 同时开启发送和接收
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 奇偶校验位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 停止位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 传输位长
    USART_Init(USART1, &USART_InitStructure);

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                  // 开启串口中断

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                 // 设置中断组
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

    USART_Cmd(USART1, ENABLE);                                      // 4.开启串口1

}


/* 中断接收封装 */
uint8_t Serial_GetRxFlag(void)
{
    if (Serial_RxFlag == 1)
    {
        Serial_RxFlag = 0;
        return 1;
    }
    return 0;
}

/* 每次发送HEX数据包加上包首和包尾 */
void Serial_SendPacket(void)
{
    Serial_SendByte(0xFF);
    Serial_SendArray(Serial_TxPacket, 4);
    Serial_SendByte(0xFE);
}

/* 中断变量封装 */
/* 
uint8_t Serial_GetRxData(void)
{
    return Serial_RxData;
}
*/

/* USART1中断函数 */
void USART1_IRQHandler(void)
{
    static uint8_t RxState = 0;                             // static类似于全局变量,只初始化一次,但与全局变量不同的是,静态变量只在本函数使用         
    static uint8_t pRxPacket = 0;                           // 指示接收到第几个了

    if (USART_GetITStatus(USART1, USART_IT_RXNE) == 1)
    {
        uint8_t RxData = USART_ReceiveData(USART1);
        switch (RxState)                                    // 这里使用了状态机
        {
            case 0:
            {
                if (RxData == 0xFF)
                {
                    RxState = 1;
                    pRxPacket = 0;
                }
            }
            break;

            case 1:
            {
                Serial_RxPacket[pRxPacket] = RxData;
                pRxPacket ++;
                
                if (pRxPacket >= 4)
                {
                    RxState = 2;
                }
            }
            case 2:
            {
                if (RxData == 0xFE)
                {
                    RxState = 0;
                    Serial_RxFlag = 1;
                }   
            }
        }
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);             
    }
}

 

串口发送文本数据包

#include "Bsp_Serial.h"

// uint8_t Serial_RxData;
char Serial_RxPacket_Char[100];                                     // 接收文本数据包变量
uint8_t Serial_RxPacket[4];                                         // 接收HEX数据包变量
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxFlag = 0;

void Serial_Init(void)
{

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);          // 1.时钟配置
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;                            // 2.配置GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                 // 在手册中推荐使用复用推挽
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          

    USART_InitTypeDef USART_InitStructure;                          // 3.配置USART      
    USART_InitStructure.USART_BaudRate = 9600;                      // 波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;     // 硬件流控制
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 同时开启发送和接收
    USART_InitStructure.USART_Parity = USART_Parity_No;             // 奇偶校验位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;          // 停止位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 传输位长
    USART_Init(USART1, &USART_InitStructure);

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                  // 开启串口中断

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                 // 设置中断组
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

    USART_Cmd(USART1, ENABLE);                                      // 4.开启串口1

}

/* 文字数据包状态机 */
void USART1_IRQHandler(void)
{
    static uint8_t RxState = 0;                             // static类似于全局变量,只初始化一次,但与全局变量不同的是,静态变量只在本函数使用         
    static uint8_t pRxPacket = 0;                           // 指示接收到第几个了

    if (USART_GetITStatus(USART1, USART_IT_RXNE) == 1)
    {
        uint8_t RxData = USART_ReceiveData(USART1);
        
        switch (RxState)
        {
            case 0:
            {
                if (RxData == '@' && Serial_RxFlag == 0)        // 如果两个条件满足才接受,如果没有Serial_RxFlag,就怕到时候传输数据太快,错位。
                {
                    RxState = 1;
                    pRxPacket = 0;
                }
            }
            break;

            case 1:
            {
                if (RxData == '\r')
                {
                    RxState = 2;
                }
                else
                {
                    Serial_RxPacket_Char[pRxPacket] = RxData;
                    pRxPacket ++;
                }
            }
            break;
            case 2:
            {
                if (RxData == '\n')
                {
                    RxState = 0;
                    Serial_RxPacket_Char[pRxPacket] = '\0';
                    Serial_RxFlag = 1;
                }   
            }
            break;
        }

        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}
posted @ 2023-05-12 18:59  烟儿公主  阅读(181)  评论(0编辑  收藏  举报