串口是计算机上一种非常通用的设备通信协议(不要与通用串行总线UniversalSerialBus或者USB混淆)。大多数计算机包含两个基于RS232的串口。串口同时也是仪器仪表设备通用的通信接口;很多GPIB兼容的设备也带有RS-232口。同时,串口通信协议也可以用于获取远程采集设备的数据。
串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。
RS-232是美国电子工业协会EIA(Electronic Industry Association)制定的一种串行物理接口标准。RS是英文“推荐标准”的缩写,232为标识号。RS-232是对电气特性以及物理特性的规定,只作用于数据的传输通路上,它并不内含对数据的处理方式。需要说明一下,很多人经常把RS-232、RS-422、RS-485 误称为通讯协议,这是很不应该的,其实它们仅是关于UART通讯的一个机械和电气接口标准(顶多是网络协议中的物理层面)。
该标准规定采用一个25 个脚的DB-25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。后来IBM的PC 机将RS-232 简化成了DB-9 连接器,从而成为今天的事实标准。而工业控制的RS-232 口一般只使用RXD(2)、TXD(3)、GND(5) 三条线。
早期由于PC都带有RS-232接口,所以我们需要使用UART时,都选择RS-232。但是现在个人电脑,不光是笔记本,包括台式机都不再带有RS-232的接口,大家看到电脑主板上面没有DB9的接口。所以现在开发板都选择TTL的UART,或者直接UART转USB做在开发板上。
嵌入式里面说的串口,一般是指UART口, 但是我们经常搞不清楚它和COM口的区别, 以及RS232, TTL等关系, 实际上UART,COM指的物理接口形式(硬件), 而TTL、RS-232是指的电平标准(电信号).
UART有4个pin(VCC, GND, RX, TX), 用的TTL电平, 低电平为0(0V),高电平为1(3.3V或以上)。
我们知道单片机是UATR口 TTL电平标准,而电脑是USB口。如果我们想用电脑的USB口与单片机串口(COM口)通信,这时就需要使用MAX232之类的电平转换芯片,将单片机TTL电平转换成RS-232电平 ,并且将USB转换为RS-232电平,这就是USB转串口。
- TTL电平:TTL电平信号被广泛使用,原因是:通常我们采用二进制来表示数据。而且规定,+5V等价于逻辑“1”,0V等价于逻辑“0”。这样的数据通信及电平规定方式,被称做TTL(晶体管-晶体管逻辑电平)信号系统。这是计算机处理器控制的设备内部各部分之间通信的标准技术。
- RS-232电平:对于数据(信息码):逻辑“1”(传号)的电平低于-3V,逻辑“0”(空号)的电平高于+3V;对于控制信号:接通状态(ON)即信号有效的电平高于+3V,断开状态(OFF)即信号无效的电平低于-3V,也就是当传输电平的绝对值大于3V时,电路可以有效地检查出来,介于-3~+3V之间的电压无意义,低于-15V或高于+15V的电压也认为无意义,因此,实际工作时,应保证电平在-3V~-15V或+3V~+15V之间。
我们所说的USB转串口,就是指单片机上装了一个TTL转RS-232的转换芯片 ,并且有COM口,使得单片机可以输出RS-232电平,这样便可以通过 USB转串口(RS-232)模块连接电脑USB口,再与单片机COM口相连,双方都是RS-232电平标准,便可以来进行通信。
市场出售的usb转串口线一般会有两个芯片,一个是CH340这类芯片,一个是MAX232类芯片。因为计算机的串口电平标准是RS-232电平,所以通过 USB转串口(RS-232)模块 ,USB经过CH340转成了TTL串口,再经由MAX232转换为RS-232电平。
由于STM32CubeMX是Java实现的,需要先安装jdk环境。
[jdk下载地址]: https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe0
[CubeMX下载地址]: https://www.st.com/en/development-tools/stm32cubemx.html
以管理员身份运行SetupSTM32CubeMX-6.3.0-Win.exe
点击next,然后接受,继续点击next
勾选“I have read and understood the…”,然后点击next
选择安装路径,注意不能出现中文
选好路径之后,点击next或确定,最后点击Done即安装完成
打开CubeMX,在Help下选择Manage embedded software packages
选择STM32F1下的固件库,点击install now
点击file然后新建一个工程,先在左上角输入芯片型号,如:STM32F103C8,然后在右边选择具体的芯片,双击进入编辑配置
在右边芯片部分设置引脚,左键单击PA1,选择GPIO_Output,其他引脚设置一样
点击左边System Core,下拉找到RCC,HSE和LSE都设置为Crystal/Ceramic Resonator
点击GPIO,设置GPIO的引脚信息,点击具体引脚,GPIO output level与Maximum output speed都设置为High,GPIO Pull-up/Pull-down设置为No pull-up and no pull-down。
点击上方的Project Manager管理工程,点击Project,然后输入工程名和选择工程路径,接着选择对应的编译器,如:MDK-ARM,V5版本。最后点击椭圆中的GENERATE CODE生成代码。
生成代码后点击,Open Project进入工程。
打开main.c,找到如下位置
输入以下代码
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET);
同以前一样,我们进行软件仿真,需要做如下修改
找到逻辑分析仪
点击Setup设置管脚,在弹出的窗口中点击右上角的矩形,输入PORTA.1即为PA1引脚,将每个引脚的Display Type设置为Bit
F5运行,观察波形
波形正确,LED闪烁周期为3秒。
下面是实物图
标准库函数对每个外设都建立了一个初始化结构体,比如 USART_InitTypeDef,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 USART_Init()调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。
初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在 stm32f10x_usart.h 文件中,初始化库函数定义在 stm32f10x_usart.c 文件中,编程时我们可以结合这两个文件内注释使用。
使用USART1,其挂接在APB2总线上,并且USART1对应STM32F103C8T6芯片管脚的PA9和PA10,所以使能时钟函数如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能 GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// 使能 USART1时钟
由于我们需要使用引脚的串口功能,所以将GPIO设置为复用功能,即Tx引脚配置为服用推挽输出,Rx引脚为浮空输入,如下:
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX 串口输出PA9
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX 串口输入PA10
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //模拟输入
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 GPIO
串口通信相关参数初始化,库函数如下:
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef*USART_InitStruct);
USART 初始化结构体
typedef struct {
uint32_t USART_BaudRate; // 波特率
uint16_t USART_WordLength; // 字长
uint16_t USART_StopBits; // 停止位
uint16_t USART_Parity; // 校验位
uint16_t USART_Mode; // USART 模式
uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;
- USART_BaudRate:波特率设置。一般设置为 2400、 9600、 19200、 115200。标准库 函 数 会 根 据 设 定 值 计 算 得 到 USARTDIV 值 , 并 设 置USART_BRR 寄存器值。
- USART_WordLength:数据帧字长,可选 8 位或 9 位。它设定 USART_CR1 寄存器的 M 位的值。如果没有使能奇偶校验控制,一般使用 8 数据位;如果使能了奇偶校验则一般设置为 9 数据位。
- USART_StopBits:停止位设置,可选 0.5 个、 1 个、 1.5 个和 2 个停止位,它设定USART_CR2 寄存器的 STOP[1:0]位的值,一般我们选择 1 个停止位。
- USART_Parity : 奇 偶 校 验 控 制 选 择 , 可 选 USART_Parity_No( 无 校 验 ) 、USART_Parity_Even( 偶 校 验 ) 以 及 USART_Parity_Odd( 奇 校 验 ) , 它 设 定USART_CR1 寄存器的 PCE 位和 PS 位的值。
- USART_Mode: USART 模式选择,有 USART_Mode_Rx 和 USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定 USART_CR1 寄存器的 RE 位和 TE 位。
- USART_HardwareFlowControl:硬件流控制选择,只有在硬件流控制模式才有效,可选有⑴使能 RTS、 ⑵使能 CTS、 ⑶同时使能 RTS 和 CTS、 ⑷不使能硬件流。
接着我们进行配置USART1
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
配置好串口后,我们还需要使能它,使能串口库函数如下:
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
我们需要使能 USART1:
USART_Cmd(USART1, ENABLE); //使能串口 1
发送字节程序(相关函数可以参考野火资料,USART_SendData函数在stm32f10x_usart.c中可以找到)
/* 发送一个字节 */
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
发送字符串
/* 发送字符串 */
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
{
}
}
创建工程
我们在Project目录下添加usart.c文件,在头文件中添加usart.h文件
usart.h
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
#include "stdio.h"
void usart_init(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
#endif
usart.c
#include "usart.h"
void usart_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能 GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// 使能 USART1时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX 串口输出PA9
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX 串口输入PA10
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //模拟输入
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 GPIO
USART_InitStructure.USART_BaudRate = 115200;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口 1
}
/* 发送一个字节 */
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/* 发送字符串 */
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
{
}
}
main.c
#include "stm32f10x.h"
#include "usart.h"
u16 USART_RX_STA=0; //接收状态标记
int main(void)
{
usart_init();
while(1)
{
Usart_SendString( USART1,"hello Windows!\r\n");
}
}
软件仿真运行
硬件运行
我们将程序烧录进芯片,通过串口调试助手来观察串口输出
串口设置好后,点击打开串口,成功接收到信息
同前面一样,我们选择好芯片后,进入配置
将PA9设置为USART1_TX,PA10设置为USART1_RX
仍将RCC中高/低速时钟设置为Crystal/Ceramic Resonator
点击Connectivity下的USART1,将Mode设为Asynchronous,勾选下方使能
点击Clock Configuration,将PLL Source Mux点到HSE
进入工程管理界面,Project中的设置同前面一样,Code Generator中需要勾选Generate peripheral…,接着生成代码即可
打开工程文件,对main.c做以下修改
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* © Copyright (c) 2021 STMicroelectronics.
* All rights reserved.
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h" //由于使用了printf,所以加上这个头文件
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
typedef struct{
UART_HandleTypeDef *huart1;
uint8_t rx_buf[10];//接收缓冲区
uint32_t tx_count;//发送成功次数技术标志位
uint32_t rx_flag;//接收完成标志位
}rxtx_it_usart_t;
//声明一个结构体并进行初始化
rxtx_it_usart_t rxtx_it_usart={
.huart1=&huart1,
};
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
uint32_t start_count =HAL_GetTick();
uint8_t send_data[]="hello windows!\r\n"; //发送数据"hello windows!"
HAL_UART_Receive_IT(rxtx_it_usart.huart1,rxtx_it_usart.rx_buf,10);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if((HAL_GetTick()-start_count)>500) //0.5秒发送一次
{
start_count=HAL_GetTick();
HAL_UART_Transmit_IT(rxtx_it_usart.huart1,send_data,sizeof(send_data)-1);
}
if(rxtx_it_usart.rx_flag==1) //接收成功一次就将数据上发一次
{
rxtx_it_usart.rx_flag=0;
printf("%s\r\n",rxtx_it_usart.rx_buf); //回显接收到的数据
HAL_UART_Receive_IT(rxtx_it_usart.huart1,rxtx_it_usart.rx_buf,10);
}
/* USER CODE END 3 */
}}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {
0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {
0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
软件仿真
除了同以前一样需要做一些修改外,此次还要勾选Target下的Use MicroLIB
运行结果如下,成功
硬件运行
接下来烧录并通过串口调试助手观察
本实验除了可以查询串口外,还可以通过中断实现通过串口调试助手发送命令使之停止等等功能。串口是单片机中非常重要的概念,单片机的通讯功能就是由串口实现的,在串口的基础上可以扩展出RS232、RS485、LIN等,因此掌握串口通信,非常重要。
STM32CubeMX之串口使用(中断方式)
STM32实例——USART串口通信实验(二)