查看: 9344|回复: 15
打印 上一主题 下一主题

【擂台挑战赛】马潮老师给出10道MCU实训题,欢迎各位挑战。

[复制链接]
沙发
发表于 2018-4-4 09:22:56 | 显示全部楼层
本帖最后由 LiMeng 于 2018-4-4 09:30 编辑
liliuyang 发表于 2018-4-1 17:03
【NO.1】  使用MCU产生6个方波输出和2个单脉冲输出

题目来源:大学电子基础实验用数字逻辑实验箱改造―― ...

我做出来了,方案如下:用了STC公司最新的STC8单片机,STC8单片机共有5个定时器,分别产生前五个方波,100Hz的定时器进中断进行分频产生最后一个方波,并为按键检测提供状脉冲。
单片机的定时器统一采用自动重载模式,并在溢出后直接翻转I/O口,整个过程除了最后10Hz进中断,其他均为硬件产生,故精度是以单片机的晶振精度为基准的。程序如下:
/***********************************************************************
* 文 件 名: 方波发生器
* 使用芯片: STC8A8K64S4A12
* 使用晶振: 24.0000MHz
* 硬件连接: P0.0连接按键
* 描述功能: P5.4输出3MHz,P3.5输出1MHz,P3.4输出100KHz,P1.3输出10KHz,P0.5输出1KHz,P0.7输出100Hz,P04输出10Hz,P0.1、P0.2为单脉冲
* 日    期: 2018/03/20
* 作    者: 李猛
* 版    本: V1.0
* 修改记录: 无
******************头文件***********************************************/
#include <STC8.H>
#include <intrins.h>

/*****************宏定义***********************************************/
#define CLKO                24000000                        //系统时钟频率
#define u8                        unsigned char
#define u16                unsigned int
#define u32                unsigned long

//=========定时器计算公式==================
#define Time_1T_ms        CLKO/1000                        //定时器1T模式下毫秒级延时计算公式
#define Time_12T_ms        CLKO/12/1000                //定时器12T模式下毫秒级延时计算公式
#define Time_1T_us        CLKO/1000000                //定时器1T模式下微秒级延时计算公式
#define Time_12T_us        CLKO/12/1000000        //定时器12T模式下微秒级延时计算公式

/*****************定义全局变量******************************************/
bit KEY_ON ,FangBo;                //定义标志位

/*************************************************************************
* 函 数 名: Cksel_Init
* 功    能: 初始化系统时钟
* 入口参数: 无
* 出口参数: 无
* 备    注: 无
*************************************************************************/
void Cksel_Init(void)
{
        P_SW2          = 0x80;                //特殊功能寄存器位于扩展RAM区域,访问需先将P_SW2的BIT7设置为1
        IRC24MCR = 0x80;                //使用内部24MHz晶振
        while(!(IRC24MCR & 1));        //等待时钟稳定
        CLKDIV          = 0x00;                //主时钟不分频
        CKSEL          = 0x70;                //选择外部输入时钟信号并在P5.4输出系统时钟的八分频
        P_SW2          = 0X00;
}

/*********************************************************************
* 函 数 名: IO_Init初始化函数
* 功    能: 初始化I/O口
* 入口参数: 无
* 出口参数: 无
* 备    注:
*            PxM1   PxM0
*             0      0     准双向I/O
*             0      1     推挽输出      
*             1      0     高阻输入     
*             1      1     开漏输出  
***********************************************************************/
void IO_Init(void)
{
        P0M1 = 0x00;    P0M0 = 0x00;//    P0 = 0x00;     //P0口设置成准双向I/O  同时全部置低
        P1M1 = 0x00;    P1M0 = 0x00;//    P1 = 0x00;     //P1口设置成准双向I/O  同时全部置低
        P2M1 = 0x00;    P2M0 = 0x00;//    P2 = 0x00;     //P2口设置成准双向I/O  同时全部置低
        P3M1 = 0x00;    P3M0 = 0x00;//    P3 = 0x00;     //P3口设置成准双向I/O  同时全部置低
        P4M1 = 0x00;    P4M0 = 0x00;//    P4 = 0x00;     //P4口设置成准双向I/O  同时全部置低
        P5M1 = 0x00;    P5M0 = 0x00;//    P5 = 0x00;     //P5口设置成准双向I/O  同时全部置低
        P6M1 = 0x00;    P6M0 = 0x00;//    P6 = 0x00;     //P6口设置成准双向I/O  同时全部置低
        P7M1 = 0x00;    P7M0 = 0x00;//    P7 = 0x00;     //P7口设置成准双向I/O  同时全部置低
}

