【无标题】

通信

目录

通信

USART--串口通信

物理层

协议层

串口的功能框图

引脚功能

数据寄存器—USART_DR(重要)

配置位有那些常用

具体流程

第三节具体的函数

第四节编程中断接收和发送

USB转串口原理图

补江科大

数据模式

数据发送接收

传送乱码解决

串口发送+接收

USART串口数据包

数据包格式的收发流程


uploading.4e448015.gif

正在上传…重新上传取消

串行:串口,UART,I2C,SPI,W500(16根数据线)

并行:SPIO,FSMC(16位的并行数据线),W5100

USART全双工,SPI全双工,

有时钟就是同步

对时钟要求比较高。

USART--串口通信

uploading.4e448015.gif

正在上传…重新上传取消

物理层

TTL电平从单片机或者从芯片里面出来的都叫TTL电平,在STM32里用3.3v来表示

RS-232高电平0,低电平是1,高低电平之差是30V

电平转换芯片是232。

霸道有232串口,在指南针可以扩展232串口(网上买)

uploading.4e448015.gif

正在上传…重新上传取消

控制器STM32

协议层

uploading.4e448015.gif

正在上传…重新上传取消

在STM32还有九个位

校验位

uploading.4e448015.gif

正在上传…重新上传取消

串口的功能框图

uploading.4e448015.gif

正在上传…重新上传取消

引脚功能

那些引脚有这些功能呢?

uploading.4e448015.gif

正在上传…重新上传取消

串口重映射

数据寄存器—USART_DR(重要)

串口是全双工的,即TDR与RDR同时收发

配置位有那些常用

具体流程

UE串口使能

TE与RE都打开后数据可以正常的接收了

发送过程(先UE=1,TE=1)

当TXE=1

uploading.4e448015.gif

正在上传…重新上传取消

接收过程先UE =1,TE=1

uploading.4e448015.gif

正在上传…重新上传取消

还有其他的位

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

这些是和中断有关的位

控制波特率的寄存器

uploading.4e448015.gif

正在上传…重新上传取消

小数部分要取最小的精度波特率怎么计算

第三节具体的函数

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

第四节编程中断接收和发送

uploading.4e448015.gif

正在上传…重新上传取消

USB转串口原理图

要安装CH340驱动软件加打开野火工具

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

第一步,开启时钟,把需要用到的USART和GPIO的时钟打开

第二步GPIO初始化,把TX配置成复用输出,RX配置成输入。TX配置复用推挽输出,RX不配置下拉输入就可以

第三步配置USART,直接使用一个结构体,

第四步如果只是需要发送的功能直接开启USART,初始化就结束了。如果需要接收的功能还需要配置中断,在开启USART之前加上ITConfig和NVIC的代码就可以了。。如果要发送数据调用接收的函数就可以了。如果要调用发送的数据调用发送的函数就可以了。如果要获取发送和接收的状态,就调用获取标注位的函数

补江科大

状态寄存器SR(Status Register)存放各种标志位

数据寄存器DR(Data Register) 存放关键数据

配置寄存器CR(Config Register)存放各种配置参数

这三类寄存器基本上每个外设都有

#include "stm32f10x.h"                  // Device header

void Serial_Init(void)
{    GPIO_InitTypeDef PA10;
    USART_InitTypeDef USAASS;
    //开启时钟
    
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    //开启PA10,PA9
    
    PA10.GPIO_Mode=GPIO_Mode_AF_PP;
    PA10.GPIO_Pin=GPIO_Pin_9;
    PA10.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&PA10);
    
    
    USAASS.USART_BaudRate=9600;
    //不适用模式
    USAASS.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
    //写模式
    USAASS.USART_Mode=USART_Mode_Tx;
    //校验位不需要
    USAASS.USART_Parity=USART_Parity_No;
    //选择一位停止位
    USAASS.USART_StopBits=USART_StopBits_1;
    //字长
    USAASS.USART_WordLength=USART_WordLength_8b;
    USART_Init(USART1,&USAASS);
    
    //开启USART1
    USART_Cmd(USART1,ENABLE);
    
    
    
    
}



