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);
    }
}

本文作者:烟儿公主

本文链接:https://www.cnblogs.com/toutiegongzhu/p/17394161.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   烟儿公主  阅读(231)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 夏日大冒险 暴躁的兔子
夏日大冒险 - 暴躁的兔子
00:00 / 00:00
An audio error has occurred.

作词 : 暴躁的兔子

作曲 : 暴躁的兔子

编曲 : IOF

混音:Gfanfan

出品:网易飓风

夏天 不要再浪费时间

实现 你承诺过的改变

别再 找一堆借口拖延

现在就和我一起飞向海边

人生苦短 你应该学会如何作乐

低着头还怎么应对挫折

人应该为自己活着

不用去迎合

要去寻欢作乐

撮合我的浪漫和悲欢

把这荒诞人生都塞满

生活难免磕磕绊绊

对抗生活的平庸就是浪漫

学会取悦自己逆风翻盘

去反抗变态的三观

把条条框框都砸烂

建立新的规则推翻谈判

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

去冒险

我真的不care你是否会喜欢我

不跟风被定义的美 全都是灾祸

我才不讨好大多数绝不与示弱

过好你的生活

你管我应该怎么快活

没有人能有资格审判

别人的生活和牵绊

快闭上你的高谈阔论

乘风破浪吧 理想的风帆

我就是肆意张扬又如何

我就是锋芒毕露又如何

我就是离经叛道又如何

我就是要出格 你管我要如何

我就是与众不同又如何

我就是特立独行又如何

我就是不知好歹又如何

你管我怎样出格 你管我如何

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

不知进退的人呐

和我一起去海边

聊聊曾经的理想 一起想当年

那曾想改变世界的人

是否还满腔热忱

不羁的我们放肆着

反抗那命运的指针

解放灵魂

推广:网易飓风

企划:贾焱祺

监制:徐思灵

出品人:谢奇笛