基于 MDK5实现STM32串口通信

一、串口协议与RS-232标准

1.串口协议

串口通信(Serial Communication), 是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式。
串口通信协议是指规定了数据包的内容,内容包含了起始位、主体数据、校验位及停止位,双方需要约定一致的数据包格式才能正常收发数据的有关规范。在串口通信中,常用的协议包括RS-232、RS-422RS-485。下面主要讲解RS-232标准。

串口通讯的数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在串口通讯的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数
在这里插入图片描述
串口通信最重要的参数是波特率、数据位、停止位奇偶校验位。对于两个进行通信的端口,这些参数必须匹配。
1.波特率
这是一个衡量符号传输速率的参数。指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。
2.数据位
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。
3.停止位
用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
4.奇偶校验位
在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。

2.RS-232标准

RS-232标准主要规定了信号的用途、通讯接口以及信号的电平标准。
使用RS-232标准的串口设备间常见的通讯结构如下:
在这里插入图片描述
在上面的通讯方式中,两个通讯设备的"DB9接口"之间通过串口信号线建立起连接,串口信号线中使用"RS-232标准"传输数据信号。由于RS-232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个"电平转换芯片"转换成控制器能识别的"TTL校准"的电平信号,才能实现通讯。
2.1.电平标准
根据通讯使用的电平标准不同,串口通讯可分为TTL标准及RS-232标准:

通讯标准电平标准(发送端)
5V TTL逻辑1:2.4V~5V
逻辑0:0~0.5V
RS-232逻辑1:-15V~-3V
逻辑0:+3V~+15V

常见的电子电路中常使用TTL的电平标准,理想状态下,使用5V表示二进制逻辑1,使用0V表示逻辑0;而为了增加串口通讯的远距离传输及抗干扰能力,它使用-15V表示逻辑1,+15V表示逻辑0。使用RS232与TTL电平校准表示同一个信号时的对比见下图
在这里插入图片描述
因为控制器一般使用TTL电平标准,所以常常会使用MA3232芯片对TTL及RS-232电平的信号进行互相转换。

3.STM32的USART

3.1.简介
STM32芯片具有多个USART外设用于串口通讯,它是 Universal Synchronous Asynchronous Receiver and Transmitter的缩写,即通用同步异步收发器可以灵活地与外部设备进行全双工数据交换。有别于USART,它还有具有UART外设(Universal Asynchronous Receiver and Transmitter),它是在USART基础上裁剪掉了同步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是USART。
STM32的USART输出的是TTL电平信号,若需要RS-232标准的信号可使用MAX3232芯片进行转换。
3.2.功能框图
掌握了功能框图,对USART就会有一个整体的把握,在编程时思路就会很清晰。
在这里插入图片描述
具体功能概述可参考链接:https://www.cnblogs.com/firege/p/5805753.html

二、STM32的USART串口通讯

完成一个STM32的USART串口通讯程序,要求:
1)设置波特率为115200,1位停止位,无校验位;
2)STM32系统给上位机(win10)连续发送“hello windows!”(win10采用“串口助手”工具接收)。

1.新建工程

STM32cubeMX的下载与安装以及相关工程的建立在之前的博客已经涉及过,详情可参考:https://blog.csdn.net/qq_54496810/article/details/120882256?spm=1001.2014.3001.5501
打开STM32cubeMX程序,点击File下的New Project新建一个工程
在这里插入图片描述
Part Number处选择STM32F103C8,然后点击中间会出现的一列芯片的信息,再点击Start Project就行了
在这里插入图片描述
点击System Core,进入里面的SYS,在debug那里选择Serial Wire
在这里插入图片描述
点击RCC,将HSE那里设为Crystal/Ceramic Resonator就行了
在这里插入图片描述
来到Clock Configuration界面,把PLLCLK右边选上,晶振改为72
在这里插入图片描述
接着点击Connnectivity,选择USART1串口1(即最小芯片RXD引脚接A10,TXD引脚接A9),Mode选择Asynchronous,此时在下面就可以看到配置的一些信息,包括波特率、字长、校验位和停止位等。

