概述
文章目录
- 前言
- 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、操作流程
▲GPIO操作流程
- GPIO clock enable
- 设置引脚工作模式(推挽、开漏、浮空、上下拉…)
- 设置引脚方向(input/output)
- 设置通过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_reg
,clr_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 功能寄存器列表
▲GPIOx_LCKR 寄存器
▲GPIOx_OTYPER 寄存器
▲GPIOx_OSPEEDER 寄存器
▲GPIOx_PUPDR 寄存器
▲GPIOx_IDR 寄存器
▲GPIOx_ODR 寄存器
▲GPIOx_BSRR 寄存器
这里要详细介绍一下GPIOx_LCKR寄存器,因为是初见
▲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:端口配置锁定
▲GPIOx_AFRL 寄存器
▲GPIOx_AFRH 寄存器
下面是本次要使用的GPIOA和GPIOG的基地址
▲GPIO基地址
2.1.2、RCC–GPIO时钟寄存器
GPIO内核时钟来源于PLL4
▲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的时钟来源
▲Peripherals-clock source
▲hclk4来源于PLL3
看到手册的RCC(Reset and clock control)模块,下面是RCC的逻辑框图
▲RCC的逻辑框图
在这里可以找到PLL1~4分别为哪些设备提供了时钟源,不难看出GPIO的kernel时钟源来自于PLL4
▲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相关的寄存器
▲RCC_PLL4CR
▲RCC_MP_AHB4ENSETR
3、实验程序
实验目的
操作PA10,点亮LED2
▲开发板原理图--用户LED
编码步骤
- RCC_PLL4CR 打开PLL4分频器
- RCC_MP_AHB4ENSETR 使能GPIOA
- GPIOA_MODER GPIOx_OTYPER GPIOx_OSPEEDR GPIOx_PUPDR
设置GPIOA_PIN10为输出模式
设定输出模式
设定输出速度
设定上下拉 - 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 <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;
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 %dn", __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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
// 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 <dev> <on|off>n", argv[0]);
printf(" eg: %s /dev/myled onn", argv[0]);
printf(" eg: %s /dev/myled offn", argv[0]);
return -1;
}
// open
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %sn", 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驱动开发基础知识之操纵GPIO点亮LED前言1、普适的GPIO操作方法2、STM32MP157A的GPIO操作方法3、实验程序的全部内容,希望文章能够帮你解决【嵌入式Linux】嵌入式Linux驱动开发基础知识之操纵GPIO点亮LED前言1、普适的GPIO操作方法2、STM32MP157A的GPIO操作方法3、实验程序所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复