跳转至

20 LoRa

作者:王世豪 | 最后修改:2026-04-10

一、lora 概述

LuatOS 的 lora2 核心库,支持多设备挂载,提供完整的 lora 通信功能。仅支持 LLCC68、SX1268 两款主流 lora 芯片。

1.1 lora 通信原理

LoRa(Long Range)是 semtech 公司创建的低功耗、远距离、无线、广域网的标准。

LoRa 模块采用扩频通信技术,其核心原理是将信号的频谱扩展到一个很宽的频率范围内,从而提高信号的抗干扰能力和安全性。LoRa 模块主要采用的是基于线性调频(LFM)的扩频技术,即 Chirp Spread Spectrum(CSS)。

LoRa 模块在发送数据时,会将数据信号与一个高速的伪随机码(PN 序列)进行模二加运算,生成扩频信号。接收端使用相同的 PN 序列对接收到的扩频信号进行解扩,恢复出原始数据信号。

如果您需要权威参考,建议查阅 Semtech 官方(https://www.semtech.cn/lora)发布的 LoRa 技术文档或 LLCC68/SX1268 系列芯片数据手册。

1.2 工作模式

  1. 发送模式:用于无线发送数据,发送完成触发 "tx_done"事件。

  2. 接收模式:监听并接收无线信号,接收成功触发 "rx_done"事件 ,超时触发 "rx_timeout"事件。

  3. 待机模式:准备就绪状态,介于工作和睡眠之间,作为发送/接收操作前的过渡状态。

1.3 注意事项

由于 LoRa 设备的半双工特性,当设备 A 处于发送模式时,若设备 B 同时发送数据,设备 A 将无法接收,导致数据丢失。

二、演示功能概述

本文使用 Air8101 核心板 +lora 模块测试 lora 的数据发送和接收功能。

注意:需要两个 Air8101 核心板 + 两个 lora 模块,才能进行有效的通信测试。

三、准备硬件环境

3.1 Air8101 核心板

使用 Air8101 核心板,如下图所示:

此开发套件的详细使用说明参考:硬件手册和证书 中的 Air8101 核心板使用说明

3.2 lora 模块

本文档演示使用的 LoRa 模块是 南京二五五物联科技255MN-L03 模块,是一款基于射频芯片LLCC68设计的无线收发模组。该模组具有+22dBm的可调输出功率,最低4.2mA的接收电流,传输距离远,可靠性高,功耗低。模块提供了SPI通用接口,采用半双工通信方式。

二五五物联官网:https://255mesh.com/

资料下载:255MN-L03 模块资料

淘宝购买链接:https://item.taobao.com/item.htm?abbucket=9&id=905046008431&mi_id=0000sz-y6pYMNtwApPC3D3afiJFxiZmXkCwCdhnQSR3qV8w&ns=1&priceTId=213e09fa17659699305601049e125b&skuId=5765457041940&spm=a21n57.1.hoverItem.2&utparam=%7B%22aplus_abtest%22%3A%22536abb31a9b47a2a9d837ffff3617cf5%22%7D&xxc=taobaoSearch

3.3 PC 电脑

WINDOWS 系统,其他暂无特别要求;

3.4 数据通信线

USB 数据线(其一端为 Type-C 接口,用于连接 Air8101 核心板)。

Air8101 核心板和数据线的硬件接线方式为

  • Air8101 核心板通过 TYPE-C USB 口供电;(核心板背面的功耗测试开关拨到 OFF 一端)

  • 如果测试发现软件频繁重启,重启原因值为:poweron reason 0,可能是供电不足,此时再通过直流稳压电源对核心板的 vbat 管脚进行 4V 供电,或者 VIN 管脚进行 5V 供电;

  • TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;

3.5 Air8101 和 lora 模块硬件接线

Air8101 核心板
lora模块
3V3
VCC
GND
GND
65/SPI1_CLK
SCK
66/SPI1_MOSI
MOSI
8/SPI1_MISO
MISO
67/SPI1_CS
CSS
9/GPIO6
RST
10/GPIO7
BUSY
14/GPIO8
DIO1

硬件连接注意事项:

1、电源要求:

  • 不同 lora 模块的供电电压范围可能存在差异,请根据具体模块规格确定合适的电源电压

2、控制信号连接:

  • RST 引脚:连接至核心板的 GPIO6,用于 lora 模块复位控制

  • BUSY 引脚:连接至核心板的 GPIO7,用于 lora 模块忙状态指示

  • DIO1 引脚:连接至核心板的 GPIO8,用于 lora 模块中断信号接收

3、连接确认:

  • 上述 GPIO 引脚分配已在测试中验证可用

  • 如有变更需求,需同步修改软件配置中的引脚定义

四、准备软件环境

4.1 软件环境

1. 烧录工具:Luatools 下载调试工具

2.本demo开发测试时使用的固件为Air8101 V2001 版本固件,(测试 lora 功能需要大于等于 V2001),所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

3. 脚本文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8101/demo/lora2

4. LuatOS 运行所需要的 lib 文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件。

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 核心板中,将本篇文章中演示使用的项目文件烧录到 Air8101 核心板中。

4.2 API 介绍

lora2 核心库:https://docs.openluat.com/osapi/core/lora2/

五、程序结构

lora2/
├── main.lua
├── lora2_main.lua
├── lora2_receiver.lua
├── lora2_sender.lua
├── uart_app.lua
└── readme.md

5.1 文件说明

  1. main.lua:主程序入口文件。

  2. lora2_main.lua:LoRa 设备初始化和核心任务管理。

  3. lora2_receiver.lua:接收数据处理和转发。

  4. lora2_sender.lua:数据发送队列和传输管理。

  5. uart_app.lua:串口应用模块。

六、代码详解

6.1 main.lua

主程序文件 main.lua 是整个项目的入口点。它负责初始化系统环境。

6.2 lora2_main.lua

lora2_main.lua 是 LoRa 主应用功能模块,负责 LoRa 设备的初始化、配置和核心任务管理。主要功能包括:

  1. 设备初始化与配置:通过 lora2_init()函数初始化 SPI 接口和 LoRa 设备,设置工作频率为 433MHz

  2. 参数配置:配置发送参数(功率、带宽、数据率等)和接收参数

  3. 事件回调处理:定义 callback()函数处理各种 LoRa 事件(tx_done、rx_done、rx_timeout、rx_error)

  4. 主任务管理:lora2_main_task_func()实现设备初始化、回调注册、数据收发管理的主循环

  5. 消息传递:通过 sys.sendMsg()实现与其他模块的通信,将设备就绪事件和操作结果通知相关模块

-- 加载依赖模块
local lora2_sender = require "lora2_sender"
local lora2_receiver = require "lora2_receiver"

local TASK_NAME = "lora2_task"

local spi_id = 1 -- SPI接口ID
local pin_cs = 3 -- 片选引脚
local pin_reset = 6 -- 复位控制引脚
local pin_busy = 7 -- 忙状态指示引脚
local pin_dio1 = 8 -- DIO1引脚

local RECEIVE_TIMEOUT = 3000  -- 接收超时时间3秒

--[[
event值有:
    tx_done     -- 发送完成:数据已成功发送
    rx_done     -- 接收完成:成功接收到数据
    rx_timeout  -- 接收超时:在指定时间内未收到数据
    rx_error    -- 接收错误:接收过程中发生错误
__]]
function callback(lora_device, event, data, size)
    if event == "tx_done" then
        sys.sendMsg(TASK_NAME, "LORA_EVENT", "tx_done")
    elseif event == "rx_done" then
        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_done", data, size)
    elseif event == "rx_timeout" then
        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_timeout")
    elseif event == "rx_error" then
        sys.sendMsg(TASK_NAME, "LORA_EVENT", "rx_error")
    else
        log.warn("未知事件类型:", event)
    end
end

local function lora2_init()
    -- 初始化SPI
    spi_lora = spi_lora or spi.deviceSetup(spi_id,pin_cs,0,0,8,10*1000*1000,spi.MSB,1,0)
    if not spi_lora then
        log.error("spi_lora init failed")
        return false
    end

    -- 初始化LORA2设备
    -- 当前支持型号:llcc68, sx1262
    lora_device = lora_device or lora2.init("llcc68",{res = pin_reset,busy = pin_busy,dio1 = pin_dio1},spi_lora)
    if not lora_device then
        log.error("lora_device init failed")
        return false
    end
    log.info("lora_device",lora_device)

    -- 设置频道频率为433MHz
    lora_device:set_channel(433000000) 

    -- 配置 lora 设备的发送参数
    lora_device:set_txconfig({
        mode=1, 
        power=22,
        fdev=0,
        bandwidth=0,
        datarate=9,
        coderate=4,
        preambleLen=8,
        fixLen=false,
        crcOn=true,
        freqHopOn=0,
        hopPeriod=0,
        iqInverted=false
    })

    -- 配置 lora 设备的接收参数
    lora_device:set_rxconfig({
        mode=1,
        bandwidth=0,
        datarate=9,
        coderate=4,
        bandwidthAfc=0,
        preambleLen=8,
        symbTimeout=0,
        fixLen=false,
        payloadLen=0,
        crcOn=true,
        freqHopOn=0,
        hopPeriod=0,
        iqInverted=false,
        rxContinuous=false
    })

    return true
end

local function lora2_main_task_func()
    local result,msg

    while true do
        result = lora2_init()
        if not result then
            log.info("lora2_init error")
            goto EXCEPTION_PROC
        end

        -- 注册回调
        lora_device:on(callback)

        -- 默认初始化后启动接收
        lora_device:recv(RECEIVE_TIMEOUT)

        --- 发送设备就绪事件,将lora_device传递给sender模块
        sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "DEVICE_READY", lora_device) 

        while true do
            msg = sys.waitMsg(TASK_NAME, "LORA_EVENT")

            if msg[2]== "tx_done" then
                log.info("lora2_main", "发送完成")
                -- 通知sender模块发送完成
                sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "TX_DONE")
                -- 发送完成后启动接收
                lora_device:recv(RECEIVE_TIMEOUT)

            elseif msg[2]== "rx_done" then
                log.info("lora2_main", "接收完成", "数据长度:", msg[4])
                -- 交由receiver模块处理数据
                lora2_receiver.proc(msg[3], msg[4], lora_device)
                -- 处理完成后启动接收
                lora_device:recv(RECEIVE_TIMEOUT)

            elseif msg[2]== "rx_timeout" then
                log.info("lora2_main", "接收超时")
                -- 接收超时后继续接收
                lora_device:recv(RECEIVE_TIMEOUT)

                -- 接收过程中发生错误
            elseif msg[2]== "rx_error" then
                log.info("lora2_main", "接收错误")
                -- 接收错误后继续接收
                lora_device:recv(RECEIVE_TIMEOUT)
            end
        end

        -- 出现异常
        ::EXCEPTION_PROC::

        -- 清空此task绑定的消息队列中的未处理的消息
        sys.cleanMsg(TASK_NAME)

        sys.wait(5000)
    end