TXD:发送数据输出引脚
RXD:接收数据输出引脚

在这里插入图片描述
来到Project Manager界面,在Project下自定义自己的工程名和工程路径,将IDE那项改为MDK-ARM,版本根据自己的需求选择
在这里插入图片描述
注意:路径中不能出现中文名,否则会报错!!!
进入Code Generate界面,勾选生成初始化.c/.h文件
在这里插入图片描述
最后点击GENERATE CODE,生成代码
在这里插入图片描述

2.重定向printf函数

进入刚刚选择的路径,打开MDK-ARM子文件夹,通过keil打开刚刚生成的项目
在这里插入图片描述
如果我们要使用printf、 scanf等 C 语言标准函数库输入输出函数,我们要进行一些配置:
点击Options for Target
在这里插入图片描述
Target界面勾选中use MicroLIB,点击OK即可
在这里插入图片描述
重定向printf函数代码如下:

// 重定向函数
int fputc(int ch,FILE *f)
{
    uint8_t temp[1]={ch};
    HAL_UART_Transmit(&huart1,temp,1,2);        //UartHandle是串口的句柄
}

注意:定义重定向printf函数记得加stdio.h头文件。

3.主要代码

3.1.usart.h文件

#ifndef __USART_H__
#define __USART_H__
#include <stdio.h>
int fputc(int ch,FILE *f);	//用户自定义函数声明
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
extern UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void);
#ifdef __cplusplus
}
#endif
#endif

3.2.usart.c文件

#include "usart.h"
#include <stdio.h>

// 重定向printf函数
int fputc(int ch,FILE *f)
{
    uint8_t temp[1]={ch};
    HAL_UART_Transmit(&huart1,temp,1,2);        //UartHandle是串口的句柄
}
UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void)
{
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART1)
  {
    __HAL_RCC_USART1_CLK_DISABLE();

    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
  }
}

3.3.main.c文件

#include "main.h"
#include "usart.h"
#include "gpio.h"

void SystemClock_Config(void);
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
	  HAL_Delay(1000);	//延时
	  printf("hello windows!\n");	//输出hello windows!
  }
}
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

void Error_Handler(void)
{
  __disable_irq();
  while (1){}
}

#ifdef  USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line){}
#endif /* USE_FULL_ASSERT */

4.编译程序生成.hex文件

编译运行程序无报错且生成相应的.hex文件
在这里插入图片描述
在文件夹MDK-ARM下,打开与工程名相同的子文件夹可以看到生成的.hex文件
在这里插入图片描述

5.程序烧录

烧录过程的操作和软件下载可参考链接:https://blog.csdn.net/qq_54496810/article/details/120777279
选择刚刚生成的.hex文件,开始编程烧录代码,成功后会返回结果
在这里插入图片描述

6.串口工具运行结果

野火串口调试助手下载链接:https://pan.baidu.com/s/1DgXknJbM7zWJdp8Cx-qx_A
提取码:1234
烧录成功后,打开野火串口调试助手程序,点击打开串口
在这里插入图片描述
可以看到串口调试助手持续输出hello windows!
在这里插入图片描述
每隔1s开发板发送一次数据,串口调试助手每隔1s接收数据。
部分数据输出如下:
在这里插入图片描述

三、小结

通过本次实验,了解了串口协议和RS-232标准,以及RS232电平与TTL电平的区别,再一次使用stm32CubeMX自动生成代码完成了STM32的一个USART串口通讯程序,更加体会到了合理的使用工具,能让我们更快的编写代码,提高了学习效率,虽然存在着不足之处,但还是收获满满。

四、参考文献

1.https://baike.baidu.com/item/%E4%B8%B2%E5%8F%A3%E9%80%9A%E4%BF%A1/3775296
2.https://blog.csdn.net/sehanlingfeng/article/details/80383117
3.https://blog.csdn.net/weixin_52288941/article/details/120848520?spm=1001.2014.3001.5501

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值