【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED

文章目录

  • 前言
  • 1、普适的GPIO操作方法
    • 1.1、操作流程
    • 1.2、操作GPIO reg的两种方法
      • 1.2.1、一般方法--使用或运算完成位操作
      • 1.2.2支持set-and-clear protocol的方法
  • 2、STM32MP157A的GPIO操作方法
    • 2.1、STM32MP157的GPIO模块结构
      • 2.1.1、GPIO功能寄存器
      • 2.1.2、RCC--GPIO时钟寄存器
        • GPIO内核时钟来源于PLL4
        • PLL4相关的寄存器
  • 3、实验程序
    • 实验目的
    • 驱动程序
    • APP程序

前言

韦东山嵌入式Linux驱动开发基础知识学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容
视频教程地址:https://www.bilibili.com/video/BV14f4y1Q7ti

1、普适的GPIO操作方法

1.1、操作流程

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第1张图片

▲GPIO操作流程
  1. GPIO clock enable
  2. 设置引脚工作模式(推挽、开漏、浮空、上下拉…)
  3. 设置引脚方向(input/output)
  4. 设置通过GPIO_reg设置电平

1.2、操作GPIO reg的两种方法

1.2.1、一般方法–使用或运算完成位操作

set GPIO_reg bit1为1

val = data_reg;
val = val|(0x01<<1);
data_reg = val;

1.2.2支持set-and-clear protocol的方法

set GPIO_reg bit1为1
支持set-and-clear protocol的设备有两组寄存器set_regclr_reg用来对GPIO_reg进行置1和置0
set_reg:假设set_reg = 0b0000 0010 则可以将GPIO_reg的bit1置1
clr_reg:假设set_reg = 0b0000 0010 则可以将GPIO_reg的bit1置0

2、STM32MP157A的GPIO操作方法

2.1、STM32MP157的GPIO模块结构

GPIO PIN
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的寄存器可以分为功能寄存器和时钟寄存器

2.1.1、GPIO功能寄存器

Register Function
配置寄存器
GPIOx_MODER 配置x组GPIO的工作模式
GPIOx_OTYPER 配置x组GPIO输出模式(推挽/开漏)
GPIOx_OSPEEDR 配置x组GPIO的输出速度
GPIOx_PUPDR 配置x组GPIO的上下拉
数据寄存器
GPIOx_IDR 包含x组GPIO的输入数据,该寄存器只读
GPIOx_ODR 包含x组GPIO的输出数据,该寄存器可读写
GPIOx_BSRR 包含x组GPIO的set和reset数据,可以借助该寄存器直接完成位操作,该寄存器只写
锁定寄存器
GPIOx_LCKR 用于锁定x组GPIO配置寄存器和锁定自己
多功能选择寄存器
GPIOx_AFRH 配置x组GPIO的pin8~15的引脚复用功能
GPIOx_AFRL 配置x组GPIO的pin0~7的引脚复用功能
▲GPIO 功能寄存器列表

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第2张图片

▲GPIOx_LCKR 寄存器

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第3张图片

▲GPIOx_OTYPER 寄存器

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第4张图片

▲GPIOx_OSPEEDER 寄存器

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第5张图片

▲GPIOx_PUPDR 寄存器

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第6张图片

▲GPIOx_IDR 寄存器

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第7张图片

▲GPIOx_ODR 寄存器

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第8张图片

▲GPIOx_BSRR 寄存器

这里要详细介绍一下GPIOx_LCKR寄存器,因为是初见
【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第9张图片【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第10张图片

▲GPIOx_LCKR 寄存器

  当正确的写入序列应用于位 16 (LCKK) 时,该寄存器用于锁定端口位的配置。位 [15:0] 的值用于锁定 GPIO 的配置。在写序列期间,LCKR[15:0] 的值不得改变。当 LOCK 序列已应用于端口位时,该端口位的值不能再修改,直到下一次 MCU 复位或外设复位

  注意:特定的写入序列用于写入 GPIOx_LCKR 寄存器。在此锁定序列期间仅允许字访问(32 位长)。
每个锁定位冻结一个特定的配置寄存器(控制和备用功能寄存器)

Bit 16 LCKK:锁定键
  该位可随时读取。它只能使用锁定密钥写入序列进行修改。
    0:端口配置锁定键未激活
    1:端口配置锁定键激活。 GPIOx_LCKR 寄存器被锁定,直到下一次 MCU 复位或外设复位。
    LOCK键写入顺序:
      写 LCKR[16] = ‘1’ + LCKR[15:0]
      写 LCKR[16] = ‘0’ + LCKR[15:0]
      写 LCKR[16] = ‘1’ + LCKR[15:0]
      读LCKR
      读 LCKR[16] = ‘1’(此读取操作是可选的,但它确认锁处于活动状态)
  注意:在 LOCK 键写入序列期间,LCK[15:0] 的值不得更改。锁定序列中的任何错误都会中止锁定。
在端口的任何位上的第一个锁定序列之后,对 LCKK 位的任何读访问都将返回“1”,直到下一次 MCU 复位或外设复位。

Bits 15:0 LCK[15:0]:端口 x 锁定 I/O 引脚 y(y = 15 到 0)
  这些位是读 / 写的,但只能在 LCKK 位为 0 时写入。
    0:端口配置未锁定
    1:端口配置锁定

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第11张图片
【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第12张图片

▲GPIOx_AFRL 寄存器

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第13张图片

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第14张图片

▲GPIOx_AFRH 寄存器

下面是本次要使用的GPIOA和GPIOG的基地址
【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第15张图片

▲GPIO基地址

2.1.2、RCC–GPIO时钟寄存器

GPIO内核时钟来源于PLL4

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第16张图片

▲STM32MP157A/D block diagram

从上面的逻辑框图可以看出GPIOA~K的时钟来源于MLAHB-AHB总线GPIOZ的时钟来源于AXIM-AHB总线
进一步搜索相关的clock source ,GPIOA~K的时钟来源于hclk4,GPIOZ的时钟来源于hclk5,其时钟频率上可以看出其和总线的对应关系

外设时钟分布
  一些外设设计为与两个彼此异步的不同时钟域一起工作:一个与寄存器和总线接口同步的时钟域(ckg_bus_perx 时钟),以及一个通常与外设同步的时钟域(内核时钟)。
  外设支持这两个时钟域的好处是用户可以更自由地为 CPU、总线矩阵和外设的内核部分选择优化的时钟频率。
因此,用户无需重新编程外设即可更改总线频率。例如,如果实时更改其 APB 时钟,则使用 UART 进行的传输不会受到干扰。
对于大多数外设,表 56 显示了 RCC 控制下的外设时钟。请注意,外围设备本身控制的时钟未显示(例如来自外部接口和外围设备的时钟)。表 56 清楚地显示(灰色阴影单元)由 RCC 提供的内核时钟,以及控制内核时钟选择器的位字段。为每个 MUX 的输出和每个总线时钟给出了最大允许频率。
  尊重这些要求取决于用户。
也就是说hclk4,hclk5是提供给BUS的时钟源而非kernel,现在我们需要找到kernel中GPIO的时钟来源,因为我们要使用MPU核心而非MCU核心,hclk4时钟源是来源于PLL3提供给MCU使用的,所以需要接着寻找kernel中GPIO的时钟来源
【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第17张图片

▲Peripherals-clock source

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第18张图片

▲hclk4来源于PLL3

看到手册的RCC(Reset and clock control)模块,下面是RCC的逻辑框图
【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第19张图片

▲RCC的逻辑框图

在这里可以找到PLL1~4分别为哪些设备提供了时钟源,不难看出GPIO的kernel时钟源来自于PLL4
【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第20张图片

▲PLL1~PLL4

RCC 提供多达 4 个 PLL,每个 PLL 都可以配置为整数或小数比率。
  • PLL1 专用于 MPU 时钟
  • PLL2 提供:
    – AXI-SS 的时钟(包括 APB4、APB5、AHB5 和 AHB6 桥)
    – DDR 接口的时钟
    – GPU 的时钟
  • PLL3 提供:
    – MCU 的时钟及其总线矩阵(包括 APB1、APB2、APB3、AHB1、AHB2、AHB3 和 AHB4)
    – 外围设备的内核时钟
  • PLL4 专用于为各种外设生成内核时钟

PLL4相关的寄存器

Register Function
RCC_PLL4CR PLL分频器及输出的使能和锁定
RCC_MP_AHB4ENSETR 使能GPIO的时钟
▲PLL4相关的寄存器

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第21张图片
【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第22张图片

▲RCC_PLL4CR

【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第23张图片

▲RCC_MP_AHB4ENSETR

3、实验程序

实验目的

操作PA10,点亮LED2
【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED_第24张图片

▲开发板原理图--用户LED

编码步骤

  1. RCC_PLL4CR 打开PLL4分频器
  2. RCC_MP_AHB4ENSETR 使能GPIOA
  3. GPIOA_MODER GPIOx_OTYPER GPIOx_OSPEEDR GPIOx_PUPDR
      设置GPIOA_PIN10为输出模式
      设定输出模式
      设定输出速度
      设定上下拉
  4. GPIOA_ODR/GPIOA_BSRR 设定GPIOA_PIN10输出值

程序分析

驱动程序

有一个问题:怎么操纵寄存器?
在Linux系统中存在内存映射的函数只要提供寄存器地址就可以将寄存器映射到变量,所以只需要对变量进行位操作就可以了
要参考的文章:
ioremap函数
【C语言】关键字volatile之有关__IO、__O、__I的故事

比如要完成对RCC_PLL4CR的bit1

// RCC_PLL4CR地址:0x50000000 + 0x894
static volatile unsigned int *RCC_PLL4CR;
/* ioremap(base_phy, size); */
// RCC_PLL4CR地址:0x50000000 + 0x894
RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);
/* enalbe PLL4, it is clock source for all gpio */
*RCC_PLL4CR |= (1<<0);
iounmap(RCC_PLL4CR);

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 + 0x894
static volatile unsigned int *RCC_PLL4CR;

// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28
static volatile unsigned int *RCC_MP_AHB4ENSETR;

// GPIOA_MODER 地址:0x50002000 + 0x00
static volatile unsigned int *GPIOA_MODER;

// GPIOA_BSRR 地址: 0x50002000 + 0x18
static volatile unsigned int *GPIOA_BSRR;

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 
	 * 01:General purpose output mode
	 */
	*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 %d\n", __FUNCTION__, __LINE__);
	
	major = register_chrdev(0, "100ask_led", &led_fops);

	/* ioremap(base_phy, size); */
	// RCC_PLL4CR地址:0x50000000 + 0x894
	RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);
	
	// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28
	RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);
	
	// GPIOA_MODER 地址:0x50002000 + 0x00
	GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);
	
	// GPIOA_BSRR 地址: 0x50002000 + 0x18
	GPIOA_BSRR = 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, "100ask_led");
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

APP程序

ledtest.c

#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;	
}

关闭心跳灯:echo none > /sys/class/leds/heartbeat/trigger

你可能感兴趣的:(#,嵌入式Linux,linux,STM32MP157,驱动开发)