精华内容
下载资源
问答
  • 入党申请书格式 单片机串口通讯协议 约定 将计算机方称为上位机将单片机方统称下位机 数据格式均采用二进制码 通信过程描述 通信采用主从式一问一答非问莫答避免下位机同时发送数据产生冲突 联机过程: 1)握手 每次...
  •  与之前一样,首先我们来了解单片机串口相关的寄存器。 SBUF 寄存器:它是两个在物理上独立的接收、发送缓冲器,可同时发送、接收数据,可通过指令对SBUF 的读写来区别是对接收缓冲器的操作还是对发送缓冲器的操作...
  • 文章我们主要讲单片机串口工作原理和如何通过程序来对串口进行设置,以及根据所给出的实例实现与PC 机通信
  • 单片机串口通信原理图,比较经典的连接方式,电阻电容的值也有标注
  • 51单片机串口通信原理讲解.pdf
  • 51单片机串口通信原理讲解

    千次阅读 多人点赞 2019-10-24 23:54:50
    51单片机串口通信 今天研究了一下51单片机的串口通信,使用的单片机是普中科技开发板,但实际上所运用到的硬件和其他品牌单片机都相同,没有区别,总结一下,自己的理解和看法。 通信原理 通信原理大致分为串行和...

    51单片机串口通信


    今天研究了一下51单片机的串口通信,使用的单片机是普中科技开发板,但实际上所运用到的硬件和其他品牌单片机都相同,没有区别,总结一下,自己的理解和看法。

    通信原理

    通信原理大致分为串行和并行两种方法,各有优缺点,也不再这里赘述了,使用到的是串行通信的方法,简单介绍一下串行通信的原理,上图

    两个设备,一根互传线,每次传一组数据,总长度不一定8位,由51单片机内部设定来决定。设备间通信有许多接口方式,我用的是51上的串行接口,挂图:
    在这里插入图片描述
    SBUF:是指串行口中的两个缓冲寄存器,一个是发送寄存器,一个是接收寄存器,在物理结构上是完全独立的,但地址是重叠的。它们都是字节寻址的寄存器,字节地址均为99H,
    TXD:Transmit(tx) Data; RXD: Receive(rx) Data;
    两个口通过缩写记一下,我经常记不住(丢人),所以上面的那一个SBUF是发送,下面的是接收寄存器,发送或者接收的数据将暂时储存于里面,编程时直接赋值就行,TH1和TL1是时钟的配置系统,主要用于控制波特率,及每秒发送的总位数。(调试时一定要对应自己设置的波特率)

    控制寄存器SCON:内部结构下图,主要用于设置串口工作方式、接发送控制,以及状态位的控制
    在这里插入图片描述
    SM0和SM1是控制工作方式下图,控制每组总数据(起止位+数据位)的位数。移位则是一个脉冲一个一个脉冲的发送输入输出数据。
    SM2多机通信控制位,方式2和3时。SM2控制RB8是否会触发RI中断,SM2=1时R8=1激活中断(中断将数据读走),R8=0则不激活;SM2=0则失去控制作用。不论RB是否为0,RI都能激活,方式0时,SM2必须为0;方式1时,SM2=0,接受到停止位,R1中断就打开。
    REN允许串行接受位,REN=1,则打开接受,否则不能接受数据。
    TB,方式2和3中才用到,是奇偶效验位
    RB,在方式2、3中还是做奇偶效验位,在方式1中做数据停止位的存放位,用来将RI自动置1,启动中断。
    在这里插入图片描述
    PCON:用于控制波特率是否加倍,及SMOD=1,波特率加倍。复位时SMOD=0;
    在这里插入图片描述
    好了,大致通信原理讲到这里,下面到使用讲解!

    程序编写

    步骤:
    1、确定TMOD(计数器)工作方式
    2、配置TH1和TL1初值
    3、配置SCON、PCON
    4、打开中断允许位(总中断、串口中断),配置中断(中断内主要是SBUF读取发送数据),配置TCON打开中断即TR1置1.

    上代码:作用串口通信输入值,再返回

    include<reg52.h>
    typedef unsigned char u8;
    
    void ready(void)
    {
    	TMOD = 0X20;
    	TH1=0XFF;
    	TL1=0XF9; //波特率9600
    	SCON=0X50;//0101 0000
    	PCON=OX80;//1000 0000
    	EA=1; //打开总中断
    	ES=1; //打开串口中断,相当于ET1 ET0
    	TR1=1;//打开计数器,当其溢出时会给SMOD一个脉冲,接受和读取数据,达到设置波特率作用
    		  //当接受到停止位时RI=1触发中断
    }
    
    void main(void)
    {
    	ready();
    	while(1);
    }
    
    void time1(void) interrupt 4
    {
    	static u8 result;
    	result=SBUF;//进入配置中断,读取数据
    	RI=0;
    	SBUF=result;//将数据输入到SBUF里面发送
    	while(!TI);//等待发送完毕
    	TI=0;
    }
    

    一个简单的串口通信收发完成了

    展开全文
  • 文章目录单片机串口通信原理和控制程序 单片机串口通信原理和控制程序 我们前边学串口通信的时候,比较注重的是串口底层时序上的操作过程,所以例程都是简单的收发字符或者字符串。在实际应用中,往往串口还要和...


    单片机串口通信原理和控制程序

    我们前边学串口通信的时候,比较注重的是串口底层时序上的操作过程,所以例程都是简单的收发字符或者字符串。在实际应用中,往往串口还要和电脑上的上位机软件进行交互,实现电脑软件发送不同的指令,单片机对应执行不同操作的功能,这就要求我们组织一个比较合理的通信机制和逻辑关系,用来实现我们想要的结果。

    本节所提供程序的功能是,通过电脑串口调试助手下发三个不同的命令,第一条指令:buzz on 可以让蜂鸣器响;第二条指令:buzz off 可以让蜂鸣器不响;第三条指令:showstr ,这个命令空格后边,可以添加任何字符串,让后边的字符串在 1602 液晶上显示出来,同时不管发送什么命令,单片机收到后把命令原封不动的再通过串口发送给电脑,以表示“我收到了„„你可以检查下对不对”。这样的感觉是不是更像是一个小项目了呢?

    对于串口通信部分来说,单片机给电脑发字符串好说,有多大的数组,我们就发送多少个字节即可,但是单片机接收数据,接收多少个才应该是一帧完整的数据呢?数据接收起始头在哪里,结束在哪里?这些我们在接收到数据前都是无从得知的。那怎么办呢?

    我们的编程思路基于这样一种通常的事实:当需要发送一帧(多个字节)数据时,这些数据都是连续不断的发送的,即发送完一个字节后会紧接着发送下一个字节,期间没有间隔或间隔很短,而当这一帧数据都发送完毕后,就会间隔很长一段时间(相对于连续发送时的间隔来讲)不再发送数据,也就是通信总线上会空闲一段较长的时间。于是我们就建立这样一种程序机制:设置一个软件的总线空闲定时器,这个定时器在有数据传输时(从单片机接收角度来说就是接收到数据时)清零,而在总线空闲时(也就是没有接收到数据时)时累加,当它累加到一定时间(例程里是 30 ms)后,我们就可以认定一帧完整的数据已经传输完毕了,于是告诉其它程序可以来处理数据了,本次的数据处理完后就恢复到初始状态,再准备下一次的接收。那么这个用于判定一帧结束的空闲时间取多少合适呢?它取决于多个条件,并没有一个固定值,我们这里介绍几个需要考虑的原则:第一,这个时间必须大于波特率周期,很明显我们的单片机接收中断产生是在一个字节接收完毕后,也就是一个时刻点,而其接收过程我们的程序是无从知晓的,因此在至少一个波特率周期内你绝不能认为空闲已经时间达到了。第二,要考虑发送方的系统延时,因为不是所有的发送方都能让数据严格无间隔的发送,因为软件响应、关中断、系统临界区等等操作都会引起延时,所以还得再附加几个到十几个 ms 的时间。我们选取的 30 ms 是一个折中的经验值,它能适应大部分的波特率(大于1200)和大部分的系统延时(PC 机或其它单片机系统)情况。

    我先把这个程序最重要的 UART.c 文件中的程序贴出来,一点点给大家解析,这个是实际项目开发常用的用法,大家一定要认真弄明白。

    /*****************************Uart.c 文件程序源代码*****************************/
    #include <reg52.h>
    
    bit flagFrame = 0; //帧接收完成标志,即接收到一帧新数据
    bit flagTxd = 0; //单字节发送完成标志,用来替代 TXD 中断标志位
    unsigned char cntRxd = 0; //接收字节计数器
    unsigned char pdata bufRxd[64]; //接收字节缓冲区
    
    extern void UartAction(unsigned char *buf, unsigned char len);
    
    /* 串口配置函数,baud-通信波特率 */
    void ConfigUART(unsigned int baud){
        SCON = 0x50; //配置串口为模式 1
        TMOD &= 0x0F; //清零 T1 的控制位
        TMOD |= 0x20; //配置 T1 为模式 2
        TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值
        TL1 = TH1; //初值等于重载值
        ET1 = 0; //禁止 T1 中断
        ES = 1; //使能串口中断
        TR1 = 1; //启动 T1
    }
    /* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */
    void UartWrite(unsigned char *buf, unsigned char len){
        while (len--){ //循环发送所有字节
            flagTxd = 0; //清零发送标志
            SBUF = *buf++; //发送一个字节数据
            while (!flagTxd); //等待该字节发送完成
        }
    }
    /* 串口数据读取函数,buf-接收指针,len-指定的读取长度,返回值-实际读到的长度 */
    unsigned char UartRead(unsigned char *buf, unsigned char len){
        unsigned char i;
        //指定读取长度大于实际接收到的数据长度时,
        //读取长度设置为实际接收到的数据长度
        if (len > cntRxd){
            len = cntRxd;
        }
        for (i=0; i<len; i++){ //拷贝接收到的数据到接收指针上
            *buf++ = bufRxd[i];
        }
        cntRxd = 0; //接收计数器清零
        return len; //返回实际读取长度
    }
    /* 串口接收监控,由空闲时间判定帧结束,需在定时中断中调用,ms-定时间隔 */
    void UartRxMonitor(unsigned char ms){
        static unsigned char cntbkp = 0;
        static unsigned char idletmr = 0;
    
        if (cntRxd > 0){ //接收计数器大于零时,监控总线空闲时间
            if (cntbkp != cntRxd){ //接收计数器改变,即刚接收到数据时,清零空闲计时
                cntbkp = cntRxd;
                idletmr = 0;
            }else{ //接收计数器未改变,即总线空闲时,累积空闲时间
                if (idletmr < 30){ //空闲计时小于 30ms 时,持续累加
                    idletmr += ms;
                    if (idletmr >= 30){ //空闲时间达到 30ms 时,即判定为一帧接收完毕
                        flagFrame = 1; //设置帧接收完成标志
                    }
                }
            }
        }else{
            cntbkp = 0;
        }
    }
    /* 串口驱动函数,监测数据帧的接收,调度功能函数,需在主循环中调用 */
    void UartDriver(){
        unsigned char len;
        unsigned char pdata buf[40];
    
        if (flagFrame){ //有命令到达时,读取处理该命令
            flagFrame = 0;
            len = UartRead(buf, sizeof(buf)); //将接收到的命令读取到缓冲区中
            UartAction(buf, len); //传递数据帧,调用动作执行函数
        }
    }
    /* 串口中断服务函数 */
    void InterruptUART() interrupt 4{
        if (RI){ //接收到新字节
            RI = 0; //清零接收中断标志位
            //接收缓冲区尚未用完时,保存接收字节,并递增计数器
            if (cntRxd < sizeof(bufRxd)){{
                bufRxd[cntRxd++] = SBUF;
            }
        }
        if (TI){ //字节发送完毕
            TI = 0; //清零发送中断标志位
            flagTxd = 1; //设置字节发送完成标志
        }
    }
    

    大家可以对照注释和前面的讲解分析下这个 Uart.c 文件,在这里指出其中的两个要点希望大家多注意下。

    1、接收数据的处理,在串口中断中,将接收到的字节都存入缓冲区 bufRxd 中,同时利用另外的定时器中断通过间隔调用 UartRxMonitor 来监控一帧数据是否接收完毕,判定的原则就是我们前面介绍的空闲时间,当判定一帧数据结束完毕时,设置 flagFrame 标志,主循环中可以通过调用 UartDriver 来检测该标志,并处理接收到的数据。当要处理接收到的数据时,先通过串口读取函数 UartRead 把接收缓冲区 bufRxd 中的数据读取出来,然后再对读到的数据进行判断处理。也许你会说,既然数据都已经接收到 bufRxd 中了,那我直接在这里面用不就行了嘛,何必还得再拷贝到另一个地方去呢?我们设计这种双缓冲的机制,主要是为了提高串口接收到响应效率:首先如果你在 bufRxd 中处理数据,那么这时侯就不能再接收任何数据,因为新接收的数据会破坏原来的数据,造成其不完整和混乱;其次,这个处理过程可能会耗费较长的时间,比如说上位机现在就给你发来一个延时显示的命令,那么在这个延时的过程中你都无法去接收新的命令,在上位机看来就是你暂时失去响应了。而使用这种双缓冲机制就可以大大改善这个问题,因为数据拷贝所需的时间是相当短的,而只要拷贝出去后,bufRxd 就可以马上准备去接收新数据了。

    2、串口数据写入函数 UartWrite,它把数据指针 buf 指向的数据块连续的由串口发送出去。虽然我们的串口程序启用了中断,但这里的发送功能却没有在中断中完成,而是仍然靠查询发送中断标志 flagTxd(因中断函数内必须清零 TI,否则中断会重复进入执行,所以另置了一个 flagTxd 来代替 TI)来完成,当然也可以采用先把发送数据拷贝到一个缓冲区中,然后再在中断中发缓冲区数据发送出去的方式,但这样一是要耗费额外的内存,二是使程序更复杂。这里也还是想告诉大家,简单方式可以解决的问题就不要搞得更复杂。

    /*****************************main.c 文件程序源代码******************************/
    #include <reg52.h>
    sbit BUZZ = P1^6; //蜂鸣器控制引脚
    bit flagBuzzOn = 0; //蜂鸣器启动标志
    unsigned char T0RH = 0; //T0 重载值的高字节
    unsigned char T0RL = 0; //T0 重载值的低字节
    
    void ConfigTimer0(unsigned int ms);
    extern void UartDriver();
    extern void ConfigUART(unsigned int baud);
    extern void UartRxMonitor(unsigned char ms);
    extern void UartWrite(unsigned char *buf, unsigned char len);
    extern void InitLcd1602();
    extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
    extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);
    
    void main(){
        EA = 1; //开总中断
        ConfigTimer0(1); //配置 T0 定时 1ms
        ConfigUART(9600); //配置波特率为 9600
        InitLcd1602(); //初始化液晶
    
        while (1){
            UartDriver(); //调用串口驱动
        }
    }
    /* 内存比较函数,比较两个指针所指向的内存数据是否相同,
    ptr1-待比较指针 1,ptr2-待比较指针 2,len-待比较长度
    返回值-两段内存数据完全相同时返回 1,不同返回 0 */
    bit CmpMemory(unsigned char *ptr1, unsigned char *ptr2, unsigned char len){
        while (len--){
            if (*ptr1++ != *ptr2++){ //遇到不相等数据时即刻返回 0
                return 0;
            }
        }
        return 1; //比较完全部长度数据都相等则返回 1
    }
    /* 串口动作函数,根据接收到的命令帧执行响应的动作
    buf-接收到的命令帧指针,len-命令帧长度 */
    void UartAction(unsigned char *buf, unsigned char len){
        unsigned char i;
        unsigned char code cmd0[] = "buzz on"; //开蜂鸣器命令
        unsigned char code cmd1[] = "buzz off"; //关蜂鸣器命令
        unsigned char code cmd2[] = "showstr "; //字符串显示命令
        unsigned char code cmdLen[] = { //命令长度汇总表
            sizeof(cmd0)-1, sizeof(cmd1)-1, sizeof(cmd2)-1,
        };
    
        unsigned char code *cmdPtr[] = { //命令指针汇总表
            &cmd0[0], &cmd1[0], &cmd2[0],
        };
    
        for (i=0; i<sizeof(cmdLen); i++){ //遍历命令列表,查找相同命令
            if (len >= cmdLen[i]){ //首先接收到的数据长度要不小于命令长度
                if (CmpMemory(buf, cmdPtr[i], cmdLen[i])){ //比较相同时退出循环
                    break;
                }
            }
        }
        switch (i){ //循环退出时 i 的值即是当前命令的索引值
            case 0:
                flagBuzzOn = 1; //开启蜂鸣器
                break;
            case 1:
                flagBuzzOn = 0; //关闭蜂鸣器
                break;
            case 2:
                buf[len] = '\0'; //为接收到的字符串添加结束符
                LcdShowStr(0, 0, buf+cmdLen[2]); //显示命令后的字符串
                i = len - cmdLen[2]; //计算有效字符个数
                if (i < 16){ //有效字符少于 16 时,清除液晶上的后续字符位
                    LcdAreaClear(i, 0, 16-i);
                }
                break;
            default: //未找到相符命令时,给上机发送“错误命令”的提示
                UartWrite("bad command.\r\n", sizeof("bad command.\r\n")-1);
                return;
        }
        buf[len++] = '\r'; //有效命令被执行后,在原命令帧之后添加
        buf[len++] = '\n'; //回车换行符后返回给上位机,表示已执行
        UartWrite(buf, len);
    }
    /* 配置并启动 T0,ms-T0 定时时间 */
    void ConfigTimer0(unsigned int ms){
        unsigned long tmp; //临时变量
        tmp = 11059200 / 12; //定时器计数频率
        tmp = (tmp * ms) / 1000; //计算所需的计数值
        tmp = 65536 - tmp; //计算定时器重载值
        tmp = tmp + 33; //补偿中断响应延时造成的误差
        T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
        T0RL = (unsigned char)tmp;
        TMOD &= 0xF0; //清零 T0 的控制位
        TMOD |= 0x01; //配置 T0 为模式 1
        TH0 = T0RH; //加载 T0 重载值
        TL0 = T0RL;
        ET0 = 1; //使能 T0 中断
        TR0 = 1; //启动 T0
    }
    /* T0 中断服务函数,执行串口接收监控和蜂鸣器驱动 */
    void InterruptTimer0() interrupt 1{
        TH0 = T0RH; //重新加载重载值
        TL0 = T0RL;
        if (flagBuzzOn){ //执行蜂鸣器鸣叫或关闭
            BUZZ = ~BUZZ;
        }else{
            BUZZ = 1;
        }
        UartRxMonitor(1); //串口接收监控
    }
    

    main 函数和主循环的结构我们已经做过很多了,就不多说了,这里重点把串口接收数据的具体解析方法给大家分析一下,这种用法具有很强的普遍性,掌握并灵活运用它可以使你将来的开发工作事半功倍。

    首先来看 CmpMemory 函数,这个函数很简单,就是比较两段内存数据,通常都是数组中的数据,函数接收两段数据的指针,然后逐个字节比较——if (ptr1++ != ptr2++),这行代码既完成了两个指针指向的数据的比较,又在比较完后把两个指针都各自+1,从这里是不是也能领略到一点 C 语言的简洁高效的魅力呢。这个函数的用处自然就是用来比较我们接收到的数据和事先放在程序里的命令字符串是否相同,从而找出相符的命令了。

    接下来是 UartAction 函数对接收数据的解析和处理方法,先把接收的数据与所支持的命令字符串逐条比较,这个比较中首先要确保接收的长度大于命令字符串的长度,然后再用上述的 CmpMemory 函数逐字节比较,如果比较相同就立即退出循环,不同则继续对比下一条命令。当找到相符的命令字符串时,最终 i 的值就是该命令在其列表中的索引位置,当遍历完命令列表都没有找到相符的命令时,最终 i 的值将等于命令总数,那么接下来就用 switch语句根据 i 的值来执行具体的动作,这个就不需要再详细说明了。

    /***************************Lcd1602.c 文件程序源代码*****************************/
    #include <reg52.h>
    #define LCD1602_DB P0
    sbit LCD1602_RS = P1^0;
    sbit LCD1602_RW = P1^1;
    sbit LCD1602_E = P1^5;
    
    /* 等待液晶准备好 */
    void LcdWaitReady(){
        unsigned char sta;
        LCD1602_DB = 0xFF;
        LCD1602_RS = 0;
        LCD1602_RW = 1;
        do {
            LCD1602_E = 1;
            sta = LCD1602_DB; //读取状态字
            LCD1602_E = 0;
        } while (sta & 0x80); //bit7 等于 1 表示液晶正忙,重复检测直到其等于 0 为止
    }
    /* 向 LCD1602 液晶写入一字节命令,cmd-待写入命令值 */
    void LcdWriteCmd(unsigned char cmd){
        LcdWaitReady();
        LCD1602_RS = 0;
        LCD1602_RW = 0;
        LCD1602_DB = cmd;
        LCD1602_E = 1;
        LCD1602_E = 0;
    }
    /* 向 LCD1602 液晶写入一字节数据,dat-待写入数据值 */
    void LcdWriteDat(unsigned char dat){
        LcdWaitReady();
        LCD1602_RS = 1;
        LCD1602_RW = 0;
        LCD1602_DB = dat;
        LCD1602_E = 1;
        LCD1602_E = 0;
    }
    /* 设置显示 RAM 起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */
    void LcdSetCursor(unsigned char x, unsigned char y){
        unsigned char addr;
        if (y == 0){ //由输入的屏幕坐标计算显示 RAM 的地址
            addr = 0x00 + x; //第一行字符地址从 0x00 起始
        }else{
            addr = 0x40 + x; //第二行字符地址从 0x40 起始
        }
        LcdWriteCmd(addr | 0x80); //设置 RAM 地址
    }
    /* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */
    void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
        LcdSetCursor(x, y); //设置起始地址
        while (*str != '\0'){ //连续写入字符串数据,直到检测到结束符
            LcdWriteDat(*str++);
        }
    }
    /* 区域清除,清除从(x,y)坐标起始的 len 个字符位 */
    void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len){
        LcdSetCursor(x, y); //设置起始地址
        while (len--){ //连续写入空格
            LcdWriteDat(' ');
        }
    }
    /* 初始化 1602 液晶 */
    void InitLcd1602(){
        LcdWriteCmd(0x38); //16*2 显示,5*7 点阵,8 位数据接口
        LcdWriteCmd(0x0C); //显示器开,光标关闭
        LcdWriteCmd(0x06); //文字不动,地址自动+1
        LcdWriteCmd(0x01); //清屏
    }
    

    液晶文件与上一个例程的液晶文件基本是一样的,唯一的区别是删掉了一个本例中用不到的全屏清屏函数,其实留着这个函数也没关系,只是 Keil 会提示一个警告,告诉你有未被调用的函数而已,可以不理会它。

    经过这几个多文件工程的练习后,大家是否发现,在采用多文件模块化编程后,不光是某些函数,甚至整个 c 文件,如有需要,我们都可以直接复制到其它的新工程中使用,非常方便功能程序的移植,这样随着实践积累的增加,你会发现工作效率变得越来越高了。

    展开全文
  • 有关串口通信原理性问题的理解(附上字丑的学习笔记) 一、基本概念讲解: 1、串行:数据在一根线上上发送出去,需要采样数据,需要多个时钟周期 优点:占用硬件资源少;抗干扰能力干扰强;传输距离远;应用场合较多...

    有关串口通信原理性问题的理解(附上字丑的学习笔记)

    一、基本概念讲解:

    1、串行:数据在一根线上上发送出去,需要采样数据,需要多个时钟周期
    优点:占用硬件资源少;抗干扰能力干扰强;传输距离远;应用场合较多;
    缺点:数据传输效率慢
    2、并行:有几位数据,在几根线上发送出去,一个时钟周期可以完成一组数据的采样
    优点:数据传输速率快
    缺点:抗高频干扰能力弱;传输距离近;占用硬件资源多;

    3、同步:即有CLK,通过时钟线上的采样点对数据信号进行采样
    4、异步:无CLK,通过采集一段时间的数据,然后对此段的数据解包,还原成最初的数据。直接与之相关的就是速率波特率。
    发送方按照一定的波特率对数据进行发送,然后接收方按照设定好的相对应的波特率接受数据一段时间,然后对这段时间的数据进行解包。这就是异步没有时钟信号的数据传输方法。

    5、单工:只能发送或者接受数据
    6、双工:既可以发送,又可以接受数据。
    双工又分为半双工和全双工
    半双工:在一条线上进行发送和接受数据,发送和接收数据不能同时进行
    全双工:在两条线上分别发送和接收数据,可以同时进行。

    二、引入串口通信

    串口:单片机上经常见到UART(UART即异步全双工的缩写)
    通过异步全双工,我们应该对串口有个大致的了解了吧,就是上面基本概念的组合起来
    即没有时钟信号,是通过约定好的波特率进行采样
    通过两条线分别实现发送和接收数据,也就是我们常见的 Tx Rx

    三、串口通信的最小单元,码元

    1、为什么要有码元?
    因为异步的串口通信读取单bit数据的出错率比较高,所以我们将一组数据打包,构成了一个码元,这样出错率会比较低一些。
    2、码元是怎么构成的?

    起始数据校验结束
    1bit(低电平起始信号)不限位数(不同情况下可以有不同的位数,不过一般是8bit或者9bit)奇(统计1的个数是不是奇数个。1bit)/偶(统计1的个数是不是偶数个。1bit)/零(统计0的个数。1bit)/无(不进行校验,不占数据位)高电平信号(位数不固定在0.5~2.5范围内,如果系统比较稳定,可以取位数少的如0.5,系统不太稳定取位数多的),确保数据同步

    我们最常用的通信方式——8N1
    8:8bit数据
    N:无校验位
    1:1bit终止信号
    另外,附加上1bit的开始信号。

    所以,8N1方式就是一次性发送10bit数据,以10bit数据为1码元

    这里提到了波特率
    波特率的表示是这样的——码元/s
    所以:9600bps按照码元是10bit的话,就是每秒传输9600*10bit数据

    本篇文章侧重理论性的东西,要想看具体的实例教程的话,推荐我的另外两篇文章,链接如下

    STC15F2K60S2串口通信/波特率设置/通信初始化/发送(接收)一个数据教程
    STC15F2K60S2单片机/DHT11/OLED12864/串口通信

    四、附上学习笔记

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 单片机串口通信原理 详细 易于理解 很详细的代码演示 很详细的解说 很有价值的资料 值得一下
  • 单片机串口通信原理和控制程序.pdf
  • 这节我们主要讲单片机串口工作原理和如何通过程序来对串口进行设置,以及根据所给出的实例实现与PC 机通信。 一、原理简介 51 单片机内部有一个全双工串行接口。什么叫全双工串口呢?一般来说,只能接受或只能...
  • 单片机串口通信原理原理

    千次阅读 2016-09-06 14:02:00
    串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行...

    转:http://bbs.elecfans.com/forum.php?mod=viewthread&tid=206905

     
    串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。典型地,串口用于ASCII码字符的传输。
    通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但是不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配:  

     a,波特率:这是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如300波特表示每秒钟发送300个bit。当我们提到时钟周期时,我们就是指波特率例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。 

      b,数据位:这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。

      c,停止位:用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。 
      d,奇偶校验位:在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。

    如果是奇校验,校验位位1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。

    转载于:https://www.cnblogs.com/jikexianfeng/p/5845527.html

    展开全文
  •  不同型号的单片机使用的串口不同,根据需求选择相应的单片机,在有些场合如果单片机如果没有需要的串行通信接口,则可以通过单片机的I/O口进行模拟。  全双工UART(异步串行通信接口)  UART有两种工作状态: ...
  • 这节我们主要讲单片机串口工作原理和如何通过程序来对串口进行设置,以及根据所给出的实例实现与PC 机通信
  • 图上是串口的结构图。 SBUF是数据缓冲寄存器,发送和接收用的是一个地址,但是不用担心冲突,读只能从接收缓冲区,写只能在发送缓冲区里。 寄存器SCON(SM0 SM1 SM2 REN TB8 RB8 RI) SM0和SM1: 工作方式选择(0...
  • 姓名:周崇杰 学号:16040120059 专业:机械设计制造及其自动化转载自:http://blog.csdn.net/a514371309/article/details/73481423...【嵌牛鼻子】:单片机,C语言,串口通信协议【嵌牛提问】:单片机是通过协议进...
  • 51单片机串口通信讲解,从原理、使用方法,程序例程多方面介绍。
  • 51单片机串口通信

    2011-09-30 11:26:17
    关于单片机串口通信原理及源程序代码,讲解非常详细,不可多得
  • 这是之前上传的单片机串口通信原理图对应的PCB板图,请结合原理图看
  • 51单片机串口多机通信原理与编程实现

    千次阅读 多人点赞 2020-06-11 12:15:44
    51单片机串口多机通信 需要用的的寄存器 (了解的可直接跳到下一节) TMOD 定时器/计数器模式控制寄存器 TCON 定时器控制寄存器 SCON 串口控制寄存器 PCON 电源控制位寄存器 IE 中断中断使能寄存器 补充说明,...
  • 单片机 串口 通信 485

    2011-05-09 13:09:25
    基于485方式的单片机与PC机的串口通信 源代码 原理
  • 上讲介绍并应用了单片机内部定时器和中断,并给出了实例。这一讲将介绍单片机上的串口通信。通过该讲,读者可以掌握单片机上串口的工作原理和如何通过程序来对串口进行设置,并根据所给出的实例实现与PC 机通信。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,625
精华内容 3,450
关键字:

单片机串口通信工作原理