一天速成MSP432

为了备战2021年的全国大学生电子设计竞赛🚀笔者于昨日开始上手TI的MSP432主控并且于今日速成了基础的开发。为了让亲爱的读者们早日脱离苦海,我将在本文中分享:开发环境安装、MSP432架构分析、时钟设置、串口开发、DMA传输和中断(💎一条龙服务)

认识MSP432

在文末有官方文献链接

声明:本文中所有历程都基于MSP432P401R LaunchPad开发板

首先,下图为MSP432R401控制器的总体架构。和STM32对比来看,这个片子架构确实简单,CPU和外设交互只有一对系统总线。外设除了常见的通信口、定时器和存储外,注意到432特色的一些设备:高精度ADC等模拟外设、时钟系统(将在下面专门讨论)和功率控制器。

架构

这个片子的资源可以说是非常充足的,参看下图的引脚分布&功能复用图,左侧都是数字类接口,右侧都是模拟类,可见TI在干扰设计上还是不错的呢。

引脚分布

安装开发环境

在Windows环境上开发TI的产品,还是推荐使用Code Composer Studio这个官方软件。看界面就知道是基于Eclipse做的定制产品,这个软件对TI的工具和资源支持非常好,笔者个人还是非常喜欢的,界面如下:

CCS

安装过程也非常的简单,从官网上分别下载:

注意安装在同一个目录下

下载好后直接双击一路next安装,接着打开CCS配置导入刚安装的SDK。至此,环境就都准备好了!打开Resource Explorer就可以看到官方的驱动库和例子了,还能找到已经配置好了的空模板工程。

Resource Explorer

从点灯开始

点灯就是嵌入式的Hello Word操作,只要我们让这个芯片正常地跑起来,点灯就是翻转GPIO口子的事情。所以我们首先要配置好芯片的时钟,下图是MSP432的时钟树:

时钟

时钟源有7种:低速振荡器(LFXT)、高速振荡器(HFXT)、数字控制振荡器(DCO)、超低功耗振荡器(VLO)、低频振荡器(REFO)、高速内部振荡器(MODOSC)和内部系统振荡器(SYSOSC)。然后读一波手册了解到:系统上电/复位后,MCLK、HSMCLK和SMCLK都默认为DCO时钟源且不分频,ACLK和BCLK默认为LFXT时钟源。这里要注意到DCO是TI设计很棒的一个时钟源,它的频率是可以随时编程改变的(可高达48M),而其他时钟源基本都是硬件定死的参数。

基于此,我们已经初步了解了时钟的产生与配置,就可以开始写代码了!🚩

工程中除了下面我们自己写的文件外,还包含了官方的DriverLibrary,以及系统文件startup_msp432p401r_ccs.csystem_msp432p401r.c两个重要文件。完整工程见文末的Git链接

首先是系统初始化。这里我们把DCO的频率设置为48MHz,也就意味着在默认配置下,系统的总时钟(MCLK)和子系统/外设时钟(HSMCLK、SMCLK)都是48MHz。

1
2
3
4
5
6
7
8
9
void sysClock_init()
{
// 停止系统看门狗定时器
MAP_WDT_A_holdTimer();

// 设置DCO中心频率为48MHz
CS_setDCOCenteredFrequency(CS_DCO_FREQUENCY_48);

}

然后在main程序中不断延时翻转LED状态即可。

这边包含的ti/devices/msp432p4xx/driverlib/driverlib.h头文件读者可以打开观察,是TI官方驱动库的总头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <ti/devices/msp432p4xx/driverlib/driverlib.h>

int main(void)
{
volatile uint32_t i;
volatile uint32_t b =0;

// 系统时钟初始化
sysClock_init();

// 外设初始化
GPIO_init();

while(1)
{
// Toggle P1.0 output
GPIO_toggleOutputOnPin(
GPIO_PORT_P1,
GPIO_PIN0
);

GPIO_toggleOutputOnPin(
GPIO_PORT_P2,
GPIO_PIN1
);
if(b)
{
GPIO_toggleOutputOnPin(
GPIO_PORT_P2,
GPIO_PIN2
);
b=0;
}
else
b=1;


// Delay
for(i=4000000; i>0; i--);

}
}

注意这里我们编写了一个GPIO初始化函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void GPIO_init()
{
// P1.0 为红色指示灯
MAP_GPIO_setAsOutputPin(
GPIO_PORT_P1,
GPIO_PIN0);

// P2.0/1/2 为RGB灯
MAP_GPIO_setAsOutputPin(
GPIO_PORT_P2,
GPIO_PIN0);
MAP_GPIO_setAsOutputPin(
GPIO_PORT_P2,
GPIO_PIN1);
MAP_GPIO_setAsOutputPin(
GPIO_PORT_P2,
GPIO_PIN2);

}

