当前位置:   article > 正文

STM32第三课:串口调试_在某次uart通信任务中,已知协议为“115200-8-n-1-n(波特率115200bps,8个数

在某次uart通信任务中,已知协议为“115200-8-n-1-n(波特率115200bps,8个数据


需求

1.设备上电后四个灯灭。
2.按下KEY1,LED1灯亮,同时串口发送“LED1灯亮”。
3.再次按下KEY1,LED1灯灭,同时串口发送“LED1灯灭”。
4.以此类推,设置KEY2,KEY3,KEY4以及对应的LED灯


一、串口调试的意义

linux和C语言的学习中,每当编译完程序时代码编译没错,但功能实现却出现错误时,我们通常会使用printf来打印一下,观察程序到底是运行到哪里出错了。
在STM32中,由于现阶段还没有学习屏幕显示,当你在程序中直接添加printf时,我们无法观察到具体现象。此时,学会如何通过串口来调试就显得及其重要了。

二、串口调试实现流程

1.开时钟

首先,打开原理图找到USART1(串口)的位置
在这里插入图片描述

打开数据手册,找到系统结构图,查看控制串口和引脚的时钟在那个总线。
在这里插入图片描述
由上图中可知,控制USART1和GPIOA的时钟均在APB2时钟总线上。

查看手册中APB2外设时钟使能寄存器是如何控制的:
在这里插入图片描述

由上图可知,控制USART1和GPIOA的时钟是由APB2外设时钟使能寄存器的第14位和第2位控制的。该模块均为1使能0关闭。

在代码中为:

	//开时钟:GPIOA,USART1
	  RCC->APB2ENR |= 0x01<<2;//GPIOA
	  RCC->APB2ENR |= 0x01<<14;//USART1
  • 1
  • 2
  • 3

2.配置对应的IO口

由于配置对应的IO口 PA9(TX)和 PA10(RX)都是高八位,所以直接打开手册找到端口配置高寄存器。
在这里插入图片描述
模式的确定是由手册中8.1.11 外设的GPIO配置确定的
在这里插入图片描述
通信方式:
串行:串行数据传输时,数据是一位一位地在通信线上传输的。
并行:并行通信传输中有多个数据位,同时在两个设备之间传输。
单工:设备只能发送,或者只能接收 只有一种功能,比如收音机。
双工:设备能够发送,也能够接收。
全双工:发送和接收可以独立工作,可以同时进行收发,互不影响。
半双工:发送的时候不能接收,接收的时候不能发送 比如对讲机。
同步:设备之间使用同一个时钟线。
异步:设备有自己的时钟,但是通信时时钟要设置成一样。
时钟作用:决定了通信的速率。
通常情况下我们使用的串口都是串行全双工异步通信接口。
所以此时我们要将PA9(TX)配置为复用推挽(1011),PA10(RX)配置成浮空输入(0100)
代码如下:

	//配置对应的IO口	
		GPIOA->CRH &= ~(0x0f<<4);//PA9清0
		GPIOA->CRH |= 0x0B<<4;//设置成复用推挽输出
		GPIOA->CRH &= ~(0x0f<<8);//PA10清0
		GPIOA->CRH |= 0x04<<8;//设置成浮空
  • 1
  • 2
  • 3
  • 4
  • 5

3.配置串口1

串口1配置时需要设置数据位、校验位、停止位和波特率。
本次配置为8数据位,0校验位,1停止位,115200波特率
在手册中找到串口的控制寄存器CR1和CR2
在这里插入图片描述
在这里插入图片描述
在CR1中可以看到数据位在12位,校验位在第10位,发送使能在第3位,串口使能在第13位。
要注意的是发送使能和串口使能要最后再配制,防止没配置好串口就开始工作了。
在CR2中可以看到停止位在13和12位,此时全配1即可。
代码如下:

        USART1->CR1 &= ~(0x01<<12);//数据位置0
		USART1->CR1 &= ~(0x01<<10);//设置0位校验位
		USART1->CR2 &= ~(0x03<<12);//设置1个停止位
  • 1
  • 2
  • 3

接下来就要配置波特率了
波特率的配置是由波特比率寄存器决定的
在这里插入图片描述
DIV_Mantissa是USARTDIV的整数部分
DIV_Fraction[3:0]:USARTDIV的小数部分
计算方式为:
72M/usartdiv/16=115200
usartdiv = 72M/16/115200 = 39.0625
39.0625 = DIV_Mantissa + (DIV_Fraction/16)
DIV_Mantissa(整数部分) = 39
DIV_Fraction(小数部分) = 0.0625*16 = 1;
其中72M是由所在的时钟总线APB2决定的。
APB2总线默认时钟频率与系统时钟频率保持一致是72MHz。
在这里插入图片描述
此时,完整的串口1配置代码为:

	//配置串口1    8数据位,0校验位,1停止位,波特率115200
		USART1->CR1 &= ~(0x01<<12);//数据位置0
		USART1->CR1 &= ~(0x01<<10);//设置0位校验位
		USART1->CR2 &= ~(0x03<<12);//设置1个停止位
		USART1->BRR = (39<<4)+1;//设置波特率
		USART1->CR1 |= 0x01<<3;//发送使能
	    USART1->CR1 |= 0x01<<13;//使能串口1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

此时的串口就完全配置好了,可以直接通过串口的数据寄存器发送一个8位的数据。
在这里插入图片描述

USART1->DR = 'A';
  • 1

4.printf函数的重定向

配置完串口后,此时想要使用printf函数进行调试,还是不行的。
我们要将printf函数进行重定向,让打印的数据通过串口输出出来。
printf函数的重定向在官方给的标准库中是存在的,只需打开官方例程即可找到。
在这里插入图片描述
找到后复制过来即可:

int fputc(int ch, FILE *f)
{
	//printf函数最终会跳转到这里来运行
	while((USART1->SR&0x1<<6)==0);
	//发送数据
	USART1->DR = (uint8_t)ch;
  return ch;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

复制完后记得在头文件中声明一下。
完成以上步骤就可实现printf串口调试功能了。

三、需求的实现

需求均已实现,完整代码如下:
main.c

#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
#include "stdio.h"

int a,b,c,d;//LED灯的标志位

int main()
{
	
    Led_Init();
    key_Init();
    Beep_Init();
	Usart1_Config();
		
    while(1)
    {
				if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1)//key1
			{
				Delay_nms(10);
			    if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1)
				{
				  while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1)
					{}
					Led_Toggle(1);
						a++;
						if(a==2)
						{
							printf("LED1灯灭\r\n");
							a=0;
						}
						else
						{
							printf("LED1灯亮\r\n");
						}
				}
			}
				if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)==0)//key2
			{
				Delay_nms(10);
			    if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)==0)
				{
				  while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)==0)
					{}
					Led_Toggle(2);
						b++;
						if(b==2)
						{
							printf("LED2灯灭\r\n");
							b=0;
						}
						else
						{
							printf("LED2灯亮\r\n");
						}
				}
			}			
			if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==0)//key3
			{
				Delay_nms(10);
			    if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==0)
				{
				  while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==0)
					{}
					Led_Toggle(3);
						c++;
						if(c==2)
						{
							printf("LED3灯灭\r\n");
							c=0;
						}
						else
						{
							printf("LED3灯亮\r\n");
						}
				}
			}			
			if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_6)==0)//key4
			{
				Delay_nms(10);
			    if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_6)==0)
				{
				  while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_6)==0)
					{}
					Led_Toggle(4);
						d++;
						if(d==2)
						{
							printf("LED4灯灭\r\n");
							d=0;
						}
						else
						{
							printf("LED4灯亮\r\n");
						}
				}
			}			
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

