什么是串口(详细介绍)


一.串口 

    什么是串口? 
    UART : Universal Asynchronous Receiver/Transmitter 
            通用异步收发器 
    USART:Universal Synchronous/Asynchronous Receiver/Transmitter
            通用同步/异步收发器 
            
        那么什么是同步?什么是异步? 
        通信双方有没有共同的时钟线来作为同步时钟使用 
        如果有那么就是同步通信 如果没有那么就是异步的通信 
        
        时钟信号可以作为通信时的同步信号,比如: 
        1.在时钟线下降沿的时候 改变信号     -->发送方发送数据 
        2.在时钟线上升沿的时候 保存信号不变     -->接收方接收数据

        同步:发送方发送数据之后 等待接收方发回响应之后 才发下一个数据包 
        异步:发送方发送数据之后 不等待接收方发回响应 接着发下一个数据包

    
    串口是单片机中最为常见 也是最简单的串行数据传输协议。
    串口只需要两根数据线 就可以实现全双工通信
    两根数据线分别为: 
        Tx : 发送数据端  用于向对方发送数据 
        Rx :  接收数据段  用于接收对方发送过来的数据

    比如:两个设置之间要通信的话 接线如下
            设备A            设备B 
             Tx    ------------->Rx 
             Rx <-------------Tx 
             VCC ------------ VCC
             GND ------------ GND
             
    全双工通信: 两个设备的接收端和发送端是互相独立的 互不干扰的 
                 两个设备可以同时收/发
                 
                还有其他的通信方式 : 单工(设备只能发或者只能收 只有一根数据线)
                                      半双工(设备不能同时收/发)
                                      
    串行: 数据的传输只有一根“电线” 每次就只能传送1bit数据  
           当多个数据发送的时候 也只能1bit 1bit地发送 
           A --------  B    
           比如: 我要发送0x5c  
                   0101 0000 
            就是把这一个字节地每一个bit  一个bit接着1个bit地发送出去  
            
            与之对应地就是并行,简单来说就是两个设备之间有多根线 可以同时发送
            
    
    但是有问题就是:比如我要发个bit1 过去 此时我把对应地线拉高 ,拉高多久就可以了呢? 
                    还有你这个电线肯定是接入完整地电路了 就算我不发数据 电线上也有一个电平状态 
                    那么我怎么知道此时电平状态代表数据呢?
    那么我们就需要一个协议了,协议就是双方约定好地传输方式 --》串口协议

二.串口协议 

    作用: 规定了串口发送和接收数据地方式 必须以帧(frame)为单位
    
        1帧(frame) = 1 start bit(起始位) + 5~9bit的数据位 + 0/1校验位 + (0.5,1,1.5,2)stop bit(停止位)
    
        起始位: 一个周期的低电平信号 
                所以串口的数据发送线Tx在空闲(idle)的时候应该要永远保持高电平。    
    
        5~9bit数据位: 通信的正文 具体是发送多少bit 由双方协商 
                       并且是先发送最低位(LSB)最后发送最高位(MSB)
                       
        1个校验位: 表示是否需要检验 
        
            0bit空间 没有校验位 
            1bit空间 有校验
                        奇校验: 要保证前面的数据位加上校验位的1的个数是奇数个 
                        偶校验: 要保证前面的数据位加上校验位的1的个数是偶数个
    
                        比如: 
                                9bit 发送0x5c (0101 1100)
                                奇校验: 0 0 1 1 1 0 1 0 x=1 
                                偶校验: 0 0 1 1 1 0 1 0 x=0 
                                
    
        0.5-2个停止位: 持续的高电平
                        具体持续多久高电平 由双方约定 
                        0.5个周期 1个周期 1.5个周期 2个周期...
                        
                    
        但是由于UART异步串口 没有时钟进行同步 因此光靠帧格式传输数据还是不能准确收发 
        因此我们有另外一个东西来保证传输准确 
        
            Baudrate:波特率 : UART的传输速率 决定1Frame传输周期 
            常见的波特率有 9600bps (bits Per second) 
            波特率的设置收发双方必须一样
     
        UART协议: 帧格式 + 波特率
    


三. 物理层标准  

    串口有不同的分类: 
    TTL level UART: TTL电平串口
            TX 数据发送端口 
            RX 数据接收端口
            VCC 电源端口(+)
            GND 接地端口(-) 
            
    还有常见的分类比如:RS-232 RS-422 RS-485
    不同的电器标准的串口 引脚的个数也不一样 但是数据线RX/TX是一定存在的 
    
    不同电器标准的串口的区别如下: 
                        TTL Uart            RS-232          RS-422        RS-485 
    高电平         3.3V/5V            -3v~-15v         +2v              +1.5v
    低电平         0V                     +3v~+15v        -2v              -1.5v
    信号           单端信号              单端信号         差分信号     差分信号 
    传输长度     <2m                    <15m             <1200m       <1200m
        
        

