什么是串行通信

  • 通信分串行和并行:串行是指用一根线通信,并行是指用多根线同时通信。
  • 这种通信也可以称为:串行通信、串口通信、UART、USART。
  • 特点:异步、串行、全双工。
  • 工作方式:有三根线(GND/RXD接收线/TXD发送线);数据在发送方和接收方的CPU中都是以字节为单位进行处理的;数据在通信线上以位为单位逐个bit传输。
  • 串行通信主要概念:起始位、数据位、奇偶效验位、停止位、波特率(一秒钟发送多少bit位,发送方和接收方波特率必须一样)
  • 串行通信功能是SOC的一个内部外设提供的,与CPU本身无关。

STC51串行通信相关寄存器

控制寄存器PCON和SCON

串行控制寄存器SCON:串行控制寄存器SCON用于选择串行通信的工作方式和某些控制功能。

  • SM0/FE:当PCON寄存器中的SMODO位为1时,该位用于帧错误检测。当检测到一个无效停止位时,通过UART接收器设置该位。它必须由软件清零。当PCON寄存器中的SMODO位为0时,该位和SM1一起指定串行通信的工作方式(SM0、SM1)。

    • 0 0 :工作方式0;同步移位串行方式;波特率是 SYSclk/12.
    • 0 1 :工作方式1;8位UART,波特率可变;
    • 1 0 :工作方式2;9位UART;
    • 1 1 :工作方式3;9位UART;波特率可变;
  • REN:允许/禁止串行接收控制位。(由软件置位和复位)
  • TI:发送数据时的中断请求标志位。当发送完一帧数据后硬件自动置位,即TI=1。
  • RI:接收数据时的中断请求标志位。当接收完一帧数据后硬件自动置位,即RI=1。

电源控制寄存器PCON:用于选择波特率是否加倍

  • SMOD:波特率选择位。SMOD=1,加倍;SMOD=0,不加倍。
  • SMOD0:帧错误检测有效控制位。

中断允许寄存器IE

  • EA : CPU的总中断允许控制位。
  • ES:串行口中断允许位。

定时器/计数器控制寄存器TCON

  • TR1:定时器T1的运行控制位。该位由软件置位和清零。
  • TR1=1时开始计数,TR1=0时禁止计数。

串行口工作模式

主要应用串行口工作模式1:8位UART,波特率可变。

  • 当软件设置SCON的SMO、SM1为“01”时,串行通信则以模式1工作。
  • 一帧信息为10位:1位起始位,8位数据位(低位在先)和1位停止位。
  • TXD为发送信息,RXD为接收信息。
  • 一帧信息发送或接收完成后,就会发起中断请求,置位RI或TI。

波特率计算公式

  • SMOD为0或1。
  • SYSclk为系统时钟频率。(我的是12MHz)
  • TH1为自动重装载值。TH1取整数。
  • 注意;计算波特率时,256-TH1的值必须要是整数附近零点零几的位置才行,差太多可能会造成乱码。(差了零点2都可能会乱码)

    代码示例

串口初始化

// 串口初始化函数
// 预设一个串口条件:8数据位、1停止位、0校验位、波特率9600
// 初始化的主要工作就是去设置相关的寄存器
void uart_init(void)
{

    SCON = 0x50;       // 01010000 串口工作在模式1(8位串口)、允许接收
    PCON = 0x00;        // 波特率不加倍

    // 通信波特率相关的设置
    TMOD = 0x20;        // 用作定时器,设置T1为模式2(8位自动重装载)
    TH1 = 243;            //2×12000000÷32÷12÷4800=13.0208,256-TH1=13,TH=243。
    TL1 = 243;           // 8位自动重装,当溢出时自动将TH1存放的值装入TL1中。TH1和TL1必须相同

    TR1 = 1;                // 开启T1定时/计数器
    ES = 1;                    //串行口中断允许位
    EA = 1;                    //总中断允许位

}

发送数据

// 单片机通过串口发送1个字节到电脑端。
void uart_send_byte(unsigned char c)
{
   // 第1步,发送一个字节
   SBUF = c;

   // 第2步,先确认串口发送部分没有在忙
   while (!TI);

   // 第3步,软件复位TI标志位
   TI = 0;
}

//单片机发送字符到电脑端
void uart_send_string(unsigned char *str)
{
    while (*str != '\0')
    {
        uart_send_byte(*str);        // 发送1个字符
        str++;                        // 指针指向下一个字符
    }
}


//电脑端发送数据到单片机,单片机接收数据,触发中断,RI=1。
void uart_isr(void) interrupt 4 using 1
{
    unsigned char tmp;

    if (RI)
    {
        tmp = SBUF;        // 读取SBUF,其实就是读出了串口接收到的1字节
        RI = 0;
    }
    uart_send_byte(tmp); //将接收到的数据再发送给电脑端,测试是否接收成功。
}