usart.c

#include "usart.h"
#include "stdio.h"

void Usart1_Config()
{
	//开时钟:GPIOA,USART1
		RCC->APB2ENR |= 0x01<<2;//GPIOA
	  RCC->APB2ENR |= 0x01<<14;//USART1
	//配置对应的IO口 PA9(tx):复用推挽 PA10(RX):浮空输入
		GPIOA->CRH &= ~(0x0f<<4);//PA9清0
		GPIOA->CRH |= 0x0B<<4;//设置成复用推挽输出
		GPIOA->CRH &= ~(0x0f<<8);//PA10清0
		GPIOA->CRH |= 0x04<<8;//设置成浮空
	//配置串口1    8数据位,0校验位,1停止位,波特率115200
		USART1->CR1 &= ~(0x01<<12);//数据位置0
		USART1->CR1 &= ~(0x01<<10);//设置0位校验位
		USART1->CR2 &= ~(0x03<<12);//设置1个停止位
	  /*
    72M/usartdiv/16=115200
    usartdiv = 72M/16/115200 = 39.0625
    39.0625 =     DIV_Mantissa + (DIV_Fraction/16)
    DIV_Mantissa(整数部分) =  39
    DIV_Fraction(小数部分) = 0.0625*16 = 1;
    0000 0010 0111 0001
    */
		USART1->BRR = (39<<4)+1;//设置波特率
		USART1->CR1 |= 0x01<<3;//使能串口发送
	  USART1->CR1 |= 0x01<<13;//使能串口1
}