end

sys.taskInitEx(lora2_main_task_func, TASK_NAME)

6.3 lora2_receiver.lua

lora2_receiver.lua 是 LoRa 数据接收应用功能模块,专注于处理接收到的 LoRa 数据。主要功能包括:

  1. 数据接收处理:提供 proc()函数作为对外接口,处理接收到的数据和大小信息

  2. 日志记录:记录接收到的数据长度和原始数据

  3. 数据转发:通过 sys.publish("LORA_RECV_DATA", data)将接收到的数据发布给其他订阅该消息的模块(如 uart_app.lua)

local lora2_receiver = {}

-- 处理接收到的lora数据
function lora2_receiver.proc(data, size)
    log.info("lora2_receiver", "收到数据", size, data)
    -- 发布数据给其他模块
    sys.publish("LORA_RECV_DATA", data)
end

return lora2_receiver

6.4 lora2_sender.lua

lora2_sender.lua 是 LoRa 数据发送应用功能模块,负责管理数据发送队列和发送任务。主要功能包括:

  1. 发送队列管理:维护 send_queue 数据结构,存储待发送的数据和回调信息

  2. 消息订阅:订阅"SEND_DATA_REQ"消息,接收其他模块的数据发送请求

  3. 任务处理:通过 lora2_sender_task_func()任务函数处理设备就绪、发送请求和发送完成等事件

  4. 数据发送:send_item_func()按顺序处理队列中的数据项,调用 LoRa 设备发送功能

  5. 回调通知:数据发送完成后通过回调函数通知发送方