四.STM32F4xx串口控制器

    参考《stm32f4xx参考手册》678页 UART框图
    从图中来看呢 串口不仅有TX RX两根线 还有两根用于硬件流控的线
    这两根线存在的作用是: 有时候发送方通过TX往外发送数据的时候  
                            如果对方还没有准备好接收 发送的数据肯定就被丢弃了 
                            所以在硬件上加了两根硬件流控的信号
                            
    RTS:(Require To Send 发送请求) 为输出信号 用于指示本设备准备好可以接收数据 
         低电平有效 低电平说明被设备可以接收数据 
    CTS:(Clear To Send 发送允许) 为输出信号 用于判断是否可以向对方发生发送数据 
         低电平有效 低电平说明本设备可以向对方发送数据
    
    接线方式如下:
        A             B             
        RTS ------>    CTS 
        CTS <------    RTS
    
    当然不一样要用RTS/CTS 
    在图的下方有一个比特率发生器(USART_BRR) 主要是用来控制数据的收发速率的 
    另外在途中有有两种寄存器:
     CR(Control Register):控制寄存器 用来控制串口的一些行为 
     SR(Status Register):状态寄存器 用来指示串口控制器的一些状态  711页左右
    
    串口收发数据的流程: 
    
    a.从读(接收数据)的角度来说 
        
        外部设备的TX ---> 我的RX --> 接收移位寄存器 --> 接收数据寄存器(RDR) --> CPU读取  
        
    b.从发(发送数据)的角度来说
    
        CPU写数据 -->发送数据寄存器(TDR) -->发送移位寄存器 --> 我的TX --> 对方的RX 
    
    这个数据收发的过程 会产生很多标志!!! 
    我们重点讲解几个标志: 
    
    RXNE: Rx Data Register Not Empty  接收数据寄存器非空标志 
        如果这个标志位被置为1 就表示接收数据寄存器(RDR)中有数据了 
        此时CPU应该要尽快地去读取RDR以获得数据
        
    TXE : Tx Data Register Empty 发送数据寄存器为空标志 
        如果这个标志被置为1 表示发送寄存器为空!!!表示可以写数据了。
        请问 你觉得如果这个标志位被置为1 能够表示数据发送出去了吗? 
        不行! 因为    数据有可能还在发送移位寄存器中!                        
    
    TC : Transmit Complete 发送完成标志 
        如果这个标志被置为1 表示数据发送完成! 
        数据发送完成的意思是 发送数据寄存器中没有数据 发送移位寄存器中也没有数据了 数据已经都通过TX引脚发送到对面了
    
    
    注意:在发送数据之前 必须要确保TDR寄存器为空(TXE被设置) 否则上一个发送的数据就有可能还没有发送完成 那么就会被覆盖 
          接收数据呢 需要等RXNE标志被设置  才能去接收 。
          因此一般使用串口中断来完成数据接收。
          另外还有一些在CR寄存器中的中断使能位 
          
          
    RXNEIE:串口接收数据寄存器不为空中断使能  
            如果该标志位被置为1 那么当串口接收数据寄存器不为空的时候 
            就会产生一个串口中断 
            
    TXEIE:发送数据寄存器为空中断使能 
            如果该标志位被置为1 那么当串口发送数据寄存器不为空的时候 
            就会产生一个串口中断 
            
    TCIE :发送完成中断使能 
            如果该标志位被置为1 那么当串口数据都发送完成后 
            就会产生一个串口中断


    注意: 一个串口控制器 只对应一个中断处理函数 但是一个串口他的多种情况都可以引起中断
           所以在对应的中断处理函数中 需要对不同的情况做出不同的处理
           
          

