基于Lua脚本语言的嵌入式UART通信的实现

发布者:lidong4069最新更新时间:2012-05-07 来源: eefocus关键字:Lua脚本  UART通信  串口通信规约 手机看文章 扫描二维码
随时随地手机看文章

  引言

  随着变电站智能化程度的逐步提高,对温度、湿度等现场状态参量的采集需求也越来越多。就目前而言,在现场应用中,此类设备多采用RS232或RS485等UART串行通信方式和IED(Intelligent Electronic Device,智能电子设备)装置进行交互。一般来说,不同的设备采用的通信数据帧格式并不相同。各式各样的串口数据帧格式,对IED装置的软件定型造成一定的困难。传统的做法一般是由装置生产厂家指定和其配套的外围设备,装置的灵活性不够理想。本文针对此类问题,提出了一种基于Lua脚本语言的解决方案,可有效地提高IED装置对各种类型串口数据报文帧格式的适应性。该方案将具体串口报文规约的组建和解析交给Lua脚本进行处理,从而使设计者在装置的软件开发中,可仅关注于相关接口的设计,而不用关心具体的串口通信规约,从而方便软件的定型,并提高了装置自身在应用中的灵活性。

  1 Lua脚本语言介绍

  Lua是一种源码开放的、免费的、轻量级的嵌入式脚本语言,源码完全采用ANSI(ISO) C.这一点使它非常适合融入目前以C语言为主的嵌入式开发环境之中。两者之间实现交互的关键在于一个虚拟的栈,通过该虚拟栈和Lua提供的可对该栈进行操作的相关接口函数,可以很方便地在它们之间实现各种类型数据的传递。

  与其他脚本语言(如Perl、Tcl、Python等)相比,Lua表现出了足够的简单性以及非常高的执行效率,结合其与平台的高度无关以及充分的可扩展性[1],这使得它越来越多地得到大家的关注。因此,在本文的方案中优先选用Lua脚本来进行设计。

  2 系统方案概述

  本方案主要是围绕着IED装置和外围串口设备之间的通信来进行设计的,系统框架如图1所示。

图1 系统框架

  当IED装置开始运行时,将创建一个用于UART通信的读写调度任务。在该任务中,首先通过Lua提供的接口函数来启动其脚本引擎,并创建Lua虚拟机。然后即可将用户编写的C函数注册到Lua虚拟机中去,并将存在于Flash文件系统中独立于装置C程序的Lua脚本文件加载到虚拟机中,从而建立起Lua和C的交互环境。在系统应用中,将需要发送到外围设备的具体数据内容都放在Lua脚本文件中。当装置C程序需要发送数据时,通过通信读写调度程序及虚拟机的配合,将这部分数据取出,并调用串口驱动程序发送给外围设备。当收到外围设备发给IED装置的报文时,再将相应数据传给虚拟机中运行的脚本程序进行处理,并由Lua根据数据处理结果来调用已注册的C函数进行相关业务处理。

