stm32f103串口学习

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


前言

重新复习一下串口学习,UASTR,SPI,IIC区别,
串口是不带时钟同步的。
spi是带片选接口。
iic是不带片选接口,可以做到组网的通讯模式。
在这里插入图片描述
在这里插入图片描述


一、数据通信方式

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

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

二、数据传输方向

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

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

三、串口通信协议简介

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

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

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 的相关知识,从而有了一个很
好的整体掌握,对之后的编程也会有一个清晰的思路。
在这里插入图片描述
为了方便大家理解,我们把整个框图分成几个部分来介绍。
① 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; /* 接收状态标记*/】很重要,这样编写,在其他文件中可以引用这个变量。
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值