说一下中断是什么,大概就是说,单片机只有一个核,就是只有一个大脑,他无法一核二用地做事,但有时候迫不得已需要去响应一些紧急的命令,就好比你打游戏开团了,你妈喊你去倒开水,倒开水就会触发咱们人类的“中断”功能。放在单片机上,进行中断操作需要以下几个条件和步骤:拥有中断源、中断控制器正常工作、触发中断、保护现场、响应中断、恢复现场。
看名字可能会比较抽象,我来具体解释一下。
中断源,单片机上会有很多的中断源,也就是有很多办法、或者说“渠道”去触发中断,Uart外设就有很多触发中断的办法,而我们本文涉及到的就是接收信息会触发的中断,具体怎么触发的,后文会详细解释。
中断控制器,这个东西是一个物理存在于单片机内核里面的一块数字电路,这一块电路的功能就是用来管理中断的。对于一些老旧型号的单片机,比如C51单片机,他内部也有这个东西,只不过其中断优先级是固定的,这个控制器只扮演了“总闸”这样的角色。再看CW32这种32位单片机,使用cortex-M0+内核,拥有可编程的中断控制器,单片机上会有很多个中断源,但这是内核可以使用和管理的部分,芯片制造厂使用这一款内核制造单片机,并不会用到所有的中断资源,不只是搭载的功能有限,还受限于封装,很多中断资源会被闲置。但是只要使用芯片的中断,都必须正确配置内核里面的中断控制器,否则中断是无法工作的,因为不论单片机外设设计的如何天花乱坠,外设只负责触发中断,而响应中断的一定是内核。
中断的触发,前面提到了中断源,一个指定的中断只能由特定的、与其绑定的中断源触发,一个中断可能绑定多个中断源,但是只会有一个与中断绑定的中断服务函数,至于什么是中断服务函数,后文会解释。那这个时候肯定会有读者问了“那单片机如何在一个中断里面区分不同的中断源呢?”,单片机对不同的中断源,都设计了中断标志位,假设有ABC三个中断源,那他们就对应了3个标志位(3比特位),没触发中断的时候,ABC的中断标志位就是默认值0,如果触发中断,电路硬件会对其对应的标志比特位进行置位操作,也叫置1操作,该比特位会变成1。这个置位行为会直接反馈到内核的中断控制器,随后内核会对中断信号进行响应。
保护现场,看名字似乎和编程关系不大,这个名词在教科书上的中断章节会高频出现。我们无法预测中断会在什么时候到来,CPU也不能一直傻傻地等中断到来,所以不需要响应中断的时候,CPU还是照常工作的。想象现在CPU正在执行一个函数function(),倘若函数还未执行完成,中断被触发,CPU应该怎么做?是放下function函数不管不顾直接去响应,抑或是先做点什么?显而易见,后者更好更合理,需要做的,正是保护现场,函数执行到哪一步,CPU就会把执行到这一步的CPU数据(不只是我们要看的数据,还包括了程序执行的情况)存放到堆栈中,在中断响应完成之前,这些数据都会被封存,以避免响应完成后数据的丢失。
响应中断,这个是大部分人最关心的部分,因为这个部分直接涉及到中断服务函数的编写。在一切准备就绪后,CPU会放弃下一条需要执行的语句并直接进入中断服务函数,这里需要理解“中断服务函数”它仍然是个函数,初学者可能会认为,C语言的函数需要调用才会被执行,这里没被调用却被执行了,那肯定不是函数。实际上看过单片机原理或者了解过计算机原理的小伙伴会告诉你,CPU内部会有一个程序指针,程序指针会按照代码编译之后的逻辑去依次指向需要被执行的函数,单片机进入中断服务函数的原理就是直接设置这个指针指向中断服务函数,之后CPU就能执行中断代码响应中断了。
恢复现场,对应于保护现场,CPU必须在响应中断之后回到之前被中断打断的语句那里继续执行,取出原路堆栈中的数据就完成了恢复。
掌握中断相关的知识后,我们就可以自己编写和中断相关的代码了,编写程序时,基本上只需要注意中断标志位、中断服务函数、中断控制器就可以,保护现场什么的单片机会自己完成。
在包含了必要的头文件之后,在初始化函数中加入下图的代码即可完成对中断控制器的设置:
第一行和第二行的函数均是对内核里的中断控制器进行寄存器操作。
解释一下第二行的设置中断优先级,这里涉及到一个中断嵌套的概念,中断不会只有一个,并且很有可能下一个中断触发的时候,上一个中断还没有执行完,此时就需要严格设置中断优先级,在单片机中,根据内核用户手册,优先级从0开始递增,优先级数字越低,其优先级越高,高优先级中断可以直接打断低优先级中断的响应,立刻响应高优先级中断,形成中断嵌套,这里设置为1是因为这个回发功能不算很重要的功能,相比之下嘀嗒定时器会为单片机程序提供时基信号,其优先级应该更高。关于优先级的具体解释,可以进行网上搜索或是查看《cortex-M0+内核手册》。
关于最后一行代码,CW_UART1这个外设拥有很多个中断源,这些中断源的使用是独立的,这里只使用了接收中断这一个中断源,芯片手册的通用异步收发器章节展示了Uart中断包含的中断源。
当有数据进入单片机的Uart1接收缓冲区时,接收中断会触发,中断标志位置1,程序跳转至Uart1的中断服务函数。单片机几乎所有的中断服务函数都会由一个单独的文件收录,名为interrupt_xxxx.c或者xxxx_it.c。这里贴一张简易的中断服务函数代码,其功能是在尽量不破坏单片机实时性的情况下把数据放入一个既有的数组。
前文有提到,硬件会根据中断标志位决定是否进入中断服务函数,如果不在中断服务函数中清除中断标志位,单片机就会反复进入中断,导致程序死在中断里。
说一下代码的思路,len是一个变量,是缓冲区内非空数据的个数;data_rx是一个字符数组,作为接收缓冲区,缓冲区大小为200;进入中断之后首先判断缓冲区是否还有位置,也就是len是否超出缓冲区数组下标上限,超出则判定为缓冲区已满,丢掉后续所有的数据直到缓冲区有空位;变量 Rx_Flag是一个8位无符号数,作为缓冲区有数据&缓冲区满的标志位使用;对于接收的所有数据,均会判断是否是“\r\n”,这个字符串在编码中是换行符,只要判断到最近接收的两个字节数据是连续的0X0D和0X0A,就认定接收到换行符,本次数据接收完毕,Rx_Flag置1表示完成一次完整的数据接收。
需要注意的是,中断的响应并非一个非常可靠的函数调用,一些编译器会试图优化掉代码对某些变量的修改操作(他们可能察觉不到中断函数的存在而认为变量不需要被修改),因此需要在中断中修改的变量需要加上“volatile”关键字以防止对变量的操作被编译器优化。
到目前位置,数据其实已经被保存在数组data_rx里面了,但这段数据我们从外部是看不到的,也看不到是否是我们设想的功能完成的接收,所以我编写了如下函数,此函数可以在Uart1完成了一次完整的数据接收(Rx_Flag置1)后立刻回发接收的数据,并清空接收缓冲区,允许进行下一次接收。
因为函数包含发送功能,所以保留了超时跳出的保险措施。这里解释一下time_ms这个变量的作用,该变量定义在嘀嗒定时器文件中,并在嘀嗒定时器中断服务函数中递增1,即每1ms该变量都会增加1,作为毫秒计数值使用,本系列教程大部分实时性较弱的功能都会依赖此功能进行定时。如有疑问可以移步《内核外设-嘀嗒定时器》章节学习。
在轮询中加入这个回发函数,最大发送容忍时间100ms,并设置间隔1000ms发送一次“success”+“换行符”。随后在串口助手中发送不超过200字节的文本数据,即可验证接收是否成功。
看来单片机顺利接收了数据并进行了回发操作,本节完。
|