在学习UART之前,我们先来了解一下单片机与外围设备之间的通信:

单片机与外围设备之间的信息交换和传输我们称为通信。过去通信方式有两种:并行通信和串行通信。

并行通信:
定义:并行通信是指利用多条传输线将一个数据的各位同时传送。
传输方式:传输一个字节(8个位)的数据时,并口是将8个位一字排开,分别在8条连接线上同时传输。
特点:传输速度块,适用于短距离通信。

缺点:虽然,并行通信传输速度快,但是由于,线与线之间存在电磁干扰,会导致数据错误。而且由于线比较多,PCB布线比较麻烦,所以并行通信不常用,而串行通信用得比较广泛。

串行通信:
定义:串行通信是指利用一条传输线将数据一位位地顺序传送。
传输方式:传输一个字节(8个位)的数据时,串口是将8个位排好队,逐个地在1条连接线上传输。
特点:通信线路简单,适用于远距离通信和上、下机通信。

缺点:传输数度比较慢。(因为它是一位一位的传输的)

串行通信根据传输数据方式来划分的话:有同步方式和异步方式:

同步方式:即发送方和接收方的时钟频率要求一直,所以双方就需要在同步时钟线的牵引下,收、发数据需要同时进行。

异步方式:双方在相同的波特率的情况下,发送器只管发送,接收器只管接收,收发互不干涉。

串行通信根据收发数据方式来划分的话:有单工、半双工、全双工:

单工通信:数据只能单方向传输。

半双工通信:数据传输上支持双方向传输,但是不能同时进行双向传输,在同一时刻,某一端只能进行发送或者接收。

全双工通信:双工是指数据同时在两个方向上传输,是两个单工通信的结合,要求发送设备和接收设备同时具有独立的接收和发送能力。

 

波特率:

所以,双方需要约定好波特率。

我们现在来学习UART

          UART,是通用异步收发传输器(USART则是通用同步/异步收发传输器),既然是“器”,显然,它就是个设备而已,要完成一个特定的功能的硬件,它本身并不是协议。那么它要完成什么功能呢?它的最基本功能,是串行数据和并行数据之间的转换。注意:UART是TTL电平。注意:串口通信是一对一的通信,而不是像I^2C那样一对多的通信。

注意:UART、RS232、RS485,它们实际上都是串口上面的属性,只不过它们的功能不一样。UART的功能是将串行转并行,而RS232、RS485则是一种电平标准。

双工:

单工:

USART与UART的区别:
        1,USART(同步,异步可选择)(全双工,半双工可选择)
        2,UART(异步)(全双工,半双工可选择)

串口通信的作用:
        1,用于设备间的通信
        2,可以用于设备调试----printf的打印

CPU片上外设,UART模块分析:

发送数据寄存器:它接收CPU从数据总线上送来的并行数据。并加以保存。
        发送移位寄存器:它接收从发送数据寄存器送来的并行数据,并给数据加上起始位和停止位(即:帧头和帧尾),然后以发送时钟的速率把数据逐位移出,即将并行数据转换为串行数据输出。

接收数据寄存器:它从输入移位寄存器中接收并行数据,由CPU(或者DMA)取走。
        接收移位寄存器:它以接收时钟的速率把出现在串行数据输入线上的数据逐位移入,当数据装满后,并行送往接收数据寄存器,即将串行数据转换成并行数据,然后去掉起始位和停止位,然后将数据传给接收数据寄存器。

UART在发送数据时,是低位先行(即:低位先发送)还是高位先行?接收数据呢?

由图知:红线部分是从移位寄存器的尾部出发的,而移位寄存器的尾部是数据的低位,所以发送数据的时候是低位先行(即:先发送低位,再发送高位)

接收数据同理:红线部分是接移位寄存器的头部,先将低位接收,然后低位在移位寄存器中逐步往后移动,所以接收数据的时候也是低位先行(即:先接收低位,再接收高位)

UART内部总结构:

UART,一帧数据的格式:

串口数据包:11bit
        1. 起始位:低电平 1bit
        2. 数据位:8个位,低位先行  8bit
        3. 奇偶校验位:1个bit
            奇校验:指的是数据位中1的个数。如果数据位中1的个数位偶数,那么该位为1。如果数据位中1的个数位奇数,那么该位为0。
            偶校验:指的是数据位中1的个数,如果数据位中1的个数位偶数,那么该位为0。如果数据位中1的个数位奇数,那么该位为1。
        4. 停止位:高电平  1bit
        
        如果串口没有奇偶校验位:一共10个bit(1+8+1 = 10)

包含奇偶效验位:数据长度为9位(不包含起始位和停止位)

注意:正因为包含了奇偶效验位,所以数据为9位长度,所以可知,奇偶效验位属于数据位中的一员。

不包含奇偶效验位:数据长度为8位(不包含起始位和停止位)

所以我们设置数据位长度的时候,如果需要使用奇偶效验位,那么就设置为9位长度,不使用则设置为8位。

奇效验就是,如果8位数据位的1为偶数,那么再加上奇偶效验位中的1,就可以将1的个数凑成奇数。如果8位数据位的1为奇数,那么再加上奇偶效验位中的0,就可以将1的个数凑成奇数。

偶效验就是,如果8位数据位的1为偶数,那么再加上奇偶效验位中的0,就可以将1的个数凑成偶数。如果8位数据位的1为奇数,那么再加上奇偶效验位中的1,就可以将1的个数凑成偶数。

由此可知:为什么奇校验中,如果数据位中1的个数位偶数,那么该位为1。如果数据位中1的个数位奇数,那么该位为0。因为要把奇偶效验位一并算在数据位上。

        但是,一般我们不用奇偶效验位。因为,每一次接收发送一帧数据,都要对数据中的1的个数进行判断,这样传输速度就会慢一些。通常我们的校验方式都会加在通信协议上面,每个字节的校验我们一般不用。

UART函数介绍:

UART初始化函数:我们可以看到,初始化函数的结构体成员有很多,但是并不是我们都要去设置的,根据异步模式还是同步模式来设置,如果是异步模式,我们只需要设置前面6个成员就行了。如果是,同步模式,则全部成员都需要设置。

第一个成员:设置波特率,它可以直接赋值。如:直接赋值为115200

第二个成员:设置数据位长度。设置为9位长度,则是包含奇偶效验位。设置为8位长度,则是不包含奇偶效验位。

第三个成员:设置停止位

第四位成员:是否使用奇偶效验位。

第5个成员:是否使用硬件流控模式

第6个成员:使能发送/接收模式

串口助手串口设置:

这个是串口助手的串口设置,我们可以看到我们程序中需要对UART的设置与串口助手中对串口设置的对象是一模一样的。

UART外设使能函数。使能UART1还是使能UART2。

UART发送数据函数,注意:它一次只能发送一个字节,而不能多个字节一起发。因为数据寄存器只发送一个字节。

接收数据函数:它有一个返回值,返回最近接收到的一个字节数据。

获取某一个事件发生的标志位函数:通过标志位函数,检测到标志位,然后进行相应的操作。我们主要是用这4个标志位。发生则返回SET,没发生则返回RESET。注意:标志位需要自己手动去清空。

USART_FLAG_TXE:当发送数据寄存器中的数据已经取完了,该标志位就会被置1,从而引发该事件的中断。所以,其实USART_FLAG_TXE就是用来标志一个事件的,通过它的值可以知道该事件有没有发生(即发送数据寄存器中的数据有没有被取走)。当数据写完后,发送数据寄存器中的数据就会被移位寄存器取走,此时发送数据寄存器里面为空。

USART_FLAG_TC:当发送移位寄存器中的1个字节数据已经通过TX脚一位一位的移出去后,1个字节数据被发送完成,该标志位就会被置1,从而引发该事件的中断。所以,其实USART_FLAG_TC就是用来标志“发送移位寄存器中的数据有没有全部发送出去”这件事的。

USART_FLAG_RXNE:表示已经接收到了完整的数据。即接收到的数据已经从移位寄存器放到数据寄存器中,并被CPU(或者DMA)取走了。注意:此时接收数据寄存器里面的数据不会被清空,它需要保留该数据,因为可能后面需要去读这个数据。

编程步骤:

实现stm32与PC串口通信,编程步骤:
        1,打开时钟---GPIOA,串口1,AFIO 
        2,GPIO初始化
            ---GPIO_Pin_9(TX)
            ---复用推挽输出
            ---速度--2MHZ
            
            ---GPIO_Pin_10(RX)
            ---浮空输入(接收到的高低电平由片外外设来决定,所以用浮空输入模式)

3,串口初始化
            ---波特率---115200
            ---数据位数---8bit
            ---停止位---1bit
            ---奇偶校验---无
            ---硬件流控---无
            ---模式---发送和接收使能
        4,使能串口

注意:USART1、GPIOA、AFIO(复用功能)都在APB2总线上面。

编写程序:

功能:MCU从PC上面接收到的数据,又通过MCU发送到PC上面显示。

