本文写于 2018年12月29日,距今已超过 1 年,距 2020年03月27日 的最后一次修改也已超过 3 个月,部分内容可能已经过时,您可以按需阅读。如果图片无法显示或者下载链接失效,请给我反馈,谢谢!


Visits: 843

0 0 投票数
评分
学校华东师范大学
专业计算机科学与技术
课程嵌入式系统原理(实践课)
教师沈建华
年份2018年秋

在调试机的超级终端上输入自己学号的后两位,并以空格键结束(下面以尾号为87为例),通过串口将该这两位数传送至MCU,再由MCU触发LED顺序显示这2位数,即先显示8再显示7(数值0显示10下)。显示的方法是以3Hz频率的闪烁,如数值8表现为LED闪烁8下。在两位数显示间熄灭LED灯1S。串口每发送一次字符,显示1次数据,不循环显示。(注:要求LED闪烁及跳变间隔控制需由硬件定时器控制)

非常简单,直接给出代码。

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

/* Standard Includes */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

#define CLK 1000000
// 时钟取1M
#define FREQ 3
// 3Hz频率
#define MAXLEN 10
// 学号的长度

char num[MAXLEN + 2]={0}; // num数组存放接收到的值,LEN表示学号的长度,这里是2,另外还有一个空格(表示结束)和一个'\0'(以方便将char*当作%s使用),所以num的长度是LEN+2
int cursor = 0; // 光标,记录下一个要接收到的字符要写入的num数组的哪个位置
int idx = 0; // 索引,表示当前在处理学号的第几位
int cnt = 0; // 计数,表示需要亮多少次,这里会对字符num[idx]先减去'0'得到数值,然后乘2,因为一次闪烁有亮和灭2个操作
int op = 1; // 表示现在是要开灯还是关灯
int len = 0;

/*
http://software-dl.ti.com/msp430/msp430_public_sw/mcu/msp430/MSP430BaudRateConverter/index.html
*/
const eUSCI_UART_Config uartConfig =
{
    EUSCI_A_UART_CLOCKSOURCE_SMCLK,          // 选用SMCLK(1M)时钟源
    6,                                       // BRDIV = 6 ,clockPrescalar时钟分频系数 
    8,                                       // UCxBRF = 8  firstModReg (BRDIV、UCxBRF、 UCxBRS和SMCLK,用于设置串口波特率)
    17,                                      // UCxBRS = 17 secondModReg
    EUSCI_A_UART_NO_PARITY,                  // 校验位None
    EUSCI_A_UART_LSB_FIRST,                  // 低位优先,小端模式
    EUSCI_A_UART_ONE_STOP_BIT,               // 停止位1位
    EUSCI_A_UART_MODE,                       // UART mode
    1  // 设置为过采样,该数值为1
};

int main(void)
{  
    /* 停用开门狗 */
    MAP_WDT_A_holdTimer();

    //![Simple FPU Config]   
    MAP_FPU_enableModule(); /* 启用FPU加快DCO频率计算,注:DCO是内部数字控制振荡器,默认是3M频率 */    
    MAP_CS_setDCOFrequency(CLK); /* 设置DCO频率为指定频率,此处DCO=1M */
    MAP_CS_initClockSignal(CS_MCLK, CS_DCOCLK_SELECT, CS_CLOCK_DIVIDER_1); /* 设置MCLK(主时钟,可用于CPU主频等),MCLK=DCO频率/时钟分频系数,此处MCLK=DCO=1M */
    MAP_CS_initClockSignal(CS_HSMCLK, CS_DCOCLK_SELECT, CS_CLOCK_DIVIDER_1); /* 设置HSMCLK(子系统主时钟),HSMCLK=DCO频率/时钟分频系数,此处HSMCLK=DCO/1=1M */
    MAP_CS_initClockSignal(CS_SMCLK, CS_DCOCLK_SELECT, CS_CLOCK_DIVIDER_1); /* 设置SMCLK(低速子系统主时钟,可用TimerA频率),SMCLK=DCO频率/时钟分频系数,此处SMCLK=DCO/1=1M */    
    //![Simple FPU Config]

    /* 选用P1.2和P1.3使用UART模式 */
    MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,GPIO_PIN2 | GPIO_PIN3, GPIO_PRIMARY_MODULE_FUNCTION);

    /* 使用uartConfig配置UART_A0 */
    MAP_UART_initModule(EUSCI_A0_BASE, &uartConfig);

    /* 使能UART_A0 */
    MAP_UART_enableModule(EUSCI_A0_BASE);

    MAP_UART_enableInterrupt(EUSCI_A0_BASE, EUSCI_A_UART_RECEIVE_INTERRUPT); /* 使能UART_A0串口接收中断 */ 
    MAP_Interrupt_enableInterrupt(INT_EUSCIA0); /* 使能UART_A0中断 */  

    // 定时器频率 = MCLK / 定时器分频系数
    MAP_Timer32_initModule(TIMER32_0_BASE, TIMER32_PRESCALER_1, TIMER32_32BIT,TIMER32_PERIODIC_MODE);
    MAP_Interrupt_enableInterrupt(INT_T32_INT1);
    MAP_Timer32_enableInterrupt(TIMER32_0_BASE);
    // TIMER32_1_BASE INT_T32_INT2

    MAP_Timer32_startTimer(TIMER32_0_BASE, false);


    MAP_Interrupt_enableMaster();  /*使能中断总开关*/

    // LED2.1作为输出灯,默认熄灭
    GPIO_setAsOutputPin(GPIO_PORT_P2,GPIO_PIN0);
    GPIO_setOutputLowOnPin(GPIO_PORT_P2,GPIO_PIN0);

    while(1);
}

