來了!STM32移植LuatOS,潘多拉示例全新教程


進擊的五月,繼上期《使用Air724UG制作簡易貪吃蛇》教程之后,@打盹的消防車 又為大家帶來基於STM32的潘多拉LuatOS移植全新教程:

 

為什么使用潘多拉作為教程呢?
 

STM32不能沒有通訊,那就選IoT開發板——潘多拉顯然沒什么短板,很適合入門使用。當然,其他STM32也可以參照本教程來做。

文中同樣涉及一些其他平台的移植思路,所以想移植LuatOS都可以看一看。

 

@

Luat OS架構分析

移植之前首先看一下LuatOS的總體構架:
 
 

LuatOS架構圖


可以看到,LuatOS做了一套適配層去對接平台,所以移植只需要做適配層就可以了(別跑,看着很多,其實移植不用做很多,許多已經做了)。

接下來我們看一下LuatOS目錄:
 
 

LuatOS文件目錄


  • bsp:

    bsp文件里存放着各種已經適配了的芯片。目前有:

    - Air001

    - Air100ST(STM32F4)

    - Air302(NB-IoT)

    - Air640W(Wi-Fi)

    - Air724UG(4G Cat.1)

    - Win32

    只有這些么?當然不是。W800(Wi-Fi+bt)本人也在做,目前做了基礎外設和LVGL;ESP32夢程在做,外設做完大部分,相信不久也會和大家見面;還有一些其他的,也已經計划適配了。

  • components:一些中間層,本次移植不需要。

  • docs:一些說明

  • lua:Lua虛擬機,重要

  • luat:luat層,重要

  • mind:思維導圖

  • script:腳本,本次移植不需要。

  • tools:工具

可以看到,我們主要做的就是移植lua、luat兩個文件夾,其中lua層為Lua虛擬機與平台無關,幾乎不用改什么,通常放進去可以直接編譯。

我們主要看luat:

- luat/cmsis_os2              
# cmsis_os2庫移植對接層,如果庫支持可以直接對接
- luat/freertos                 
# freertos庫移植對接層,如果使用freertos可以直接對接
- luat/rtt                      
# RT-Thread庫移植對接層,如果使用RT-Thread可以直接對接
- luat/include     # 頭文件 
- luat/module     # lua庫實現,幾乎無需改動
- luat/packages/lua-cjson   
# 平台無關的json庫(自由選擇軟件包)考。

 
 

LuatOS移植思路


介紹Luatos構架之后,我們說一下移植思路。需要移植的核心功能有:

- lua虛擬機

- msgbus(消息隊列)

- timer(定時)

- uart(打印)

- fs(文件系統)

- 外設

lua虛擬機我們直接把lua文件夾放進去編譯即可;

msgbus(消息隊列)、timer(定時)如果使用FreeRTOS、RT-Thread或者cmsis_os2,可以直接使用現成的,無需移植(可能不同平台需要微調);

uart(打印)和fs(文件系統)以及外設,我們需要針對自己的芯片進行對接。

Msgbus(消息隊列)移植


 

首先看msgbus(消息隊列),我們要實現luat_msgbus.h中的函數:

// 定義接口方法
void luat_msgbus_init(void);
uint32_t luat_msgbus_put(rtos_msg_t* msg, size_t timeout);
uint32_t luat_msgbus_get(rtos_msg_t* msg, size_t timeout);
uint32_t luat_msgbus_freesize(void);

可以看到我們只需要實現四個函數就可以:

luat_msgbus_init(消息隊列初始化)

luat_msgbus_put(消息隊發送)

luat_msgbus_get(消息獲取)

luat_msgbus_freesize(消息隊列剩余空閑位置)
 

這里我們以FreeRTOS為例:

void luat_msgbus_init(void) 

{

 if (!xQueue) { 

 #if configSUPPORT_STATIC_ALLOCATION xQueue =

 xQueueCreateStatic( QUEUE_LENGTH,

 ITEM_SIZE,ucQueueStorageArea,&xStaticQueue );

 #else

 xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);

 #endif

 }

}

uint32_t luat_msgbus_put(rtos_msg_t* msg, size_t timeout) 

{

if (xQueue == NULL)

 return 1;

return xQueueSendFromISR(xQueue, msg, NULL) == pdTRUE ? 0 : 1;

 }

