串口调试

  • 前言
  • 一、什么是串口?
  • 二、使用串口给上位机发送“hello windows!”
  • (一)串口初始化
  • (二)代码如下
  • (三)串口调试
  • 三、使用串口控制LED灯
  • 总结



前言


很多时候,我们在编写程序时不可避免的要调试代码,或者输出一些调试信息,但设计硬件的调试不像纯软件一样,直接在黑窗口上就可以看到想要输出的调试信息。我们通过串口将硬件与上位机连接起来,就可输出一些调试信息,用电脑USB口接收十分方便,串口调试软件很多,接下来我们一起来体会一下串口通讯的特点。


一、什么是串口?

串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。

串口通讯的数据由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。

  • 串口通信协议

在串口通讯的协议中,规定了数据包的内容,它由启始位、数据位、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。

stm32 Ymodem串口_串口

起始位: 在通信线上没有数据传送时处于逻辑“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,我们等会儿需要配置的就是这两个引脚

stm32 Ymodem串口_stm32 Ymodem串口_02

  • 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

stm32 Ymodem串口_#include_03

  • 定义重定向函数
///重定向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

  • 打开串口调试助手,将串口配置好(端口号要连接到开发板才会显示),打开串口

stm32 Ymodem串口_初始化_04

  • 串口打开成功

stm32 Ymodem串口_#include_05


我这里是每隔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;      
	}
}	
}
  • 接线方式

stm32 Ymodem串口_#include_06

  • 实现效果

通过串口调试助手发送指令‘7’

stm32 Ymodem串口_#include_07


LED灯全亮

stm32 Ymodem串口_#include_08


通过串口调试助手发送指令‘4’

stm32 Ymodem串口_初始化_09


绿灯常亮

stm32 Ymodem串口_初始化_10


关于通过串口控制LED灯,还有其他实现方式,在这里通过简单的发送特定的数据控制LED的变化,如果你有兴趣,可以试着控制更多的LED状态变化。

总结


以上是对于串口通信方式的一些理解,如有不当之处,敬请指教。