/*UART_A0中断函数*/
void EUSCIA0_IRQHandler(void)
{
    uint32_t status = MAP_UART_getEnabledInterruptStatus(EUSCI_A0_BASE); /* 将UART_A0中断标志位的值赋值给status */
    MAP_UART_clearInterruptFlag(EUSCI_A0_BASE, status); /* 清空UART_A0中断标志位 */
    if(status & EUSCI_A_UART_RECEIVE_INTERRUPT_FLAG) {
        /* 判断是否为UART_A0接受中断 */
        char c = MAP_UART_receiveData(EUSCI_A0_BASE);
        if (c == ' ') {
            len = cursor;
            idx = 0; // 如果收到了空格,说明当前的数据已经接收完毕(以空格键结束),那么接下来就是要闪烁了,所以处理第idx=0位
            cnt = num[idx] - '0'; // 是字符,减去ord('0'),得到数值
            // if (cnt == 0) cnt = 10; // 如果学号某一位是0,那么'0'-'0'=0,需要闪烁10次,所以这里设置为10
            cnt *=2; // 由于下面是把亮、灭看作2个操作,所以闪烁cnt次就有cnt*2次操作
            MAP_Timer32_setCount(TIMER32_0_BASE, CLK / FREQ / 2); // 1M除以3Hz是一次亮灭的时间,如果把亮和灭看作2个操作则还要除以2
        } else {
            // 如果不是空格,那么就把当前接收到的char c存入num数组的cursor指向的位置
            num[cursor++] = c;
            // MAP_UART_transmitData(EUSCI_A0_BASE, c); // 如果必要(方便调试),可以将得到的信息回显,重新输出到UART
        }
    }   
}

/*计时器中断函数*/
void T32_INT1_IRQHandler() {
    MAP_Timer32_clearInterruptFlag(TIMER32_0_BASE);
    if (cnt != 0){
        // 如果cnt不为0,说明还有剩下的操作,说明还要继续闪烁,则执行
        // MAP_UART_transmitData(EUSCI_A0_BASE, cnt+'0'); // 如果必要(方便调试),可以输出看看当前做到第几步了
        if (op)
            GPIO_setOutputHighOnPin(GPIO_PORT_P2,GPIO_PIN0);
        else
            GPIO_setOutputLowOnPin(GPIO_PORT_P2,GPIO_PIN0);
        op = 1 - op; // 根据op是1还是0决定是开灯还是关灯,并将op的值更新为下一次的操作,用op = 1 - op
        cnt--; // 执行了一次操作了
        MAP_Timer32_setCount(TIMER32_0_BASE, CLK / FREQ / 2); // 3Hz频率
    } else {
        // cnt为0,说明当前这一位学号已经闪烁完毕了,下面首先熄灭1秒
        GPIO_setOutputLowOnPin(GPIO_PORT_P2,GPIO_PIN0);
        MAP_Timer32_setCount(TIMER32_0_BASE, CLK);
        idx++; // 然后要对下一位学号进行闪烁
        if (idx < len){
            // 如果当前处理的学号位数没有超过总长度,那么,继续执行下一个学号
            cnt = num[idx] - '0';
            // if (cnt == 0) cnt = 10;
            cnt *= 2; // 与之前的操作类似
        } else {
            cursor = 0; // 如果idx超过了LEN,说明所有学号都闪烁完毕了,这时候,idx > LEN,将不执行闪烁
            // 但是重置 cursor = 0,这样下一次 UART 收到消息的时候,就是从头开始填入了
        }
    }
}

/*
2个特性(我觉得可以给我加分,逃):
一,
可以多次接收值。
最后一位学号处理完,会进入 idx > LEN,如果画成有限状态机,那么不执行任何操作,自旋等待,当计时器触发时,发现不满足任何 if 条件,不执行,在中断绕一圈就回去。
直到重新获得值,在UART中断保存值,并直到空格,重置 idx = 0。
二,
甚至可以接收3个长度的学号,例如输入 154[空格],那么闪烁1下、间隔1秒、闪烁5下、间隔1秒、闪烁4下,当然更长的长度也可以。
原理很简单,len = cursor,然后用 len 控制有限状态机。
*/
0 0 投票数
评分
2条留言
订阅评论
提醒
guest

在点击发表评论按钮时,网络请求的数据包含浏览器版本、操作系统版本和 IP 地址;您的网络服务提供商、雇主或学校、政府机构可能会看到您的访问活动;根据浏览器默认行为、操作系统设置和安全防护软件的设置不同,您的浏览器可能会也可能不会在本地 Cookies 缓存您输入的用户名、邮箱以便下次评论使用。

请对自己的言行负责。

您想以什么身份发表评论
邮箱将在您的评论被回复时给您通知
(可选)如果您也有个人网站,不妨分享一下
我对这篇文章的评分
这篇文章给您带来多大帮助
2 评论
内联反馈
查看所有评论
zxx
zxx Chrome 78.0.3904.108 Windows 10
游客
2019年12月23日 21:20

tttttql

czc
czc Chrome 79.0.3945.88 Windows 10
游客
2019年12月23日 16:53

tql