0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

HAL库UART在cubemx中的配置

jf_L18yujSQ 来源:小飞哥玩嵌入式 2023-07-05 09:47 次阅读

串口原理图

串口1咱们已经用作rtt的print使用了,所以使用另外一组串口来进行串口的教程,这里一定要注意下,alios的这个板子原理图是有点问题的,标注的是串口3PA2和PA3,实际上小飞哥调了好久,最后万用表量引脚才发现是原理图标注错误,实际上是UART4,PA0和PA1

86dfda86-1a6f-11ee-962d-dac502259ad0.png

cubemx中引脚选择预配置

选择PA0、PA1,配置为串口模式,波特率什么的见图示:

8712d2c4-1a6f-11ee-962d-dac502259ad0.png

开启中断,优先级可以根据自己的需求配置,本次不使用DMA,所以DMA就先不进行配置了

8755e60e-1a6f-11ee-962d-dac502259ad0.png

配置是非常简单的,就不多啰嗦了,配置完直接生成代码就OK了

HAL库串口代码详解

cubemx里面配置了一大堆,生成的应用代码主要在初始化中:

876ece94-1a6f-11ee-962d-dac502259ad0.png87a026a6-1a6f-11ee-962d-dac502259ad0.png

关于串口的接口是很多的,本次主要使用3个接口,发送、接收和接收回调

87c89cbc-1a6f-11ee-962d-dac502259ad0.png

HAL库数据接收的设计思想是底层配置完成后,暴露给用户的是一组回调函数,用户不用关心底层实现,只需要关注应用层逻辑即可,回调函数是定义为_weak属性的接口,用户可以在应用层实现

/**
*@briefRxTransfercompletedcallback.
*@paramhuartUARThandle.
*@retvalNone
*/
__weakvoidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart)
{
/*Preventunusedargument(s)compilationwarning*/
UNUSED(huart);

/*NOTE:Thisfunctionshouldnotbemodified,whenthecallbackisneeded,
theHAL_UART_RxCpltCallbackcanbeimplementedintheuserfile.
*/
}

发送也有对应的callback,我们只需要在callback处理我们的逻辑即可。

串口收发设计

教程不玩虚的,本章节小飞哥从实际应用出发,通过解析协议数据,顺便讲解uart的收发设计。

1、串口接收:

先来看看HAL库串口接收的接口函数,这就是使用库函数的好处,底层实现不用关心,只要会用接口就行了

/**
*@briefReceiveanamountofdataininterruptmode.
*@noteWhenUARTparityisnotenabled(PCE=0),andWordLengthisconfiguredto9bits(M1-M0=01),
*thereceiveddataishandledasasetofu16.Inthiscase,Sizemustindicatethenumber
*ofu16availablethroughpData.
*@paramhuartUARThandle.
*@parampDataPointertodatabuffer(u8oru16dataelements).
*@paramSizeAmountofdataelements(u8oru16)tobereceived.
*@retvalHALstatus
*/
HAL_StatusTypeDefHAL_UART_Receive_IT(UART_HandleTypeDef*huart,uint8_t*pData,uint16_tSize);

如何使用这个接口接收数据呢?

从接口描述可以看到,第1个参数是我们的串口号,第2个参数数我们用于接收数据的buffer,第3个参数是数据长度,即要接受的数据量,这里我们每次仅接收一个数据即进入逻辑处理

每次取一个数据,放到rxdata的变量中

HAL_UART_Receive_IT(&huart4,&rxdata,1);

HAL库所有的串口是共享一个回调函数的,那么如何区分数据是来自哪一个串口的?这个逻辑可以在应用实现,区分不同的串口号,根据对应的串口号实现对应的逻辑即可

voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart)
{

if(huart->Instance==UART4)
{
//rt_sem_release(sem_uart_rec);
embedded_set_uart_rec_flag(RT_TRUE);
embedded_set_uart_timeout_cnt(0);
HAL_UART_Receive_IT(&huart4,&rxdata,1);
mb_process_frame(rxdata,CHANNEL_MODBUS);
}
}

2、数据帧接收完成判断

通讯基本上都是不定长数据的接收,一般对于一个完整的通讯帧来说,是有长度字段的,分以下几种接收完成判断方式

特殊数据格式,比如结束符,像正点原子串口教程的“回车、换行(0x0D,0x0A)”

数据长度,适用已知数据长度的数据帧,根据接收到的数据长度跟数据帧里面的长度是否一致,判断接受是否完成

超时判断,定时器设计一个超时机制,一定时间内没有数据进来即认为数据传输结束

空闲中断,串口是有个空闲中断的,这个实现类似于超时机制