/*********************************************************************
* 函 数 名: Routine_Init
* 功    能: 初始化中断
* 入口参数: 无
* 出口参数: 无
* 备    注: 在主函数初始化的最后开启总中断(EA = 1;)
*          IP、IP2  IPH、IP2H    中断优先级控制寄存器
*             0               0        最低级(0级)
*             0               1        较低级(1级)      
*             1              0        较高级(2级)     
*             1                    1        最高级(3级)  
***********************************************************************/
void Routine_Init(void)
{        
//===============T0============================1MHz 0.5us P3.5
        TL0          = 65536-5*Time_1T_us/10;                //对定时器0赋初值        1T
        TH0          = 65536-5*Time_1T_us/10 >> 8;
        TMOD         &= 0;                                //0000 0000               
        AUXR         |= T0x12;                        //1000 0000
        INTCLKO |= T0CLKO;                        //0000 0001        溢出自动取反
        TF0          = 0;                                //T0溢出标志位清零
        TR0          = 1;                                //T0运行控制位
//        ET0          = 1;                                //定时/计数器T0中断允许位

//===============T1============================100KHz 5us P3.4
        TL1          = 65536-5*Time_1T_us;                //对定时器1赋初值        1T
        TH1          = 65536-5*Time_1T_us >> 8;
        TMOD         &= 0;                                //0000 1000
        AUXR         |= T1x12;                        //0100 0000
        INTCLKO |= T1CLKO;                        //0000 0010        溢出自动取反
        TF1          = 0;                                //T1溢出标志位清零
        TR1          = 1;                                //T1运行控制位
//        ET1          = 1;                              //定时/计数器T1中断允许位
        
//===============T2============================10KHz 50us P1.3
        TL2                 = 65536-50*Time_1T_us;                //对定时器2赋初值        1T
        TH2          = 65536-50*Time_1T_us >> 8;
        AUXR         |= 0x14;                        //0001 0100
        INTCLKO |= T2CLKO;                        //0000 0010        溢出自动取反
//        IE2         |= ET2;                                //定时/计数器T2中断允许位
        
//===============T3============================1KHz 500us  P0.5
        TL3          = 65536-50*Time_1T_us*10;                //对定时器3赋初值        1T
        TH3          = 65536-50*Time_1T_us*10 >> 8;
        T4T3M         |= 0x0B;                        //0000 1011
//        IE2         |= ET3;                                //定时/计数器T3中断允许位
        
//===============T4============================100Hz 5ms        P0.7
        TL4          = 65536-5*Time_12T_ms;                //对定时器3赋初值        12T
        TH4          = 65536-5*Time_12T_ms >> 8;
//        T4T3M         &= 0x0F;                        //0000 1111
        T4T3M         |= 0x90;                        //1001 0000
        IE2         |= ET4;                                //定时/计数器T4中断允许位
}

