USART-通信详解

发布时间 2023-09-25 22:51:36作者: 一步一磕头的菜鸡

一. 通信基本概念

1. 根据数据传输方式划分

  • 串行通信: 一般是8根数据线以下, 数据一位一位的进行传输.
  • 并行通信:一般是指使用8、16、32及64根或更多的数据线进行传输的通讯方式.

2. 根据数据传输方向划分

  • 全双工: 同一时刻, 两个设备之间可以同时收发数据.
  • 半双工: 两设备之间可以收发数据, 但不能同一时刻.
  • 单工: 在任何时刻都只能一个方向通信, 即固定一个设备为发送设备, 另一个固定为接收设备.

3. 根据数据同步方式划分

  • 同步通信: 收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调同步数据.
  • 异步通信: 不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包, 以数据帧的格式传输数据.

二. USART流程分析

1. USART协议

  • 串口通信协议的数据包内容由: 起始位, 主体数据, 校验位以及停止位组成. 双方需要约定好相同的波特率以及数据包格式, 才能正常通信.
  • 波特率: 两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码, 图 串口数据包的基本组成 中用虚线分开的每一格就是代表一个码元。常见的波特率为4800、9600、115200等
  • 起始信号和停止信号: 串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示, 而数据包的停止信号可由0.5、1(常用)、1.5或2个逻辑1的数据位表示,只要双方约定一致即可。
  • 有效数据: 在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为5、6、7或8位长。(一般是8位)
  • 数据校验: 分为奇校验和偶校验, 奇校验, 0校验和1校验.奇校验8个数据位加上校验位,其1的数量为奇数.(比如一个8位长的有效数据为:01101001,此时总共有4个“1”, 为达到奇校验效果,校验位为“1”) 后者比较好理解(0校验是不管有效数据中的内容是什么,校验位总为“0”,1校验是校验位总为“1”).

2. USART框图分析

  • 从数据发送流程看: 数据从总线进入 ---> 发送数据寄存器(TDR) ---> 发送移位寄存器(移位寄存器不能直接将数据正确发出, 因为他是由发送控制器控制着, 而发送控制器由许多寄存器配置. 以及时钟控制这里也叫波特率)) ---> 最后进入到发送引脚TX(PA9).

3. 寄存器分析

  • 控制发送相关寄存器位分析: 从上图发送控制器可以看出, 对其有影响的位有 '发送时钟BRR', 'UE', 'PCE', 'TE', 'RXNE', 'IDLE'等. 具体描述如下图.

  • 控制接收相关寄存器位分析: 从上图接收控制器可以看出, 对其有影响的为由: '发送时钟', 'UE', 'PCE', 'RE'等. 具体描述如下图.

  • 以上两个公共要用到的寄存器位

三. USART驱动代码

1. 寄存器方式驱动

  • 以接受寄存器接受到上位机数据后, 进入中断后将数据发回给上位机.
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "delay.h"

#define BAUD        115200
char arr[256] = {0};
uint16_t len = 0;

void USART_Baud(uint32_t baud);
void USART_GPIO_Init_M(void);
void USART_Write_Bit(char *arr, uint16_t len);
void USART_Regiser_Init();
char* USART_Read_Bit();
void NVIC_USART1_Config();

int main()
{
    delay_init(72);
    USART_GPIO_Init_M();
    USART_Regiser_Init();
    NVIC_USART1_Config();
    
    while(1)
    {
        //USART_Read_Bit();
    }
}

void USART_GPIO_Init_M(void)
{
    GPIO_InitTypeDef GPIO_InitStructe;
    GPIO_InitTypeDef GPIO_InitStructe_10;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    GPIO_InitStructe.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructe.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructe.GPIO_Speed = GPIO_Speed_50MHz;
    
    GPIO_InitStructe_10.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructe_10.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    
    GPIO_Init(GPIOA, &GPIO_InitStructe);
    GPIO_Init(GPIOA, &GPIO_InitStructe_10);
    
}

void USART_Regiser_Init()
{
    USART1->CR1 |= 1 << 3;     //TE: 发送使能
    USART1->CR1 |= 1 << 13;    //UE: USART使能
    USART1->CR1 &= ~(1 << 10); //PCE: 校验位失能
    USART1->CR1 &= ~(1 << 12); //M: 字长8个数据位
    
    USART1->CR1 |= 1 << 2;     //RE使能
    USART1->CR1 |= 1 << 4;     //USART中断使能
    USART1->CR1 |= 1 << 5;     //接受缓冲区中断使能
    
    USART1->CR2 &= ~(0x03 << 12); //STOP: 1个停止位
    USART_Baud(BAUD);
}

void USART_Baud(uint32_t baud)
{
    float usart_div = 72000000 / 16.0 / baud;
    uint16_t div_mantissa = (uint16_t) usart_div;
    uint16_t div_fraction = (usart_div - div_mantissa) * 16 + 0.5;
    
    USART1->BRR = (div_mantissa << 4) + div_fraction;
}


void USART_Write_Bit(char *arr, uint16_t len)
{
    int i;
    USART1->SR;
    for(i = 0; i < len; i ++)
    {
        while((USART1->SR & (1 << 6)) == 0x00); // !!! 等待上一位发送完成需要在前面判断
        USART1->DR = arr[i];
        //while((USART1->SR & (1 << 7)) == 0x00); // !!! 放后面判断应该是发送寄存器空
    
    }
}

char* USART_Read_Bit()
{
    
    if(((USART1->SR & (1 << 5)) != 0x00))
    {
        arr[len] = USART1->DR;
        len ++;
    }
    
     if(((USART1->SR & (1 << 4)) != 0x00))
    {
        USART1->DR;
        USART_Write_Bit(arr, len);
        len = 0;
    }
    return arr;
}

void USART1_IRQHandler()
{

    if(((USART1->SR & (1 << 5)) != 0x00))
    {
        arr[len] = USART1->DR;
        len ++;
    }
    
     if(((USART1->SR & (1 << 4)) != 0x00))
    {
        USART1->DR;
        USART_Write_Bit(arr, len);
        len = 0;
    }
}

void NVIC_USART1_Config()
{
    NVIC_InitTypeDef NVIC_InitStructe;
    //NVIC分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitStructe.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructe.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructe.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructe.NVIC_IRQChannelSubPriority = 1;
    
    NVIC_Init(&NVIC_InitStructe);

}

2. 固件库方式驱动