一项目需求
- 搭建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灯板上显示任意的点和矩形,由这些点和矩形可以快速扫描出任何可见的图形,但是扫描间隔不能太快,会重复显示。
(图二) (图三)
三实现思路
使用lcd屏幕和摇杆,设置定时时间,在确认后创建HC595扫描任务,扫描任务会等待imu任务检测到陀螺仪反转一次并发增加(减少)的led的灯板,再次期间扫描任务一直被阻塞。一轮扫描结束后删除扫描任务,等待再次设定时间。
四 实现过程
4.1 程序流程图
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能发挥更多的作用;优化代码逻辑,这次的项目完代码是非常的繁琐了,其实就这个完成度可以很简单,但是有了这些基础,后续可以添加更多的功能。