uint32_t luat_msgbus_get (rtos_msg_t* msg, size_t timeout)

{

if (xQueue == NULL) 

 return 1;

return xQueueReceive(xQueue, msg, timeout) == pdTRUE ? 0 : 1; 

 }

uint32_t luat_msgbus_freesize(void)

{

if (xQueue == NULL) 

 return 1; 

 return 1;

 }

  

可以看到,我們做的就是將LuatOS的消息隊列對接到RTOS。
 
 
 

Timer(定時器)移植


接下來移植timer(定時器),我們要實現luat_timer.h中的函數:

int luat\_timer\_start(luat\_timer\_t\* timer);

int luat\_timer\_stop(luat\_timer\_t\* timer);

luat\_timer\_t\* luat\_timer\_get(size\_t timer\_id);

int luat\_timer\_mdelay(size\_t ms);

 

也很簡單,我們只有需要實現:

luat_timer_start(定時器開啟)

luat_timer_stop(定時器停止)

luat_timer_get(定時器獲取)

luat_timer_mdelay(延遲)
 
 

同樣我們以FreeRTOS為例:

 

int luat_timer_start(luat_timer_t* timer)

{

TimerHandle\_t os\_timer;

int timerIndex;

timerIndex = nextTimerSlot();

if (timerIndex < 0可以) 

{ 

 return 1; // too many timer!!

} 

os\_timer = xTimerCreate("luat\_timer", timer->timeout / portTICK\_RATE\_MS, timer->repeat, timer, luat\_timer\_callback);

if (!os\_timer) 

{ 

  return -1; 

} 

timers\[timerIndex\] = timer; 

timer->os\_timer = os\_timer;

int re = xTimerStart(os\_timer, 0);

if (re != pdPASS) 

{ 

 xTimerDelete(os\_timer, 0); 

 timers\[timerIndex\] = 0; 

 } 

 return re == pdPASS ? 0 : -1;

 }

int luat_timer_stop(luat_timer_t* timer) 

{ 

  if (!timer) 

 return 1; 

 for (size_t i = 0; i < FREERTOS\_TIMER\_COUNT; i++)          { 

  if (timers\[i\] == timer) 

 { 

 timers\[i\] = NULL; 

  break;

 } 

 } 

 xTimerStop((TimerHandle\_t)timer->os\_timer, 10); 

 xTimerDelete((TimerHandle\_t)timer->os\_timer, 10); 

 return 0; 

 }; 

 luat_timer_t\* luat_timer_get(size_t timer\_id) 

 {

 for (size_t i = 0; i < FREERTOS\_TIMER\_COUNT; i++){ if (timers\[i\] && timers\[i\]->id == timer\_id) 

 { 

 return timers\[i\]; 

 } 

}

return NULL; 

}

int luat_timer_mdelay(size_t ms) 

{ 

  if (ms > 0) 

 { 

 vTaskDelay(ms / portTICK\_RATE\_MS); 

 } 

return 0; 

}

可以看到,和消息隊列一樣,只要將LuatOS的定時函數對接RTOS的定時函數就OK啦,很簡單是不是。

 
 
 

Uart(打印)移植


接下來uart(打印),我們需要實現luat_uart.h,針對使用的板子實現以下幾個串口基本的函數即可:

int l\_uart\_handler(lua\_State \*L, void\* ptr);

int luat\_uart\_setup(luat\_uart\_t\* uart);

int luat\_uart\_write(int uartid, void\* data, size\_t length);

int luat\_uart\_read(int uartid, void\* buffer, size\_t length);

int luat\_uart\_close(int uartid);

int luat\_uart\_exist(int uartid);

int luat\_setup\_cb(int uartid, int received, int sent);

 
 
 

文件系統移植


剩下一個文件系統,如果我們的板子支持posix風格,那么恭喜,可以直接對接,否則我們需要實現luat_fs.h。

