单片机 > 单片机程序设计 > 详情

STM32串口使用详解

发布时间:2024-07-16 发布时间:
|

一、关于串口需要了解的几个知识点:

1.波特率:在串行通讯中,数据是按位进行传送的,因此传送速率用每秒钟传送格式位的数目来表示,称为波特率。

   波特率决定了串口传输的速度,1波特=1bps(位/秒)。波特率为9600的话就是1s传输9600位的数据。

   串口的传输与网络等其他的传输有着相似之处,比如常用的wifi,区别在与这些网络的单位是k,只有串口是按位来计数的。

2.单工,半双工,全双工:

   单工:只能一个方向传输

   半双工:可以两个方向传输,但需要分时复用

   全双工:两个方向传输


二、初始化函数

串口的初始化包括以下几部分:

1.时钟初始化:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口1

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//端口复用

初始化的时候一共有三个时钟需要打开,一是IO口,二是串口,三是端口复用。

STM32的串口1用的是PA9、PA10两个端口,串口2用的是PA2、PA3两个端口,因此初始化的时候打开的是GPIOA的时钟。加粗部分是重点,我在用端口的时候就没有加这句话,导致一直不好使。要区分开端口复用和端口重映射,它们完全是两个不同的概念。在51单片机里面,没有端口复用这种用法,某个端口在用作串口的时候也会被当做普通IO来区分,这就像你洗衣服的时候各种衣服都混在一起洗,不加以区分。但是到了STM32的时候,这里有一个复用功能,当你洗浅色衣服的时候,深色的衣服不会进入到这个盆里,这就避免了许多问题,这也是STM32比51高级的地方。


2.IO初始化:

PA9是发送口,在设置的时候注意要相应的设为复用推挽输出。

PA10是接收口,在设置的时候要设为浮空输入,由于是输入,所以没有必要再设置口线翻转速度。

  

 //USART1_TX   GPIOA.9

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;            //PA.9

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;      //复用推挽输出

  GPIO_Init(GPIOA, &GPIO_InitStructure);               //初始化GPIOA.9

3.串口初始化:

USART_InitTypeDef  USART_InitStructure;

USART_InitStructure.USART_BaudRate=9600;//波特率:9600

USART_InitStructure.USART_WordLength=USART_WordLength_8b;//数据长度:8位

USART_InitStructure.USART_StopBits=USART_StopBits_1;//停止位: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_ITConfig(USART1,USART_IT_RXNE,ENABLE);//打开接收中断标志使能

USART_Cmd(USART1,ENABLE);//打开串口使能

USART_ClearFlag(USART1,USART_FLAG_TC);  //清除发送完成标志位

接收中断标志使能是在使用串口接收中断的时候才需要的。在这里清除发送完成标志位是为了避免接收不到第一个数据的情况。


4.中断初始化:

串口的使用方式有两种:查询、中断

一般情况下,发送数据常用查询方式,接收数据常用中断的方式。这和51的串口类似,查询就是判断发送或接收标志位是否被置位,中断是当接收到别的地方发来的数据的时候中断标志位就会溢出从而触发中断,打断主程序去执行中断服务程序。


中断:

与其他的中断设置方式类似,要设置中断分组,响应优先级,抢占优先级。

void NVIC_Configuration(void)

{

    NVIC_InitTypeDef NVIC_InitStructure; //定义结构体

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //设置中断分组

    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口1的中断

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级为0

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级为1

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //打开中断

    NVIC_Init(&NVIC_InitStructure);

}

注意如果用到中断的话还要使能接收中断,这个使能在串口初始化中完成,见上面。


三、串口的使用

1.查询方式:

在主程序里面使用时有两句话:

while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);//等待发送完成标志位被置位

USART_SendData(USART1,xxx);//向上位机发送数据

发送完成标志位被置位说明上一个数据已经发送完成了,于是进行下一个数据的发送


2.中断方式:

中断服务函数的编写套路如下:

void USART1_IRQHandler(void)

{

   if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)//判断接收中断标志位是否置位,即是否有数据发送过来

   {

        xxx=USART_ReceiveData(USART1);//将接收到的数据赋给xxx

        xxxxxxx。。。。       

   }

}

四:完整代码


#if EN_USART1_RX   //如果使能了接收

//串口1中断服务程序

//注意,读取USARTx->SR能避免莫名其妙的错误     

u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.

//接收状态

//bit15,  接收完成标志

//bit14,  接收到0x0d

//bit13~0,  接收到的有效字节数目

u16 USART_RX_STA=0;       //接收状态标记    

  

void uart_init(u32 bound)

