找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2167|回复: 4
打印 上一主题 下一主题
收起左侧

STM32串口学习_总结

[复制链接]
跳转到指定楼层
楼主
ID:689247 发表于 2021-8-29 20:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一.STM32串口介绍     
   a.串口的数据包格式为 起始位+数据位+校验位+停止位,所以一般需要设置数据位为8,校验位为1,停止位为1。我们再发送过程中只发送数据,其他的都由硬件来完成了,所以通信的双方在数据包格式配置相同时才能正确通信。
   b.除去数据包格式设置一样外,因为串口大多数都是用异步通信,由于没有时钟信号,所以2个通信设备需约定好波特率,常见的有4800、9600、115200等,波特率一致时才能正确通信。
   c.stm32的库文件中将这些需要配置的参数都写在了USART_InitTypeDef 结构体中,我们只要对其进行赋值,再调用函数USART_Init(),USART_Init函数会将USART_InitTypeDef 结构体中的数据写入相应的寄存器,这样就完成了对32串口的配置。

二.串口初始化(统一初始化)
   a.串口配置时,只有少数值需要时常更改,大部分都是重复内容,因此将常用的这些值做为参数传入。这样调用一个函数可以对所有的串口进行赋值。(串口使用的GPIO在后续的文章中统一配置)借鉴前辈的代码。
   b.串口初始化流程   开外设时钟->配置引脚(统一配置)->配置参数 ->使能中断 ->使能串口

void User_Usart_Init(USART_TypeDef* USARTx, u32 BaudRate, u16 WordLength, u16 StopBits, u16 Parity)
{
    USART_InitTypeDef USART_InitStructure;
    USART_ClockInitTypeDef USART_ClockInitStructure ;

    if(USARTx == USART1)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// Enable USART1使能或者失能APB2外设时钟  高速72MHz
    }
    else if(USARTx == USART2)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//  Enable USART2使能或者失能APB1外设时钟    低速36MHz
    }
    else if(USARTx == USART3)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);//  Enable USART3使能或者失能APB1外设时钟    低速36MHz
    }
    else if(USARTx == UART4)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4,ENABLE);//  Enable USART4使能或者失能APB1外设时钟    低速36MHz
    }
    else if(USARTx == UART5)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5,ENABLE);//  Enable USART5使能或者失能APB1外设时钟    低速36MH
    }

    USART_DeInit(USARTx);

    USART_InitStructure.USART_BaudRate = BaudRate;                 //波特率
    USART_InitStructure.USART_WordLength = WordLength;        //数据长度
    USART_InitStructure.USART_StopBits = StopBits;                      //一个停止位
    USART_InitStructure.USART_Parity = Parity;                               //无校验
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //禁止硬件流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //Receive and transmit enabled
    USART_Init(USARTx, &USART_InitStructure);                              // Configure the USART1
   
    USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;        //USART Clock disabled
    USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;            //USART CPOL: Clock is active low
    USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;         //USART CPHA: Data is captured on the second edge
    USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;     //USART LastBit: The clock pulse of the last data bit is not output to the SCLK pin
    USART_ClockInit(USARTx, &USART_ClockInitStructure);
    USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE);                  //允许接收寄存器非空中断
    USART_Cmd(USARTx, ENABLE);                                                // Enable USARTx
}

三.串口结构体   
    a.使能串口中断后,串口在接收到数据后会进入中断函数,中断函数就是我们要对数据进行整理的地方。(中断函数中不能写大量代码,有可能导下次中断来之前,数据还未处理完成,所以数据分析在后文)。
    b.stm32的串口数量很多,因此将每个串口在运行中所需要的变量整合写进一个结构体中,相对更加方面快捷。按照本人经常使用的数据,在串口对应的.H文件中写出的结构体如下,之后在.C文件中对使用的结构体进行初始化就可以了。

#define SBUF_SIZE 255                 //数据缓冲区大小
#define RBUF_SIZE 255