local lora2_sender = {}

--[[
数据发送队列,数据结构为:
{
    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
}
data: 要发送的数据,string类型,必须存在;
cb.func: 数据发送结果的用户回调函数,可以不存在;
cb.para: 数据发送结果的用户回调函数参数,可以不存在;
]]

local send_queue = {}

lora2_sender.TASK_NAME = "lora2_sender"

-- "SEND_DATA_REQ"消息的处理函数
local function send_data_req_proc_func(tag, data, cb)
    -- 将数据插入到发送队列send_queue中
    table.insert(send_queue, {data=data, cb=cb})
    -- 发送消息通知 lora sender task,有新数据等待发送
    sys.sendMsg(lora2_sender.TASK_NAME, "LORA_EVENT", "SEND_REQ") 
    log.info("队列", #send_queue)
end

-- 按照顺序发送send_queue中的数据
-- 发送请求提交后,返回当前正在发送的数据项,等待发送完成事件
-- 如果设备未初始化,则通知回调函数发送失败,并继续处理下一条
local function send_item_func(lora_device)
    local item
    -- 如果发送队列中有数据等待发送
    while #send_queue>0 do
        -- 取出来第一条数据赋值给item
        -- 同时从队列send_queue中删除这一条数据
        item = table.remove(send_queue, 1)

        -- 检查设备是否初始化
        if not lora_device then
            log.error("lora2_sender", "设备未初始化")
            -- 通知回调函数发送失败
            if item.cb and item.cb.func then
                item.cb.func(false, item.cb.para)
            end
            return nil
        end

        -- 发送数据
        lora_device:send(item.data)

        -- 返回当前发送项,等待发送完成"TX_DONE"事件
        return item
    end
    return nil
end

-- 处理发送结果的回调函数
local function send_item_cbfunc(item, result)
    if item then
        -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
        if item.cb and item.cb.func then
            item.cb.func(result, item.cb.para)
        end
    end
end

-- lora client sender的任务处理函数
local function lora2_sender_task_func()
    local lora_device
    local send_item = nil
    local msg

    while true do
        -- 等待"LORA_EVENT"消息
        msg = sys.waitMsg(lora2_sender.TASK_NAME, "LORA_EVENT")
        log.info("lora2_sender", "收到消息", msg[2])

        -- 设备就绪事件
        if msg[2] == "DEVICE_READY" then
            lora_device = msg[3]
            -- 如果当前没有正在发送的数据,则开始发送队列中的数据
            if lora_device and not send_item then
                send_item = send_item_func(lora_device)
            end

        -- 发送数据请求
        elseif msg[2] == "SEND_REQ" then
            -- 如果当前没有正在发送的数据,则开始发送队列中的数据
            if lora_device and not send_item then
                send_item = send_item_func(lora_device)
            end
        -- 发送完成事件
        elseif msg[2] == "TX_DONE" then
            -- 通知回调函数发送成功
            send_item_cbfunc(send_item, true)
            -- 清空当前发送项
            send_item = nil
            -- 继续处理队列中的下一条数据
            send_item = send_item_func(lora_device)
        end
    end
end

-- 订阅"SEND_DATA_REQ"消息;
-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
-- 本demo项目中uart_app.lua中publish了这个消息;
sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)