int luat_fs_init(void);
int luat_fs_mkfs(luat_fs_conf_t *conf);
int luat_fs_mount(luat_fs_conf_t *conf);
int luat_fs_umount(luat_fs_conf_t *conf);
int luat_fs_info(const char* path, luat_fs_info_t *conf);
FILE* luat_fs_fopen(const char *filename, const char *mode);
int luat_fs_getc(FILE* stream);
int luat_fs_fseek(FILE* stream, long int offset, int origin);
int luat_fs_ftell(FILE* stream);
int luat_fs_fclose(FILE* stream);
int luat_fs_feof(FILE* stream);
int luat_fs_ferror(FILE *stream);
size_t luat_fs_fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t luat_fs_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int luat_fs_remove(const char *filename);
int luat_fs_rename(const char *old_filename, const char *new_filename);
size_t luat_fs_fsize(const char *filename);
int luat_fs_fexist(const char *filename);
int luat_fs_mkdir(char const* _DirName);
int luat_fs_rmdir(char const* _DirName);
#ifdef LUAT_USE_FS_VFS
struct luat_vfs_file_opts
{
FILE* (*fopen)(void* fsdata, const char *filename, const char *mode); i
nt (*getc)(void* fsdata, FILE* stream); 
int (*fseek)(void* fsdata, FILE* stream, long int offset, int origin); 
int (*ftell)(void* fsdata, FILE* stream); 
int (*fclose)(void* fsdata, FILE* stream);
int (*feof)(void* fsdata, FILE* stream);
int (*ferror)(void* fsdata, FILE *stream); 
size_t (*fread)(void* fsdata, void *ptr, size_t size, size_t nmemb, FILE *stream); 
size_t (*fwrite)(void* fsdata, const void *ptr, size_t size, size_t nmemb, FILE *stream);
};
struct luat_vfs_filesystem_opts 
{
int (*remove)(void* fsdata, const char *filename);   int (*rename)(void* fsdata, const char *old_filename, const char *new_filename); 
size_t (*fsize)(void* fsdata, const char *filename); int (*fexist)(void* fsdata, const char *filename); 
int (*mkfs)(void* fsdata, luat_fs_conf_t *conf); 
int (*mount)(void** fsdata, luat_fs_conf_t *conf); 
int (*umount)(void* fsdata, luat_fs_conf_t *conf); int (*info)(void* fsdata, const char* path, luat_fs_info_t *conf); 
int (*mkdir)(void* fsdata, char const* _DirName); int (*rmdir)(void* fsdata, char const* _DirName);
};
struct luat_vfs_filesystem 
{
char name[16]; 
struct luat_vfs_filesystem_opts opts;
struct luat_vfs_file_opts fopts;
};
typedef struct luat_vfs_mount 
{
 struct luat_vfs_filesystem *fs;
void *userdata; char prefix[16]; 
int ok;
} 
luat_vfs_mount_t;
typedef struct luat_vfs_fd
{
   FILE* fd;
luat_vfs_mount_t *fsMount;
}
luat_vfs_fd_t;
typedef struct luat_vfs
{
 struct luat_vfs_filesystem*   fsList[LUAT_VFS_FILESYSTEM_MAX];
luat_vfs_mount_t mounted[LUAT_VFS_FILESYSTEM_MOUNT_MAX];
luat_vfs_fd_t fds[LUAT_VFS_FILESYSTEM_FD_MAX+1];
}
luat_vfs_t;
int luat_vfs_init(void* params);
int luat_vfs_reg(const struct luat_vfs_filesystem* fs);
FILE* luat_vfs_add_fd(FILE* fd, luat_vfs_mount_t * mount);
int luat_vfs_rm_fd(FILE* fd);
#endif

這部分需要針對各自的平台實現對接,各位需要針對自己的去實現。

隨后我們需要創建一個luat_base_xxx.c去管理我們移植的庫以及自己的板卡信息,這里我們以Air302為例:
 
 
 

Air302移植示例