void Serial_SendByte(uint8_t Byte)
{
    //把数据传入串口
    USART_SendData(USART1,Byte);
    //把数据写入TDR了,写完了之后,等TDR转移到移位寄存器才可以放心。
    //如果数据在TDR等待我们在写入数据,就会数据覆盖。所以在发送后我们还需要等待一下标志位
    //等待这个位TXE置一
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
    //不需要手动清零
    
}


int main(void)
{
    Serial_Init();
    Serial_SendByte(0x41);
    //就会产生0x41对应的波形,可以通过USB转串口传电脑
    while(1)
    {
    
    
    }
}

数据模式

HEX模式,以原始数据形式显示,收到什么数据显示什么数据,

文本模式/字符模式:以原始数据编码后的形式显示,每个数据通过查找字符集显示。

数据发送接收

数据的发送和接收都是原始数据的形式

uploading.4e448015.gif

正在上传…重新上传取消

使用printf必须打开

#include "stm32f10x.h"                  // Device header

void Serial_Init(void)
{    GPIO_InitTypeDef PA10;
    USART_InitTypeDef USAASS;
    //开启时钟
    
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    //开启PA10,PA9
    
    PA10.GPIO_Mode=GPIO_Mode_AF_PP;
    PA10.GPIO_Pin=GPIO_Pin_9;
    PA10.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&PA10);
    
    
    USAASS.USART_BaudRate=9600;
    //不适用模式
    USAASS.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
    //写模式
    USAASS.USART_Mode=USART_Mode_Tx;
    //校验位不需要
    USAASS.USART_Parity=USART_Parity_No;
    //选择一位停止位
    USAASS.USART_StopBits=USART_StopBits_1;
    //字长
    USAASS.USART_WordLength=USART_WordLength_8b;
    USART_Init(USART1,&USAASS);
    
    //开启USART1
    USART_Cmd(USART1,ENABLE);
    
    
    
    
}



void Serial_SendByte(uint8_t Byte)
{
    //把数据传入串口
    USART_SendData(USART1,Byte);
    //把数据写入TDR了,写完了之后,等TDR转移到移位寄存器才可以放心。
    //如果数据在TDR等待我们在写入数据,就会数据覆盖。所以在发送后我们还需要等待一下标志位
    //等待这个位TXE置一
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
    //不需要手动清零
    
}


int main(void)
{
    Serial_Init();
    Serial_SendByte(0x41);
    
    //sprintf()使用 
    char sting[100];//定义100个字符
    sprintf(sting,'num=%d\r\n',666);
    Serial_SendByte(sting);
    
    //就会产生0x41对应的波形,可以通过USB转串口传电脑
    while(1)
    {
    
    
    }
}


#include <stdio.h>
//发送数字函数

void Serial_SendArray(uint8_t *Array,uint16_t Length)
{
    uint16_t i;
    for(i=0;i<Length;i++)
    {
        Serial_SendByte(Array[i]);
    }
}

//发送字符串函数
void serial_sendstring(char *string)
{
    uint8_t i;
    for (i=0; string[i]!='0';i++)
    {Serial_SendByte(string[i]);
    }
    
}

//次方函数
uint32_t serial_pow(uint32_t X,uint32_t Y)
{
    uint32_t Result=1;
    while (Y--)
    {
        Result*=X;
        
    }
    return Result;
}


//发送数字函数
void Serial_sendNumber(uint8_t Number,uint8_t Length)
{
    uint8_t i;
    for(i=0;i<Length;i++)
    {
        Serial_SendByte(Number/serial_pow(10,Length-i-1)%10+'0');
        //加上字符的0
    
    }
        
}
//printf函数的移值方法
//要#include<stdio.h>
//主函数输入fputc("num=%d\r\n",666)输出num=666
int fputc(int ch,FILE *f)
{
    Serial_SendByte(ch);
    return ch;
}


//sprintf如果多个串口想用都可以

//封装sprintf
#include<stdarg.h>
void serial_printf(char *format,...)
{
    char sting[100];
    va_list arg;
    va_start(arg,format);
    vsprintf(sting,format,arg);
    va_end(arg);
    Serial_SendByte(sting);
}

传送乱码解决

我们选的UTF-8编码,所以终发送代串口,汉字会以UTF-8的方式编码

方法一

需要给编译器输入参数 --no-multibyte-chars

方法二

切换为GN2312

但当时文件还是UTF-8,需要重新删除文件再打开文件

