单片机串口通信教学

2018-09-03 20:05:17 baidu_24919727 阅读数 839

本文作者:小嗷

微信公众号:aoxiaoji

吹比QQ群:736854977

链接:https://mp.weixin.qq.com/s?__biz=MzU1MTgxNjQyMg==&tempkey=OTcyX0EwKzJvbDFaVXVLdUtOYThUdW9QU3ZDajJzNGxjM2tvbjFxdURMZ2dJNTc5SHM0UmRVLVV5LVBFN3dnWTc4eXpvakF2XzlGZmpPSnZJZ3Y5QXpfRXZZVjRUdVJPdFgzaVNac3Q5NlhkMW1IbXpsWG5iZjVhZGllc3luRWFZRy1HWUFTRWJWWnlYSlc0TDZMVUJsRlJDU1lQTTdKVF9SX2lGU2NiYnd%2Bfg%3D%3D&chksm=7b8adc4c4cfd555a1bd85304c823214b212222999801a631abfc0ddac7f85c92d316d88bec2f#rd


1.前言

本文你会找到以下问题的答案:

  1. 单片机的串口通信
  2. 从零开始开发QT软件思路
  3. 波特率、流控制、奇偶校验位、
  4. C++11中引入的auto

今天,有个做测试的小伙伴,问我上位机什么写?其实,我一开始想没有,快滚。不过,还是用当年的javaSwing写的串口软件,甩他脸算。算了,看看能不能QT利用搞一个串口软件给他,不能就叫他上网自己查。

业务需求:

  1. 使用单片机检测硬件
  2. 再通过单片机其他IO口连接上COM口(串口)
  3. 上传数据给PC端处理结果
  4. 最后,上传到服务器

2.1 我需要怎么做(产品经理的一般套路,看看别人产品):

如下图:

1.串口软件图

2.2 串口查一查英文单词是什么?

2.串口英文

哦!seial port(其实,小嗷早就知道,英文是什么。只是为了配合那个测试小白)

2.3 打开QT软件 -> 欢迎 -> 示例 -> 搜serial,如下

3.搜串口

鼠标移到其中一个小窗框上

4.QSerialPort

英文意思:展示如何使用标准QSerialPort的API,在一个非图形用户界面思路上

从小窗口可以看出没有现成的,都是边边角角。

2.4 先不急打开看看示例,转移目标查QSerialPort的API

网址如下:

http://doc.qt.io/qt-5/qserialport.html#BaudRate-enum

打开图如下:

5.打开网站

大概意思就是提供函数进入串口,如上图得出:

cpp需要导入:#include <QSerialPort>(Header)
pro需要导入:QT += serialport(qmake)

2.5 开始创建项目名QSerialPortTool,一路Next

创建成功后,在pro添加QT += serialport(qmake)

7.添加serialport

2.6 继续看网址的API介绍,发现如下

8.下拉网址

公共类型:

  • BaudRate:波特率(点击BaudRate)

9.波特率

波特率是什么?介绍完API下方有介绍。

  • DataBits:数据位(这个枚举描述使用的数据位的数量)

10.数据位

  • Direction:用法(这个枚举描述了数据传输的可能方向。)

11用法.PNG

小嗷简单说说吧:只能允许输入/只能允许输出/同时允许输入输出(有点意思,小嗷不懂什么是同时输入输出。)

  • FlowControl:这个枚举描述了所使用的流控制。

12.流控制.PNG

大概分为软硬件流控制,-1过时不建议使用。

同理,下方有流控制的解释

  • Parity:奇偶校正(这个枚举描述了使用的奇偶校验方案。)

13.奇偶校正.PNG

同理下方。

  • PinoutSignal:针输出信号?(这个枚举描述了可能的RS-232 pinout信号。)

14.pinout signals.PNG

这参数不太清楚是什么,RS-232指的是:我们台式电脑的9Pin(9针的插头,电子专业俗称COM口),大概是定义COM口的那个针输出信号(小嗷猜的)

  • SerialPortError:串口错误信息(这个枚举描述了串口::error属性所包含的错误。)

15.端口错误API.PNG

  • StopBits:停止位(这个枚举描述了所使用的停止位的数量。)

16.停止位.PNG

用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。


  • BaudRate:波特率(点击BaudRate)
  • DataBits:数据位(这个枚举描述使用的数据位的数量)
  • Direction:用法(这个枚举描述了数据传输的可能方向。)
  • FlowControl:这个枚举描述了所使用的流控制。
  • Parity:奇偶校正(这个枚举描述了使用的奇偶校验方案。)
  • PinoutSignal:针输出信号?(这个枚举描述了可能的RS-232 pinout信号。)
  • SerialPortError:串口错误信息(这个枚举描述了串口::error属性所包含的错误。)
  • StopBits:停止位(这个枚举描述了所使用的停止位的数量。)

再看看我们对标的产品图:

1.串口软件图.PNG

这时,小嗷大概了解的自己要做什么。即:对标产品的图,除了设置接收的编码方式,基本在QSerialPort类中,可以直接调用API函数(如:波特率等)进行相关设置。

在往下就是功能函数:其中,黄色部分有设置COM的序号什么,估计搞软件的时候需要用它来设置COM序号(如:COM 1-256)。

17.在从下面看.PNG

再往下就是重载函数(重载是什么?)

