stm32入门-----USART串口通讯(上——实践篇)

 目录

前言

一、数据编码 

二、发送——C语言编程步骤

1.初始化步骤

1.1开启时钟

1.2配置GPIO口引脚 

1.3初始化USART

 1.4USART使能

2.发送数据步骤

2.1发送单个无符号类型数据

2.1发送数组

2.3发生字符串 

2.4发送有符号整形数字

2.5重定向printf和sprintf函数

3.项目示例

三、接收——C语言编程步骤

1.初始化

2.项目示例

2.1查询法

2.2中断法(推荐)


前言

        上一期我发布了USART串口之间通讯的理论知识(链接:stm32入门-----USART串口通讯(上——理论篇)-CSDN博客),那么本期我们主要去学习USART串口通讯的项目实践。本次项目有两个部分,第一个就是USART串口实现stm32向电脑发生数据,第二个就是USART串口实现stm32从电脑接收数据,总体来说就是stm32与电脑互相接收和发送数据。(视频:[9-3] 串口发送&串口发送+接收_哔哩哔哩_bilibili

本期项目的全部代码以及串口助手软件都在百度网盘,可自行下载。

链接:https://pan.baidu.com/s/1qvBbwlQiRIMS2CumzX8jLw?pwd=0721 
提取码:0721

一、数据编码 

在学习代码之前,我们需要了解一个很重要的东西也就是数据编码,其实这部分大家应该都不陌生的说。比如阿斯克码,每一个我们常见的字符都有对应的编码样式,所以数据的发送是需要去进行编码和解码的,而不是直接发送过去的,所以这部分我们还是需要学习一下的。

本期我们主要是用串口助手来去查看发送和接收到的数据,主要了解HEX模式的数据和文本模式的数据。

  • HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
  • 文本模式/字符模式:以原始数据编码后的形式显示

入下图所示,我们的文本数据在USART之间是以HEX编码模式来传输的,到达对方我们可以进行译码查看到文本数据。 

二、发送——C语言编程步骤

1.初始化步骤

首先我们先看到stm32的引脚图,这里我们以USART1为示例使用,如果想用USART2等其他的可以去查看引脚表。在stm32中USART1对应的发送和接收引脚是PA9和PA10。

同样的我们根据下面的结构图去把通道打通配置就行了。 

1.1开启时钟
//1.开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
1.2配置GPIO口引脚 
 //2.配置引脚,这里只需要配置输出
    GPIO_InitTypeDef GPIO_initstruct; 
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
	GPIO_initstruct.GPIO_Pin=GPIO_Pin_9;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_initstruct);
1.3初始化USART
//3.初始化USART
    USART_InitTypeDef USART_initstruct;
    USART_initstruct.USART_BaudRate=9600; //波特率,直接写就行了,初始化会自动计算和配置好的
    USART_initstruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//硬件流控,这里选择不使用
    USART_initstruct.USART_Mode=USART_Mode_Tx;//选择串口模式,这里我们只需要发送所以选择发送模式即可
    USART_initstruct.USART_Parity=USART_Parity_No;//选择校验位,这里不需要选择no
    USART_initstruct.USART_StopBits=USART_StopBits_1;//选择停止位,这里选1位
    USART_initstruct.USART_WordLength=USART_WordLength_8b; //字长,可选8位或者9位,这里没有校验所以选择8位
    USART_Init(USART1,&USART_initstruct);
 1.4USART使能
//4.开启USART接口
    USART_Cmd(USART1,ENABLE);

以上四步就搞定USART发送功能的初始化,整体代码如下:

void Serial_init(){

    //1.开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    //2.配置引脚,这里只需要配置输出
    GPIO_InitTypeDef GPIO_initstruct; 
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
	GPIO_initstruct.GPIO_Pin=GPIO_Pin_9;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_initstruct);

    //3.初始化USART
    USART_InitTypeDef USART_initstruct;
    USART_initstruct.USART_BaudRate=9600; //波特率,直接写就行了,初始化会自动计算和配置好的
    USART_initstruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//硬件流控,这里选择不使用
    USART_initstruct.USART_Mode=USART_Mode_Tx;//选择串口模式,这里我们只需要发送所以选择发送模式即可
    USART_initstruct.USART_Parity=USART_Parity_No;//选择校验位,这里不需要选择no
    USART_initstruct.USART_StopBits=USART_StopBits_1;//选择停止位,这里选1位
    USART_initstruct.USART_WordLength=USART_WordLength_8b; //字长,可选8位或者9位,这里没有校验所以选择8位
    USART_Init(USART1,&USART_initstruct);
    
    //4.开启USART接口
    USART_Cmd(USART1,ENABLE);
}

