USART-通信详解

一. 通信基本概念

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. GPIO串口通信

  • 这里以发送一个字节为例, 由于精度问题这里波特率应该太高(5000).
#include "stm32f10x.h"
#include "delay.h"

void USART_Pin_C13_Config();
void Send_Data(uint8_t data);

BitAction flage;

int main()
{
    delay_init(72);
    USART_Pin_C13_Config();
    
    while(1)
    {
        Send_Data(11); // 00001011
        delay_ms(2000);
        
    }
}

void USART_Pin_C13_Config()
{
    GPIO_InitTypeDef GPIO_InitStructe;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    
    GPIO_InitStructe.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructe.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructe.GPIO_Speed = GPIO_Speed_50MHz;
    
    GPIO_Init(GPIOC, &GPIO_InitStructe);
    GPIO_SetBits(GPIOC, GPIO_Pin_13);
}

void Send_Data(uint8_t data)
{
    int j;
    flage = 0;
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, flage);
    delay_us(200);
    
    for(j = 0; j < 8; j ++)
    {
         flage = (data & (0x01 << j) ? 1 : 0);
        GPIO_WriteBit(GPIOC, GPIO_Pin_13, flage);
        delay_us(200);
    }
    
    flage = 1;
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, flage);
    delay_us(200);
}

2. 寄存器方式驱动

  • 以接受寄存器接受到上位机数据后, 进入中断后将数据发回给上位机.
#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. 固件库方式驱动

  • 通过打印printf方式和USART_SendData()将数据发生给上位机. (通过USART_SendData()源代码可以发现, DR数据寄存器中可以一次性写8位, 会通过移位寄存器自动发送. 而不需要通过循环一位一位的写进去并等待发送完成. 我们只需要判断 {TXE发送寄存器为空并且TC发送完成寄存器为1} 即表明数据发送完成.)
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "delay.h"
#include <stdio.h>

#define BAUD        115200

void USART_GPIO_Init_M(void);
void USART_Config(uint32_t buad);
int fputc(int ch, FILE *file);

int main()
{
    int num = 100;
    delay_init(72);
    
    USART_GPIO_Init_M();
    USART_Config(BAUD);
    
    while(1)
    {
        delay_ms(2000);
        USART_SendData(USART1, 'D');
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //我们只需要判断 {TXE发送寄存器为空并且TC发送完成寄存器为1} 即表明数据发送完成.
        USART_SendData(USART1, ':');
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //我们只需要判断 {TXE发送寄存器为空并且TC发送完成寄存器为1} 即表明数据发送完成.
        printf("中华人民共和国万岁!"); 
    }
}

void USART_Config(uint32_t baud)
{
    USART_InitTypeDef USART_InitStructe;
    USART_Cmd(USART1, ENABLE);
    USART_ITConfig(USART1 ,USART_IT_RXNE | USART_IT_IDLE, ENABLE);
    
    USART_InitStructe.USART_BaudRate = baud;
    USART_InitStructe.USART_WordLength = USART_WordLength_8b;
    USART_InitStructe.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructe.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStructe.USART_Parity = USART_Parity_No;
    USART_InitStructe.USART_StopBits = USART_StopBits_1;
    
    USART_Init(USART1, &USART_InitStructe);
    
}

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);
    
}


int fputc(int ch, FILE *file)
{
    //while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); 在前面进行等待会导致发最后一个比特没有等待时间. 可能被下一次数据吃掉
    USART_SendData(USART1, (uint8_t)ch);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //后面等待的好处是使得数据发送完整.
    
    return ch;
}
posted @ 2023-09-25 22:34  烙铁666  阅读(2189)  评论(0)    收藏  举报