USART_FLAG_RXNE 是USART的RXNE标志位

串口发送+接收

读查询方式

#include "stm32f10x.h"                  // Device header

void Serial_Init(void)
{    GPIO_InitTypeDef PA10;
    //定义两个脚可以只用一个结构体,
    USART_InitTypeDef USAASS;
    //开启时钟
    
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    //开启PA10,PA9
    
    PA10.GPIO_Mode=GPIO_Mode_AF_PP;
    PA10.GPIO_Pin=GPIO_Pin_9;
    PA10.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&PA10);
    
    PA10.GPIO_Mode=GPIO_Mode_IPU;
    PA10.GPIO_Pin=GPIO_Pin_10;
    PA10.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&PA10);
    
    
    USAASS.USART_BaudRate=9600;
    //不适用模式
    USAASS.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
    //写模式+读模式
    USAASS.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
    //校验位不需要
    USAASS.USART_Parity=USART_Parity_No;
    //选择一位停止位
    USAASS.USART_StopBits=USART_StopBits_1;
    //字长
    USAASS.USART_WordLength=USART_WordLength_8b;
    USART_Init(USART1,&USAASS);
    
    //开启USART1 对USART来说有查询和中断俩个模式。使用查询就结束了。如果使用中断要开启中断
    //查询的流程是在主函数不断的判读RXNE标志位,如果置1,说明收到了数据了,在调用ReciveData,读取寄存器就可以了
    USART_Cmd(USART1,ENABLE);
    
    
    
    
}



void Serial_SendByte(uint8_t Byte)
{
    //把数据传入串口
    USART_SendData(USART1,Byte);
    //把数据写入TDR了,写完了之后,等TDR转移到移位寄存器才可以放心。
    //如果数据在TDR等待我们在写入数据,就会数据覆盖。所以在发送后我们还需要等待一下标志位
    //等待这个位TXE置一
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
    //不需要手动清零
    
}

uint8_t RxData;
int main(void)
{
    Serial_Init();
    Serial_SendByte(0x41);
    
    
    
    //就会产生0x41对应的波形,可以通过USB转串口传电脑
    while(1)
    {//读取RXNE位
    if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
    {//数据放入RxData
        //读RXData自动清除标志位(手册可查USART_SR状态寄存器)
        RxData=USART_ReceiveData(USART1);
        //展示第一行第一列长度为2
        OLED_ShowHexNum(1,1,RxData,2);
    }
    }
}

读中断查询方式

#include "stm32f10x.h"                  // Device header

void Serial_Init(void)
{    GPIO_InitTypeDef PA10;
    //定义两个脚可以只用一个结构体,
    NVIC_InitTypeDef NVIC_InitStructure;
    USART_InitTypeDef USAASS;
    //开启时钟
    
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    //开启PA10,PA9
    
    PA10.GPIO_Mode=GPIO_Mode_AF_PP;
    PA10.GPIO_Pin=GPIO_Pin_9;
    PA10.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&PA10);
    
    PA10.GPIO_Mode=GPIO_Mode_IPU;
    PA10.GPIO_Pin=GPIO_Pin_10;
    PA10.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&PA10);
    
    
    USAASS.USART_BaudRate=9600;
    //不适用模式
    USAASS.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
    //写模式+读模式
    USAASS.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
    //校验位不需要
    USAASS.USART_Parity=USART_Parity_No;
    //选择一位停止位
    USAASS.USART_StopBits=USART_StopBits_1;
    //字长
    USAASS.USART_WordLength=USART_WordLength_8b;
    USART_Init(USART1,&USAASS);
    //开启RXNE标志位到NVIC(中断)的输出
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    
    
    //NVIC设置
    //选择中断组2
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2);
    
    NVIC_InitStructure.NVIC_IRQChannel= USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
    NVIC_Init(&NVIC_InitStructure);
    //然后在中断函数设置 中断函数的名字必须在启动文件找
    
    //开启USART1 对USART来说有查询和中断俩个模式。使用查询就结束了。如果使用中断要开启中断
    //查询的流程是在主函数不断的判读RXNE标志位,如果置1,说明收到了数据了,在调用ReciveData,读取寄存器就可以了
    USART_Cmd(USART1,ENABLE);
    
    
    
    
    
    
}
uint8_t serial_Rxdata;
uint8_t serial_Rxflag;