void SendData(uint8_t data)
{
	while((USART1->SR&0x01<<6)==0){}//等待上次发送完成
	USART1->DR = data;//发送数据
}

int fputc(int ch, FILE *f)
{
	//printf函数最终会跳转到这里来运行
	while((USART1->SR&0x1<<6)==0);
	//发送数据
	USART1->DR = (uint8_t)ch;
  return ch;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

usart.h

#ifndef _USART_H_
#define _USART_H_
#include "stm32f10x.h"
#include "stdio.h"

void Usart1_Config();
void SendData(uint8_t data);
int fputc(int ch, FILE *f);


#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

key.c

#include "stm32f10x.h"

void key_Init()
{
	//开时钟
	RCC->APB2ENR |= 0x01<<4;//PC
	RCC->APB2ENR |= 0x01<<2;//PA
	//配置模式
	GPIOC->CRL &=~(0X0F << 24);//PC6   key4
	GPIOC->CRL |= 0X04 << 24;
  GPIOC->CRL &=~(0X0F << 20);//PC5   key3
	GPIOC->CRL |= 0X04 << 20;
	GPIOC->CRL &=~(0X0F << 16);//PC4   key2
	GPIOC->CRL |= 0X04 << 16;
	GPIOA->CRL &=~0X0F;//PA0   key1
	GPIOA->CRL |= 0X04;
	
}

int Get_Key_Val(void)
{
	int key_val = 0;
	if(!!(GPIOA->IDR &(0X01 << 0))==1)
		key_val = 1;
	if(!!(GPIOC->IDR &(0X01 << 4))==0)
		key_val = 2;
	if(!!(GPIOC->IDR &(0X01 << 5))==0)
		key_val = 3;
	if(!!(GPIOC->IDR &(0X01 << 6))==0)
		key_val = 4;
	
	return key_val;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

key.h

#ifndef _KEY_H_
#define _KEY_H_

void key_Init();
int Get_Key_Val(void);

#endif

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

led.c

#include "stm32f10x.h"

void Led_Init()
{
	//配置好模式,然后全灭
	//开APB2时钟
	RCC->APB2ENR |= 0X01 << 6;
    //配置PE2--PE5为通用推挽输出
	GPIOE->CRL &=~(0X0F << 20);//PE5
	GPIOE->CRL |= 0X03 << 20;
	GPIOE->CRL &=~(0X0F << 16);//PE4
	GPIOE->CRL |= 0X03 << 16;
    GPIOE->CRL &=~(0X0F << 12);//PE3
	GPIOE->CRL |= 0X03 << 12;
	GPIOE->CRL &=~(0X0F << 8);//PE2
	GPIOE->CRL |= 0X03 << 8;
	//4个引脚均输出高电平
	GPIOE->ODR |= (0x0F << 2);
}
//开关灯
void Led1_Ctrl(int flag)
{
  if(!!flag)
	 {
		GPIOE->ODR &= ~(0x0F << 2);
	 }
	else
	 {
		GPIOE->ODR |= (0x0F << 2);
	 }
	 
}

void Led_Toggle(int flag)
{
	GPIOE->ODR ^= 0x01<<(flag+1);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

led.h

#ifndef _LED_H_
#define _LED_H_

void Led_Init();
void Led1_Ctrl(int flag);
void Led_Toggle(int flag);

#endif

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

delay.c

#include "stm32f10x.h"
#include "delay.h"

void Delay_nus(uint32_t time)
{
    uint32_t i=0;
    for(i=0;i<time;i++){
        delay1us();
    }    
}

void Delay_nms(uint32_t time)
{
    uint32_t i=0;
    for(i=0;i<time;i++){
         Delay_nus(1000);//延时1ms
    }    
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

delay.h

#ifndef _DELAY_H_
#define _DELAY_H_
#include "stm32f10x.h"

#define delay1us() {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
    __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();}

void Delay_nus(uint32_t time);
void Delay_nms(uint32_t time);
#endif
		
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

总结

1.了解多种通信方式和作用。
2.学会了如何在固件库中查找以及使用官方例程。
3.学会了USART1的配置及使用,以后就可以对程序直接进行printf调试了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/784225
推荐阅读
相关标签
  

闽ICP备14008679号