/*************************************************************************
* 函 数 名: Read_Key
* 功    能: 监测按键子程序
* 入口参数: 无
* 出口参数: Key_return
* 备    注: 无
*************************************************************************/
#define Key_input        P00                //按键输入端口
#define Key_state_0        0                //检测按键状态
#define Key_state_1        1                //确认按键状态
#define Key_state_2        2                //待释放按键状态
u8 Read_Key(void)
{
        static u8 Key_state = 0;                //静态局部变量,仅执行一次
        static u8 Key_return = 0;
        switch(Key_state)
        {
                case Key_state_0:        //检测阶段
                        if(!Key_input)                                        //如果有按键按下
                                Key_state = Key_state_1;        //转入确认阶段
                        break;
                case Key_state_1:        //确认阶段
                        if(!Key_input)                                        //若还有按键按下
                        {
                                Key_state = Key_state_2;        //转入释放阶段
                                Key_return = 1;                                //返回值置“1”
                        }
                        else
                        {
                                Key_state = Key_state_0;        //如果没有按键按下,则认为是抖动,返回检测阶段
                                Key_return = 0;                                //返回值置“1”
                        }
                        break;
                case Key_state_2:        //待释放阶段
                        if(!Key_input)                                        //等待按键释放
                                Key_state = Key_state_2;        //转入检测阶段
                        else
                        {
                                Key_return = 0;
                                Key_state = Key_state_0;        //转入检测阶段
                        }
                        break;
        }
        return Key_return;                                                //返回状态
}

/*************************************************************************
* 函 数 名: main主函数
* 功    能: 执行主程序
* 入口参数: 无
* 出口参数: 无
* 备    注: 无
*************************************************************************/
void main(void)
{        
        Cksel_Init();                //初始化系统时钟
        IO_Init();                        //I/O口初始化
        Routine_Init();     //中断初始化函数
        EA = 1;                                //总中断允许位
        while(1)            //死循环
        {
                if(KEY_ON)                                        //10ms时间到
                {
                        KEY_ON = 0;                        //清除标志位
                        switch(Read_Key())
                        {
                                case 0:        //检测阶段
                                        P01 = 1;
                                        P02 = 0;
                                        break;
                                case 1:        //确认阶段
                                        P01 = 0;
                                        P02 = 1;
                                        break;
                        }        
                }
        };
}

/*************************************************************************
* 函 数 名: TM4_Rountine
* 功    能: 定时器4中断服务程序
* 入口参数: 无
* 出口参数: 无
* 备    注: 无
**************************************************************************/
void TM4_Rountine(void)        interrupt 20
{
        u8 j,i;                                        //定义变量
        
        AUXINTIF &= ~T4IF;                //清中断标志位
        
        if(++j >= 2)                        //两分频
        {
                j = 0;
                KEY_ON = 1;                        //置按键标志位
                if(++i >= 5)                //五分频
                {
                        i = 0;
                        P04 = !P04;                //10Hz P1.0
                }
        }
}

回复 支持 2 反对 0

使用道具 举报

板凳
发表于 2018-4-4 09:48:37 | 显示全部楼层
我也是小白一枚,程序有问题的地方还望大家多多指教
回复 支持 反对

使用道具 举报

地板
发表于 2018-4-13 18:21:37 | 显示全部楼层
本帖最后由 LiMeng 于 2018-4-15 11:09 编辑
liliuyang 发表于 2018-4-1 17:03
【NO.1】  使用MCU产生6个方波输出和2个单脉冲输出

题目来源:大学电子基础实验用数字逻辑实验箱改造―― ...

