STM32MP157(直接操作寄存器点灯)
如何通过寄存器方式点灯
linux下是不与寄存器寄存器打交道的,但作为开篇第一个实验体验一下驱动开发就先以直接操作寄存器方式实现
首先想要操作一个GPIO设备必然需要操作总线时钟、配置模式(中断/输入/输出模等模式式、上拉/浮空/下拉等模式),和裸机开发一样。
相关寄存器
GPIOI端口时钟寄存器
假设控制板载LED0 即为PI0引脚操作寄存器就会涉及到地址,我们通过查阅STM32MP157手册可得知GPIOI的基地址为0x5000A000为起始地址,同时该外设隶属于AHB4总线下
在设置GPIOI端口寄存器时还需使能AHB4总线下具体外设寄存器时钟RCC_MP_AHB4ENSETR,通过查看手册可得知该寄存器地址为0xA28即AHB4总线地址偏移0xA28,已知AHB4基地址为0x50000000,则RCC_MP_AHB4ENSETR寄存器地址值为0x50000A28
GPIO模式寄存器 GPIOX_MODER
通过查阅手册可知GPIOX_MODER寄存器地址为0x00,则GPIOI_MODE地址即为GPIOI总线起始地址加上偏移地址0x00即为0x5000A000
GPIO输出模式寄存器 GPIOx_OTYPER
模式 | 对应关系 |
---|---|
0 | 推挽输出 |
1 | 开漏输出 |
一共32位,只有低16位有效,[15:0]对应GPIO15-0 | |
GPIOx_OTYPER地址为0x04所以GPIOI_OTYPER地址为0x5000A000+0x04即0x5000A004,这里我们设置为推挽输出即可 |
GPIO速度模式寄存器 GPIOx_OSPEEDR
模式 | 对应关系 |
---|---|
00 | 低速 |
01 | 中等速度 |
10 | 高速 |
11 | 超高速 |
GPIOx_OSPEEDR地址为0x08所以GPIOI_OSPEEDR地址为0x5000A000+0x08即0x5000A008,这里我们设置为中等速度即可也就是01模式
GPIO上拉/下拉寄存器 GPIOx_PUPDR
寄存器为32位,每一个GPIO口对应两个位刚刚好一共15组
模式 | 对应关系 |
---|---|
00 | 不上拉也不下拉 |
01 | 上拉 |
10 | 下拉 |
11 | 暂时没用 |
GPIOx_PURDR地址为0x0C所以GPIOI_PURDR地址为0x5000A000+0x0C即0x5000A00C
GPIO设置电平寄存器 GPIOx_BSRR
GPIOx_BSRR地址为0x18所以GPIOI_BSRR地址为0x5000A000+0x18即0x5000A018
地址[15:0]是复位引脚的对应GPIO15-GPIO0,[31:16]是置位引脚对应GPIO15-GPIO0
控制流程
执行流程如下流程所示
初始化时钟->初始化GPIO口->设置GPIO口速度->设置GPIO口电平
在linux里面操作地址需要获取虚拟地址,所以关于地址的申请获取需要使用ioremap函数,当然有申请就会有注销对应的函数为iounmap函数
I/O内存访问函数
读操作函数
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
写操作函数
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
代码编写
驱动代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LED_Major 200
#define LED_Name "LED_Device"
#define License "GPL"
#define Author "shishukeya"
#define GPIOI_CLK 0X50000000
#define GPIOI_BASE 0x5000A000
#define RCC_MP_AHB4ENSETR (GPIOI_CLK+0xA28) //时钟地址
#define GPIOI_MODER (GPIOI_BASE+0x00) //模式地址
#define GPIOI_OTYPER (GPIOI_BASE+0x04) //输出模式地址
#define GPIOI_OSPEEDR (GPIOI_BASE+0x08) //速度地址
#define GPIOI_PUPDR (GPIOI_BASE+0x0C) //上下拉地址
#define GPIOI_BSRR (GPIOI_BASE+0x18) //置位/复位地址
static void __iomem *RCC_MP_AHB4ENSETR_Virtual;
static void __iomem *GPIOI_MODE_Virtual;
static void __iomem *GPIOI_OTYPER_Virtual;
static void __iomem *GPIOI_OSPEEDR_Virtual;
static void __iomem *GPIOI_PUPDR_Virtual;
static void __iomem *GPIOI_BSRR_Virtual;
static int led_open(struct inode *inode, struct file *filp)
{
int res = 0;
return res;
}
static int led_release(struct inode *inode, struct file *filp)
{
int res = 0;
return res;
}
/*
* @description : LED初始化,申请虚拟地址
* @param - NULL : NULL
* @param - NULL : NULL
* @param - NULL : NULL
* @param - NULL : NULL
* @return : 写入的字节数,如果为负值,表示写入失败
*/
void Led_Init(void)
{
u32 ahb4_clock_val = 0,mode_val = 0,otyper_val = 0,ospeed_val,pupdr_val = 0,bsrr_val = 0;
RCC_MP_AHB4ENSETR_Virtual = ioremap(RCC_MP_AHB4ENSETR,4);
GPIOI_MODE_Virtual = ioremap(GPIOI_MODER,4);
GPIOI_OTYPER_Virtual = ioremap(GPIOI_OTYPER,4);
GPIOI_PUPDR_Virtual = ioremap(GPIOI_PUPDR,4);
GPIOI_OSPEEDR_Virtual = ioremap(GPIOI_OSPEEDR,4);
GPIOI_BSRR_Virtual = ioremap(GPIOI_BSRR,4);
//初始化时钟
ahb4_clock_val = readl(RCC_MP_AHB4ENSETR_Virtual);
ahb4_clock_val &= ~(1 << 8);
ahb4_clock_val |= (1 << 8);
writel(ahb4_clock_val,RCC_MP_AHB4ENSETR_Virtual);
//设置为输出模式
mode_val = readl(GPIOI_MODE_Virtual);
mode_val &= ~(0x03 << 0);
mode_val |= (0x01 << 0);
writel(mode_val,GPIOI_MODE_Virtual);
//设置为推挽输出
otyper_val = readl(GPIOI_OTYPER_Virtual);
otyper_val &= 0xFFFFFFFE;
writel(otyper_val,GPIOI_OTYPER_Virtual);
//设置GPIO速度
ospeed_val = readl(GPIOI_OSPEEDR_Virtual);
mode_val &= ~(0x03 << 0);
ospeed_val |= 0x01;
writel(ospeed_val,GPIOI_OSPEEDR_Virtual);
//设置GPIO不上拉也不下拉
pupdr_val = readl(GPIOI_PUPDR_Virtual);
pupdr_val &= 0xFFFFFFFC;
writel(pupdr_val,GPIOI_PUPDR_Virtual);
//初始化关闭LED灯
bsrr_val = readl(GPIOI_BSRR_Virtual);
bsrr_val |= 0x01;
writel(bsrr_val,GPIOI_BSRR_Virtual);
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
int res=0;
char led_val[1] ={0};
u32 set_val = 0;
res = copy_from_user(led_val,buf,cnt);
if(res >= 0)
{
if(led_val[0] == '0')
{
set_val = readl(GPIOI_BSRR_Virtual);
set_val &= 0xFFFFFFFE;
set_val |= (0x1 << 16);
writel(set_val,GPIOI_BSRR_Virtual);
}
else if(led_val[0] == '1')
{
set_val = readl(GPIOI_BSRR_Virtual);
set_val &= 0xFFFEFFFF;
set_val |= 0x01;
writel(set_val,GPIOI_BSRR_Virtual);
}
}
else
{
printk("write led error\r\n");
}
return res;
}
void led_unmap(void)
{
iounmap(RCC_MP_AHB4ENSETR_Virtual);
iounmap(GPIOI_MODE_Virtual);
iounmap(GPIOI_OTYPER_Virtual);
iounmap(GPIOI_PUPDR_Virtual);
iounmap(GPIOI_OSPEEDR_Virtual);
iounmap(GPIOI_BSRR_Virtual);
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt,loff_t *offt)
{
int res = 0;
return res;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
.open = led_open,
.release = led_release,
};
static int __init led_init(void)
{
int res = 0;
Led_Init();
res = register_chrdev(LED_Major, LED_Name, &led_fops);
if(res < 0)
{
printk("led init error\r\n");
goto fail_map;
}
printk("led init\r\n");
return res;
fail_map:
led_unmap();
return -1;
}
static void __exit led_exit(void)
{
unregister_chrdev(LED_Major, LED_Name);
printk("led exit\r\n");
}
module_init(led_init); //驱动注册函数
module_exit(led_exit); //驱动卸载函数
MODULE_LICENSE(License);
MODULE_AUTHOR(Author);
MODULE_INFO(intree,"Y");
应用层代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include "string.h"
#include <stdlib.h>
int main(int argc,char *argv[])
{
int fp,res;
unsigned char buff[1]={0};
if(argc !=3)
{
printf("missing parameter\r\n");
return -1;
}
else
{
/* 打开文件 */
fp = open(argv[1],O_RDWR); //打开制定文件
if(fp != 0)
{
// printf("open %s file success\r\n",argv[1]);
if(atoi(argv[2]) == 1)
{
res = write(fp,"1",1);
if(res < 0)
{
printf("user write error\r\n");
}
}
else if(atoi(argv[2]) == 0)
{
res = write(fp,"0",1);
if(res < 0)
{
printf("user write error\r\n");
}
}
res = close(fp);
if(res == 0)
{
// printf("close %s file success\r\n",argv[1]);
}
else
{
printf("close %f file error\r\n",argv[1]);
return -1;
}
}
else
{
printf("open %s file error\r\n",argv[1]);
return -1;
}
}
return 0;
}
验证实验
先编译代码,在文件夹路径下执行make
shishukeya@ubuntu:~/linux/Linux_Drivers/02_regled$ make
编译应用层代码
shishukeya@ubuntu:~/linux/Linux_Drivers/02_regled$ arm-none-linux-gnueabihf-gcc LedAPP.c -o APP
将应用层以及驱动拷贝到开发板根文件系统下
shishukeya@ubuntu:~/linux/Linux_Drivers/02_regled$ sudo cp APP led.ko /home/shishukeya/linux/nfs/rootfs/ -f
去开发板挂载驱动
[root@ATK-stm32mp1]:/$ insmod led.ko
手动创建节点
[root@ATK-stm32mp1]:/$ mknod /dev/led c 200 0
执行代码观察现象
[root@ATK-stm32mp1]:/$ ./APP /dev/led 0
可以看到我们输入命令让GPIO输出低电平对应原理图低电平点亮,开发板实际灯亮起
[root@ATK-stm32mp1]:/$ ./APP /dev/led 1
输入1使GPIO引脚输出高电平对应原理图熄灭LED,开发板实际也是熄灭