然后就可以看到开发板上灯不停闪了

基础通信(UART)

eUSCI-UART框图解析

接下来介绍的就是MSP432的基础通信外设了——eUSCI(全称:Enhanced Universal Serial Communication Interface),从引脚分布图上就可以观察到在432上有两个eUSCI实例:

  • A实例:可配置为UART、IrDA和SPI
  • B实例:可配置为IIC和SPI

而现在我们只关注UART配置,那么我们使用到的就是A实例,该实例有4个通道,每个通道的UART实现如下框图:

UART

当我们使用UART通信时,需要依次配置图中的时钟源选择器、波特率控制器和相应的使能位。操作还是相对好理解的,但是配置波特率的几个参数还是挺头疼的,有

  • UCBR:时钟预分频寄存器
  • UCBRF:调制1阶段寄存器
  • UCBRS:调整2阶段寄存器
  • UCOS16:过采样控制寄存器(Universal Communication OverSampling),16代表使用16倍过采样率

直接看名字和框图的话,UCBR还好理解,但是这个UCBRS和UCBRF只能根据官方手册硬算和查表,整体步骤如下:

  1. 计算分频因子

    $$
    N=\frac{f_{BRCLK}}{BaudRate}
    $$

  2. 过采样选择

    $$
    \text{UCOS16}=\begin{cases}
    1, &N>16, &使能过采样 \\
    0, &N<16, &不使能
    \end{cases}
    $$

  3. 配置UCBR分频器参数

    $$
    \text{UCBR} = \begin{cases}
    \text{INT}(\frac{N}{16}), &N>16 \\
    \text{INT}(N), &N<16
    \end{cases}
    $$

    以下的两个参数只有当过采样时有用

  4. 计算UCBRF参数

    $$
    BRF =\text{INT} ([\frac{N}{16} - \text{INT}(\frac{N}{16})]\times 16)
    $$

  5. 查表UCBRS参数

    根据上面计算出的分频因子N,因为一般是有小数部分,所以取出小数部分查下表即可获得UCBRS参数

    UCBRS

探讨环节🧐TI官方没有在框图中给出这两个寄存器的实现原理,但是笔者大致将BRF和BRS理解为波特率微调匹配用途。欢迎读者分享个人想法

通信实战

基于上点灯实验(注意时钟为48MHz),我们选用USCIA3-UART实例以115200波特率进行通信:

首先计算波特率寄存器相关参数:

  • 分频因子

    $$
    N=\frac{48M}{115200}=416.667
    $$

  • BR

    $$
    BR=\frac{416.667}{16} =26
    $$

  • BRF

    $$
    BRF=0
    $$

  • BRS

    $$
    BRS=\text{0xD6}
    $$

则我们可创建一个串口配置对象(eUSCI_UART_ConfigV1类型),然后使用配置对象,对串口实例进行参数初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "BSP.h"

void Uart4OpenMV_init()
{
// GPIO初始化为串口外设
MAP_GPIO_setAsPeripheralModuleFunctionInputPin(
GPIO_PORT_P3,GPIO_PIN2 | GPIO_PIN3,
GPIO_PRIMARY_MODULE_FUNCTION);

// 初始化串口参数
MAP_UART_initModule(EUSCI_A2_BASE, &uartConfig4openMV);

// 使能串口
MAP_UART_enableModule(EUSCI_A2_BASE);
}

接着修改我们的main函数,加入上面编写的初始化函数并调用串口发送数据API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "BSP.h"

int main(void)
{
volatile uint32_t i;
volatile uint32_t b =0;

// 系统时钟初始化
sysClock_init();

// 外设初始化
GPIO_init();
Uart4OpenMV_init();


while(1)
{
// Toggle P1.0 output
GPIO_toggleOutputOnPin(
GPIO_PORT_P1,
GPIO_PIN0
);

GPIO_toggleOutputOnPin(
GPIO_PORT_P2,
GPIO_PIN1
);
if(b)
{
GPIO_toggleOutputOnPin(
GPIO_PORT_P2,
GPIO_PIN2
);
b=0;
}
else
b=1;


// Delay
for(i=4000000; i>0; i--);
UART_transmitData(EUSCI_A2_BASE,'c');
UART_transmitData(EUSCI_A2_BASE,'\n');

}
}

这里附以下BSP.h头文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef _BSP_H_

#include <ti/devices/msp432p4xx/driverlib/driverlib.h>

/**
* @brief 初始化时钟类外设
* @note 无
* @author 江榕煜
* @param None
* @retn None
*/
void sysClock_init();

/**
* @brief 初始化GPIO外设
* @note 无
* @author 江榕煜
* @param None
* @retn None
*/
void GPIO_init();