图2 系统程序流程

  本系统的程序流程如图2所示。

  其中,串口通信芯片采用TI公司的带64字节FIFO的4通道可编程UART芯片TL16C754B来实现。它的4个通道可分别独立编程,在3.3 V的操作电压下,数据传输速率可高达2 Mbps,适合多种UART通信环境中的应用[2]。基于装置的应用环境,本文采用RS485的问答机制并结合查询方式来对该串口通信方案进行设计。在方案实现中,装置将每隔一定时间通过串口芯片发送一次查询报文,当查询到外围设备发送的正确响应报文后,再进行相关业务处理。

  3 功能实现

  在嵌入式应用领域,串口通信的应用比较成熟,因此,本文将着重介绍Lua是如何服务于这一应用的。从图2可以看出,Lua的使用主要体现在如下几个方面:

  ◆ Lua与C交互环境的建立;

  ◆ 提取脚本中的串口配置数据;

  ◆ 调用Lua函数设置发送缓冲区;

  ◆ 通过Lua函数处理接收缓冲区数据。

        3.1 Lua与C交互环境的建立

  要建立交互环境,首先要启动Lua脚本引擎,并创建虚拟机。其机制虽然相对复杂,但对应用来说却比较简单,通过“L=lua_open(NULL);”即可实现。其中,L是一个指向结构类型为lua_State的指针变量,该结构将负责对Lua的运行状态进行维护。[page]

  为了实现Lua脚本函数对系统程序中串口发送和接收缓存区的数据进行访问,定义了几个C函数供脚本调用,即用于设置串口发送缓冲区的函数set_tx_buf、读取串口接收缓冲区的函数get_rx_buf,以及在Lua脚本中判断串口数据交互正常时调用的结果处理函数uart_ok_del.

  在Lua脚本中,要成功调用以上函数,必须将其加载到Lua虚拟机中去,本文采用Lua提供的一种注册C函数库的方法来实现。具体加载过程如下:

  ① 按以下格式定义调用函数:

  static int set_tx_buf(lua_State *L);

  static int get_rx_buf(lua_State *L);

  static int uart_ok_del(lua_State *L);

  ② 声明一个结构数组,每个数组元素分别为C函数在Lua脚本中的调用名字及对应的C函数,即以“name-function”对的形式出现,如下所示:static const struct luaL_reg uartLib[] ={

  {“set_tx_buf”,set_tx_buf},

  {“get_tx_buf”, get_tx_buf},

  {“uart_ok_del”, uart_ok_de},

  {NULL, NULL}

  };

  ③ 调用以下函数对C函数库进行注册:luaL_register(L, “ied”, uartLib );其中,参数L即为创建虚拟机时的函数返回值(以下同),字符串“ied”为注册到虚拟机中的库名称。第3个参数uartLib即为前面声明的结构数组,对应需要注册的库函数表。

  通过以上步骤,即可完成Lua脚本中需要调用的3个C函数的注册过程,从而就可以在Lua脚本中通过“库名称。库函数”的形式来对其进行调用,如“ied.set_tx_buf(函数参数)”。

  脚本文件本身的加载则相对简单,只需通过如下函数调用即可:

  luaL_dofile(L, “uart_script.lua”);

  其中,参数L和以上的函数调用相同,第2个参数则为脚本文件在Flash中的具体存储路径。

  至此,就成功建立了一个Lua与C的交互环境。

  3.2 提取脚本中的串口配置数据

  要正确地进行Lua和C的交互过程,首先必须对Lua和C交互时所采用虚拟栈的作用和操作有比较深入的了解。在Lua和C的交互中,它们彼此之间函数参数以及返回值都将由该栈来负责传递。Lua和C在栈的操作方式上稍有不同,在Lua中采用严格的LIFO方式,而C则还可以通过索引的方式进行。以3个参数为例,参数1首先入栈,参数2、3随后顺次入栈,Lua虚拟栈存储结构及索引对应关系如图3所示。