void USART1_Config(void)
{
    GPIO_InitTypeDef  GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    // 1,打开时钟---GPIOA,串口1,AFIO 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1,ENABLE);

// 2,GPIO初始化
    GPIO_InitStruct.GPIO_Pin    =GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode   =GPIO_Mode_AF_PP;        //推挽输出
    GPIO_InitStruct.GPIO_Speed  =GPIO_Speed_2MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin    =GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode   =GPIO_Mode_IN_FLOATING;        //浮空输入
    GPIO_Init(GPIOA,&GPIO_InitStruct);

// 3,串口初始化
    USART_InitStruct.USART_BaudRate      =115200;        //波特率
    USART_InitStruct.USART_HardwareFlowControl  =USART_HardwareFlowControl_None;        //硬件流控失能
    USART_InitStruct.USART_Mode            =USART_Mode_Rx|USART_Mode_Tx;     //注意:这里使能RX/TX是可以用或操的
    USART_InitStruct.USART_Parity               =USART_Parity_No;        //奇偶效验位
    USART_InitStruct.USART_StopBits             =USART_StopBits_1;        //停止位
    USART_InitStruct.USART_WordLength           =USART_WordLength_8b;        //数据位长度
    USART_Init(USART1,&USART_InitStruct);

/*清空标志位,因为很多标志位一开始都是默认置上的(即:置1),所以我们需要在初始化的时候,就将标志位清空。如果没有这一步清空标志位,那么printf第一个字母就会打印不出来,所以我们需要在传输数据之前,就清空标志位。如果不清空标志位,那么printf函数打印的第一个字节还没来得即发送给PC,就会被第二个字节给覆盖掉了。*/

USART_ClearFlag(USART1, USART_FLAG_TC|USART_FLAG_TXE);

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

/*功能:MCU从PC上面接收到的数据,又通过MCU发送到PC上面显示。这样,我们就可以即可以验证接收功能是否成功,也可以验证发送功能是否成功*/

int main(void)
{
   
    Systick_Config(72);
    USART1_Config();
    printf("HELLO!\n");

while(1){

/*由于接收数据需要等移位寄存器接收数据,然后将数据移到接收数据寄存器中去,这些操作需要花费时间,如果我们不等数据接收完就马上进行下一个数据的接收,那么就会导致后面的数据将前面的数据给覆盖掉,这样的话数据就出现了错误*/
        if(SET==USART_GetFlagStatus(USART1,USART_FLAG_RXNE)){
            USART_ClearFlag(USART1,USART_FLAG_RXNE);        //清空标志位
            USART_SendData(USART1,USART_ReceiveData(USART1));  //发送数据给电脑,注意:是一个字节一个字节的发送
        }
 
    }
}

注意:

1.烧录程序的时候,记得要把串口助手给关掉,否则串口被占用,无法烧录程序。

2.发送/接收都是一个字节一个字节的发送/接收的。

3.我们一开始就需要清空标志位。因为很多标志位一开始都是默认置上的(即:置1),所以我们需要在初始化的时候,就将标志位清空。如果一开始不清空标志位,那么第一个字节还没来得即发送,就会被第二个字节给覆盖掉了。

4.标志位,只是一个值,它是用来给我们写while阻塞或者中断的。接收/发生标志位置不置位,单片机都不会去自动阻塞,如果我们需要阻塞,则需要我们自己手动去写while()循环在main函数中进行手动阻塞(这也体现出了它的灵活性)。

printf重定向

我们写单片机上面写printf函数,并不能将数据打印出来,因为它没有屏幕,嘿嘿。

那么,将数据传输到PC上面,在PC上面显示不就行了吗?这是个好主意。但是,在PC上面要怎么才能打印出数据来?

我们可以借助上面学的串口UART,利用UART将数据传输到串口助手上面,将数据显示出来。

但是,又个问题。printf函数内部并没有UART函数的配置,那么我们怎么将printf与UART相结合呢?

        我们可以重写printf函数。实际上prinf函数是一个库函数,每次执行printf函数的时候,它就会调用内部的 fputc()函数。fputc()函数将数据打印在屏幕上面。所以,我们需要重新编写fputc()函数,将其内部改写成UART相关操作。

如果在单片机中需要使用printf,需要做两件事情
        1,需要重写fputc----重定向
            int fputc(int ch,FILE *f)
            {
                USART_SendData(USART1, ch);
                while(SET!=USART_GetFlagStatus(USART1, USART_FLAG_TC))

;
                USART_ClearFlag(USART1, USART_FLAG_TC);
                return ch;
            }
        2,使用微库
            keil---->魔术棒---->target---->USE MicroLIB打勾
            
    12》printf第一个字母打印不出来----串口配置函数
        USART_ClearFlag(USART1, USART_FLAG_TC|USART_FLAG_TXE);

编写程序:

#include "stdio.h"

int fputc(int ch,FILE *f)
{
    USART_SendData(USART1, ch);
    while(SET!=USART_GetFlagStatus(USART1, USART_FLAG_TC))    //如果一个字节数据没有发送完,那么就阻塞在这里

;
    USART_ClearFlag(USART1, USART_FLAG_TC);        //传输完数据后,手动清空标志位
    return ch;  
}

int main(){

printf("HELLO!\n");        //此时串口助手上面就打印出来HELLO!了

return 0;

}

注意:不能在中断服务函数里面中用printf函数,这样会把MCU搞晕的。

为什么可以重写printf()函数?

因为在启动文件中,printf()函数是WEAK属性

weak属性也就是,如果没有重写该函数,那么就执行库中提供的函数。如果重写了该函数的话,就执行重写后的函数。

stm32f103——串口UART相关推荐

  1. micropython stm32f030_STM32F0单片机快速入门六 用库操作串口(UART)原来如此简单

    1.从 GPIO 到 UART 前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断.接下来我们介绍的也是最常用的一个模块,串口(UART). 串口可以说是最古老,而且生命力最强的 ...

  2. 串口UART串行总线协议

    串口UART 串行端口是异步的(不传输时钟相关数据),两个设备在使用串口通信时,必须先约定一个数据传输速率,并且这两个设备各自的时钟频率必须与这个速率保持相近,某一方的时钟频率相差很大都会导致数据传输 ...

  3. Esp8266 进阶之路25【高级篇】深聊下esp8266的串口 Uart 通讯中断编程,为您准备好了 NONOS 版本 和 RTOS 系统的串口驱动文件。(附带Demo)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 序号 SDK版本 内容 链接 1 nonos2.0 搭建 ...

  4. TQ2440裸奔程序串口UART的PC机按键测试程序

    //========================================= // NAME: main.c // DESC: TQ2440串口UART测试程序 //============ ...

  5. 痞子衡嵌入式:嵌入式里串口(UART)自动波特率识别程序设计与实现

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是嵌入式里串口(UART)自动波特率识别程序设计与实现. 串口(UART)是嵌入式里最基础最常用也最简单的一种通讯(数据传输)方式,可以说 ...

  6. CSR8675 使用串口 UART 收发功能

    CSR8675 使用串口 UART 收发功能 CSR8675 实现 UART 功能有两种方式,一种是托管连接,另一种是直接连接. 托管连接:不直接操作 Stream,通过 VM 层创建 Source ...

  7. 【Linux应用】串口UART编程

    1.前言 通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称作UART) 是一种串行异步收发协议,应用十分广泛.UART工作原理是将数据 ...

  8. STM32f407与STM32F103 串口采用DMA收发数据配置方法的异同

    最近有个项目需要用到STM32F407ZET6这款芯片,其中有一个串口收发数据的应用.因为之前有用过STMF32F103ZET6通过DMA收发数据的方案,所以我打算移植之前的代码实现这个功能,STM3 ...

  9. DSP在SYS/BIOS下串口(UART)接收之环形队列

    众所周知串口收/发数据是以字节为单位的位传输通信协议. 当串口接收数据按固定数据长度接收:则可能会由于传输过程中出现丢数据,发送端少发数据或多发数据导致接收错位无法正确获取数据. 为了解决数据接收错位 ...