/**
* @brief 初始化UART外设
* @note 服务于OpenMV通信
* @author 江榕煜
* @param None
* @retn None
*/
void Uart4OpenMV_init();

#endif

现在找个USB转TTL连接上开发板的P3.2和P3.3就可以收到432不停发送的测试数据了。

高级硬件资源

对于MSP432P401这种CPU主频最高48MHz的设备,如果不使用DMA和中断则很难开发出高性能的产品。接下来让笔者依次解析DMA和中断的开发

DMA解析

首先,观察下DMA框架图,可以看到这个DMA架构还是很简单的(ARM的μDMA实例IP),左边是数据通道,右边是控制DMA的一些接口。

DMA

相比起来,STM32的DMA就强大多了,具有FIFO等功能,可以进行数据格式转换等操作

只要配置好DMA控制器中各个通道的参数,就可以启动传输了,可以简要概括为:

  1. DMA实例使能
  2. DMA通道对象选择
  3. DMA通道参数设置
  4. DMA通道内存地址设置
  5. 分配DMA通道中断
  6. 使能DMA中断
  7. 启动DMA传输

中断简析

MSP432的嵌套中断向量控制器(NVIC:Nested Vectored Interrupt Controller)具有如下特性:

  • 共64中断源
  • 每个中断可编程为8级优先次序。注意,0为最高中断优先级
  • 外部不可屏蔽中断

中断的概念相对还是简单的,本文中不加解释。

串口DMA传输实战

基于上基础的串口发送例子,我们将串口的初始化文件升级为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include "BSP.h"

uint8_t recBuffer[1024];
uint8_t getMsg[]="get data\n";

/* DMA Control Table */
#if defined(__TI_COMPILER_VERSION__)
#pragma DATA_ALIGN(controlTable, 1024)
#elif defined(__IAR_SYSTEMS_ICC__)
#pragma data_alignment=1024
#elif defined(__GNUC__)
__attribute__ ((aligned (1024)))
#elif defined(__CC_ARM)
__align(1024)
#endif
uint8_t controlTable[1024];

const eUSCI_UART_ConfigV1 uartConfig4openMV =
{
EUSCI_A_UART_CLOCKSOURCE_SMCLK, // 选择时钟源,注:48MHz
26, // 时钟分频器, UCxBR
0, // 模式寄存器1,UCxBRF
0xD6, // 模式寄存器2,UCxBRS
EUSCI_A_UART_NO_PARITY, // 无校验位
EUSCI_A_UART_LSB_FIRST, // 小端序
EUSCI_A_UART_ONE_STOP_BIT, // 一个停止位
EUSCI_A_UART_MODE, // 普通串口模式
EUSCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION, // 过采样
EUSCI_A_UART_8_BIT_LEN // 数据长度
};

/**
* @brief 初始化UART外设
* @note 服务于OpenMV通信
* @author 江榕煜
* @param None
* @retn None
*/
void Uart4OpenMV_init()
{
// GPIO初始化为串口外设
MAP_GPIO_setAsPeripheralModuleFunctionInputPin(
GPIO_PORT_P3,GPIO_PIN2 | GPIO_PIN3,
GPIO_PRIMARY_MODULE_FUNCTION);

// 初始化串口参数
MAP_UART_initModule(EUSCI_A2_BASE, &uartConfig4openMV);

// 使能串口
MAP_UART_enableModule(EUSCI_A2_BASE);

// 配置串口中断
// MAP_UART_enableInterrupt(EUSCI_A2_BASE,EUSCI_A_UART_RECEIVE_INTERRUPT);
// MAP_Interrupt_enableInterrupt(INT_EUSCIA2);
// // MAP_Interrupt_enableSleepOnIsrExit(); // 退出中断的时候顺手进入低功耗睡眠模式
// MAP_Interrupt_enableMaster();

// DMA初始化
memset(recBuffer, 0x00, 1024);
MAP_DMA_enableModule();
MAP_DMA_setControlBase(controlTable);

MAP_DMA_assignChannel(DMA_CH4_EUSCIA2TX);
MAP_DMA_assignChannel(DMA_CH5_EUSCIA2RX);

MAP_DMA_disableChannelAttribute(DMA_CH4_EUSCIA2TX, // 取消通道的原有属性
UDMA_ATTR_ALTSELECT | // 替代控制结构
UDMA_ATTR_USEBURST | // 只使用突发模式
UDMA_ATTR_HIGH_PRIORITY | // 通道优先级为High
UDMA_ATTR_REQMASK); // 屏蔽来自外设的硬件请求信号
MAP_DMA_disableChannelAttribute(DMA_CH5_EUSCIA2RX,
UDMA_ATTR_ALTSELECT |
UDMA_ATTR_USEBURST |
UDMA_ATTR_HIGH_PRIORITY |
UDMA_ATTR_REQMASK);

MAP_DMA_setChannelControl(UDMA_PRI_SELECT | DMA_CH4_EUSCIA2TX,
UDMA_SIZE_8 | // 单次传输数据单元大小
UDMA_SRC_INC_8 | // 地址增量
UDMA_DST_INC_NONE |
UDMA_ARB_1); // 被仲裁到别的通道前最少传1个数据
MAP_DMA_setChannelControl(UDMA_PRI_SELECT | DMA_CH5_EUSCIA2RX,
UDMA_SIZE_8 |
UDMA_SRC_INC_NONE |
UDMA_DST_INC_8 |
UDMA_ARB_1);

MAP_DMA_setChannelTransfer(UDMA_PRI_SELECT | DMA_CH4_EUSCIA2TX,
UDMA_MODE_BASIC,
getMsg,
(void*) MAP_UART_getTransmitBufferAddressForDMA(EUSCI_A2_BASE),
sizeof(getMsg));
MAP_DMA_setChannelTransfer(UDMA_PRI_SELECT | DMA_CH5_EUSCIA2RX,
UDMA_MODE_BASIC,
(void*)MAP_UART_getReceiveBufferAddressForDMA(EUSCI_A2_BASE),
recBuffer,
4);

MAP_DMA_assignInterrupt(DMA_INT1, 5); // 使能中断
MAP_Interrupt_enableInterrupt(INT_DMA_INT1);

// MAP_DMA_enableChannel(4);
MAP_DMA_enableChannel(5);
}

