前言
这篇文章我们来实际上手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的工程。
不出意外,迎接你的就是下图的README界面了。
此时,这个空例程已经导入了工作区,在工作区文件夹中也能看到工程文件夹。但是这个工程名字就是我们选择导入的例程名,显然我更希望能够自己命名工程,因此我一般的做法是将这个空例程复制粘贴一份,再改名。这个原始的例程可以保留,以后需要新工程的时候,直接复制这个空例程即可。
CCS工程在拷贝前,先执行一下Clean
,减少一些不需要的文件,这里由于导入的工程没有编译,所以就不用Clean了。然后保持工程开启状态,在工作区菜单里右键工程选择Copy
,然后再右键选择Paste
,这时会弹出工程改名界面,我这里把工程名改为“LearningGPIO”,然后旧的工程就可以关闭了。
这时你会发现,刚才似乎改名改了个半寂寞,新的工程名字改对了,但文件还是旧的名字。解决方法很简单,手动把所有没被正确改名的旧文件都改成新名字即可(乐)。对于上面建的空工程来说,其实只有主.c文件和syscfg文件需要改名;而对于已经编译和调试过的工程,拷贝之后,还要找出旧名字的链接文件等一些东西,都改成新名字,所以先执行Clean
再拷贝工程是个明智的选择。改名后,拷贝工程就完成了,即可进行下一步开发。
时钟树配置
在点灯之前,我们需要先配置一下芯片型号和系统时钟。由于我使用的番茄派开发板用的是48脚的芯片(当时64脚的没货),需要在System Config右侧点开芯片界面,然后点击SWITCH
来切换型号。
确认完成后,再将器件选为MSPM0G3507,看到右侧是48脚芯片,无报错即可。Project Configuration File
和Board
选项中的内容目前都不用修改,默认即为使用DriverLib和开启SWD调试等。直接进入SYSCTL
选项,这里可以配置系统设置、供电和时钟等,对于时钟,我更喜欢STM32 CubeMX那种图形化时钟树配置,因此勾选Use Clock Tree
选项,然后在左侧点击图标进入时钟树界面。
番茄派开发板上使用的是40MHz有源晶振,因此我这里将HFCLK_IN EXT
的输入频率设为40MHz,并启用它。MSPM0G3507芯片最高主频是80MHz,这里我直接设置PLL进行2倍频给它拉满,这时会提示ULPCLK
频率不能高于40MHz,因此设置UDIV
分频器将其分频至40MHz即可。配置使要注意各个MUX的设置,让你希望使用的时钟进入系统。
可以看到配置完时钟树后,CPUCLK
频率变为了80MHz,下面就可以点灯啦。
GPIO输出——点灯
回到Software
配置界面,添加一项GPIO配置,我这里起名为“GPIO_LED”,默认已经添加了1个引脚,这里我将引脚改名为“G”,方向为输出,设置PORT为PORTA,Pin为4,然后将下面的PinMux
锁定,此时PA4
引脚就将被初始化为输出,并且PORTA被命名为GPIO_LED_PORT,PA4
引脚被命名为GPIO_LED_G_PIN。
配置完成后,点击编译,完成之后,初始化代码就能看到了。然后加入闪灯逻辑代码:
#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_setPins
和DL_GPIO_clearPins
分别是用于将引脚置1和置0的函数,实际上只是操作了DOUTSET31_0
和DOUTCLR31_0
寄存器,这样实现闪灯。而DL_Common_delayCycles
则是DriverLib内置的一个延时函数,延时单位是CPU时钟周期,这样延时简单,但显然很不方便,之后的文章中我们会使用SysTick定时器中断来实现更优雅的延时。
编写完代码后进行编译,由于我使用的是J-Link进行调试,烧录之前需要在工程的Properties中将调试器改为J-Link。
接上板子烧录程序,效果如下。
上述代码是用置位和复位函数实现的闪灯,当然也可以使用翻转函数实现:
#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
中启用内部上拉电阻。
番茄派开发板的按钮是一端接地,一端串一个小电阻至单片机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);
}
}
}
GPIO中断——旋转编码器
番茄派开发板上有一个旋转编码器便于交互,下面使用这个旋转编码器来调节LED闪灯的速度。通常旋转编码器的旋转信号有A和B两个通道,每旋转1步,两个通道各会产生1个脉冲,A和B两个脉冲的相位关系与旋转方向有关。因此我们可以以A通道的脉冲上升沿为基准,每当A通道有1个上升沿出现,就检查B通道的电平,根据B通道是高还是低电平,来对编码器的计数值加或减。编码器的脉冲很短,所以上述按键轮询的方式并不适合用在编码器上,但GPIO中断方式则非常合适。另外番茄派开发板上的旋转编码器的两个通道输出接有RC低通滤波器,即在硬件上已经做了消抖处理,因此软件上就不用进行消抖了。
再增加一组GPIO配置,命名为“ENC”,这次添加2个引脚,分别为“A”和“B”,分配至PB15
和PB16
,都配置为输入,然后启用A引脚的中断,极性选择上升沿。配置完成后编写代码。
#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值。
结语
至此,我们就把M0单片机的GPIO常用功能过了一遍,实际上M0的GPIO还有诸如输入滤波器、外部唤醒等功能,不过对于我就没那么常用了。另外这篇文章中的代码很少,我直接全写在了工程的主.c文件中,但这种方式对于较大的工程来说就是个灾难,后续对于更大的工程,我会按自己平时的风格来处理工程结构,也会分享一些关于工程结构和代码风格的心得。