当前位置: 首页 > news >正文

【嵌入式Linux学习笔记】基于Linux官方库的标准外设驱动

在这里插入图片描述

对于标准的外设如LED,KEY,PWM等,以及标准通信协议,Linux都自带有标准的驱动库,不需要我们自行编写,只需要配置好相应的GPIO属性和电气属性,即可匹配相应的驱动,在应用程序中直接使用相应的外设。

学习视频地址:【正点原子】STM32MP157开发板

驱动流程

  1. 使能Linux内核相关库,make menuconfig
  2. 编写GPIO电气属性,文件路径:Linux内核路径/arch/arm/boot/dts/stm32mp15-pinctrl.dtsi
  3. 添加设备树节点,匹配属性及驱动,文件路径:Linux内核路径/arch/arm/boot/dts/stm32mp157d-atk.dts
  4. 编写相应的应用程序或者直接命令行调用

LED

驱动使能
在这里插入图片描述
在这里插入图片描述

电气属性

/* gpio led config */
led_pins_a: gpioled-0 {pins {pinmux = <STM32_PINMUX('I', 0, GPIO)>, /* LED0 */<STM32_PINMUX('F', 3, GPIO)>; /* LED1 */drive-push-pull;bias-pull-up;output-high;slew-rate = <0>;};
};

设备树节点

gpio-leds{compatible = "gpio-leds";pinctrl-0 = <&led_pins_a>;led0 {label = "red";gpios = <&gpioi 0 GPIO_ACTIVE_LOW>;linux,default-trigger = "heartbeat";default-state = "on";};led1 {label = "green";gpios = <&gpiof 3 GPIO_ACTIVE_LOW>;default-state = "off";};}; 

应用指令

echo 1 > /sys/class/leds/red/brightness #打开 LED0
echo 0 > /sys/class/leds/red/brightness #关闭 LED0

KEY

驱动使能
在这里插入图片描述

电气属性

/* gpio key config */key_pins_a: key_pins-0 {pins1 {pinmux = <STM32_PINMUX('G', 3, GPIO)>,	/* KEY0 */<STM32_PINMUX('H', 7, GPIO)>;	/* KEY1 */bias-pull-up;slew-rate = <0>;};pins2 {pinmux = <STM32_PINMUX('A', 0, GPIO)>;	/* WK_UP */bias-pull-down;slew-rate = <0>;};};

设备树节点

gpio-keys{compatible = "gpio-keys";pinctrl-names = "default";pinctrl-0 = <&key_pins_a>;autorepeat;  /* 支持连按 */key0{label = "GPIO KEY L";linux,code = <KEY_L>;gpios = <&gpiog 3 GPIO_ACTIVE_LOW>;};key1{label = "GPIO KEY S";linux,code = <KEY_L>;gpios = <&gpioh 7 GPIO_ACTIVE_LOW>;};wkup{label = "GPIO KEY Enter";linux,code = <KEY_ENTER>;gpios = <&gpioa 0 GPIO_ACTIVE_HIGH>;gpio-key,wakeup;};
};

应用指令

hexdump /dev/input/event0 #查看按键事件的原始值

PWM

驱动使能
在这里插入图片描述
电气属性

/* pwm1 config */
pwm1_pins_a: pwm1-0 {pins {pinmux = <STM32_PINMUX('A', 10, AF1)>; /* TIM1_CH3 */bias-pull-down;drive-push-pull;slew-rate = <0>;};
};pwm1_sleep_pins_a: pwm1-sleep-0 {pins {pinmux = <STM32_PINMUX('A', 10, ANALOG)>; /* TIM1_CH3 */};
};

设备树节点

/* 官方文件中已有定时器节点,但是默认是失能的,所以需要在应用时追加定时器配置 */
&timers1 {status = "okay";/* spare all DMA channels since they are not needed for PWM output *//delete-property/dmas;/delete-property/dma-names;pwm1: pwm {pinctrl-0 = <&pwm1_pins_a>;pinctrl-1 = <&pwm1_sleep_pins_a>;pinctrl-names = "default", "sleep";#pwm-cells = <2>;status = "okay";};
};

检验

因为我们需要调用pwm下的pwmchipx来控制某路PWM,所以我们需要先确认pwmchipx是否对应我们使用的定时器timer。

cd pwmchip0

这时会打印出以下信息,我们需要匹配定时器的寄存器地址以及该pwm设备的寄存器地址。可以看到,地址都是44000000,说明这个pwm设备对应的定时器就是timer1。
在这里插入图片描述在这里插入图片描述
应用指令

echo 2 > /sys/class/pwm/pwmchip0/export 				# 开启TIM1_CH3
echo 50000 > /sys/class/pwm/pwmchip0/pwm2/period 		# 设置周期值为50000ns
echo 10000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle 	# 设置一个周期值中高电平的时间,即间接设置占空比
echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable 			# 使能PWM,需要设置好周期和占空比再使能,否则会报错
echo "inversed" > /sys/class/pwm/pwmchip0/pwm2/polarity #极性反转
echo "normal" > /sys/class/pwm/pwmchip0/pwm2/polarity 	#极性恢复

I2C

I2C子系统架构
在这里插入图片描述I2C适配器驱动SOC厂商已经写好了,所以我们只需要写I2C设备驱动即可。
电气属性

i2c5_pins_a: i2c5-0 {pins {pinmux = <STM32_PINMUX('A', 11, AF4)>, 	/* I2C5_SCL */<STM32_PINMUX('A', 12, AF4)>; 		/* I2C5_SDA */bias-disable;drive-open-drain;slew-rate = <0>;};
};i2c5_pins_sleep_a: i2c5-1 {pins {pinmux = <STM32_PINMUX('A', 11, ANALOG)>, 	/* I2C5_SCL */<STM32_PINMUX('A', 12, ANALOG)>; 		/* I2C5_SDA */};
};

设备树节点
以正点原子STM32MP1开发板上的AP3216C传感器为例,它挂载在I2C5总线上。

&i2c5 {pinctrl-names = "default", "sleep";pinctrl-0 = <&i2c5_pins_a>;pinctrl-1 = <&i2c5_pins_sleep_a>;status = "okay";/* 1e代表器件地址 */ap3216c@1e {compatible = "alientek,ap3216c";reg = <0x1e>;};
};

驱动代码
下面的两个函数是i2c设备最主要的数据读写函数,设备的驱动就在这个基础上通过对不同寄存器的读写实现。i2c总线设备的操作与platform总线类似。

/** @description	: 从ap3216c读取寄存器数据* @param - dev:  ap3216c设备* @param - reg:  要读取的寄存器首地址* @param - val:  读取到的数据* @param - len:  要读取的数据长度* @return 		: 操作结果*/
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->client;/* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr;			/* ap3216c地址 */msg[0].flags = 0;					/* 标记为发送数据 */msg[0].buf = &reg;					/* 读取的首地址 */msg[0].len = 1;						/* reg长度*//* msg[1]读取数据 */msg[1].addr = client->addr;			/* ap3216c地址 */msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/msg[1].buf = val;					/* 读取数据缓冲区 */msg[1].len = len;					/* 要读取的数据长度*/ret = i2c_transfer(client->adapter, msg, 2);if(ret == 2) {ret = 0;} else {printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);ret = -EREMOTEIO;}return ret;
}/** @description	: 向ap3216c寄存器写入数据* @param - dev:  ap3216c设备* @param - reg:  要写入的寄存器首地址* @param - val:  要写入的数据缓冲区* @param - len:  要写入的数据长度* @return 	  :   操作结果*/
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)dev->client;b[0] = reg;					/* 寄存器首地址 */memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */msg.addr = client->addr;	/* ap3216c地址 */msg.flags = 0;				/* 标记为写数据 */msg.buf = b;				/* 要写入的数据缓冲区 */msg.len = len + 1;			/* 要写入的数据长度 */return i2c_transfer(client->adapter, &msg, 1);
}

SPI

SPI总线的操作与I2C总线操作相似。
电气属性

spi1_pins_a: spi1-0 {pins1 {pinmux = <STM32_PINMUX('Z', 0, AF5)>, /* SPI1_SCK */<STM32_PINMUX('Z', 2, AF5)>; /* SPI1_MOSI */bias-disable;drive-push-pull;slew-rate = <1>;};pins2 {pinmux = <STM32_PINMUX('Z', 1, AF5)>; /* SPI1_MISO */bias-disable;};pins3 {pinmux = <STM32_PINMUX('Z', 3, GPIO)>; /* SPI1_NSS */bias-pull-up;drive-push-pull;output-high;slew-rate = <0>;};
};spi1_sleep_pins_a: spi1-sleep-0 {pins {pinmux = <STM32_PINMUX('Z', 0, ANALOG)>, /* SPI1_SCK */<STM32_PINMUX('Z', 1, ANALOG)>, /* SPI1_MISO */<STM32_PINMUX('Z', 2, ANALOG)>; /* SPI1_MOSI */<STM32_PINMUX('Z', 3, ANALOG)>; /* SPI1_NSS */};
};

设备树节点

&spi1 {pinctrl-names = "default", "sleep";pinctrl-0 = <&spi1_pins_a>;pinctrl-1 = <&spi1_sleep_pins_a>;cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>;status = "okay";spidev: icm20608@0 {compatible = "alientek,icm20608";reg = <0>; /* CS #0 */spi-max-frequency = <8000000>;};
};

驱动代码

/** @description	: 从icm20608读取多个寄存器数据* @param - dev:  icm20608设备* @param - reg:  要读取的寄存器首地址* @param - val:  读取到的数据* @param - len:  要读取的数据长度* @return 		: 操作结果*/
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{int ret = -1;unsigned char txdata[1];unsigned char * rxdata;struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->spi;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */if(!t) {return -ENOMEM;}rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);	/* 申请内存 */if(!rxdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,一共要读取len个字节长度的数据,*/txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 */			t->tx_buf = txdata;			/* 要发送的数据 */t->rx_buf = rxdata;			/* 要读取的数据 */t->len = len+1;				/* t->len=发送的长度+读取的长度 */spi_message_init(&m);		/* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m);	/* 同步发送 */if(ret) {goto out2;}memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */out2:kfree(rxdata);					/* 释放内存 */
out1:	kfree(t);						/* 释放内存 */return ret;
}/** @description	: 向icm20608多个寄存器写入数据* @param - dev:  icm20608设备* @param - reg:  要写入的寄存器首地址* @param - val:  要写入的数据缓冲区* @param - len:  要写入的数据长度* @return 	  :   操作结果*/
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{int ret = -1;unsigned char *txdata;struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->spi;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */if(!t) {return -ENOMEM;}txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);if(!txdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,len为要写入的寄存器的集合,*/*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */memcpy(txdata+1, buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */t->tx_buf = txdata;			/* 要发送的数据 */t->len = len+1;				/* t->len=发送的长度+读取的长度 */spi_message_init(&m);		/* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m);	/* 同步发送 */if(ret) {goto out2;}out2:kfree(txdata);				/* 释放内存 */
out1:kfree(t);					/* 释放内存 */return ret;
}

UART/RS232/RS485

Linux也提供了串口相关的驱动库供我们使用。

usart3_pins_c: usart3-2 {pins1 {pinmux = <STM32_PINMUX('D', 8, AF7)>; /* USART3_TX */bias-disable;drive-push-pull;slew-rate = <0>;};pins2 {pinmux = <STM32_PINMUX('D', 9, AF7)>; /* USART3_RX */bias-disable;};
};usart5_pins_a: usart5-0 {pins1 {pinmux = <STM32_PINMUX('B', 13, AF14)>; /* USART5_TX */bias-disable;drive-push-pull;slew-rate = <0>;};pins2 {pinmux = <STM32_PINMUX('B', 12, AF14)>; /* USART5_RX */bias-disable;};
};

设备树节点
aliases节点用于设置串口别名,串口驱动会读取aliases节点数据,表示在系统启动生成一个名为“/dev/ttySTM0”的设备文件,serial1就会生成“/dev/ttySTM1”如此类推,最多 8 个。

# 这个在根节点下
/{
......aliases {serial0 = &uart4;serial1 = &uart5;serial2 = &usart3;};
......
};&usart3 {pinctrl-names = "default";pinctrl-0 = <&usart3_pins_c>;status = "okay";
};&uart5 {pinctrl-names = "default";pinctrl-0 = <&usart5_pins_a>;status = "okay";
};

串口软件移植
minicom 类似我们常用的串口调试助手,是 Linux 下很常用的一个串口工具,builroot里面已经集成了这个软件,所以只需重新配置builroot就行。
在这里插入图片描述在这里插入图片描述

sudo make # 保持上述配置后,进行编译
cd output/images/  #进入到 output/images 目录
sudo tar -axvf rootfs.tar -C /home/jozenlee/arm_linux/nfs/rootfs #解压到 nfsroot 目录

minicom配置

minicom -s #打开配置界面

CAN

STM32MP1提供的是功能更强大的FDCAN控制器,支持数据高速传输,并且数据量从8位加到了64位
电气属性

m_can1_pins_a: m-can1-0 {pins1 {pinmux = <STM32_PINMUX('H', 13, AF9)>; /* CAN1_TX */slew-rate = <1>;drive-push-pull;bias-disable;};pins2 {pinmux = <STM32_PINMUX('I', 9, AF9)>; /* CAN1_RX */bias-disable;};
};m_can1_sleep_pins_a: m_can1-sleep-0 {pins {pinmux = <STM32_PINMUX('H', 13, ANALOG)>, /* CAN1_TX */<STM32_PINMUX('I', 9, ANALOG)>; /* CAN1_RX */};
};

设备树节点

&m_can1 {pinctrl-names = "default", "sleep";pinctrl-0 = <&m_can1_pins_a>;pinctrl-1 = <&m_can1_sleep_pins_a>;status = "okay";
};

软件移植
iproute2
在这里插入图片描述在这里插入图片描述
can-utils
在这里插入图片描述
应用指令

# 设置 can0 速度为 1000Kbit/s,使用的是一般模式
ip link set can0 type can bitrate 1000000 # 设置 can0 速度为 1000Kbit/s,数据波特率为 5000 kBit/s,使用FDCAN
ip link set can0 up type can bitrate 1000000 dbitrate 5000000 fd on
ip link set can0 up type can bitrate 200000 dbitrate 1000000 fd on
ip link set can0 up type can bitrate 100000 dbitrate 500000 fd on# 打开can0
ifconfig can0 up# 关闭can0
ifconfig can0 down# 接收数据
candump can0# 发送数据
cansend can0 5A1#11.22.33.44.55.66.77.88
http://www.lryc.cn/news/43422.html

相关文章:

  • 网络爬虫抓包工具
  • 蓝桥杯倒计时 | 倒计时17天
  • 【Spring Cloud Alibaba】7.Sentinel熔断器仪表盘监控
  • 个人博客系统项目测试报告
  • flutter安装自用笔记
  • tomcat线程池以及在SpringBoot中的启动过程
  • 第十四届中国大学生创新创业大赛
  • LeetCode:322. 零钱兑换——动态规划从案例入门
  • 【lwIP(第四章)】网络接口
  • Vue3 pinia入门篇(一)
  • python面向对象编程解释
  • ARM(IMX6U)嵌入式软件裸机开发之环境搭建与配置
  • Java文件复制多种方法
  • Java语言-----封装、继承、抽象、多态、接口
  • 基于深度学习的瓶子检测软件(UI界面+YOLOv5+训练数据集)
  • 仿网易云小程序(一)
  • 【C++】vector模拟实现及其应用
  • JS看这一篇就够啦,JS基础大全,可用于快速回顾知识,面试首选
  • 武汉凯迪正大GB4208外壳防护等级试具
  • Cent OS 从零部署ruoyi-cloud教程
  • ChatGPT相关核心算法
  • Python导入模块,Python import用法(超级详细)
  • 大量产品“GPT 化”,开源大模型 AI 应用开发框架发布
  • STM32——IIC总线(MPU6050应用)
  • ADB使用经验
  • 详解LinkedHashSet和LinkedHashMap
  • C++ LinuxWebServer 2万7千字的面经长文(下)
  • RK3568平台开发系列讲解(驱动基础篇)IO 模型的分类
  • ChatGPT 有哪些 “激动人心的时刻“?以及自己的一些思考
  • Thingsboard开源物联网平台智慧农业实例快速部署教程(二)【手把手部署UI与动态数据】