static const luaL\_Reg loadedlibs\[\] = { 

 {"\_G", luaopen\_base}, // \_G 

 {LUA\_LOADLIBNAME, luaopen\_package}, // require 

 {LUA\_COLIBNAME, luaopen\_coroutine}, // coroutine協程庫 

 {LUA\_TABLIBNAME, luaopen\_table},    // table庫,操作table類型的數據結構 

 {LUA\_IOLIBNAME, luaopen\_io},    // io庫,操作文件 

 {LUA\_OSLIBNAME, luaopen\_os},    // os庫,已精簡 

 {LUA\_STRLIBNAME, luaopen\_string},   // string庫,字符串操作 

 {LUA\_MATHLIBNAME, luaopen\_math},    // math 數值計算 

 {LUA\_DBLIBNAME, luaopen\_debug},   // debug庫,已精簡

// 往下是LuatOS定制的庫, 如需精簡請仔細測試

//------------------------------------------------------------

// 核心支撐庫, 不可禁用!! 

 {"rtos",    luaopen\_rtos},    // rtos底層庫, 核心功能是隊列和定時器 

 {"log",     luaopen\_log},    // 日志庫 

 {"timer",   luaopen\_timer},    // 延時庫

//-------------------------------------------------------------

// 設備驅動類, 可按實際情況刪減. 即使最精簡的固件, 也強烈建議保留uart庫 

 {"uart",    luaopen\_uart},   // 串口操作 

 {"gpio",    luaopen\_gpio},   // GPIO腳的操作 

 {"i2c",     luaopen\_i2c},    // I2C操作 

 {"spi",     luaopen\_spi},    // SPI操作 

 {"adc",     luaopen\_adc},    // ADC模塊 

 {"pwm",     luaopen\_pwm},   // PWM模塊

//-------------------------------------------------------------

// 工具庫, 按需選用 

 {"json",    luaopen\_cjson},    // json的序列化和反序列化 

 {"pack",    luaopen\_pack},    // pack.pack/pack.unpack 

 {"mqttcore",luaopen\_mqttcore},    // MQTT 協議封裝 

 {"libcoap", luaopen\_libcoap},   // 處理COAP消息 

 {"libgnss", luaopen\_libgnss},    // 處理GNSS定位數據 

 {"fs",   luaopen\_fs},    // 文件系統庫,在io庫之外再提供一些方法 

 {"sensor",  luaopen\_sensor},    // 傳感器庫,支持DS18B20 

 {"disp",  luaopen\_disp},    // OLED顯示模塊,支持SSD1306 

 {"u8g2", luaopen\_u8g2},     // u8g2 

 {"crypto",luaopen\_crypto},   // 加密和hash模塊

 // {"eink",  luaopen\_eink}, 

 // 電子墨水屏,試驗階段 

//{"iconv", luaopen\_iconv}, 

// 編碼轉換,暫不可用

//----------------------------------------------------------------

// 聯網及NBIOT特有的庫 

 {"socket",  luaopen\_socket},     // 套接字操作 

 {"lpmem",   luaopen\_lpmem},      // 低功耗時仍工作的內存塊 

 {"nbiot",   luaopen\_nbiot},   // NBIOT專屬模塊 

 {"pm",      luaopen\_pm},    // 低功耗模式 

 {"http",  luaopen\_http},  // http庫 

 {"ctiot", luaopen\_ctiot},   // ctiot庫,中國電信ctwing平台 

 {NULL, NULL}

 };

// 按不同的rtconfig加載不同的庫函數

void luat\_openlibs(lua\_State \*L) { 

// 加載系統庫 

const luaL\_Reg \*lib;

/\* "require" functions from 'loadedlibs' and set results to global table \*/ 

for (lib = loadedlibs; lib->func; lib++) { luaL\_requiref(L, lib->name, lib->func, 1); 

 lua\_pop(L, 1);  /\* remove lib \*/ 

 }

 }

const char\* luat\_os\_bsp(void) { 

return "ec616"; 

}

  

  

我們可以將未實現的或者不想編譯的注釋掉,修改bsp名等,隨后在我們的主程序中啟用lua虛擬機。


#include "bget.h

"#include "luat\_base.h"

void app\_main(void)

{

bpool(ptr, size); // lua vm需要一塊內存用於內部分配, 給出首地址及大小   

luat_main();// luat_main是LuatOS的主入口, 該方法通常不會返回

}

接下來我們就是編譯,根據報錯修改、調試。

這樣LuatOS基礎移植就實現了,隨后就是外設的適配。和之前一樣,查看對應的.h文件,去對接需要實現的函數,可以參考已經實現的做移植。

可以看到——LuatOS移植的依賴並不多,甚至沒有RTOS也可以實現移植。

 
 
 

潘多拉移植示例


移植順序按照wendal在LuatOS上的bsp移植順序,依次為:編譯環境的集成、核心功能的適配、外設的適配以及網絡接口的適配。
 
 

編譯環境的集成


首先我們需要一個潘多拉的rtt工程,clone rtt的最新倉庫,進去潘多拉的bsp使用scons --dist命令提取一個工程。