基于51单片机的I2C通信之EEPROM

基本知识

  • ROM(只读存储器)、RAM(随机访问存储器)、PROM(可编程ROM)
  • EPROM(可擦除ROM)、EEPROM(电可擦除ROM)
  • EEPROM在系统中的存在形式:内置在单片机内部或外部扩展。
  • EEPROM原理图:

I2C总线协议规定

  • 任何将数据传输到总线的都作为发送器,任何从总线接收数据的器件都作为接收器。
  • 数据传输由主器件控制,总线的串行时钟、起始停止条件均由主器件产生。
  • 只有在总线非忙时才允许进行数据传输。
  • 在数据传送时,当时钟线为高电平,数据线必须为固定状态,不允许有跳变。时钟线为高电平时,数据线的任何电平变化将被当作总线的启动或停止条件。
  • 启动条件:启动条件必须在所以操作命令之前发送。时钟线保持高电平期间,数据线电平从高到低的跳变作为I2C总线的启动信号。
  • 停止条件:时钟线保持高电平期间,数据线从低到高的跳变作为I2C总线的停止信号。操作结束时必须发送停止条件。
  • I2C数据传输方式:

I2C总结

  • 主CPU和其附属芯片最常用的接口,尤其是各种传感器。
  • 三根控制线:GND、SCL、SDA。串行、电平式。(STM32单片机中也是如此)
  • 发送和接收字节时都是从高位开始。
  • 总线式结构,可以一对多,总线上可以挂载上百个器件,用从地址来区分。
  • 主从式,由主设备发起通信及总线仲裁,从设备被动响应。
  • 通信速率较低,不适合语音、视频等。

I2C代码示例

/*******************************************************************************
* 函 数 名         : I2C_Start()
* 函数功能           : 起始信号:在I2C_SCL时钟信号在高电平期间I2C_SDA信号产生一个下降沿
* 输    入         : 无
* 输    出         : 无
* 备    注         : 起始之后I2C_SDA和I2C_SCL都为0
*******************************************************************************/

void I2C_Start()
{
    I2C_SDA = 1;
    I2C_Delay10us();
    I2C_SCL = 1;
    I2C_Delay10us();//建立时间是I2C_SDA保持时间>4.7us
    I2C_SDA = 0;
    I2C_Delay10us();//保持时间是>4us
    I2C_SCL = 0;
    I2C_Delay10us();
}
/*******************************************************************************
* 函 数 名           : I2C_Stop()
* 函数功能             : 终止信号:在I2C_SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿
* 输    入           : 无
* 输    出              : 无
* 备    注           : 结束之后保持I2C_SDA和I2C_SCL都为1;表示总线空闲
*******************************************************************************/

void I2C_Stop()
{
    I2C_SDA = 0;
    I2C_Delay10us();
    I2C_SCL = 1;
    I2C_Delay10us();//建立时间大于4.7us
    I2C_SDA = 1;
    I2C_Delay10us();

}
/*******************************************************************************
* 函 数 名           : I2cSendByte(uchar num)
* 函数功能              : 通过I2C发送一个字节。在I2C_SCL时钟信号高电平期间,
*                    * 保持发送信号I2C_SDA保持稳定
* 输    入           : num ,ack
* 输    出              : 0或1。发送成功返回1,发送失败返回0
* 备    注           : 发送完一个字节I2C_SCL=0, 需要应答则应答设置为1,否则为0
*******************************************************************************/

uchar I2C_SendByte(uchar dat, uchar ack)
{
    uchar a = 0,b = 0;//最大255,一个机器周期为1us,最大延时255us。
    // 为了保证时序正确,这里应该加一句 SCL = 0;
    for(a=0; a<8; a++)//要发送8位,从最高位开始
    {
        I2C_SDA = dat >> 7;     //起始信号之后I2C_SCL=0,所以可以直接改变I2C_SDA信号
        dat = dat << 1;
        I2C_Delay10us();
        I2C_SCL = 1;
        I2C_Delay10us();//建立时间>4.7us
        I2C_SCL = 0;
        I2C_Delay10us();//时间大于4us
    }

    I2C_SDA = 1;            // 主设备释放SDA线给从设备去操作
    I2C_Delay10us();
    I2C_SCL = 1;            // 主设备开始了第9个周期
    while(I2C_SDA && (ack == 1))//等待应答,也就是等待从设备把I2C_SDA拉低
    {
        b++;
        if(b > 200)     //如果超过200us没有应答发送失败,或者为非应答,表示接收结束
        {
            I2C_SCL = 0;
            I2C_Delay10us();
            return 0;
        }
    }

    I2C_SCL = 0;
    I2C_Delay10us();
     return 1;
}
/*******************************************************************************
* 函 数 名           : I2cReadByte()
* 函数功能             : 使用I2c读取一个字节
* 输    入           : 无
* 输    出              : dat
* 备    注           : 接收完一个字节I2C_SCL=0
*******************************************************************************/