/**
* @brief 串口中断处理
*/
void EUSCIA2_IRQHandler(void)
{
uint32_t status = MAP_UART_getEnabledInterruptStatus(EUSCI_A2_BASE);

if(status & EUSCI_A_UART_RECEIVE_INTERRUPT_FLAG)
{
// 串口中断处理
}
}

/**
* @brief DMA传输中断
*/
void DMA_INT1_IRQHandler(void)
{
MAP_DMA_disableChannel(5); // 失能DMA通道
MAP_DMA_disableInterrupt(INT_DMA_INT1); // 失能DMA中断
MAP_DMA_enableChannel(4); // 使能DMA发送
}

至于中断程序为什么是这个名字,可以打开start_msp432p401r_ccs.c文件,这是CCS创建工程时系统自己生成的,在里面定义了中断向量表,每个中断对应的实际IRQ函数名字都可以查到

此时注意删除原本main程序中的串口发送API调用,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "BSP.h"

int main(void)
{
volatile uint32_t i;
volatile uint32_t b =0;

// 系统时钟初始化
sysClock_init();

// 外设初始化
GPIO_init();
Uart4OpenMV_init();


while(1)
{
// Toggle P1.0 output
GPIO_toggleOutputOnPin(
GPIO_PORT_P1,
GPIO_PIN0
);

GPIO_toggleOutputOnPin(
GPIO_PORT_P2,
GPIO_PIN1
);
if(b)
{
GPIO_toggleOutputOnPin(
GPIO_PORT_P2,
GPIO_PIN2
);
b=0;
}
else
b=1;


// Delay
for(i=4000000; i>0; i--);
// UART_transmitData(EUSCI_A2_BASE,'c');
// UART_transmitData(EUSCI_A2_BASE,'\n');

}
}

BSP.h文件内容不变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifndef _BSP_H_

#include <ti/devices/msp432p4xx/driverlib/driverlib.h>

/**
* @brief 初始化时钟类外设
* @note 无
* @author 江榕煜
* @param None
* @retn None
*/
void sysClock_init();

/**
* @brief 初始化GPIO外设
* @note 无
* @author 江榕煜
* @param None
* @retn None
*/
void GPIO_init();

/**
* @brief 初始化UART外设
* @note 服务于OpenMV通信
* @author 江榕煜
* @param None
* @retn None
*/
void Uart4OpenMV_init();

#endif

将上工程烧录进开发板,就可以体验一波DMA传输的快感了。

从上代码中,我们可以体验到DMA传输到来的性能优势。由于DMA传输请求是硬件产生的,所以在等待接收过程中,CPU可以完全不管UART实例,等数据来了CPU也不用管,直到配置接收的数据量完全到达后,在中断中CPU对数据进行处理;在发送数据时,DMA的优势更加明显了,让我们计算使用轮询方法发送一个“helloWorld”字符串,采用115200的波特率,总共传输80个bit,也就是需要694us,对于48MHz的主频就是浪费了约3万条指令,而采用DMA传输时,CPU完全是在做别的计算。

上述性能提升只能证明程序性能得到了优化,但并不是绝对计算结果提升量,因为我们观察到在系统框图中CPU和DMA在和外设通信时使用的是同一对数据总线!

参考文献

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2023 RY.J
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信