也可以从软件设计实现,比如设计一个队列,取数据即可,队列中没数据即认为数据接受完成

方式有很多,本章节主要使用数据长度和定时器超时两种方式来讲解

3、串口发送

串口发送比较简单,先来看看发送接口函数,类似接收函数,只需要把我们的数据放进发送buffer,启动发送即可

/**
*@briefSendanamountofdatainblockingmode.
*@noteWhenUARTparityisnotenabled(PCE=0),andWordLengthisconfiguredto9bits(M1-M0=01),
*thesentdataishandledasasetofu16.Inthiscase,Sizemustindicatethenumber
*ofu16providedthroughpData.
*@noteWhenFIFOmodeisenabled,writingadataintheTDRregisteraddsone
*datatotheTXFIFO.WriteoperationstotheTDRregisterareperformed
*whenTXFNFflagisset.Fromhardwareperspective,TXFNFflagand
*TXEaremappedonthesamebit-field.
*@paramhuartUARThandle.
*@parampDataPointertodatabuffer(u8oru16dataelements).
*@paramSizeAmountofdataelements(u8oru16)tobesent.
*@paramTimeoutTimeoutduration.
*@retvalHALstatus
*/
HAL_StatusTypeDefHAL_UART_Transmit(UART_HandleTypeDef*huart,constuint8_t*pData,uint16_tSize,uint32_tTimeout);

数据接收及协议帧解析设计

数据接收:

基于数据长度和超时时间完成数据帧发送完成的判断:

定时器中断回调设计,实现逻辑为,当收到串口数据时,开始计时,超过100ms无数据进来,认为数据帧结束,同时释放数据接收完成的信号量,接收到接受完成的信号量之后,重置一些数据,为下一次接收做好准备

voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim)
{
if(htim->Instance==TIM15)
{
//if(RT_EOK==rt_sem_take(sem_uart_rec,RT_WAITING_NO))
//{
if(embedded_get_uart_rec_flag())
{
/*100ms超时无数据接收*/
if(embedded_get_uart_timeout_cnt()>9)
{
embedded_set_uart_rec_flag(RT_FALSE);

rt_sem_release(sem_uart_timeout);
}
}

//}
}
}

串口回调设计:

串口回调要实现的逻辑比较简单,主要是数据接收、解析:

voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart)
{
if(huart->Instance==UART4)
{
//rt_sem_release(sem_uart_rec);
embedded_set_uart_rec_flag(RT_TRUE);
embedded_set_uart_timeout_cnt(0);
HAL_UART_Receive_IT(&huart4,&rxdata,1);
process_frame(rxdata,CHANNEL_UART4);
}
}

/协议架构/

/数据头(2字节)+数据长度(2字节,不包含数据头)+功能码+数据+校验码(CRC16-MODBUS)/

我们采用这个协议框架来解析数据,数据解析可以设计成一个简单的状态机,根据每一步决定下一步做什么

比如针对上面的协议,我们就可以分几步设计:

1、解析数据头1;

2、解析数据头2;

3、解析数据长度;

4、接收数据;

5、校验数据CRC;

6、调用命令回调函数;

把握好这个步骤,设计其实非常简单

先来定义一个简单的枚举,表示每一个状态:

typedefenum
{
STATUS_HEAD1=0,
STATUS_HEAD2,
STATUS_LEN,
STATUS_HANDLE_PROCESS
}frame_status_e;

然后封装数据解析函数:

/*协议架构*/

/**数据头(1字节)+数据长度(2字节,不包含数据头)+功能码+数据+校验码(CRC16-MODBUS)**/

#definePROTOCOL_HEAD10x5A
#definePROTOCOL_HEAD20xA5

intprocess_frame(constuint8_tdata,constuint8_tchannel)
{
uint16_tcrc=0;
uint16_tlen=0;

staticframe_status_eframe_status;
staticuint16_tindex=0;

/*timeoutresetthereceivestatus*/
if(RT_EOK==rt_sem_take(sem_uart_timeout,RT_WAITING_NO))
{
index=0;
frame_status=STATUS_HEAD1;
}
switch(frame_status)
{
caseSTATUS_HEAD1:
if(data==PROTOCOL_HEAD1)
{
frame_status=STATUS_HEAD2;
buffer[index++]=data;
}
else
{
frame_status=STATUS_HEAD1;
index=0;
}
break;
caseSTATUS_HEAD2:
if(data==PROTOCOL_HEAD2)
{
frame_status=STATUS_LEN;
buffer[index++]=data;
}
else
{
frame_status=STATUS_HEAD1;
index=0;
}
break;
caseSTATUS_LEN:
if(data>=0&&data<= MAX_DATA_LEN)
        {
            frame_status = STATUS_HANDLE_PROCESS;
            buffer[index++] = data;
        }
        else
        {
            frame_status = STATUS_HEAD1;
            index = 0;
        }
        break;
    case STATUS_HANDLE_PROCESS:
        buffer[index++] = data;
        len = buffer[LEN_POS];
        if (index - 3 == len)
        {
            crc = embedded_mbcrc16(buffer, index - 2);
            if (crc == (buffer[index - 1] | buffer[index - 2] << 8))
            {
                call_reg_cb(buffer, index, channel, buffer[CMD_POS]);
            }
            index = 0;
            frame_status = STATUS_HEAD1;
        }

        break;

    default:
        frame_status = STATUS_HEAD1;
        index = 0;
    }
}

对用的功能函数:

我们采用 attribute at机制的方式,将我们的回调函数注册进去:

typedefvoid(*uart_dispatcher_func_t)(constuint32_t,constuint8_t*,constuint32_t);
typedefstructuart_dispatcher_item
{
union
{
struct
{
uint8_tchannel;
uint8_tcmd_id;
};

uint32_tmagic_number;
};

uart_dispatcher_func_tfunction;

}uart_dispatcher_item_t;

#defineUART_DISPATCHER_CALLBACK_REGISTER(ch,id,fn)staticconstuart_dispatcher_item_tuart_dis_table_##ch##_##id
__attribute__((section("uart_dispatcher_table"),__used__,aligned(sizeof(void*))))=
{.channel=ch,.cmd_id=id,.function=fn}
intcall_reg_cb(uint8_t*frame,uint8_tdata_len,intchannel,uint8_tcmd_id);

回调函数:

这样设计可以把驱动层,协议解析层和应用层完全分开,用户只需要注册相关的命令,实现回调即可,完全不用关心底层实现

voiddispatcher_on_02_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len)
{
constchar*str="func02isrunning
";
uart_write((uint8_t*)str,rt_strlen(str),100);
rt_kprintf("func02isrunning
");
}
UART_DISPATCHER_CALLBACK_REGISTER(1,0x02,dispatcher_on_02_callback);

voiddispatcher_on_03_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len)
{
constchar*str="func03isrunning
";
uart_write((uint8_t*)str,rt_strlen(str),100);
rt_kprintf("func03isrunning
");
}
UART_DISPATCHER_CALLBACK_REGISTER(1,0x03,dispatcher_on_03_callback);

voiddispatcher_on_04_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len)
{
constchar*str="func04isrunning
";
uart_write((uint8_t*)str,rt_strlen(str),100);
rt_kprintf("func04isrunning
");
}
UART_DISPATCHER_CALLBACK_REGISTER(1,0x04,dispatcher_on_04_callback);

voiddispatcher_on_05_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len)
{
rt_kprintf("func05isrunning
");
}
UART_DISPATCHER_CALLBACK_REGISTER(1,0x05,dispatcher_on_05_callback);

voiddispatcher_on_06_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len)
{
rt_kprintf("func06isrunning
");
}
UART_DISPATCHER_CALLBACK_REGISTER(1,0x06,dispatcher_on_06_callback);

测试效果

通过上面的回调函数注册,我们来测试下是不是达到预期情况:

87fd2ba8-1a6f-11ee-962d-dac502259ad0.png





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 定时器
    +关注

    关注

    23

    文章

    3242

    浏览量

    114583
  • RTT
    RTT
    +关注

    关注

    0

    文章

    65

    浏览量

    17099
  • UART接口
    +关注

    关注

    0

    文章

    124

    浏览量

    15280
  • HAL库
    +关注

    关注

    1

    文章

    121

    浏览量

    6191

原文标题:04-HAL库UART配置及协议解析设计

文章出处:【微信号:小飞哥玩嵌入式,微信公众号:小飞哥玩嵌入式】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    stm32 HALUART中断 使用说明

    大家参考。发帖真的不习惯,word粘贴也不好。所以详见附件吧。也有例程一个。首先,下载一个STM32CubeMX,图形化配置外设,十分方便。至此,已非常明确HAL
    发表于 06-30 22:32

    STM32 HAL CUBEMX配置 ADC采集 精选资料分享

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录STM32 HAL CUBEMX配置 ADC采集软硬件型号1.单通道不定时任意时刻采集信号
    发表于 08-11 07:46

    HAL配合CUBEMX配置

    本次教程主要介绍 :HAL配合CUBEMX配置一些常用外设的初始化,直观感受STM32编程,用最短时间入门STM32。
    发表于 08-11 07:07

    【STM32的HAL开发】CubeMX配置HAL,不进串口中断的问题 精选资料分享

    【STM32的HAL开发】串口中断开发环境main.c添加代码(1/2)stm32f4xx_it.c添加代码(2/2)注意附代码开发环境cub
    发表于 08-16 07:00

    使用CUBEMX配置hal输入捕获

    之前有使用标准配置的,具体可以看我之前的博客这个项目是使用CUBEMX配置hal来写,相
    发表于 01-06 07:23

    STM32CubeMX 配置STM32F407 实现HAL延时微妙方案

    STM32CubeMX 配置STM32F407 实现HAL延时微妙方案
    发表于 11-24 20:51 20次下载
    STM32<b class='flag-5'>CubeMX</b> <b class='flag-5'>配置</b>STM32F407 实现<b class='flag-5'>HAL</b><b class='flag-5'>库</b>延时微妙方案

    HALCubeMX系列|Cubemx新建HAL工程

    前言  工欲善其事,必先利其器。HAL的开发不一定必须使用cubemx,但是使用了cubemx,你绝对不会后悔。基于一些小伙伴对cubemx
    发表于 12-05 14:21 10次下载
    <b class='flag-5'>HAL</b><b class='flag-5'>库</b>与<b class='flag-5'>CubeMX</b>系列|<b class='flag-5'>Cubemx</b>新建<b class='flag-5'>HAL</b><b class='flag-5'>库</b>工程

    串口通信小试牛刀~使用STM32CubeMX+ HAL点亮流水灯

    STM32CubeMX与Keil MDK配合HAL完成流水灯的点亮。目录一、STM32串口通信基础二、STM32CubeMX与keil基于HAL
    发表于 12-07 10:06 14次下载
    串口通信小试牛刀~使用STM32<b class='flag-5'>CubeMX</b>+ <b class='flag-5'>HAL</b><b class='flag-5'>库</b>点亮流水灯

    STM32 CubeMX+HAL基本操作

    STM32 CubeMX+HAL基本操作
    发表于 12-07 11:21 50次下载
    STM32 <b class='flag-5'>CubeMX+HAL</b><b class='flag-5'>库</b>基本操作

    STM32实战 2 | STM32CubeMXHAL点亮LED

    STM32实战 2 | STM32CubeMXHAL点亮LED
    发表于 12-08 12:36 34次下载
    STM32实战 2 | STM32<b class='flag-5'>CubeMX</b>及<b class='flag-5'>HAL</b><b class='flag-5'>库</b>点亮LED

    【STM32 HALUART串口通讯

    HALUART的三种收发方式(一)阻塞收发特点:简单粗暴,占满单片机资源进行收发简介:发送:发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志接收:接收指定长度的数据
    发表于 12-24 18:44 5次下载
    【STM32 <b class='flag-5'>HAL</b>】<b class='flag-5'>UART</b>串口通讯

    STM32 HAL CubeMX教程(五)串口通信基础

    STM32 HAL CubeMX教程(五)串口通信基础串口通信简介CubeMX配置初始化程序分析程序编写
    发表于 12-24 18:49 12次下载
    STM32 <b class='flag-5'>HAL</b><b class='flag-5'>库</b> <b class='flag-5'>CubeMX</b>教程(五)串口通信基础

    STM32 HAL CUBEMX配置 ADC采集

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录STM32 HAL CUBEMX配置 ADC采集软硬件型号1.单通道不定时任意时刻采集信号
    发表于 12-27 18:57 25次下载
    STM32 <b class='flag-5'>HAL</b><b class='flag-5'>库</b> <b class='flag-5'>CUBEMX</b><b class='flag-5'>配置</b> ADC采集

    STM32串口通信HAL配置 UART_IT_xx与UART_FLAG_xx 的区别

    STM32串口通信HAL配置 UART_IT_xx与UART_FLAG_xx 的区别:(最后
    发表于 12-28 19:05 2次下载
    STM32串口通信<b class='flag-5'>HAL</b><b class='flag-5'>库</b><b class='flag-5'>配置</b><b class='flag-5'>中</b> <b class='flag-5'>UART</b>_IT_xx与<b class='flag-5'>UART</b>_FLAG_xx 的区别

    HAL无法实现UART的DMA传输真是这样吗?

    使用STM32CubeMx进行图形化配置,并生成基于HAL的初始代码,要实现UART收发功能的DMA传输的话,除了安排好的收发缓冲内存外,
    的头像 发表于 01-08 11:16 2682次阅读