串口 --- 数据通信模式

一.数据通信的基础概念

<1>按数据通信方式分类

  • 串行通信:数据逐位按顺序依次传输;
  • 并行通信:数据各位通过多条线同时传输。
特点传输速率抗干扰能力通信距离IO资源占用成本
串行通信较低较强较长较少较低
并行通信较高较弱较短较多较高

<2>按数据传输方向分类

  • 单工通信:数据只能沿一个方向传输;
  • 半双工通信:数据可以沿两个方向传输,但需要分时进行;
  • 全双工通信:数据可以同时进行双向传输。

<3>按数据同步方向分类

  •  同步通信:共用同一时钟信号;
  • 异步通信:没有时钟信号,通过在数据信号中加入起始位和停止位等一些同步信号。

<4>波特率

1.比特率 

每秒钟传送的比特数,单位bit/s.

2.波特率

每秒钟传送的码元数,单位Baud.

码元:信号经过调制之后并且进行了编码。

3.波特率和波特率的关系

比特率=波特率*log2M,M表示每个码元承载的信息量;

二进系统中,波特率数值上等于比特率。

 4.常见的串行通信接口

通信接口

接口引脚

数据同步方式数据传输方向

UART

(通用异步收发器)

TXD:发送端

RXD:接收端

异步通信全双工
1-wireDQ:发送/接收端异步通信半双工
IIC

SCL:同步时钟

SDA:数据输入/输出端

同步信号半双工
SPI

SCK:同步时钟

MISO:主机输入,从机输出

MOSI:主机输出,从机输入

CS:片选信号

同步通信全双工

二.串口(RS-232)

<1>串口

1.串口的概念

串行通信接口:指按位发送和接收的接口。如:RS-232/422/485等。

2.RS-232接口(DB9)

数据线:

  • TXD(pin3):串口数据输出;
  • RXD(pin2):串口数据输入。

 握手(同步信号):

  • RTS(pin7):请求发送;
  • CTS(pin8):清除发送;
  • DSR(pin6):数据发送就绪;
  • DCD(pin1):数据载波检测;
  • DTR(pin4):数据终端就绪。

地线:

GND(pin5):信号地,与两根数据线可组成异步通信。 

其他:

RI(pin9):振铃指示。

<2>RS-232电平与CMOS/TTL电平对比 

1.RS-232电平

逻辑1:-15V ~ -3V

逻辑0:+3V ~ +15V

2.CMOS电平(3.3V)--- STM32

逻辑1:3.3V

逻辑0:0V

3.TTL电平(5V) --- 51

逻辑1:5V

逻辑0:0V

注意:CMOS/TTL电平不能与RS-232电平直接交换信息。 

<3>设备间的RS-232通信示意图

电平转换芯片:MAX3232、SP3232等

注意:两个设备之间的TXD和RXD,必须交叉连接,方可正常通信。

 <4>STM32串口与电脑USB口通信示意图

PC还需要安装CH340 USB虚拟串口驱动。

注意:两个设备之间的TXD和RXD,必须交叉连接,方可正常通信。

<5>RS-232异步通信协议 

由于是异步通信,所以SCLK线并不使用

  •  启动位:必须占1个位长,保持逻辑0电平;
  • 有效数据位:可选5、6、7、8、9个位长。LSB(最低有效位)在前,MSB在后;
  • 校验位:可选占1个位长,也可以没有该位;
  • 停止位:必须有,可选占0.5、1、1.5、2个位长,保持逻辑1电平。

三.STM32的USART

<1>USART简介

Universal synchronous asynchronous receiver transmitter,通用同步异步收发器;

Universal asynchronous receiver transmitter,通用异步收发器。

USART/UART都可以与外部设备进行全双工异步通信;

USART,我们常用的也是异步通信。

<2>USART主要特征

1.全双工异步通信;

2.单线半双工通信;

3.单独的发送器和接收器使能位;

4.可配置使用DMA的多缓冲器通信;

5.多个带标志的中断源。

 <3>USART框图

<4>设置USART/UART波特率(F1) 

1.计算公式

波特率计算公式:baud = f(ck) / 16 * USARTDIV;

其中f(ck)是串口的时钟,如:USART1的时钟是PCLK2,其他串口都是PCLK1;

USARTDIV = DIV_Mantissa + (DIV_Fraction / 16)。

2.波特比率寄存器(BRR)

把USARTDIV的整数部分写入[15:4],USARTDIV的小数部分写入[3:0]。

 fraction加上0.5是进行四舍五入。

<5>USART寄存器简介(F1)

设置好控制和波特率寄存器后,往该寄存器(DR)写入数据即可发送,接收数据则读该寄存器。

 四.HAL库外设初始化MSP回调机制

五.HAL库中断回调机制 

六.USART/UART异步通信配置步骤 

  1. 配置串口工作参数:HAL_UART_Init()
  2. 串口底层初始化:HAL_UART_MspInit() --- 配置GPIO、NVIC、CLOCK等
  3. 开启串口异步接收中断:HAL_UART_Receive_IT()
  4. 设置优先级、使能中断:HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ()
  5. 编写中断服务函数:USARTx_IRQHandler()、UARTx_IRQHandler()
  6. 串口数据发送:USART_DR、HAL_UART_Transmit()

HAL库相关函数:

 

 七.IO引脚的复用功能

<1>复用的概念

  • 通用:IO端口的输入或输出是由GPIO外设控制;
  • 复用:IO端口的输入或输出是由其它非GPIO外设控制。

<2>F1的IO引脚复用 

  • 可查数据手册引脚定义来看各IO支持什么复用功能;
  • 同一时间IO只能用作一种复用功能,否则会发生冲突;
  • 遇到IO复用功能冲突,可考虑重映射功能。

<3>F4/F7/H7的IO引脚复用 

为了解决F1系列存在的IO复用功能冲突问题,F4往后的系列都加入了复用器

复用器特点:

  • 每个IO引脚都有一个复用器;
  • 复用器采用16路复用功能输入(AF0到AF15);
  • 复用器一次仅允许一个外设的复用功能(AF)连接到IO引脚;
  • 通过GPIOx_AFRL和GPIOx_AFRH寄存器进行配置。

 注意:复位完成后,所有IO都会连接到系统的复用功能0(AF0)。

八.通过串口接收或者发送一个字符

uart.c

#include "uart.h"


/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1

#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((USART_UX->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */

    USART_UX->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif
/******************************************************************************************/
uint8_t g_rx_buffer[1];/*HAL库使用到的串口接受数据缓冲区*/
uint8_t g_usart1_rx_flag = 0;/*串口接收到数据标志*/

UART_HandleTypeDef g_uart1_hadle;/*UART句柄*/

/*uart初始化*/
void usart_init(uint32_t baudrate)
{
	g_uart1_hadle.Instance = USART1;
	g_uart1_hadle.Init.BaudRate = baudrate;
	g_uart1_hadle.Init.WordLength = UART_WORDLENGTH_8B;
	g_uart1_hadle.Init.StopBits = UART_STOPBITS_1;
	g_uart1_hadle.Init.Parity = UART_PARITY_NONE;
	g_uart1_hadle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	g_uart1_hadle.Init.Mode = UART_MODE_TX_RX;

	HAL_UART_Init(&g_uart1_hadle);
	HAL_UART_Receive_IT(&g_uart1_hadle,(uint8_t *)g_rx_buffer,1);
}

/*串口MSP回调函数*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
	GPIO_InitTypeDef gpio_init_struct;
	if(huart->Instance == USART1) /*如果是串口1,进行串口1 MSP初始化*/
	{
		__HAL_RCC_USART1_CLK_ENABLE();
		__HAL_RCC_GPIOA_CLK_ENABLE();
		
		gpio_init_struct.Pin = GPIO_PIN_9;
		gpio_init_struct.Mode = GPIO_MODE_AF_PP;
		gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
		HAL_GPIO_Init(GPIOA,&gpio_init_struct);
		
		gpio_init_struct.Pin = GPIO_PIN_10;
		gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
		gpio_init_struct.Pull = GPIO_PULLUP;
		HAL_GPIO_Init(GPIOA,&gpio_init_struct);
		
		HAL_NVIC_SetPriority(USART1_IRQn,3,3);
		HAL_NVIC_EnableIRQ(USART1_IRQn);
		/*(1)使能USART1和对应IO时钟,(2)初始化IO,(3)使能USART1中断,设置优先级*/
	}
}

/*串口1中断服务函数*/
void USART1_IRQHandler(void)
{
	HAL_UART_IRQHandler(&g_uart1_hadle);
	HAL_UART_Receive_IT(&g_uart1_hadle,(uint8_t *)g_rx_buffer,1);
}
/*串口数据接收完成回调函数*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	g_usart1_rx_flag = 1;/*如果用到多个串口,要先判断串口寄存器基地址*/
}













uart.h

#ifndef _UART_H
#define _UART_H

#include "./SYSTEM/sys/sys.h"
#include "stdio.h"

/******************************************************************************************/
/* 引脚 和 串口 定义 
 * 默认是针对USART1的.
 * 注意: 通过修改这几个宏定义,可以支持USART1~UART5任意一个串口.
 */
#define USART_TX_GPIO_PORT                  GPIOA
#define USART_TX_GPIO_PIN                   GPIO_PIN_9
#define USART_TX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define USART_RX_GPIO_PORT                  GPIOA
#define USART_RX_GPIO_PIN                   GPIO_PIN_10
#define USART_RX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define USART_UX                            USART1
#define USART_UX_IRQn                       USART1_IRQn
#define USART_UX_IRQHandler                 USART1_IRQHandler
#define USART_UX_CLK_ENABLE()               do{ __HAL_RCC_USART1_CLK_ENABLE(); }while(0)  /* USART1 时钟使能 */

/******************************************************************************************/

extern uint8_t g_rx_buffer[1];/*HAL库使用到的串口接受数据缓冲区*/
extern uint8_t g_usart1_rx_flag;/*串口接收到数据标志*/
extern UART_HandleTypeDef g_uart1_hadle;/*UART句柄*/

void usart_init(uint32_t bound);                /* 串口初始化函数 */

#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "uart.h"
#include "./SYSTEM/delay/delay.h"

void led_init(void);                       /* LED初始化函数声明 */

int main(void)
{
    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);      /* 设置时钟, 72Mhz */
    delay_init(72);                          /* 延时初始化 */
		usart_init(115200);											 /* 波特率设置为115200 */
    
	printf("请输入一个字符:\r\n\r\n");
    while(1)
    { 
			if(g_usart1_rx_flag == 1)
			{
				printf("您输入的字符为:\r\n");
				HAL_UART_Transmit(&g_uart1_hadle,(uint8_t *)g_rx_buffer,1,1000);
				while(__HAL_UART_GET_FLAG(&g_uart1_hadle,UART_FLAG_TC) != 1)
					printf("\r\n");
				g_usart1_rx_flag = 0;
			}else
			{
				delay_ms(10);
			}			
    
    }
}

 九.通过串口接收或者发送一个字符串

uart.c

#include "uart.h"


/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1

#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((USART_UX->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */

    USART_UX->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif
/******************************************************************************************/
/* 接收缓冲, 最大USART_REC_LEN个字节. */
uint8_t g_usart_rx_buf[USART_REC_LEN];

/*  接收状态
 *  bit15,      接收完成标志
 *  bit14,      接收到0x0d
 *  bit13~0,    接收到的有效字节数目
*/
uint16_t g_usart_rx_sta = 0;

uint8_t g_rx_buffer[RXBUFFERSIZE];  /* HAL库使用的串口接收缓冲 */

UART_HandleTypeDef g_uart1_hadle;/*UART句柄*/

/*uart初始化*/
void usart_init(uint32_t baudrate)
{
	g_uart1_hadle.Instance = USART1;
	g_uart1_hadle.Init.BaudRate = baudrate;
	g_uart1_hadle.Init.WordLength = UART_WORDLENGTH_8B;
	g_uart1_hadle.Init.StopBits = UART_STOPBITS_1;
	g_uart1_hadle.Init.Parity = UART_PARITY_NONE;
	g_uart1_hadle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	g_uart1_hadle.Init.Mode = UART_MODE_TX_RX;

	HAL_UART_Init(&g_uart1_hadle);
	HAL_UART_Receive_IT(&g_uart1_hadle,(uint8_t *)g_rx_buffer,1);
}

/*串口MSP回调函数*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
	GPIO_InitTypeDef gpio_init_struct;
	if(huart->Instance == USART1) /*如果是串口1,进行串口1 MSP初始化*/
	{
		__HAL_RCC_USART1_CLK_ENABLE();
		__HAL_RCC_GPIOA_CLK_ENABLE();
		
		gpio_init_struct.Pin = GPIO_PIN_9;
		gpio_init_struct.Mode = GPIO_MODE_AF_PP;
		gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
		HAL_GPIO_Init(GPIOA,&gpio_init_struct);
		
		gpio_init_struct.Pin = GPIO_PIN_10;
		gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
		gpio_init_struct.Pull = GPIO_PULLUP;
		HAL_GPIO_Init(GPIOA,&gpio_init_struct);
		
		HAL_NVIC_SetPriority(USART1_IRQn,3,3);
		HAL_NVIC_EnableIRQ(USART1_IRQn);
		/*(1)使能USART1和对应IO时钟,(2)初始化IO,(3)使能USART1中断,设置优先级*/
	}
}

/**
 * @brief       串口1中断服务函数
 * @param       无
 * @retval      无
 */
void USART1_IRQHandler(void)
{
	 HAL_UART_IRQHandler(&g_uart1_hadle);   /* 调用HAL库中断处理公用函数 */
}
/**
 * @brief       串口数据接收回调函数
                数据处理在这里进行
 * @param       huart:串口句柄
 * @retval      无
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART_UX)                    /* 如果是串口1 */
    {
        if ((g_usart_rx_sta & 0x8000) == 0)             /* 接收未完成 */
        {
            if (g_usart_rx_sta & 0x4000)                /* 接收到了0x0d(即回车键) */
            {
                if (g_rx_buffer[0] != 0x0a)             /* 接收到的不是0x0a(即不是换行键) */
                {
                    g_usart_rx_sta = 0;                 /* 接收错误,重新开始 */
                }
                else                                    /* 接收到的是0x0a(即换行键) */
                {
                    g_usart_rx_sta |= 0x8000;           /* 接收完成了 */
                }
            }
            else                                        /* 还没收到0X0d(即回车键) */
            {
                if (g_rx_buffer[0] == 0x0d)
                    g_usart_rx_sta |= 0x4000;
                else
                {
                    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0];
                    g_usart_rx_sta++;

                    if (g_usart_rx_sta > (USART_REC_LEN - 1))
                    {
                        g_usart_rx_sta = 0;             /* 接收数据错误,重新开始接收 */
                    }
                }
            }
        }

        HAL_UART_Receive_IT(&g_uart1_hadle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
    }
}













uart.h

#ifndef _UART_H
#define _UART_H

#include "./SYSTEM/sys/sys.h"
#include "stdio.h"

/******************************************************************************************/
/* 引脚 和 串口 定义 
 * 默认是针对USART1的.
 * 注意: 通过修改这几个宏定义,可以支持USART1~UART5任意一个串口.
 */
#define USART_TX_GPIO_PORT                  GPIOA
#define USART_TX_GPIO_PIN                   GPIO_PIN_9
#define USART_TX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define USART_RX_GPIO_PORT                  GPIOA
#define USART_RX_GPIO_PIN                   GPIO_PIN_10
#define USART_RX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define USART_UX                            USART1
#define USART_UX_IRQn                       USART1_IRQn
#define USART_UX_IRQHandler                 USART1_IRQHandler
#define USART_UX_CLK_ENABLE()               do{ __HAL_RCC_USART1_CLK_ENABLE(); }while(0)  /* USART1 时钟使能 */

/******************************************************************************************/

#define USART_REC_LEN               200         /* 定义最大接收字节数 200 */
#define USART_EN_RX                 1           /* 使能(1)/禁止(0)串口1接收 */
#define RXBUFFERSIZE   1                        /* 缓存大小 */

extern UART_HandleTypeDef g_uart1_hadle;       /* HAL UART句柄 */

extern uint8_t  g_usart_rx_buf[USART_REC_LEN];  /* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint16_t g_usart_rx_sta;                 /* 接收状态标记 */
extern uint8_t g_rx_buffer[RXBUFFERSIZE];       /* HAL库USART接收Buffer */

void usart_init(uint32_t bound);                /* 串口初始化函数 */
#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "uart.h"
#include "./SYSTEM/delay/delay.h"

void led_init(void);                       /* LED初始化函数声明 */

int main(void)
{
		uint8_t len;
    uint16_t times = 0;
    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);      /* 设置时钟, 72Mhz */
    delay_init(72);                          /* 延时初始化 */
		usart_init(115200);											 /* 波特率设置为115200 */
    
	printf("请输入一个字符:\r\n\r\n");
    while(1)
    { 
			  if (g_usart_rx_sta & 0x8000)        /* 接收到了数据? */
        {
            len = g_usart_rx_sta & 0x3fff;  /* 得到此次接收到的数据长度 */
            printf("\r\n您发送的消息为:\r\n");

            HAL_UART_Transmit(&g_uart1_hadle,(uint8_t*)g_usart_rx_buf, len, 1000);    /* 发送接收到的数据 */
            while(__HAL_UART_GET_FLAG(&g_uart1_hadle,UART_FLAG_TC) != SET);           /* 等待发送结束 */
            printf("\r\n\r\n");             /* 插入换行 */
            g_usart_rx_sta = 0;
        }
        else
        {
            times++;

            if (times % 5000 == 0)
            {
                printf("\r\n 串口实验 r\n");
                
            }

            if (times % 200 == 0) printf("请输入数据,以回车键结束\r\n");

           

            delay_ms(10);
        }
    }
    
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值