指南-luat二次开发教程指南-设备驱动-UART设备

目录

UART接口

简介

串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,主要按位(bit)发送和接收字节,典型地,串口用于ASCII码字符的传输。通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。


合宙Cat.1模块UART接口可以作为主器件或从器件,与带外部UART串口的设备进行数据之间的相互通信,进行数据接收和发送,达到相互控制的目的。


UART接口控制

UART的库由底层core实现,相关API接口如下:

API接口 描述
uart.on() 注册串口的数据接收或发生函数
uart.setup() 配置并且打开串口,可设置使用模块的串口ID号,波特率,数据位,奇偶校验位等
uart.write() 向串口写字符串或者整型数据
uart.read() 从串口读取字符串
uart.getchar() 从串口读取单字符
uart.close() 关闭uart对应接口

详细的API介绍见luat core API章节

相关硬件接口

UART硬件通道对应的UART id如下:

硬件接口 UART id
UART1 1
UART2 2
UART3 3

注意:1. 不同的模块支持的UART管脚不同,具体请参考Air724模块UART硬件设计章节
2.合宙系列除以上三路串口外,常见的还有HOST串口,该串口不能作为普通串口使用,用户不必过于纠结。

UART使用示例

UART 的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下:

  1. 配置串口参数,即设置与从机或上位机通信一致的波特率、数据位、校验位等。
  2. 注册串口数据接收函数,通过uart.on()将用户设计的读取函数注册,串口收到数据后,会以中断方式注册的函数读取数据。
  3. 注册串口数据发送函数,当用户调用uart.write()函数发送数据时,将请求一次发送通知函数。
module(...,package.seeall)
require"utils"
require"pm"
--[[
功能定义:
uart按照帧结构接收外围设备的输入,收到正确的指令后,回复ASCII字符串
帧结构如下:
帧头:1字节,0x01表示扫描指令,0x02表示控制GPIO命令,0x03表示控制端口命令
帧体:字节不固定,跟帧头有关
帧尾:1字节,固定为0xC0
收到的指令帧头为0x01时,回复"CMD_SCANNER\r\n"给外围设备;例如接收到0x01 0xC0两个字节,就回复"CMD_SCANNER\r\n"
收到的指令帧头为0x02时,回复"CMD_GPIO\r\n"给外围设备;例如接收到0x02 0xC0两个字节,就回复"CMD_GPIO\r\n"
收到的指令帧头为0x03时,回复"CMD_PORT\r\n"给外围设备;例如接收到0x03 0xC0两个字节,就回复"CMD_PORT\r\n"
收到的指令帧头为其余数据时,回复"CMD_ERROR\r\n"给外围设备;例如接收到0x04 0xC0两个字节,就回复"CMD_ERROR\r\n"
]]
--串口ID,1对应uart1
--如果要修改为uart2,把UART_ID赋值为2即可
local UART_ID = 1
--帧头类型以及帧尾
local CMD_SCANNER,CMD_GPIO,CMD_PORT,CMD_DATA,FRM_TAIL = 1,2,3,4,string.char(0xC0)
--串口读到的数据缓冲区
local rdbuf = ""
--[[
函数名:parse
功能  :按照帧结构解析处理一条完整的帧数据
参数  :
        data:所有未处理的数据
返回值:第一个返回值是一条完整帧报文的处理结果,第二个返回值是未处理的数据
]]
local function parse(data)
    if not data then return end
    local tail = string.find(data,string.char(0xC0))
    if not tail then return false,data end
    local cmdtyp = string.byte(data,1)
    local body,result = string.sub(data,2,tail-1)
    log.info("testUart.parse",data:toHex(),cmdtyp,body:toHex())
    if cmdtyp == CMD_SCANNER then
        write("CMD_SCANNER")
    elseif cmdtyp == CMD_GPIO then
        write("CMD_GPIO")
    elseif cmdtyp == CMD_PORT then
        write("CMD_PORT")
    elseif cmdtyp == CMD_DATA then
        write("Hello world!")
    else
        write("CMD_ERROR")
    end
    return true,string.sub(data,tail+1,-1)
end
--[[
函数名:proc
功能  :处理从串口读到的数据
参数  :
        data:当前一次从串口读到的数据
返回值:无
]]
local function proc(data)
    if not data or string.len(data) == 0 then return end
    --追加到缓冲区
    rdbuf = rdbuf..data
    local result,unproc
    unproc = rdbuf
    --根据帧结构循环解析未处理过的数据
    while true do
        result,unproc = parse(unproc)
        if not unproc or unproc == "" or not result then
            break
        end
    end
    rdbuf = unproc or ""
