Using Lua for Embedded Development vs. Traditional C Code

16 min read Original article ↗

Embedded systems have long been dominated by C and C++, valued for their low-level control and performance. However, as modern embedded development increasingly involves complex logic, especially in IoT and edge devices, many developers are now combining C/C++ with higher-level languages like Lua, supported by frameworks such as Xedge.

This is not Lua instead of C; it's Lua with C. You still write your low-level drivers and performance-critical code in C, but you embed Lua into your firmware to handle the high-level logic, aka business logic.

This hybrid model brings serious advantages: faster development, easier maintenance, and greater flexibility, all without sacrificing the raw performance C gives you at the hardware level.

Lua is a lightweight, high-level language designed for embedding into applications and firmware. Unlike languages such as C# and Java, where developers typically write nearly the entire system within the managed language and its runtime, Lua differs as it was specifically designed to live within a host application (often written in C). In this model, the low-level, performance-critical components remain in C, while Lua handles higher-level logic, configuration, and rapid iteration. This hybrid approach gives embedded and firmware developers the fine-grained control of C, combined with the flexibility and agility of a high-level scripting language. This dual-language culture, embraced by the developers, is central to how Lua is used in embedded and non-embedded systems.

Lua is written in ANSI C for cross-platform compatibility, it offers a simple C API that makes integration easy and efficient. Lua is compiled into bytecode, which is then executed by a virtual machine, similar to how Java operates. Lua's syntax is straightforward to learn, combining the simplicity of C, but it is way more forgiving.

Why Lua Loves C and Why Smart C Developers Love Lua

Lua was not designed as a standalone language but crafted as an embeddable extension language, i.e., you embed the Lua C code library in your C application. Lua simplifies the interaction between Lua and C code; Lua can call functions written in C, and C code can invoke Lua functions. Lua's compact size, impressive speed, and engineering excellence make it unique. It is also the fastest non-JIT high-level language.

Lua loves C

Speed of Development

Developing embedded business logic in C typically involves writing hundreds of lines of code just to handle low-level tasks like memory management and managing complex APIs. In contrast, Lua, when used with a framework such as Xedge, abstracts away these details by providing ready-to-use libraries and modules for things like network communication and device management. Instead of dealing with memory allocation and other complex C tasks, developers can immediately start writing high-level business logic, such as handling sensor data, processing user inputs, or managing cloud connections. This reduces development time from weeks to days, allowing teams to focus on implementing features rather than struggling with low-level code. Lua's simplicity allows for rapid prototyping and iteration, essential for modern IoT and embedded projects where agility and security are critical.

Lua Simplifies Embedded Development in Larger Teams

One of the most overlooked advantages of Lua in embedded systems is how it enables more people to contribute effectively, even if they aren’t low-level C experts. Lua was designed to be simple, lightweight, and easy to learn. That means a junior developer or someone with limited embedded experience can start building useful features almost immediately.

The critical requirement is that one experienced C developer handles the integration layer, which serves as the bridge between the Lua runtime and the underlying hardware. Once this layer (Lua to C API) is in place, the rest of the team can stay focused on business logic, user interfaces, or protocol handling in Lua. This division of labor is powerful: instead of everyone needing to wrestle with registers, interrupts, and datasheets, most of the team writes in a high-level language that is approachable, consistent, and portable.

The result is faster iteration and reduced risk. Features can be prototyped and tested quickly, changes can be rapidly deployed without modifying C code, and long-term maintenance becomes easier because the core logic lives in Lua, not scattered across low-level C code. Over the lifetime of a product, that adds up to lower costs, less developer frustration, and a much smoother path for onboarding new team members.

Memory Management and Safety

Manual memory management is one of the most challenging aspects of C development. Mismanagement can lead to common issues like memory leaks, buffer overflows, and pointer errors. Lua, by contrast, is a garbage-collected language, meaning that developers do not have to manually allocate and free memory, reducing the risk of these errors.

Lua ensures that these memory issues are handled more safely, reducing the time spent debugging hard-to-trace memory faults that are typical in C-based embedded systems.