五.STM32F4xx代码

    串口配置步骤: 
        
        串口的TX和RX引脚是GPIO的功能复用而来的 
        开发板的三个串口 都可以由跳线帽选择不同的用途 
        
        USART1: 
            1-3 和 2-4(跳线帽全接左边)作为调试/烧写串口(通过CH340转USB后可以与PC相连)
            3-5 和 4-6(跳线帽全接右边)表示作为P4的外接串口使用
            
        USART2 和USART3 类似
        如果想把他们用作外接串口的话 就把对应的两个跳线帽接到右边
        
        所以在使用串口的时候 先根据用途 对跳线帽进行短接 
        然后再通过程序写代码
    
    //USART1_TX  PA9 
    //USART1_RX  PA10
    
    0.串口对应的GPIO引脚的配置 
        a.使能GPIO分组时钟 
        
        b.初始化GPIO 
            GPIO_Init() -->AF模式
            
        c.配置GPIO具体复用成哪个功能
            GPIO_PinAFConfig
    
    1.使能USART的外设时钟 
    
    RCC_xxxPeriphClockCmd()
    xxx就表示你这个外设接在哪根总线上 不同的串口可能位于不同的总线上面    APB1 or APB2 
    
    
    2.初始化USART 
    
        void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
    
        参数列表: 
                USARTx: 具体你要初始化的串口编号 
                            USART1 
                            ...
                    
                USART_InitStruct:指向你配置串口信息的结构体 
                    
                    typedef struct
                    {
                      uint32_t USART_BaudRate; //指定串口通信的波特率   是一个整数 通信双方必须要保持一致
                                                //常见的有9600 或 115200 bps

                      uint16_t USART_WordLength; //指定数据帧数据位的长度 也即是传输字长  
                                                //USART_WordLength_8b 无校验8bit数据位 《---
                                                //USART_WordLength_9b 有校验9bit数据位

                      uint16_t USART_StopBits; //指定停止位长度 
                                               //USART_StopBits_0_5 
                                               //USART_StopBits_1            1个周期的停止位
                                               //USART_StopBits_1_5
                                               //USART_StopBits_2

                      uint16_t USART_Parity;   //指定校验方式 
                                               //USART_Parity_Odd     奇校验
                                               //USART_Parity_Even  偶校验 
                                               //USART_Parity_No    不要校验   <---
                     
                      uint16_t USART_Mode;//指定串口的收发模式  
                                          //USART_Mode_Rx 接收模式 只接受数据 
                                          //USART_Mode_Tx 发送模式 只发送数据 
                                          // USART_Mode_Rx | USART_Mode_Tx   收发模式(全双工)

                      uint16_t USART_HardwareFlowControl;//指定硬件流控 
                                            //USART_HardwareFlowControl_CTS     
                                            //USART_HardwareFlowControl_RTS 
                                            //USART_HardwareFlowControl_RTS_CTS 
                                            //USART_HardwareFlowControl_None  不需要硬件流控 <---
                      
                    } USART_InitTypeDef;
    
    
    3)中断配置  
    
        1. 中断源的控制 
            产生串口的中断的事件或标志有很多 比如: 
                TXE --> 串口中断 
                RXNE --> 串口中断
                ...
                TC    -->串口中断  
                
            这些事件需要“中断控制位使能”才能产生中断 
            怎么去配置呢? 
                
                void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
                
                参数列表: 
                        USARTx: 你要配置哪个串口产生中断
                                    USART1
                                    USART2
                                    ...
                        
                        USART_IT:你配置串口的哪种具体事件产生中断
                                    USART_IT_TXE        : 表示配置    发送数据寄存器为空的时候 就可以产生一个中断
                                    USART_IT_RXNE        :表示配置  当接收数据寄存器不为空的时候 就可以产生一个中断 
                                    USART_IT_TC         :表示配置  当发送完成的时候 就可以产生一个中断
                                    ...
                        
                        NewState: 使能或者禁止中断
                                    ENABLE 
                                    DISABLE 
            
        
        
        2.    NVIC的控制
        
            串口中断使能(需要配置NVIC) 
            
                线:USART1_IRQn
                ...
                
                
    4)使能串口 
    
        void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
        
        参数列表: 
                USARTx: 你要具体操作的串口 
                            USART1 
                            USART2 
                            ... 
                            
                NewState: 使能/禁止 
                            ENABLE  
                            DISABLE 
    
    
    
    5)串口数据的收发  
    
        一般来说 在串口通信中 接收部分几乎都是由中断来完成的 
        那么中断处理函数名字如下: 
        
        void USART1_IRQHandle(void)
        {
            //有多个事件(RXNE TXE TC...)可以引起中断
            //所以 你在串口中断处理函数中 一般要判断到底是什么事件引起了这次串口中断 再进行对应的处理 
            if(USART_GetITStatus(USART1,USART_IT_RXNE)  == SET  ) //RXNE事件产生了串口中断
            {
                //去读取串口收到的数据 
                ...
                
                
                //清除中断标志 USART_ClearITPendingBit()
                
            }
            else if(xxx)
            {
                ...
            }
            ...
        
        }
    

        获取串口中断标志: 
            
            FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
            ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT) 
            
            参数列表: 
                    USARTx: 你要查看哪个串口的中断标志 指定具体的串口
                    
                    USART_IT or USART_FLAG : 你要查看串口的具体哪个中断标志 指定具体的中断标志
            
            
            返回值: 
                    SET : 1  表示查询的对应的事件产生了 
                    RESET:0  表示查询的对应的事件没有产生

        清除串口中断标志: 
        
            void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG); 
            void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

        串口接收数据: 从指定的串口接收一个字节数据 
        
            uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
            
            参数列表:     
                    USARTx : 你要从哪个串口接收数据 
                    
            返回值:     
                    会把接收到的数据返回给你  
                    返回从串口接收到的数据


        串口发送数据: 
        
            void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
            
            参数列表: 
                     USARTx: 你要通过哪个串口发送数据
                     
                     Data: 你要发送的数据   1个字节

            
        注意:在发送数据之前必须要确保TDR为空 如果没有为空的话 不能发送下一个字节 
        接收是被动的  我们并不知道什么时候有数据过来需要接收 所以在中断服务函数中接收 
        而发送是主动的 由我们自己决定什么时候发送数据出去 所以不用中断实现发送 
        而自定义串口发送函数 
        
        /*
            USART_SendDatas:通过指定的串口把sendbuf里面的数据 发送n个字节出去 
            参数列表:         
                    @USARTx :指定串口编号 
                    @sendbuf :指向你要发送的字符串 
                    @n : 你要发送的字节个数
        */
        void USART_SendDatas(USART_TypeDef* USARTx , uint8_t * sendbuf , uint8_t n)
        {
            int i ;
            for(i=0;i<n;i++)
            {
                //当TDR不为空的时候 我就等待 当TDR为空的时候 我就发送一个字节
                while(USART_GetFlagStatus(USARTx , USART_FLAG_TXE) == RESET) ;
                USART_SendData(USARTx,sendbuf[i]);
            }
        
        }
            

            
        配置串口1(USRAT1跳线帽全接左边) 实现与PC机(串口助手XCOM)双向通信


    在STM32中可以将printf重定向到串口: 
    一般把串口1作为调试串口。
    步骤: 
        1.初始化USART1 
        2.在调用的文件处添加stdio.h 同时keil需要勾选<options>--><target>--><USE MicroLIB>
        3.printf实际上是调用fputc这个函数来实现输出的 
            所以我们需要重新改写fputc这个函数如下
            int fputc(int c, FILE * stream)
            {
                USART_SendData(USART1,c & 0xff);
                while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET );
                return 0;
            
            }
            
    之后就可以使用printf函数 并且可以在串口1作为调试串口中看到输出结果
        
        见工程文件usart.c
    

