stm32f103串口学习

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

stm32f103串口学习

  • 前言
  • 一、数据通信方式
  • 二、数据传输方向
  • 三、串口通信协议简介
    • 1. 波特率
    • 2. 数据帧格式
  • 四、 STM32F1 的串口简介
    • 1. USART 框图
    • 接受串口中断代码
    • 发送中断代码
  • **总结**


前言

重新复习一下串口学习,UASTR,SPI,IIC区别,
串口是不带时钟同步的。
spi是带片选接口。
iic是不带片选接口,可以做到组网的通讯模式。
stm32f103串口学习_第1张图片
stm32f103串口学习_第2张图片


一、数据通信方式

按数据通信方式分类,可分为串行通信和并行通信两种。串行和并行的对比如下图所示:

stm32f103串口学习_第3张图片
图 数据传输方式
串行通信的基本特征是数据逐位顺序依次传输,优点是传输线少、布线成本低、灵活度高
等优点,一般用于近距离人机交互,特殊处理后也可以用于远距离,缺点就是传输速率低。
而并行通信是数据各位可以通过多条线同时传输,优点是传输速率高,缺点就是线多成本
就高了,抗干扰能力差因而适用于短距离、高速率的通信。

二、数据传输方向

根据数据传输方向,通信又可分为全双工、半双工和单工通信。全双工、半双工和单工通信的比较如:

  • 单工是指数据传输仅能沿一个方向,不能实现反方向传输。
  • 半双工是指数据传输可以沿着两个方向,但是需要分时进行。
  • 全双工是指数据可以同时进行双向传输。 这里注意全双工和半双工通信的区别:半双工通信是共用一条线路实现双向通信, 而全双工是利用两条线路,一条用于发送数据,另一条用于接收数据。

三、串口通信协议简介

串口通信是一种设备间常用的串行通信方式,串口按位( bit)发送和接收字节。尽管比特字节(byte)的串行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。 串口通信协议是指规定了数据包的内容,内容包含了起始位、主体数据、校验位及停止位,双方需要约定一致的数据包格式才能正常收发数据的有关规范。在串口通信中,常用的协议包括 RS-232、 RS-422 和 RS-485 等。

随着科技的发展, RS-232 在工业上还有广泛的使用,但是在商业技术上,已经慢慢的使
用 USB 转串口取代了 RS-232 串口。我们只需要在电路中添加一个 USB 转串口芯片,就可以
实现 USB 通信协议和标准 UART 串行通信协议的转换,而我们开发板上的 USB 转串口芯片是
CH340C 这个芯片。下面我们来学习串口通信协议,这里主要学习串口通信的协议层。
串口通信的数据包由发送设备的 TXD 接口传输到接收设备的 RXD 接口。在串口通信的
协议层中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通讯双方
的数据包格式要约定一致才能正常收发数据,其组成如图 所示。
stm32f103串口学习_第4张图片
串口通信协议数据包组成可以分为波特率和数据帧格式两部分。

1. 波特率

本章主要讲解的是串口异步通信,异步通信是不需要时钟信号的,但是这里需要我们约定
好两个设备的波特率。 波特率表示每秒钟传送的码元符号的个数,所以它决定了数据帧里面每
一个位的时间长度。两个要通信的设备的波特率一定要设置相同,我们常见的波特率是 4800、
9600、 115200 等。

2. 数据帧格式

数据帧格式需要我们提前约定好,串口通信的数据帧包括起始位、停止位、有效数据位以及校验位。
⚫ 起始位和停止位
串口通信的一个数据帧是从起始位开始,直到停止位。数据帧中的起始位是由一个逻辑 0的数据位表示,而数据帧的停止位可以是 0.5、 1、 1.5 或 2 个逻辑 1 的数据位表示,只要双方约定一致即可。
⚫ 有效数据位
数据帧的起始位之后,就接着是数据位,也称有效数据位,这就是我们真正需要的数据,有效数据位通常会被约定为 5、 6、 7 或者 8 个位长。有效数据位是低位(LSB)在前,高位(MSB)在后。
⚫ 校验位
校验位可以认为是一个特殊的数据位。校验位一般用来判断接收的数据位有无错误,检验方法有:奇检验、偶检验、 0 检验、 1 检验以及无检验。下面分别介绍一下:
奇校验是指有效数据位和校验位中“1”的个数为奇数,比如一个 8 位长的有效数据为:10101001,总共有 4 个“1”,为达到奇校验效果,校验位设置为“1”,最后传输的数据是 8 位的有效数据加上 1 位的校验位总共 9 位。
偶校验与奇校验要求刚好相反,要求帧数据和校验位中“ 1”的个数为偶数,比如数据帧11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。
0 校验是指不管有效数据中的内容是什么,校验位总为“0”, 1 校验是校验位总为“1”。
无校验是指数据帧中不包含校验位。我们一般是使用无检验的情况。

四、 STM32F1 的串口简介