--创建并且启动一个task
--运行这个task的处理函数lora2_sender_task_func
sysplus.taskInitEx(lora2_sender_task_func, lora2_sender.TASK_NAME)

return lora2_sender

6.5 uart_app.lua

uart_app.lua 是 UART 应用功能模块,实现串口通信和数据转发功能。主要功能包括:

1、串口初始化:配置 UART1,波特率 115200,数据位 8,停止位 1,无奇偶校验位

2、数据接收处理:实现 read()中断处理函数,非阻塞读取串口数据

3、数据拼接:通过 concat_timeout_func()和 50 毫秒定时器实现数据拼接,避免大数据包被拆分成多个小包

4、数据转发:

  • 串口数据通过 sys.publish("SEND_DATA_REQ", "uart", read_buf)转发给 LoRa 发送模块

  • 接收 LoRa 数据通过 uart.write(UART_ID, data.."\r\n")发送到 PC 端串口工具

5、消息订阅:订阅"LORA_RECV_DATA"消息,处理接收到的 LoRa 数据

  • 该模块实现了 PC 端与 LoRa 设备之间的数据双向传输。
-- 使用UART1
local UART_ID = 1
-- 串口接收数据缓冲区
local read_buf = ""

-- 末尾增加回车换行两个字符,通过uart发送出去,方便在PC端换行显示查看
local function recv_data_from_lora_proc(data)
    uart.write(UART_ID, data.."\r\n")
