首页 > 评测 > RTT-Studio环境下的APM32外设驱动使用指南

RTT-Studio环境下的APM32外设驱动使用指南

  
  • 作者:
  • 来源:
  • [导读]
  • 本帖最后由 luobeihai 于 2023-4-19 00:08 编辑 #申请原创# @21小跑堂 1. 简介由于自己平时在项目中经常使用 APM32 系列的 MCU,而且经常是和 RT-Thread 系统一起使用,慢慢的就对 APM32 系列 MCU 的

本帖最后由 luobeihai 于 2023-4-19 00:08 编辑

#申请原创#  @21小跑堂


1. 简介

由于自己平时在项目中经常使用 APM32 系列的 MCU,而且经常是和 RT-Thread 系统一起使用,慢慢的就对 APM32 系列 MCU 的 RT-Thread 外设驱动使用熟悉了起来。
目前,RTT-Studio 环境已经支持了 APM32F0/F1/F4/E1/S1 系列的 MCU,每个系列 MCU 的 RTT驱动使用方法基本相似。下面我就以 APM32F407 这款 MCU 为例,介绍下在 RTT-Studio 环境下如何使用 APM32 的外设驱动,希望能帮助到大家快速把 APM32 的 RT-Thread 驱动使用起来。
对于每个外设驱动的使用和测试,大家可以配合 RTT 的官方文档中心(https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/README)的设备和驱动模块进行阅读,文档中心对于每一个驱动如何使用和测试都有详细的介绍。

2. 新建RTT-Studio工程

下面是以 APM32F407 进行介绍,所以需要先安装 APM32F4 最新的 RTT-Studio 软件支持包。

2.1 下载APM32F4软件支持包

打开 SDK 管理器,然后找到 APM32F4 的软件支持包,然后点击安装即可。

2.2 基于芯片新建一个 APM32F407 的工程

控制台串口默认是UART1,如果想要更改控制台串口号,下面会详细介绍,这里先默认UART1。然后点击完成即可创建一个 APM32F407 的工程。

3. apm32_msp.c 文件介绍

新建完工程之后,驱动目录结构如下:
可以看到,已经支持了大部分的外设驱动了,如 can/flash/eth/pwm/sdio 等等外设驱动。其中有一个文件是比较重要的, apm32_msp.c 文件,打开这个文件就可以了解到这个文件的作用就是初始化外设时钟和 GPIO 口。比如用到 spi 外设,那么我们就必须要在这个文件里面添加你自己板子所使用到的 spi 外设 GPIO 口的初始化。
我们在后面有很多用到的外设驱动,关于外设引脚的初始化代码,都可以编写放进这个文件中。官方也已经给出了这些外设的部分引脚的初始化代码了,如果我们所使用的硬件引脚和给出的代码是一致的,那么就不需要更改给出的示例代码,如果所使用的引脚不一样,就需要自己编写引脚的初始化代码了。
为什么编写驱动的时候,没有把外设 GPIO 口和对应外设的时钟初始化都编写到对应的驱动程序中呢?这样用户在使用时不就可以编写更少的代码了吗?
关于这一点,主要是因为每个人所使用的硬件是不一样的,比如说有的人可能使用的是 spi1 外设,也可能使用 spi2 外设,而且就算是 spi1 外设,所使用的引脚也可能不同,因为 spi1 外设的 GPIO 引脚很可能有多个引脚可选。简单来说就是关于这部分代码的不确定性太大了,如果在驱动程序中把所有的可能都写出来,会导致驱动程序太过复杂和冗余。所以人家在编写这部分驱动代码的时候,把 GPIO 引脚初始化的代码给剥离出来了,让使用者自己去编写这部分代码。
当然有一些简单的外设驱动,也可以把引脚的初始化相关的代码给写到驱动程序中去。APM32的外设驱动,如 adc/dac/hwtimer 这些驱动,底层引脚初始化和外设时钟的开启就是写到了驱动程序里面的。

4. UART设备

新建完工程之后,默认是已经打开了UART和PIN设备的,因为这两个外设是最常用的。只要编译下载,就可看到串口打印的输出信息。
对于这两个外设的 API 如何使用,可以参考RTT的官方文档,这里不再介绍。下面重点介绍的是如何修改控制台的输出串口,因为在实际的项目中,很多时候控制台所使用的串口并不是 UART1 。

4.1 确定串口引脚,修改串口GPIO初始化代码