{

  //GPIO端口设置

  GPIO_InitTypeDef GPIO_InitStructure;

  USART_InitTypeDef USART_InitStructure;

  NVIC_InitTypeDef NVIC_InitStructure;

   

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟

  

  //USART1_TX   GPIOA.9

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;            //PA.9

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;      //复用推挽输出

  GPIO_Init(GPIOA, &GPIO_InitStructure);               //初始化GPIOA.9

   

  //USART1_RX   GPIOA.10初始化

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;           //PA10

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入

  GPIO_Init(GPIOA, &GPIO_InitStructure);               //初始化GPIOA.10  

 

 

  //Usart1 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寄存器

  

   //USART 初始化设置

  USART_InitStructure.USART_BaudRate = bound;//串口波特率

  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_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断

  USART_Cmd(USART1, ENABLE);                    //使能串口1 

 

 

}

 

 

void USART1_IRQHandler(void)                  //串口1中断服务程序

  {

  u8 Res;

#if SYSTEM_SUPPORT_OS     //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.

  OSIntEnter();    

#endif

  if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)

    {

    Res =USART_ReceiveData(USART1); //读取接收到的数据

    

    if((USART_RX_STA&0x8000)==0)//接收未完成

                    {

        if(USART_RX_STA&0x4000)//接收到了0x0d

                            {

                              if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始

                              else USART_RX_STA|=0x8000;  //接收完成了 

                            }

        else //还没收到0X0D

                            { 

                              if(Res==0x0d)USART_RX_STA|=0x4000;

                              else

                                {

                                  USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;

                                  USART_RX_STA++;

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

                                }    

                            }

    }        

     } 

#if SYSTEM_SUPPORT_OS   //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.

  OSIntExit();                         

#endif

#endif  

/********************************************2018年6月15号*****************************************/


使用查询方式的USART:

设置时钟:

      RCC_APB2Periph_AFIO  功能复用IO时钟

      RCC_APB2Periph_GPIOA  GPIOA时钟

      RCC_APB2Periph_USART1 USART1时钟

 

你可以用 //使能串口1,PA,AFIO总线  RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1,ENABLE);

 

或直接

 

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ALL,ENABLE); //全部APB2外设时钟开启

 

注意USART2的你开启为RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);

 

设置GPIO:

 

   GPIO_InitTypeDefGPIO_InitStructure;

    

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

   GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;  //推挽输出-TX

   GPIO_Init(GPIOA, &GPIO_InitStructure);

 

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入-RX

   GPIO_Init(GPIOA, &GPIO_InitStructure);

 

设置USART:

 

   这里我用的是3.0的库相对于2.0的库来说多了一步,先说2.0

   USART_InitTypeDef USART_InitStructure;

   USART_StructInit(&USART_InitStructure);//装填默认值

   USART_Init(USART1, &USART_InitStructure);//根据USART_InitStruct中指定的参数初始化外设USARTx寄存器

   USART_Cmd(USART1, ENABLE); //启用就好了~!

 

   而3.0的库需要

   USART_InitTypeDefUSART_InitStructure;

   USART_ClockInitTypeDefUSART_ClockInitStructure;

   USART_StructInit(&USART_InitStructure);

   USART_ClockStructInit(&USART_ClockInitStructure);

   USART_ClockInit(USART1,&USART_ClockInitStructure);

   USART_Init(USART1,&USART_InitStructure);

   USART_Cmd(USART1,ENABLE);

//只是多分出了1个USART_ClockInitStructure 我也不知为啥要这样??为了同步异步模式?USART_InitStruct中指定的参数内容为:(2.0的)

typedef struct

{

u32 USART_BaudRate; //USART传输的波特率

u16 USART_WordLength;//一个帧中传输或者接收到的数据位数通常是8

u16 USART_StopBits; //停止位

u16 USART_Parity; //奇偶校验

u16 USART_HardwareFlowControl; //硬件流控制模式使能还是失能

u16 USART_Mode; //指定了使能或者失能发送和接收模式

u16 USART_Clock;//提示了USART时钟使能还是失能

u16 USART_CPOL;//指定了下SLCK引脚上时钟输出的极性

u16 USART_CPHA;//指定了下SLCK引脚上时钟输出的相位

u16 USART_LastBit;

//来控制是否在同步模式下,在SCLK引脚上输出最后发送的那个数据字通常用USART_LastBit_Disable

} USART_InitTypeDef;

 

 

我靠~!太细了~!我只知道(9600,8,n,1)这就够了 其他的统统默认~!

 

  USART_StructInit(&USART_InitStructure);

  USART_ClockStructInit(&USART_ClockInitStructure); //2.0不用这句,这样就设好了好了~!自动为您装填了默认参数。默认的参数如下(3.0的库):

 

voidUSART_StructInit(USART_InitTypeDef* USART_InitStruct)

{

  USART_InitStruct->USART_BaudRate= 9600;

  USART_InitStruct->USART_WordLength =USART_WordLength_8b;

  USART_InitStruct->USART_StopBits= USART_StopBits_1;

  USART_InitStruct->USART_Parity =USART_Parity_No ;

  USART_InitStruct->USART_Mode =USART_Mode_Rx | USART_Mode_Tx;

  USART_InitStruct->USART_HardwareFlowControl =USART_HardwareFlowControl_None; 

}

 

