2022 暑假在家练-简易电子沙漏
使用rp2040及led灯板,采用c语言编程制作的电子沙漏。
标签
显示
LED
RP2040
灯板
sll
更新2022-09-02
442

内容介绍

 

一项目需求

  • 搭建rp2040c语言编程环境
  • 移植freertos和lvgl到工程,采用lvgl自带的矩阵键盘和文本框组件实现定时时间输入。
  • 驱动hc959 8*8led灯板,并在其上显示沙漏动画,
  • 驱动板载的mma7660陀螺仪,控制沙漏的翻转。

二完成的功能

2.1 c语言环境搭建

2.1.1在Ubuntu下搭建

使用官方提供的安装脚本

pico-setup/pico_setup.sh at master · raspberrypi/pico-setup (github.com)

详细请参考官方文档

需要一些简单的修改

#!/bin/bash

# Exit on error
set -e

if grep -q Raspberry /proc/cpuinfo; then
    echo "Running on a Raspberry Pi"
else
    echo "Not running on a Raspberry Pi. Use at your own risk!"
fi

# Number of cores when running make
JNUM=4

# Where will the output go?
OUTDIR="$(pwd)/pico"

# Install dependencies
GIT_DEPS="git"
SDK_DEPS="cmake gcc-arm-none-eabi gcc g++"
OPENOCD_DEPS="gdb-multiarch automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev"
VSCODE_DEPS="code"
UART_DEPS="minicom"

# Build full list of dependencies
DEPS="$GIT_DEPS $SDK_DEPS"

if [[ "$SKIP_OPENOCD" == 1 ]]; then
    echo "Skipping OpenOCD (debug support)"
else
    DEPS="$DEPS $OPENOCD_DEPS"
fi

echo "Installing Dependencies"
sudo apt update
sudo apt install -y $DEPS

echo "Creating $OUTDIR"
# Create pico directory to put everything in
mkdir -p $OUTDIR
cd $OUTDIR

# Clone sw repos
GITHUB_PREFIX="https://gitclone.com/github.com/raspberrypi/" #添加github国内下载连接
GITHUB_SUFFIX=".git"
SDK_BRANCH="master"
//开始clone所有文件包括子模块,可以直接从我的附件中获取。解压到/home/user_name/pico下会自动跳过下载
for REPO in sdk examples extras playground
do
    DEST="$OUTDIR/pico-$REPO"

    if [ -d $DEST ]; then
        echo "$DEST already exists so skipping"
    else
        REPO_URL="${GITHUB_PREFIX}pico-${REPO}${GITHUB_SUFFIX}"
        echo "Cloning $REPO_URL"
        git clone -b $SDK_BRANCH $REPO_URL

        # Any submodules
        cd $DEST

        cd $OUTDIR

        # Define PICO_SDK_PATH in ~/.bashrc
        VARNAME="PICO_${REPO^^}_PATH"
        echo "Adding $VARNAME to ~/.bashrc"
        echo "export $VARNAME=$DEST" >> ~/.bashrc
        export ${VARNAME}=$DEST
    fi
done

cd $OUTDIR

# Pick up new variables we just defined
source ~/.bashrc
//编译例程
# Build a couple of examples
cd "$OUTDIR/pico-examples"

cd build
cmake ../ -DCMAKE_BUILD_TYPE=Debug

for e in blink hello_world
do
    echo "Building $e"
    cd $e
    make -j$JNUM
    cd ..
done

cd $OUTDIR
//下载Picoprobe picotool,没什么问题,直接clone就能过
# Picoprobe and picotool
for REPO in picoprobe picotool
do
    DEST="$OUTDIR/$REPO"
    REPO_URL="${GITHUB_PREFIX}${REPO}${GITHUB_SUFFIX}"
    #git clone $REPO_URL

    # Build both
    cd $DEST
    #mkdir build
    cd build
    cmake ../
    make -j$JNUM

    if [[ "$REPO" == "picotool" ]]; then
        echo "Installing picotool to /usr/local/bin/picotool"
        sudo cp picotool /usr/local/bin/
    fi

    cd $OUTDIR
done

#openocd可以在别的教程里学习,这里在拉取子模块时会出问题,它的子模块不在github。
if [ -d openocd ]; then
    echo "openocd already exists so skipping"
    SKIP_OPENOCD=1
fi

if [[ "$SKIP_OPENOCD" == 1 ]]; then
    echo "Won't build OpenOCD"
