拿到 Grove 接口的加速度计,我们先把加速度计调通,使用 Zephyr RTOS 作为运行时,采集加速度三轴数据,然后输出到 OLED 屏幕上.
效果预览

准备工作
操作系统使用 Zephyr RTOS v4.3.1,开发环境用 podman compose 声明,选用了 Zephyr SDK v0.17.4,对应镜像版本 v0.28,配置文件如下.
x-podman: in_pod: false services: zephyr: image: ghcr.io/zephyrproject-rtos/zephyr-build:v0.28-branch hostname: zephyr container_name: zephyr tty: true volumes: - $HOME/projects:/apps:z - /dev/ttyACM0:/dev/ttyACM0 - /dev/bus:/dev/bus user: 1000:1000 userns_mode: keep-id:uid=1000,gid=1000 tmpfs: - /tmp:size=1G working_dir: /apps networks: default: enable_ipv6: true
除了开箱帖中从 DigiKey 采购的物料外,额外准备了一个 SSD1306 主控的 I2C 模式的 OLED 屏幕. 自己画了一个扩展板接入 OLED、加速度计以及蜂鸣器模块,OLED、加速度计接入 NUCLEO 板子上面的 Arduino UNO 连接器的 I2C 总线,蜂鸣器接入 GPIO.


设备树编写
Zephyr 编写时一般偏好使用设备树进行声明式地描述外设功能,查阅 NUCLEO-H533RE 的 user manual 理清 I/O 关系,Arduino UNO 连接器上的 SCL SDA 分别对应 MCU 的 PB6 和 PB7.