重载,简单说,就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。

18.在往下面看.PNG

好了,该补得都补了,下面第4点就来看看如何实现从零开始实现串口代码部分。

21.波特率意思等.png

3.1 波特率

单片机或计算机在串口通信时的速率。指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如:

每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,

比特率为10位*240个/秒=2400bps
1Bps=8bps
1Mbps=128KBps
下载速度最高为128KBps 

每秒钟传送240个二进制位,这时的波特率为240Bd,比特率也是240bps。

3.2 流控制

用途:数据在传输过程中容易出现数据丢失的现象。

例如:两台计算机通过串口传输数据时,或者台式机与单片机之间进行通信时,可能由于两端计算机的处理速度不同,出现接收端的数据缓冲区已满,而发送端依然继续发送数据,则导致数据丢失。

流控制的出现就是为了解决这种数据丢失的问题。

工作原理:当接收端的数据缓冲区已满,无法处理数据来时,就发出”不再接收”的信号,发送端则停止发送,直到发送端收到”可以继续发送”的信号再发送数据。

计算机中常用的两种流控制分别是硬件流控制(RTS/CTS、DTR/DSR等)和软件流控制(XON/XOFF)。

硬件流控制 :硬件流控制必须将相应的电缆线连上。

硬件流控制常用方式为:RTS/CTS(请求发送/清除发送)流控制和DTR/DSR(数据终端就绪/数据设置就绪)流控制。

当用RTS/CTS流控制时,需将通讯两端的RTS、CTS线对应相连,数据终端设备(如计算机)使用RTS来启动调制解调器或其它数据通讯设备的数据流,而数据通讯设备(如调制解调器)则用CTS来启动和暂停来自计算机的数据流。

这种硬件握手方式的过程为:通过程序为接收端缓冲区大小设置一个高位标志(可为缓冲区大小的75%)和一个低位标志(可为缓冲区大小的25%),当缓冲区内数据量达到高位时,接收端将CTS线置低电平(送逻辑0),当发送端的程序检测到CTS为低后,就停止发送数据,直到接收端缓冲区的数据量低于低位而将CTS置高电平。RTS则用来标明接收设备有没有准备好接收数据。

DTR/DSR流控制的工作方式与RTS/CTS流控制类似,不再进行赘述。

简单讲讲就是
RTS:标明家属有没有准备好钱;
CTS:标明绑匪接不接受钱;
有点意思,和服务器的排他锁思想类似(学过服务器就知道)

3.3 奇偶校验位(Parity)

奇偶校验位(Parity),在数据存储和传输中,字节中额外增加一个比特位,用来检验错误。它常常是从两个或更多的原始数据中产生一个冗余数据,冗余数据可以从一个原始数据中进行重建。不过,奇偶校验数据并不是对原始数据的完全复制。被用在RAID的2、3、4、5级别中。

使用

由于它很简单,所以奇偶校验位用于许多计算机硬件中遇到麻烦时能够重新操作或者通过简单的错误检测就能起到很大作用的场合。例如SCSI总线使用奇偶校验位检测传输错误,许多微处理器的指令高速缓存中也包括奇偶校验位保护。因为指令缓存数据是主内存数据的副本,所以在发现错误的时候能够抛弃错误数据并且重新取回数据。

在串行数据通信中,常用的格式是 7 个数据位、1 个校验位、1 到 2 个停止位。这种格式用方便的 8 位字节巧妙地适应了所有的 7 位 ASCII 字符。也可以用其它的格式表示,8 位数据加上 1 个校验位可以传输任意的 8 位字节数据。

在串行通信中,奇偶校验位通常是由UART这样的接口硬件生成、校验的,在接收方,通过接口硬件中的寄存器的状态位传给 CPU 以及操作系统。错误数据的恢复通常是通过重新发送数据,这个过程通常由如操作系统输入输出程序这样的软件处理的。

奇偶校验块(其实,可以不讲。不过,个人觉得有点意思。涉及硬盘数据恢复原理)

一些冗余磁盘阵列(en:RAID)使用奇偶校验块实现冗余。如果阵列中的一块磁盘出现故障,工作磁盘中的数据块与奇偶校验块一起来重建丢失的数据。

下面每列表示一个磁盘,假设 A1 = 00000111、A2 = 00000101 以及 A3 = 00000000。A1、A2、A3 异或得到的 Ap 等于 00000010。如果第二个磁盘出现故障,A2 将不能被访问,但是可以通过 A1、A3 与 Ap 的异或进行重建:

A1 XOR A3 XOR Ap = 00000101

冗余磁盘阵列

A1 A2 A3

Ap B1 B2

Bp C1 C2

C3 C4 Cp

注意:数据块是格式 A#,奇偶校验块是 Ap。

22.从零开始实现串口代码.png

其实,我们从零开始串口代码,有2条路选:

  1. 直接看官方的例子,用到啥补啥(如什么是波特率,流控?)
  2. 另外一种就是小嗷的方法,先查查相关产品图(对标别的产品。对标这个词,我第一次听是从腾讯工作的一位大水比听到的,我们称它吹比达人),再去看看相关的类,再补补不懂的地方,再反过来对比相关产品,再看看例子。这样做的话,就像我们学完高中三年相关知识,去高考(我们不一定复习完全部内容)。