2.发送数据步骤

2.1发送单个无符号类型数据

下面是对这个函数的定义图,这里我们可以看到,最后是对DR寄存器进行写入的操作,然后把数据发送出去。

这里我们可以去把这个功能的函数封装起来,代码如下: 

//发送一个字符数据
void Serial_sendbyte(uint8_t byte){
    USART_SendData(USART1,byte);//把要写入的数据写入到DR寄存器(TDR)

    //当DR寄存器完成了移位到移位寄存器后,下面就去判断是否完成标志位1,然后再去对DR寄存器清零(清零是硬件自动处理的),重新放入数据
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
2.1发送数组

发送数组,实际上我们只需要对这个数组进行遍历,然后一位一位发送出去即可,所以实际上就是对上面发送单个无符号类型数据的功能进行封装。

// 发送一个数组
void Serial_sendarray(uint8_t * array,uint16_t length){
    uint16_t i;
    for(i=0;i<length;i++){
        Serial_sendbyte(array[i]);
    }
}
2.3发生字符串 

同样的,发送字符串也是一个一个发送出去,不同与数组的是,字符串不需要去确定长度,字符串本身是有一个终止符 '\0' 的,所以只需要识别到就结束即可。 

//发送字符串
void Serial_sendstring(char* string){
    uint8_t i;
    for(i=0;string[i]!='\0';i++){
        Serial_sendbyte(string[i]);
    }
}
2.4发送有符号整形数字

 发送数字的话,我们是需要把数字去进行类型转换为字符串然后再去发送,具体怎么转换的,方法很多,学过C语言的都应该知道怎么去转换了,下面我就给出一个样例。

//发送数字,转字符串
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 num,uint8_t length){
    uint8_t i;
    for(i=0;i<length;i++){
        Serial_sendbyte(num/Serial_pow(10,length-i-1)%10+'0');
    }
}
2.5重定向printf和sprintf函数

这两个函数我们都不陌生了吧,是标准输入输出里面的函数,对于printf函数,这个是打印函数,打印的结果只能在我们电脑显示出来,但是如果我想去把这个作为一个数据写入的函数,我们就需要去对其进行底层的重映射。

//fputc重定向
//重定向printf ,这个函数的底层实际上就是fputc,多次调用
int fputc(int ch,FILE*f){
    Serial_sendbyte(ch);
    return ch;
}

 sprintf函数是常见的格式化函数,这里我们也行把其映射为数据写入函数,同样的我们也要去进行修改,这里要用到的是#include <stdarg.h>里面的功能。

//对sprintf进行封装,可变参数格式 头文件 #include <stdarg.h>
void Serial_sprintf(char* format,...){
    char str[100];
    va_list arg;
    va_start(arg,format);
    vsprintf(str,format,arg);
    va_end(arg);
    Serial_sendstring(str);
}

3.项目示例

我们把上面提到的函数封装到Serial文件里面,工程文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main(void)
{	
	OLED_Init();
	Serial_init();

	Serial_sendbyte('A');
	uint8_t arr[] = { "1234" };
	Serial_sendarray(arr, 4);
	Serial_sendnumber(12345,5);
	Serial_sendstring("hello word\r\n");//如果想换行就要加上\r\n,表示回车换行
	printf("num=%d\r\n", 66666);
	Serial_sprintf("我爱中国\r\n");

	while(1){
		
	}
}

显示结果如下,这里我们要把波特率设置到跟我们上面配置的一样9600,接受模式选择文本模式即可,这里要注意一下文本编码是否跟代码的文本编码一致,不然中文会显示乱码,我这里是utf8编码的。 

三、接收——C语言编程步骤

1.初始化

实际上接收和发送的初始化是一样的,不过只需要进行添加和修改即可,其中要修改的有三个部分:

(1)配置接收部分的引脚

(2)初始化USART添加RX模式

(3)如果要通过中断来接收数据,那就要去配置中断的NVIC(如果用直接查询的话就不需要这一条)

初始化代码如下:

void Serial_init(){

    //1.开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    //2.配置引脚,这里只需要配置输出
    //发送部分
    GPIO_InitTypeDef GPIO_initstruct; 
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
	GPIO_initstruct.GPIO_Pin=GPIO_Pin_9;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_initstruct);

    //接收部分
    GPIO_initstruct.GPIO_Mode=GPIO_Mode_IPU; //上拉输入浮空输入下拉输入都行
	GPIO_initstruct.GPIO_Pin=GPIO_Pin_10;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_initstruct);

    //3.初始化USART
    USART_InitTypeDef USART_initstruct;
    USART_initstruct.USART_BaudRate=9600; //波特率,直接写就行了,初始化会自动计算和配置好的
    USART_initstruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//硬件流控,这里选择不使用
    USART_initstruct.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;//选择串口模式,选择RX或上TX,发送和接收都有
    USART_initstruct.USART_Parity=USART_Parity_No;//选择校验位,这里不需要选择no
    USART_initstruct.USART_StopBits=USART_StopBits_1;//选择停止位,这里选1位
    USART_initstruct.USART_WordLength=USART_WordLength_8b; //字长,可选8位或者9位,这里没有校验所以选择8位
    USART_Init(USART1, &USART_initstruct);

    //开启RX读取的中断 
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    //配置NVIC
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef NVIC_initstruct;
    NVIC_initstruct.NVIC_IRQChannel = USART1_IRQn; //选择中断的通道
    NVIC_initstruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_initstruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_initstruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_initstruct);

    //4.开启USART接口
    USART_Cmd(USART1, ENABLE);
}

2.项目示例

这里我会展示查询法和中断法接收数据这两种方法的示例,虽然两种方法的效果是一致的,但是中断法是更好的,可以去节省软件的资源。

先看实验现象:

注意发送区和接收区文本编码一致。

电路连线图:

工程文件:

2.1查询法
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main(void)
{	
	OLED_Init();
	Serial_init();
	OLED_ShowString(1, 1, "Rxdata:");

	while (1) {
		//下面是查询方法,也就是一直去判断DR读取标志位是否为1,如果为1那就有数据进来,获取这些数据.
		if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
			data = USART_ReceiveData(USART1); //这里DR读完后,标志位硬件会自动清除的
			OLED_ShowHexNum(1, 1, data, 2);
		}
	}
}
2.2中断法(推荐)
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t data;
uint8_t flag;

uint8_t Serial_getflag();
uint8_t Serial_getdata();

int main(void)
{	
	OLED_Init();
	Serial_init();
	OLED_ShowString(1, 1, "Rxdata:");

	while (1) {
		if (Serial_getflag()) {
			data = Serial_getdata();//获取数据
			Serial_sendbyte(data); //回传到电脑上
			OLED_Clear();
			OLED_ShowString(1, 1, "Rxdata:");
			OLED_ShowChar(1, 8, data);
		}

	}
}

//中断标志位
uint8_t Serial_getflag() {
	if (flag) {
		flag = 0;
		return 1;
	}
	return 0;
}
//获取数据
uint8_t Serial_getdata() {
	return data;
}
//中断读取
void USART1_IRQHandler() {
	//先判断标志位
	if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
		//对于标志位,如果读取了DR的值,那么硬件会自动清除,如果什么都不做,那得手动去清除
		data = USART_ReceiveData(USART1);
		flag = 1;
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除中断标志位
	}
}

对比上面两部分代码我们就可以看出,查询法是每次循环都要去判断是否有数据被读取,而中断法代码相对比较多,但是不用每次循环都去判断是否有数据要读取,当有数据的读取时候只需要去中断操作即可,极大的提高了程序的运行效率。

以上就是本期的全部内容了,我们下次见!

今日壁纸:

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fitz&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值