可以根据外设和 I/O 关系编写下面的 Zephyr 设备树描述,包含了 OLED、LIS3DH 加速度计和蜂鸣器.
/ {
aliases {
buzzer = &bz;
accel = &lis3dh;
oled = &ssd1306;
};
buzzers {
compatible = "gpio-leds";
bz: bz {
gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>;
label = "Buzzer";
};
};
};
&i2c1 {
pinctrl-0 = <&i2c1_scl_pb6 &i2c1_sda_pb7>;
pinctrl-names = "default";
clock-frequency = <DT_FREQ_K(400)>;
status = "okay";
lis3dh: lis3dh@19 {
compatible = "st,lis3dh", "st,lis2dh";
reg = <0x19>;
status = "okay";
};
ssd1306: ssd1306@3c {
compatible = "solomon,ssd1306fb";
reg = <0x3c>;
status = "okay";
width = <128>;
height = <64>;
segment-offset = <0>;
page-offset = <0>;
display-offset = <0>;
multiplex-ratio = <63>;
prechargep = <0xf1>;
};
}初始化代码编写
我们使用 Zephyr 的 GPIO API 对要用到的通用引脚初始化,通过设备抽象可以检测设备的就绪状态,初始化过程中除了检测设备就绪外,由于涉及显示,预先对屏幕使用 Zephyr 的 Display 和 CFB API 进行清屏,否则上电后 OLED 的显存是不可预测的状态,会显示雪花状随机图样.
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/display.h>
#include <zephyr/display/cfb.h>
struct sensor_accel {
struct sensor_value xyz[3];
};
struct app {
const struct device *accel;
const struct device *oled;
struct gpio_dt_spec buzzer;
};
static int app_init(struct app *app);
static int app_fetch_accel(struct app *app, struct sensor_accel *accel);
static int app_update_oled(struct app *app, struct sensor_accel *accel);
static struct app app_main = {
.accel = DEVICE_DT_GET(DT_ALIAS(accel)),
.oled = DEVICE_DT_GET(DT_ALIAS(oled)),
.buzzer = GPIO_DT_SPEC_GET(DT_ALIAS(buzzer), gpios),
};
static int app_init(struct app *app)
{
int ret;
if (!gpio_is_ready_dt(&app->buzzer)) {
printk("Buzzer GPIO is not ready.\n");
return -1;
}
ret = gpio_pin_configure_dt(&app->buzzer, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
printk("Cannot configure buzzer GPIO: %d.\n", ret);
return ret;
}
if (!device_is_ready(app->accel)) {
printk("Accelerometer is not ready.\n");
return -1;
}
if (!device_is_ready(app->oled)) {
printk("OLED is not ready.\n");
return -1;
}
ret = display_set_pixel_format(app->oled, PIXEL_FORMAT_MONO10);
if (ret < 0) {
ret = display_set_pixel_format(app->oled, PIXEL_FORMAT_MONO01);
if (ret < 0) {
printf("Cannot set required pixel format: %d.\n", ret);
return ret;
}
}
ret = display_blanking_off(app->oled);
if (ret < 0) {
printf("Cannot turn on OLED: %d.\n", ret);
return ret;
}
ret = cfb_framebuffer_init(app->oled);
if (ret < 0) {
printf("Cannot initialize framebuffer: %d.\n", ret);
return ret;
}
ret = cfb_framebuffer_set_font(app->oled, 0);
ret = cfb_framebuffer_invert(app->oled);
ret = cfb_framebuffer_clear(app->oled, true);
return 0;
}主程序
主循环中,调用 Sensor API 获取加速度计的三轴数据,然后格式化成三位小数精度的字符串,由 CFB API 绘制在 OLED 屏幕上,周期为 1s.
int main(void)
{
int ret;
ret = app_init(&app_main);
if (ret < 0) {
printk("App init error: %d.\n", ret);
return -1;
}
while (1) {
struct sensor_accel accel;
ret = app_fetch_accel(&app_main, &accel);
if (ret < 0) {
printk("Fetch accel error: %d.\n", ret);
continue;
}
printf("a: %d, %d, %d\n", accel.xyz[0].val1, accel.xyz[1].val1, accel.xyz[2].val1);
app_update_oled(&app_main, &accel);
k_sleep(K_MSEC(1000));
}
return 0;
}
static int app_fetch_accel(struct app *app, struct sensor_accel *accel)
{
int ret;
ret = sensor_sample_fetch(app->accel);
if (ret < 0) {
return ret;
}
ret = sensor_channel_get(app->accel, SENSOR_CHAN_ACCEL_XYZ, accel->xyz);
if (ret < 0) {
return ret;
}
return 0;
}
static inline int float_round_to_milli(float f)
{
return (f * 10000 + 5) / 10;
}
static inline char *oled_format_accel(char *buf, size_t n, const char *name, float f)
{
int milli;
int frac;
milli = float_round_to_milli(f);
frac = (milli % 1000 + 1000) % 1000;
snprintf(buf, n, "%s: %d.%03d", name, milli / 1000, frac);
return buf;
}
static int app_update_oled(struct app *app, struct sensor_accel *accel)
{
int ret;
float ax;
float ay;
float az;
#define OLED_ACCEL_BUF 32
char buf[OLED_ACCEL_BUF];
ax = sensor_value_to_float(&accel->xyz[0]);
ay = sensor_value_to_float(&accel->xyz[1]);
az = sensor_value_to_float(&accel->xyz[2]);
ret = cfb_framebuffer_clear(app->oled, false);
oled_format_accel(buf, OLED_ACCEL_BUF, "ax", ax);
ret = cfb_print(app->oled, buf, 0, 0);
oled_format_accel(buf, OLED_ACCEL_BUF, "ay", ay);
ret = cfb_print(app->oled, buf, 0, 24);
oled_format_accel(buf, OLED_ACCEL_BUF, "az", az);
ret = cfb_print(app->oled, buf, 0, 48);
ret = cfb_framebuffer_finalize(app->oled);
return ret;
}烧录程序
使用 Zephyr 的 west 构建工具烧录程序时,对于 ST 或者 NXP 这类厂商,往往优先使用官方的私有编程器软件即 STM32CubeProgrammer,可到官网下载,当然 OpenOCD 其实也是可以的.

我要赚赏金
