【简介】
在前两篇(s32k146适配zephyr(一)添加board )(s32k146适配zephyr(二)适配板级别差异),我们已经将本地的S32K146 的开发板添加到zephyr 系统,我们基于此基础上继续使用I2C设备驱动。zephyr 的驱动开发有点像linux 下驱动开发,因为驱动中已经支持了S32K 的I2C总线驱动,我们需要做的是配置DTS 中的I2C节点,开启I2C总线的驱动就可以使用zephyr 的I2C驱动。对S32K146 LPI2C的说明可以查看之前的该帖子(使用S32DS适配I2C接口),在此就不重复赘述。
【I2C总线设备树配置】
S32K146 芯片内部有集成了一个LPI2C的硬件I2C模块,对应的设备节点在\zephyr\dts\arm\nxp\nxp_s32k1xx.dtsi 通用的dtsi文件中描述如下:
lpi2c0: i2c@40066000 { compatible = "nxp,lpi2c"; clock-frequency = <I2C_BITRATE_STANDARD>; #address-cells = <1>; #size-cells = <0>; reg = <0x40066000 0x1000>; interrupts = <24 0>; clocks = <&clock NXP_S32_LPI2C0_CLK>; status = "disabled"; };
再次配置中status 属性是关闭的,我们需要在我们的board 对应的DTS 配置中重写该属性开启I2C设备树节点,对应我们的board下的DTS中引用 lpi2c0 设备并更新对应的属性信息。
&lpi2c0 { pinctrl-0 = <&lpi2c0_default>; pinctrl-names = "default"; scl-gpios = <&gpioa 3 GPIO_ACTIVE_HIGH>; sda-gpios = <&gpioa 2 GPIO_ACTIVE_HIGH>; status = "okay"; };
大家看到这不知是否有个疑问?这里面的配置项目代表的含义是什么,我看到这的时候是有这个疑问的,带着这个疑问查看了官方的nxp,lpi2c 的节点说明,官方的链接(https://docs.zephyrproject.org/latest/build/dts/api/bindings/i2c/nxp%2Clpi2c.html#dtbinding-nxp-lpi2c)有对应的属性描述,我们截取了部分的属性说明如下:
除了上述I2C的节点配置外,我们还需要在PIN_CTRL节点中配置GPIO为I2C功能,对应PIN_CTRL配置如下
&pinctrl { lpuart1_default: lpuart1_default { group0 { pinmux = <LPUART1_RX_PTC8>, <LPUART1_TX_PTC9>; drive-strength = "low"; }; }; lpi2c0_default: lpi2c0_default { group1 { pinmux = <LPI2C0_SDA_PTA2>, <LPI2C0_SCL_PTA3>; drive-strength = "low"; }; }; };
【I2C驱动配置】
我们在上面更新了DTS相关的I2C配置,我们只需要在zephyr 的工程config 中开启I2C驱动就可以将I2C驱动编译到系统中,在应用\zephyr\samples\drivers\uart\shell\prj.conf 文件中添加I2C总线驱动配置。
【测试代码】
更新好上述配置后,我们可以就可以在代码中添加如下测试代码验证I2C总线驱动,在zephyr 中i2c设备驱动通过i2c_msg结构来对总线进行读写访问。
i2c_transfer() 函数将上述的i2c_msg结构信息发送到总线。
我们编写测试代码对总线的地址范围进行扫描遍历,对应的节点是否有应答来确认总线上的节点数目信息,该测试在之前使用S32DS配置I2C总线时使用过(使用S32DS适配I2C接口),只要修改底层I2C驱动访问接口即可。我们使用zephyr I2C驱动函数访问I2C对应修改如下。
I2C 传输使用
#include <stdio.h> #include "littleshell.h" #include <zephyr/kernel.h> #include <zephyr/device.h> #include <zephyr/drivers/i2c.h> #include <ringbuffer.h> #include <zephyr/drivers/gpio.h> #include <string.h> #include <zephyr/logging/log.h> #include <zephyr/device.h> /******************************************************************************************************** * Private Function Declarations * *******************************************************************************************************/ static int i2c_probe(const struct device *dev,char addr) { uint8_t data = 0; static struct i2c_msg msg; int ret = -1; msg.len = 1; msg.buf = &data; msg.flags = I2C_MSG_WRITE; ret = i2c_transfer(dev,&msg,1,addr); if(ret == 0) { msg.len = 1; msg.buf = &data; msg.flags = I2C_MSG_READ | I2C_MSG_STOP; ret = i2c_transfer(dev,&msg,1,addr); } return ret == 0 ? 1 : 0; } static void i2c_scan(const struct device *dev,uint8_t start_addr, uint8_t stop_addr) { printf(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"); uint8_t pos = start_addr <= 0xF? 0x00: start_addr & 0xF; for(; pos < stop_addr; pos++) { if((pos & 0xF) == 0) { printf("\r\n%02X: ", pos); } if(pos < start_addr) { printf(" "); continue; } if( i2c_probe(dev,pos) == 1) { printf("%02X", pos); } else { printf("--"); } printf(" "); } printf("\r\n"); } unsigned int i2c(char argc,char *argv[]) { static const struct device * dev; if(strcmp(argv[1],"scan") == 0) { i2c_scan(dev,0x00,0x7f); } if(strcmp(argv[1],"init") == 0) { dev = device_get_binding("i2c@40066000"); if(dev == NULL) { printf("i2c init failed.\r\n"); } } return 0; }
【实机验证】
上述测试代码,扫描总线的地址信息输出如下,跟之前验证的结果一致。
上述结果已经能说明I2C 总线驱动已经按照预期工作,我们通过逻辑分析仪查看I2C总线的读写访问功能的波形也是很规整的。
以下是扫描到的0x32地址的设备的完整波形,从结果看是有接收到ACK应答。
以下是扫描0x33地址,没有接收到ACK应答的波形。
适配好总线驱动后,后续我们就可以使用I2C总线设备来驱动板子上的I2C外设了。