void Serial_SendByte(uint8_t Byte)
{
    //把数据传入串口
    USART_SendData(USART1,Byte);
    //把数据写入TDR了,写完了之后,等TDR转移到移位寄存器才可以放心。
    //如果数据在TDR等待我们在写入数据,就会数据覆盖。所以在发送后我们还需要等待一下标志位
    //等待这个位TXE置一
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
    //不需要手动清零
    
}

uint8_t RxData;
int main(void)
{
    uint8_t serial__getRxData(void);
    uint8_t serial_getRxflag(void);
    Serial_Init();
    Serial_SendByte(0x41);
    
    
    
    
    
    //就会产生0x41对应的波形,可以通过USB转串口传电脑
    while(1)
    {//读取RXNE位
    if(serial_getRxflag()==1)
    {//数据放入RxData
        //读RXData自动清除标志位(手册可查USART_SR状态寄存器)
        RxData=serial__getRxData();
        Serial_SendByte(RxData);
        //展示第一行第一列长度为2
        OLED_ShowHexNum(1,1,RxData,2);
    }
    }
}



//读后自动清除的功能
uint8_t serial_getRxflag(void)
{
    if(serial_Rxflag==1)
    {
        serial_Rxflag=0;
        return 1;
    }return 0;
}
uint8_t serial__getRxData(void)
{
    return serial_Rxdata;
}


//中断函数的名字找到后重定义,写中断函数
void USART1_IRQHandler(void)
{//读取RXNE位
    if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
{
    //如果读取RXNE位就不用清零(表示数据来到移位寄存器),不读就要清
    //读取输入
    serial_Rxdata=USART_ReceiveData(USART1);
    serial_Rxflag=1;
    //清除RXNE位
    USART_ClearFlag( USART1, USART_IT_RXNE);
}
}

USART串口数据包

数据包作用是:把一个个单独的数据给打包起来,方便我们进行多字节的数据通信。在之前我们写了单个字节的发送和接收。但在实际应用中,我们需要把多个字节打包为一个整体来进行发送。

数据包格式的定义

HEX数据包

比如说我们有一个陀螺仪传感器,需要我们用串口发送数据到STM32,陀螺仪的数据,比如说X轴一个字节,Y轴一个字节,Z轴一个字节。总共三个数据需要连续不断的发送,当你像这样,XYZXYZXYZ连续的发送的时候。就会出现一个问题就是接收方,它不知道这数据哪个对应X,哪个对应Y,哪个对应Z。因为接收方可能会从任意位置开始接收。所以数据会出现错位的现象.这个时候我们需要研究一种方式需要对数据进行分割,把XYZ这一批数据分割开,分成一个个数据包。这样接收的时候就知道了,数据包第一个数据是X,第二个是Y,第三个是Z。这就是数据包的任务,就是把属于同一批的数据进行打包和分割,方便接收方进行识别。关于分割打包的方法可以自己设计。比如说设置在XYZXYZ数据包中,数据包的第一个数据是,也就是X的数据包,他最高位置一,其余数据包最高位置0。当我接收到数据就判断一下最高位,如果是1就是X数据,然后紧跟着的数据就是YZ。实际的例子就是UTF-8的编码方法。但这种数据包分割方法破环了原有的数据,使用起来比较复杂。我们的串口数据包,通常使用的是额外添加包头包尾的方式。比如说下图

第一种是固定包长,也就是每个数据包的长度都固定不变。数据包前面是包头后面是包尾。

第二种是可变包长,含包头包尾。也就是每个数据包的长度可以是不一样的。

他们的格式可以根据需求,自己规定的,也可以是自己买个模块,别的开发者规定的。那这里规定的是比如固定包长这里,一批数据规定有4个字节,在这4个字节之前加一个包头。比如定义0XFF为包头,在4个字节之后,加一个包尾,比如定义0XFE为包尾。当我们接收到0XFF之后,我就知道了一个数据包来了,接着再接收4个字节。就当作数据包的第1,2,3,4个数据,存在一个数组里。最后跟一个包尾,当我们收到0XFE之后就可以置一个标志位,告诉程序,我收到了一个数据包。然后新的数据包过来,再重复之前的过程。这样就可以再一个连续不断的数据流中分割出我们想要的数据包了。