typedef struct
{
    u8 sbuf[SBUF_SIZE];                  //发送数组
    u8 rbuf[RBUF_SIZE];                  //接收数组
    u8 temporary_buf[RBUF_SIZE];   //接收临时存储buf
    u16 sbuf_head;                          //需要发送数据的位置
    u16 sbuf_tail;                             //需要发送数据的结束位置
    u16 rbuf_head;                           //需要发送数据的位置
    u16 rbuf_tail;                             //需要发送数据的结束位置
    u8 com_already;                         //接收到数据
    u32 com_timeout;                       //接收到数据到处理数据间延时
    uint32_t rc;                                //计数
}UART_InformationType;

//使用几个串口就可以创建几个结构体
extern UART_InformationType  UART1_Information;  //创建串口1的结构体
extern UART_InformationType  UART2_Information;
extern UART_InformationType  UART3_Information;



四.串口中断
    a.结构体写好后,接下来就是中断函数,串口中断来对接受的数据进行整理,如果串口处理数据的方法相差不是太大,都可以使用此中断函数来整理接收的数据。
    b.串口数据整理的思想,以数据接受为例:
        1.开辟两个256字节的数组,用来存放接受或者发送的数据。
        2.数据接收:给256个字节设数据头尾,每当进入一次中断,有一个数据传入就把数据写到结构体的rbuf数组中保存起来,同时把数据头rbuf_head 值+1,当数据头超过数据缓冲区大小时清零。
        3.数据处理:有数据传入就把标志位 com_already 置1,处理完数据后清0,同时更新数据尾部rbuf_tail的数值。
        4.例如:刚上电时都为0,传入8个字节正确的数据,先将8个字节的数据保存在结构体中,同时每传入一个字节数据头加1。置1标志位等待数据处理函数。 数据处理函数处理完成数据后将数据尾加8等于数据头。(此时假设数据都是正确的情况,这样就可以造成循环可以保存接受的每一个数据,详情请看第5节代码。)
    c.中断函数中只写了数据的接受,对于stm32来说,数据发送直接封装为函数更加简单方便。
/********************************************************************
*函数描述:usart1中断
*入口说明:无
*返回说明:无
**********************************************************************/
void USART1_IRQHandler(void)
{
    Dispose_USART_IRQHandler(USART1,&UART1_Information);
}

/*********************************************************************
*函数描述:usart2中断
*入口说明:无
*返回说明:无
**********************************************************************/
void USART2_IRQHandler(void)
{
    Dispose_USART_IRQHandler(USART2,&UART2_Information);
}

/*********************************************************************
*函数描述:usart3中断
*入口说明:无
*返回说明:无
**********************************************************************/
void USART3_IRQHandler(void)
{
    Dispose_USART_IRQHandler(USART3,&UART3_Information);
}

/*********************************************************************
*函数描述:usart中断,处理接受的数据
*入口说明:USART_TypeDef* USARTx   UART_InformationType* USARTx_Information
                        中断的串口                         对应串口的结构体
*返回说明:无
**********************************************************************/
void Dispose_USART_IRQHandler(USART_TypeDef* USARTx,UART_InformationType* USARTx_Information)
{
    if(USART_GetITStatus(USARTx, USART_IT_RXNE) != RESET)   //接收数据
    {
        USARTx_Information->rbuf[USARTx_Information->rbuf_head++] = (u8)USARTx->DR;
        if(USARTx_Information->rbuf_head == SBUF_SIZE)
        {
            USARTx_Information->rbuf_head = 0;
        }
        USARTx_Information->com_already = USART_SBUF_NO_EMPTY;//USART_SBUF_NO_EMPTY自定义的数值为1
       // USARTx_Information->com_timeout = Timer_1ms;                  //更新空闲计时
    }
}

/*********************************************************************
*函数描述:usart发送数据
*入口说明:USARTx:选择USART通道      
                 data:发送的数据
                 data_long:数据长度
*返回说明:无
**********************************************************************/
void Send_Usart_data(USART_TypeDef* USARTx,u8* data,u16 data_long)
{
    u16 a;
    for(a=0;a<data_long;a++)                                                                                                                                //发送数据
        {
                USART_SendData(USART1,*(data+a));
                while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);                                                
        }
}