\- lua                    # Lua虛擬機 

\- luat/module       # lua庫實現

放進去編譯,確保編譯沒問題。

核心功能的適配


我們使用的RTT,這部分移植已經做好了,只需要把RTT目錄放進去,首次移植編譯我們只加入一些核心基礎的就可以,不需要加入RTT目錄中全部代碼。

可以看到,核心的移植已經都做好了。編譯之前需要配置一下RTT:
 
 
 

RTT基礎配置操作


• menuconfig進入開啟文件系統

• 開啟nor flash(我們使用了板載的nor flash)

• 修改主線程heap

• 開啟libc庫

• 開啟ymodem為了后面下載腳本

外設開啟QSPI FLASH驅動

開啟timer等其他驅動(按自己實際需要)

軟件包開啟FAL

軟件包開啟littlefs

隨后,將luat_rtt_base.c中未使用的庫注釋掉:

編譯看看,會報錯:

我們的bsp已經做了FAL配置,所以進入FAL軟件包,把sample去掉:

然后我們初始化文件系統,新建一個luat_fs_init.c:
 
 
 

新建luat_fs_init.c文件


#include "luat_base.h"

#include "luat_malloc.h"

#include "luat_msgbus.h"

#include "luat_timer.h"

#include "luat_gpio.h"

#include "rtthread.h"

#include <rtdevice.h>

/* 添加 fal 頭文件 */

#include <fal.h>

/* 添加文件系統頭文件 */

#include <dfs_fs.h>

  

#define DBG_TAG "port.fs"

#define DBG_LVL       DBG_LOG

#include <rtdbg.h>

  

#include "drv_flash.h"

#include "lfs.h"  

  

/* 定義要使用的分區名字 */

#define FS_PARTITION_NAME             "filesystem"

static uint8_t fs_ok = 0;

extern char luadb_inline[];

  

int luat_fs_init(void){ 

 if (fs_ok) return 0;

 fs_ok = 1;

struct rt_device *mtd_dev= RT_NULL; 

 /* 初始化 fal */    
 fal_init();  /* 生成 mtd 設備 */    
 mtd_dev = fal_mtd_nor_device_create(FS_PARTITION_NAME); 

if (!mtd_dev)
   {        
   LOG_E("Can't create a mtd device on '%s' partition.", FS_PARTITION_NAME);
   }

else   
{ 

 /* 掛載 littlefs */        
 if (dfs_mount(FS_PARTITION_NAME, "/", "lfs", 0, 0) == 0)       
 {
             LOG_I("Filesystem initialized!");        
 } 

 else
{ 

 /* 格式化文件系統 */            
 dfs_mkfs("lfs", FS_PARTITION\_NAME); 

 /* 掛載 littlefs */            
 if (dfs_mount("filesystem", "/", "lfs", 0, 0) == 0)            
 {                
   LOG_I("Filesystem initialized!");            
 } 

 else            
 {                LOG_E("Failed to initialize filesystem!");            
 }        
}
} 

// 嘗試掛載luadb區域
 mkdir("/lua", 0); 

 return 0;} 
 INIT_ENV_EXPORT(luat_fs_init);

 
 
 

修改main文件


#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#define DBG_ENABLE#define DBG_SECTION_NAME     "main"
#define DBG_LEVEL       DBG_LOG
#define DBG_COLOR

#include <rtdbg.h>
#include "luat_base.h"
int main(void)
{
    rt_thread_mdelay(100); // 故意延后100ms    luat_log_set_uart_port(1);
    luat_main();    
   while (1)
    {        rt_thread_delay(10000000);    }}

此時編譯測試正常,下載測試:

可以看到虛擬機正常跑起來了,因為沒找到main.lua所以重啟。

我們把sys.lua和main.lua,通過ymodem下載進去重啟:

腳本運行成功,至此LuatOS基礎移植成功。
 
 
 

外設的適配


基於RTT的大部分外設已經適配了,直接添加我們之前刪除的RTT目錄下的文件編譯測試即可。
 
 
 

網絡接口的適配


基於RTT的網絡接口也已經適配了,直接添加我們之前刪除的RTT目錄下的文件編譯測試即可。

本期移植教程就講到這里了.

源碼下載

上海合宙通信模塊 - 合宙Luat,讓萬物互聯更簡單


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM