首页 > 其他分享 >异步通信点灯

异步通信点灯

时间:2023-07-30 17:45:56浏览次数:35  
标签:异步 点灯 USART 串口 通信 InitStruct GPIO USART1

目录

前言

前面我们学习了PWM定时器脉冲来进行点灯,这些只是通过设置好的代码来让LED进行亮灭,有没有什么办法可以通过上位机来对LED的亮灭进行控制呢?

一、串口通信

在单片机中如果要让两个机器进行通信的话可以使用到通信的串口进行通信的,比如说单片机出现了个问题,那可以通过串口通信向上位机发送一个信息说明出的问题。

当然我们也可以通过上位机向单片机发送一些数据让它执行一些功能,所以我们可以使用串口通信对单片机发送指令让单片机点亮LED。

二、异步通信和同步通信

在stm32单片机中有两种通信方法,一种是异步通信(我们要学的)和同步通信。

异步通信是发送方发送的时间间隔可以不均,接收方是在数据的起始位和停止位的帮助下实现信息同步的。

同步通信是发送端在发送串行数据的同时,提供一个时钟信号,并按照一定的约定来发送数据。

而我们这使用的是异步通信,同步和异步的大概了解一下就可以了,后面还会提到的。

三、异步通信的端口

在stm32中有几个通信口,这些通信口都是规定好的,可以通过去查看手册了解哪些端口是用来通信的。

这里我列出我们需要使用的端口:

端口 作用
GPIOA9 USART1-TX
GPIOA10 USART1-RX

使用到的就两个端口,第一个就是PA9,这个端口是USART异步通信1通道的输出口,第二个是PA10,这个是输入口,知道使用的端口后就可以写一下通讯的实现了。

四、实现异步通信

实现的方法很简单,可以分为下面几步:

  • 设置引脚
  • 设置通信参数并使能

看起来非常的简单,我们就按照这个步骤一点一点的来实现。

1.设置引脚

之前说过,通信是有指定的引脚的,所以我们首先将对应的引脚按照之前设置LED的时候来进行设置,先使能时钟:

RCC_APB2PeriphClockCmd(GPIOA, ENABLE);

这里需要使能一下GPIOA的时钟,因为在上面也说过,是按照GPIOA上的PA9和PA10来进行操作的。

然后就是配置引脚,在配置引脚之前需要打开一下USART1的时钟,要不然配置半天结果开关没打开基本上是没用的:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART1, ENABLE);

这里我们要使用的是USART1通道的串口,而这个串口是在APB1上的,配置完后就可以配置引脚了:

GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;  // 这里设置复用推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_Pin = GPIO_Pin_10;
GPIO_Mode = GPIO_Mode_IN_FLOATING;  // GPIOA10设置为模拟输入
GPIO_Init(GPIOA, &GPIO_InitStruct);

需要注意一下,这里的发送和接收口的模式,一个是复用推挽输出另一个是模拟输入,这里一定不要配置错了。

2.设置异步通信

设置完端口后就可以对通信进行配置了,首先先得创建一个结构体变量来装配置的信息:

USART_InitTypeDef USART_InitStruct = {0};

然后配置这个结构体中的内容:

USART_InitStruct.USART_BaudRate = 115200;  // 设置通信的波特率
USART_InitStruct.USART_WordLength = USART_WordLength_8b;   // 设置数据长度
USART_InitStruct.USART_StopBits = USART_StopBits_1;     // 设置停止位1位
USART_InitStruct.USART_Parity = USART_Parity_No;     // 无校验位
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;   // 失能硬件流
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;   //设置异步通信的模式为接收和发送

配置完成后就可以将设置好的配置去配置USART的通道了,这里因为使用的是USART的通道1,所以在配置的时候需要填写USART1

USART_Init(USART1, &USART_InitStruct);

配置好之后需要使能一下这个通道1,要不然是使用不了的:

USART_Cmd(USART1, ENABLE);

这样就配置完成USART了,是不是非常的简单。

3.完整代码

这里是完整代码,不想写的可以直接使用:

void MX_Usart_Init(void){
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	USART_InitTypeDef USART_InitStruct = {0};
	
	// 打开端口的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	// 打开USART时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	// 设置GPIOA的端口
	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_IN_FLOATING;    // 设置为浮空输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 设置串口通讯
	USART_InitStruct.USART_BaudRate = 115200;     // 设置波特率
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;    // 设置数据位为8位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;         // 设置停止位1位
	USART_InitStruct.USART_Parity = USART_Parity_No;            // 无校验
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;   // 失能硬件流
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;    // 开启发送和接收模式
	USART_Init(USART1, &USART_InitStruct);    // 初始化USART
	USART_Cmd(USART1, ENABLE);
}

