[TOC]
前言
前面我们学习了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。
写完后运行一下烧录进系统测试一下:
可以看到数据是已经发送到上位机中了。
但是又会出现一个问题,如果我要发送一个字符串怎么办呢?
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
打上勾
然后再编译并烧录,输出的结果就是一个字符串了:
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做一个上位机控制程序:
在单片机上就很轻松的控制了。
总结
对于串口通讯的功能还有很多,大家可以再多多学习一下,后面也会经常使用的。