前沿
1. 安装交叉编译器
- 在开发板光盘 A-基础资料->5、开发工具->1、交叉编译器路径下找到 st-example-image-qt
wayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh。将它拷贝到 Ubuntu 虚拟机上。 拷贝到 Ubuntu 后,赋予 st-example-image-qtwayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh 可执行权限。
*chmod +x st-example-image-qtwayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh - 执行./st*.sh脚本安装
- 安装完成后,安装的交叉编译工具链都会安装在/opt/目录下。
- ls /opt/st/
- 安装完成之后,在使用之前先对交叉编译工具的环境进行设置,使用 source 执行安装目录下的
environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi 脚本文件即可,如下所示:
source /opt/st/stm32mp1/3.1-snapshot/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
注意:每个终端需要执行上面的source命令之后,才能打印出${CC}.
使用${CC} -o led led.c编译
生成的led可执行文件通过scp命令传输到开发板,./led执行。
1.控制led
15_led.c:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define LED_TRIGGER "/sys/class/leds/user-led/trigger"
#define LED_BRIGHTNESS "/sys/class/leds/user-led/brightness"
#define USAGE() fprintf(stderr, "usage:\n" \
" %s <on|off>\n" \
" %s <trigger> <type>\n", argv[0], argv[0])
int main(int argc, char *argv[])
{
int fd1, fd2;
/* 校验传参 */
if (2 > argc) {
USAGE();
exit(-1);
/* 打开文件 */
fd1 = open(LED_TRIGGER, O_RDWR);
if (0 > fd1) {
perror("open error");
exit(-1);
}
fd2 = open(LED_BRIGHTNESS, O_RDWR);
if (0 > fd2) {
perror("open error");
exit(-1);
}
/* 根据传参控制 LED */
if (!strcmp(argv[1], "on")) {
write(fd1, "none", 4); //先将触发模式设置为 none
write(fd2, "1", 1);
//点亮 LED
}
else if (!strcmp(argv[1], "off")) {
write(fd1, "none", 4); //先将触发模式设置为 none
write(fd2, "0", 1);
//LED 灭
}
else if (!strcmp(argv[1], "trigger")) {
if (3 != argc) {
USAGE();
exit(-1);
}
if (0 > write(fd1, argv[2], strlen(argv[2])))
perror("write error");
}
else
USAGE();
exit(0);
}
使用${CC} -o 15_led 15_led.c编译
在虚拟机使用ifconfig ens33 192.168.137.4配置ip
开发板ifconfig eth 192.168.137.3配置ip
开发板:scp tao@192.168.137.4:~/linux/c_cpp/15_led ~/将文件传输至开发板。
./15_led on点亮ds1,./15_led off熄灭ds1,./15_led trigger heartbeat使其闪烁。
也可以使用 DS0 进行测试,将 源码中的路径修改一下即可(/sys/class/leds/user-led/修改为/sys/class/leds/sys-led/)
2.操作GPIO
* 与 LED 设备一样,GPIO 同样也是通过 sysfs 方式进行操控,进入到/sys/class/gpio 目录下,如下所示:
图 16.1.1 /sys/class/gpio 目录
可以看到该目录下包含两个文件 export、unexport 以及许多个以 gpiochipX(X 等于 0、32、64、96、
128)命名的文件夹。
gpiochipX:当前 SoC 所包含的 GPIO 控制器,我们知道 STM32MP157 一共包含了 12 个 GPIO 控制器,分别为 GPIOA、GPIOB、GPIOC…在这里分别对应 gpiochip0、gpiochip16、gpiochip32…以此类推,每一个 gpiochipX 文件夹用来管理一组 GPIO。随便进入到其中某个目录下,可以看到这些目录下包含了如下文件:
图 16.1.2 gpiochip0 目录下的文件,在这个目录我们主要关注的是 base、label、ngpio 这三个属性文件,这三个属性文件均是只读、不可写。
- base:与 gpiochipX 中的 X 相同,表示该控制器所管理的这组 GPIO 引脚中最小的编号。每一个 GPIO引脚都会有一个对应的编号,Linux 下通过这个编号来操控对应的 GPIO 引脚;label:该组 GPIO 对应的标签,也就是名字;ngpio:该控制器所管理的 GPIO 引脚的数量(所以引脚编号范围是:base ~ base+ngpio-1);可使用cat ngpio查看;
- 对于给定的一个 GPIO 引脚GPIOB_IO10,那它对应的编号是16 + 10 = 26;同理 GPIOC_IO05对应的编号是 32 + 5 = 37。
export:用于将指定编号的 GPIO 引脚导出。
- 在使用 GPIO 引脚之前,需要将其导出,导出成功之后才能使用它。注意 export 文件是只写文件,不能读取,将一个指定的编号写入到 export 文件中即可将对应的 GPIO 引脚导出,譬如:
echo 0 > export # 导出编号为 0 的 GPIO 引脚(对于 STM32MP157 来说,也就是 GPIOA_IO0)
导出成功之后会发现在/sys/class/gpio 目录下生成了一个名为 gpio0 的文件夹(gpioX,X 表示对应的编号) - 这个文件夹就是导出来的 GPIO 引脚对应的文件夹,用于管理、控制该 GPIO 引脚,
unexport:将导出的 GPIO 引脚删除,也就是取消导出 GPIO。
- 当使用完 GPIO 引脚之后,我们需要将导出的引脚删除,同样该文件也是只写文件、不可读,譬如:
echo 0 > unexport # 删除导出的编号为 0 的 GPIO 引脚,取消导出 GPIO
导出的引脚文件夹
- direction:配置 GPIO 引脚为输入或输出模式。该文件可读、可写,读表示查看 GPIO 当前是输入还是输出模式,写表示将 GPIO 配置为输入或输出模式;读取或写入操作可取的值为"out"(输出模式)和"in"(输入模式)
- value:在 GPIO 配置为输出模式下,向 value 文件写入"0"控制 GPIO 引脚输出低电平,写入"1"则控制 GPIO 引脚输出高电平。在输入模式下,读取 value 文件获取 GPIO 引脚当前的输入电平状态。
1.获取 GPIO 引脚的输入电平状态
echo “in” > direction
cat value
2.控制 GPIO 引脚输出高电平
echo “out” > direction
echo “1” > value
active_low:这个属性文件用于控制极性,可读可写,默认情况下为 0,譬如:
active_low 等于 0 时
echo “0” > active_low
echo “out” > direction
echo “1” > value #输出高
echo “0” > value #输出低
active_low 等于 1 时
echo “1” > active_low
echo “out” > direction
echo “1” > value #输出低
echo “0” > value #输出高
由此看出,active_low 的作用已经非常明显了,就是用于控制极性,对于输入模式来说也同样适用。
edge:控制中断的触发模式,
- 该文件可读可写。在配置 GPIO 引脚的中断触发模式之前,需将其设置为输入模式:
非中断引脚:echo “none” > edge
上升沿触发:echo “rising” > edge
下降沿触发:echo “falling” > edge
边沿触发:echo “both” > edge
当引脚被配置为中断后可以使用 poll()函数监听引脚的电平状态变化
输出示例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
static char gpio_path[100];
static int gpio_config(const char *attr, const char *val)
{
char file_path[100];
int len;
int fd;
sprintf(file_path, "%s/%s", gpio_path, attr);
if (0 > (fd = open(file_path, O_WRONLY))) {
perror("open error");
return fd;
}
len = strlen(val);
if (len != write(fd, val, len)) {
perror("write error");
close(fd);
return -1;
}
close(fd); //关闭文件
return 0;
}
int main(int argc, char *argv[])
{
/* 校验传参 */
if (3 != argc) {
fprintf(stderr, "usage: %s <gpio> <value>\n", argv[0]);
exit(-1);
}
/* 判断指定编号的 GPIO 是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
if (access(gpio_path, F_OK)) {//如果目录不存在 则需要导出
int fd;
int len;
if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
perror("open error");
exit(-1);
}
len = strlen(argv[1]);
if (len != write(fd, argv[1], len)) {//导出 gpio
perror("write error");
close(fd);
exit(-1);
}
close(fd); //关闭文件
}
/* 配置为输出模式 */
if (gpio_config("direction", "out"))
exit(-1);
/* 极性设置 */
if (gpio_config("active_low", "0"))
exit(-1);
/* 控制 GPIO 输出高低电平 */
if (gpio_config("value", argv[2]))
exit(-1);
/* 退出程序 */
exit(0);
}
执行程序时需要传入两个参数,argv[1]指定 GPIO 的编号、argv[2]指定输出电平状态(0 表示低电平、1 表示高电平)。
如./gpio 0 1表示gpio0 输出高电平
gpio输入demo
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
static char gpio_path[100];
static int gpio_config(const char *attr, const char *val)
{
char file_path[100];
int len;
int fd;
sprintf(file_path, "%s/%s", gpio_path, attr);
if (0 > (fd = open(file_path, O_WRONLY))) {
perror("open error");
return fd;
}
len = strlen(val);
if (len != write(fd, val, len)) {
perror("write error");
close(fd);
return -1;
}
close(fd); //关闭文件
return 0;
}
int main(int argc, char *argv[])
{
char file_path[100];
char val;
int fd;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <gpio>\n", argv[0]);
exit(-1);
}
/* 判断指定编号的 GPIO 是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
if (access(gpio_path, F_OK)) {//如果目录不存在 则需要导出
int len;
if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
perror("open error");
exit(-1);
}
len = strlen(argv[1]);
if (len != write(fd, argv[1], len)) {//导出 gpio
perror("write error");
close(fd);
exit(-1);
}
close(fd); //关闭文件
}
/* 配置为输入模式 */
if (gpio_config("direction", "in"))
exit(-1);
/* 极性设置 */
if (gpio_config("active_low", "0"))
exit(-1);
/* 配置为非中断方式 */
if (gpio_config("edge", "none"))
exit(-1);
/* 读取 GPIO 电平状态 */
sprintf(file_path, "%s/%s", gpio_path, "value");
if (0 > (fd = open(file_path, O_RDONLY))) {
perror("open error");
exit(-1);
}
if (0 > read(fd, &val, 1)) {
perror("read error");
close(fd);
exit(-1);
}
printf("value: %c\n", val);
/* 退出程序 */
close(fd);
exit(0);
}
执行程序时需要传入一个参数,argv[1]指定要读取电平状态的 GPIO 对应的编号。
gpio 中断demo
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
static char gpio_path[100];
static int gpio_config(const char *attr, const char *val)
{
char file_path[100];
int len;
int fd;
sprintf(file_path, "%s/%s", gpio_path, attr);
if (0 > (fd = open(file_path, O_WRONLY))) {
perror("open error");
return fd;
}
len = strlen(val);
if (len != write(fd, val, len)) {
perror("write error");
return -1;
}
close(fd); //关闭文件
return 0;
}
int main(int argc, char *argv[])
{
struct pollfd pfd;
char file_path[100];
int ret;
char val;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <gpio>\n", argv[0]);
exit(-1);
}
/* 判断指定编号的 GPIO 是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
if (access(gpio_path, F_OK)) {//如果目录不存在 则需要导出
int len;
int fd;
if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
perror("open error");
exit(-1);
}
len = strlen(argv[1]);
if (len != write(fd, argv[1], len)) {//导出 gpio
perror("write error");
exit(-1);
}
close(fd); //关闭文件
}
/* 配置为输入模式 */
if (gpio_config("direction", "in"))
exit(-1);
/* 极性设置 */
if (gpio_config("active_low", "0"))
exit(-1);
/* 配置中断触发方式: 上升沿和下降沿 */
if (gpio_config("edge", "both"))
exit(-1);
/* 打开 value 属性文件 */
sprintf(file_path, "%s/%s", gpio_path, "value");
if (0 > (pfd.fd = open(file_path, O_RDONLY))) {
perror("open error");
exit(-1);
}
/* 调用 poll */
pfd.events = POLLPRI; //只关心高优先级数据可读(中断)
read(pfd.fd, &val, 1);//先读取一次清除状态
for ( ; ; ) {
ret = poll(&pfd, 1, -1); //调用 poll
if (0 > ret) {
perror("poll error");
exit(-1);
}
else if (0 == ret) {
fprintf(stderr, "poll timeout.\n");
continue;
}
/* 校验高优先级数据是否可读 */
if(pfd.revents & POLLPRI) {
if (0 > lseek(pfd.fd, 0, SEEK_SET)) {//将读位置移动到头部
perror("lseek error");
exit(-1);
}
if (0 > read(pfd.fd, &val, 1)) {
perror("read error");
exit(-1);
}
printf("GPIO 中断触发<value=%c>\n", val);
}
}
/* 退出程序 */
exit(0);
}
执行程序时需要传入一个参数,argv[1]指定要读取电平状态的 GPIO 对应的编号。