最新文章

  1. 大型云原生项目在数字化企业落地过程解密
  2. wxWidgets:剪贴板 wxWidgets 示例
  3. C++数据结构struct
  4. IE haslayout的理解与bug修复
  5. Linux(CentOS)中常用软件安装,使用及异常——Zookeeper, Kafka
  6. 01-复杂度1 最大子列和问题 (20 分)
  7. 【转】一键将Web应用发布到云-Azure Web App!
  8. P5735 【深基7.例1】距离函数(python3实现)
  9. 计算机在机械制造领域中的应用论文,高科技在机械制造工艺中的应用论文
  10. 反编译Silverlight项目
  11. Python: SystemError: Unknown opcode
  12. (2)css的复合选择器与特性
  13. PMP试题 | 每日一练,快速提分 8.5
  14. xtrabackup 原理详解
  15. 新华三培训2---HSRP/VRRP/GLBP
  16. MapReduce: 大规模集群上的简化数据处理
  17. 学简单python好学吗_python好学吗
  18. Error: The project seems to require yarn but it‘s not installed
  19. 全球区块链农业技术平台Dimitra与 Morpheus AMA回顾
  20. 通过response返回json数据到前端

热门文章

  1. 知识付费,也逃不过“焦虑”的本质
  2. 【IPTV】Hybrid Video解决方案概念与价值
  3. 大佬总结的电磁兼容知识,EMC整改六步走,看完感觉太简单了点
  4. Android 9.0系统源码_SystemUI(一)SystemUI的启动流程
  5. 表格上方插入文字 html,word表格上方怎么输入不了文字
  6. 转:人只能领导他喜欢的人
  7. 使用pygame绘制文字
  8. iOS推送语音播报(类似支付宝收款提醒)
  9. 基于ibeacon的博物馆智能导览系统解决方案
  10. 数到三就删除游戏(python)