else
    # Build OpenOCD
    echo "Building OpenOCD"
    cd $OUTDIR
    # Should we include picoprobe support (which is a Pico acting as a debugger for another Pico)
    INCLUDE_PICOPROBE=1
    OPENOCD_BRANCH="rp2040"
    OPENOCD_CONFIGURE_ARGS="--enable-ftdi --enable-sysfsgpio --enable-bcm2835gpio"
    if [[ "$INCLUDE_PICOPROBE" == 1 ]]; then
        OPENOCD_CONFIGURE_ARGS="$OPENOCD_CONFIGURE_ARGS --enable-jlink"
    fi

    git clone "${GITHUB_PREFIX}openocd${GITHUB_SUFFIX}" -b $OPENOCD_BRANCH --depth=1
    cd openocd
    ./bootstrap
    ./configure $OPENOCD_CONFIGURE_ARGS
    make -j$JNUM
    sudo make install
fi

cd $OUTDIR
//安装vscode
if [[ "$SKIP_VSCODE" == 1 ]]; then
    echo "Skipping VSCODE"
else
    echo "Installing VSCODE"
    sudo apt install -y $VSCODE_DEPS

    # Get extensions
    code --install-extension marus25.cortex-debug
    code --install-extension ms-vscode.cmake-tools
    code --install-extension ms-vscode.cpptools
fi

2.1.2在win环境下搭建

不推荐,太慢了。详细请参考官方文档

2.2 LCD显示及按键交互

通过遥感可以控制鼠标移动,选择要设定的时间,B键按下选中,设置完成后鼠标点击“Enter”后等待程序执行。

图一

                           (图一)

2.3 在2* 8*8led灯板上显示任意的点和矩形,由这些点和矩形可以快速扫描出任何可见的图形,但是扫描间隔不能太快,会重复显示。

FgKz0r0-wq9eAinNJ4HCDFsR9awfFkOILzIiiXt-k5mhBgq0nzFDUdeA

(图二)                                                                            (图三)

三实现思路

使用lcd屏幕和摇杆,设置定时时间,在确认后创建HC595扫描任务,扫描任务会等待imu任务检测到陀螺仪反转一次并发增加(减少)的led的灯板,再次期间扫描任务一直被阻塞。一轮扫描结束后删除扫描任务,等待再次设定时间。

四 实现过程

4.1 程序流程图

FlySUa-rx5j7Vci9eKT9sg9XAdf6

4.2 HC595任务

4.2.1  rp2040 spi初始化

    spi_init(spi1, 10000 * 1000);//初始化spi1
    spi_set_format(spi1, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST);设置spi数据为8位,极性
    gpio_set_function(26, GPIO_FUNC_SPI);//MOSI数据线
    gpio_set_function(27, GPIO_FUNC_SPI);//时钟线
    gpio_init(22);
    gpio_set_dir(22, GPIO_OUT);//latch信号
    spi_sent_led(clear, sizeof(clear)); //清除一次595储存器,避免初始化后屏幕有亮起

4.2.2 向hc595发送数据

void spi_sent_led(const uint8_t *src, size_t len)
{
    for (uint8_t i = 0; i < len; i++)
    {
        uint8_t c = src[i];
        spi_write_blocking(spi1, &c, 1);
        latch();  //latch信号,将储存器的数据输出。
    }
}

4.2.3   显示一个矩形

