文章目录

  • 硬件知识 LED 原理
  • GPIO 引脚操作方法
    • GPIO 模块一般结构
    • GPIO 寄存器的一般操作
  • STM32MP157的GPIO操作方法
    • 先使能PLL4
    • MPU、MCU共享GPIO模块
      • 1. 在MPU上使能某个GPIO模块
      • 2. 在MCU上使能某个GPIO模块
    • GPIO模块
    • 设置引脚工作模式:GPIO模式
    • 对于输出引脚:设置输出类型
    • 对于输出引脚:设置输出速度
    • 对于输入/输出引脚:设置上下拉电阻
    • 对于输入/输出引脚:读取引脚电平
    • 对于输出引脚:设置引脚电平,方法1
    • 对于输出引脚:设置引脚电平,方法2
  • STM32MP157的LED操作方法
  • STM32MP157点亮LED灯
    • led_drv.c
    • ledtest.c
    • Makefile
    • 编译测试

硬件知识 LED 原理

LED 的驱动方式,常见的有四种。

  • ① 使用引脚输出 3.3V 点亮 LED,输出 0V 熄灭 LED。
  • ② 使用引脚拉低到 0V 点亮 LED,输出 3.3V 熄灭 LED。
  • ③ 使用引脚输出 1.2V 点亮 LED,输出 0V 熄灭 LED。
  • ④ 使用引脚输出 0V 点亮 LED,输出 1.2V 熄灭 LED。

有的芯片为了省电等原因,其引脚驱动能力不足,这时可以使用三极管驱动(如方式3和4)。

由此,主芯片引脚输出高电平/低电平,即可改变 LED 状态,而无需关注GPIO 引脚输出的是 3.3V 还是 1.2V。所以简称输出 1 或 0:

GPIO 引脚操作方法

GPIO 模块一般结构

  • 有多组 GPIO,每组有多个 GPIO
  • 使能:电源/时钟
  • 模式(Mode):引脚可用于 GPIO 或串口或其他功能
  • 方向:引脚 Mode 设置为 GPIO 时,可以继续设置它是输出引脚,还是输入引脚
  • 数值
    • 对于输出引脚,可以设置寄存器让它输出高、低电平
    • 对于输入引脚,可以读取寄存器得到引脚的当前电平

GPIO 寄存器的一般操作

芯片手册一般有相关章节,用来介绍:power/clock

  • 可以设置对应寄存器使能某个 GPIO 模块(Module)
  • 有些芯片的 GPIO 是没有使能开关的,即它总是使能的

一个引脚可以用于 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-and-clear protocol:
set_reg, clr_reg, data_reg 三个寄存器对应的是同一个物理寄存器,a) 要设置 bit n:set_reg = (1<<n);b) 要清除 bit n:clr_reg = (1<<n);//这里的置1表示该位清零,硬件内部会操作

STM32MP157的GPIO操作方法

先使能PLL4

PLL4用于给各种外设提供时钟,最先要使能PLL4;

GPIO是低速设备,我们可以先不去设置PLL4的频率;仅仅使能即可

GPIO 外设的时钟来源各自不同,具体的可以从手册当中可以看到,其中 GPIOA-K 的时钟来源为 hclk4,GPIOZ 的时钟来源为 hclk5。因此为了使用 GPIO,我们需要使能锁相环和外设 GPIO 各自对应的时钟。设置 RCC_PLL4CR 使能 hclk4 使用的时钟

RCC_PLL4CR地址:0x50000000 + 0x894


还需要读取bit 1 ,查看PLL4是否使能

MPU、MCU共享GPIO模块

对于A7、M4而言,GPIO模块是公用的,寄存器的操作也是类似的

1. 在MPU上使能某个GPIO模块

2. 在MCU上使能某个GPIO模块

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 外设时钟控制寄存器。

设置引脚工作模式:GPIO模式

对于输出引脚:设置输出类型

对于输出引脚:设置输出速度


输出速度越快越容易对其他外设造成影响

对于输入/输出引脚:设置上下拉电阻

对于输入/输出引脚:读取引脚电平

对于输出引脚:设置引脚电平,方法1

对于输出引脚:设置引脚电平,方法2

该方法部分芯片支持,需要阅读芯片手册

STM32MP157的LED操作方法

LED的操作主要分为两个部分:

  1. 查看开发板内部的LED原理图(引脚),GPIOA的基地址
  2. 查看开发板GPIO的操作方法

所以,打开原理图,该开发板提供两个LED模块

本次实验使用到的 GPIOA 和 GPIOG 的基地址

根据上一章中GPIO操作的方法,操作指定寄存器,完成指定功能

  1. 先使能PLL4:RCC_PLL4CR地址:0x50000000 + 0x894
  2. 使能GPIOA:RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28(或RCC_MC_AHB4ENSETR地址:0x50000000 + 0xAA8)
  3. 设置PA10,用作输出:GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01
  4. 设置PA10的输出电平:
    1. 方法一:读寄存、修改值、写回去(低效):GPIOA_ODR地址: 0x50002000 + 0x14
    2. 方法二:直接写寄存器,一次操作即可,高效GPIOA_BSRR地址: 0x50002000 + 0x18

STM32MP157点亮LED灯

功能:

  • 实现 led_open 函数,在里面初始化 LED 引脚。
  • 实现 led_write 函数,在里面根据 APP 传来的值控制 LED。

led_drv.c

#include #include #include #include #include #include #include #include #include #include #include #include static int major;static struct class *led_class;/* registers */// RCC_PLL4CR地址:0x50000000 + 0x894static volatile unsigned int *RCC_PLL4CR = NULL;// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28static volatile unsigned int *RCC_MP_AHB4ENSETR = NULL;// GPIOA_MODER 地址:0x50002000 + 0x00static volatile unsigned int *GPIOA_MODER = NULL;// GPIOA_BSRR 地址: 0x50002000 + 0x18static 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 + 0x894RCC_PLL4CR =(volatile unsigned int *)ioremap(0x50000000 + 0x894, 4);// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28RCC_MP_AHB4ENSETR =(volatile unsigned int *)ioremap(0x50000000 + 0xA28, 4);// GPIOA_MODER 地址:0x50002000 + 0x00GPIOA_MODER =(volatile unsigned int *)ioremap(0x50002000 + 0x00, 4);// GPIOA_BSRR 地址: 0x50002000 + 0x18GPIOA_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");

ledtest.c

#include #include #include #include #include #include // ledtest /dev/myled on// ledtest /dev/myled offint 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;}// openfd = open(argv[1], O_RDWR);if (fd < 0){printf("can not open %s\n", argv[0]);return -1;}// 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_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin # 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,# 请参考各开发板的高级用户使用手册KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o ledtest ledtest.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f ledtestobj-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