ESP32 uses either the RISC-V or Xtensa chips. I have the ESP32-DevKitC-VE which uses the Xtensa toolchain, so I will use that as the example here.
Setting up clangd
Install
clangd
from your package manager of choice, and you
should be able to find it in $PATH
. lsp-mode
can also download and install a copy under
$HOME/.emacs.d/cache/
but I generally prefer the system
package manager.
clangd
requires a compiler driver with appropriate
flags. Instead of specifying all headers and flags manually, it can use
a compiler
command database which can be generated with cmake.
To do this with idf.py
, simply add
CMAKE_EXPORT_COMPILE_COMMANDS=1
to your environment
variables:
$ CMAKE_EXPORT_COMPILE_COMMANDS=1 idf.py build
compile_commands.json
is expected to be in any of the
parent directories of the file you're editing. I put it in my project
root directory as a symbolic link, as the one generated by cmake above
is put under build/
. In project root, run:
$ ln -s build/compile_commands.json compile_commands.json
As Xtensa uses gcc
where some options may differ from
clang
, I have added a .clangd
file under
project root which is checked into vcs with the appropriate options.
CompileFlags:
Remove: [-mlongcalls, -fstrict-volatile-bitfields, -fno-tree-switch-conversion]
The list of options was taken from my first run of lsp where
flycheck
was complaining of unknown options.
Setting up lsp-mode
To setup Emacs to use lsp-mode
and its repertoire of helpers including company-mode
and
flycheck-mode
,
we need to install lsp-mode
. I use use-package
here.
To have lsp-mode
use the system clangd
with
appropriate configurations, I have added lsp-clients-clangd-executable
and lsp-clients-clangd-args
.
Since I have setup ESP32 toolchain
outside of default path, clangd
will not use it as a query
driver to find system headers. Without the
--query-driver
argument, clangd
will first
find the appropriate compiler from the compile command database, but
will refuse it as it's not in default path. This is a security measure
to prevent running arbitrary executables and the user is expected to
"whitelist" the compiler driver to be used by clangd
. The
driver can be any compiler compatible with gcc
or
clangd
. In this case, Xtensa uses the gcc
compiler.
Here's the Emacs lisp snippet to set up both lsp-mode
and lsp-ui
(optional).
use-package lsp-mode
(
:commands (lsp lsp-deferred)
:init; to get lsp-mode going with xtensa
setq lsp-clients-clangd-executable "clangd")
(setq lsp-clients-clangd-args '("--query-driver=/**/bin/xtensa-esp32-elf-*" "--background-index" "--header-insertion=iwyu" "-j=4" ))
(
:hook
(c-mode . lsp)
(lsp-mode . lsp-enable-which-key-integration))
use-package lsp-ui
( :commands lsp-ui-mode)
Setup projectile
for
running
You can also use projectile
's lifecycle
commands to build and run the project with idf.py
using
a per-directory
variable file. To do this, add a file named
.dir-locals.el
to project root with the following
contents.
nil . ((projectile-project-compilation-cmd . "idf.py build")
(("idf.py flash monitor")))) (projectile-project-run-cmd .
And that's pretty much it. To debug any issues that may arise, add
"--log=verbose"
to lsp-client-clangd-args
and
have a look at clangd
buffers in Emacs.
This setup can theoretically be adapted for use with any
cross-compiler toolchain using either clang
or
gcc
as its compiler.