两者的区别在于:

  1. 记忆的时效性而言1比2快忘记
  2. 1就算做完,心里也是没什么底气(开发软件,很少出现题海战术。就像高一开始,我只做历年真题,难度大。做完试卷,转个弯你未必能转过来【出现bug的时候】)
  3. 1比2优点,网上搜例子运气好的话。直接复制粘贴改一改就完事。就看别人开源给不给力。给力的话,用不了20分钟实现产品【一般像Java/Android/IOS/QT什么自带不能直接完事】。同理,要是别人开源代码挖几个坑,我相信你百分百走着走着掉下去。挖坑的人,基本不会填坑(为啥救你?救你要花钱买绳子吊你上来,再花钱买铲子填坑,耗时耗钱耗力)。
  4. 如果时间允许的话,我还是建议大家选择2.

网址如下:http://doc.qt.io/qt-5/qserialport.html#BaudRate-enum

4.1 点击Qt Serial Port

19.实现代码第一步.PNG

4.2 阅读英文翻译

得到的信息如下:

使用qt中的串口通信的时候需要用到的两个头文件分别为:

#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>

除了加上面两个头文件之外,还需要在工程文件.pro中加下面一行代码(上面搞过):

QT       += serialport

我们一般都需要先定义一个全局的串口对象,记得在自己的头文件中添加上 n :

QSerialPort *serial;

20.实现代码第2步.PNG

4.3 查一查QSerialPortInfo

QSerialPortInfo英文意思大概是串口信息,小嗷也不清楚是什么,查一查如下:

网址:http://doc.qt.io/qt-5/qserialportinfo.html#details

QSerialPortInfo Class:

提供关于现有串口的信息。
使用静态函数生成QSerialPortInfo对象列表。列表中的每个QSerialPortInfo对象都代表一个串行端口,并且可以查询港口名称、系统位置、描述和制造商。QSerialPortInfo类也可以用作QSerialPort类的setPort()方法的输入参数。

21.查一查QSerialPortInfo.PNG

4.4 点击examples(例子)

网址:http://doc.qt.io/qt-5/qtserialport-index.html

22.点击examples

如下信息:

网址:http://doc.qt.io/qt-5/qtserialport-examples.html

23.点击examples(如下信息:).PNG

4.5 点击Blocking Master Example

小嗷其实就是任意点一个例子看看。当然,英文内容,小嗷还是读懂大概意思。

24.点击Blocking Master Example.PNG

纳里,就是第一个搜索图的项目代码的讲解,很好很好,没有注解的代码,不是好代码。小嗷一般都不写注解,哈哈哈。小嗷估计其余项目例子都一样,

25.就是第一个搜索图的项目代码的讲解.PNG

4.6 点击Terminal Example

小嗷再翻翻其他项目翻到Terminal Example,看看内容就知道可以动手搞定。项目如下:

网址如下:http://doc.qt.io/qt-5/qtserialport-terminal-example.html

26.Terminal Example1.PNG

4.6.1 在MainWindow主界面创建new QSerialPort(this)对象;

27.Terminal Example.PNG

4.6.2 链接控件的回调函数(信号与槽,打个比方就是:按钮按一下,调用这个函数【槽】。当然不怎么专业,为了便于你们理解)

28.Terminal Example.PNG

4.6.3 打开串口【设置什么波特率,几号串口等,再连接串口】

29.打开串口.PNG

4.6.4 关闭串口,读串口数据和发送串口数据

30. 关闭串口.PNG

基本上,拥有我们需要的功能:打开串口 -> 发送数据/接收数据 -> 关闭串口(当然,上面已经介绍的错误识别。具体情况,在实现过程,看看。)

31.代码实现

5.1 添加到.pro

QT       += serialport

32.代码实现

5.2 写界面(Label + Combo Box【左键双击控件】)

只写比较关键操作

5.2.1 Combo Box【左键双击控件,添加值】,需要翻看上面的功能函数定义

33.代码实现

相关控件命名:

34.代码实现

35.代码实现

5.3 头文件mainwindow.h

36.代码实现

代码如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
//上图忘了添加QtSerialPort和QSerialPortInfo两个类
#include <QMainWindow>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

//创建按钮的回调函数(响应事件)和接收数据的回调函数
private slots:
    void on_OpenSerialButton_clicked();

    void ReadData();

    void on_SendButton_clicked();

//定义一个QSerialPort的全局变量:serial
private:
    Ui::MainWindow *ui;
    QSerialPort *serial;
};

#endif // MAINWINDOW_H

5.4 解析源文件mainwindow.cpp

5.4.1 查找可用的串口

37.产品对标

对标别人的产品,打开软件自动获取当前电脑的COM口的信息。

实现思路:

  1. 查看系统是否有用的串口
  2. 有用就添加到ComboBox这个组合框中(小嗷在Android中,习惯说下拉菜单)

步骤:

第一个问题: 怎么获取系统是否有用的串口?

小嗷记得之前有个QSerialPortInfo Class查一查,发现一个有用串口的东东,点击进入

网址: http://doc.qt.io/qt-5/qserialportinfo.html#availablePorts

38.SerialPortInfo函数

39.SerialPortInfo函数

英文翻译:返回一个在系统上有用的串口列表

第二个问题:怎么读取列表(list)中的内容

本来小嗷向上网查查怎么读取列表(list)中的内容。这个自动获取串口怎么这么眼熟?

