串口通信是一种通过串行接口进行数据传输的通信方式。在串口通信中,数据是以位的形式逐个传输的,通常使用RS-232、RS-485、USB等接口标准。串口通信可以用于连接计算机和外部设备、嵌入式系统之间的通信,常见的应用包括串口打印机、串口调制解调器、串口通信设备等。串口通信的优点是传输距离远、成本低、稳定可靠,但传输速度相对较慢。
通用同步异步收发器(USART)能够灵活地与外部设备进行全双工数据交换,满足外部设备对工业标准NRZ异步串行数据格式的要求。USART通过小数波特率发生器提供了多种波特率。
它支持同步单向通信和半双工单线通信;还支持LIN(局域互连网络)、智能卡协议与IrDA (红外线数据协会)SIR ENDEC规范,以及调制解调器操作(CTS/RTS)。而且,它还支持多处理器通信。通过配置多个缓冲区使用DMA可实现高速数据通信。
1. 异步或同步传输:USART可以以异步模式或同步模式进行数据传输。异步传输使用起始位和停止位确定数据帧的开始和结束,而同步传输使用外部时钟信号进行同步。
2. 支持多种数据格式:USART支持多种数据格式,包括数据位数(通常为8位)、校验位(奇偶校验或无校验)和停止位数(通常为1位或2位)的配置。
3. 双工通信:USART支持全双工通信,可以同时进行数据的发送和接收。
4. 可编程波特率:USART允许用户根据需要设置波特率,以适应不同的通信速率要求。
5. 中断支持:USART提供中断功能,可以在数据接收或发送完成时触发中断,以便及时处理数据。
6. 多个通信模式:USART可以配置为主模式或从模式。在主模式下,USART可以控制通信的时序和流程,而在从模式下,USART将根据外部设备的控制进行通信。
7. 缓冲器支持:USART通常具有接收和发送缓冲器,用于存储接收到的数据和待发送的数据。
8. 硬件流控制:一些USART支持硬件流控制功能,包括使用RTS(请求发送)和CTS(清除发送)信号进行数据流控制。
接口通过三个引脚从外部连接到其他设备(请参见USART框图)。任何USART双向通信均需要至少两个引脚:接收数据输入引脚(RX)和发送数据输出引脚(TX)。
RX:接受数据输入引脚是指串行数据输入引脚。过采样技术可区分有效输入数据和噪声,从而用于恢复数据。
TX:发送数据输出引脚,如果关闭发送器,该输出引脚模式由其I/O端口决定。如果使能了发送器但没有待发送数据,则TX引脚处于高电平。在单线和智能卡模式下,该I/O用于发送和接收数据(USART电平下,随后在SW_RX上接收数据)。
USART框图如下:
可通过对USART_CR1寄存器中的M位进行编程来选择8位或9位的字长(请参见USART字符框图)。TX引脚在起始位工作期间处于低电平状态。在停止位工作期间处于高电平状态。
空闲字符可理解为整个帧周期内电平均为“1”(停止位的电平也是“1”),该字符后是下一个数据帧的起始位。
停止字符可理解为在一个帧周期内接收到的电平均为“O”。发送器在中断帧的末尾插入1或2个停止位(逻辑“1”位)以确认起始位。
发送和接收由通用波特率发生器驱动,发送器和接收器的使能位分别置1时将生成相应的发送时钟和接收时钟。
USART字符框图如下所示:
可以在控制寄存器⒉的位13和位12中编程将随各个字符发送的停止位的数量。
1个停止位。这是停止位数量的默认值。
2个停止位。正常USART模式、单线模式和调制解调器模式支持该值。
0.5个停止位。在智能卡模式下接收数据时使用。
1.5个停止位。在智能卡模式下发送和接收数据时使用。
空闲帧发送将包括停止位。
m=0时,中断发送是10个低电平位,然后是已配置数量的停止位;m=1时,中断发送是11个低电平位,然后是已配置数量的停止位。无法传送长中断(中断长度大于10/11个低电平位)。
USART可配置的停止位框图如下:
USART中断请求框图如下:
USART中断事件被连接到相同的中断向量(请参见USART中断映射图)。
发送期间:发送完成、清除以发送或发送数据寄存器为空中断。
接收期间:空闲线路检测、上溢错误、接收数据寄存器不为空、奇偶校验错误、LIN断路检测、噪声标志(仅限多缓冲区通信)和帧错误(仅限多缓冲区通信) 如果相应的使能控制位置1,则这些事件会生成中断。USART中断映射图如下所示:
1.开启串口时钟和GPIO时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
2. GPIO端口模式设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
3.PA9和PA10要设置为复用功能
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //PA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);//PA10复用为USART1
4. 串口参数初始化:设置波特率,字长,奇偶校验等参数
串口初始化是调用函数USART_Init来实现,各个参数配置如下:
USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;
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); //初始化串口
5. 使能串口
USART_Cmd(USART1, ENABLE); //使能串口
6. 串口数据发送与接收函数
(1)数据发送函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
(2)数据接收函数
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
7. 串口状态
(1)读取串口状态的函数是:
FlagStatus USART_GetFlagStatus(USART_TypeDef*USARTx, uint16_t USART_FLAG);
(2)判断读寄存器是否非空(RXNE)
USART_GetFlagStatus(USART1,USART_FLAG_RXNE);
(3)判断发送是否完成(TC)
USART_GetFlagStatus(USART1,USART_FLAG_TC);
8. 开启中断并且初始化NVIC,使能相应中断
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //响应优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);//根据指定的参数初始化VIC寄存器
同时,我们还需要使能相应中断,使能串口中断的函数原型是:
void USART_ITConfig(USART_TypeDef*USARTx,uint16_t USART_IT,FunctionalState NewState);
接收到数据的时候(RXNE读数据寄存器非空),我们要产生中断时函数为:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断,接收到数据中断
发送数据结束的时候(TC,发送完成)要产生中断:
USART_ITConfig(USART1,USART_IT_TC,ENABLE);
9. 获取相应中断状态
使用的函数是
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
调用这个函数来判断到底是否是串口发送完成中断:
USART_GetITStatus(USART1, USART_IT_TC);
10. 中断服务函数
void USART1_IRQHandler(void);
#include "usart.h"
/*****加入以下代码,支持printf函数,而不需要选择use MicroLIB*****/
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
void _ttywrch(int ch)
{
ch = ch;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1, ch);
return ch;
}
/**********************printf support end**********************/
#endif
void USART1_Init(u32 bound){
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能A口时钟--RCC
RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 , ENABLE ); //初始化串口1的时钟--RCC
//初始化引脚PA9/10--复用推挽输出--GPIO
GPIO_InitTypeDef GPIO_InitStruct={0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; //复用
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;//引脚
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed;//25MHZ翻转速度
GPIO_Init( GPIOA, &GPIO_InitStruct);
//引脚映射USART1上--GPIO
GPIO_PinAFConfig( GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig( GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
//初始化串口:--USART
USART_InitTypeDef USART_InitStruct={0};
USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;
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); //初始化串口
//配置中断--USART
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能串口1的接收中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //使能串口1的空闲中断
//配置NVIC--MISC
NVIC_InitTypeDef NVIC_InitStruct = {0};
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; //37主控芯片头文件stm32f4xx.h
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //开启串口1的中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3; // 优先级 默认
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3; // 响应
NVIC_Init(&NVIC_InitStruct);
//开启串口CMD--USART
USART_Cmd(USART1, ENABLE);
}
//发送--先等待前一个发送完成,再发送下一个 -- USART
void Usart1_Send(char ch)//发送一个字节函数{
while( USART_GetFlagStatus( USART1, USART_FLAG_TC ) != SET )//死等发送完成
{
;
}
USART_SendData(USART1, ch);//发送一个字节
}
//接收--先等接收完成,返回接收数据 -- USART
char Usart1_Receive(void){
char ch=0;
while( USART_GetFlagStatus( USART1, USART_FLAG_RXNE ) != SET )//死等接收完成
{
;
}
ch=USART_ReceiveData( USART1 );
return ch;
}
Usart1struct Usart1dat={0};
//中断服务函数--启动文件(.s--CMSIS)无返回值,无形参 //0 0011 0000 1 1帧 '0'
void USART1_IRQHandler(void){
//判断是谁产生的中断--USART
if(USART_GetITStatus(USART1, USART_IT_RXNE)== SET){
Usart1dat.Usart1_Rbuf[Usart1dat.Usart1_Rlen++] = USART_ReceiveData( USART1 );
if(Usart1dat.Usart1_Rlen >=256) Usart1dat.Usart1_Rlen = 0;
}
if(USART_GetITStatus(USART1, USART_IT_IDLE)== SET){ //空闲中断接收完成
Usart1dat.Usart1_Rbuf[Usart1dat.Usart1_Rlen] = '\0'; //变成字符串
Usart1dat.Usart1_Rflag = 1; //接收完成标记位置1
USART_ReceiveData(USART1); // 清除空闲中断标志
}
}
#ifndef USART_H
#define USART_H
#include "stm32f4xx.h"
#include "stdio.h"
typedef struct{
char Usart1_Rbuf[256];
int Usart1_Rlen;
char Usart1_Rflag;
}Usart1struct;
extern Usart1struct Usart1dat;
void USART1_Init(u32 bound);
void Usart1_Send(char ch);
char Usart1_Receive(void);
#endif
#include "systick.h"
#include "usart.h"
#include "string.h"
#include "led.h"
int main(){
SsyTick_Init(168); //延时初始化
LED_Init(); // LED初始化
UASRT1_Init(9600);
printf("我已进入程序,请启动吧!\r\n");
while(1){
printf("已启动,请输入数据吧!\r\n");
if(Usart1dat.Usart1_Rflag == 1){
printf("%s\r\n",Usart1dat.Usart1_Rbuf);
if(strcmp(Usart1dat.Usart1_Rbuf, "ledlon") == 0{
LED1 = 0; // 输出低电平,亮灯
}
else if(strcmp(Usart1dat.Usart1_Rbuf, "ledoff") == 0){
LED1 = 1; //灭灯
}
}
}
}
所以我直接使用下面代码,/*****加入以下代码,支持printf函数,而不需要选择use MicroLIB*****/
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
void _ttywrch(int ch)
{
ch = ch;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1, ch);
return ch;
}
/**********************printf support end**********************/
#endif
中断服务函数是在相应的中断事件发生时由系统自动调用的函数。上面usart1.c中给出的函数USART1_IRQHandler是用于处理USART1的中断事件的中断服务函数。函数声明中void表示该函数不返回任何值。
程序优先处理部分,通过优先级排序
1、优先级
中断会打断主循环的代码,那如果多个中断同时发生呢?这时候就需要一个东西来约定—>优先级 M4 ---3种
优先级分为三种:(数字越小越高)比如: 1和5 1的优先级比5高
(1)自然优先级(普通会员)----天生的
ST工程师设定的优先级,不可修改。 给了固定的标号
A(自然优先级45)< B(自然优先级40)
(2)响应优先级(VIP会员)----可以修改
用户自定义的优先级
A(响应优先级2)> B(响应优先级3),C(响应优先级为1)
假设事件A和事件B同时发生,管理员(NVIC)通知内核优先处理事件A,如果这个时候事件C发送了,那么处理完事件A后先处理事件C,最后再处理事件B。
//不打断正在发生的中断事件,后续中断事件都需按优先级进行排队
(3)抢占优先级(SVIP会员,—>专属客户)----可以修改
用户自定义的优先级,抢占优先级 > 响应优先级
A (抢占优先级2)> B(抢占优先级4), C(抢占优先级为1)
在处理事件A的过程中,发生了事件B,这个时候事件A不会被打断。如果这个时候发送了事件C,那么事件A将会被打断,优先执行事件C。
//会打断正在发生的中断事件,后续中断事件都需按优先级进行排队
如果抢占优先级一样呢?对比响应优先级,如果响应优先级还是一样呢?对比自然优先级,自然优先级必然是不一样的。
2、优先级分组 抢占 响应优先级 最多能设置多少呢?
NVIC控制器的优先级使用8个二进制位表示,分成两部分表示,分别是抢占优先级和响应优先级(也称子优先级),这8个比特位用于设置中断源的优先级,可以有8种分配方式。Cortex-M允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,但是最少不能低于3位。ST的工程师在设计的时候只用了四个位,这四个位可以分成5组不同的组合 11 11
第0组:所有4位用于指定响应优先级,无抢占优先级。 0~15=16种
第1组:最高1位用于指定抢占优先级,最低3位用于指定响应优先级。
第2组:最高2位用于指定抢占优先级,最低2位用于指定响应优先级。 4 4
第3组:最高3位用于指定抢占优先级,最低1位用于指定响应优先级。 8 2
第4组:最高4位用于指定抢占优先级,无响应优先级。
注:系统复位后默认组0
在学习USART串口通信需要理解基本概念、掌握配置和初始化、熟悉数据收发和中断处理,并在实际应用中进行调试和验证。通过不断的学习和实践,可以掌握USART串口通信并应用于嵌入式系统和通信设备中。
注意:本人所写文章均用于记录自己的学习过程!!!