4.编写测试代码

写完之后我们可以在main.c中的main函数中来进行测试了,首先先的调用一下刚才写的初始化函数:

MX_USART_Init();

然后在写一个发送数据的函数:

USART_SendData(USART1, 0x30);

这里的USART_SendData是发送数据的函数,函数中的第一个参数是你要使用USART的哪一个通道进行发送,第二个参数是你要发送的数据,这里要注意,发送的数据是ASCII,0x30是ASCII值,对应的是字符0。

写完后运行一下烧录进系统测试一下:

img

可以看到数据是已经发送到上位机中了。

但是又会出现一个问题,如果我要发送一个字符串怎么办呢?

5.串口发送字符串

这个办法有点复杂,需要我们重写一个函数才可以实现这个功能。

在初始化USART后的下面,我们重写一下fputs函数,将这个函数重写后就可以使用printf函数输出字符串了,重写的函数如下:

int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;
}

这个函数直接使用就可以,拿来用。

重写完这个函数后可以将USART_SendData给更改为printf了,代码如下:

int main(void){
	MX_Usart_Init();
	printf("hello");
	while(1){
	}
}

但是当你烧录后发现没办法输出,这个是为什么呢?

原因是没有勾选最小库的选项,如果不勾选这个最小库,编译的时候就不会补全一些没导入的库和函数了,打开的方法很简单,实现点击"魔术棒",在里面把Use MicroLIB打上勾

img

然后再编译并烧录,输出的结果就是一个字符串了:

img

6.通过串口控制LED亮灭

现在我们已经做好输出了,然后就是输入,输入使用的函数也是很简单:

USART_ReceiveData(USART1);

使用上面的函数就可以获取输入了,输入的结果是以返回值来进行接收的,所以我们需要创建一个变量进行接收这个输入内容,这里得到的内容也是ASCII,所以使用的是整形来进行接收:

unsigned short value = USART_ReceiveData(USART1);

现在知道了输入我们就可以来实现通过串口通讯来控制LED的亮灭了,实现还是和上面一样先配置串口通讯,然后再配置LED,之后在主函数中编写代码。

代码的思路就是获取串口的值,然后判断一下是不是0,如果是0就亮,如果说1就灭,代码如下:

int main(void){
	unsigned char flag = 0;
	MX_Usart_Init();
	MX_Led_Init();
	
	while(1){
		if (USART_ReceiveData(USART1) == '0' && flag == 1){
			// LED亮
			GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_RESET);
			printf("LED is open\r\n");   // 提示信息
			flag = 0;
		}
		else if (USART_ReceiveData(USART1) == '1' && flag == 0){
			GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
			printf("LED is close\r\n");
			flag = 1;
		}
		
		delay_ms(100);
	}
}

这里我添加了一个标记,如果没有这个标记,那它会一直接收一直反复的使用那一个值,为了解决这个问题,我设置了一个标志flag,如果LED亮了这个标志就为0,没亮就为1。

五、使用中断串口通信来控制LED

在上面我们发现了一个问题,就是如果不添加标志,那么它会一直输出和一直执行,那我们该如何解决这个问题呢?

这里我们可以使用中断来进行解决,当有信息发送过来后就触发一下中断,这样它只有当有信息传递过来的时候才会对LED进行操作,就不用我们制作标志了。

1.打开串口的中断

在上面初始化串口的后面继续写一下语句,在之前我们学习了中断的配置,知道中断是需要配置中断的,配置中断的方法是:

  • 打开中断时钟
  • 配置中断处理器NVIC
  • 初始化

那么我们就依次来进行配置。

实现打开中断的时钟,在打开时钟之前我们要先明确这个时钟的打开位置是APB1还是APB2,要知道是哪一个还得去查看手册来知道,通过手册我们知道,对于串口的中断,直接就打开APB1中的RCC_APB1Periph_USART1即可,这个时钟我们前面配置窗口的时候就已经配置了,所以可以省略。

下一步就是配置中断NVIC的内容,这个和之前配置中断的方法一模一样:

NVIC_InitTypeDef NVIC_InitStruct = {0};
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;   // 配置串口通道1的中断
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;   // 配置抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;   // 配置响应优先级
NVIC_Init(USART1, &NVIC_InitStruct);   // 初始化中断

配置完成后就可以使能串口接收中断了:

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

配置好后我们就可以书写中断处理函数了,那么这个中断函数使用的是哪一个呢?

这里使用的是:USART1_IRQHandler函数来书写中断处理函数。

然后中断处理函数中书写的内容和上面写的内容一样:

// 接收中断处理函数
void USART1_IRQHandler(void){
	if (USART_ReceiveData(USART1) == 0x30){
		// 开灯
		GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_RESET);
		// 打开灯提示一下用户
		printf("LED is open\r\n");
	}
	else if (USART_ReceiveData(USART1) == 0x31){
		// 关灯
		GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
		//  关灯提醒一下用户
		printf("LED is close\r\n");
	}
}

在主函数中只需要做初始化即可。

六、上位机

写完后还是觉得不高级,所以我决定用Qt做一个上位机控制程序:

img

在单片机上就很轻松的控制了。

总结

对于串口通讯的功能还有很多,大家可以再多多学习一下,后面也会经常使用的。

标签:异步,点灯,USART,串口,通信,InitStruct,GPIO,USART1
From: https://www.cnblogs.com/Lavender-edgar/p/17591746.html

相关文章

  • Linux TCP Socket实现进程间通信
    LinuxSocket由内核实现。服务端初始化Socket->绑定地址和端口->监听端口->accept阻塞等待客户端连接->处理请求并响应客户端初始化Socket->连接服务端->发送请求->读取数据->关闭连接common.h#include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>......
  • 获评最高级别权威认证!融云通过中国信通院「办公即时通信软件安全能力」评测
    近期,融云再获权威认可,旗下百幄智能在线办公套件平台正式通过中国信通院“办公即时通信软件安全能力”测评,并获得最高级别“卓越级”证书。关注【融云RongCloud】,了解协同办公平台更多干货。7月27日,“2023数字生态发展大会暨中国信通院‘铸基计划’年中会议”在京举办,融云受邀......
  • Android多线程及异步处理问题
    1、问题提出1)为何需要多线程?2)多线程如何实现?3)多线程机制的核心是啥?4)到底有多少种实现方式?2、问题分析1)究其为啥需要多线程的本质就是异步处理,直观一点说就是不要让用户感觉到“很卡”。eg:你点击按钮下载一首歌,......
  • android网络通信之HTTP协议教程实…
    在现在的开发和应用中,网络通讯是必不可少的。虽然还是比较怀念小时候,抱着一台95在那里玩单机游戏玩的天昏地暗的时光,但是,现在,就算一个幼儿园的小盆友如果问你要手机玩游戏,突然发现居然买不了冰激凌草莓果汁什么的去喂talkinggina,或者切出一个超爆的水果分数却传不到网上去炫......
  • WIFI&蓝牙(ESP32)转CAN总线&串口TTL模块-A2-蓝牙和CAN总线透传通信(经典蓝牙主机)
    <p><iframename="ifd"src="https://mnifdv.cn/resource/cnblogs/ESP32_CAN"frameborder="0"scrolling="auto"width="100%"height="1500"></iframe></p>          实现的......
  • 异步http响应与异步任务处理
    Golang线程池实现http异步响应参考链接https://www.cnblogs.com/aaronhoo/p/16364492.html使用Gin框架实现异步任务处理功能参考链接https://www.php.cn/faq/564681.html......
  • MIL-STD-1553B总线通信模块(1553B板卡)
    MIL-STD-1553B总线通信模块(1553B板卡)产品具有以下特点:1.产品覆盖多种接口CPCI/PXI/PCI/PC104/PC104+/USB等,满足用户不同平台的使用要求;2.自主知识产权IP核,通信速率支持1M/4M ......
  • 关于异步多线程
    方法一:利用线程池或@Async注解使用@Async注解,可以实现多个方法并行执行,然后将它们的返回结果进行处理。@Async注解会使被标注的方法在调用时,将任务提交给一个线程池中的线程去执行,不会阻塞主线程。下面是一个简单的示例,演示如何使用@Async注解来处理多个方法的返回结果:......
  • Java中常见的网络通信模型
    目前最近仔学习RocketMQ以及Dubbo还有Spring5框架的底层部分,了解到这些技术的底层都是采用的Netty作为底层的通信的软件,于是便需要详细了解以下网络中的通信的模型以及Netty的通信模型原理。本篇是通过Redis以及Netty进行网络通信模型的逐渐演化来进行介绍,其中还会夹杂着一些比......
  • 高效文件传输:小文件采用零拷贝、大文件采用异步io+直接io
    一般会如何实现文件传输?服务器提供文件传输功能,需要将磁盘上的文件读取出来,通过网络协议发送到客户端。如果需要你自己编码实现这个文件传输功能,你会怎么实现呢?通常,你会选择最直接的方法:从网络请求中找出文件在磁盘中的路径后,如果这个文件比较大,假设有320MB,可以在内存中分配32KB......