打开QT提供的例子 -> 在“.cpp”中 ctrl + F 查找“availablePorts”关键字

40.产品对标

41.产品代码

C++11中引入的auto主要有两种用途:自动类型推断和返回值占位

const auto infos = QSerialPortInfo::availablePorts();
//for循环中的:符号是什么意思
//for(x:y)表示x属于y,并且遍历y中的所有元素    
for (const QSerialPortInfo &info : infos)
    serialPortComboBox->addItem(info.portName());   

一套带走,搞定,嗷嗷嗷!

实现代码加强版如下(因为考虑到有的串口没有关闭的状态):

//查找可用的串口
const auto infos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : infos)
{
    QSerialPort serial;
    serial.setPort(info);
    //如果某个串口打开,读取正常,统统关闭
    if(serial.open(QIODevice::ReadWrite))
    {
        ui->PortBox->addItem(info.portName());
        serial.close();
    }
}

设置选择的项,如:当我数据位下拉菜单选择8时,设置Data8

    //设置数据位数
    switch (ui->BitBox->currentIndex())
    {
    case 8:
        serial->setDataBits(QSerialPort::Data8);//设置数据位8
        break;
    default:
        break;
    }

为啥小嗷不写全?

42.产品

瞄了一眼,波特率要写8个,每个3行。大概从波特率到流控估计要写多60-N行代码。算了,直接写死。

5.5 mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //查找可用的串口
    const auto infos = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : infos)
    {
        QSerialPort serial;
        serial.setPort(info);
        //如果某个串口打开,读取正常,统统关闭
        if(serial.open(QIODevice::ReadWrite))
        {
            ui->PortBox->addItem(info.portName());
            serial.close();
        }
    }
    //开机设置所有下拉菜单默认显示第3项(0为第一项,看项目需求)
    ui->BaudBox->setCurrentIndex(2);
    ui->BitBox->setCurrentIndex(2);
    ui->ParityBox->setCurrentIndex(2);
    ui->BitBox->setCurrentIndex(2);
    ui->FlowBox->setCurrentIndex(2);

}

MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::on_OpenSerialButton_clicked()
{
    if(ui->OpenSerialButton->text() == tr("打开串口"))
    {
        serial = new QSerialPort;
        //设置串口名
        serial->setPortName(ui->PortBox->currentText());
        //打开串口
        serial->open(QIODevice::ReadWrite);
        //设置波特率
        serial->setBaudRate(QSerialPort::Baud115200);//设置波特率为115200
        //设置数据位数
        switch (ui->BitBox->currentIndex())
        {
        case 8:
            serial->setDataBits(QSerialPort::Data8);//设置数据位8
            break;
        default:
            break;
        }
        //设置校验位
        switch (ui->ParityBox->currentIndex())
        {
        case 0:
            serial->setParity(QSerialPort::NoParity);
            break;
        default:
            break;
        }
        //设置停止位
        switch (ui->BitBox->currentIndex())
        {
        case 1:
            serial->setStopBits(QSerialPort::OneStop);//停止位设置为1
            break;
        case 2:
            serial->setStopBits(QSerialPort::TwoStop);
        default:
            break;
        }
        //设置流控制
        serial->setFlowControl(QSerialPort::NoFlowControl);//设置为无流控制

        //关闭设置菜单使能
        ui->PortBox->setEnabled(false);
        ui->BaudBox->setEnabled(false);
        ui->BitBox->setEnabled(false);
        ui->ParityBox->setEnabled(false);
        ui->StopBox->setEnabled(false);
        ui->OpenSerialButton->setText(tr("关闭串口"));

        //连接信号槽
        QObject::connect(serial,&QSerialPort::readyRead,this,&MainWindow::ReadData);
    }
    else
    {
        //关闭串口
        serial->clear();
        serial->close();
        serial->deleteLater();

        //恢复设置使能
        ui->PortBox->setEnabled(true);
        ui->BaudBox->setEnabled(true);
        ui->BitBox->setEnabled(true);
        ui->ParityBox->setEnabled(true);
        ui->StopBox->setEnabled(true);
        ui->OpenSerialButton->setText(tr("打开串口"));

    }




}
//读取接收到的信息
void MainWindow::ReadData()
{
    QByteArray buf;
    buf = serial->readAll();
    if(!buf.isEmpty())
    {
        QString str = ui->textEdit->toPlainText();
        str+=tr(buf);
        ui->textEdit->clear();
        ui->textEdit->append(str);

    }
    buf.clear();
}

//发送按钮槽函数
void MainWindow::on_SendButton_clicked()
{
    //Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。ISO-8859-1编码是单字节编码,向下兼容ASCII
    //其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
    serial->write(ui->textEdit_2->toPlainText().toLatin1());
}

5.6 效果图如下:

43.效果图

44感想

2016-01-23 01:11:16 wait_for_taht_day5 阅读数 59053

通信方式


并行

适合短距离通信,并行通信控制简单、相对传输速度快(8位一起传输)。



串行

只能一位一位的传送。



同步(了解)

建立发送方时钟对接收方时钟的直接控制,使双方达到完全同步。此时,传输数据的位之间的距离均为“位间隔”的整数倍,同时传送的字符间不留间隙。

发送方对接收方的同步可以通过外同步和自同步