STM32F103 的串口资源相当丰富,功能也相当强劲。 STM32F103RCT6 最多可提供 5 路串口,有分数波特率发生器、支持同步单线通信和半双工单线通讯、支持 LIN、支持调制解调器操作、智能卡协议和 IrDA SIR ENDEC 规范、具有 DMA 等。
STM32F1 的串口分为两种: USART(即通用同步异步收发器)和 UART(即通用异步收发器)。 UART 是在 USART 基础上裁剪掉了同步通信功能,只剩下异步通信功能。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用串口通信基本都是异步通信。
STM32F1 有 3 个 USART 和 2 个 UART,其中 USART1 的时钟源来于 APB2 时钟,其最大频率为 72MHz,其他 4 个串口的时钟源可以来于 APB1 时钟,其最大频率为 36MHz。
STM32 的串口输出的是 TTL 电平信号,如果需要 RS-232 标准的信号可使用 MAX3232 芯片进行转换。

1. USART 框图

下面先来学习 USART 框图,通过 USART 框图引出 USART 的相关知识,从而有了一个很
好的整体掌握,对之后的编程也会有一个清晰的思路。
stm32f103串口学习_第5张图片
为了方便大家理解,我们把整个框图分成几个部分来介绍。
① USART 信号引脚
TX:发送数据输出引脚
RX:接收数据输入引脚
SCLK:发送器时钟输出,适用于同步传输
SW_RX:数据接收引脚,属于内部引脚,用于智能卡模式
IrDA_RDI: IrDA 模式下的数据输入
IrDA_TDO: IrDA 模式下的数据输出
nRTS:发送请求,若是低电平,表示 USART 准备好接收数据
nCTS:清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送
② 数据寄存器
USART_DR 包含了已发送或接收到的数据。由于它本身就是两个寄存器组成的,一个专门给发送用的(TDR),一个专门给接收用的(RDR) ,该寄存器具备读和写的功能。 TDR 寄存器提供了内部总线和输出移位寄存器之间的并行接口。 RDR 寄存器提供了输入移位寄存器和内部总线之间的并行接口。当进行数据发送操作时,往 USART_DR 中写入数据会自动存储在TDR 内;当进行读取操作时,向 USART_DR 读取数据会自动提去 RDR 数据。
USART 数据寄存器(USART_DR)低 9 位数据有效,其他数据位保留。 USART_DR 的第9 位数据是否有效跟 USART_CR1 的 M 位设置有关,当 M 位为 0 表示 8 位数据字长;当 M 位为 1 时表示 9 位数据字长,一般使用 8 位数据字长。
当使能校验位(USART_CR1 中 PCE 位被置位)进行发送时,写到 MSB 的值(根据数据的长度不同, MSB 是第 7 位或者第 8 位)会被后来的校验位取代。
③ 控制器
USART 有专门控制发送的发送器,控制接收的接收器,还有唤醒单元、中断控制等等,
具体在后面讲解 USART 寄存器的时候细讲。
④ 时钟与波特率
这部分的主要功能就是为 USART 提供时钟以及配置波特率。
波特率,即每秒钟传输的码元个数,在二进制系统中(串口的数据帧就是二进制的形式),波特率与波特率的数值相等,所以我们今后在把串口波特率理解为每秒钟传输的二进制位数。
波特率通过以下公式得出:
=/16 ∗ USARTDIV
fck 是给串口的时钟(USART2\3\3\4\5 的时钟源为 PCLK1, USART1 的时钟源为 PCLK2),USARTDIV 是一个无符号的定点数,存放在波特率寄存器(USART_BRR)的低 15 位,其中DIV_Mantissa[11:0] 存 放 的 是 USARTDIV 的 整 数 部 分 , DIV_Fractionp[3:0] 存 放 的 是USARTDIV 的小数部分。
下面举个例子说明:
当串口 1 设置需要得到 115200 的波特率, fck = 72MHz,那么可得:
115200 =72000000/16 ∗ USARTDIV
得到 USARTDIV = 39.0625,分离 USARTDIV 的整数部分与小数部分,整数部分为 39,即 0x27,那么 DIV_Mantissa = 0x27;小数部分为 0.0625,转化为十六进制即 0.0625*16 = 1,所以 DIV_Fractionp = 0x1, USART_BRR 寄存器应该赋值为 0x271,成功设置波特率为 115200。
值得注意 USARTDIV 是允许有余数的,我们用四舍五入进行取整,这样会导致波特率会有所偏差,而这样的小误差是可以被允许的。

#ifndef __USART_H
#define __USART_H
//这里定义extern,这样 就可以在其他文件中引用下面变量。
extern uint8_t  g_usart_rx_buf[USART_REC_LEN];  /* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint16_t g_usart_rx_sta;                 /* 接收状态标记 */

串口初始化

/**
 * @brief       串口X初始化函数
 * @param       sclk: 串口X的时钟源频率(单位: MHz)
 *              串口1 的时钟源来自: PCLK2 = 72Mhz
 *              串口2 - 5 的时钟源来自: PCLK1 = 36Mhz
 * @note        注意: 必须设置正确的sclk, 否则串口波特率就会设置异常.
 * @param       baudrate: 波特率, 根据自己需要设置波特率值
 * @retval      无
 */
