本章简单介绍,imx6ull (cortex-A7)和 imxrt1062(cortex-m7)驱动开发模式对比
1.imx6ull 准备内核文件,这里笔者使用的内核版本是 4.9.88,当然也可以下载其他版本,只要支持就行
The Linux Kernel Archives
Search | NXP Semiconductors
2.imxrt1062 官方sdk (实际imx6ull官方给了一套sdk,可基于IAR开发,感兴趣的可以试试,但是一个A核芯片肯定基于linux开发才会发挥其作用)
Select Board | MCUXpresso SDK Builder (nxp.com)
3.两款芯片数据手册
imx6ull 驱动开发模式(linux内核驱动开发):芯片厂商会从 The Linux Kernel Archives下载某版本的linux内核,然后将其移植到自己的CPU上,测试成功后就会将其开放到自己网站上,提供给开发者使用。我们去研读,提供的内核文件,肯定能找到,底层驱动操作(涉及到该芯片寄存器读写)
imxrt1062 驱动开发模式(常规单片机开发,如:stm32这种),官方驱动包sdk上直接调用,基本不用啥驱动框架(近些年,小型嵌入式系统逐渐有了驱动框架形式,如:rt-thread 这类物联网操作系统等等)
驱动开发共同点,不论是ARM哪种核,最终操作的肯定是内存读写,对某个驱动的寄存器读写。另外对于一些有IO的外设,都是需要设置IO的复用和电气属性。接下来我们就拿一个SAI(全称:Synchronous Audio Interface )外设简单举例说明
sai1寄存器存在位置,起始地址:0x0202_8000 大小:0x4000(16KB)
寄存器地址描述位置,存在于imx6ull.dtsi文件中,路径: ./arch/arm/boot/dts/,通过该dtsi文件我们可以定制属于自己的dts设备树文件(截图下方:imx6ull-14x14-evk.dts 为官方开发板设备树文件,可以通过官方文件照猫画虎深入学习)
sai1 寄存器存在位置
寄存器地址描述位置,存在于MIMXRT1062.h头文件中
首先我们需要通过imx6ull.dtsi文件找到sai1,其次找到compatible描述位置,我们可以通过:“fsl,imx6ul-sai”,“fsl,imx6sx-sai”,全局搜索,可以找到fsl_sai.c文件,可以看出该文件还支持 imx7 imx8等等
研读fsl_sai.c fsl_sai.h文件,我们可以找到很多描述该外设寄存器和读写该外设寄存器的信息,从源码中会找到一些regmap字眼,regmap是很重要的子系统(regmap 用于提供一套方便的 API 函数去操作底层硬件寄存器,以提高代码的可重用性),具体细节可以网上搜索理解。另外还有描述寄存器偏移的信息,用datasheet对比是不是一样。
补:根据如上方法,我们可以试试查阅 adc ,wdog, i2c等外设的信息
/*省略......*/
wdog1: wdog@020bc000 {
compatible = "fsl,imx6ul-wdt", "fsl,imx21-wdt";
reg = <0x020bc000 0x4000>;
interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_WDOG1>;
};
/*省略......*/
adc1: adc@02198000 {
compatible = "fsl,imx6ul-adc", "fsl,vf610-adc";
reg = <0x02198000 0x4000>;
interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ADC1>;
num-channels = <2>;
clock-names = "adc";
status = "disabled";
};
/*省略......*/
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
/*省略......*/
在文件底部,将会注册该驱动:module_platform_driver(内核文件中大量使用该函数)
module_platform_driver(fsl_sai_driver);
/*查询......*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
/*查询......*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
/*省略......*/
驱动文件可以直接从sdk drivers文件夹中找到
注:各家厂商有区别,这里只是描述nxp的
根据linux内核路劲:./arch/arm/boot/dts/ 可以找到:imx6ul-pinfunc.h imx6ull-pinfunc.h 和 imx6ull-pinfunc-snvs.h,根据宏后面的信息可知其相关配置信息
另外在imx6ull.dtsi文件中有个iomux节点的重用信息,同样我们可以通过"fsl,imx6ul-iomuxc"查找到pinctrl-imx6ull.c源码
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
除此之外配置信息,设备树dts文件中的需要做io配置,这里拿sai2举例
&iomuxc {
pinctrl-names = "default";
/*省略......*/
pinctrl_sai2: sai2grp {
fsl,pins = <
MX6UL_PAD_JTAG_TDI__SAI2_TX_BCLK 0x17088
MX6UL_PAD_JTAG_TDO__SAI2_TX_SYNC 0x17088
MX6UL_PAD_JTAG_TRST_B__SAI2_TX_DATA 0x11088
MX6UL_PAD_JTAG_TCK__SAI2_RX_DATA 0x11088
MX6UL_PAD_JTAG_TMS__SAI2_MCLK 0x17088
>;
};
/*省略......*/
}
/*省略......*/
&sai2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai2>;
assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>,
<&clks IMX6UL_CLK_SAI2>;
assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <0>, <12288000>;
status = "okay";
};
rt1062 io配置
io配置文件可以直接从sdk drivers文件夹中找到 fsl_iomuxc.h
然后调用两个函数,实现最终配置
/*!
* @brief Sets the IOMUXC pin mux mode.
* @note The first five parameters can be filled with the pin function ID macros.
*
* This is an example to set the PTA6 as the lpuart0_tx:
* @code
* IOMUXC_SetPinMux(IOMUXC_PTA6_LPUART0_TX, 0);
* @endcode
*
* This is an example to set the PTA0 as GPIOA0:
* @code
* IOMUXC_SetPinMux(IOMUXC_PTA0_GPIOA0, 0);
* @endcode
*
* @param muxRegister The pin mux register.
* @param muxMode The pin mux mode.
* @param inputRegister The select input register.
* @param inputDaisy The input daisy.
* @param configRegister The config register.
* @param inputOnfield Software input on field.
*/
static inline void IOMUXC_SetPinMux(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister,
uint32_t inputOnfield)
{
*((volatile uint32_t *)muxRegister) =
IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) | IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);
if (inputRegister != 0UL)
{
*((volatile uint32_t *)inputRegister) = inputDaisy;
}
}
/*!
* @brief Sets the IOMUXC pin configuration.
* @note The previous five parameters can be filled with the pin function ID macros.
*
* This is an example to set pin configuration for IOMUXC_PTA3_LPI2C0_SCLS:
* @code
* IOMUXC_SetPinConfig(IOMUXC_PTA3_LPI2C0_SCLS,IOMUXC_SW_PAD_CTL_PAD_PUS_MASK|IOMUXC_SW_PAD_CTL_PAD_PUS(2U))
* @endcode
*
* @param muxRegister The pin mux register.
* @param muxMode The pin mux mode.
* @param inputRegister The select input register.
* @param inputDaisy The input daisy.
* @param configRegister The config register.
* @param configValue The pin config value.
*/
static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister,
uint32_t configValue)
{
if (configRegister != 0UL)
{
*((volatile uint32_t *)configRegister) = configValue;
}
}
补:除次之外还有时钟配置,外设时钟源选择,时钟分频等等,这里就不展开说说了,大家可以试试。