uchar I2C_ReadByte()
{
    uchar a = 0,dat = 0;
    I2C_SDA = 1;            //CPU释放总线,让从设备接管。(51单片机中是这样处理,32中可能有点不同)
    I2C_Delay10us();
    // 按道理这里应该有一个SCL = 0的
    for(a=0; a<8; a++)//接收8个位
    {
        I2C_SCL = 1;        // 通知从设备我要开始读了,可以放1bit数据到SDA了
        I2C_Delay10us();
        dat <<= 1;            // 读取的时候是高位在前的
        dat |= I2C_SDA;
        I2C_Delay10us();
        I2C_SCL = 0;        // 拉低,为下一个bit的周期做准备
        I2C_Delay10us();
    }
    return dat;
}

EEPROM(AT24C02芯片介绍(最多保存255个字节))

器件寻址

  • 主器件通过发送一个起始信号启动发送过程,然后发送从器件地址。(从地址是芯片本身定义的)
  • 8位从器件地址的高4位固定为1010,接下来的3位为从器件的地址位,最低位为从器件的读写位(1表示对从器件进行读操作,0表示对从器件进行写操作)
  • 在主器件发送起始信号和从器件地址后,AT24C02会监视总线,当某一个器件的地址与发送的从地址相同时,响应一个应答信号(通过SDA线)。

应答信号

  • I2C总线数据传输时,每成功传输一个字节数据后,接收器都必须产生一个应答信号,接收器在第九个时钟周期将SDA拉低,表示已经收到一个字节数据。
  • 当AT24C02工作于被读状态时,在发送一个字节之后会释放总线,并监视总线,如果接收到应答信号,则继续发送数据;若没有接收到应答信号,则停止发送数据。
  • 写操作时序图)(ACK就是应答信号)

写操作

  • 单写一个字节:

    • 首先主器件发送起始命令和从器件地址信息。等待应答。
    • 然后发送要写入数据的内存地址(即字节地址)。等待应答。
    • 然后开始发送数据。等待应答。
    • 最后发送停止信号。

  • 代码示例
/*******************************************************************************
* 函 数 名         : void At24c02Write(unsigned char addr,unsigned char dat)
* 函数功能           : 往24c02的一个地址写入一个数据
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void At24c02Write(unsigned char addr,unsigned char dat)
{
    I2C_Start();
    I2C_SendByte(0xa0, 1);//发送从器件地址
    I2C_SendByte(addr, 1);//发送要写入内存地址
    I2C_SendByte(dat, 0);    //发送数据
    I2C_Stop();
}
  • 连续写入多个字节(AT24C01可一次写入8个字节,AT24C02/04/08/16可一次写入16个字节)

    • 每发送一个字节数据后,AT24C芯片会产生一个应答并将字节地址低位加一,高位不变。
    • 如果在发送停止信号之前,主器件发送的字节超出规定字节,则超出的字节将会覆盖先前写入的字节。

读操作

  • 立即地址读:

    • 如果上一次读/写操作的地址为N,则立即读的地址从N+1开始。如果读数据时超过地址极限(AT04C02最多只能保存255字节),则会翻转到0地址继续读取。

  • 选择性读:

    • 选择性读操作允许主器件对寄存器的任意地址进行读操作。
    • 读取过程:主器件首先通过发送起始信号和从器件地址以及想读取的字节数据地址执行一个伪写操作;在应答之后主器件重新发送起始信号和从器件地址,此时地址最后一位置一(进行读操作);AT24C02响应并发送应答信号,然后发送所需要的数据,主器件不发送应答信号但发送停止信号。

  • 代码示例
/*******************************************************************************
* 函 数 名         : unsigned char At24c02Read(unsigned char addr)
* 函数功能           : 读取24c02的一个地址的一个数据
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

unsigned char At24c02Read(unsigned char addr)
{
    unsigned char num;
    I2C_Start();
    I2C_SendByte(0xa0, 1); //发送从器件地址
    I2C_SendByte(addr, 1); //发送要读取的地址
    I2C_Start();
    I2C_SendByte(0xa1, 1); //发送读器件地址
    num=I2C_ReadByte(); //读取数据
    I2C_Stop();
    return num;
}
03-05 23:23