五.数据处理
   a.串口接收完数据后,在数据处理函数中,处理相应的数据。
        在实际使用中串口通信一般会规定相应的协议举例下面两种,实际中协议复杂多样,本例子以2为基础进行代码编写。
            1. 01 03 00 00 00 02 crcl crch     
            //常用的MODBUS协议格式 01为读取的设备地址,03为功能码,00 00 为读取的寄存器 00 02 为读取的数据 ,后两位为数据校验
            2. FA 04 00 02  xx xx FF   
            //FA为规定的协议头部 04为功能码 00 02 为数据长度  xx xx 为数据 FF为数据结尾
            串口接收是,我们会收到一大串数据,我们首先要判断一串数据第一位,用IF来判断第一位是不是我们想要的数据,不是的话就判断下一位,知道找到正确数据,最后对接收到的数据进行校验,看收到的一大串数据是否正确,从而进行下一步处理。


/*********************************************************************
*函数名称:  Usart1_Dispos_Send_dat
*函数描述:usart1处发送的数据
*入口说明:无
*返回说明:无
**********************************************************************/
void Usart1_Dispos_Send_command(void)
{
    u16 i,j = 0;
    u16 m,length;
    u16 crc16 = 0;

    if(!UART1_Information.com_already)                                          //串口标志位未使能就返回
        return;
    UART1_Information.com_already = USART_SBUF_EMPTY;            //更新串口标志位
    i = UART1_Information.rbuf_tail;
    while(i != UART1_Information.rbuf_head)                                   //如果此时的数据尾等于数据头退出循环
    {        
             if(UART1_Information.rbu== 0xfa)                                   //判断数据头是不是想要的数据
             {   
                 m = i;         
                 length  = UART1_Information.rbuf[i+3]+5;                 //如果数据正确,判断数据长度,rbuf[i+3]为数据长度,再加5为一包数据的长度
                 for(j = 0;j < length  ;j++)                                            //提取每一帧数据,把数据放进临时数组
                 {
                        if(m == UART1_Information.rbuf_head)                  //提取过程中数据尾等于数据头说明长度不够不是正确的数据,返回        
                             return;
                        UART1_Information.temporary_buf[j] = UART1_Information.rbuf[m++];
                        if(m == RBUF_SIZE)
                             m = 0;
                 }
                 if(UART1_Information.temporary_buf[j-1] == 0xff)           //有效数据
                 {
                     Dispose_SVR_Commd(UART1_Information.temporary_buf); //处理临时数组数据
                     UART1_Information.rbuf_tail = m;   

                     i=m;                  
                  }
                 else                                                                              //无效数据i++进行下一位的判断
                 {
                      i++;
                      if(i == RBUF_SIZE)                                                   //如果i等于数组上限清零
                             i = 0;
                  }
              }         else                                                                                  //如果第一位不是想要的数据,进行下一位判断
             {
                  i++;
                  if(i == RBUF_SIZE)
                       i = 0;
              }
        }
}

/*********************************************************************
*函数名称: Dispos_Commd
*函数描述:处理服务器发送的指令
*入口说明:P_tbuf:保存服务器指令数组的指针
*返回说明:无
**********************************************************************/
void Dispos_Commd(u8 * p)
{
   u8 function,length;
   u16 register_addr;

    function = *(p+1);
    register_addr =  *(p+3);
    length= *p;

    if(function==0x04)                                                                         //功能码判,功能吗为自定义的功能
          Write_Data(UART1_Information.temporary_buf,length);               //写入数据
    //if else() {}                                                                                 
    else{}                                                                                            //
           return;
}


/*********************************************************************
*函数名称: Write_Data
*函数描述:写入数据
*入口说明:buf 要写入的数据 ,length 要写入的数据长度
*返回说明:无
**********************************************************************/
void Write_Data(u8 *p,u8 length)
{
     u8 length = 0;
     u8 buf[10]={0};
      //自己定义写到flash中或者各种地方
     //下列数据是需要的返回的数据,可以写数据返回成功,写可以返回一些其他数据,供发送者观看,或者判段是否接收成功

     buf[length++] = 0xfa;
     buf[length++] = 0x04;
     buf[length++] = 0x00;
     buf[length++] = 0x02;
     buf[length++] = 0x00;
     buf[length++] = 0x00;
     buf[length++] = 0xff;
     Send_Usart_data(USART1,buf,length);
}


STM32F103X模板.7z

177.76 KB, 下载次数: 18, 下载积分: 黑币 -5