end
--[[
函数名: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

--[[
函数名:write
功能  :通过串口发送数据
参数  :
        s:要发送的数据
返回值:无
]]
function write(s)
    log.info("testUart.write",s)
    uart.write(UART_ID,s.."\r\n")
end

local function writeOk()
    log.info("testUart.writeOk")
end
--保持系统处于唤醒状态,此处只是为了测试需要,所以此模块没有地方调用pm.sleep("testUart")休眠,不会进入低功耗休眠状态
--在开发“要求功耗低”的项目时,一定要想办法保证pm.wake("testUart")后,在不需要串口时调用pm.sleep("testUart")
pm.wake("testUart")
--注册串口的数据接收函数,串口收到数据后,会以中断方式,调用read接口读取数据
uart.on(UART_ID,"receive",read)
--注册串口的数据发送通知函数
uart.on(UART_ID,"sent",writeOk)

--配置并且打开串口
--uart.setup(UART_ID,115200,8,uart.PAR_NONE,uart.STOP_1)
--如果需要打开“串口发送数据完成后,通过异步消息通知”的功能,则使用下面的这行setup,注释掉上面的一行setup
uart.setup(UART_ID,115200,8,uart.PAR_NONE,uart.STOP_1,nil,1)

常见问题

  1. 为什么串口通信中会出现一些乱码数据?
    答:串口波特率不正确或者串口通信的电平不匹配。

    • 首先确认代码中所配置的通信的波特率与用户使用通信设备的波特参数设置的是否一直,然后确认用户所使用的通信设备的串口通信电平是否与Air724模块串口电平一致,Air724开发板上的Air724模块的3路串口电平均为1.8V,如不匹配,请确保一致。不能过高或过低,过低有可能会导致电平电压不能被识别,过高有可能会损坏UART通信接口。
  2. 一次发送的数据模块分为2个包来接收;第一个64bytes,第二次26bytes
    答:在core中的应用层,串口驱动接收到的数据插入缓冲区;脚本有轮询和中断两种方式,通过uart.read(…)接口读取缓冲区中的数据。
    需要注意如下两点:

      1、脚本读取的速度要大于数据插入的速度,否则会造成缓冲区溢出,数据出错
    
      2、MCU一次性发送给模块的数据,调用uart.read接口,并不一定能够一次性读取完整,必须使用“循环读取数据”+“数据拼接判断完整性”的方案来处理;例如MCU一次性发送1460字节的数据,模块使用轮询或者中断第一次读取数据时,缓冲器里面可能才接收到10字节的数据。
    

    轮询方式读取数据

      轮询方式比较简单,脚本定时调用uart.read接口读取数据、拼接数据、检查数据完整性、处理数据即可
    

    中断方式读取数据

      中断方式处理逻辑如下:
    
      1、脚本调用uart.on(id, "receive", intFnc)注册中断处理函数intFnc
    
      2、脚本接收到core中产生的数据中断消息后,执行intFnc
    
      3、脚本在intFnc中循环调用uart.read接口读取数据、拼接数据、检查数据完整性、处理数据,直至没有数据可读
    
      什么情况下,core中才会产生数据中断消息呢?当缓冲区为空时,收到数据才会插入缓冲区,然后产生数据中断消息;如果缓冲区不为空,收到数据时仅仅插入缓冲区,并不会产生数据中断消息。所以第3步要把缓冲区中的数据读完,这样才能保证以后收到的数据,可以产生中断消息,脚本可以及时处理
    

如图测试115200和9600波特率sscom发送300字节会分三次接收:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v9342JpQ-1624718810588)(http://openluat-luatcommunity.oss-cn-hangzhou.aliyuncs.com/images/20210608151556458_企业微信截图_16231202339482.png “undefined”)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zTDrpOXX-1624718810592)(http://openluat-luatcommunity.oss-cn-hangzhou.aliyuncs.com/images/20210608151620636_9600.png “undefined”)]发送100字节的时候是一次接收的:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHembeUz-1624718810593)(http://openluat-luatcommunity.oss-cn-hangzhou.aliyuncs.com/images/20210608151659767_100.png “undefined”)]

  1. 休眠、唤醒和功耗控制

      默认状态下,在合适的时间点(此时间点不可预知),系统会自动进入休眠状态;串口数据收发之前,必须通过pm.wake(...)接口使系统持续处于唤醒状态,才能保证收发功能正常;收发结束后,可以通过pm.sleep(...)接口允许系统自动休眠
    
      如果项目不要求低功耗,为了编程方便,可以调用pm.wake使系统一直处于唤醒状态
    
      如果项目要求低功耗,除了动态控制休眠唤醒外,还要使用uart.close关闭串口,这样才能完全消除串口功能的功耗
    

相关资料以及购买链接

来自转载:https://doc.openluat.com/wiki/21?wiki_page_id=1935

上一篇:Mini2440裸机开发之串口UART


下一篇:UVa 1583 Digit Generator(数学)