芯片基础属性
- 时钟
- 中断
- 堆栈
- 看门狗
- 工作模式
时钟篇
一时候不知应该从哪里开始写起。不过整体来说的话,这个系列的单片机时钟结构其实并不算复杂。
系统时钟
主要是四个时钟的概念:
外部时钟都是要外接晶振的,内部时钟则是通过RC震荡电路来提供脉冲。
上电的时候默认是都选用内部时钟的。
fsys是系统时钟的意思,可以由高速时钟fH(即HXT或者HIRC其中一个)提供,也可以由低速时钟(即LXT或者LIRC其中一个)提供。高速时钟还提供了多种分频,相对来说比较灵活。
时钟,指令周期,时钟周期
合泰的8位单片机大部分都是4T架构的,可以通过数据手册提供的信息推导出来。
如果系统是16M的话,1个时钟周期则是1/16000000=0.0625us。
指令周期为0.25us的话也就是说明两者关系如下:
指令周期 = 时钟周期 * 4
同理可得,
8M的指令周期是0.5us,时钟周期是0.125us。
12M的指令周期是0.33us,时钟周期是0.08us,这个除不尽。
所有指令都可在 1~3 个指令周期内完成。
这个也是数据手册提到的,一条指令并不是指一句C语言代码,而是将这个C的代码转成的汇编指令,再看用了多少条汇编指令来完成。所以就算是看到的一个简单的C语言程序,转成汇编的话也是几条指令。
如图只是把PB4翻转一下,这里用了18条汇编指令,如果是按照8M的时钟,指令周期位0.5us,一个简单的操作用了9us。
但是如果用异或来完成这个操作,转换成的汇编指令就只有6条,耗时3us。
所以用这种主频较低的单片机就很锻炼程序功底的。很多东西随便都能写出来,但是如果要保证效率,同时兼顾ROM和RAM空间,就要考虑非常多东西,我的意思是,没什么必要不要折腾自己,外面很多资源丰富的单片机😢
高低速时钟选择范围
内部高速时钟只有三个选择,8M,12M,16M,外部时钟可选范围比较大,最大是16M,我还没有试过看看超过16M会发生什么🧐
内部低速时钟是整数的32kHz,和外部低速晶振不一样。
注意事项
说一点比较杂的东西。
比如要从HIRC切换到HXT的时候,要确保HXT能用之后才切换过去,然后再关掉HIRC。因为这个单片机没有那种检测不到外部时钟脉冲的话自动切回内部时钟,如果检测不到还把内部时钟关掉的的话,整个程序就直接停掉了😒
中断
片内外设的中断控制,一般是总中断控制总开关,然后才是各个外设的开关,关系如图。
中断向量
中断触发的时候,会先检测总中断以及对应外设中断开关有无开启,然后再跳转到对应的地址,从那个地方开始执行中断部分的程序,执行完再跳转回去。这个跳转的地址就是中断向量,用法就是使用特殊的函数指定一个中断函数名字(可以随便起名)以及指定一个中断向量。
两种指定中断函数的写法。
void __attribute((interrupt(vector))) isr_name(void)
DEFINE_ISR(isr_name, vector)
系统的头文件把上面那一长串封装成下面这个相对简洁的宏,效果是一样的。
多功能中断
有部分中断共同使用同一个中断向量,举个例子🌰
假设多功能中断1包含外设A中断和外设B中断,假设A的中断触发了,进入到对应的中断函数里面,B中断触发也是进入到同一个中断函数,如果不想A中断触发执行到B中断部分的程序,那就要进入中断之后马上判断是哪个中断请求位进来的,然后再根据不同的中断请求位去到不同的分支里面去执行程序。
多功能中断和其他普通的中断不一样,其他中断的请求标志位是由硬件自动清的,但是多功能中断里面包含的中断要手动清。
中断优先级
中断函数不存在嵌套的情况,数据手册有提到进入中断的时候,总中断会被关掉,这个我不确定是硬件强行关掉,程序打不开这种,还是其他意思。
有一种情况可能就是当中断A触发,还在执行中断函数的时候,中断B和C的请求位置1,等中断A执行完之后,比较中断B和C的优先级,高的先执行这种,我还没有针对性测试过,但是大概率是这么回事儿。
堆栈
这个堆栈就是用来存放跳转前的地址,正常情况能让程序跳转的操作只有中断
和函数调用
。也就意味着函数调用不能内嵌太多层数,否则会出现堆栈溢出,程序不一定会马上复位,因为当前的程序指针已经不知道跑到哪里去了,程序大概率是不能正常运行,看门狗没有及时清零这个才会导致复位。
该款MCU的堆栈层有8层,还算可以吧,因为我用过堆栈层只有2层和4层的,用起来相当憋屈😤
主要注意以下几个点:
- main函数也会占用一层堆栈。
- 因为不知道中断什么时候会触发,如果堆栈刚好满8层的时候,又跳转到中断函数里面,直接溢出了。所以要尽量预留一层堆栈给中断。
- 中断里面最好也不要调用函数。
- 看门狗关掉的话,堆栈溢出就没有办法复位了。
如果不确定各种函数内嵌有无超过堆栈,可以查看工程里面的MAP文件,这个文件其中一个功能是可以看到堆栈使用的情况,但是要编译成功之后才有这个文件的,编译失败的话会自动删除这个文件的。
从这个图看的话,貌似main函数不占一层堆栈,可能是由编译器识别到的一个程序入口,并不是自己指定地址的一个函数,所以可能不算函数吗?只要保证最后面的数字不大于7就行了,因为要预留一层给中断。
看门狗
使用的时钟固定是由内部低速时钟LIRC提供。可以手动打开和关闭,溢出周期可以自己配置,但是要注意这里面的时间不是那种直线上升的连续关系。强调这一点是因为,不同系列的单片机看门狗功能有些差别,部分单片机我印象中是关不了看门狗的,而且溢出的时间也是不完全一样的。
工作模式
还没有仔细研究过,一直都是用的快速模式来跑程序😳总感觉那个休眠模式跟我想得不太一样。
程序配置
HT_Common.h
这个头文件相当于就用来放一些不好分类,比较乱的东西。
#ifndef _HT_COMMON_H_
#define _HT_COMMON_H_
/*----------------通用宏封装----------------*/
enum Bool
{
False = 0,
True = 1,
};
enum Function_Status
{
Disable = 0,
Enable = 1,
};
enum Parameter_Check
{
Parameter_Normal = 0,
Parameter_Abnormal = 1,
};
#define REG_2_BIT (0b00000011)
#define REG_3_BIT (0b00000111)
#define REG_4_BIT (0b00001111)
#define REG_5_BIT (0b00011111)
#define REG_6_BIT (0b00111111)
#define REG_7_BIT (0b01111111)
/*----------------MCU系统级宏封装----------------*/
#define EMI_Cmd(x) _emi = x
/* 总中断使能 */
#define Feed_Dog() _clrwdt();
/* 清除看门狗 */
#define Watch_Dog_Cmd(x) _wdtc = (_wdtc&(~(REG_5_BIT<<3)))|(x<<3)
/* 看门狗功能模块使能除能 */
typedef enum
{
Watch_Dog_Disable = 21,
Watch_Dog_Enable = 10,
}Watch_Dog_T;
#define Watch_Dog_Period(x) _wdtc = (_wdtc&(~(REG_3_BIT<<0)))|(x<<0)
/* 看门狗溢出时间,注意倍率不是连续的 */
typedef enum
{
Watch_Dog_2e8 = 0, /* 8 ms */
Watch_Dog_2e10, /* 32 ms */
Watch_Dog_2e12, /* 128 ms */
Watch_Dog_2e14, /* 512 ms */
Watch_Dog_2e15, /* 1 s */
Watch_Dog_2e16, /* 2 s */
Watch_Dog_2e17, /* 4 s */
Watch_Dog_2e18, /* 8 s */
}Watch_Dog_Period_T;
#define System_Reset() _rstc = 0xFF
/* 系统复位 */
#define System_ResetFlag_Reset() _rstf = 0
/* 软件复位标志位清零 */
#define System_Clock_Config(x) _scc = (_scc&(~(REG_3_BIT<<5)))|(x<<5)
/* 系统时钟选择 */
typedef enum
{
System_Clock_FH = 0,
System_Clock_FH_Div2,
System_Clock_FH_Div4,
System_Clock_FH_Div8,
System_Clock_FH_Div16,
System_Clock_FH_Div32,
System_Clock_FH_Div64,
System_Clock_FSub,
}System_Clock_Config;
#define High_FreqClock_Config(x) _fhs = x
/* 高频时钟选择 */
typedef enum
{
High_FreqClock_HIRC = 0,
High_FreqClock_HXT,
}High_FreqClock_Config_T;
#define Low_FreqClock_Config(x) _fss = x
/* 低频时钟选择 */
typedef enum
{
Low_FreqClock_LIRC = 0,
Low_FreqClock_LXT,
}Low_FreqClock_Config_T;
#define HIRC_Cmd(x) _hircen = x
/* 内部高速RC振荡时钟使能 */
#define HIRC_Freq_Config(x) _hircc = (_hircc&(~(REG_3_BIT<<2)))|(x<<2)
/* 内部高速RC频率选择 */
typedef enum
{
HIRC_Freq_8MHZ = 0,
HIRC_Freq_16MHZ = 2,
}HIRC_Freq_Config_T;
#define HIRC_StableFlag() _hircf
/* 内部高速RC振荡电路稳定标志位 */
#define HXT_Cmd(x) _hxten = x
/* 外部高速晶振使能 */
#define HXT_Freq_Config(x) _hxtm = x
/* 外部高速晶振频率选择 */
typedef enum
{
HXT_Freq_LE10MHZ,
HXT_Freq_GT10MHZ,
}HXT_Freq_Config_T;
#define HXT_OSC_SOURCE HXT_OSC_8M
#define HXT_OSC_8M 0x01
#define HXT_OSC_16M 0x02
#define HXT_StableFlag() _hxtf
/* 外部高速晶振稳定标志位 */
#define High_Clock_Dor_Control(x) _fhiden = x
/* CPU休眠时高速时钟关闭 */
#define Slow_Clock_Dor_Control(x) _fsiden = x
/* CPU休眠时低速时钟关闭 */
/*------------------多功能中断部分------------------*/
#define MUL_FUNC_0_IntAddress 0x0C
#define MUL_FUNC_1_IntAddress 0x10
#define MUL_FUNC_2_IntAddress 0x14
#define MUL_FUNC_0_InterruptCmd(x) _mf0e = x
#define MUL_FUNC_1_InterruptCmd(x) _mf1e = x
#define MUL_FUNC_2_InterruptCmd(x) _mf2e = x
#define MUL_FUNC_0_FlagReset() _mf0f = 0
#define MUL_FUNC_1_FlagReset() _mf1f = 0
#define MUL_FUNC_2_FlagReset() _mf2f = 0
/*----------------变量声明----------------*/
typedef enum
{
HXT_16M = 0x80,
HXT_8M = 0x20,
LXT_32K = 0x10,
HIRC_16M = 0x08,
HIRC_8M = 0x02,
LIRC_32K = 0x01,
}System_Clock_T;
typedef struct
{
System_Clock_T Clock;
}System_Info;
extern System_Info Sys;
/*----------------函数声明----------------*/
void System_Init(void);
void System_HIRC_Config(HIRC_Freq_Config_T fre_select);
void System_HXT_Config();
#endif
时钟部分
先说明一点,单片机是可以使用12M时钟的,但是由于后续计算不是很舒服,我在写这个库的时候直接不预留了。
高速时钟配置
这个部分是配置内部高速时钟HIRC。
void System_HIRC_Config(HIRC_Freq_Config_T fre_select)
{
HIRC_Cmd(Enable); /* HIRC使能 */
HIRC_Freq_Config(fre_select); /* HIRC频率选择:8MHz或者16MHz */
while(HIRC_StableFlag() == False); /* 等待HIRC稳定 */
High_FreqClock_Config(High_FreqClock_HIRC); /* 高速时钟选择:HIRC */
Sys.Clock = (fre_select == HIRC_Freq_8MHZ)?(HIRC_8M):(HIRC_16M);
return ;
}
这个部分是配置外部高速时钟HXT。
void System_HXT_Config()
{
/* 引脚功能复用 */
GPIOC_0_Mode(IO_Mode4);
GPIOC_1_Mode(IO_Mode4);
#if (HXT_OSC_SOURCE == HXT_OSC_8M)
HXT_Freq_Config(HXT_Freq_LE10MHZ); /* HXT频率<10MHz */
#elif (HXT_OSC_SOURCE == HXT_OSC_16M)
HXT_Freq_Config(HXT_Freq_GT10MHZ); /* HXT频率>10MHz */
#endif
HXT_Cmd(Enable); /* 使能外部8M晶振 */
while(HXT_StableFlag() == False); /* 等待HXT稳定 */
High_FreqClock_Config(High_FreqClock_HXT); /* 高速时钟选择:HXT */
HIRC_Cmd(Disable); /* 关闭HIRC */
#if (HXT_OSC_SOURCE == HXT_OSC_8M)
Sys.Clock = HXT_8M;
#elif (HXT_OSC_SOURCE == HXT_OSC_16M)
Sys.Clock = HXT_16M;
#endif
return ;
}
低速时钟配置
低速时钟就没有那么复杂了,因为频率是不能选的,只能选择外部还是内部,调用一个宏就可以了。
Low_FreqClock_Config(Low_FreqClock_LIRC); /* 低速时钟选择:LIRC */
系统初始化
因为剩下的内容没有太复杂的东西,时钟要调用函数啥的所以单独讲。
系统初始化配置就整理成一个函数。
void System_Init(void)
{
/* 时钟部分 */
Low_FreqClock_Config(Low_FreqClock_LIRC); /* 低速时钟选择:LIRC */
System_HIRC_Config(HIRC_Freq_16MHZ); /* 高速时钟选择:HIRC - 16M */
System_Clock_Config(System_Clock_FH); /* 系统时钟 */
/* 看门狗部分 */
Watch_Dog_Cmd(Watch_Dog_Enable);
Watch_Dog_Period(Watch_Dog_2e18); /* 溢出时间大约为 8 s */
/* 中断部分 */
EMI_Cmd(Enable); /* 总中断使能 */
return ;
}