这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【Let'sdo2026第3期】加速度计数据显示

共1条 1/1 1 跳转至页

【Let'sdo2026第3期】加速度计数据显示

菜鸟
2026-10-19 13:23:05     打赏

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

效果预览

oled_1792386966602_0.gif

准备工作

操作系统使用 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.

image_1792385531242_0.png

image_1792384789633_0.png

设备树编写

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

image_1792384337218_0.png

可以根据外设和 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 其实也是可以的.

image_1792385402640_0.png




共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]