【MSPM0学习笔记】02-时钟树与GPIO

前言

  这篇文章我们来实际上手MSPM0G3507单片机的开发,入门单片机的第一步自然是配置时钟,然后操作GPIO,于是我们先从单片机界的Hello World——点灯开始熟悉M0的单片机GPIO的输入输出基本操作,再试一试配置上下拉、中断等一些稍微高级的操作,顺便也熟悉一下在CCS中如何建立带有System Config工具的工程。

  这里我使用的开发板是上一篇提到的番茄派开发板,下载器使用J-Link OB。

建立工程与拷贝工程

  为了建立带有SDK和System Config工具的CCS工程,官方的建议是选择例程导入实现,因此我们首先打开Resource Explorer,找到MSPM0 SDK,进入对应芯片型号的Example,然后导入工程。我选择的是MSPM0G3507芯片的空工程,由于使用CCS开发,需要选择TI Clang Compiler的工程。

图1 导入SDK空例程

图1 导入SDK空例程

  不出意外,迎接你的就是下图的README界面了。

图2 导入SDK空例程效果

图2 导入SDK空例程效果

  此时,这个空例程已经导入了工作区,在工作区文件夹中也能看到工程文件夹。但是这个工程名字就是我们选择导入的例程名,显然我更希望能够自己命名工程,因此我一般的做法是将这个空例程复制粘贴一份,再改名。这个原始的例程可以保留,以后需要新工程的时候,直接复制这个空例程即可。
  CCS工程在拷贝前,先执行一下Clean,减少一些不需要的文件,这里由于导入的工程没有编译,所以就不用Clean了。然后保持工程开启状态,在工作区菜单里右键工程选择Copy,然后再右键选择Paste,这时会弹出工程改名界面,我这里把工程名改为“LearningGPIO”,然后旧的工程就可以关闭了。

图3 拷贝工程界面

图3 拷贝工程界面

  这时你会发现,刚才似乎改名改了个半寂寞,新的工程名字改对了,但文件还是旧的名字。解决方法很简单,手动把所有没被正确改名的旧文件都改成新名字即可(乐)。对于上面建的空工程来说,其实只有主.c文件和syscfg文件需要改名;而对于已经编译和调试过的工程,拷贝之后,还要找出旧名字的链接文件等一些东西,都改成新名字,所以先执行Clean再拷贝工程是个明智的选择。改名后,拷贝工程就完成了,即可进行下一步开发。

时钟树配置

  在点灯之前,我们需要先配置一下芯片型号和系统时钟。由于我使用的番茄派开发板用的是48脚的芯片(当时64脚的没货),需要在System Config右侧点开芯片界面,然后点击SWITCH来切换型号。

图4 切换芯片型号

图4 切换芯片型号

  确认完成后,再将器件选为MSPM0G3507,看到右侧是48脚芯片,无报错即可。Project Configuration FileBoard选项中的内容目前都不用修改,默认即为使用DriverLib和开启SWD调试等。直接进入SYSCTL选项,这里可以配置系统设置、供电和时钟等,对于时钟,我更喜欢STM32 CubeMX那种图形化时钟树配置,因此勾选Use Clock Tree选项,然后在左侧点击图标进入时钟树界面。
  番茄派开发板上使用的是40MHz有源晶振,因此我这里将HFCLK_IN EXT的输入频率设为40MHz,并启用它。MSPM0G3507芯片最高主频是80MHz,这里我直接设置PLL进行2倍频给它拉满,这时会提示ULPCLK频率不能高于40MHz,因此设置UDIV分频器将其分频至40MHz即可。配置使要注意各个MUX的设置,让你希望使用的时钟进入系统。

图5 时钟树配置

图5 时钟树配置

  可以看到配置完时钟树后,CPUCLK频率变为了80MHz,下面就可以点灯啦。

GPIO输出——点灯

  回到Software配置界面,添加一项GPIO配置,我这里起名为“GPIO_LED”,默认已经添加了1个引脚,这里我将引脚改名为“G”,方向为输出,设置PORT为PORTA,Pin为4,然后将下面的PinMux锁定,此时PA4引脚就将被初始化为输出,并且PORTA被命名为GPIO_LED_PORT,PA4引脚被命名为GPIO_LED_G_PIN。

图6 GPIO输出配置

图6 GPIO输出配置

  配置完成后,点击编译,完成之后,初始化代码就能看到了。然后加入闪灯逻辑代码:

#include "ti_msp_dl_config.h"

int main(void) {
    SYSCFG_DL_init();

    while (1) {
        DL_GPIO_setPins(GPIO_LED_PORT, GPIO_LED_G_PIN);
        DL_Common_delayCycles(8000000);
        DL_GPIO_clearPins(GPIO_LED_PORT, GPIO_LED_G_PIN);
        DL_Common_delayCycles(8000000);
    }
}

  上述代码中,SYSCFG_DL_init函数是由System Config工具生成的初始化代码,位于ti_msp_dl_config.c文件中,可以点进去查看它是如何按之前我们的配置来进行初始化的。DL_GPIO_setPinsDL_GPIO_clearPins分别是用于将引脚置1和置0的函数,实际上只是操作了DOUTSET31_0DOUTCLR31_0寄存器,这样实现闪灯。而DL_Common_delayCycles则是DriverLib内置的一个延时函数,延时单位是CPU时钟周期,这样延时简单,但显然很不方便,之后的文章中我们会使用SysTick定时器中断来实现更优雅的延时。

  编写完代码后进行编译,由于我使用的是J-Link进行调试,烧录之前需要在工程的Properties中将调试器改为J-Link。

图7 修改调试器

图7 修改调试器

  接上板子烧录程序,效果如下。

动图1 单片机闪灯效果

动图1 单片机闪灯效果

  上述代码是用置位和复位函数实现的闪灯,当然也可以使用翻转函数实现:

#include "ti_msp_dl_config.h"

int main(void) {
    SYSCFG_DL_init();

    while (1) {
        DL_GPIO_togglePins(GPIO_LED_PORT, GPIO_LED_G_PIN);
        DL_Common_delayCycles(8000000);
    }
}

GPIO输入——内部上拉的按键

  GPIO输入的配置与输出类似,这里添加一组GPIO,命名为“GPIO_BTN”,添加1个引脚,命名为“MID”(表示中间的按钮),分配至PB14并锁定PinMux,将这个引脚设为输入,并在Digital IOMUX Features中启用内部上拉电阻。

图8 GPIO上拉输入配置

图8 GPIO上拉输入配置

  番茄派开发板的按钮是一端接地,一端串一个小电阻至单片机IO口,因此在未按下时,引脚被内部上拉至高电平;按下后,引脚电压被按钮拉至低电平。配置好后,编写代码,实现按键按下时才闪灯,不按时LED熄灭:

#include "ti_msp_dl_config.h"

int main(void) {
    SYSCFG_DL_init();

    while (1) {
        if (!DL_GPIO_readPins(GPIO_BTN_PORT, GPIO_BTN_MID_PIN)) {
            // 按键按下, 闪灯
            DL_GPIO_setPins(GPIO_LED_PORT, GPIO_LED_G_PIN);
            DL_Common_delayCycles(8000000);
            DL_GPIO_clearPins(GPIO_LED_PORT, GPIO_LED_G_PIN);
            DL_Common_delayCycles(8000000);
        }
        else {
            // 按键未按下, 熄灭
            DL_GPIO_clearPins(GPIO_LED_PORT, GPIO_LED_G_PIN);
        }
    }
}

动图2 按键控制闪灯效果

动图2 按键控制闪灯效果

GPIO中断——旋转编码器

  番茄派开发板上有一个旋转编码器便于交互,下面使用这个旋转编码器来调节LED闪灯的速度。通常旋转编码器的旋转信号有A和B两个通道,每旋转1步,两个通道各会产生1个脉冲,A和B两个脉冲的相位关系与旋转方向有关。因此我们可以以A通道的脉冲上升沿为基准,每当A通道有1个上升沿出现,就检查B通道的电平,根据B通道是高还是低电平,来对编码器的计数值加或减。编码器的脉冲很短,所以上述按键轮询的方式并不适合用在编码器上,但GPIO中断方式则非常合适。另外番茄派开发板上的旋转编码器的两个通道输出接有RC低通滤波器,即在硬件上已经做了消抖处理,因此软件上就不用进行消抖了。

  再增加一组GPIO配置,命名为“ENC”,这次添加2个引脚,分别为“A”和“B”,分配至PB15PB16,都配置为输入,然后启用A引脚的中断,极性选择上升沿。配置完成后编写代码。

图9 GPIO中断配置

图9 GPIO中断配置
#include "ti_msp_dl_config.h"

volatile int DelayCount = 10;
int i;

int main(void) {
    SYSCFG_DL_init();
    NVIC_EnableIRQ(GPIO_ENC_INT_IRQN); // 使能GPIO中断
    while (1) {
        DL_GPIO_togglePins(GPIO_LED_PORT, GPIO_LED_G_PIN); // LED翻转
        for (i = 0; i < DelayCount; i++) {
            DL_Common_delayCycles(800000); // 根据DelayCount延时
        }
    }
}

// GPIO中断服务函数
void GROUP1_IRQHandler(void) {
    if (DL_GPIO_getPendingInterrupt(GPIO_ENC_PORT) == GPIO_ENC_A_IIDX) {
        if (DL_GPIO_readPins(GPIO_ENC_PORT, GPIO_ENC_B_PIN)) { // 逆时针旋转
            DelayCount--;
            if (DelayCount < 10) DelayCount = 10;
        }
        else { // 顺时针旋转
            DelayCount++;
        }
    }
}

  上述代码中,DelayCount变量用于控制LED翻转的延时间隔,它在GPIO中断中根据旋转编码器的旋转方向进行修改。由于在中断中使用这个变量,需要将其定义为volatile类型以防编译器将其优化掉(当然默认其实是无优化)。初始化时使能GPIO中断,在GPIO中断服务函数中,判断若为旋转编码器A通道的中断信号,则检查B通道的电平确定旋转方向,然后修改DelayCount值。

动图3 旋转编码器控制闪灯速度效果

动图3 旋转编码器控制闪灯速度效果

结语

  至此,我们就把M0单片机的GPIO常用功能过了一遍,实际上M0的GPIO还有诸如输入滤波器、外部唤醒等功能,不过对于我就没那么常用了。另外这篇文章中的代码很少,我直接全写在了工程的主.c文件中,但这种方式对于较大的工程来说就是个灾难,后续对于更大的工程,我会按自己平时的风格来处理工程结构,也会分享一些关于工程结构和代码风格的心得。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值