我们根据原理图,确定自己所使用的串口调试具体是哪个串口,和对应的串口引脚。然后我们在 apm32_msp.c 文件中,添加串口引脚的初始化代码即可。比如,我的板子就用的UART6,那我们在这个文件添加UART6的引脚初始化代码。
官方所提供的示例代码中,已经提供了 UART1/UART2 的引脚初始化了,我们在 apm32_msp.c 文件中的apm32_usart_init 函数添加 UART6 的引脚初始化代码:
  1. void apm32_usart_init(void)
  2. {
  3.     GPIO_Config_T GPIO_ConfigStruct;
  4. #ifdef BSP_USING_UART6
  5.     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOC);
  6.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_USART6);
  7.  
  8.     GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_6, GPIO_AF_USART6);
  9.     GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_7, GPIO_AF_USART6);
  10.  
  11.     GPIO_ConfigStruct.pin = GPIO_PIN_6 | GPIO_PIN_7;
  12.     GPIO_ConfigStruct.mode = GPIO_MODE_AF;
  13.     GPIO_ConfigStruct.otype = GPIO_OTYPE_PP;
  14.     GPIO_ConfigStruct.pupd = GPIO_PUPD_UP;
  15.     GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
  16.     GPIO_Config(GPIOC, &GPIO_ConfigStruct);
  17. #endif
  18. }
复制代码

4.2 在board.h中修改UART端口号

当然,如果我们在新建工程时,就已经选择了UART6作为控制台的串口,那么我们只需要做完第一步,把所需要的UART引脚进行初始化即可,下面的步骤就都不需要做了。

4.3 在 rtconfig.h 文件中修改控制台所需要的串口设备

修改完之后,重新编译下载,然后可以看到串口打印信息如下:
而且,也可以看到串口设备变为 uart6 了。

5. ADC设备
5.1 使能ADC外设驱动

打开 RT-Thread Setting 配置文件,然后找到 组件 -> 设备驱动程序 -> 使用ADC设备驱动程序,开启该驱动程序即可。

5.2 在board.h文件中定义ADC宏

board.h文件,都是定义和板级硬件所使用的宏定义,如下我们定义 BSP_USING_ADC1这个宏。

5.3 ADC设备测试方法

关于ADC设备的测试和使用示例,可以查阅RTT的文档。下面我简单介绍下,怎么测试我们的ADC外设驱动是否正常工作了。
在命令行终端,可以输入adc命令,然后有一下的使用方法:
  1. adc probe <adc_name>   - probe adc by name
  2. adc read <channel>     - read adc value on the channel
  3. adc disable <channel>  - disable adc channel
  4. adc enable <channel>   - enable adc channel
复制代码

我们对照着输入上面的命令,即可获取ADC采样的电压值。
我们使用的是adc1外设,然后adc1外设通采样道对应的是哪个GPIO引脚,大家可以查看 APM32F407 的数据手册,当然也可称查看ADC外设的驱动代码,如下:
然后,我们在串口终端输入下面的命令,读取PA5(通道5)口的电压值。
可以看到,PA5引脚接3.3V和GND读取到的数值是不一样的,当然这个数据还需要根据12位的分辨率,然后进行电压值的转换。计算公式:vol = value * 3300 / (1<<12)

6. PWM设备
6.1 使能PWM外设驱动

在 RT-Thread Setting 配置文件中使能PWM外设驱动。

6.2 在board.h文件中定义PWM外设相关的宏

这里,我打算使用定时器3的通道1进行PWM输出,所有定义PWM3这个宏。

6.3 在apm32_msp.c文件中添加定时器通道初始化代码

