mirror of
https://github.com/whowechina/aic_pico.git
synced 2026-09-22 22:48:04 +03:00
Accelerometer support
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -236,20 +236,21 @@ You may use some instant adhesive to fix stickers.
|
||||
<img src="doc/pico_assemble_4.jpg" width="40%">
|
||||
|
||||
## Building AIC Touch
|
||||
AIC Touch, another variant of AIC Pico, comes with an integrated touchscreen. However, its tiny FPC/FFC connector significantly increases the soldering and assembly difficulty.
|
||||
AIC Touch, another variant of AIC Pico, comes with an integrated touchscreen and an optional accelerometer. However, its tiny FPC/FFC connector significantly increases the soldering and assembly difficulty.
|
||||
|
||||
### Components
|
||||
* For Pi Pico, NFC module and 6x WS2812B 1204 LEDS, follow the guide for AIC Key. Pico 2 is highly recommened for smoother GUI.
|
||||
* C1, C2, C3, 0603 0.1uF capacitors, they contribute to a more stable power supply.
|
||||
* R1, R2, pull-up resistors for the I2C bus, 0603 1Kohm ~ 4.7Kohm.
|
||||
* 1.69 inch 240x280 LCD touchscreen (ST7789 + CST816). Several vendors are making this model, as long as one has the exact same looking, it will be fine. It looks like this.
|
||||
|
||||
* 1.69 inch 240x280 LCD touchscreen (ST7789 + CST816). Several vendors are making this model, as long as one has the exact same looking, it will be fine. It looks like this.
|
||||
<img src="doc/touchscreen.jpg" width="30%">
|
||||
|
||||
* The FPC/FFC flip cover connector for the touchscreen. 18P, 0.5mm Pitch, 1.0H (1mm height), front flip (flip cover at cable side), bottom contact. It looks like this:
|
||||
|
||||
* The FPC/FFC flip cover connector for the touchscreen. 18P, 0.5mm Pitch, 1.0H (1mm height), front flip (flip cover at cable side), bottom contact. It looks like this:
|
||||
<img src="doc/fpc_socket.jpg" width="30%">
|
||||
|
||||
* Optional LIS3DH accelerometer module. It makes the AIC Touch capable of detecting gravity direction. The shape of the module is not important, as long as it can interface with I2C and fit into the case.
|
||||
<img src="doc/lis3dh.jpg" width="30%">
|
||||
|
||||
* PCB file "aic_touch_v*.zip".
|
||||
<img src="doc/pcbs.jpg" width="60%">
|
||||
|
||||
@@ -263,7 +264,7 @@ AIC Touch, another variant of AIC Pico, comes with an integrated touchscreen. Ho
|
||||
|
||||
Top parts should be printed upside-down.
|
||||
|
||||
To achieve the best aesthetic results, I recommend using the combination of clear PLA and black PLA. For the top part, make the first 2 layers of the sub-object "Top" black, and all others clear.
|
||||
To achieve the best aesthetic results, I recommend using the combination of clear PLA and black PLA. For the top part, make the first 2 layers of the sub-object "Top" black, and all others clear.
|
||||
<img src="doc/touch_3d_print.jpg" width="40%">
|
||||
|
||||
### Assembly
|
||||
@@ -285,11 +286,15 @@ For most of the part, just follow the guide of AIC key.
|
||||
* When soldering main pins, only apply a very small amount of solder to the iron tip.
|
||||
* If you apply too much solder, a solder wick can be used effectively to remove the excess.
|
||||
|
||||
* Remember to put 1mm-thick soft pads to support the touchscreen. You can use 1mm 3M VHB double-sided tape, but do NOT remove the red film liner (don't let the tape stick to the touchscreen).
|
||||
* Put some thin insulating tape on the back of the touchscreen.
|
||||
|
||||
* Apply 1mm-thick soft pads to support the touchscreen. You can use 1mm 3M VHB double-sided tape, but do NOT remove the red film liner (don't let the tape stick to the touchscreen).
|
||||
<img src="doc/touch_assemble_1.jpg" width="30%">
|
||||
<img src="doc/touch_assemble_2.jpg" width="30%">
|
||||
|
||||
* The VCC, GND, SCL and SDA of the accelerometer are shared with the PN532's corresponding pins. You need some thin wires to connect them. On the module, pull up the SD0 and CS pins (i.e., connect them to VCC) with short thin wires. The sensor can be placed at the bottom of AIC Touch, between the PCB and the NFC module, with the chip marking facing the front, and oriented in the same direction as the AIC Touch logo. Use double-sided tape to fix it in place and ensure proper insulation. In the picture, I cut off its ears to fit it into the tight space.
|
||||
<img src="doc/lis3dh_assemble.jpg" width="30%">
|
||||
|
||||
* Done!
|
||||
|
||||
<img src="doc/touch_assemble_3.jpg" width="30%">
|
||||
|
||||
+12
-9
@@ -237,20 +237,21 @@ AIC Key 是 AIC Pico 的一个变种 - 集成了一个小键盘。比 "AIC Pico"
|
||||
<img src="doc/pico_assemble_4.jpg" width="40%">
|
||||
|
||||
## 制作 AIC Touch
|
||||
AIC Touch 是 AIC Pico 的另一种版本,配备了触摸屏。不过它微小的 FPC/FFC 连接器大大增加了焊接和组装的难度。
|
||||
AIC Touch 是 AIC Pico 的另一种版本,配备了触摸屏。还可以选装一个加速度传感器。不过它微小的 FPC/FFC 连接器大大增加了焊接和组装的难度。
|
||||
|
||||
### 组件
|
||||
* 对于 Pi Pico、NFC 模块、6x WS2812B 1204 LED,请参考 AIC Key 的指南。强烈推荐使用 Pico 2 来获得更流畅的 GUI。
|
||||
* C1、C2、C3,0603 0.1uF 电容,它们有助于更稳定的电源供应。
|
||||
* R1、R2,I2C 总线的上拉电阻,0603 1Kohm ~ 4.7Kohm。
|
||||
* 1.69 英寸 240x280 LCD 触摸屏(ST7789 + CST816)。有几个供应商在销售这个型号,只要看起来完全相同,就应该可以。它看上去长这个样子。
|
||||
|
||||
* 1.69 英寸 240x280 LCD 触摸屏(ST7789 + CST816)。有几个供应商在销售这个型号,只要看起来完全相同,就应该可以。它看上去长这个样子。
|
||||
<img src="doc/touchscreen.jpg" width="30%">
|
||||
|
||||
* 触摸屏的 FPC/FFC 翻盖连接器。18P,0.5mm 间距,1.0H(1mm 高度),前翻盖下接。它看起来像这样:
|
||||
|
||||
* 触摸屏的 FPC/FFC 翻盖连接器。18P,0.5mm 间距,1.0H(1mm 高度),前翻盖下接。它看起来像这样:
|
||||
<img src="doc/fpc_socket.jpg" width="30%">
|
||||
|
||||
* LIS3DH 加速度传感器模块(可选)。它可以使 AIC Touch 具备重力方向感应能力。模块的形状不重要,只要它能接 I2C 接口并且能塞进壳子里就行了。
|
||||
<img src="doc/lis3dh.jpg" width="30%">
|
||||
|
||||
* PCB 文件 "aic_touch_v*.zip"。
|
||||
<img src="doc/pcbs.jpg" width="60%">
|
||||
|
||||
@@ -264,7 +265,7 @@ AIC Touch 是 AIC Pico 的另一种版本,配备了触摸屏。不过它微小
|
||||
|
||||
顶部部分需要上下颠倒打印。
|
||||
|
||||
为了获得最佳的外观效果,我建议使用透明 PLA 和黑色 PLA 的组合。其中顶部除了子对象“Top”的最开始两层用黑色外,其他都用透明。
|
||||
为了获得最佳的外观效果,我建议使用透明 PLA 和黑色 PLA 的组合。其中顶部除了子对象“Top”的最开始两层用黑色外,其他都用透明。
|
||||
<img src="doc/touch_3d_print.jpg" width="40%">
|
||||
|
||||
### 组装
|
||||
@@ -285,13 +286,15 @@ AIC Touch 是 AIC Pico 的另一种版本,配备了触摸屏。不过它微小
|
||||
* 在焊接主引脚时,只需要在烙铁尖上附着极少量的焊锡。
|
||||
* 如果你用了过多的焊锡,可以用吸锡带去除多余的焊锡。
|
||||
|
||||
* 记得贴一层 1mm 厚度的软垫以支撑触摸屏。你可以用 1mm 厚度的 3M VHB 双面胶带,但是不要移除红色的保护层(绝对不要让胶带粘在触摸屏上)。
|
||||
|
||||
* 触摸屏背后需要粘一层用于绝缘的薄的胶带。
|
||||
* 贴一层 1mm 厚度的软垫以支撑触摸屏。你可以用 1mm 厚度的 3M VHB 双面胶带,但是不要移除红色的保护层(绝对不要让胶带粘在触摸屏上)。
|
||||
<img src="doc/touch_assemble_1.jpg" width="30%">
|
||||
<img src="doc/touch_assemble_2.jpg" width="30%">
|
||||
|
||||
* 搞定!
|
||||
* 加速度传感器的 VCC, GND, SCL 和 SDA 和 PN532 的四个引脚共用,你需要一些细软的电线来连接它们。模块上,请用短电线把 SD0 和 CS 引脚拉高(即连接到 VCC)。传感器可以放 AIC Touch 底部,PCB 和 NFC 模块之间,芯片印字面向 AIC Touch 正面,方向和 AIC Touch Logo 一致。用双面胶固定,同时做好绝缘。图片里我剪掉了它的耳朵让它能适应狭小空间。
|
||||
<img src="doc/lis3dh_assemble.jpg" width="30%">
|
||||
|
||||
* 搞定!
|
||||
<img src="doc/touch_assemble_3.jpg" width="30%">
|
||||
|
||||
## 固件
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 244 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
@@ -17,7 +17,7 @@ function(make_firmware board board_def)
|
||||
|
||||
add_executable(${board}
|
||||
main.c save.c config.c commands.c light.c keypad.c cardio.c
|
||||
cst816t.c st7789.c gui.c gfx.c rle.c
|
||||
cst816t.c st7789.c lis3dh.c gui.c gfx.c rle.c
|
||||
cli.c usb_descriptors.c)
|
||||
|
||||
target_compile_definitions(${board} PUBLIC ${board_def}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "keypad.h"
|
||||
#include "light.h"
|
||||
#include "lis3dh.h"
|
||||
|
||||
#include "aime.h"
|
||||
#include "bana.h"
|
||||
@@ -43,7 +44,14 @@ static void display_sys()
|
||||
{
|
||||
int mhz = clock_get_hz(clk_sys) / 1000000;
|
||||
printf("[System]\n");
|
||||
|
||||
const char *cpu = "RP2040";
|
||||
#ifdef PICO_RP2350
|
||||
cpu = "RP2350";
|
||||
#endif
|
||||
printf(" Processor: %s\n", cpu);
|
||||
printf(" Clock: %d MHz\n", mhz);
|
||||
printf(" LIS3DH: %s\n", lis3dh_is_present() ? "Present" : "N/A");
|
||||
}
|
||||
|
||||
static void display_nfc()
|
||||
|
||||
+273
-2
@@ -29,6 +29,8 @@
|
||||
#include "gfx.h"
|
||||
#include "gui.h"
|
||||
|
||||
#include "lis3dh.h"
|
||||
|
||||
void gui_init()
|
||||
{
|
||||
cst816t_init_i2c(i2c1, 3, 2);
|
||||
@@ -318,7 +320,213 @@ static void gen_pallete(uint16_t pallete[16], uint32_t color)
|
||||
}
|
||||
}
|
||||
|
||||
static void run_background()
|
||||
static const int8_t sin_table[256] = {
|
||||
0, 3, 6, 9, 12, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46,
|
||||
48, 51, 54, 57, 60, 62, 65, 68, 70, 73, 75, 78, 80, 83, 85, 87,
|
||||
90, 92, 94, 96, 98,100,102,104,106,107,109,111,112,114,115,117,
|
||||
118,119,121,122,123,124,125,126,126,127,127,127,127,127,127,127,
|
||||
127,127,127,127,127,127,126,126,125,124,123,122,121,119,118,117,
|
||||
115,114,112,111,109,107,106,104,102,100, 98, 96, 94, 92, 90, 87,
|
||||
85, 83, 80, 78, 75, 73, 70, 68, 65, 62, 60, 57, 54, 51, 48, 46,
|
||||
43, 40, 37, 34, 31, 28, 25, 22, 19, 16, 12, 9, 6, 3, 0, -3,
|
||||
-6, -9,-12,-16,-19,-22,-25,-28,-31,-34,-37,-40,-43,-46,-48,-51,
|
||||
-54,-57,-60,-62,-65,-68,-70,-73,-75,-78,-80,-83,-85,-87,-90,-92,
|
||||
-94,-96,-98,-100,-102,-104,-106,-107,-109,-111,-112,-114,-115,-117,
|
||||
-118,-119,-121,-122,-123,-124,-125,-126,-126,-127,-127,-127,-127,-127,
|
||||
-127,-127,-127,-127,-127,-127,-127,-126,-126,-125,-124,-123,-122,-121,
|
||||
-119,-118,-117,-115,-114,-112,-111,-109,-107,-106,-104,-102,-100,-98,
|
||||
-96,-94,-92,-90,-87,-85,-83,-80,-78,-75,-73,-70,-68,-65,-62,-60,
|
||||
-57,-54,-51,-48,-46,-43,-40,-37,-34,-31,-28,-25,-22,-19,-16,-12,
|
||||
-9, -6, -3
|
||||
};
|
||||
|
||||
#define CELL_SIZE 8
|
||||
static void run_particle(uint16_t angle)
|
||||
{
|
||||
enum {
|
||||
GRID_W = 240 / CELL_SIZE,
|
||||
GRID_H = 280 / CELL_SIZE,
|
||||
FP_BITS = 8,
|
||||
FP_ONE = 1 << FP_BITS,
|
||||
PARTICLE_COUNT = (GRID_W * GRID_H) * 75 / 100,
|
||||
GRAVITY_ACCEL = 30,
|
||||
PRESSURE_ACCEL = 20,
|
||||
JITTER_ACCEL = 6,
|
||||
MAX_SPEED = FP_ONE * 2,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int16_t vx;
|
||||
int16_t vy;
|
||||
} fluid_particle_t;
|
||||
|
||||
static bool inited = false;
|
||||
static fluid_particle_t particles[PARTICLE_COUNT];
|
||||
static uint8_t density[GRID_H][GRID_W]; // each cell stores particle count in low-res tube space
|
||||
static uint32_t rng = 0x6c8e9cf5u;
|
||||
static int last_angle = 0;
|
||||
|
||||
const int tube_x = (st7789_get_crop_width() - GRID_W * CELL_SIZE) / 2;
|
||||
const int tube_y = (st7789_get_crop_height() - GRID_H * CELL_SIZE) / 2;
|
||||
const int max_x = (GRID_W - 1) * FP_ONE;
|
||||
const int max_y = (GRID_H - 1) * FP_ONE;
|
||||
|
||||
uint8_t hue = time_us_32() / 100000 + 127;
|
||||
uint16_t bg_color = 0;
|
||||
uint16_t water_color = st7789_rgb565(rgb32_from_hsv(hue, 255, 180));
|
||||
uint16_t foam_color = st7789_rgb565(rgb32_from_hsv(hue, 160, 160));
|
||||
|
||||
if (!inited) {
|
||||
// Start with 30% fill from the bottom to make initial shape stable.
|
||||
for (int i = 0; i < PARTICLE_COUNT; i++) {
|
||||
int cell_x = i % GRID_W;
|
||||
int cell_y = GRID_H - 1 - (i / GRID_W);
|
||||
particles[i].x = cell_x * FP_ONE + (FP_ONE / 2);
|
||||
particles[i].y = cell_y * FP_ONE + (FP_ONE / 2);
|
||||
particles[i].vx = 0;
|
||||
particles[i].vy = 0;
|
||||
}
|
||||
last_angle = angle;
|
||||
inited = true;
|
||||
}
|
||||
|
||||
memset(density, 0, sizeof(density));
|
||||
|
||||
// 8-bit trig lookup based gravity vector, angle=0 means gravity points down.
|
||||
int angle_norm = angle % 360;
|
||||
if (angle_norm < 0) {
|
||||
angle_norm += 360;
|
||||
}
|
||||
int phase = (angle_norm * 256) / 360;
|
||||
int16_t gx = sin_table[phase & 0xff];
|
||||
int16_t gy = sin_table[(phase + 64) & 0xff];
|
||||
|
||||
int dangle = angle - last_angle;
|
||||
if (dangle > 180) {
|
||||
dangle -= 360;
|
||||
} else if (dangle < -180) {
|
||||
dangle += 360;
|
||||
}
|
||||
last_angle = angle;
|
||||
|
||||
// First pass: build local density field from particle positions.
|
||||
for (int i = 0; i < PARTICLE_COUNT; i++) {
|
||||
int cx = particles[i].x >> FP_BITS;
|
||||
int cy = particles[i].y >> FP_BITS;
|
||||
|
||||
if (cx < 0) cx = 0;
|
||||
if (cx >= GRID_W) cx = GRID_W - 1;
|
||||
if (cy < 0) cy = 0;
|
||||
if (cy >= GRID_H) cy = GRID_H - 1;
|
||||
|
||||
if (density[cy][cx] < 255) {
|
||||
density[cy][cx]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < PARTICLE_COUNT; i++) {
|
||||
fluid_particle_t *p = &particles[i];
|
||||
|
||||
int cx = p->x >> FP_BITS;
|
||||
int cy = p->y >> FP_BITS;
|
||||
if (cx < 0) cx = 0;
|
||||
if (cx >= GRID_W) cx = GRID_W - 1;
|
||||
if (cy < 0) cy = 0;
|
||||
if (cy >= GRID_H) cy = GRID_H - 1;
|
||||
|
||||
// Base gravity acceleration.
|
||||
p->vx += (gx * GRAVITY_ACCEL) >> 7;
|
||||
p->vy += (gy * GRAVITY_ACCEL) >> 7;
|
||||
|
||||
// Pressure-like force from neighboring density gradient.
|
||||
int left = (cx > 0) ? density[cy][cx - 1] : density[cy][cx];
|
||||
int right = (cx < GRID_W - 1) ? density[cy][cx + 1] : density[cy][cx];
|
||||
int up = (cy > 0) ? density[cy - 1][cx] : density[cy][cx];
|
||||
int down = (cy < GRID_H - 1) ? density[cy + 1][cx] : density[cy][cx];
|
||||
p->vx += (left - right) * PRESSURE_ACCEL;
|
||||
p->vy += (up - down) * PRESSURE_ACCEL;
|
||||
|
||||
// When angle changes, add a tangential impulse to make visible slosh.
|
||||
if (dangle != 0) {
|
||||
p->vx += ((-gy * dangle) >> 6);
|
||||
p->vy += ((gx * dangle) >> 6);
|
||||
}
|
||||
|
||||
// Overcrowded cells get tiny deterministic jitter to break symmetry.
|
||||
int c = density[cy][cx];
|
||||
if (c > 2) {
|
||||
rng = rng * 1664525u + 1013904223u;
|
||||
p->vx += (int16_t)((int)((rng >> 24) & 0x03) - 1) * JITTER_ACCEL;
|
||||
rng = rng * 1664525u + 1013904223u;
|
||||
p->vy += (int16_t)((int)((rng >> 24) & 0x03) - 1) * JITTER_ACCEL;
|
||||
}
|
||||
|
||||
if (p->vx > MAX_SPEED) p->vx = MAX_SPEED;
|
||||
if (p->vx < -MAX_SPEED) p->vx = -MAX_SPEED;
|
||||
if (p->vy > MAX_SPEED) p->vy = MAX_SPEED;
|
||||
if (p->vy < -MAX_SPEED) p->vy = -MAX_SPEED;
|
||||
|
||||
int nx = p->x + p->vx;
|
||||
int ny = p->y + p->vy;
|
||||
|
||||
if (nx < 0) {
|
||||
nx = 0;
|
||||
p->vx = -(p->vx * 3) / 4;
|
||||
} else if (nx > max_x) {
|
||||
nx = max_x;
|
||||
p->vx = -(p->vx * 3) / 4;
|
||||
}
|
||||
|
||||
if (ny < 0) {
|
||||
ny = 0;
|
||||
p->vy = -(p->vy * 3) / 4;
|
||||
} else if (ny > max_y) {
|
||||
ny = max_y;
|
||||
p->vy = -(p->vy * 3) / 4;
|
||||
}
|
||||
|
||||
p->x = nx;
|
||||
p->y = ny;
|
||||
|
||||
// Viscous damping to keep movement smooth and stable.
|
||||
p->vx = (p->vx * 245) >> 8;
|
||||
p->vy = (p->vy * 245) >> 8;
|
||||
}
|
||||
|
||||
memset(density, 0, sizeof(density));
|
||||
for (int i = 0; i < PARTICLE_COUNT; i++) {
|
||||
int cx = particles[i].x >> FP_BITS;
|
||||
int cy = particles[i].y >> FP_BITS;
|
||||
|
||||
if (cx < 0) cx = 0;
|
||||
if (cx >= GRID_W) cx = GRID_W - 1;
|
||||
if (cy < 0) cy = 0;
|
||||
if (cy >= GRID_H) cy = GRID_H - 1;
|
||||
|
||||
if (density[cy][cx] < 255) {
|
||||
density[cy][cx]++;
|
||||
}
|
||||
}
|
||||
|
||||
st7789_clear(bg_color, false);
|
||||
|
||||
for (int y = 0; y < GRID_H; y++) {
|
||||
for (int x = 0; x < GRID_W; x++) {
|
||||
uint8_t n = density[y][x];
|
||||
if (n == 0) {
|
||||
continue;
|
||||
}
|
||||
uint16_t color = (n > 1) ? foam_color : water_color;
|
||||
uint8_t mix = (n >= 3) ? 0xff : (n == 2 ? 0xe0 : 0xc8);
|
||||
st7789_bar(tube_x + x * CELL_SIZE, tube_y + y * CELL_SIZE,
|
||||
CELL_SIZE - 1, CELL_SIZE - 1, color, mix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bg_deepspace()
|
||||
{
|
||||
static int phase = 0;
|
||||
phase++;
|
||||
@@ -473,10 +681,73 @@ static void sliding_render()
|
||||
}
|
||||
}
|
||||
|
||||
static bool orient_up = true;
|
||||
|
||||
#define ORIENT_BODY_PHASE_DEG 270
|
||||
#define ORIENT_UP_ANGLE_DEG 0
|
||||
#define ORIENT_SWITCH_WINDOW_DEG 45
|
||||
#define ORIENT_NEAR_FLAT_XY_MIN_SQ (400 * 400)
|
||||
#define ORIENT_NEAR_FLAT_XY_Z_RATIO 9
|
||||
|
||||
static int norm_deg(int deg)
|
||||
{
|
||||
deg %= 360;
|
||||
if (deg < 0) {
|
||||
deg += 360;
|
||||
}
|
||||
return deg;
|
||||
}
|
||||
|
||||
static int angle_sep(int a, int b)
|
||||
{
|
||||
int d = abs(norm_deg(a) - norm_deg(b));
|
||||
return (d > 180) ? (360 - d) : d;
|
||||
}
|
||||
|
||||
static void update_orientation(uint16_t angle)
|
||||
{
|
||||
int x = lis3dh_read_x();
|
||||
int y = lis3dh_read_y();
|
||||
int z = lis3dh_read_z();
|
||||
|
||||
int xy_sq = x * x + y * y;
|
||||
int zz = z * z;
|
||||
|
||||
bool near_flat = (xy_sq < ORIENT_NEAR_FLAT_XY_MIN_SQ) ||
|
||||
(xy_sq * ORIENT_NEAR_FLAT_XY_Z_RATIO < zz);
|
||||
|
||||
int up_angle = norm_deg(ORIENT_UP_ANGLE_DEG);
|
||||
int down_angle = norm_deg(up_angle + 180);
|
||||
bool in_up_window = angle_sep(angle, up_angle) <= ORIENT_SWITCH_WINDOW_DEG;
|
||||
bool in_down_window = angle_sep(angle, down_angle) <= ORIENT_SWITCH_WINDOW_DEG;
|
||||
|
||||
if (near_flat) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_down_window && orient_up) {
|
||||
orient_up = false;
|
||||
} else if (in_up_window && !orient_up) {
|
||||
orient_up = true;
|
||||
}
|
||||
}
|
||||
|
||||
void gui_loop()
|
||||
{
|
||||
run_background();
|
||||
st7789_scroll(0, 0);
|
||||
if (lis3dh_is_present()) {
|
||||
uint16_t raw_angle = (uint16_t)(lis3dh_read() * 360 / 4096);
|
||||
uint16_t gravity_angle = norm_deg(raw_angle + ORIENT_BODY_PHASE_DEG);
|
||||
|
||||
st7789_invert(false);
|
||||
run_particle(gravity_angle);
|
||||
|
||||
update_orientation(gravity_angle);
|
||||
} else {
|
||||
bg_deepspace();
|
||||
}
|
||||
|
||||
st7789_invert(!orient_up);
|
||||
if (slide.sliding) {
|
||||
sliding_render();
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* LIS3DH 3-Axis Accelerometer
|
||||
* I2C Interface, AD0=High (addr 0x19)
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#include "lis3dh.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "hardware/i2c.h"
|
||||
|
||||
#define LIS3DH_ADDR 0x19
|
||||
#define LIS3DH_REG_WHO_AM_I 0x0F
|
||||
#define LIS3DH_WHO_AM_I_ID 0x33
|
||||
#define LIS3DH_REG_CTRL1 0x20
|
||||
#define LIS3DH_REG_OUT_X_L 0x28 /* X_L..Z_H at 0x28..0x2D */
|
||||
|
||||
/* Set bit7 of register address to enable address auto-increment on LIS3DH */
|
||||
#define LIS3DH_MULTI_READ(reg) ((reg) | 0x80)
|
||||
|
||||
static i2c_inst_t *lis3dh_i2c = i2c0;
|
||||
|
||||
static int lis3dh_write_reg(uint8_t reg, uint8_t val)
|
||||
{
|
||||
uint8_t buf[2] = {reg, val};
|
||||
return i2c_write_blocking_until(lis3dh_i2c, LIS3DH_ADDR, buf, 2, false,
|
||||
make_timeout_time_ms(1));
|
||||
}
|
||||
|
||||
static int lis3dh_read_bytes(uint8_t reg, uint8_t *data, int len)
|
||||
{
|
||||
if (i2c_write_blocking_until(lis3dh_i2c, LIS3DH_ADDR, ®, 1, true,
|
||||
make_timeout_time_ms(1)) != 1) {
|
||||
return -1;
|
||||
}
|
||||
return i2c_read_blocking_until(lis3dh_i2c, LIS3DH_ADDR, data, len, false,
|
||||
make_timeout_time_ms(len));
|
||||
}
|
||||
|
||||
static uint8_t chip_id = 0;
|
||||
|
||||
void lis3dh_init(i2c_inst_t *i2c_port, uint8_t odr)
|
||||
{
|
||||
lis3dh_i2c = i2c_port;
|
||||
|
||||
if ((lis3dh_read_bytes(LIS3DH_REG_WHO_AM_I, &chip_id, 1) != 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lis3dh_is_present()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* CTRL_REG1: ODR[7:4] | LPen=0 | Zen=1 | Yen=1 | Xen=1 */
|
||||
uint8_t ctrl1 = ((odr & 0x0F) << 4) | 0x07;
|
||||
lis3dh_write_reg(LIS3DH_REG_CTRL1, ctrl1);
|
||||
}
|
||||
|
||||
bool lis3dh_is_present()
|
||||
{
|
||||
return chip_id == LIS3DH_WHO_AM_I_ID;
|
||||
}
|
||||
|
||||
static struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t z;
|
||||
uint16_t angle;
|
||||
} cache;
|
||||
|
||||
uint16_t lis3dh_read()
|
||||
{
|
||||
return cache.angle;
|
||||
}
|
||||
|
||||
int16_t lis3dh_read_x()
|
||||
{
|
||||
return cache.x;
|
||||
}
|
||||
|
||||
int16_t lis3dh_read_y()
|
||||
{
|
||||
return cache.y;
|
||||
}
|
||||
|
||||
int16_t lis3dh_read_z()
|
||||
{
|
||||
return cache.z;
|
||||
}
|
||||
|
||||
void lis3dh_update()
|
||||
{
|
||||
if (!lis3dh_is_present()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read X_L..Z_H in one burst (auto-increment via bit7) */
|
||||
uint8_t buf[6];
|
||||
if (lis3dh_read_bytes(LIS3DH_MULTI_READ(LIS3DH_REG_OUT_X_L), buf, 6) != 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.x = (buf[1] << 8) | buf[0];
|
||||
cache.y = (buf[3] << 8) | buf[2];
|
||||
cache.z = (buf[5] << 8) | buf[4];
|
||||
|
||||
/* atan2 -> [-π, π], normalize to [0, 2π), map to [0..4095] */
|
||||
float angle_rad = atan2f((float)cache.y, (float)cache.x);
|
||||
if (angle_rad < 0.0f) {
|
||||
angle_rad += 2.0f * (float)M_PI;
|
||||
}
|
||||
|
||||
cache.angle = (uint16_t)(angle_rad * 4096.0f / 2.0f / (float)M_PI) & 0xFFF;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* LIS3DH 3-Axis Accelerometer
|
||||
* I2C Interface, AD0=High (addr 0x19)
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef LIS3DH_H
|
||||
#define LIS3DH_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "hardware/i2c.h"
|
||||
|
||||
/* odr: ODR field value for CTRL_REG1[7:4]
|
||||
* 0x0 = power-down, 0x1 = 1Hz, 0x2 = 10Hz, 0x3 = 25Hz,
|
||||
* 0x4 = 50Hz, 0x5 = 100Hz, 0x6 = 200Hz, 0x7 = 400Hz */
|
||||
void lis3dh_init(i2c_inst_t *i2c_port, uint8_t odr);
|
||||
bool lis3dh_is_present();
|
||||
|
||||
uint16_t lis3dh_read(); // XY gravity angle, 12-bit [0..4095] = [0..360deg)
|
||||
|
||||
int16_t lis3dh_read_x();
|
||||
int16_t lis3dh_read_y();
|
||||
int16_t lis3dh_read_z();
|
||||
|
||||
void lis3dh_update();
|
||||
|
||||
#endif
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "cardio.h"
|
||||
#include "light.h"
|
||||
#include "keypad.h"
|
||||
#include "lis3dh.h"
|
||||
#include "gui.h"
|
||||
|
||||
#define DEBUG(...) if (aic_runtime.debug) printf(__VA_ARGS__)
|
||||
@@ -281,6 +282,8 @@ static void core0_loop()
|
||||
|
||||
keypad_update();
|
||||
report_usb_hid();
|
||||
|
||||
lis3dh_update();
|
||||
|
||||
save_loop();
|
||||
cli_fps_count(0);
|
||||
@@ -364,6 +367,8 @@ void init()
|
||||
aime_virtual_aic(aic_cfg->reader.virtual_aic);
|
||||
bana_init(cdc_reader_putc);
|
||||
|
||||
lis3dh_init(i2c0, 0x4);
|
||||
|
||||
cli_init("aic_pico>", "\n << AIC Pico >>\n"
|
||||
" https://github.com/whowechina\n\n");
|
||||
|
||||
|
||||
+29
-15
@@ -43,9 +43,10 @@ static struct crop_ctx {
|
||||
} crop = { .w = WIDTH, .h = HEIGHT };
|
||||
|
||||
static struct {
|
||||
int x;
|
||||
int y;
|
||||
} scroll;
|
||||
int scroll_x;
|
||||
int scroll_y;
|
||||
bool invert;
|
||||
} trans;
|
||||
|
||||
#ifdef PICO_RP2350
|
||||
static uint16_t vram_buf[2][HEIGHT * WIDTH];
|
||||
@@ -168,6 +169,11 @@ void st7789_reset()
|
||||
update_addr();
|
||||
}
|
||||
|
||||
void st7789_invert(bool enable)
|
||||
{
|
||||
trans.invert = enable;
|
||||
}
|
||||
|
||||
void st7789_crop(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
|
||||
{
|
||||
crop.x = x;
|
||||
@@ -231,8 +237,12 @@ static void vram_dma(uint32_t offset, const void *src, bool inc, size_t pixels)
|
||||
|
||||
void static inline put_pixel(int x, int y, uint16_t color)
|
||||
{
|
||||
x += scroll.x;
|
||||
y += scroll.y;
|
||||
if (trans.invert) {
|
||||
x = crop.w - 1 - x;
|
||||
y = crop.h - 1 - y;
|
||||
}
|
||||
x += trans.scroll_x;
|
||||
y += trans.scroll_y;
|
||||
if ((x < 0) || (x >= crop.w) || (y < 0) || (y >= crop.h)) {
|
||||
return;
|
||||
}
|
||||
@@ -241,10 +251,10 @@ void static inline put_pixel(int x, int y, uint16_t color)
|
||||
|
||||
static void soft_fill(uint16_t *pattern, size_t size)
|
||||
{
|
||||
int x = scroll.x > 0 ? scroll.x : 0;
|
||||
int y = scroll.y > 0 ? scroll.y : 0;
|
||||
int w = crop.w - abs(scroll.x);
|
||||
int h = crop.h - abs(scroll.y);
|
||||
int x = trans.scroll_x > 0 ? trans.scroll_x : 0;
|
||||
int y = trans.scroll_y > 0 ? trans.scroll_y : 0;
|
||||
int w = crop.w - abs(trans.scroll_x);
|
||||
int h = crop.h - abs(trans.scroll_y);
|
||||
if ((crop.w <= 0) || (crop.h <= 0)) {
|
||||
return;
|
||||
}
|
||||
@@ -260,7 +270,7 @@ static void soft_fill(uint16_t *pattern, size_t size)
|
||||
|
||||
void st7789_clear(uint16_t color, bool raw)
|
||||
{
|
||||
if (raw || !(scroll.x || scroll.y)) {
|
||||
if (raw || !(trans.scroll_x || trans.scroll_y)) {
|
||||
uint32_t c32 = (color << 16) | color;
|
||||
vram_dma(0, &c32, false, crop.w * crop.h);
|
||||
return;
|
||||
@@ -271,7 +281,7 @@ void st7789_clear(uint16_t color, bool raw)
|
||||
|
||||
void st7789_fill(uint16_t *pattern, size_t size, bool raw)
|
||||
{
|
||||
if (raw || !(scroll.x || scroll.y)) {
|
||||
if (raw || !(trans.scroll_x || trans.scroll_y)) {
|
||||
int remain = crop.w * crop.h;
|
||||
int offset = 0;
|
||||
while (remain > 0) {
|
||||
@@ -288,8 +298,8 @@ void st7789_fill(uint16_t *pattern, size_t size, bool raw)
|
||||
|
||||
void st7789_scroll(int dx, int dy)
|
||||
{
|
||||
scroll.x = dx;
|
||||
scroll.y = dy;
|
||||
trans.scroll_x = dx;
|
||||
trans.scroll_y = dy;
|
||||
}
|
||||
|
||||
void static inline mix_pixel(int x, int y, uint16_t color, uint8_t mix, uint8_t bits)
|
||||
@@ -298,8 +308,12 @@ void static inline mix_pixel(int x, int y, uint16_t color, uint8_t mix, uint8_t
|
||||
return;
|
||||
}
|
||||
|
||||
x += scroll.x;
|
||||
y += scroll.y;
|
||||
if (trans.invert) {
|
||||
x = crop.w - 1 - x;
|
||||
y = crop.h - 1 - y;
|
||||
}
|
||||
x += trans.scroll_x;
|
||||
y += trans.scroll_y;
|
||||
if ((x < 0) || (x >= crop.w) || (y < 0) || (y >= crop.h)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -35,3 +35,4 @@ void st7789_bar(int x, int y, uint16_t w, uint16_t h, uint16_t color, uint8_t mi
|
||||
void st7789_line(int x0, int y0, int x1, int y1, uint16_t color, uint8_t mix);
|
||||
|
||||
void st7789_scroll(int dx, int dy);
|
||||
void st7789_invert(bool enable);
|
||||
|
||||
Reference in New Issue
Block a user