晨旭的博客
你想干什么!
晨旭的博客~
Luat系列教程:7、串口收发

LUAT系列全部教程可以点击下面链接查看(建议保存书签):

https://www.chenxublog.com/tag/luat系列教程

阅读本文需要具有的技能:
看过该系列前几篇文章或明白前几篇文章内容的
可以明白字符串、字节码之间的区别
了解串口的原理和使用

其实串口这个部分,我觉得挺简单的,看demo都能看懂吧。。

官方demo代码

官方代码可以在github(https://github.com/openLuat/Luat_2G_RDA_8955/)的Luat_2G_RDA_8955/script_LuaTask/demo/uart目录或luatools的LuaTools 1.x.x\script\script_LuaTask\demo\uart目录找到

如果你能看懂官方例程,那么可以直接去使用,不需要再看本文了

先定义一个假装能用来测试的串口收发规则

  • 串口通讯使用9600波特率,3.3V ttl电平
  • 模块开机第10秒后,向设备发送0x01 0x02 0x03三个字节
  • 模块收到qwerty字符串后,回复asdfgh字符串
  • 模块收到0xaa 0xbb 0xcc三个字节后,回复0xdd 0xee 0xff三个字节
  • 模块收到abcdefghijklmnopqrstuvwxyz字符串后,回复ok字符串

建立文件结构

测试需要新建两个文件,main.luatestuart.lua

main.lua在前面的文章中,已经新建过大概不下四次了,所以这里不再举例。main.lua需要require"testuart"

testuart.lua

module(...,package.seeall)

require"utils"
require"pm"

--串口ID,1对应uart1
--如果要修改为uart2,把UART_ID赋值为2即可
local UART_ID = 1


--保持系统处于唤醒状态,此处只是为了测试需要,所以此模块没有地方调用pm.sleep("testUart")休眠,不会进入低功耗休眠状态
--在开发“要求功耗低”的项目时,一定要想办法保证pm.wake("testUart")后,在不需要串口时调用pm.sleep("testUart")
pm.wake("testUart")

--配置并且打开串口
uart.setup(UART_ID,9600,8,uart.PAR_NONE,uart.STOP_1)

可以看到,uart文件中已经配置好了串口

发送串口信息

发送接口特别简单,只需要调用uart.write()函数即可,我们可以在设置唤醒状态代码前面加上发送函数:

--发送串口数据
function write(s)
    log.info("testuart.write",s:toHex(),s)
    uart.write(UART_ID,s)
end

接收串口信息

为了能接收到串口消息,我们在配置并且打开串口函数的上方注册串口接收函数:

--注册串口的数据接收函数,串口收到数据后,会以中断方式,调用read接口读取数据
uart.on(UART_ID,"receive",read)

并在发送函数上方新建接收函数:

--接收串口数据
local function read()
    local data = ""
    --底层core中,串口收到数据时:
    --如果接收缓冲区为空,则会以中断方式通知Lua脚本收到了新数据;
    --如果接收缓冲器不为空,则不会通知Lua脚本
    --所以Lua脚本中收到中断读串口数据时,每次都要把接收缓冲区中的数据全部读出,这样才能保证底层core中的新数据中断上来,此read函数中的while语句中就保证了这一点
    while true do        
        data = uart.read(UART_ID,"*l")
        --数据不存在时停止接收数据
        if not data or string.len(data) == 0 then break end
        --打开下面的打印会耗时
        log.info("testUart.read bin",data)
        log.info("testUart.read hex",data:toHex())

        --真正的串口数据处理函数
        proc(data)
    end
end

我们可以看到,所有串口数据都交给了proc()函数进行处理,我们可以在接收函数上方新建一个proc()函数:

--处理串口数据
local function proc(data)
    --todo
end

实现功能

模块开机第10秒后,向设备发送0x01 0x02 0x03三个字节

实现这个功能,我们只需要在文件末尾加上一个定时器即可:

--模块开机第10秒后,向设备发送`0x01 0x02 0x03`三个字节
sys.timerStart(function()
    write(string.fromHex("010203"))
end,10000)

模块收到qwerty字符串后,回复asdfgh字符串

处理这个信息,可以去proc()函数里进行修改,将函数更改为如下形式:

--处理串口数据
local function proc(data)
    if data == "qwerty" then
        --模块收到`qwerty`字符串后,回复`asdfgh`字符串
        write("asdfgh")
    end
end

剩下两个需求

剩下两个需求处理起来和前面一样,我们直接仿照着改就行:

--处理串口数据
local function proc(data)
    if data == "qwerty" then
        --模块收到`qwerty`字符串后,回复`asdfgh`字符串
        write("asdfgh")
    elseif data == string.fromHex("AABBCC") then
        --模块收到`0xaa 0xbb 0xcc`三个字节后,回复`0xdd 0xee 0xff`三个字节
        write(string.fromHex("DDEEFF"))
    elseif data == "abcdefghijklmnopqrstuvwxyz" then
        --模块收到`abcdefghijklmnopqrstuvwxyz`字符串后,回复`ok`字符串
        write("ok")
    end
end

测试

这个测试需要大家自己去测试了

测试结果会发现,第三条指令无法完成。为什么呢?因为串口会有截断现象。

处理串口数据截断问题

串口数据接收经常会出现的一个问题:数据被截断
这个现象很常见,你可以像普通单片机一样一个字节一个字节去解析,也可以加一个缓冲区定时清空处理

我们首先在proc()函数上方,新建一个缓冲区:

local buf = ""

然后可以把proc()函数改造成下面这样:

--缓存数据
local buf = ""
--处理串口数据
local function proc(data)
    data = buf..data
    log.info("testUart.read proc",data)
    local used = true--数据是否被处理?
    if data == "qwerty" then
        --模块收到`qwerty`字符串后,回复`asdfgh`字符串
        write("asdfgh")
    elseif data == string.fromHex("AABBCC") then
        --模块收到`0xaa 0xbb 0xcc`三个字节后,回复`0xdd 0xee 0xff`三个字节
        write(string.fromHex("DDEEFF"))
    elseif data == "abcdefghijklmnopqrstuvwxyz" then
        --模块收到`abcdefghijklmnopqrstuvwxyz`字符串后,回复`ok`字符串
        write("ok")
    else
        --数据没匹配上任何东西,没被使用
        used = false
    end
    if not used then--数据没被使用
        if buf == "" then--如果缓冲区是空的
            sys.timerStart(function()
                buf = ""
            end,500)--500ms后清空缓冲区
        end
        buf = data--数据追加到缓存区
    else
        buf = ""
    end
end

函数中首先判断数据是否被使用,如果没被使用,就将数据追加到缓冲区,如果已被使用,缓冲区内容会被清除

完整代码

经过一系列修改,testuart.lua整体代码如下:

module(...,package.seeall)

require"utils"
require"pm"

--串口ID,1对应uart1
--如果要修改为uart2,把UART_ID赋值为2即可
local UART_ID = 1

--缓存数据
local buf = ""
--处理串口数据
local function proc(data)
    data = buf..data
    log.info("testUart.read proc",data)
    local used = true--数据是否被处理?
    if data == "qwerty" then
        --模块收到`qwerty`字符串后,回复`asdfgh`字符串
        write("asdfgh")
    elseif data == string.fromHex("AABBCC") then
        --模块收到`0xaa 0xbb 0xcc`三个字节后,回复`0xdd 0xee 0xff`三个字节
        write(string.fromHex("DDEEFF"))
    elseif data == "abcdefghijklmnopqrstuvwxyz" then
        --模块收到`abcdefghijklmnopqrstuvwxyz`字符串后,回复`ok`字符串
        write("ok")
    else
        --数据没匹配上任何东西,没被使用
        used = false
    end
    if not used then--数据没被使用
        if buf == "" then--如果缓冲区是空的
            sys.timerStart(function()
                buf = ""
            end,500)--500ms后清空缓冲区
        end
        buf = data--数据追加到缓存区
    else
        buf = ""
    end
end

--接收串口数据
local function read()
    local data = ""
    --底层core中,串口收到数据时:
    --如果接收缓冲区为空,则会以中断方式通知Lua脚本收到了新数据;
    --如果接收缓冲器不为空,则不会通知Lua脚本
    --所以Lua脚本中收到中断读串口数据时,每次都要把接收缓冲区中的数据全部读出,这样才能保证底层core中的新数据中断上来,此read函数中的while语句中就保证了这一点
    while true do
        data = uart.read(UART_ID,"*l")
        --数据不存在时停止接收数据
        if not data or string.len(data) == 0 then break end
        --打开下面的打印会耗时
        log.info("testUart.read bin",data)
        log.info("testUart.read hex",data:toHex())

        --真正的串口数据处理函数
        proc(data)
    end
end

--发送串口数据
function write(s)
    log.info("testuart.write",s:toHex(),s)
    uart.write(UART_ID,s)
end



--保持系统处于唤醒状态,此处只是为了测试需要,所以此模块没有地方调用pm.sleep("testUart")休眠,不会进入低功耗休眠状态
--在开发“要求功耗低”的项目时,一定要想办法保证pm.wake("testUart")后,在不需要串口时调用pm.sleep("testUart")
pm.wake("testUart")

--注册串口的数据接收函数,串口收到数据后,会以中断方式,调用read接口读取数据
uart.on(UART_ID,"receive",read)

--配置并且打开串口
uart.setup(UART_ID,9600,8,uart.PAR_NONE,uart.STOP_1)

--模块开机第10秒后,向设备发送`0x01 0x02 0x03`三个字节
sys.timerStart(function()
    write(string.fromHex("010203"))
end,10000)

最终测试

开机后发送第10秒后,向设备发送0x01 0x02 0x03三个字节

https://source.papapoi.com/wp-content/uploads/2018/08/1.png?imageView2/2/w/800|watermark/1/image/aHR0cDovL3NvdXJjZS5wYXBhcG9pLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNi8wOC9sb2dvcGljLnBuZw==/dissolve/100/gravity/SouthEast/dx/10/dy/10

模块收到qwerty字符串后,回复asdfgh字符串

https://source.papapoi.com/wp-content/uploads/2018/08/2.png?imageView2/2/w/800|watermark/1/image/aHR0cDovL3NvdXJjZS5wYXBhcG9pLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNi8wOC9sb2dvcGljLnBuZw==/dissolve/100/gravity/SouthEast/dx/10/dy/10

模块收到0xaa 0xbb 0xcc三个字节后,回复0xdd 0xee 0xff三个字节

https://source.papapoi.com/wp-content/uploads/2018/08/3.png?imageView2/2/w/800|watermark/1/image/aHR0cDovL3NvdXJjZS5wYXBhcG9pLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNi8wOC9sb2dvcGljLnBuZw==/dissolve/100/gravity/SouthEast/dx/10/dy/10

模块收到abcdefghijklmnopqrstuvwxyz字符串后,回复ok字符串

https://source.papapoi.com/wp-content/uploads/2018/08/4.png?imageView2/2/w/800|watermark/1/image/aHR0cDovL3NvdXJjZS5wYXBhcG9pLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNi8wOC9sb2dvcGljLnBuZw==/dissolve/100/gravity/SouthEast/dx/10/dy/10

赞赏
如非特殊说明,本站所有文章均采用知识共享 署名-非商业性使用-相同方式共享4.0国际许可协议(CC BY-NC-SA 4.0)进行许可。

发表评论

textsms
account_circle
email

  • https://secure.gravatar.com/avatar/6a8b45b101f07a67fd852ce1143476b9?s=80&d=mm&r=x
    Mr.teng
    Google Chrome 68.0.3440.106 Google Chrome 68.0.3440.106 Windows 10 x64 Edition Windows 10 x64 Edition
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36

    为什么这么淘气,哈哈哈

    1月前回复

晨旭的博客~

Luat系列教程:7、串口收发
LUAT系列全部教程可以点击下面链接查看(建议保存书签): https://www.chenxublog.com/tag/luat系列教程 阅读本文需要具有的技能: 看过该系列前几篇文章或明白前几篇文章内容的 …
扫描二维码继续阅读
2018-08-15