end

local function concat_timeout_func()
    -- 如果存在尚未处理的串口缓冲区数据;
    -- 将数据通过publish通知其他应用功能模块处理;
    -- 然后清空本文件的串口缓冲区数据
    if read_buf:len() > 0 then
        sys.publish("SEND_DATA_REQ", "uart", read_buf)
        read_buf = ""
    end
end

-- UART1的数据接收中断处理函数,UART1接收到数据时,会执行此函数
local function read()
    local s
    while true do
        -- 非阻塞读取UART1接收到的数据,最长读取1024字节
        s = uart.read(UART_ID, 1024)

        -- 如果从串口没有读到数据
        if not s or s:len() == 0 then
            -- 启动50毫秒的定时器,如果50毫秒内没收到新的数据,则处理当前收到的所有数据
            -- 这样处理是为了防止将一大包数据拆分成多个小包来处理
            -- 例如pc端串口工具下发1100字节的数据,可能会产生将近20次的中断进入到read函数,才能读取完整
            -- 此处的50毫秒可以根据自己项目的需求做适当修改,在满足整包拼接完整的前提下,时间越短,处理越及时
            sys.timerStart(concat_timeout_func, 50)
            -- 跳出循环,退出本函数
            break
        end

        log.info("uart_app.read len", s:len())

        -- 将本次从串口读到的数据拼接到串口缓冲区read_buf中
        read_buf = read_buf..s
    end
end

-- 初始化UART1,波特率115200,数据位8,停止位1
uart.setup(UART_ID, 115200, 8, 1)

-- 注册UART1的数据接收中断处理函数,UART1接收到数据时,会执行read函数
uart.on(UART_ID, "receive", read)

-- 订阅"LORA_RECV_DATA"消息的处理函数recv_data_from_lora_proc
-- 收到"LORA_RECV_DATA"消息后,会执行函数recv_data_from_lora_proc
sys.subscribe("LORA_RECV_DATA", recv_data_from_lora_proc)

七、演示功能

下面将进行 Air8101 核心板搭配 lora 模块发送和接收数据的演示,Air8101 核心板 +lora 模块,下面统称为 lora 设备。

上电后,当初始化 LoRa 设备成功后,系统会输出 "LORA*: 609B7780" 这样的日志信息,表明 LoRa 设备已准备就绪并分配了相应的内存地址。

初始化完成后,lora 设备 A 和 B 会进入 lora 接收状态,等待接收数据; 若接收超时,会继续等待接收, 所以在没有数据传输时,luatools 会一直打印接收超时信息。

7.1 lora 设备 A 和 B 互传数据

通过下面的图片,可以看到:

LoRa 设备 A 通过串口发送"hello, i am A!",经 LoRa 无线传输至设备 B,B 接收后通过串口显示在 PC 工具界面;

同理,设备 B 通过串口发送"hello, i am B!",经 LoRa 无线传输至设备 A,A 接收后通过串口显示在 PC 工具界面。

八、总结

至此,我们已使用 Air8101 核心板实现了基于 LoRa2 模块的无线数据收发通信功能。