一、串口介绍
众所周知,串口通信是MCU最基本的通信方式,对于STM32来说也是如此。本文重点讲述STM32单片机的串口通信,主要包括的内容是:通信基础知识、串口通信原理、USART有关寄存器和自定义编写串口通信函数。
1.处理器与外部设备通信的两种方式
- 通信目的:的将一个设备数据传送到另一个设备,扩展硬件系统。
- 通信协议:制定通信规则,通信双方按照协议规则进行数据收发。
并行通信:
-⤴️传输原理:数据各个位同时传输。
-⤴️优点:速度快
-⤴️缺点:占用引脚资源多
4.串行通信:
-⤴️传输原理:数据按位顺序传输。
-⤴️优点:占用引脚资源少
-⤴️缺点:速度相对较慢
2.串行通信的分类
串行通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式,如SPI、USART、EEPROM通信等。串口通信实现了上位机(一般就是我们的电脑)与下位机(也就是我们的嵌入式设备,如51单片机,STM32)之间的信息交互。上位机可通过串口调试助手等实现数据的发送接收。
- 😉a单工:数据传输只支持数据在一个方向上传输,发送端->接收端。(只收不发和只发不收)
- 😉b半双工:允许数据在两个方向上传输,但是在某一时刻,只允许数据在一个方向上传输。实际上是一种能切换方向的单工通信。(能发能收,但不能同时进行)
- 😉c全双工:允许数据同时在两个方向上传输。全双工通信是两个单工通信方式的结合,它要求发送和接受设备都有独立的接收和发送能力。(能发能收,且能同时进行)
😇按照是否带有时钟同步信号分为:
- 同步通信:带时钟同步信号(SCK,SCL....)。传输信息发送设备与信息接收设备需要时钟同步,两者间除数据线互连外,还需要有额外的时钟线进行互连,比如:SPI,IIC通信接口。
- 异步通信:不带时钟同步信号。信息发送设备与接收设备之间只需要数据线连接,无需额外的时钟线互联。虽然双方没有时钟线互联,但是通信双方是需要有约定的,比如说波特率(波特率确定了,每个位传输的时间就确定了),起始位,停止位,奇偶校验位等。接收设备根据约定利用自己的本地时钟对数据进行采样。比如:UART(通用异步收发器),单总线(1-wire)。
常见的串行通信接口:
🤬UART异步通信方式的引脚连接:
TXD:数据输出脚,数据发送。RXD:数据输入脚,数据接收。
二、STM32的串口通信基础
😋STM32的串行通信接口:UART:通用异步收发器 ; USART:通用同步异步收发器
STM32F4XX目前最多支持8个UART,STM32F103支持5个UART。
这里需要注意的是:TX引脚要连接另一个控制器的RX引脚,反之RX亦然。
😋 UART异步通信方式引脚(STM32F407ZGT6的6个串口):芯片数据手册查。
😋UART异步通信方式特点:
- 全双工异步通信。
- 支持小数波特率发生器系统,提供精确的波特率。
- 可配置8倍或者1倍过采样,为速度容差和时钟容差的灵活配置提供可能。
- 可编程的数据字长(8或9位),是否带奇偶校验位。
- 可配置的停止位(1或2位)。
- 可配置使用DMA多缓冲器通信(支持DMA)。
- 单独的发送器和接收器使能位。
- 可触发中断。
- 校验控制,四个错误检测标志。
😋STM32串口通信过程:
数据发送过程:
数据接收过程:
😋STM32串口异步通信需要定义的参数:
- 起始位:比如下面范例,一开始电平为高,到起始位电平拉低告诉接收方要接收有用的数据了。
- 数据位:(8位或9位,9位是带奇偶校验位)低位先行。要发送0011,就要1100这样顺序。
- 奇偶校验位(第9位):奇校验:包括校验位在内的9个数据会出现奇数个1。偶校验同理。
- 停止位:与起始位差不多。
- 波特率设置:决定了每隔多长时间发送一位 / 接收一位。
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;
😋STM32串口框图:
😋串口配置的一般步骤:
- 串口时钟使能:RCC_APBxPeriphClockCmd();
- GPIO 时钟使能RCC_AHB1PeriphClockCmd();
- 引脚复用映射:GPIO_PinAFConfig();
- GPIO端口模式设置:GPIO_Init();模式设置为:GPIO_Mode_AF
- 串口参数初始化:USART_Init();
- 开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤):
- NVIC_Init(); USART_ITConfig();//使能相关中断
- 使能串口:USART_Cmd();
- 编写中断处理函数:USARTx_IRQHandler();
- 串口数据收发:void USART_SendData();//发送数据到DR
- uint16_t USART_ReceiveData()//从DR读取接收到的数据
- 串口传输状态获取:FlagStatus USART_GetFlagStatus();
- void USART_ClearITPendingBit();
😋NVIC配置中断优先级
我们在串口接收信息时采用了触发中断事件,所以要配置一下串口中断的优先级:
void Usart_Init(u32 bound) { // 结构体定义变量 GPIO_InitTypeDef GPIO_Type_USART; //GPIO结构体 USART_InitTypeDef USART_Type; //串口结构体 NVIC_InitTypeDef NVIC_InitStruct; //中断结构体 // 串口时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // GPIO时钟使能 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); // 引脚复用映射--将PA9和PA10引脚连接到串口1的硬件 GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); // GPIO端口模式设置 GPIO_Type_USART.GPIO_Pin=GPIO_Pin_9 | GPIO_Pin_10; GPIO_Type_USART.GPIO_Mode=GPIO_Mode_AF;//复用功能模式 GPIO_Type_USART.GPIO_OType=GPIO_OType_PP; //推挽输出 GPIO_Type_USART.GPIO_PuPd= GPIO_PuPd_UP;//上拉 GPIO_Type_USART.GPIO_Speed=GPIO_Speed_100MHz; //100MHZ // 初始化GPIO GPIO_Init(GPIOA,&GPIO_Type_USART); // 串口参数配置 //配置串口1相关参数:波特率、无校验位、8位数位、1位停止位 USART_Type.USART_BaudRate=bound; //波特率 USART_Type.USART_WordLength=USART_WordLength_8b; //数据长度 USART_Type.USART_StopBits=USART_StopBits_1;//停止位 USART_Type.USART_Mode=USART_Mode_Rx |USART_Mode_Tx;//收发模式 USART_Type.USART_Parity=USART_Parity_No;//无奇偶校验 USART_Type.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//无硬件流控制 // 串口初始化 USART_Init(USART1,&USART_Type); //初始化串口1 // 串口接收中断使能 USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); // 开启空闲中断-使能 USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); // 中断配置 NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn; //启用或禁用的IRQ通道--串口1中断通道 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3; //抢占优先级 NVIC_InitStruct.NVIC_IRQChannelSubPriority=3; //响应优先级 NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;//IRQ通道使能 NVIC_Init(&NVIC_InitStruct); //初始化NVIC寄存器 // 使能串口 USART_Cmd(USART1,ENABLE); //使能串口1 } //USART外设发送一个字符串 void USART1_Send(u8 *arr) { // TC // 0:传送未完成 // 1:传送已完成 while(*arr!='\0') { while(USART_GetITStatus(USART1,USART_FLAG_TXE)!=RESET);//传输数据寄存器空标志 USART_SendData(USART1,*arr); arr++; } } //USART外设接收一个字符串 u8 USART1_Receive(void) { while(USART_GetITStatus(USART1,USART_FLAG_RXNE)!=RESET);//接收中断不空标志 return USART_ReceiveData(USART1); } //字符串处理函数 void USART1_Receive_str(u8 *arr) { while(1) { *arr=USART1_Receive(); if(*arr =='\r' ||*arr =='\n' ) { *arr='\0'; return; } arr++; } } u8 arr[50]={0}; //临时数组 u8 flage; //标志位 //中断服务函数 int len=0; //中断服务函数 void USART1_IRQHandler(void) { //检查标志位 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //空闲中断 { arr[len++]=USART_ReceiveData(USART1); //字符发送 //USART_SendData(USART1,d); //字符接收 } if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //接收中断 { arr[len]='\0'; flage=1; USART_ReceiveData(USART1);//清空缓冲区和中断标志位 } //memset(arr,0,sizeof(arr)); //USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除标志位 }
通过中断接收
在
stm32f10x_it.c
中编写USART1中断源相对应得中断函数,利用了固件库函数中的
USART_ReceiveData(DEBUG_USARTx);接收函数
USART_SendData(DEBUG_USARTx, x);发送函数
USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE);判断标志位函数USART_ReceiveData(USART1);//清空缓冲区和中断标志位
NVIC--向量中断控制器(串口)---在文件misc.c中查找--配置优先级
中断服务函数,在启动文件里找-----写中断服务函数,完事了清除标志位,,配置了中断优先级,主函数必须调用 优先级分组 文章来源:https://www.toymoban.com/news/detail-788928.html
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置优先级分组 2:抢占 2:响应文章来源地址https://www.toymoban.com/news/detail-788928.html
💦重定向
//想要使用printf函数,就必须引用该头文件,否则就会报错 /* 告知连接器不从C库链接使用半主机的函数 */ #pragma import(__use_no_semihosting) //预处理指令 /* 定义 _sys_exit() 以避免使用半主机模式 */ void _sys_exit(int x) { x = x; } /* 标准库需要的支持类型 */ struct __FILE { int handle; }; FILE __stdout; /* */ int fputc(int ch, FILE *stream) { /* 堵塞判断串口是否发送完成 */ while(!USART_GetFlagStatus(USART1,USART_FLAG_TC)); //等待USART_FLAG_TC发送完成标志位置为1 USART_SendData(USART1, ch); return ch; }
到了这里,关于STM32 学习————串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!