#include "usart.h"
/*
    串口1的初始化函数
        @baudrate:串口传输的波特率
*/
void Usart1_Init(int baudrate)
{
    //*
        1.串口GPIO配置
    */
    //使能GPIO分组时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);

    //≥ı ºªØGPIO
    GPIO_InitTypeDef p;
    p.GPIO_Pin    	=   GPIO_Pin_9 | GPIO_Pin_10;
    p.GPIO_OType 	=   GPIO_OType_PP;
    p.GPIO_Mode  	=   GPIO_Mode_AF;
    p.GPIO_PuPd  	=   GPIO_PuPd_NOPULL;
    p.GPIO_Speed 	=   GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&p);

    //配置GPIO复用功能
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
    
/*
        2.USART配置
    */
    //使能USART分组时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);

    //初始化配置USART
    USART_InitTypeDef u;
    u.USART_BaudRate           	=   baudrate;
    u.USART_HardwareFlowControl 	=   USART_HardwareFlowControl_None;
    u.USART_Mode              	=   USART_Mode_Rx | USART_Mode_Tx;
    u.USART_Parity              	=   USART_Parity_No;
    u.USART_StopBits            	=   USART_StopBits_1;
    u.USART_WordLength         	=   USART_WordLength_8b;
    USART_Init(USART1,&u);
    
     /*
        3.中断配置
    */
    //中断控制位使能
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);

    //配置NVIC
    NVIC_InitTypeDef n;
    n.NVIC_IRQChannel       	=   USART1_IRQn;
    n.NVIC_IRQChannelCmd    	=   ENABLE;
    n.NVIC_IRQChannelPreemptionPriority 	=  2;
    n.NVIC_IRQChannelSubPriority        	=  2;
    NVIC_Init(&n);

     /*
        4.开启串口
    */
    USART_Cmd(USART1,ENABLE);
}

/*
    通过USARTx指定的串口将SendBuf中n个字节的数据发送出去
*/
void USART_SendDatas(USART_TypeDef* USARTx,uint8_t* SendBuf,uint8_t n)
{
    int i;
    for(i = 0;i < n;i++)
    {
        while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET)
            ;
        USART_SendData(USARTx,SendBuf[i]);
    }
}


/*
    串口1的中断服务函数
*/
void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)//RXNE事件产生
    {
         //去读取数据
        unsigned char buf = USART_ReceiveData(USART1);
        if(buf == '0')
        {
           Led_Ctrl_Lib(LED1,LED_OFF);
        }
        else if(buf == '1')
        {
            Led_Ctrl_Lib(LED1,LED_ON);
        }
        else
        {
            //...
        }
        //清除中断标志
        USART_ClearITPendingBit(USART1,USART_IT_RXNE);
    }
}

int fputc(int c,FILE *stream)
{
	USART_SendData(USART1,c & 0xFF);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
   	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值