打印
[应用相关]

STM32串口收发单字节数据原理及程序实现

[复制链接]
2027|29
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
610u|  楼主 | 2024-3-31 13:26 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
线路连接:
        显示屏的SCA接在B11,SCL接在B10,串口的RX连接A9,TX连接A10。

程序编写:
        在上一个博客中实现了串口的发送代码,这里实现串口的接收代码,在上一个代码的基础上增加程序功能。

Seiral.c初始化函数:
初始化A9引脚,设置为复用推挽输出,也就是让内部硬件控制引脚
初始化A10引脚,设置为浮空输入或上拉输入,这里使用上拉输入,具有较好的抗干扰能力
不使用硬件流控制,也就是不使用RTS,CTS等
串口模式为TX|RX(Transform)|(Receive)表示发送和接收
无校验位,可选择奇校验,偶校验等
1位停止位,可选择0.5 1 1.5 2这几个
8字长,不需要校验选8位,需要选9位
开启RXNE(RX No Empty)到NVIC的输出,也就是开启中断
配置中断
初始化程序:

void Serial_Init() {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
        GPIO_InitTypeDef GPIO_InitStructure;
        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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);

       
        USART_InitTypeDef USART_InitStructure;
        USART_InitStructure.USART_BaudRate = 9600;//波特率
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制(不使用,CTS,CTS&RTS)
        USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式 可以使用(或)|符号实现Tx和Rx同时设置
        USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,无需校验
        USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长
        USART_Init(USART1, &USART_InitStructure);
        //串口接收部分可以采用查询或者中断的方式,如果采用中断就需要在这里配置NVIC
       
        //开启中断
       
        USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE到NVIC的输出
       
        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);//开启USART
}

使用特权

评论回复
沙发
610u|  楼主 | 2024-3-31 13:27 | 只看该作者
两种实现方式:
不使用中断,直接在主函数实现:
        while(1){
                if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
                        RxData = USART_ReceiveData(USART1);//根据手册这里读DR可以自动清除标志位
                        OLED_ShowHexNum(1,1,RxData,2);//后面不需要清除标志位
                }
        }
}

使用特权

评论回复
板凳
610u|  楼主 | 2024-3-31 13:27 | 只看该作者
这个代码就是不使用中断直接进行数据接收操作,如程序所示,在主函数while循环中,不断地查询RXNE标志位是否置1,如果置1,则说明数据从读数据移位寄存器(RDR)中被转移到了DR寄存器中,表示收到数据,这时候,读取DR寄存器,也就是if成立后下面的代码,当读取DR寄存器时,RXNE会自动置0,也不需要手动清除标志位。这样就实现了读取一个字节的数据。

使用特权

评论回复
地板
610u|  楼主 | 2024-3-31 13:27 | 只看该作者
使用中断:
        在初始化中,已经将NVIC初始化,这里编写中断函数
void USART1_IRQHandler() {
        if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
                //如果读取DR就自动清除标志位,如果没有就需要手动清除
                Serial_RxData = USART_ReceiveData(USART1);
                Serial_RxFlag = 1;
                USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        }
}

使用特权

评论回复
5
610u|  楼主 | 2024-3-31 13:27 | 只看该作者
这里两个变量Serial_RxData; Serial_RxFlag为事先定义好的全局变量,表示收到的数据和标志位。

        这里如果没有读取RxData数据就需要手动清除标志位,也就是USART_ClearITPendingBit();这行代码,为了保险起见,建议加上这行代码

        这里再对这两个变量进行封装,也可以使用extern声明出去,让别的文件也可以操作这两个变量,这里使用函数封装,如下面代码所示,这里RxFlag也实现了自动清除功能。
uint8_t Serial_GetRxFlag() {
        if(Serial_RxFlag == 1){
                Serial_RxFlag = 0;
                return 1;
        }
        return 0;
}
uint8_t SerialGetRxData() {
        return Serial_RxData;
}

使用特权

评论回复
6
610u|  楼主 | 2024-3-31 13:27 | 只看该作者
主函数实现:

int main() {
        OLED_Init();
        Serial_Init();
        OLED_ShowString(1, 1, "RxData:");

        while(1){
                if(Serial_GetRxFlag() == 1) {
                        RxData = SerialGetRxData();//根据手册这里读DR可以自动清除标志位
                        Serial_SendByte(RxData);
                        OLED_ShowHexNum(1,8,RxData,2);//后面不需要清除标志位
                }
        }
}

使用特权

评论回复
7
610u|  楼主 | 2024-3-31 13:28 | 只看该作者
函数代码:
Serial.c
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

void Serial_Init() {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
        GPIO_InitTypeDef GPIO_InitStructure;
        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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);

       
        USART_InitTypeDef USART_InitStructure;
        USART_InitStructure.USART_BaudRate = 9600;//波特率
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制(不使用,CTS,CTS&RTS)
        USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式 可以使用(或)|符号实现Tx和Rx同时设置
        USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,无需校验
        USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长
        USART_Init(USART1, &USART_InitStructure);
        //串口接收部分可以采用查询或者中断的方式,如果采用中断就需要在这里配置NVIC
       
        //开启中断
       
        USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE到NVIC的输出
       
        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);//开启USART
}
void Serial_SendByte(uint8_t Byte) {
        USART_SendData(USART1, Byte);//发送数据
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) {//等待发送寄存器空,
                //TXE就是发送寄存器空的标志位,不需要手动清零,下一次发送数据时候会自动清零
        }
}
void Serial_SendArray(uint8_t *Array, uint16_t Length){
        uint16_t i;
        for(int i = 0; i < Length; i++) {
                Serial_SendByte(Array[i]);
        }

}
void Serial_SendString(char *Str) {//字符串自带结束标志位
        uint8_t i;
        for(int i = 0; Str[i] != '\0'; i++) {
                Serial_SendByte(Str[i]);
        }

}
uint32_t Serial_Pow(uint32_t X, uint32_t y) {
        uint32_t Result = 1;
        while(y--) {
                Result *= X;
        }
        return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length) {
        uint8_t i;
        for(int i = 0; i < Length; i++){
                Serial_SendByte((Number / Serial_Pow(10, Length - i - 1)) % 10 + '0');
        }

}
int fputc(int ch, FILE* f){
        Serial_SendByte(ch);//重定向到串口,使得Printf打印到串口
        return ch;

}
//使用sprintf让其他的串口也能使用,sprintf可以把格式化字符输出到一个字符串里
void Serial_Printf(char* format,...){//三个点用来接收后面可变参数列表
        char String[100];
        va_list arg;
        va_start(arg, format);//从format位置开始接收参数表,放在arg里面
        vsprintf(String, format, arg);
        va_end(arg);
        Serial_SendString(String);
}
uint8_t Serial_GetRxFlag() {
        if(Serial_RxFlag == 1){
                Serial_RxFlag = 0;
                return 1;
        }
        return 0;
}
uint8_t SerialGetRxData() {
        return Serial_RxData;
}
void USART1_IRQHandler() {
        if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
                //如果读取DR就自动清除标志位,如果没有就需要手动清除
                Serial_RxData = USART_ReceiveData(USART1);
                Serial_RxFlag = 1;
                USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        }
}

使用特权

评论回复
8
610u|  楼主 | 2024-3-31 13:28 | 只看该作者
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>

void Serial_Init();
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char* format,...);
uint8_t Serial_GetRxFlag();
uint8_t SerialGetRxData();


#endif

使用特权

评论回复
9
610u|  楼主 | 2024-3-31 13:28 | 只看该作者
main.c
#include "stm32f10x.h"                  // Device header
#include "DELAY.h"
#include "OLED.h"
#include "Serial.h"
uint8_t RxData;
int main() {
        OLED_Init();
        Serial_Init();
        OLED_ShowString(1, 1, "RxData:");

        while(1){
                //查询:在主函数里一直查看RXNE标志位,如果置1则说明收到数据,再调用读取DR寄存器代码就获得了数据
                /*
                if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
                        RxData = USART_ReceiveData(USART1);//根据手册这里读DR可以自动清除标志位
                        OLED_ShowHexNum(1,1,RxData,2);//后面不需要清除标志位
                }
                */
                if(Serial_GetRxFlag() == 1) {
                        RxData = SerialGetRxData();//根据手册这里读DR可以自动清除标志位
                        Serial_SendByte(RxData);
                        OLED_ShowHexNum(1,8,RxData,2);//后面不需要清除标志位
                }
        }
}

使用特权

评论回复
10
610u|  楼主 | 2024-3-31 13:28 | 只看该作者
程序现象:

使用特权

评论回复
11
中国龙芯CDX| | 2024-3-31 22:58 | 只看该作者
其实串口直接HAL库就能快速搞定,简单实用

使用特权

评论回复
12
ccook11| | 2024-4-4 11:50 | 只看该作者
STM32的串口(UART)是一种常用的异步串行通信接口,可以用于单字节或多字节数据的收发。下面将详细介绍STM32串口收发单字节数据的原理及程序实现。

使用特权

评论回复
13
ccook11| | 2024-4-4 14:38 | 只看该作者
初始化了一个UART_HandleTypeDef结构体,用于存储串口的配置信息。然后,我们使用USART2_Init函数配置了USART2模块的波特率、数据位、停止位、奇偶校验等参数。

使用特权

评论回复
14
eefas| | 2024-4-4 16:50 | 只看该作者
当一个起始位被检测到时,USART开始同步接收数据。
数据位按配置的位长(通常是8位)被接收。
可能的奇偶校验位也被接收。
最后是停止位。
接收到的数据被存储在接收缓冲器中,等待CPU读取。

使用特权

评论回复
15
houjiakai| | 2024-4-4 20:41 | 只看该作者
时钟配置:通过RCC(Reset and Clock Control)使能UART模块和相应GPIO端口的时钟。
GPIO配置:配置串口的TX(发送)和RX(接收)引脚为复用功能模式。
UART配置:设置波特率、数据位、停止位、校验位等参数。

使用特权

评论回复
16
zerorobert| | 2024-4-6 11:28 | 只看该作者
当USART接收到一帧数据时,它会将其存储在接收缓冲器中,而当发送数据时,数据从发送缓冲器传输到USART的发送线路上。

使用特权

评论回复
17
jonas222| | 2024-4-6 14:58 | 只看该作者
要发送单字节数据,可以使用UART的发送数据寄存器(通常称为USART_DR)。将数据直接写入此寄存器即可。当数据被写入后,UART会自动将其转换为串行数据并通过TX引脚发送出去。发送过程完成后,UART会自动清空发送寄存器。

使用特权

评论回复
18
dspmana| | 2024-4-7 20:03 | 只看该作者
#include "stm32f10x.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

void USART_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 开启USART1和GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    // 配置USART1的TX引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置USART1的RX引脚
    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_Cmd(USART1, ENABLE);
}

void USART_SendByte(uint8_t data)
{
    // 等待发送数据寄存器为空
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

    // 将数据写入发送数据寄存器
    USART_SendData(USART1, data);
}

uint8_t USART_ReceiveByte(void)
{
    // 等待接收数据寄存器非空
    while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);

    // 从接收数据寄存器读取数据
    return USART_ReceiveData(USART1);
}

int main(void)
{
    uint8_t received_data;

    USART_Config();

    while (1)
    {
        // 发送一个字节的数据
        USART_SendByte('A');

        // 接收一个字节的数据
        received_data = USART_ReceiveByte();
    }
}

使用特权

评论回复
19
albertaabbot| | 2024-4-10 11:09 | 只看该作者
STM32串口收发单字节数据的原理是通过串行通信协议,将数据逐位(bit)进行传输。在STM32中,串口通常使用USART(Universal Synchronous Asynchronous Receiver Transmitter,通用同步异步接收器发射器)模块实现。

使用特权

评论回复
20
sdlls| | 2024-4-11 23:49 | 只看该作者
通过DMA(Direct Memory Access)可以无需CPU介入直接进行数据传输。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

49

主题

517

帖子

0

粉丝