异步

以字符(构成的帧)为单位进行传输。数据位从低到高传送。

格式:


这里的空闲时间是任意的。


串行通信的制式(传输方向)


单工(仅能沿一个方向)



半双工(可进行双向,但需分时)



全双工




串行通信的错误校验


奇偶校验

在发送数据时,数据位尾随的1位为奇偶校验位(1/0)。奇校验时,数据中1的个数与检验位1的个数之和应为奇数;偶校验时,数据中1的个数与校验位1的个数之和应为偶数。接收字符时,对1的个数进行校验,若字符不一致,则说明传输数据过程中出现错误。


代码和校验

发送方将所发数据块求和(或各字节异或),产生一个字节的校验字符(校验和)附加到数据块末尾。接收方接收数据时,同时对数据块(除校验字节外)求和(或各字节异或),将所得的结果与发送方的“校验和”进行比较,一致则无差。


循环冗余校验

通过某种数学预算实现有效信息与校验位之间的循环校验,常用语对磁盘信息的传输、存储区的完整性校验。



串口的基本结构



SBUF:51单片机中的特殊寄存器,串行数据缓冲器(一个接收一个发送),两个其实是共用的一个地址99H,但是两个在物理上面是分开的。
发送使用时,就采用SBUF=XXX;  (XXX为需要传送的数据)
接收使用时,采用XXX=SBUF;
记得因为是串行的所以传输都是一位一位进行的。
T1溢出率:T1计时器的溢出频率(就是计时器每次低位计满向高位进位时间的倒数)
用处:用于计算波特率(每秒传输二进制代码的位数)

实现单片机与电脑之间的互相传送字符串通信


工具:STC-ISP


代码:

#include <reg52.h>
#include <stdio.h>              //printf头文件
#define uc unsigned char 
#define uint unsigned int

uc flag,i,flag_t,s[50]="",j=0,flag_n=0;
uc code table[]="I get ";        

void init()
{
	TMOD=0x20;	  //定时器工作方式,选择了定时器1,工作方式2 八位初值自动重装的8位定时器。		 
	TH1=0xfd;	  //定时器1初值	,设置波特率为9600 晶振11.0529MHZ?
	TL1=0xfd;
	TR1=1;		  //开启定时器1

	SM0=0;
	SM1=1;		  //10位异步接收,(8位数据)波特率可变
    REN=1;		  //允许串行口接收位
	EA=1;	      //允许中断(总闸)
	ES=1;		  //允许串口中断
}
void main()
{
	init();
	while(1)
{
	  if(flag==1)
	{	  if(flag_n!=0)		   //使第二个及以后I get xx 换行,不与You transfer在一行(单纯为了格式好看)
			{TI=1;
			printf("\n");
			while(!TI);
			TI=0;
			}
		for(i=0;i<6;i++)
		{
			SBUF=table[i];
			while(!TI);
			TI=0;
		}
			for(i=0;s[i]!='#'&&i<50;i++)
		{
			SBUF=s[i];
			while(!TI);
			TI=0;
		}
		flag=0;
	}
	if(flag_t==1)						   //发送完毕之后,在电脑端输出。
	{
		TI=1;							   //printf之前必须将T1置为1才行。
		printf ("\nYou transfer %s",s);
		while(!TI);
		TI=0;
		flag_t=0;
	}	
}
}

void ser() interrupt 4
{
		if(RI)		 //接收数据,手动将RI清0
	{	    
		RI=0;
		
		if(flag==0&&j!=0)//1.循环赋值为'\0'(字符串结尾标志符),j=0,为了第二次传递字符串是又是从头输出
		{			 //2.flag为0和j不为0时,保证是第二次及以后,传输字符串(控制输出格式)
			flag_n++;			 
			for(j=0;s[j]!='#'&&j<50;j++)
				  s[j]='\0';
				  j=0;
		}
		s[j]=SBUF;
		flag=1;
		if(s[j]=='#'||j==49)	 //以'#'作为传送字符串的结尾符,我定义的字符数组最长为50所以49也应该结束。
			flag_t=1;
		else
		 	j++;
	}

	if(TI)     //发送数据
	{
	}	 
}



运行截图:




代码解读:基本上就是几个模块:计时器、中断以及串口通信

中断

寄存器介绍

IE(interrupt enable):(可位寻址)设定各个中断源的打开和关闭
IP(interrupt prior)中断优先级寄存器:(可位寻址)用来设定各个中断源属于两级中断中的哪一级

中断源:




中断响应条件:

1.中断源有中断请求
2.此中断源的中断允许位为1
3.CPU开中断(EA=1)

代码书写:

1.先开总中断EA
2
.然后再开特定的中断去控制
3.如果有特殊需要优先级问题再设置IP
4.中断函数书写
格式
void 函数名() interrupt 中断号(上面图示的序号)
//中断函数返回值一定是void  
//函数名随便写
//中断号用来判断是哪个中断源


计时器

寄存器介绍

TCON 支持位寻址 :控制寄存器,控制T1、T0的启动和停止及设置溢出标志


TMOD,不支持位寻址:定时/计数器的工作方式寄存器,确定工作方式和功能







计时器代码书写步骤:

1.EA=1;

2.ETX=1;                            //开启计时器X中断

3.配置工作方式  TMOD=0x..; //根据自己需求按照上表来配     

3.配置计时器初值

//THX=(65535-N)/256;
//TLX=(65535-N)%256;    
//N由你要计时的时长决定。计时器计一个数花费一个时钟周期来计算。

4.TRX=1;                           //开启计时器X


串口通信

寄存器介绍

PCON电源管理寄存器 :(不可位寻址)

用来管理单片机的电源部分,包括上电复位检测、掉电模式

、空闲模式等





SCON:(可位寻址)用以设定串行口的工作方式、接收/发送控制以及设置状态标志







波特率计算




SMOD就是PCON中的第一位,默认为0
fosc为晶振频率,所以自己设定不同波特率时,也要考虑晶振不同的问题。


代码书写

1.上面都书写完毕之后
2.还需要ES=1
3.传输数据时,SBUF=XX
   接收数据时,XX=SBUF
4.中断函数书写 
一定要将RI清0,但是TI的清0在主函数中进行
//因为TI在中断中进行,(1)没有if(TI)的判断,那么就会和RI的处理混淆(2)如果有TI判断
//那么有可能永远进行不了传输数据,因为最开始TI是为0的,无法进入TI条件,就无传
//输数据(SBUF=XX)。而且在传输数据的时候会又一次进入中断,就是还没处理中
//断就又进入了另外一个中断,导致通信出现异常。


相信有了这些模块的讲解之后加上代码的注释应该都懂了~
如果有任何问题和不懂的都可以提出~

2019-11-22 23:39:32 weixin_43130546 阅读数 417

STC89C52_51单片机_串口配置_UART串口通信







寄存器配置

PCON电源管理寄存器

位序号 D7 D6 D5 D4 D3 D2 D1 D0
位符号 SM0 SM1 SM2 REN TB8 RB8 TI RI
模式 模式 模式1直接清零 使能串口接收 模式1接收停止位 发送标志位,软件清零 接受标志位,软件清0
//不能位寻址

SCON串口控制寄存器

位序号 D7 D6 D5 D4 D3 D2 D1 D0
位符号 SM0 SM1 SM2 REN TB8 RB8 TI RI
模式 模式 模式1直接清零 使能串口接收 模式1接收停止位 发送标志位,软件清零 接受标志位,软件清0
/*  SCON  */
sbit SM0   = SCON^7;
sbit SM1   = SCON^6;
sbit SM2   = SCON^5;
sbit REN   = SCON^4;
sbit TB8   = SCON^3;
sbit RB8   = SCON^2;
sbit TI    = SCON^1;
sbit RI    = SCON^0;

模式&波特率(宋雪松P183)

SCON主要用模式1,的波特率
对应的,要用定时器T1&T2的模式2

TH1 = TL1 = 256 - 晶振值/12/2/16/波特率
(256是TL1的溢出值,12指12个时钟周期,16是硬件因素)

SBUF

两个SBUF寄存器,分别负责接收和发送缓冲

流程

配置串口为模式1
配置定时器T1为模式2
根据波特率计算TH0&TL0的值
配置PCON&SCON寄存器
打开定时器







代码及链接







IO口模拟UART串口通信

UART串口传送数据示意图

在这里插入图片描述

挖坑:波特率&TH0是怎么算的?


#include<reg52.h>

sbit PIN_RXD = P3^0;
sbit PIN_TXD = P3^1;

bit RxdOrTxd = 0;
bit RxdEnd = 0;
bit TxdEnd = 0;
unsigned char RxdBuf = 0;
unsigned char TxdBuf = 0;

void configUART(unsigned long baud);
void startRXD(void);
void startTXD(unsigned char dat);

void main(void)
{
	EA = 1;
	configUART(9600);

	while(1)
	{
		while(PIN_RXD);
		startRXD();
		while(!RxdEnd);
		startTXD(RxdBuf+1);
		while(!TxdEnd);
	}
}

void configUART(unsigned long baud)
{
	TMOD &= ~(0xF<<0);
	TMOD |= 0x1<<1;
	TH0 = 256 - (11059200 / 12) / baud;
}

void startRXD(void)
{
	TL0 = 256 - ((256 - TH0) >> 1);

	ET0 = 1;
	TR0 = 1;

	RxdEnd = 0;
	RxdOrTxd = 0;
}

void startTXD(unsigned char dat)
{
	TxdBuf = dat;

	TL0 = TH0;

	ET0 = 1;
	TR0 = 1;

	PIN_TXD = 0;
	RxdEnd = 0;
	RxdOrTxd = 1;
}

void timer0(void) interrupt 1
{
	static unsigned char cnt = 0;

	if(RxdOrTxd)
	{
		cnt++;
		if(cnt<=8)
		{
			PIN_TXD = TxdBuf & 0x01;
			TxdBuf >>= 1;
		}
		else if(cnt==9)
		{
			PIN_TXD = 1;
		}
		else 
		{
			cnt = 0;
			TR0 = 0;
			TxdEnd = 1;
		}
	}

	else
	{
		if(cnt==0)
		{
			if(!PIN_RXD)
			{
				RxdBuf = 0;
				cnt++;
			}
			else
			{
				TR0 = 0;
			}
		}
		else if(cnt<=8)
		{
			RxdBuf >>= 1;
			if(PIN_RXD)
			{
				RxdBuf |= 0x80;
			}
			cnt++;
		}
		else
		{
			cnt = 0;
			TR0 = 0;
			if(PIN_RXD)
			{
				RxdEnd = 1;
			}
		}
	}
}







UART串口通信

教学版


#include<reg52.h>

void configUART(unsigned long baud);

void main(void)
{
	configUART(9600);
	while(!RI);
	RI = 0;
	SBUF = SBUF + 1;
	while(!TI);
	TI = 0;
}

void configUART(unsigned long baud)
{
	SCON = 0x50;

	TH1 = 256 - (11059200 / 12 / 2 / 16) / baud;
	TL1 = TH1;
	TMOD &= ~(0xF<<4);
	TMOD |= 0x2<<4;
	ET1 = 0;
	TR1 = 1;
}


工业版


#include<reg52.h>

void configUART(unsigned long baud);

void main(void)
{
	EA = 1;
	configUART(9600);
	while(1);
}

void configUART(unsigned long baud)
{
	SCON = 0x50;

	TH1 = 256 - (11059200 / 12 / 2 / 16) / baud;
	TL1 = TH0;
	TMOD &= ~(0xF<<4);
	TMOD |= 0x2<<4;
	ET1 = 0;
	ES = 1;
	TR1 = 1;
}

void UART(void) interrupt 4
{
	if(RI)
	{
		RI = 0;
		SBUF = SBUF + 1;
	}
	if(TI)
	{
		TI = 0;
	}
}







计算机发送数据,在数码管中显示


#include<reg52.h>

sbit wei = P2^7;
sbit duan = P2^6;

unsigned char code weitable[6] = 
{
	~0x20,~0x10,~0x08,~0x04,~0x02,~0x01
};

unsigned char code duantable[16] = 
{
	0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
	0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71
};

unsigned char ledbuff[6] = 
{
	0x00,0x00,0x00,0x00,0x00,0x00
};

unsigned char T0RH = 0,T0RL = 0;
unsigned char RxdByte = 0;

void configtimer0(unsigned char ms);
void configUART(unsigned long baud);

void main(void)
{
	EA = 1;
	configtimer0(1);
	configUART(9600);
	while(1)
	{
		ledbuff[0] = duantable[RxdByte & 0x0F];
		ledbuff[1] = duantable[RxdByte >> 4];
	}
}

void configtimer0(unsigned char ms)
{
	unsigned long tmp;

	tmp = 11059200 / 12;
	tmp = (tmp * ms) / 1000;
	tmp = 65536 - tmp;
	tmp = tmp + 12;

	T0RH = (unsigned char)(tmp>>8);
	T0RL = (unsigned char)tmp;

	TH0 = T0RH;
	TL0 = T0RL;
	TMOD &= ~(0xF<<0);
	TMOD |= 0x1<<0;
	ET0 = 1;
	TR0 = 1;
}

void configUART(unsigned long baud)
{
	TH1 = 256 - (11059200 / 12 / 2 / 16) / baud;
	TL1 = TH0;
	SCON = 0x50;
	TMOD &= ~(0xF<<4);
	TMOD |= 0x2<<4;
	ET1 = 0;
	ES = 1;
	TR1 = 1;
}

void ledscan(void)
{
	static unsigned char i = 0;

	P0 = 0x00;
	duan = 1;
	duan = 0;

	P0 = weitable[i];
	wei = 1;
	wei = 0;
	P0 = ledbuff[i];   
	duan = 1;
	duan = 0;

	if(i<5)
		i++;
	else
		i = 0;
}

void timer0(void) interrupt 1
{
	TH0 = T0RH;
	TL0 = T0RL;

	ledscan();
}

void UART(void) interrupt 4
{
	if(RI)
	{
		RI = 0;
		RxdByte = SBUF;
		SBUF = RxdByte;
	}
	if(TI)
	{
		TI = 0;
	}
}

2008-01-17 15:59:00 mybirdsky 阅读数 3296
2006-05-04 14:16:54

字体变小 字体变大
 
单片机和PC机串口通讯试验

51单片机有一个全双工的串行通讯口,所以单片机和电脑之间可以方便地进行串口通讯。进行串行通讯时要满足一定的条件,比如电脑的串口是RS232电平的,而单片机的串口是TTL电平的,两者之间必须有一个电平转换电路,我们采用了专用芯片MAX232进行转换,虽然也可以用几个三极管进行模拟转换,但是还是用专用芯片更简单可靠。我们采用了三线制连接串口,也就是说和电脑的9针串口只连接其中的3根线:第5脚的GND、第2脚的RXD、第3脚的TXD。这是最简单的连接方法,但是对我们来说已经足够使用了,电路如下图所示,MAX232的第10脚和单片机的11脚连接,第9脚和单片机的10脚连接,第15脚和单片机的20脚连接。本站提供的带扩展元件的51单片机实验板上已经装配好了全部硬件。

串口通讯的硬件电路如上图所示

为了能够在电脑端看到单片机发出的数据,我们必须借助一个WINDOWS软件进行观察,这里我们利用一个免费的电脑串口调试软件。

这个串口调试软件,在光盘里,这是一个绿色的软件,无需安装,可以直接在当前位置运行这个软件。软件界面如上图,我们先要设置一下串口通讯的参数,将波特率调整为4800,勾选十六进制显示。串口选择为COM1,当然将网站提供的51单片机实验板的串口也要和电脑的COM1连接,将烧写有以下程序的单片机插入单片机实验板的万能插座中,并接通51单片机实验板的电源,这时只要按下K1一次,在串口调试助手软件的接收区界面中就会增加一个“AF”字符,表示单片机向电脑发送“AF”字符成功。串口实验的源程序如下所示:

;这是一个AT89C51单片机实验开发板向PC机的串口单向发送数据AF的演示程序
;采用MAX232专用芯片作RS232/TTL电平转换.
;通讯波特率为4800KBPS,只要按下一次K1(就是P3.6引脚变成低电平)
;就发送一个16进制的AF字符

ORG 0000H
MOV SCON,#50H;设置成串口1方式
MOV TMOD,#20H;波特率发生器T1工作在模式2上
MOV PCON,#80H;波特率翻倍为2400x2=4800BPS
MOV TH1,#0F3H;预置初值(按照波特率2400BPS预置初值)
MOV TL1,#0F3H;预置初值(按照波特率2400BPS预置初值)
SETB TR1;启动定时器T1
;以上完成通讯初始化设置

WRIT:JB P3.6,$;判断K1是否按下,如果没有按下就等待
ACALL DELAY10;延时10毫秒消触点抖动
JB P3.6,WRIT;去除干扰信号
JNB P3.6,$;等待按键松开

MOV A,#0AFH;将16进制的字符AF发送到串口去
MOV SBUF,A;将AF通过串口发送出去

AJMP WRIT

;10毫秒延时子程序
DELAY10:MOV R4,#20
D2:MOV R5,#248
DJNZ R5,$
DJNZ R4,D2
RET

END

SP-51K实验板:108元

购单片机编程器或单片机实验板赠送以下光盘:

1)编程器软件光盘(含Keil.51软件,Keil软件多媒体教学,Uedie编辑器,8051模拟软件,编程器软件,

51单片机开发小工具,Protel99多媒体教学软件等)

2)51多媒体教学光盘一张

长期提供单片机编程器,单片机实验板和配套电子器材

SP-51A单片机实验板(带1602屏) 165元 SP-51pro单片机编程器: 98元
SP-51B单片机实验板 108元 SP-51单片机仿真器: 108元
SP-51K单片机实验板 108元 NSP单片机多功能编程器: 298元

郑州超越单片机技术有限公司

郑州市郑东新区郑汴路白庄小区23号楼

TEL:0371--66160112(可接传真) 66160113(可接传真) 66160068

电子信箱: saxmcu@yahoo.com.cn

 
 
2019-11-30 16:10:40 qq_37631068 阅读数 286

实验目的和任务

目的:利用“模块化单片机教学实验平台”,加深对单片机的串行口的理解。

任务:利用单片机的串行口完成程序设计。

实验内容

使用AT89S52单片机的串行口通过RS232通信接口与PC机进行通信,让单片机把接收到的每一帧数据(即PC机发送给单片机的每一帧数据)直接再发送给PC机。(串行口波特率设定为9600Bit/s,使用方式1)注意:使用串口调试助手(Baud 9600、数据位8、停止位1、效验位无)作为上位机来向单片机发送数据和接收单片机串口所发的数据,观察串口调试助手接收窗口。

实验过程和结果

电路图

硬件连线:

母版

CPU

J57/J59RXD

P2P3.0

J57/J59TXD

P2P3.1

用232串口线连接计算机的USB口和MAIN_BOARD的RS2/RS1串口。

注意:实验箱的AT89S52单片机的晶振频率为11.0592MHz!

参考流程图:

 

实验结果图

  1. 实验心得

实验过程让我熟悉了中断程序和串口的编写步骤和单片机执行串口传输的工作流程。实验中由于不熟悉中端口的相关寄存器分布和功能,导致错误设置了特殊功能寄存器,程序不能正常执行,后来在老师的指导下修改了程序和中断入口地址,程序能正常执行并返回输入内容。

  1. 附录(代码)

(1)基本实验

ORG 0000H

LJMP MAIN               

ORG 0023H

LJMP U

MAIN: 

MOV SCON,#01010000B

SETB ES

SETB EA

MOV TMOD,#00100000B

MOV TH1,#0FDH

SETB TR1

LJMP $

U:

CLR RI

MOV A,SBUF

MOV SBUF,A

JNB RI,$

CLR RI

RETI

END

(2)扩展实验

ORG 0000H

LJMP MAIN               

ORG 0023H

LJMP U

MAIN: 

MOV SCON,#01010000B

SETB ES

SETB EA

MOV TMOD,#00100000B

MOV TH1,#0FDH

SETB TR1

SETB T1      ;手动执行中断

LJMP $

U:

CLR RI

MOV SBUF,#68H

MOV SBUF,#65H

MOV SBUF,#6CH

MOV SBUF,#6CH

MOV SBUF,#6FH

MOV SBUF,#26H

MOV SBUF,#20H

MOV SBUF,#77H

MOV SBUF,#6FH

MOV SBUF,#72H

MOV SBUF,#6CH

MOV SBUF,#64H

MOV SBUF,#0DH

MOV SBUF,#0AH

MOV SBUF,#0AH

JNB RI,$

CLR RI

LJMP U      ;发送完毕后回到中断程序头部,循环发送hello world

RETI

END