方波发生器开发计划说明书
为了更好的锻炼和提升大家的单片机实战开发能力,华东师范大学马潮教授根据多年教学和项目开发经验亲自为单片机爱好者出了十道由易到难的MCU开发题目。本项目为十道题目中的第一题。
1.1项目概述
题目来源:大学电子基础实验用数字逻辑实验箱改造――由箱改造成板,目的是学生可以带回到寝室作实验。原数字逻辑实验箱上的多路方波信号源使用了多片逻辑电路芯片实现,现要求使用一片20PIN以下的MCU替代。
1.2项目内容
1、6路方波输出源,方波的频率为1M、100K、10K、1K、100Hz、10Hz
2、2路单脉冲源。外接一个按键,按下按键后,一个I/O口输出低电平(另一个输出高电平),释放按键后该I/O口恢复高电平(另一个恢复为低电平),要作按键消抖处理。
3、给出系统实现的设计方案,方案中必须给出各个输出方波信号的精度估计,以及可能产生的设计误差估算和产生原因。
4、具体动手实现
5、实现后,分析完成6路方波产生功能所占用CPU的百分比。
6、 本设计方案先在实验板上采用已有的MCU实现,再整理成工程上实用的设计方案,此时只是完成上面所描述的功能,考虑到性价比、PCB占用的面积等,应该选择20PIN以下的MCU芯片。
1.3项目方案
产生脉冲方波需使用MCU内部硬件定时器资源,故MCU需满足自带定时器功能,且不应少于三个。
产品输出6路方波和两路脉冲和一个按键,共占用9个I/O口,加上MCU必须的电源2个,下载程序端口2个,为保证方波的精度,使用外部晶振,故外部晶振占用I/O 2个,共需引脚9+2+2+2=15个。
使用芯片:STC15F2K60S2        20脚
使用晶振:6.000MHz (备用12.000MHz、24.000MHz)
STC15F2K60S2芯片内部拥有3个定时器,且含有自动重载与自动I/O翻转功能。可满足高频的产生,且启动后不占用CPU。
频率        1MHz        100KHz        10KHz        1KHz        100Hz        10Hz
周期时间        1us        10us        100us        1ms        10ms        100ms
翻转时间        0.5us        5us        50us        500us        5ms        50ms
1MHz、100KHz和10KHz采用定时器硬件翻转I/O实现,保证其正常工作后不占用CPU资源。控制产生10KHz的定时器溢出后进中断,完成1KHz、100Hz、10Hz的产生。
考虑到PCB的布局方便参照单片机引脚顺序(具体见下载器中的引脚定义),引脚定位
频率        1MHz        100KHz        10KHz        1KHz        100Hz        10Hz
引脚        P3.5         P3.4                 P3.0                 P3.6   P3.7        P3.3
按键连接P1.4,要翻转的I/O口为P1.0、P1.1

因采用的是新型51单片机,故程序执行效率有提高,但还达不到1T,根据数据手册提供的执行效率0.7计算,6*0.7*50 = 210条指令。在中断函数中执行代码最多为15条指令,假设其指令均为慢指令(3个时钟执行一条)15*3=45占比为45/210=21.43%,且程序也并非每次进去都执行全部的程序,故CPU有充足的时间执行完指令。
        采用6.000MHz晶振可满足题目要求,虽降低了单片机的执行速度,但增加了系统稳定性,也降低了单片机功耗。
1.4题目回答:
3、由于1MHz与100KHz10KHz均有定时器自动重载和自动翻转I/O产生,故这三个频率的精度取决于外部晶振的精度,只要外部晶振保证精度足够,这三个频率则相当准确。
        1KHz、100Hz、10Hz这三个均来自10KHz的分频,由于要进中断执行程序,故会有相应的误差,准度由程序决定,根据计算准度可以保证达到,精度误差最多在10条指令的执行时间以内,依旧采取最大误差计算方法:10*3*1/6=5us,1KHz时间500us,精度99%;1KHz时间5000us,精度99.9%;10Hz时间50000us,精度99.99%。
5、21.43%(具体参见项目方案)

针对中断函数更加好的改进
采用“--”“==”的方式写出的程序经编译,得到汇编15条指令,占用CPU21.43%
采用“++”“==”的方式写出的程序经编译,得到汇编20条指令,占用CPU28.57%
采用“++”“>=”的方式写出的程序经编译,得到汇编25条指令,占用CPU35.71%
(具体参见程序),故一个好的程序,经过程序的优化,对CPU效率的提升是有很大效果的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 加入因仑

本版积分规则

快速回复 返回顶部 返回列表