void Draw_Line(uint8_t led_x, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
{
  // spi_sent_led(clear, sizeof(clear)/2);
  uint8_t c = 0b1;
  uint8_t r = 0b1;
  uint8_t num_1 = 0;
  num_1 = x1 - x0;
  c <<= (x0 - 1); //列初始坐标
  r <<= (y0 - 1); //行初始坐标
  for (int i = 0; i < num_1; i++)
  {
    x1--;
    c |= (1 << x1);
  }
  num_1 = y1 - y0;
  for (int i = 0; i < num_1; i++)
  {
    y1--;           // 7-1 = 6
    r |= (1 << y1); // r |= 1000000
  }
  if (led_x == 1)
  {
    dat_1[0] = c;
    dat_1[1] = r;
    spi_sent_led(dat_2, 2); //发送两次,把原先led_2上的数据覆盖一次,防止led_1的数据移动到led_2;
    spi_sent_led(dat_1, 2);
  }
  else if (led_x == 2)
  {
    dat_2[0] = c;
    dat_2[1] = r;
    spi_sent_led(dat_2, 2);
    spi_sent_led(dat_1, 2);
  }
}

4.3 IMU任务

基于树莓派 RP2040 的嵌入式系统学习平台板载了一颗MMA7660姿态传感器,主要用于检测 X、Y、Z 三个轴所受到的加速度大小。检测范围是 - 1.5g ~ 1.5g,其中,g 为一个重力加速度。由于 MMA7660 比较低端,因此也只有 6BIT的精度,而且输出值上还会有 3 个刻度的误差,因此在值的输出上,必须经过一个软件的均值滤波处理。一般来说,如果传感器只是应用于方位检测的话,8 个值的滤波就够了。而用于动作检测的话,一般使用 32 阶的均值滤波。

4.3.1 驱动程序

这里只对传感器原始数据进行判断,不需要高精度和具体的角度换算,所以程序相对简单。移植的程序为Arduino的Accelerometer_MMA7660库。

#include "MMA7660.h"
#include "hardware/i2c.h"
#include "pico/binary_info.h"
#include "pico/stdlib.h"
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"

struct MMA7660_LOOKUP accLookup[64];

/*Function: Write a byte to the register of the MMA7660*/
void MMA7660_write(uint8_t _register, uint8_t _data)
{
    uint8_t buf[2];
    buf[0] = _register;
    buf[1] = _data;
    i2c_write_blocking(i2c1, MMA7660_ADDR, buf, 2, false);
}

/*Function: Read a byte from the regitster of the MMA7660*/
void MMA7660_read(uint8_t _register, uint8_t *buf, uint8_t len)
{
    uint8_t val = _register;
    i2c_write_blocking(i2c1, MMA7660_ADDR, &val, 1, true);
    i2c_read_blocking(i2c1, MMA7660_ADDR, buf, len, false);
}

void MMA7660_initAccelTable()
{
    int i;
    float val, valZ;

    for (i = 0, val = 0; i < 32; i++)
    {
        accLookup[i].g = val;
        val += 0.047;
    }

    for (i = 63, val = -0.047; i > 31; i--)
    {
        accLookup[i].g = val;
        val -= 0.047;
    }

    for (i = 0, val = 0, valZ = 90; i < 22; i++)
    {
        accLookup[i].xyAngle = val;
        accLookup[i].zAngle = valZ;

        val += 2.69;
        valZ -= 2.69;
    }

    for (i = 63, val = -2.69, valZ = -87.31; i > 42; i--)
    {
        accLookup[i].xyAngle = val;
        accLookup[i].zAngle = valZ;

        val -= 2.69;
        valZ += 2.69;
    }

    for (i = 22; i < 43; i++)
    {
        accLookup[i].xyAngle = 255;
        accLookup[i].zAngle = 255;
    }
}

void MMA7660_init()
{
    i2c_init(i2c1, 400 * 1000);
    gpio_set_function(10, GPIO_FUNC_I2C);
    gpio_set_function(11, GPIO_FUNC_I2C);
    gpio_pull_up(10);
    gpio_pull_up(11);
    // Make the I2C pins available to picotool
    bi_decl(bi_2pins_with_func(10, 11, GPIO_FUNC_I2C));

    MMA7660_initAccelTable();
    MMA7660_setMode(MMA7660_STAND_BY);
    MMA7660_setSampleRate(AUTO_SLEEP_32);
    MMA7660_setMode(MMA7660_ACTIVE);
}
void MMA7660_setMode(uint8_t mode)
{
    MMA7660_write(MMA7660_MODE, mode);
}
void MMA7660_setSampleRate(uint8_t rate)
{
    MMA7660_write(MMA7660_SR, rate);
}

/*Function: Get the contents of the registers in the MMA7660*/
/*          so as to calculate the acceleration.            */
bool MMA7660_getXYZ(int8_t *x, int8_t *y, int8_t *z)
{

    unsigned char val[3];
    int count = 0;
    val[0] = val[1] = val[2] = 64;
    MMA7660_read(MMA7660_X, val, 3);
    count++;
    *x = ((int8_t)(val[0] << 2)) / 4;
    *y = ((int8_t)(val[1] << 2)) / 4;
    *z = ((int8_t)(val[2] << 2)) / 4;

    return 1;
}

bool MMA7660_getAcceleration(float *ax, float *ay, float *az)
{
    int8_t x, y, z;
    if (!MMA7660_getXYZ(&x, &y, &z))
    {
        return 0;
    }
    *ax = x / 21.00;
    *ay = y / 21.00;
    *az = z / 21.00;

    return 1;
}

   MMA7660.h

#ifndef __MMC7660_H__
#define __MMC7660_H__
#include "pico/stdlib.h"

#define MMA7660_ADDR 0x4c

#define MMA7660_X 0x00
#define MMA7660_Y 0x01
#define MMA7660_Z 0x02
#define MMA7660_TILT 0x03
#define MMA7660_SRST 0x04
#define MMA7660_SPCNT 0x05
#define MMA7660_INTSU 0x06
#define MMA7660_SHINTX 0x80
#define MMA7660_SHINTY 0x40
#define MMA7660_SHINTZ 0x20
#define MMA7660_GINT 0x10
#define MMA7660_ASINT 0x08
#define MMA7660_PDINT 0x04
#define MMA7660_PLINT 0x02
#define MMA7660_FBINT 0x01
#define MMA7660_MODE 0x07
#define MMA7660_STAND_BY 0x00
#define MMA7660_ACTIVE 0x01
#define MMA7660_SR 0x08     // sample rate register
#define AUTO_SLEEP_120 0X00 // 120 sample per second
#define AUTO_SLEEP_64 0X01
#define AUTO_SLEEP_32 0X02
#define AUTO_SLEEP_16 0X03
#define AUTO_SLEEP_8 0X04
#define AUTO_SLEEP_4 0X05
#define AUTO_SLEEP_2 0X06
#define AUTO_SLEEP_1 0X07
#define MMA7660_PDET 0x09
#define MMA7660_PD 0x0A

struct MMA7660_DATA
{
  uint8_t X;
  uint8_t Y;
  uint8_t Z;
  uint8_t TILT;
  uint8_t SRST;
  uint8_t SPCNT;
  uint8_t INTSU;
  uint8_t MODE;
  uint8_t SR;
  uint8_t PDET;
  uint8_t PD;
};

struct MMA7660_LOOKUP
{
  float g;
  float xyAngle;
  float zAngle;
};

struct MMA7660_ACC_DATA
{
  struct MMA7660_LOOKUP x;
  struct MMA7660_LOOKUP y;
  struct MMA7660_LOOKUP z;
};
void MMA7660_init();
void MMA7660_initAccelTable();
void MMA7660_setSampleRate(uint8_t rate);
void MMA7660_setMode(uint8_t mode);
void MMA7660_write(uint8_t _register, uint8_t _data);
void MMA7660_read(uint8_t _register, uint8_t *buf, uint8_t len);
bool MMA7660_getXYZ(int8_t *x, int8_t *y, int8_t *z);
bool MMA7660_getAcceleration(float *ax, float *ay, float *az);
#endif

五 遇到的主要难题

使用c语言环境移植st7789驱动程序的时候发现现有的历程都驱动不起来,后来在案例中检索发现是板子上的芯片版本的问题。

    • 这个屏幕只支持spi极性1,相位1来驱动(st7789手册中的其它一些spi mode不支持)
    • 这个屏幕必须在硬reset和软reset前发送0xFF指令(实际这个指令除了让它能正常不用cs引脚初始化外,没有任何对应功能)
      void ST7789_Reset()
      {	
      	ST7789_WriteCommand(0xff);//在复位前发送0xff
      	if (st7789_pinRST != -1)
      	{
      		gpio_put(st7789_pinRST, 0);
      		sleep_ms(5);
      		gpio_put(st7789_pinRST, 1);
      	}
      }
      //感谢笛子老师!

环境的搭建是个大问题,起初在win上搭建,但是完成之后速度确实太慢,还不如直接使用arduino进行开发方便。后来在liunx上搭建,使用的是书梅派官方提供的安装脚本(需要魔法或者更换github host),但是需要一些修改。Debug配置很麻烦(没使用过工具链编译听麻烦的)我使用的是pico probe。

led灯板的显示,起初想法是一个点一个点的刷屏,但是如果间隔太短会导致其他的二极管中的电流无法完全释放。只能减少写hc595的次数,来提高显示效果。

六 总结

这是第一次使用编译链进行开发,中间遇到了网络,命令行的debug,还有等等的问题。总之学到很东西。本次是打算去学习freertos+lvgl并完成这个项目,但是个人能力有限学了个中不溜,勉勉强强完成了基本功能,后续能有改进,加上更多的lcd动画,让imu能发挥更多的作用;优化代码逻辑,这次的项目完代码是非常的繁琐了,其实就这个完成度可以很简单,但是有了这些基础,后续可以添加更多的功能。

附件下载

user_code.uf2
uf2文件
源码和环境.txt

团队介绍

个人

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号