void usart_init(uint32_t sclk, uint32_t baudrate)
{
    uint32_t temp;
    /* IO 及 时钟配置 */
    USART_TX_GPIO_CLK_ENABLE(); /* 使能串口TX脚时钟 */
    USART_RX_GPIO_CLK_ENABLE(); /* 使能串口RX脚时钟 */
    USART_UX_CLK_ENABLE();      /* 使能串口时钟 */

    sys_gpio_set(USART_TX_GPIO_PORT, USART_TX_GPIO_PIN,
                 SYS_GPIO_MODE_AF, SYS_GPIO_OTYPE_PP, SYS_GPIO_SPEED_HIGH, SYS_GPIO_PUPD_PU);   /* 串口TX脚 模式设置 */

    sys_gpio_set(USART_RX_GPIO_PORT, USART_RX_GPIO_PIN,
                 SYS_GPIO_MODE_IN, SYS_GPIO_OTYPE_PP, SYS_GPIO_SPEED_HIGH, SYS_GPIO_PUPD_PU);   /* 串口RX脚 必须设置成输入模式 */

    temp = (sclk * 1000000 + baudrate / 2) / baudrate;  /* 得到BRR, 采用四舍五入计算 */
    /* 波特率设置 */
    USART_UX->BRR = temp;       /* 波特率设置 */
    USART_UX->CR1 = 0;          /* 清零CR1寄存器 */
    USART_UX->CR1 |= 0 << 12;   /* M = 0, 1个起始位, 8个数据位, n个停止位(由USART_CR2 STOP[1:0]指定, 默认是0, 表示1个停止位) */
    USART_UX->CR1 |= 1 << 3;    /* TE = 1, 串口发送使能 */
#if USART_EN_RX  /* 如果使能了接收 */
    /* 使能接收中断 */
    USART_UX->CR1 |= 1 << 2;    /* RE = 1, 串口接收使能 */
    USART_UX->CR1 |= 1 << 5;    /* RXNEIE = 1, 接收缓冲区非空中断使能 */
    sys_nvic_init(3, 3, USART_UX_IRQn, 2); /* 组2,最低优先级 */
#endif
    USART_UX->CR1 |= 1 << 13;   /* UE = 1, 串口使能 */
}

接受串口中断代码

void USART_UX_IRQHandler(void)
{
    uint8_t rxdata;
#if SYS_SUPPORT_OS  /* 如果SYS_SUPPORT_OS为真,则需要支持OS. */
    OSIntEnter();
#endif

    if (USART_UX->SR & (1 << 5))                /* 接收到数据 */
    {
        rxdata = USART_UX->DR;

        if ((g_usart_rx_sta & 0x8000) == 0)     /* 接收未完成? */
        {
            if (g_usart_rx_sta & 0x4000)        /* 接收到了0x0d?   0100 0000 0000 0000*/ 
            {
                if (rxdata != 0x0a)             /* 接收到了0x0a? (必须先接收到到0x0d,才检查0x0a) */
                {
                    g_usart_rx_sta = 0;         /* 接收错误, 重新开始 */
                }
                else
                {
                    g_usart_rx_sta |= 0x8000;   /* 收到了0x0a,标记接收完成了 1000 0000 0000 0000 */
                }
            }
            else      /* 还没收到0x0d */
            {
                if (rxdata == 0x0d)
                {
                    g_usart_rx_sta |= 0x4000;   /* 标记接收到了 0x0d */
                }
                else
                {
                    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = rxdata;   /* 存储数据到 g_usart_rx_buf */
                    g_usart_rx_sta++;

                    if (g_usart_rx_sta > (USART_REC_LEN - 1))g_usart_rx_sta = 0;/* 接收数据溢出, 重新开始接收 */
                }
            }
        }
    }

}

**

发送中断代码

这个部分单独写了一个历程,这样可以更好理解。
采用中断的方式发送数据。要发送数据时,使能串口的发送缓冲区空中断,在ISR中判断是否有数据要发送,如果有,则将要发送的字节存入串口数据寄存器。当所有数据发送完毕后禁止串口的发送缓冲区空中断。

void usartsend(void)
{
    USART_ITConfig(USART1,USART_IT_TXE,ENABLE);//使能串口的发送缓冲区空中断
}
 
void USART1_IRQHandler(void) //串口1的ISR              
{
    if(USART_GetITStatus(USART1,USART_IT_TXE)==SET)  //判断是什么中断,发送中断
    {
        USART1->DR=txbuf[cnt];
        cnt++;
        if(cnt>=100)
        {
            USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//数据发送完毕,禁止串口的发送缓冲区空中断
        }
    }
} 

这种方式主要是ISR占用时间。采用115200的波特率实验时,发送100字节数据用时约8.51ms。

总结

  1. 代码里面有结束结束标志位“0x0d和0x0a”,接受到回车键和换行符才结束结束。
  2. 串口头文件中的【extern uint16_t g_usart_rx_sta; /* 接收状态标记*/】很重要,这样编写,在其他文件中可以引用这个变量。

你可能感兴趣的:(stm32,单片机,学习)