void USART_ClockStructInit(USART_ClockInitTypeDef*USART_ClockInitStruct)

{

 

  USART_ClockInitStruct->USART_Clock =USART_Clock_Disable;

  USART_ClockInitStruct->USART_CPOL= USART_CPOL_Low;

  USART_ClockInitStruct->USART_CPHA= USART_CPHA_1Edge;

  USART_ClockInitStruct->USART_LastBit =USART_LastBit_Disable;

}

 

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

 

当然了你也可以自己设参数,比如这样。

void USART_Configuration(void)

{

 USART_InitTypeDef USART_InitStructure;

 USART_ClockInitTypeDefUSART_ClockInitStructure;

 

 USART_InitStructure.USART_BaudRate =9600;

 USART_InitStructure.USART_WordLength =USART_WordLength_8b;

 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_ClockInitStructure.USART_Clock =USART_Clock_Disable;

 USART_ClockInitStructure.USART_CPOL =USART_CPOL_Low;

 USART_ClockInitStructure.USART_CPHA =USART_CPHA_2Edge;

 USART_ClockInitStructure.USART_LastBit =USART_LastBit_Disable;

 

 USART_ClockInit(USART1,&USART_ClockInitStructure);

 USART_Init(USART1,&USART_InitStructure);

 

 USART_Init(USART1, &USART_InitStructure);

 

 USART_ClockInit(USART1,&USART_ClockInitStructure);

 USART_Cmd(USART1, ENABLE);

 

} ////USART_ClockInitStructure.USART_CPHA=USART_CPHA_2Edge;除了这句以外其他的都和默认的参数一样,二者有啥区别我至今也不太清楚但就一般的应用来说两个都可以正常工作。

 

 

收发的方法:

 1.发送

void USART1_Puts(char *str)

{

   while(*str)

    {

       USART_SendData(USART1, *str++);

       

       while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

    }

}

USART1_Puts("hello-java~! "); //这样就发送了hello-java~!跟C语言的printf不太一样在于 并没有另起一行要用个 这样在终端上好看。

 

 2.接收

u8 uart1_get_data; //存放接受的内容

while(1)

{

  if(USART_GetFlagStatus(USART1,USART_IT_RXNE)==SET)

    {           

      uart1_get_data = USART_ReceiveData(USART1);

      USART1_Puts(" 获取到串口1数据:");

       USART1_Putc(uart1_get_data);

      USART1_Puts(" ");  

    }

 

}

 

查询法的可以看出要不断扫描不是很好,下面介绍中断法。

 

首先配置时钟:这里我拿USART2说事:

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//USART2和USART3都在在APB1上而USART1是在APB2上的

 

设置GPIO:

 

GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_AFIO |ENABLE);

 

    //A2 做T2X

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

   GPIO_Init(GPIOA, &GPIO_InitStructure);

 

    //A3 做R2X

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;

   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

   GPIO_Init(GPIOA, &GPIO_InitStructure);

 

 配置SUART2:

 

 USART_InitTypeDef USART_InitStructure;

 USART_ClockInitTypeDefUSART_ClockInitStructure;

 

 USART_StructInit(&USART_InitStructure);

 USART_ClockStructInit(&USART_ClockInitStructure);

 USART_ClockInit(USART2,&USART_ClockInitStructure);

 USART_Init(USART2,&USART_InitStructure);

 USART_Cmd(USART2, ENABLE);

 

 USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//开启SUART2的接收中断同理还有【看图】然后中断服务程序:这个自己在stm32f10x_it.c添加就可以了。

 

void USART2_IRQHandler(void)

{

 //接收中断

 if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)

 {

  USART_ClearITPendingBit(USART2,USART_IT_RXNE);

  Uart2_Get_Data=USART_ReceiveData(USART2);

  Uart2_Get_Flag=1;

 }

 

 //溢出-如果发生溢出需要先读SR,再读DR寄存器则可清除不断入中断的问题[牛人说要这样]

 if(USART_GetFlagStatus(USART2,USART_FLAG_ORE)==SET)

 {

  USART_ClearFlag(USART2,USART_FLAG_ORE); //读SR其实就是清除标志

  USART_ReceiveData(USART2);    //读DR

 }

}

 

然后在main里检测Uart2_Get_Flag

 

if(Uart2_Get_Flag)

  {

   Uart2_Get_Flag=0;

   USART2_Puts(" 2获取到串口2数据:");

   USART2_Putc(Uart2_Get_Data);

   USART2_Puts(" ");

  }


关键字:STM32  串口使用 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
STC89C52RC单片机的NRF24L01无线通信程序.收发一体