之前学习STM32F103C8T6的时候,学习过对应GPIO的输出:
操作STM32的GPIO需要3个步骤: 使用RCC开启GPIO的时钟、使用GPIO_Init函数初始化GPIO、使用输入/输出函数控制GPIO口。
【STM32】GPIO输出-CSDN博客
这里再看看STM32MP157的GPIO引脚使用
1 普适的 GPIO 引脚操作方法
GPIO: General-purpose input/output,通用的输入输出口
1.1 GPIO 模块一般结构
⚫ 有多组 GPIO,每组有多个 GPIO
⚫ 使能:电源/时钟
⚫ 模式(Mode):引脚可用于 GPIO 或其他功能
⚫ 方向:引脚 Mode 设置为 GPIO 时,可以继续设置它是输出引脚,还是输入引脚
⚫ 数值:
◼ 对于输出引脚,可以设置寄存器让它输出高、低电平
◼ 对于输入引脚,可以读取寄存器得到引脚的当前电平
1.2 GPIO 寄存器操作
⚫ 芯片手册一般有相关章节,用来介绍: power/clock
◼ 可以设置对应寄存器使能某个 GPIO 模块(Module)
◼ 有些芯片的 GPIO 是没有使能开关的,即它总是使能的
⚫ 一个引脚可以用于 GPIO、串口、 USB 或其他功能,
◼ 有对应的寄存器来选择引脚的功能
⚫ 对于已经设置为 GPIO 功能的引脚,有方向寄存器用来设置它的方向:输出、输入
⚫ 对于已经设置为 GPIO 功能的引脚,有数据寄存器用来写、读引脚电平状态
GPIO 寄存器的 2 种操作方法: 原则:不能影响到其他位
① 直接读写:读出、修改对应位、写入
a) 要设置 bit n:
val = data_reg; // 读出来
val = val | (1<<n); // 1左移n位,再或
data_reg = val; // 写回去
b) 要清除 bit n:
val = data_reg;
val = val & ~(1<<n);
data_reg = val;
② set-and-clear protocol:
set_reg, clr_reg, data_reg 三个寄存器对应的是同一个物理寄存器,
a) 要设置 bit n: set_reg = (1<<n);
b) 要清除 bit n: clr_reg = (1<<n);
2 STM32MP157 GPIO 操作方法
RCC: Reset and clock control (复位和时钟控制)
GPIO: General-purpose input/output,通用的输入输出口
如下图所示,我们需要操作 LED2 引脚标号为 PA10 LED3 引脚标号为 PG8
2.1 STM32MP157 的 GPIO 模块结构
参 考 资 料 : CPU 开 发 手 册 DM00327695.pdf《 13: General-purposeI/Os(GPIO)》。
GPIO 寄存器有多种,包括功能寄存器和 GPIO 时钟寄存器,下面进行具体介绍,如下为引脚对应的表。
GPIOA (16 pins) <--对应--> gpiochip0
GPIOB (16 pins) <--对应--> gpiochip1
GPIOC (16 pins) <--对应--> gpiochip2
GPIOD (16 pins) <--对应--> gpiochip3
GPIOE (16 pins) <--对应--> gpiochip4
GPIOF (16 pins) <--对应--> gpiochip5
GPIOG (16 pins) <--对应--> gpiochip6
GPIOH (16 pins) <--对应--> gpiochip7
GPIOI (16 pins) <--对应--> gpiochip8
....
GPIOZ (8 pins) <--对应--> gpiochip9
由于 GPIO 的功能多种多样,我们需要根据实际功能来设置 GPIO 引脚的工作模式。对于 STM32MP157 来说,每一个 GPIO 端口有四个32位的配置寄存器( GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR 和 GPIOx_PUPDR),两个32 位的数据寄存器(GPIOx_IDR 和 GPIOx_ODR),一个 32 位的设置/复位寄存器 (GPIOx_BSRR) 。 此 外 , 所 有 的 GPIO 都 有 一 个 32 位 的 锁 定 寄 存 器( GPIOx_LCKR ) 和 两 个 32 位 的 多 功 能 选 择 寄 存 器 ( GPIOx_AFRH and GPIOx_AFRL)。此外,还有 GPIO 外设时钟控制寄存器。
通过编程寄存器,我们可以设置 GPIO 为不同的模式,包括以下几种。
⚫ 输入悬空
⚫ 输入上拉
⚫ 输入下拉
⚫ 模拟输入
⚫ OD 输出,支持上拉或者下拉
⚫ Push-pull 输出,支持上拉或者下拉
⚫ 多功能 push-pull 输出,支持上拉或者下拉
⚫ 多功能 OD 输出,支持上拉或者下拉
下图为 GPIO 的基本结构简图,可以看到支持的各种不同模式和对应的寄存器,我们需要根据实际的功能来设置输入输出功能。
和STM32F108C8T6一样
参考:【STM32】GPIO输出-CSDN博客
每 个 GPIO 端 口 的 基 地 址 不 同 , 具 体 的 对 应 可 以 通 过 查 看 参考手册《dm00327659.pdf》的 memory map 章节来查看,下面红框内是本次实验使用到的 GPIOA 和 GPIOG 的基地址
GPIO 的 使 用 涉 及 到 多 个 寄 存 器 , 包 括 RCC 时 钟 GPIOx_MODER 、GPIOx_IDR 和 GPIOx_ODR 等。
2.2 RCC 用于设置是否向 GPIO 模块提供时钟
数据手册截图
GPIO 外设的时钟来源各自不同,具体的可以从参考手册当中可以看到,详见《dm00327659.pdf》 RCC 章节 Table56。下图为截图,其中 GPIOA-K 的时钟来源为 hclk4, GPIOZ 的时钟来源为 hclk5。因此为了使用 GPIO,我们需要使能锁相环和外设 GPIO 各自对应的时钟。
接下来我们要找到配置锁相环 PLL4 时钟源的寄存器,我们需要将其使能,并等待完成锁定。查看 CPU 开发手册 DM00327695.pdf《10 Reset and clock control (RCC)》
由于 STM32MP157 包含 cortex-m4 和 cortex-A7 两部分,并且 GPIO 可以分别由 MCU 和 APU 来控制,因此 GPIO 的时钟使能也分成两种。
2.3 GPIOx_MODER 配置 GPIO 模式
GPIOx_MODER 用于配置 GPIO 的模式,包括输入、通用输出、多功能和模拟共四种模式。该寄存器共 32 位,,涉及 16 个 GPIO,每个 GPIO 对应 2 位。GPIOx_MODER 的各位定义如下,我们在这里分别选择 00 和 01 两种,各自对应输入和输出模式。(上电默认为输入悬空模式)。其中 00 对应输入功能, 01 对应输出功能。
我们需要设置 PA10 对应 GPIOA10 其中 GPIOA_MODER 寄存器基地址为0x50002000 由 于 每 个 GOIO 引 脚 对 应 两 个 bit 位, 所 以 我们需要操 作bit[21:20]设置为 01 输出模式
PG8 对应 GPIOG8 其中 GPIOA_MODER 寄存器基地址为 0x50008000 由于每个 GOIO 引脚对应两个 bit 位,所以我们需要操作 bit[17:16]设置为 01 输出模式
2.4 GPIOx_OTYPER 配置 GPIO 输出
GPIOx_OTYPER 寄存器用于配置输出类型,可以设置为输出为 OD 或者push-pull 两种结构。该寄存器共 32 位,低 16 位对应 16 个 GPIO,通过设置位的高低来选择 OD 或者 push-pull 的输出类型
3 GPIOx_IDR 设置输入 GPIO
GPIOx_IDR 是 GPIO 输入数据寄存器,用于存储外部输入的数据。可以通过读取该寄存器的值来判断外部输入电平的高低。具体的寄存器定义如下图所示,只使用低 16 位
3.1 读GPIO
1.设置 RCC_PLL4CR 使能 hclk4 使用的时钟。
2.设置 RCC_MP_AHB4ENSETR 使能 GPIOA 外设时钟。
3.设置 GPIOA_MODER 中某位为 1,把该引脚设置为输入功能。
4.读 GPIOG_IDR 某位的值为 1 或者 0 来判断输入是否电平。
/* 使能 PLL4 */
/* RCC_PLL4CR 地址: 0x50000000 + 0x894 */
pReg = (volatile unsigned int*)(0x50000000 + 0x894);
*pReg |= (1<<0);
while(*pReg & (1 << 1) == 0);
/* for A7 (0x50000000 + 0xA28) */
gpio_clk = (volatile unsigned int*)(0x50000000 + 0xA28);
*gpio_clk |= (1 << 6); // GPIOG 的外设时钟
// GPIOG 相关的寄存器地址
GPIOG_MODER = (volatile unsigned int*)(0x50008000);
GPIOG_ODR = (volatile unsigned int*)(0x50008014);
// 设置 PG8 为输出 , PG2 和 PG3 为输入
val = *GPIOG_MODER;
val &= ~((3 << 4) | (3 << 6));
unsigned int key_val = 0;unsigned int val;
val = *GPIOG_IDR; // 读取 GPIOG 的输入值
if(( val&0x0004) == 0) // 如果按键 KEY2, PG2 被按下,则输出 1
{key_val = 1;
}
else if((val &0x0008) == 0) // 如果按键 KEY1, PG3 被按下,则输出 1
{key_val = 2;
}
else // 如果没有按键按下,则输出 0
{key_val = 0;
}
return key_val; // 输出检测到的按键值
代码需要研究一下
3.2 写 GPIO
1 设置 RCC_PLL4CR 使能 hclk4 使用的时钟。
2 设置 RCC_MP_AHB4ENSETR 使能 GPIOA 外设时钟。
3 设置 GPIOA_MODER 中某位为 1,把该引脚设置为输出功能。
4 写 GPIOA_ODR 某位的值为 1 或者 0,让其输出高电平或低电平。
/* 使能 PLL4 */
// 锁相环 PLL4 初始
/* RCC_PLL4CR 地址 0x50000000 + 0x894*/
pll_clk = (volatile unsigned int*)(0x50000000 + 0x894);
*pll_clk |= (1 << 0); // 使能锁相环 PLL4
while(*pll_clk & (1 << 1) == 0); // 等待锁相环完成锁频
/*GPIOA 和 GPIOG 外设初始化
/* for A7 (0x50000000 + 0xA28) */
gpio_clk = (volatile unsigned int*)(0x50000000 + 0xA28);
*gpio_clk |= (1<<0); // 使能 GPIOA 的外设时钟
unsigned int val;
// GPIOA 相关的寄存器地址
GPIOA_MODER= (volatile unsigned int*)(0x50002000);
GPIOA_ODR = (volatile unsigned int*)(0x50002014);
// 设置 PA10 为输出
val = *GPIOA_MODER;
val &= ~(3 << 20);
val |= (1 << 20);
*GPIOA_MODER = val;
// 设置 PA10 输出低电平
val = *GPIOA_ODR;
val &= ~(1 << 10);
*GPIOA_ODR = val;
4 LED 驱动程序框架
4.1 回顾字符设备驱动程序框架
图中驱动层访问硬件外设寄存器依靠的是 ioremap 函数去映射到寄存器地址,然后开始控制寄存器。那么该如何编写驱动程序?
① 确定主设备号,也可以让内核分配;
② 定义自己的 file_operations 结构体, 这是核心;
③ 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体;
④ 把 file_operations 结构体告诉内核:通过 register_chrdev 函数;
⑤ 谁来注册驱动程序?需要一个入口函数:安装驱动程序时,就会去调用这个入口函数;
⑥ 有入口函数就应该有出口函数:卸载驱动程序是,出口函数调用unregister_chrdev;
⑦ 其它完善:提供设备信息,自动创建设备节点: class_create,device_create;
4.2 对于 LED 驱动,我们想要什么样的接口?
驱动程序访问硬件,必须先ioremap。
先随便找一个驱动程序,模仿写,作为模版
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/bitrev.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/io.h>// 主设备号
static int major = 0;
static struct class *led_class;// write函数
static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{// 从用户拷贝数据char value;copy_from_user(&value, buf, 1); // 将用户空间buf的数据拷贝1字节到value中// 设置GPIO寄存器1/0if (value){// 设置led on}else{// 设置led off}return 1;
}// open函数
static int led_open(struct inode *inode, struct file *filp)
{// 使能GPIO// 将某个引脚配置成GPIO// 配置GPIO是输出模式,只有用户程序open的时候,才表示要使用这个引脚,这个时候再配置引脚return 0;
}// file_operations结构体
static struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.open = led_open,
};// 入口函数
static int __init led_init(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 打印// 注册file_operations结构体,返回值是主设备号major = register_chrdev(0, "winter_led", &led_fops);// 驱动程序访问硬件,必须先ioremap// 创建classled_class= class_create(THIS_MODULE, "myled");// 创建设备device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); // 系统会创建名为/dev/myled的设备节点return 0;
}// 出口函数
static void __exit led_exit(void)
{// 卸载设备device_destroy(led_class, MKDEV(major, 0));// 销毁类class_destroy(led_class);// 卸载unregister_chrdev(major, "winter_led");};module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
怎么访问寄存器呢?使用指针。
4.3 简单而led驱动程序
在上面模版的基础上开始完善。
首先根据PPT(文档)设置寄存器:
1.设置 RCC_PLL4CR 使能 hclk4 使用的时钟。
2.设置 RCC_MP_AHB4ENSETR 使能 GPIOA 外设时钟。
3.设置 GPIOA_MODER 中某位为 1,把该引脚设置为输入功能。
4.读 GPIOG_IDR 某位的值为 1 或者 0 来判断输入是否电平。
先定义
// 不能使用物理地址,需要映射
// 1寄存器
// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的
static volatile unsigned int* RCC_PLL4CR;// 2使能GPIOA本身
// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
static volatile unsigned int* RCC_MP_AHB4ENSETR;// 3设置引脚为输出模式
// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式
static volatile unsigned int* GPIOA_MODER;// 4设置输出电平
// 方法2:直接写寄存器,一次操作即可,高效
// GPIOA_BSRR地址: 0x50002000 + 0x18
static volatile unsigned int* GPIOA_BSRR;
在init函数中,重映射
// 入口函数
static int __init led_init(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 打印// 注册file_operations结构体,返回值是主设备号major = register_chrdev(0, "winter_led", &led_fops);// 驱动程序访问硬件,必须先ioremap,在这里映射,映射的是一页4k的地址,参考// ioremap(base_phy, size);// 1寄存器// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的// static volatile unsigned int* RCC_PLL4CR;RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);// 2使能GPIOA本身// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28// static volatile unsigned int* RCC_MP_AHB4ENSETR;RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);// 3设置引脚为输出模式// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式// static volatile unsigned int* GPIOA_MODER;GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);// 4设置输出电平// 方法2:直接写寄存器,一次操作即可,高效// GPIOA_BSRR地址: 0x50002000 + 0x18// static volatile unsigned int* GPIOA_BSRR;GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);// 创建classled_class = class_create(THIS_MODULE, "myled");// 创建设备device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); // 系统会创建名为/dev/myled的设备节点return 0;
}
出口函数unremap
// 出口函数
static void __exit led_exit(void)
{iounmap(RCC_PLL4CR);iounmap(RCC_MP_AHB4ENSETR);iounmap(GPIOA_MODER);iounmap(GPIOA_BSRR);// 卸载设备device_destroy(led_class, MKDEV(major, 0));// 销毁类class_destroy(led_class);// 卸载unregister_chrdev(major, "winter_led");};
接着在open函数中:
(1)使能PLL4,是所有GPIO的时钟
1.设置 RCC_PLL4CR 使能 hclk4 使用的时钟。
设置bit0位为1,PLL4 ON
*RCC_PLL4CR |= (1 << 0); // 设置bit0为1
(2)使能GPIOA
2.设置 RCC_MP_AHB4ENSETR 使能 GPIOA 外设时钟。
// 使能GPIOA
*RCC_MP_AHB4ENSETR |= (1 << 0); // 1左移0位
(3)将GPIOA的第十个引脚配置成GPIO
3.设置 GPIOA_MODER 中某位为 1,把该引脚设置为输入功能。
// 将GPIOA的第十个引脚配置成GPIO
// 配置GPIO是输出模式,只有用户程序open的时候,才表示要使用这个引脚,这个时候再配置引脚
*GPIOA_MODER &= ~(3 << 20); // 清零 11左移20位,取反,
*GPIOA_MODER |= (1 << 20); // 20位设置成1,配置成01,输出模式
3的二进制是11,左移20位是:11 0000 0000 0000 0000,再取反,和原来的数据&,就将原来20/21位清零;再或上1左移20位,也就,01 0000 0000 0000 0000,
led_open总的代码
// open函数
static int led_open(struct inode *inode, struct file *filp)
{// 使能PLL4,是所有GPIO的时钟*RCC_PLL4CR |= (1 << 0); // 设置bit0为1while ((*RCC_PLL4CR & (1 << 1)) == 0); // 如果bit1一直为0的话,就等待// 使能GPIOA*RCC_MP_AHB4ENSETR |= (1 << 0); // 1左移0位// 将GPIOA的第十个引脚配置成GPIO// 配置GPIO是输出模式,只有用户程序open的时候,才表示要使用这个引脚,这个时候再配置引脚 *GPIOA_MODER &= ~(3 << 20); // 清零 11左移20位,取反,*GPIOA_MODER |= (1 << 20); // 20位设置成1,配置成01,输出模式return 0;
}
最后是write函数:将用户空间buf的数据拷贝内核中(对用户来说是写操作,对内核而言是读操作)
4.读 GPIOG_IDR 某位的值为 1 或者 0 来判断输入是否电平。
10和26都对应的是GPIO10,26是1的时候,表示低电平,也就是开启led;10是1的时候,表示高电平,也就是关闭led
// write函数
static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{// 从用户拷贝数据char value;copy_from_user(&value, buf, 1); // 将用户空间buf的数据拷贝1字节到value中// 设置GPIOA10寄存器1/0if (value){// 设置led on,让引脚输出低电平*GPIOA_BSRR = (1 << 26); // 1左移26}else{// 设置led off,让引脚输出高电平*GPIOA_BSRR = (1 << 10); // 1左移10}return 1;
}
总结:结合参考手册/数据手册写代码
总的LED驱动程序:led_drv.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>// 主设备号
static int major = 0;
static struct class *led_class;// 不能使用物理地址,需要映射
// 1寄存器
// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的
static volatile unsigned int* RCC_PLL4CR;// 2使能GPIOA本身
// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
static volatile unsigned int* RCC_MP_AHB4ENSETR;// 3设置引脚为输出模式
// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式
static volatile unsigned int* GPIOA_MODER;// 4设置输出电平
// 方法2:直接写寄存器,一次操作即可,高效
// GPIOA_BSRR地址: 0x50002000 + 0x18
static volatile unsigned int* GPIOA_BSRR;// write函数
static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{// 从用户拷贝数据char value;copy_from_user(&value, buf, 1); // 将用户空间buf的数据拷贝1字节到value中// 设置GPIOA10寄存器1/0if (value){// 设置led on,让引脚输出低电平*GPIOA_BSRR = (1 << 26); // 1左移26}else{// 设置led off,让引脚输出高电平*GPIOA_BSRR = (1 << 10); // 1左移10}return 1;
}// open函数
static int led_open(struct inode *inode, struct file *filp)
{// 使能PLL4,是所有GPIO的时钟*RCC_PLL4CR |= (1 << 0); // 设置bit0为1while ((*RCC_PLL4CR & (1 << 1)) == 0); // 如果bit1一直为0的话,就等待// 使能GPIOA*RCC_MP_AHB4ENSETR |= (1 << 0); // 1左移0位// 将GPIOA的第十个引脚配置成GPIO// 配置GPIO是输出模式,只有用户程序open的时候,才表示要使用这个引脚,这个时候再配置引脚 *GPIOA_MODER &= ~(3 << 20); // 清零 11左移20位,取反,*GPIOA_MODER |= (1 << 20); // 20位设置成1,配置成01,输出模式return 0;
}// file_operations结构体
static struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.open = led_open,
};// 入口函数
static int __init led_drv(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 打印// 注册file_operations结构体,返回值是主设备号major = register_chrdev(0, "winter_led", &led_fops);// 驱动程序访问硬件,必须先ioremap,在这里映射,映射的是一页4k的地址,参考// ioremap(base_phy, size);// 1寄存器// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的// static volatile unsigned int* RCC_PLL4CR;RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);// 2使能GPIOA本身// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28// static volatile unsigned int* RCC_MP_AHB4ENSETR;RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);// 3设置引脚为输出模式// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式// static volatile unsigned int* GPIOA_MODER;GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);// 4设置输出电平// 方法2:直接写寄存器,一次操作即可,高效// GPIOA_BSRR地址: 0x50002000 + 0x18// static volatile unsigned int* GPIOA_BSRR;GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);// 创建classled_class = class_create(THIS_MODULE, "myled");// 创建设备device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); // 系统会创建名为/dev/myled的设备节点return 0;
}// 出口函数
static void __exit led_exit(void)
{iounmap(RCC_PLL4CR);iounmap(RCC_MP_AHB4ENSETR);iounmap(GPIOA_MODER);iounmap(GPIOA_BSRR);// 卸载设备device_destroy(led_class, MKDEV(major, 0));// 销毁类class_destroy(led_class);// 卸载unregister_chrdev(major, "winter_led");};module_init(led_drv);
module_exit(led_exit);
MODULE_LICENSE("GPL");
测试程序:led_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>// ledtest /dev/myled on 点灯
// lettest /dev/myled off 熄灯int main(int argc, char** argv)
{if (argc != 3){printf("Usage: %s <dev> <on/off>\n", argv[0]);printf("eg: %s /dev/myled on\n", argv[0]);printf("eg: %s /dev/myled off\n", argv[0]);return -1;}// openint fd = open(argv[1], O_RDWR); // 可读可写if (fd < 0){printf("Failed to open %s\n", argv[1]);return -1;}char status = 0;// writeif (strcmp(argv[2], "on") == 0){status = 1;}write(fd, &status, 1); // 写回去return 0;
}
Makefile
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4all:make -C $(KERN_DIR) M=`pwd` modules$(CROSS_COMPILE)gcc -o led_test led_test.cclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f led_testobj-m += led_drv.o
编译
有bug就照着报错的该就行了
装载驱动程序
insmod led_drv.ko
lsmod
执行测试程序
./led_test /dev/myled on
./led_test /dev/myled off
板子上看不出任何变化,关掉【心跳灯】
ls /sys/class/leds/
echo none > /sys/class/leds/heartbeat/trigger
在执行led on/led off看效果,灯果然灭了。