问题一:包头包尾和数据载荷重复的问题

定义0XFF为包头,0XFE为包尾,如果我传输的数据就是0XFE和0XFF怎么办?

如果数据和包头包尾重复可能会引起误判,对于这个问题有以下几个解决方法。

第一种,限制载荷数据的范围。如果可以的话,我们可以在发送的时候,对数据进行限幅,比如XYZ三个数,变化的范围都可以是0~100.那我们可以在载荷中只发送0~100的数据。这样就不会和包头包尾重复了。

第二种,如果无法避免载荷数据和包头包尾重复,那我们就尽量使用固定长度的数据包。只要我们通过包头包尾对齐了数据。我们就可以知道哪个数据是包头包尾,哪个数据是载荷数据。在接收数据时,我们会判断它是不是包头包尾,在接收数据时,我们会判断它是不是确实是包头包尾。用于数据对齐。这样在经过几个数据包的对齐之后,剩下的数据包就不会出现问题了。

第三种,增加包头包尾的数量,并且让它尽量呈现出载荷数据出现不了的状态。比如使用FF,FE为包头,FD,FC为包尾。这样也可以避免载荷数据和包头包尾重复的情况发送。

第二个问题,这个包头包尾并不是全部都需要的。可以只要一个包头,把包尾去掉,这样数据包的格式就是一个包头FF,加4个数据。这样也可以,不过载荷和包头重复问题更严重一些。不确定包头FF和数据FF。

第三个问题,固定包长和可变包长的选择

对于HEX数据包来说,如果载荷出现和包头包尾重复的情况,那就最好选择固定包长,这样可以避免接收错误。如果又会重复,又可以选择可变包长,那么数据容易就乱套了。 ----- 如果载荷不会和可变包头包尾重复,那可以选择可变包长。数据长度像这样,4位,3位等待,1位,10位来回任意变肯定没问题。因为包头包尾是唯一的,只要出现包头,就开始数据包,只要出现包尾就结束数据包,这样就非常灵活了。

第四个问题,各种数据转换成字节流的问题

这里的数据包都是一个字节一个字节组成的,如果想发送16位的整形数据,32位的整型数据,float,double都可以,甚至是结构体,其实都没问题。因为它们内部都是由一个字节一个字节组成的,只需用一个uint8_t指向它,把它们当成一个字节数组发送就可以了

文本数据包

文本数据包和HEX数据包就分别对应了文本模式和HEX模式,在HEX数据包里,数据都是以原始的字节数据本身呈现的,而在文本数据包里,每个字节都经过了一层编码和译码。最终表现出来的就是文本格式,但实际上每个文本字符的背后,其实都还是一个HEX数据

这同样也是固定包长和可变包长两种模式,由于数据译码成为了字符形式,就会存在大量的字符可以作为包头和包尾,可以有效避免载荷和包头包尾重复的问题。比如图中规定的是@作为包头,\r\n也就是换行符作为包尾。在载荷数据中间出现除了包头包尾的任意字符。在包头包尾之间可以出现除了包头包尾的任意字符。这很容易做到,所以文本数据包基本不用担心载荷和包头包尾重复的问题,使用非常灵活。可变包长,各种字母,符号,数字,都可以任意使用。当我们接收到载荷数据之后得到的就是一个字符串,在软件中再对字符串进行操作和判断。就可以实现各种指令控制的功能。而且字符串数据包表达的意义很明显,可以把字符串数据直接打印到串口助手上。什么数据,什么指令一眼就可以看明白。所以这个文本数据包通常会以换行为包尾。

HEX数据包与文本数据包的优劣

HEX数据包 优先是传输最直接,解析数据非常简单。比较适合模块发送原始的数据,比如一些使用串口通信的陀螺仪,温湿度传感器。缺点是灵活性不足,载荷容易和包头包尾重复。

文本数据包,优点是数据直接易理解,非常灵活。比较适合一些输入指令进行人机交互的场合。比如蓝牙模块常用的AT指令,CNC和3D打印机常用的G代码。都是文本数据包的格式。缺点是解析的效率低。比如发送数据100,HEX数据包就是一个字节100,文本数据包就是三个字节的字符。‘1’,‘0’,‘0’。收到后要把字符转成数据,才能得到100

数据包格式的收发流程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小C学Python青春版

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值