Ease of Learning and Collaboration

The barrier to entry for developers learning C is higher due to its complexity and low-level nature. On the other hand, Lua is easy to learn and has a very readable syntax, making it accessible to a wider range of developers, including those with less experience in embedded systems, including cloud, IoT, and web developers.

Lua: as readable as pseudocode, but actually runs.

This ease of learning promotes collaboration in development teams. In environments where not everyone is a highly specialized embedded engineer, Lua allows less experienced embedded C developers to contribute effectively, thus enhancing team productivity. Lua's intuitive design makes it easier to onboard new developers, whereas C's steep learning curve can be a bottleneck.

Modularity and Maintainability

Lua frameworks such as Xedge support modular development, meaning developers can easily break down applications into smaller, more manageable components. Lua's lightweight design complements this, making it easier to manage large codebases and implement features incrementally.

Conversely, C often requires more upfront architecture planning and suffers from complex dependencies that can make long-term maintenance difficult, particularly when the project scales or changes over time.

Flexibility and Integration

Lua's flexibility is one of its standout features. For example, with Xedge, you can easily integrate Lua scripts into existing C-based systems, algorithms, and libraries when needed. This means developers can use C for the performance-critical parts of the application while using Lua for higher-level logic, configuration, or UI development.

Lua Binding

Figure 1: Lua can call C Code, and C code can call Lua via Lua bindings.

This hybrid approach gives you the best of both worlds - leveraging C's performance where needed while enjoying Lua's flexibility and simplicity for rapid feature development. Xedge's tools facilitate this integration seamlessly, helping to maintain performance while boosting development efficiency.

Performance vs. Development Overhead

C indeed offers the highest level of control and performance, which is why it remains essential in scenarios requiring tight resource management or real-time processing. However, the overhead of writing and maintaining high-level business logic using C code can quickly outpace its benefits for non-performance-critical tasks. While slower than C, Lua offers more than adequate performance for a vast array of embedded applications.

With Xedge, developers gain access to optimizations that make Lua-based solutions fast enough for many IoT and embedded applications, particularly where the flexibility of rapid iteration is more important than squeezing out every ounce of CPU performance.

Python vs. Lua

If you’re a Python programmer or considering Python for embedded systems, be sure to explore the tutorial Python vs. Lua. In short, while Python is a versatile language, it is not an embeddable C code library like Lua. This means you miss out on Lua's seamless and efficient integration with C, which is a significant advantage for many embedded applications.

If you're familiar with MicroPython or CircuitPython, take a moment to read MicroPython vs. Lua for Professional Development. The article explains the hyperloop issues that often surface in these Python environments.

A Python programmer can easily pick up Lua in just a few hours, thanks to their shared simplicity and similar features like dynamic typing, first-class functions, and clean syntax.

How Do I Integrate My C (or C++) Components With Lua?

By creating Lua bindings, you expose your C functions to Lua so they can be called like regular Lua functions. This lets you keep performance-critical code in C while using Lua for high-level logic.

You can create bindings in two ways:

  • Manually, using the Lua C API for full control.
  • Automatically, using tools like Real Time Logic’s binding generator to speed up integration.

Once registered, your C functions are accessible from Lua scripts, enabling a clean separation between low-level implementation and high-level behavior. See the Lua Binding Tutorial for details.

How to Embed Lua Code in Firmware

A practical way to embed Lua code in monolithic firmware is to package all Lua source files into a ZIP archive, convert it into a C array, and link it into the firmware at compile time. This ZIP archive acts as a Read-Only Memory File System (ROMFS), enabling access to individual files without requiring a traditional file system. At runtime, a ZIP file driver can extract the files on demand, allowing the Lua engine to compile and execute scripts as needed. This setup enables seamless interaction between C and Lua code, making it easy to build hybrid applications in tightly constrained environments.

Embedding Lua in Firmware

Figure 2: An ideal approach to embedding Lua in a monolithic firmware build.

Example: The Barracuda App Server's I/O interface features a built-in ZIP file driver.

Figure 2 illustrates how Lua can be embedded in a monolithic firmware build where the code executes directly from flash memory. In a high-level operating system (HLOS) environment, where the executable is loaded into RAM, it’s usually better to keep the ZIP file external rather than linking it into the binary. This approach reduces RAM usage and makes updates simpler. See the illustration on the Barracuda App Server download page for a clear comparison between RTOS and HLOS usage.

What Does "Business Logic" Mean in an Embedded System?

In traditional software, business logic usually refers to the rules that drive decision-making and behavior, and it's no different in embedded systems. Think of it like this: C gives you raw power and low-level access, while Lua lets you define what your device should actually do with that power.

Here's a few examples:

  • Networking logic - like when and how to connect to a server, retry strategies, failover handling, or choosing between Wi-Fi and Ethernet.
  • Web server behavior - handling REST API requests, managing authentication tokens, and serving dynamic web content.
  • IoT message processing - parsing MQTT messages, reacting to incoming topics, deciding when to publish telemetry or alerts.
  • Device orchestration - controlling when sensors collect data, how often it's sent, and what to do if something goes wrong.
  • Configuration handling - reading, validating, and applying settings without rebooting or reflashing firmware.

These are areas where flexibility, maintainability, and fast iteration matter more than raw speed. And that's where Lua shines.

But What About Performance?

Lua is often underestimated in this space, but it's more than fast enough for real-world IoT workloads. For example, consider our MQTT speed test: we sent and received 2,000 messages in under three seconds on a low-end ESP32 microcontroller, which was handled by a Lua-implemented MQTT stack. That's not just fast; that's production-grade performance on a budget-friendly chip.

Conclusion

For embedded systems, especially in the IoT space, balancing performance with development efficiency is crucial. While C will always have its place in high-performance or resource-constrained environments, Xedge and Lua combined with C code provide a faster, more flexible approach to building complex systems without sacrificing too much performance.

By leveraging Lua's simplicity in frameworks, developers can accelerate development cycles, reduce bugs, and create systems that are easier to maintain and scale, leading to faster time-to-market and a better end-user experience. Xedge with Lua is an excellent alternative to the traditional C development model if your embedded project demands agility, faster prototyping, and scalability.

The Lua Culture

With Lua, it's natural to write performance-critical code in C and use Lua for high-level logic. That kind of seamless C integration is part of the Lua culture. In contrast, Python and other high-level language projects rarely take this approach; C extensions exist, but they're more of a workaround than a core design philosophy.

As you have learned in this tutorial, using Lua standalone is only somewhat useful. However, when integrated into a framework, Lua makes using that framework super easy, enabling rapid development. These frameworks provide very different Lua APIs, as they are designed for various purposes.

Here's a list of some frameworks and platforms that use Lua, showcasing its versatility across different domains:

  • Awesome WM - A tiling window manager for X11 that uses Lua for extensive customization and scripting.
  • Corona SDK (Solar2D) - A cross-platform framework for creating games and apps with an emphasis on mobile platforms.
  • Defold - A game engine for creating lightweight 2D games with built-in Lua scripting support.
  • LÖVE - A 2D game development framework that's simple and beginner-friendly.
  • Mako Server - An embedded web server framework ideal for building secure, lightweight applications in Lua.
  • OpenResty - A web platform built on Nginx, designed for server-side Lua scripting.
  • Prosody - A lightweight XMPP server framework for building messaging applications with Lua.
  • Redis (via Lua scripting) - Redis integrates Lua for scripting complex operations within the database.
  • Roblox - A platform and game engine where Lua powers the scripting for game logic and interactivity.
  • World of Warcraft - Lua is used extensively to create and customize in-game add-ons.
  • Xedge - Embedded device framework. See details below.

By using Lua in these frameworks, developers can fast-track the development of complex systems while enjoying the simplicity and speed of scripting in Lua.

Now, imagine these frameworks instead offered only a C or C++ API - learning and using those APIs would take significantly longer.

C++ vs. Lua Comparison Example:

For a practical illustration, consider our C++ WebSocket example on GitHub. The C++ version spans over 200 lines of code, requiring careful management of threads and synchronization. In contrast, the Lua equivalent achieves the same functionality in just 26 lines, thanks to our Cosocket technology, which eliminates the need for complex thread handling. You can review the code yourself; the C++ example includes the Lua version as a comment at the top of the file for easy comparison.

When Lua Might Not Be the Right Fit

While Lua works great for many embedded systems, it's important to note that it's not well-suited for ultra-low-resource microcontrollers that only have internal SRAM. Lua shines best on systems equipped with external RAM or SoCs like the ESP32-S3, which offer enough memory to run the Lua interpreter and Lua scripts comfortably. If you're targeting devices with limited RAM, a C-only approach is likely the better choice.

Lua excels in network and connectivity applications, and many legacy systems that could benefit from Lua when being IoT-enabled should consider offloading Lua execution to a dedicated co-processor.

Lua vs. Embedded Beginner Frameworks

If you're just starting with embedded systems, you may be comparing Lua to beginner-friendly options like Arduino or MicroPython. To help you understand where Lua fits and why it can be a smarter long-term choice, you may find these two articles valuable:

Both explain how Lua, paired with C, can simplify development while giving you more flexibility as your projects grow.

Getting Started with Lua for Embedded Systems Using Xedge

When working with embedded systems, it’s possible to build your own integration using stock Lua, but this often involves significant effort, including porting Lua to your environment and writing a comprehensive set of Lua bindings to interface with your platform. This approach is time-consuming and complex, detracting from the core development focus, especially when IoT and web APIs are required.

The Xedge IoT and Embedded Web Framework is your key to overcoming these challenges. It provides a ready-made, Lua-powered framework built on the Barracuda App Server (BAS). Designed to simplify development and deployment for embedded systems, Xedge is a powerful tool that combines flexibility and versatility. It supports monolithic RTOS devices and functions as a Mako Server plugin for high-level operating system (HLOS) environments such as embedded Linux. Whether you're targeting monolithic firmware or operating systems like embedded Linux, Xedge streamlines the process, enabling developers to focus on delivering efficient, robust solutions that can stand up to the demands of diverse embedded platforms. For a comparison with stock Lua, explore the extensive Barracuda App Server APIs.

Video: How to use the Xedge Lua IDE

Ready to Run Xedge for the ESP32 microcontroller

For RTOS applications, the simplest way to get started with Xedge is by using our pre-built binary for the ESP32 processor, known as Xedge32. Xedge32 is built with a rich South Bridge API, allowing you to directly access and control ESP32’s GPIO (General Purpose Input/Output) through Lua. This hands-on capability lets you interface with hardware in real-time using straightforward Lua commands, making it easy to manage the ESP32’s physical components.


Follow the discussion in Real-Time Embedded Engineering

Do you agree with this article’s view on blending scripting flexibility with the stability of traditional C code? Join the conversation and share your thoughts over at Real-Time Embedded Engineering.

C business logic

References:


Additional Lua Tutorials


External Lua Articles

Why Every Programmer Should Learn Lua

The article Why Every Programmer Should Learn Lua explores Lua's key advantages, including its simplicity, portability, and powerful extensibility, which make it an excellent choice for both beginners and experienced developers.

Lua is so underrated

As explained in the article Lua is so underrated, Lua is often overlooked in discussions about powerful and efficient programming languages, but it has quietly been powering some of the most innovative projects and systems out there. This article dives into why Lua is so underrated, exploring its unique features, flexibility, and real-world applications that make it a hidden gem for developers.

Lua, a misunderstood language

As explained in the article Lua, a misunderstood language, Lua prioritizes mechanisms over policies, empowering developers to tailor it to their needs instead of rigid standards. It is the fastest non-JIT high-level language, primarily designed for embedding within applications to offer scripting capabilities. This makes Lua an excellent choice for embedding and integrating with software like our Barracuda App Server, which extends the Lua APIs for IoT and the web.

Posted in Whitepapers