由于人家驱动程序并不清楚你的硬件使用的是哪个定时器哪个通道,所以把这部分的初始化程序剥离出来留给用户去写了(因为ADC外设驱动比较简单,所以ADC的引脚的初始化写进了驱动程序中)。
我使用的是PC6引脚输出PWM,就是定时器3的通道1。关于该通道初始化代码,刚好官方提供的初始化代码就有这个,所以不用重新写了。但是如果你用的不是这个定时器通道,就必须添加该部分初始化代码。
  1. void apm32_msp_timer_init(void *Instance)
  2. {
  3. #ifdef BSP_USING_PWM3
  4.     GPIO_Config_T gpio_config;
  5.     TMR_T *tmr_x = (TMR_T *)Instance;
  6.  
  7.     if (tmr_x == TMR3)
  8.     {
  9.         RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOC);
  10.         RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR3);
  11.  
  12.         /* TMR3 channel 1 gpio init */
  13.         GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_6, GPIO_AF_TMR3);
  14.         gpio_config.pin = GPIO_PIN_6;
  15.         gpio_config.mode = GPIO_MODE_AF;
  16.         gpio_config.otype = GPIO_OTYPE_PP;
  17.         gpio_config.speed = GPIO_SPEED_50MHz;
  18.         GPIO_Config(GPIOC, &gpio_config);
  19.  
  20.         /* TMR3 channel 2 gpio init */
  21.         GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_7, GPIO_AF_TMR3);
  22.         gpio_config.pin = GPIO_PIN_7;
  23.         GPIO_Config(GPIOC, &gpio_config);
  24.  
  25.         /* TMR3 channel 3 gpio init */
  26.         GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_8, GPIO_AF_TMR3);
  27.         gpio_config.pin = GPIO_PIN_8;
  28.         GPIO_Config(GPIOC, &gpio_config);
  29.  
  30.         /* TMR3 channel 4 gpio init */
  31.         GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_9, GPIO_AF_TMR3);
  32.         gpio_config.pin = GPIO_PIN_9;
  33.         GPIO_Config(GPIOC, &gpio_config);
  34.     }
  35. #endif
  36. }
复制代码

6.4 PWM设备使用示例

详细的PWM设备使用示例,我们可以参考RTT的官方文档。
下面的代码是从RTT文档中复制过来的,PWM输出的引脚与LED相连,可以观察到 LED 灯不停的由暗变到亮,然后又从亮变到暗。
  1. /*
  2. * 程序清单:这是一个 PWM 设备使用例程
  3. * 例程导出了 pwm_led_sample 命令到控制终端
  4. * 命令调用格式:pwm_led_sample
  5. * 程序功能:通过 PWM 设备控制 LED 灯的亮度,可以看到LED不停的由暗变到亮,然后又从亮变到暗。
  6. */
  7.  
  8. #include <rtthread.h>
  9. #include <rtdevice.h>
  10.  
  11. #define PWM_DEV_NAME        "pwm3"  /* PWM设备名称 */
  12. #define PWM_DEV_CHANNEL     4       /* PWM通道 */
  13.  
  14. struct rt_device_pwm *pwm_dev;      /* PWM设备句柄 */
  15.  
  16. static int pwm_led_sample(int argc, char *argv[])
  17. {
  18.     rt_uint32_t period, pulse, dir;
  19.  
  20.     period = 500000;    /* 周期为0.5ms,单位为纳秒ns */
  21.     dir = 1;            /* PWM脉冲宽度值的增减方向 */
  22.     pulse = 0;          /* PWM脉冲宽度值,单位为纳秒ns */
  23.  
  24.     /* 查找设备 */
  25.     pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
  26.     if (pwm_dev == RT_NULL)
  27.     {
  28.         rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM_DEV_NAME);
  29.         return RT_ERROR;
  30.     }
  31.  
  32.     /* 设置PWM周期和脉冲宽度默认值 */
  33.     rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
  34.     /* 使能设备 */
  35.     rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL);
  36.  
  37.     while (1)
  38.     {
  39.         rt_thread_mdelay(50);
  40.         if (dir)
  41.         {
  42.             pulse += 5000;      /* 从0值开始每次增加5000ns */
  43.         }
  44.         else
  45.         {
  46.             pulse -= 5000;      /* 从最大值开始每次减少5000ns */
  47.         }
  48.         if (pulse >= period)
  49.         {
  50.             dir = 0;
  51.         }
  52.         if (0 == pulse)
  53.         {
  54.             dir = 1;
  55.         }
  56.  
  57.         /* 设置PWM周期和脉冲宽度 */
  58.         rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
  59.     }
  60. }
  61. /* 导出到 msh 命令列表中 */
  62. MSH_CMD_EXPORT(pwm_led_sample, pwm sample);
复制代码

我们把该代码,复制到main.c文件中,然后编译下载。然后再串口终端输入 pwm_led_sample 这个命令,就可观察到板子的LED灯不停的由暗到亮了(要确保PWM输出的引脚与LED的引脚相连了)。

7. SPI设备
7.1 使能SPI外设驱动和串行Flash驱动程序

我的硬件板子,SPI外设是连接了 SPI Flash 芯片的,所以我们在开启使能SPI外设驱动时,把SPI Flash驱动程序也一起打开,如下:

7.2 在board.h文件中定义SPI相关的宏

我所使用到的是SPI1,所以开启SPI1的宏定义。

7.3 在apm32_msp.c中编写SPI外设引脚的初始化代码

对于SPI驱动程序,需要用户自己去编写对于的SPI外设引脚初始化代码。我的板子所使用到的是SPI1的PB3/4/5引脚,所以相关初始化代码如下:
  1. void apm32_msp_spi_init(void *Instance)
  2. {
  3. #if defined (BSP_USING_SPI1) || defined (BSP_USING_SPI2) || defined (BSP_USING_SPI3)
  4.     GPIO_Config_T gpioConfig;
  5.     SPI_T *spi_x = (SPI_T *)Instance;
  6.  
  7.     if(spi_x == SPI1)
  8.     {
  9.         /* Enable related Clock */
  10.         RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);
  11.         RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SPI1);
  12.         RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);
  13.  
  14.         /* Config SPI1 PinAF */
  15.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_3, GPIO_AF_SPI1);
  16.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_4, GPIO_AF_SPI1);
  17.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_5, GPIO_AF_SPI1);
  18.  
  19.         /* Config SPI GPIO, SCK=PB3, MISO=PB4, MOSI=PB5 */
  20.         GPIO_ConfigStructInit(&gpioConfig);
  21.         gpioConfig.pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
  22.         gpioConfig.speed = GPIO_SPEED_100MHz;
  23.         gpioConfig.mode = GPIO_MODE_AF;
  24.         gpioConfig.otype = GPIO_OTYPE_PP;
  25.         gpioConfig.pupd = GPIO_PUPD_NOPULL;
  26.         GPIO_Config(GPIOB, &gpioConfig);
  27.     }
  28. #endif
  29. }
复制代码

如果你使用的是其他SPI引脚,编写对于的SPI引脚初始化代码即可。

7.4 挂载SPI设备

SPI 驱动会注册 SPI 总线,SPI 设备需要挂载到已经注册好的 SPI 总线上。
SPI驱动程序已经提供好了  rt_spi_bus_attach_device 这个函数进行SPI设备的挂载了。我们调用该函数就可以SPI Flash设备挂载到某个SPI总线上,代码如下:
  1. #include <rtthread.h>
  2. #include "spi_flash.h"
  3. #include "spi_flash_sfud.h"
  4. #include "drv_spi.h"
  5.  
  6. static int rt_hw_spi_flash_init(void)
  7. {
  8.     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOG);
  9.     rt_hw_spi_device_attach("spi1", "spi10", GPIOG, GPIO_PIN_8);
  10.  
  11.     if (RT_NULL == rt_sfud_flash_probe("W25Q128", "spi10"))
  12.     {
  13.         return -RT_ERROR;
  14.     }
  15.  
  16.     return RT_EOK;
  17. }
  18. INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);
复制代码

把上述代码复制到 main.c 文件中(我这里只是为了测试验证,为了方便并没有新建一个文件,而是直接复制到main.c文件),然后编译下载程序,查看串口终端输出信息如下:
可以看到,已经发现了一个华邦的SPI Flash芯片,而且容量大小是 16M byte。

8. CAN设备

8.1 使能CAN外设驱动

打开 RT-Thread Settings ,使能CAN设备驱动程序。

8.2 在board.h定义与CAN外设相关的宏

RTT-Studio 自动生成的 board.h 文件,还没有CAN相关的宏定义,那么我们就自己定义CAN外设驱动所需要的宏定义即可。定义如下的宏:
  1. #define BSP_USING_CAN1
  2. #define BSP_USING_CAN2
复制代码

用到哪个CAN,就定义哪个就行。

8.3 在apm32_msp.c文件中编写CAN引脚初始化代码

同样的,自己板子用到的是哪路CAN,哪个引脚,还需要在apm32_msp.c文件中编写引脚相关的初始化代码。下面的CAN引脚初始化代码,是apm32_msp.c文件已有的示例代码,如果你使用的CAN引脚,刚好是示例代码所使用的引脚,那么你就不用更改代码了。
  1. void apm32_msp_can_init(void *Instance)
  2. {
  3. #if defined(BSP_USING_CAN1) || defined(BSP_USING_CAN2)
  4.     GPIO_Config_T  GPIO_InitStructure;
  5.     CAN_T *CANx = (CAN_T *)Instance;
  6.  
  7.     if (CAN1 == CANx)
  8.     {
  9.         RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN1);
  10.  
  11.         RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);
  12.  
  13.         /* PB8: CAN1_RX, PB9: CAN1_TX */
  14.         GPIO_InitStructure.pin = GPIO_PIN_8 | GPIO_PIN_9;
  15.         GPIO_InitStructure.mode = GPIO_MODE_AF;
  16.         GPIO_InitStructure.otype = GPIO_OTYPE_PP;
  17.         GPIO_InitStructure.speed = GPIO_SPEED_100MHz;
  18.         GPIO_InitStructure.pupd = GPIO_PUPD_UP;
  19.         GPIO_Config(GPIOB, &GPIO_InitStructure);
  20.  
  21.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_8, GPIO_AF_CAN1);
  22.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_9, GPIO_AF_CAN1);
  23.     }
  24.     else if (CAN2 == CANx)
  25.     {
  26.         /* When using the CAN2 peripheral, the CAN1 clock must be turned on */
  27.         RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN1);
  28.         RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN2);
  29.  
  30.         RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);
  31.  
  32.         /* PB12: CAN2_RX, PB13: CAN2_TX */
  33.         GPIO_InitStructure.pin = GPIO_PIN_12 | GPIO_PIN_13;
  34.         GPIO_InitStructure.mode = GPIO_MODE_AF;
  35.         GPIO_InitStructure.otype = GPIO_OTYPE_PP;
  36.         GPIO_InitStructure.speed = GPIO_SPEED_100MHz;
  37.         GPIO_InitStructure.pupd = GPIO_PUPD_UP;
  38.         GPIO_Config(GPIOB, &GPIO_InitStructure);
  39.  
  40.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_12, GPIO_AF_CAN2);
  41.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_13, GPIO_AF_CAN2);
  42.     }
  43. #endif
  44. }
复制代码

8.4 CAN设备使用示例

CAN设备驱动的使用示例,可以参考RTT官方文档 https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/can/can ,文档有详细的介绍。下面我就简单介绍下使用步骤:
  • 首先把文档中的示例代码复制到main.c文件,然后编译下载。
  • 使用CAN分析仪连接到你板子的CANH和CANL(我所使用的是创芯科技的CAN分析仪)。
  • 打开创芯科技的CAN分析仪上位机软件,然后按照1M的波特率启动设备(因为CAN驱动程序默认的波特率是1M,如果你修改了CAN驱动程序中的波特率,那么CAN接收数据的上位机设置的波特率要与之对应)。
  • 在串口终端运行 can_sample 这个命令。
  • 然后查看 CAN 上位机工具是否接收到了开发板发送的CAN数据。
  • 也可以提供CAN上位机发送数据到开发板,然后板子会把接收到的数据打印出来。
详细的使用示例可以看RTT的文档,我这里不再重复介绍了。

9. SDIO设备
9.1 使能SDIO外设驱动以及FatFs

1、使能SDIO设备驱动程序,有些要填写的参数,默认值就行。
2、使能虚拟文件系统和FatFs。

9.2 在board.h文件中定义SDIO外设相关的宏

9.3 在apm32_msp.c文件中编写SDIO引脚初始化代码

对于SDIO外设,使能的引脚基本都是固定的,所以 apm32_msp.c 给出的代码就可以直接使用,不用修改引脚初始化相关的代码。
  1. void apm32_msp_sdio_init(void *Instance)
  2. {
  3. #ifdef BSP_USING_SDIO
  4.     GPIO_Config_T  GPIO_InitStructure;
  5.  
  6.     /* Enable the GPIO Clock */
  7.     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOC | RCM_AHB1_PERIPH_GPIOD);
  8.  
  9.     /* Enable the SDIO Clock */
  10.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SDIO);
  11.  
  12.     /* Enable the SDIO peripheral reset */
  13.     RCM_EnableAPB2PeriphReset(RCM_APB2_PERIPH_SDIO);
  14.  
  15.     /* Configure the GPIO pin */
  16.     GPIO_InitStructure.pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
  17.     GPIO_InitStructure.mode = GPIO_MODE_AF;
  18.     GPIO_InitStructure.speed = GPIO_SPEED_50MHz;
  19.     GPIO_InitStructure.otype = GPIO_OTYPE_PP;
  20.     GPIO_InitStructure.pupd = GPIO_PUPD_UP;
  21.     GPIO_Config(GPIOC, &GPIO_InitStructure);
  22.  
  23.     GPIO_InitStructure.pin = GPIO_PIN_2;
  24.     GPIO_Config(GPIOD, &GPIO_InitStructure);
  25.  
  26.     GPIO_ConfigPinAF(GPIOC,GPIO_PIN_SOURCE_8, GPIO_AF_SDIO);
  27.     GPIO_ConfigPinAF(GPIOC,GPIO_PIN_SOURCE_9, GPIO_AF_SDIO);
  28.     GPIO_ConfigPinAF(GPIOC,GPIO_PIN_SOURCE_10, GPIO_AF_SDIO);
  29.     GPIO_ConfigPinAF(GPIOC,GPIO_PIN_SOURCE_11, GPIO_AF_SDIO);
  30.     GPIO_ConfigPinAF(GPIOC,GPIO_PIN_SOURCE_12, GPIO_AF_SDIO);
  31.     GPIO_ConfigPinAF(GPIOD,GPIO_PIN_SOURCE_2, GPIO_AF_SDIO);
  32.  
  33.     /* Disable the SDIO peripheral reset */
  34.     RCM_DisableAPB2PeriphReset(RCM_APB2_PERIPH_SDIO);
  35. #endif
  36. }
复制代码

9.4 SD卡挂载elm FatFs文件系统

经过上面的步骤之后,SDIO其实已经正常工作起来了的。然后,我们把下面的代码复制到 main.c 文件中。
  1. #include <dfs_elm.h>
  2. #include <dfs_fs.h>
  3. #include <dfs_file.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <sys/stat.h>
  7. #include <sys/statfs.h>
  8.  
  9. void sd_mount(void *parameter)
  10. {
  11.     while (1)
  12.     {
  13.         rt_thread_mdelay(500);
  14.         if(rt_device_find("sd0") != RT_NULL)
  15.         {
  16.             if (dfs_mount("sd0", "/", "elm", 0, 0) == RT_EOK)
  17.             {
  18.                 LOG_I("sd card mount to '/'");
  19.                 break;
  20.             }
  21.             else
  22.             {
  23.                 LOG_W("sd card mount to '/' failed!");
  24.             }
  25.         }
  26.     }
  27. }
  28.  
  29. int apm32_sdcard_mount(void)
  30. {
  31.     rt_thread_t tid;
  32.  
  33.     tid = rt_thread_create("sd_mount", sd_mount, RT_NULL,
  34.                            2048, RT_THREAD_PRIORITY_MAX - 2, 20);
  35.     if (tid != RT_NULL)
  36.     {
  37.         rt_thread_startup(tid);
  38.     }
  39.     else
  40.     {
  41.         LOG_E("create sd_mount thread err!");
  42.     }
  43.     return RT_EOK;
  44. }
  45. INIT_APP_EXPORT(apm32_sdcard_mount);
复制代码

最后编译下载程序,可以看到串口终端输出如下信息:
说明SD卡已经挂载到了根目录 "/" 了。这时,我们可以使用 ls/cat/cd 等等基本的文件系统操作命令。

10. ETH设备
10.1 使能LwIP堆栈

在 RT-Thread Settings ,使能网络接口设备和LwIP堆栈。由于我的开发板没有插路由器,而是直接插到电脑的网口了,所以把DHCP功能给关掉,使用静态IP地址。默认静态IP是: 192.168.1.30 。可以自己更改。

10.2 在board.h中定义以太网相关的宏

我们在board.h中开启以太网设备的宏定义,以及使能相关的PHY芯片宏定义。

10.3 在apm32_msp.c文件中添加ETH外设初始化代码

使能了LwIP之后,我们还需要添加以太网外设引脚初始化相关的代码。我们需要根据原理图,确认MCU和物理层芯片是通过 RMII 还是 MII 接口连接的,然后在apm32_msp.c 文件中添加对应接口引脚的初始化代码。我使用的是 RMII 接口,对应引脚初始化代码如下:
  1. void apm32_msp_eth_init(void *Instance)
  2. {
  3. #ifdef BSP_USING_ETH
  4.     GPIO_Config_T GPIO_ConfigStruct;
  5.  
  6.     /* Enable SYSCFG clock */
  7.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);
  8.  
  9.     /* Enable GPIOs clocks */
  10.     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA | RCM_AHB1_PERIPH_GPIOC | RCM_AHB1_PERIPH_GPIOG);
  11.  
  12.     /* MII/RMII Media interface selection */
  13.     SYSCFG_ConfigMediaInterface(SYSCFG_INTERFACE_RMII);
  14.  
  15.     /*********************** Ethernet pins configuration ***************************/
  16.     /*
  17.         ETH_MDIO -------------------------> PA2
  18.         ETH_MDC --------------------------> PC1
  19.         ETH_MII_RX_CLK/ETH_RMII_REF_CLK---> PA1
  20.         ETH_MII_RX_DV/ETH_RMII_CRS_DV ----> PA7
  21.         ETH_MII_RXD0/ETH_RMII_RXD0 -------> PC4
  22.         ETH_MII_RXD1/ETH_RMII_RXD1 -------> PC5
  23.         ETH_MII_TX_EN/ETH_RMII_TX_EN -----> PG11
  24.         ETH_MII_TXD0/ETH_RMII_TXD0 -------> PG13
  25.         ETH_MII_TXD1/ETH_RMII_TXD1 -------> PG14
  26.     */
  27.     /* Configure PC1, PC4 and PC5 */
  28.     GPIO_ConfigStruct.pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
  29.     GPIO_ConfigStruct.speed = GPIO_SPEED_100MHz;
  30.     GPIO_ConfigStruct.mode  = GPIO_MODE_AF;
  31.     GPIO_ConfigStruct.otype = GPIO_OTYPE_PP;
  32.     GPIO_ConfigStruct.pupd  = GPIO_PUPD_NOPULL;
  33.  
  34.     GPIO_Config(GPIOC, &GPIO_ConfigStruct);
  35.     GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_1, GPIO_AF_ETH);
  36.     GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_4, GPIO_AF_ETH);
  37.     GPIO_ConfigPinAF(GPIOC, GPIO_PIN_SOURCE_5, GPIO_AF_ETH);
  38.  
  39.     /* Configure PG11, PG13 and PG14 */
  40.     GPIO_ConfigStruct.pin =  GPIO_PIN_11 | GPIO_PIN_13 | GPIO_PIN_14;
  41.     GPIO_Config(GPIOG, &GPIO_ConfigStruct);
  42.     GPIO_ConfigPinAF(GPIOG, GPIO_PIN_SOURCE_11, GPIO_AF_ETH);
  43.     GPIO_ConfigPinAF(GPIOG, GPIO_PIN_SOURCE_13, GPIO_AF_ETH);
  44.     GPIO_ConfigPinAF(GPIOG, GPIO_PIN_SOURCE_14, GPIO_AF_ETH);
  45.  
  46.     /* Configure PA1, PA2 and PA7 */
  47.     GPIO_ConfigStruct.pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7;
  48.     GPIO_Config(GPIOA, &GPIO_ConfigStruct);
  49.     GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_1, GPIO_AF_ETH);
  50.     GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_2, GPIO_AF_ETH);
  51.     GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_7, GPIO_AF_ETH);
  52. #endif
  53. }
复制代码

10.4 添加 phy 芯片复位函数

我们需要通过原理图确认,自己硬件板子上的 phy 芯片的复位引脚具体是连接到了那个 GPIO 口,当然也有可能是 phy 芯片的复位引脚没有连接到任何 GPIO 口,而是连接到了硬件的复位电路上,这个时候我们就不需要编写该函数的任何内容,但为了避免编译报错,还是要编写一个  void phy_reset(void) 。
我的硬件板子 phy 复位引脚就没有连接到任何的 GPIO 口,所以只需要编写下面的空函数即可。
  1. void phy_reset(void)
  2. {
  3.         /* phy复位相关代码 */
  4. }
复制代码

10.5 网络功能测试

做完上面的步骤之后,我们就可以编译下载程序了。程序启动后可以在串口终端看到如下打印信息,输入 ifconfig 命令可以打印网络连接状态和 IP 地址。
保证我们的电脑和开发板的IP地址在同一网段,然后我们在电脑的命令行终端输入ping命令,可以看到电脑可以ping通开发板。
然后我们在串口终端输入ping命令,去ping电脑的IP地址,如下:
也可以正常ping通电脑的IP,说明网络功能已经正常运行起来了。

11. SDRAM设备
11.1 打开SDRAM相关的代码

对于SDRAM的使用,并不用使能什么设备驱动(RTT也没有SDRAM设备框架),我们只需要在 board.h 文件中定义 BSP_USING_SDRAM 这个宏,把SDRAM相关的代码开启即可。

11.2 修改SDRAM时序参数以及行、列、大小等参数

在 drv_sdram.c 文件中,已经提供了 SDRAM 的时序及 SDRAM 行列宽度,SDRAM大小等参数的初始化了。但是提供的那些参数可能不一定适用于你使用的 SDRAM芯片,我们需要根据SDRAM的芯片手册参数进行更改代码。
APM32 的固件库函数中,DMC_TimingConfig_T 这个结构体就是时序结构体,需要根据SDRAM芯片的特性进行赋值,该结构体定义如下:
  1. /**
  2. * [url=home.php?mod=space&uid=247401]@brief[/url] Timing config definition
  3. */
  4. typedef struct
  5. {
  6.     uint32_t    latencyCAS  : 2;       /*!< DMC_CAS_LATENCY_T */
  7.     uint32_t    tRAS        : 4;       /*!< DMC_RAS_MINIMUM_T */
  8.     uint32_t    tRCD        : 3;       /*!< DMC_DELAY_TIME_T */
  9.     uint32_t    tRP         : 3;       /*!< DMC_PRECHARGE_T */
  10.     uint32_t    tWR         : 2;       /*!< DMC_NEXT_PRECHARGE_T */
  11.     uint32_t    tARP        : 4;       /*!< DMC_AUTO_REFRESH_T */
  12.     uint32_t    tCMD        : 4;       /*!< DMC_ATA_CMD_T */
  13.     uint32_t    tXSR        : 9;       /*!< auto-refresh commands, can be 0x000 to 0x1FF */
  14.     uint16_t    tRFP        : 16;      /*!< Refresh period, can be 0x0000 to 0xFFFF */
  15. } DMC_TimingConfig_T;
复制代码

然后还有一个初始化结构体,主要适合SDRAM行列宽度,bank宽度有关的,我们也需要根据SDRAM芯片的特性对其进行赋值,该结构体定义如下:
  1. /**
  2. * [url=home.php?mod=space&uid=247401]@brief[/url] Config struct definition
  3. */
  4. typedef struct
  5. {
  6.     DMC_BANK_WIDTH_T        bankWidth;     /*!< Number of bank bits */
  7.     DMC_ROW_WIDTH_T         rowWidth;      /*!< Number of row address bits */
  8.     DMC_COL_WIDTH_T         colWidth;      /*!< Number of col address bits */
  9.     DMC_CLK_PHASE_T         clkPhase;      /*!< Clock phase */
  10.     DMC_TimingConfig_T      timing;        /*!< Timing */
  11. } DMC_Config_T;
复制代码

我们要在 drv_sdram.c 和 drv_sdram.h 文件中,根据自己所使用的 SDRAM 芯片的特性,对时序结构体和初始化结构体进行赋值,修改对应的代码,下面是 drv_sdram.c 文件中的 SDRAM_Init 函数部分代码截图,我们主要就是要修改下这部分代码。

11.3 SDRAM测试

我们修改完 SDRAM 的时序结构体和初始化结构体的参数之后,编译下载到板子运行,可以看到下面的提示信息:
然后我们可以在串口终端中输入SDRAM的读写测试命令, sdram_test,可以看到测试结果如下:
可以看到SDRAM读写测试成功,说明SDRAM就可以正常使用起来了。

12. 总结

以上就是对部分外设驱动使用介绍了,基本上我们要使用 APM32 的某一个外设驱动,大致的步骤都是差不多的。
  • 首先使能某个外设驱动设备
  • 在board.h文件中定义该外设驱动相关的宏
  • 在apm32_msp.c文件中添加对应外设驱动的引脚初始化代码(当然有些简单的外设,已经把引脚的初始化代码写到了外设驱动中,不需要我们再编写了,我们可以查看对应外设驱动代码是否有把引脚的初始化代码写进去了)
  • 根据RTT的官方的文档介绍,添加外设驱动的使用代码。
上面并没有把所有的外设驱动使用都介绍完,还有一些没有介绍的外设驱动使用起来也比较简单,这里就不一一介绍了。大家只要对照着上面的使用步骤,然后结合RTT的官方文档介绍,基本都可以使用起来。



  • 本文系21ic原创,未经许可禁止转载!

网友评论