评分

参与人数 2黑币 +55 收起 理由
1096131187 + 5 很给力!
admin + 50

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏5 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:689247 发表于 2021-9-17 17:29 | 只看该作者
1.修改了文章中出现的一些bug
例如发送数据 Send_Usart_data  函数中 ,书写错误。
//           USART_SendData(USARTx,*data);                      //发送一位数据
//           data++;                                                              //指向下一个发送的数据
//           USART_ITConfig(USARTx, USART_IT_TC, ENABLE);
等等

2.上传了附件
附件内容只添加串口的部分应用.
可以作为模板来使用
    里面统一书写了GPIO初始化,方便统一管理,和修改
void User_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
/*
    GPIO_Speed_10MHz -------最高输出速率10MHz
    GPIO_Speed_2MHz --------最高输出速率2MHz
    GPIO_Speed_50MHz -------最高输出速率50MHz

    GPIO_Mode_AIN ----------模拟输入
    GPIO_Mode_IN_FLOATING --浮空输入
    GPIO_Mode_IPD ----------下拉输入
    GPIO_Mode_IPU ----------上拉输入
    GPIO_Mode_Out_OD -------开漏输出
    GPIO_Mode_Out_PP -------推挽输出
    GPIO_Mode_AF_OD --------复用开漏输出
    GPIO_Mode_AF_PP --------复用推挽输出
*/
    GPIO_DeInit(GPIOA); //将外设GPIOx寄存器重设为缺省值
    GPIO_DeInit(GPIOB);
    GPIO_DeInit(GPIOC);
       
#ifdef STM32F10X_CL                //使用更高级的芯片就初始化更多的引脚
    GPIO_DeInit(GPIOD);
    GPIO_DeInit(GPIOE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE,ENABLE);
#endif
   
        GPIO_AFIODeInit();                                                                                                                                                          //将复用功能(重映射事件控制和EXTI设置)重设为缺省值
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);         //使能APB2外设时钟,高速72MHz
   
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;


    //设置模拟输入端口    ad/da都设置为模拟输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

//    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;                       //AD输入
//    GPIO_Init(GPIOA, &GPIO_InitStructure);                          //端口A

//    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|
//                                  GPIO_Pin_3;                       //AD输入
//    GPIO_Init(GPIOC, &GPIO_InitStructure);                          //端口C


    //设置浮空输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

//    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;                       //ETH中断输入
//    GPIO_Init(GPIOE, &GPIO_InitStructure);                          //端口E
      。。。。
}

3.程序增加了时基单元  详情还是看代码吧   注意STM32F10X.it函数中书写滴答定时器中断函数
/*********************************************************************
*函数描述:循环发送数据到服务器
*入口说明:无
*返回说明:无
*说明:利用好滴答定时器,就可使系统按照指定的时间执行程序 不用再使用delay去降低
单片性能。类似此处就定时1s发送一包数据。
        timer_1ms 随抵达定时器1ms数值增长一次
        usart_send_time 是1s更新一次数值(对照timer_1ms)
        timer_1ms 可以当作是整个系统运行的时基
**********************************************************************/
void Cycle_Send_SenerData_To_SVR(void)
{
        if(timer_1ms - usart_send_time < 1000)                                                //定时时间为1s                                               
                return;
        usart_send_time = timer_1ms;                                                                //更新usart_send_time时间
        USART_SendData(USART1,0xaa);
}

评分

参与人数 1黑币 +40 收起 理由
admin + 40 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

板凳
ID:689247 发表于 2021-9-18 10:36 | 只看该作者
发送指令是
FA     03       00 04        00 01       00 02         ff
头 功能吗   数据长度     起始地址  询问长度    数据尾
回复

使用道具 举报

地板
ID:584195 发表于 2021-9-19 20:22 | 只看该作者
楼主,谢谢你的分享,学习了,能不能把DMA的收发也做个教程呀!
回复

使用道具 举报

5#
ID:689247 发表于 2021-9-22 14:20 | 只看该作者
zyluglugl 发表于 2021-9-19 20:22
楼主,谢谢你的分享,学习了,能不能把DMA的收发也做个教程呀!

最近比较忙,而且没咋用过DMA,等用空了,总结一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表