串口调试
- 前言
- 一、什么是串口?
- 二、使用串口给上位机发送“hello windows!”
- (一)串口初始化
- (二)代码如下
- (三)串口调试
- 三、使用串口控制LED灯
- 总结
前言
很多时候,我们在编写程序时不可避免的要调试代码,或者输出一些调试信息,但设计硬件的调试不像纯软件一样,直接在黑窗口上就可以看到想要输出的调试信息。我们通过串口将硬件与上位机连接起来,就可输出一些调试信息,用电脑USB口接收十分方便,串口调试软件很多,接下来我们一起来体会一下串口通讯的特点。
一、什么是串口?
串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。
串口通讯的数据由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。
- 串口通信协议
在串口通讯的协议中,规定了数据包的内容,它由启始位、数据位、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。
起始位:
在通信线上没有数据传送时处于逻辑“1”状态。当发送设备发送一个字符数据时,首先发出一个逻辑“0”信号,这个逻辑低电平就是起始位(下降沿)。起始位通过通信线传向接收设备,当接收设备检测到这个逻辑低电平后,就开始准备接收数据信号。因此,起始位所起的作用就是表示字符传送开始。
数据位:
数据位紧跟在起始位之后,是通信中的真正有效信息,即要传输的主体数据内容。数据位的位数可以由通信双方共同约定,一般可以是5位、7位或8位。
数据校验位:
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输 数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验 (odd)、偶校验 (even)、 0 校验 (space)、 1校验 (mark) 以及无校验 (noparity)。
停止位:
停止位可以是是1位、1.5位或2位,可以由软件设定。它一定是“1”,标志着传输一个字符的结束。
二、使用串口给上位机发送“hello windows!”
- 新建工程
这部分在之前的实验中以及涉及到,就不再赘述了。
详情可参考下面博客,新建一个工程
keil下C与汇编语言混合编程
(一)串口初始化
配置步骤
- 初始化串口所用到的GPIO引脚
- 配置串口的工作模式
- 重定义Printf函数
- 向上位机(电脑)发送信息
- GPIO初始化结构体介绍
typedef struct
{
uint16_t GPIO_Pin; /*!选择需要配置的引脚*/
GPIOSpeed_TypeDef GPIO_Speed; /*配置所选引脚的输出速度 */
GPIOMode_TypeDef GPIO_Mode; /*!配置所选引脚的工作模式*/
}GPIO_InitTypeDef;
查看数据手册后,我们发现对应需要配置的GPIO引脚分别位PA9、PA10,我们等会儿需要配置的就是这两个引脚
- USART初始化结构体介绍
typedef struct
{
uint32_t USART_BaudRate; /*!选择串口通信的速度 波特率*/
uint16_t USART_WordLength; /*!选择数据位的长度 */
uint16_t USART_StopBits; /*!选择停止位的长度 */
uint16_t USART_Parity; /*!选择是否进行校验及校验方式 */
uint16_t USART_Mode; /*!配置串口工作模式 */
uint16_t USART_HardwareFlowControl; /*!选择硬件流控制及其控制方式 */
} USART_InitTypeDef;
- 重定向printf函数
使用printf、 scanf等 C 语言标准函数库输入输出函数,我们要勾选下图中的use MicroLIB
- 定义重定向函数
///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
(二)代码如下
-
usart.c
文件
#include "usart.h" //包含对应头文件
static void Usart_GPIO_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA端口的时钟
GPIO_InitTypeDef GPIO_InitStruct; //GPIO初始化结构体
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //将PA9配置为复用推挽输出
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; //输出速度配置为50MHz
GPIO_Init(GPIOA, &GPIO_InitStruct); //GPIO初始化函数,这里初始化的是PA9
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING; //将PA10配置为模拟输入
GPIO_Init(GPIOA, &GPIO_InitStruct); //GPIO初始化函数,这里初始化的是PA10
}
static void Usart_Mode_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启UART1外设的时钟
USART_InitTypeDef USART_InitStruct; //UART1初始化结构体
USART_InitStruct.USART_BaudRate=115200; //设置波特率为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_Mode=USART_Mode_Rx|USART_Mode_Tx ; //开启串口发送、接收功能
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None ; //关闭硬件控制流,我们这里采用软件控制
USART_Init(USART1, &USART_InitStruct); //USART初始化函数
USART_Cmd(USART1, ENABLE); //使能串口1,使其工作
}
///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t) ch);//调用固件库提供函数,发送一个字节数据到串口
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完毕
return (ch);
}
///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
// 等待串口输入数据
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
void Usart_Init(void) //初始化函数
{
Usart_GPIO_Config(); //初始化GPIO
Usart_Mode_Config(); //初始化USART工作模式
}
-
usart.h
文件
#ifndef _USART_H //条件编译,避免头文件被多次编译
#define _USART_H
#include <stdio.h>
#include "stm32f10x.h" //包含标准库文件,方便调用
void Usart_Init(void);
int fputc(int ch, FILE *f); //用户自定义函数声明
int fgetc(FILE *f);
#endif
-main.c
文件
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
int main(void)
{
Usart_Init(); //串口初始化
delay_init(); //延时初始化
while(1)
{
printf("Hello windows\n"); //输出测试信息
delay_ms(1000); //延时1s
}
}
注意:
while(1)
循环里一定要有延时或者执行其他操作,不然串口发送速率过快,会出现问题。
此外,我在循环函数中只实现了串口的循环发送,除此之外并没有实现其他功能的函数,这种方式效率不高,后面我们使用串口查询方式控制LED亮、灭。
(三)串口调试
- 下载串口调试助手
我这里使用的是野火多功能调试助手
下载链接 提取码:u659
- 打开串口调试助手,将串口配置好(端口号要连接到开发板才会显示),打开串口
- 串口打开成功
我这里是每隔1s开发板发送一次数据,串口调试助手每隔1s接收数据。
三、使用串口控制LED灯
使用getchar()函数接收发送的字符,将接收的字符与switch语句中的case比较,执行符合条件的语句
-
main.c
文件
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "usart.h"
int main(void)
{
char ch;
Usart_Init();
LED_Init(); //LED初始化
delay_init(); //延时初始化
while(1)
{
/* 获取字符指令 */
ch=getchar();
/* 根据字符指令控制彩灯颜色 */
switch(ch)
{
case '0':
LED_Off(); //LED灯全灭
break;
case '1':
LED_R_TOGGLE(); //红灯闪烁
break;
case '2':
LED_Y_TOGGLE(); //黄灯闪烁
break;
case '3':
LED_G_TOGGLE(); //绿灯闪烁
break;
case '4':
LED_G_On(); //绿灯常亮
break;
case '5':
LED_Y_On(); //黄灯常亮
break;
case '6':
LED_R_On(); //红灯常亮
break;
default:
LED_On(); //LED灯全亮
break;
}
}
}
- 接线方式
- 实现效果
通过串口调试助手发送指令‘7’
LED灯全亮
通过串口调试助手发送指令‘4’
绿灯常亮
关于通过串口控制LED灯,还有其他实现方式,在这里通过简单的发送特定的数据控制LED的变化,如果你有兴趣,可以试着控制更多的LED状态变化。
总结
以上是对于串口通信方式的一些理解,如有不当之处,敬请指教。