图3 Lua虚拟栈结构示例图

  如需在C中访问参数1,则既可以通过索引号1进行,也可通过索引号-3进行。其中,正索引按入栈顺序从1依次递增,负索引按出栈顺序从-1依次递减。

  通常情况下,串口的配置主要有以下几项:是否使能、数据位数、停止位数、奇偶校验标志位和波特率。因此,在Lua脚本中,本文采用Lua的表结构对其进行设置,示例如下(本文中斜体代码表示为Lua脚本,以下同):

  uart_p0={

  enable=1,--使能位

  dataBits=8 , --数据位数

  stopBits=1 , --停止位数

  parityBit=2 , --奇偶校验

  baudRate=9600 --波特率

  };

        该例表示对UART芯片的P0口进行使能,并且采用8位数据位、1位停止位、偶校验(本文定义parityBit的值取0为无校验,取1为奇校验,取2为偶校验)的帧格式,波特率为9 600 bps.

  在C语言中,要获取表中enable属性字段的值,可采用以下步骤:

  ① 调用接口函数并以表名称作为参数,将该表入栈:

  lua_getglobal(L, “uart_p0”);

  ② 调用接口函数将enable属性字段的属性名称入栈:

  lua_pushstring(L, “enable”);

  ③ 调用接口函数提取属性值,该操作在C中可看作是一个先出栈再入栈的过程,结果将在②中已入栈的属性名称所在位置填入属性值:

  lua_gettable(L, -2);

  其中,参数“-2”为栈中的索引号。

  ④ 调用接口函数取出栈顶中该属性字段的值,并调用出栈函数,以恢复调用环境:

  p0_enable = (int)lua_tonumber(L, -1);

  lua_pop(L, 1);

  其中,lua_tonumber函数的参数“-1”也为栈中的索引号,该操作将取出栈顶元素的数值,鉴于Lua中的数据都为浮点数,所以需将其强制转换为整型数据。lua_pop中参数“1”为非索引,仅说明从栈顶将1个元素出栈。

  通过以上操作,就可以正确地取出脚本中p0口参数设置表中enable属性字段的值。其他属性字段的提取与其相同。虚拟栈中的内容变化如图4所示。

图4 提取表中属性值时的虚拟栈操作示意图[page]

  3.3 调用Lua函数设置发送缓冲区

  为通过Lua脚本对串口发送缓冲区进行设置,在脚本中定义了如下函数:

  data ={0x11, 0x22, 0x33, 0x44, 0x55 };

  function uart_p0_set_txBuf()

  local port=0;

  local p0_send_num=5;

  for i=1, p0_send_num do

  ied.set_tx_buf(port,i-1, data[i])

  end

  return p0_send_num

  end

  从脚本内容可以看出,在此采用了一个Lua中的循环结构对发送缓冲区进行设置,并返回设置的数据个数。其中,全局变量data是Lua脚本中的表,类似于数组,在此表示需要设置的缓冲区内容;ied.set_tx_buf()为在3.1节中提到的已注册到虚拟机中的C函数库中的一个函数。其参数port表示端口号,i-1表示缓冲区索引号,data[i]表示具体的数据内容。在应用中需要注意的是,在Lua中,数组索引默认从1开始,而不像C中从0开始。另外,在C中定义set_tx_buf函数时并未设置参数,这主要是因为参数的提取必须借助于虚拟栈才能实现。在脚本中调用时,对其参数将按照从左到右的顺序依次入栈,在C中要取出参数时,按照其在栈中相应的索引号取出即可。在Lua中对每个函数的调用都有一个独立的栈,因此,若以i取2时调用情况为例,在C函数set_tx_buf中看到的栈内容将如图5所示。

