STM32入门(30-35)
低功耗模式原理
本节用到的固件库函数
- NVIC_SystemLPConfig(手册13.2.22)//选择系统进入低功耗模式的条件
- PWR_EnterSTOPMode(手册14.2.6)//进入停机(STOP)模式
- PWR_WakeUpPinCmd (手册14.2.5)//使能或者失能唤醒管脚功能
- PWR_EnterSTANDBYMode (手册 14.2.7)//进入待机(STANDBY)模式
文件用到了 sys.h,delay.h,relay.h,oled0561.h,led.h,key.h,NVIC.h;这里只需改一下 NVIC.h,NVIC.c即可
睡眠模式程序
NVIC.h
# ifndef __NVIC_H
# define __NVIC_H
# include "sys.h"
extern u8 INT_MARK;//中断标志位
void KEY_INT_INIT (void);
# endif
NVIC.c
# include "NVIC2.h"
u8 INT_MARK;//中断标志位
void KEY_INT_INIT(void)//按键中断初始化
{
NVIC_InitTypeDef NVIC_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//启动GPIO时钟 (需要与复用时钟一同启动)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//配置端口中断需要启用复用时钟
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); //定义 GPIO 中断
EXTI_InitStruct.EXTI_Line=EXTI_Line0;//定义中断线0
EXTI_InitStruct.EXTI_LineCmd=ENABLE;//中断使能
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;//中断模式为 中断
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿触发
EXTI_Init(&EXTI_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel=EXTI0_IRQn;//中断线
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;//使能中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级 2
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;//子优先级 2
NVIC_Init(&NVIC_InitStruct);
}
void EXTI0_IRQHandler(void)//名字是固定的!!!千万不能漏写尤其是0,刚刚搞半天原来是0漏了写
{
if(EXTI_GetITStatus(EXTI_Line0)!=RESET)//判断某条线上的中断是否发生,Set:发生 Reset:没发生
{
INT_MARK=1;//标志位置1,表示有按键中断
EXTI_ClearITPendingBit(EXTI_Line0);//清除LINE 上的中断标志位
}
}
main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "led.h"
# include "key.h"
# include "NVIC2.h"
int main (void) //主程序
{
delay_ms(500); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RELAY_Init();//继电器初始化
LED_Init();
KEY_Init();
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化
OLED_DISPLAY_8x16_BUFFER(0, " SLEEP TEST "); //显示字符串
INT_MARK=0;//标志位清0
NVIC_Configuration();//设置中断优先级
KEY_INT_INIT();//按键中断初始化(PA0是按键中断输入)
//SEVONPEND: 0:只有使能的中断或事件才能唤醒内核。1:任何中断和事件都可以唤醒内核。(0=DISABLE,1=ENABLE)
NVIC_SystemLPConfig(NVIC_LP_SEVONPEND,DISABLE);
//SLEEPDEEP: 0:低功耗模式为睡眠模式。1:进入低功耗时为深度睡眠模式。
NVIC_SystemLPConfig(NVIC_LP_SLEEPDEEP,DISABLE);
//SLEEPONEXIT: 0: 被唤醒进入线程模式后不再进入睡眠模式。1:被唤醒后执行完相应的中断处理函数后进入睡眠模式。
NVIC_SystemLPConfig(NVIC_LP_SLEEPONEXIT,DISABLE);
while(1)
{
GPIO_SetBits(LEDPORT,LED1);//LED控制,亮
OLED_DISPLAY_8x16_BUFFER(4," CPU Sleep! "); //显示字符串
delay_ms(500);
__WFI(); //进入睡眠模式,等待中断唤醒
// __WFE(); //进入睡眠模式,等待事件唤醒
GPIO_ResetBits(LEDPORT,LED1);//LED控制,灭
OLED_DISPLAY_8x16_BUFFER(4," CPU WAKE UP! "); //显示字符串
delay_ms(500); //
}
}
实验现象
停机模式程序
文件跟 31一样,只是 main.c 不一样;
main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "led.h"
# include "key.h"
# include "NVIC2.h"
int main (void) //主程序
{
delay_ms(500); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RELAY_Init();//继电器初始化
LED_Init();
KEY_Init();
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化
OLED_DISPLAY_8x16_BUFFER(0, " STOP TEST "); //显示字符串
INT_MARK=0;//标志位清0
NVIC_Configuration();//设置中断优先级
KEY_INT_INIT();//按键中断初始化(PA0是按键中断输入)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能电源PWR时钟
while(1)
{
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));
OLED_DISPLAY_8x16_BUFFER(4," CPU Stop! "); //显示字符串
delay_ms(500);
PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI);//进入停机模式
RCC_Configuration(); //系统时钟初始化(停机唤醒后会改用HSI时钟,需要重新对时钟初始化)
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0));//LED控制,灭
OLED_DISPLAY_8x16_BUFFER(4," CPU WAKE UP! "); //显示字符串
delay_ms(500); //
}
}
实验现象跟睡眠模式差不多。
待机模式程序
文件跟 31一样,只是 main.c 不一样;
main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "led.h"
# include "key.h"
# include "NVIC2.h"
int main (void) //主程序
{
delay_ms(500); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RELAY_Init();//继电器初始化
LED_Init();
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化
OLED_DISPLAY_8x16_BUFFER(0, " STANDBY TEST "); //显示字符串
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能电源PWR时钟
PWR_WakeUpPinCmd(ENABLE);//WKUP唤醒功能开启(待机时WKUP脚PA0为模拟输入)
GPIO_ResetBits(LEDPORT,LED1);
OLED_DISPLAY_8x16_BUFFER(4," CPU RESET! "); //显示字符串
delay_ms(500); //
GPIO_SetBits(LEDPORT,LED1);
OLED_DISPLAY_8x16_BUFFER(4," STANDBY! "); //显示字符串
delay_ms(500); //
PWR_EnterSTANDBYMode();//进入待机模式
//因为待机唤醒后程序从头运行,所以不需要加while(1)的主循环体。
实验现象
看门狗
本节用到的固件库函数
- IWDG_WriteAccessCmd(手册 12.2.1)//使能或者失能对寄存器IWDG_PR和IWDG_RLR的写操作
- IWDG_SetPrescaler(手册 12.2.2)//设置IWDG预分频值
- IWDG_SetReload(手册 12.2.3)//设置IWDG重装载值
- IWDG_ReloadCounter(手册 12.2.4)//按照IWDG重装载寄存器的值重装载IWDG计数器
- IWDG_Enable(手册 12.2.5)//使能IWDG
- WWDG_SetPrescaler(手册 22.1.2)//设置WWDG预分频值
- WWDG_SetWindowValue(手册 22.1.3)//设置WWDG窗口值
- WWDG_Enable(手册 22.1.6)//使能WWDG并装入计数器值
- WWDG_ClearFlag(手册 22.1.8)//清除早期唤醒中断标志位
- WWDG_EnableIT(手册 22.1.4)//使能WWDG早期唤醒中断(EWI)
- WWDG_SetCounter(手册 22.1.5)//设置WWDG计数器值
文件与 31 相同,只需添加 iwdg.h,iwdg.c 文件即可(Lib 文件添加 stm32f10x_iwdg.c)
- 窗口看门狗必须在规定的时间范围内喂狗入;作用是监控单片机运行时效是否精确
独立看门狗程序
iwdg.h
# ifndef __IWDG_H
# define __IWDG_H
# include "sys.h"
//看门狗定时时间计算公式:Tout=(预分频值*重装载值)/40 (单位:ms)
//当前pre为64,rlr为625,计算得到Tout时间为1秒(大概值)。
//(64x625)/40=1000ms=1s
# define pre IWDG_Prescaler_64
# define rlr 625
void IWDG_Init(void);
void IWDG_Feed(void);
# endif
iwdg.c
- 独立看门狗的溢出时间计算:Tout=(预分频值*重装载值)/40 (单位:ms) prer是分频系数,rlr是重装值 其中:/40是独立看门狗专有的时钟,时钟频率为40k
# include "iwdg.h"
void IWDG_Init(void){ //初始化独立看门狗
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //使能对寄存器IWDG_PR和IWDG_RLR的写操作(取消寄存器写保护)
IWDG_SetPrescaler(pre); //设置IWDG预分频值
IWDG_SetReload(rlr); //设置IWDG重装载值
IWDG_ReloadCounter(); //按照IWDG重装载寄存器的值重装载IWDG计数器
IWDG_Enable(); //使能IWDG
}
void IWDG_Feed(void){ //喂狗程序
IWDG_ReloadCounter();//固件库的喂狗函数
}
main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "led.h"
# include "key.h"
# include "iwdg.h"
int main (void){//主程序
delay_ms(500); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RELAY_Init();//继电器初始化
LED_Init();//LED
KEY_Init();//KEY
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化---------------"
OLED_DISPLAY_8x16_BUFFER(0," IWDG TEST "); //显示字符串
OLED_DISPLAY_8x16_BUFFER(4," RESET! "); //显示字符串
delay_ms(800); //
OLED_DISPLAY_8x16_BUFFER(4," "); //显示字符串
IWDG_Init(); //初始化并启动独立看门狗
while(1){
IWDG_Feed(); //喂狗
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){
delay_s(2); //延时2秒,使程序不能喂狗而导致复制
}
}
}
实验现象
窗口看门狗程序
文件和 31 相同,只需增加 wwdg.h,wwdg.c 即可
wwdg.h
# ifndef __WWDG_H
# define __WWDG_H
# include "sys.h"
//窗口看门狗定时时间计算公式:
//上窗口超时时间(单位us) = 4096*预分频值*(计数器初始值-窗口值)/APB1时钟频率(单位MHz)
//下窗口超时时间(单位us) = 4096*预分频值*(计数器初始值-0x40)/APB1时钟频率(单位MHz)
# define WWDG_CNT 0x7F //计数器初始值,范围:0x40~0x7F
# define wr 0x50 //窗口值,范围:0x40~0x7F
# define fprer WWDG_Prescaler_8 //预分频值,取值:1,2,4,8
//如上三个值是:0x7f,0x50,8时,上窗口48MS,下窗口64MS。
void WWDG_Init(void);
void WWDG_NVIC_Init(void);
void WWDG_Feed(void);
# endif
wwdg.c
# include "wwdg.h"
void WWDG_Init(void)//初始化窗口看门狗
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);//WWDG 时钟使能
WWDG_SetPrescaler(fprer);//设置IWDG预分频值
WWDG_SetWindowValue(wr);//设置窗口值
WWDG_Enable(WWDG_CNT);//使能看门狗,设置 counter
WWDG_ClearFlag();//清除提前唤醒中断标志位
WWDG_NVIC_Init();//初始化窗口看门狗 NVIC
WWDG_EnableIT();//开启窗口看门狗中断
}
void WWDG_NVIC_Init(void)//窗口看门狗中断服务程序(被WWDG_Init调用)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=WWDG_IRQn;//WWDG 中断
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占2 子优先级 3 组2
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;//抢占 2,子优先级 3,组 2
NVIC_Init(&NVIC_InitStructure);//NVIC初始化
}
void WWDG_Feed(void)//窗口喂狗程序
{
WWDG_SetCounter(WWDG_CNT);//清除提前唤醒中断标志位
}
void WWDG_IRQHandler(void)//窗口看门狗中断处理程序
{
WWDG_ClearFlag();//清除提前唤醒中断标志位
//此处加入在复位前需要处理的工作或保存数据
}
main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "led.h"
# include "key.h"
# include "wwdg.h"
int main (void){//主程序
delay_ms(500); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RELAY_Init();//继电器初始化
LED_Init();//LED
KEY_Init();//KEY
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化---------------"
OLED_DISPLAY_8x16_BUFFER(0," WWDG TEST "); //显示字符串
OLED_DISPLAY_8x16_BUFFER(4," RESET! "); //显示字符串
delay_ms(800); //
OLED_DISPLAY_8x16_BUFFER(4," "); //显示字符串
WWDG_Init(); //初始化并启动独立看门狗
while(1){
delay_ms(54); //用延时找到喂狗的窗口时间
WWDG_Feed(); //喂狗
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){
delay_s(2); //延时2秒,使程序不能喂狗而导致复制
}
}
}
实验现象跟独立看门狗差不多。
定时器原理与应用
本节用到的固件库函数(有的在 28步)
- TIM_ITConfig (手册 19.2.9)//使能或者失能指定的TIM中断
- TIM_GetITStatus (手册 19.2.71)//检查指定的TIM中断发生与否
- TIM_ClearITPendingBit (手册 19.2.72)//清除TIMx的中断待处理位
文件和31相同,只需添加 tim.h,tim.c 即可(Lib 文件需要添加 stm32f10x_tim.c)
tim.h
# ifndef __PWM_H
# define __PWM_H
# include "sys.h"
void TIM3_Init(u16 arr,u16 psc);
void TIM3_NVIC_Init (void);
# endif
tim.c
- Tout = ((重装载值+1)*(预分频系数+1))/时钟频率 :(9999+1)x(7199+1)/72 = 100,0000us=1s
# include "tim.h"
# include "led.h"
//定时器时间计算公式Tout = ((重装载值+1)*(预分频系数+1))/时钟频率;
//例如:1秒定时,重装载值=9999,预分频系数=7199
void TIM3_Init(u16 arr, u16 psc) ////TIM3 初始化 arr重装载值 psc预分频系数
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3
TIM3_NVIC_Init();//开启TIM3中断向量
TIM_TimeBaseInitStructure.TIM_Period = arr; //设置自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; //预分频值
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器向上溢出
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟的分频因子,起到了一点点的延时作用,一般设为TIM_CKD_DIV1
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //TIM3初始化设置
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //使能TIM3中断
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
void TIM3_NVIC_Init(void)//开启TIM3中断向量
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x3; //设置抢占和子优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void TIM3_IRQHandler(void)//TIM3中断处理函数
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //判断是否是TIM3中断
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);//清除TIM3中断标志
//此处写入用户自己的处理程序
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 - GPIO_ReadOutputDataBit(LEDPORT, LED1))); //取反LED
}
}
main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "led.h"
# include "key.h"
# include "tim.h"
int main (void) //主程序
{
delay_ms(500); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RELAY_Init();//继电器初始化
LED_Init();//LED
KEY_Init();//KEY
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化---------------"
OLED_DISPLAY_8x16_BUFFER(0, " TIM TEST "); //显示字符串
TIM3_Init(9999, 7199); //定时器初始化,定时1秒(9999,7199)
while(1)
{
//写入用户的程序
//LED1闪烁程序在TIM3的中断处理函数中执行
}
}
实验现象
CRC与芯片ID原理
本节用到的固件库函数
文件跟 33 一样,没用到另外的文件只需在 main.c 上写即可
main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "led.h"
# include "key.h"
int main (void) //主程序
{
u32 a, b;
u8 c;
u32 y[3] = {0x87654321, 0x98765432, 0x09876543};
delay_ms(500); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RELAY_Init();//继电器初始化
LED_Init();//LED
KEY_Init();//KEY
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化---------------"
OLED_DISPLAY_8x16_BUFFER(0, " CRC TEST "); //显示字符串
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);//开启CRC时钟
while(1)
{
CRC_ResetDR();//复位CRC,需要清0重新计算时先复位
CRC_CalcCRC(0x12345678);//CRC计算一个32位数据。参数:32位数据。返回值:32位计算结果
CRC_CalcCRC(0x23456789);//CRC计算一个32位数据。参数:32位数据。返回值:32位计算结果
a = CRC_CalcCRC(0x34567890);//CRC计算一个32位数据。参数:32位数据。返回值:32位计算结果
CRC_ResetDR();//复位CRC,需要清0重新计算时先复位
b = CRC_CalcBlockCRC(y, 3); //CRC计算一个32位数组。参数:32位数组名,数组长度。返回值:32位计算结果
CRC_SetIDRegister(0x5a);//向独立寄存器CRC_IDR写数据。参数:8位数据。
c = CRC_GetIDRegister();//从独立寄存器CRC_IDR读数据。返回值:8位数据。
//此时,a存放的是3个独立数的CRC结果。(32位)
//b存放的是数组y中3个数据CRC计算结果。(32位)
//c存放的是我们写入的独立寄存器数据0x5a。(8位)
}
}
// 以下是CRC固件库函数,可在主程序中直接调用 //
// RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);//开启CRC时钟,主程序开始时调用
// CRC_ResetDR();//复位CRC,需要清0重新计算时先复位
// uint32_t CRC_CalcCRC(uint32_t Data);//CRC计算一个32位数据。参数:32位数据。返回值:32位计算结果
// uint32_t CRC_CalcBlockCRC(uint32_t pBuffer[], uint32_t BufferLength);//CRC计算一个32位数组。参数:32位数组名,数组长度。返回值:32位计算结果
// uint32_t CRC_GetCRC(void);//从CRC中读出计算结果。返回值:32位计算结果。
// void CRC_SetIDRegister(uint8_t IDValue);//向独立寄存器CRC_IDR写数据。参数:8位数据。
// uint8_t CRC_GetIDRegister(void);//从独立寄存器CRC_IDR读数据。返回值:8位数据。
芯片ID程序
文件复制 34 即可 ,用到了 usart.h ,usart.c
usart.c 需要把中断关闭,因为只用到了 printf 函数
main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "led.h"
# include "key.h"
# include "usart.h"
int main (void) //主程序
{
u32 ID[3];
delay_ms(500); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RELAY_Init();//继电器初始化
LED_Init();//LED
KEY_Init();//KEY
USART1_Init(115200); //串口初始化(参数是波特率)
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化---------------"
OLED_DISPLAY_8x16_BUFFER(0, " CHIP ID TEST "); //显示字符串
ID[0] = *(__IO u32 *)(0X1FFFF7E8); //读出3个32位ID 高字节
ID[1] = *(__IO u32 *)(0X1FFFF7EC); //
ID[2] = *(__IO u32 *)(0X1FFFF7F0); // 低字节
printf("ChipID: %08X %08X %8X \r\n", ID[0], ID[1], ID[2]); //从串口输出16进制ID
if(ID[0] == 0x066EFF48 && ID[1] == 0x48577152 && ID[2] == 0x87241422) //检查ID是否匹配
{
printf("chipID OK! \r\n"); //匹配
}
else
{
printf("chipID error! \r\n"); //不同
}
while(1)
{
}
}
实验现象
把程序烧到开发板后打开超级终端,再按一下单片机的复位按键即可显示,如果芯片ID和程序里写的匹配则返回 OK,反之返回 Error
回顾与总结
关于HAL库
HAL库并不是HAL库就要全面优秀于标准库,它们各有优势用标准库作为入门教学更好,网上相关的资料多、用的人多、有助于对程序框架的理解。HAL库学习起来更简单,也更封闭。可以在入门之后、理解STM32编程框架之后使用。ST公司未来会全面更新、支持HAL库。
在B站上看了 stm32入门100步 已经距离现在是一个月左右了,到这说明已经结束了,但是也是一个新的开始,因为现在只是入门了而已,还有很多很多知识还没学到,继续努力吧!后面还有关于物联网的入门30步我会继续看下去并且也做好笔记,加油,学习真的会上瘾(要是我中专就觉悟了该多好,现在想想非常后悔),但是世上没有后悔药所以大专觉悟还来得及!