LED 的驱动方式,常见的有四种。
有的芯片为了省电等原因,其引脚驱动能力不足,这时可以使用三极管驱动(如方式3和4)。
由此,主芯片引脚输出高电平/低电平,即可改变 LED 状态,而无需关注GPIO 引脚输出的是 3.3V 还是 1.2V。所以简称输出 1 或 0:
芯片手册一般有相关章节,用来介绍:power/clock
一个引脚可以用于 GPIO、串口、USB 或其他功能,
对于已经设置为 GPIO 功能的引脚,有方向寄存器用来设置它的方向:输出、输入
对于已经设置为 GPIO 功能的引脚,有数据寄存器用来写、读引脚电平状态;GPIO 寄存器的 2 种操作方法:
a) 要设置 bit n:
val = data_reg;
val = val | (1<<n);
data_reg = val;
b) 要清除 bit n:
val = data_reg;
val = val & ~(1<<n);
data_reg = val;
set_reg, clr_reg, data_reg 三个寄存器对应的是同一个物理寄存器,
a) 要设置 bit n:set_reg = (1<<n);
b) 要清除 bit n:clr_reg = (1<<n);//这里的置1表示该位清零,硬件内部会操作
PLL4用于给各种外设提供时钟,最先要使能PLL4;
GPIO是低速设备,我们可以先不去设置PLL4的频率;仅仅使能即可
GPIO 外设的时钟来源各自不同,具体的可以从手册当中可以看到,其中 GPIOA-K 的时钟来源为 hclk4,GPIOZ 的时钟来源为 hclk5。因此为了使用 GPIO,我们需要使能锁相环和外设 GPIO 各自对应的时钟。设置 RCC_PLL4CR 使能 hclk4 使用的时钟
RCC_PLL4CR地址:0x50000000 + 0x894
对于A7、M4而言,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 andGPIOx_AFRL)。此外,还有 GPIO 外设时钟控制寄存器。
LED的操作主要分为两个部分:
所以,打开原理图,该开发板提供两个LED模块
本次实验使用到的 GPIOA 和 GPIOG 的基地址
根据上一章中GPIO操作的方法,操作指定寄存器,完成指定功能
功能:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int major;
static struct class *led_class;
/* registers */
// RCC_PLL4CR地址:0x50000000 + 0x894
static volatile unsigned int *RCC_PLL4CR = NULL;
// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28
static volatile unsigned int *RCC_MP_AHB4ENSETR = NULL;
// GPIOA_MODER 地址:0x50002000 + 0x00
static volatile unsigned int *GPIOA_MODER = NULL;
// GPIOA_BSRR 地址: 0x50002000 + 0x18
static volatile unsigned int *GPIOA_BSRR = NULL;
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
char val;
/* copy_from_user : get data from app */
copy_from_user(&val, buf, 1);
/* to set gpio register: out 1/0 */
if (val)
{
/* set gpa10 to let led on */
*GPIOA_BSRR = (1<<26);
}
else
{
/* set gpa10 to let led off */
*GPIOA_BSRR = (1<<10);
}
return 1;
}
static int led_open(struct inode *inode, struct file *filp)
{
/* enalbe PLL4, it is clock source for all gpio */
*RCC_PLL4CR |= (1<<0);
while ((*RCC_PLL4CR & (1<<1)) == 0);
/* enable gpioA */
*RCC_MP_AHB4ENSETR |= (1<<0);
/*
* configure gpa10 as gpio
* configure gpio as output
*/
*GPIOA_MODER &= ~(3<<20);//清零
*GPIOA_MODER |= (1<<20);
return 0;
}
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__);
major = register_chrdev(0, "my_led", &led_fops);
/* ioremap(base_phy, size); */
// RCC_PLL4CR地址:0x50000000 + 0x894
RCC_PLL4CR = (volatile unsigned int *)ioremap(0x50000000 + 0x894, 4);
// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28
RCC_MP_AHB4ENSETR = (volatile unsigned int *)ioremap(0x50000000 + 0xA28, 4);
// GPIOA_MODER 地址:0x50002000 + 0x00
GPIOA_MODER = (volatile unsigned int *)ioremap(0x50002000 + 0x00, 4);
// GPIOA_BSRR 地址: 0x50002000 + 0x18
GPIOA_BSRR = (volatile unsigned int *)ioremap(0x50002000 + 0x18, 4);
led_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, "my_led");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
#include
#include
#include
#include
#include
#include
// ledtest /dev/myled on
// ledtest /dev/myled off
int main(int argc, char **argv)
{
int fd;
char status = 0;
if (argc != 3)
{
printf("Usage: %s \n" , argv[0]);
printf(" eg: %s /dev/myled on\n", argv[0]);
printf(" eg: %s /dev/myled off\n", argv[0]);
return -1;
}
// open
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %s\n", argv[0]);
return -1;
}
// write
if (strcmp(argv[2], "on") == 0)
{
status = 1;
}
write(fd, &status, 1);
return 0;
}
# 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_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ledtest ledtest.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f ledtest
obj-m += led_drv.o
makefile原理可以查看博文:驱动程序——字符设备驱动框架
在Makefile文件目录下执行make指令,此时,目录下有编译好的内核模块hello_drv.ko和可执行程序hello_drv_test,移植到开发板上
insmod /mnt/led_drv.ko //挂载驱动程序
echo none > /sys/class/leds/heartbeat/trigger // 关闭心跳灯
./ledtest /dev/myled on // 点灯
./ledtest /dev/myled off