图5 函数调用时的虚拟栈示例

  从而在C程序中,只需要调用下面语句即可将该串口发送缓冲区中索引为1的内存区域设置成0x22:

  port=(int)lua_tonumber(L,1);//取端口号

  index=(int)lua_tonumber(L,2);//取索引

  data=(char)lua_tonumber(L,3);//取数据

  uart_port_tx_buf[port].data[index]=data;

  当在C程序中需对串口发送缓冲区进行设置时,将按如下方法调用该脚本函数:

  lua_getglobal(L, “uart_p0_set_txBuf ”);

  lua_pcall(L, 0, 1, 0);

  其中,函数lua_getglobal的参数“uart_p0_set_txBuf”为要调用的脚本函数名,函数lua_pcall的函数原型为:

  int (lua_pcall) (

  lua_State *L,

  int nargs, //调用函数的参数个数

  int nresults, //返回的参数个数

        int errfunc //错误处理函数号

  );

  因所调用的脚本函数uart_p0_set_txBuf没有参数,有一个返回值,所以分别将nargs、nresults置为0、1,而错误处理函数暂不使用,故置为0.

  对于脚本中的返回值,将在脚本函数调用结束时,置于lua_pcall调用环境所在的虚拟栈的栈顶中,可由C程序根据索引取出。

  经以上过程,就完成了对串口发送缓冲区的内容设置,然后就可以通过串口芯片的驱动程序将其发送到外围设备。

  在现场应用时,只需根据不同外围设备问询报文的要求来修改脚本中data数组以及p0_send_num变量的内容即可,而不用对装置的C程序进行任何修改。

  3.4 通过Lua函数处理接收缓冲区数据

  通过Lua和C的交互来对串口接收缓冲区数据的处理方法同发送缓冲区的处理基本相似。

  当装置通过串口驱动程序将外围设备发来的数据置入接收缓冲区后,在C函数中调用脚本函数:

  lua_getglobal(L, “uart_p0_del_rxBuf”);

  lua_pushnumber(L, size);

  ret=lua_pcall(L, 1, 1, 0);

  其中,参数uart_p0_del_rxBuf为脚本中定义的缓冲区数据处理函数名,通过lua_pushnumber将接收数据的大小入栈,从而传给Lua脚本函数,脚本函数的原型如下:

  function uart_p0_del_rxBuf(rx_size)

  在该函数中,可通过调用注册的C函数get_rx_buf来获取接收缓冲区中的内容:

  data[i] = ied.get_rx_buf(port,index)

  其中,data为脚本中类似于数组的表类型。port为串口芯片的端口号,index为缓冲区的索引号,在C程序中通过以下语句对脚本返回所取数据值:

  port=(int)lua_tonumber(L,1);//取端口号

  index=(int)lua_tonumber(L,2);//取索引

  data=uart_port_rx_buf[port].data[index];

  lua_pushnumber(L, data);//返回值入栈

  可以看出,在脚本中也是借助于虚拟栈来获取C程序的返回值。通过以上方法成功获取了串口接收缓存区的内容后,就可根据具体的外围设备在脚本中对其接收数据的正确性进行判断,如果判断结果正确,则调用前面注册的C函数uart_ok_del进行相关业务处理。

  ied. uart_ok_del (port)

  结语

  从本文提供的方案可以看出,从始至终,IED装置的C语言应用程序在Lua虚拟机与外围设备之间,除了报文的透明传输功能外,并不负责具体数据业务的处理,这就使在C程序的设计中完全不需要考虑外围设备所采用的串口通信数据格式,具体的数据内容都可放在脚本文件中进行设置和处理。在现场应用中,就可以达到仅修改Lua脚本文件就能完成IED装置与不同的串口通信外围设备之间的数据交互功能,从而实现对装置串口通信规约的现场可配置化。

关键字:Lua脚本  UART通信  串口通信规约 引用地址:基于Lua脚本语言的嵌入式UART通信的实现

上一篇:用VMLAB进行AVR单片机硬件/软件协仿真
下一篇:基于Atmega16的室内照明系统设计

推荐阅读最新更新时间:2024-03-16 12:58

一文看懂 UART 通信协议
UART 通信简介 UART即通用异步收发器,是一种通用的串行、异步通信总线,该总线有两条数据线,可以实现全双工的发送和接收,在嵌入式系统中常用于主机与辅助设备之间的通信。在UART通信中,两个UART直接相互通信。发送UART将来自控制设备(如CPU)的并行数据转换为串行形式,将其串行传输到接收UART,然后UART将串行数据转换回接收设备的并行数据。在两个UART之间传输数据只需要两根线。数据从发送 UART 的 Tx 引脚流向接收 UART 的 Rx 引脚: UART异步传输数据,这意味着没有时钟信号将发送UART的位输出同步到接收UART的位采样。发送UART不是时钟信号,而是将开始位和停止位添加到正在传输的数据包中。
[单片机]
HAL+Cube MX 学习之UART串口通信
一、UART’s Configuration 在Connectivity的USART1配置中,Mode选择为Asynchronous(异步通信),Cube MX已经配置好了相关的引脚,下面的波特率没有特别要求,需要跟串口助手的波特率一致,常用的波特率是9600和115200,然后生成代码。 二、Coding in MDK 可以直接用printf来打印,打印的内容会在串口调试助手上显示,但是用printf需要添加头文件和函数, /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN
[单片机]
HAL+Cube MX 学习之<font color='red'>UART</font><font color='red'>串口通信</font>
STM32F103标准库开发---Uart串口通信实验---printf()函数重定向
一、printf()函数重定向 方法一:使用MicroLIB库 1. 勾选 Use MicroLIB 具体如下图所示: 2. 重定向 fputc 函数 具体代码如下: #include stdio.h //需要调用stdio.h文件 /**********************printf重定向****************************/ int fputc(int ch, FILE *f) { USART_SendData(USART1, ch); //发送数据 while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);//等待发送完
[单片机]
STM32F103标准库开发---<font color='red'>Uart</font><font color='red'>串口通信</font>实验---printf()函数重定向
51单片机UART 串口通信[ 方式1 ]实现
1 51串行口结构 51单片机的UART串行通信是基于其串行口的可编程硬件结构,只要用正确的程序代码通过初始化串行口对应寄存器的形式将其串行硬件结构初始化,再编写符合此串行口通信的程序代码便能够实现串行通信,其硬件结构决定了编程机制( 当然还要靠51芯片内CPU等机制 )。此结构具有UART( 通用异步收发器 )的全部功能,能同时进行数据的发送和接收,也可作为同步移位寄存器使用。此结构集成于单片机内部。 Figure1:51串行口结构 2 51串行口通信编程机制 决定编程机制的先决条件是51单片机的硬件结构及51芯片内部CPU的执行机制。根据串行通信口硬件结构,实现UART通信可以分为两步: (1)初始化串行通信口(
[单片机]
51单片机<font color='red'>UART</font> <font color='red'>串口通信</font>[ 方式1 ]实现
UART0串口编程(一):通信协议设计;RS232标准
嵌入式系统之间采用通信方式交换数据,串行通信是一种最基本的通信手段。现在由于各类CPU均集成了UART部件。本节对串口编程时如何设计串口通信协议,以及对RS232接口进行了说明。 1. 通信协议设计 在进行通信时,每次需要传送的信息一般超过1字节。为此,通信双方必须约定通信数据的排列格式,即通信协议。通常把一次通信过程的全部内容称为一个“通信帧”。通信协议设计就是帧结构设计。在一帧内容中,通常包括以下部分。 (1)起始码:由一个或若干个包含特定内容的字节,表示一个通信帧的开始。起始码的内容必须与通信数据内容有明显区别(不能在通信数据里出现与起始码相同的内容)。当通信帧的总长度比较短,信道质量比较高时,为了提高效率,可
[单片机]
<font color='red'>UART</font>0串口编程(一):<font color='red'>通信</font>协议设计;RS232标准
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • ARM裸机篇--按键中断
    先看看GPOI的输入实验:按键电路图:GPF1管教的功能:EINT1要使用GPF1作为EINT1的功能时,只要将GPFCON的3:2位配置成10就可以了!GPF1先配 ...
  • 网上下的--ARM入门笔记
    简单的介绍打今天起菜鸟的ARM笔记算是开张了,也算给我的这些笔记找个存的地方。为什么要发布出来?也许是大家感兴趣的,其实这些笔记之所 ...
  • 学习ARM开发(23)
    三个任务准备与运行结果下来看看创建任务和任运的栈空间怎么样的,以及运行输出。Made in china by UCSDN(caijunsheng)Lichee 1 0 0 ...
  • 学习ARM开发(22)
    关闭中断与打开中断中断是一种高效的对话机制,但有时并不想程序运行的过程中中断运行,比如正在打印东西,但程序突然中断了,又让另外一个 ...
  • 学习ARM开发(21)
    先要声明任务指针,因为后面需要使用。 任务指针 volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • 学习ARM开发(20)
  • 学习ARM开发(19)
  • 学习ARM开发(14)
  • 学习ARM开发(15)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved