7.1 uart
电平通信:绝对的电压是没有意的,电压差是真正的意义。电平信号的传输线中有一个参考电平线(一般是GND)
容易受到干扰,届时传输失败
8位二进制并行通信时:需要9根线(1参考GND+8数据线)
差分信号:也是两条线,但是没有1和0,通过高电平减去其对称的低电平(详情1.7.1 37分钟),可能有9V-0.6V
最显著的特征就是抗干扰能力比较强,传输质量比较稳定,现代通信一般使用差分信号,电平信号很少。
而且电压整体上移或者下移没有影响。
8位二进制并行通信时:需要16根线(8x2数据线)
串行通信和并行通信:看起来并行通信快一些,但是串行通信才是王道,因为省信号线,速度可以由通讯提高。
经过发展,最终胜出的通信:异步、串行、差分(USB和网络)
串口通信的基本概念
本节讲述传输通信涉及到的基本概念,如:波特率、起始位、数据位、奇偶校验位、停止位、RS232电平、TTL电平等。主要学习目的是让大家对串口通信涉及到的主要概念做个了解,方便后续课程中使用。
================================================================
7.2.串口通信的基本概念
1.7.2.1、串口通信的特点:异步、电平信号、串行
(1)、异步:串口通信的发送方和接收方之间是没有统一的时钟信号的。
(2)、电平信号:串口通信出现的时间较早,速率较低,传输的距离较近,所以干扰还不太明显,因此当时使用了电平信号传输。后期出现的传输协议都改成差分信号传输了。
(3)、串行通信:串口通信每次同时只能传输1个二进制位。
1.7.2.2、RS232电平和TTL电平
(1)电平信号是用信号线电平减去参考线电平得到电压差,这个电压差决定了传输值是1还是0.
(2)在电平信号时多少V代表1,多少V代表0不是固定的,取决于电平标准。譬如RS232电平中-3V~-15V表示1;+3~+15V表示0;TTL电平则是+5V表示1,0V表示0.
(3)不管哪种电平都是为了在传输线上表示1和0.区别在于适用的环境和条件不同。RS232的电平定义比较大,适合干扰大、距离远的情况;TTL电平电压范围小,适合距离近且干扰小的情况。
(4)我们台式电脑后面的串口插座就是RS232接口的,在工业上用串口时都用这个,传输距离小于15米;TTL电平一般用在电路板内部两个芯片之间。
(5)对编程来说,RS232电平传输还是TTL电平是没有差异的。所以电平标准对硬件工程师更有意义,而软件工程师只要略懂即可。(把TTL电平和RS232电平混接是不可以的)
1.7.2.3、波特率
(1)波特率(bandrate),指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位。譬如每秒种可以传输9600个二进制位(传输一个二进制位需要的时间是1/9600秒,也就是104us),波特率就是9600.
(2)串口通信的波特率不能随意设定,而应该在一些值中去选择。一般最常见的波特率是9600或者115200(低端单片机如51常用9600,高端单片机和嵌入式SoC一般用115200).为什么波特率不可以随便指定?主要是因为:第一,通信双方必须事先设定相同的波特率这样才能成功通信,如果发送方和接收方按照不同的波特率通信则根本收不到,因此波特率最好是大家熟知的而不是随意指定的。第二,常用的波特率经过长久发展,就形成了共识,大家常用就是9600或者115200.
1.7.2.4、起始位、数据位、奇偶校验位、停止位
(1)串口通信时,收发是一个周期一个周期进行的,没周期传输n个二进制位。这一个周期就叫做一个通信单元,一个通信单元是由:起始位+数据位+奇偶校验位+停止位组成的。
(2)起始位表示发送方要开始发送一个通信单元;数据位是一个通信单元中发送的有效信息位;奇偶校验位是用来校验数据位,以防止数据位出错的;停止位是发送方用来表示本通信单元结束标志的。
(3)起始位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。
(4)数据位是本次通信真正要发送的有效数据,串口通信一次发送多少位有效数据是可以设定的(一般可选的有6、7、8、9,99%情况下我们都是选择8位数据位。因为我们一般通过串口发送的文字信息都是ASCII码编码的,而ASCII码中一个字符刚好编码为8位。)
(5)奇偶校验位是用来给数据位进行奇偶校验(把待校验的有效数据逐个位的加起来,总和为奇数奇偶校验位就为1,总和为偶数奇偶校验位就为0)的,可以在一定程度上防止位反转。
(6)停止位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。常见的有1位停止位,1.5位停止位,2位停止位等。99%情况下都是用1位停止位。
总结:串口通信时因为是异步通信,所以通信双方必须事先约定好通信参数,这些通信参数包括:波特率、数据位、奇偶校验位、停止位(串口通信中起始位定义是唯一的,所以一般不用选择)
异步通信: 发送方发送消息是想发就发
接收方必须是一直接收
串行性:一次只发一个
DB9接口:
DB9是早期串口通信早期比较常用的一种规范化接口
串行通信在早期是计算机与外界通讯的主要手段。DB9就是那个9针大头,我们只引出了3个
即TX RX GND。以前9针中的其余6根是用于流控的,现在多用于调试,禁用流控 ,所以6根就不再使用了。
================================================
7.4 UART 通用异步收发器
universal asynchronous reciver and transmitter
串口的时钟来源:APB peripheral BUS
串口分两部分:
发送器 和 接收器
由一个控制单元控制,但彼此独立。
发送器:用于210发送信息
接收器:用于从外部收信息到210
transmit shifter 发送移位器:帮助我们自动移位8位2进制,移动1位发1位。
将来计算串口控制器 的源时钟时,是以APB总线来计算的。要设定源时钟频率
transmitter由发送缓冲区和发送移位器构成,先将ASCII信息转为二进制流,然后将1帧数据写入发送缓冲区。剩下的发送部分是硬件自动的,发送移位器自动的读取,将其发送到Tx通信位。
我们只需要知道写 “要发送的信息”到发送缓冲区就行了
reciver由接收缓冲区和接收移位器。有人发送信息时,移位器自动将二进制保存至缓冲区
流控线有两端,发送方一根线拉高或拉低,接受方一根线拉低或拉高
其目的是串口通信非常可靠。发送方速率比接收方快的时候,流控可以保证 不会漏掉东西。
现在不用流控?是因为有了USB和Internet,有了更高级的工作方式。现在串口只用于SoC输出调试信息。而且由于硬件水平的提高,现在的串口的速度基本不会出现“发送方速率比接收方快”的情况,所以流控几乎是没有作用了。
所以我们就不用流控了=v=
==========================================
7.5串口补充:
串口重点在上面,但现在210等SoC设备,串口发展了高级功能,如下
FIFO模式
DMA模式
IrDA模式
这三个51都没有,stm32有一部分
FIFO模式
发送缓冲区是标准8字节,单独一个寄存器。但这里有一个发送效率的问题,要一直发一个字节一个字节不停的与CPU发送请求,CPU要切换进程,OS的效率就会很低。
解决方案:扩展串口的发送/接收缓冲区(如设置为64字节,CPU一次给64字节 待发送数据)
cpu就可以极大缓解反复切换进程的时间。
串口控制器要注意还是1字节缓冲区,这个有硬件问题和兼容问题所以不方便改。那么要实现这个想法就要启用FIFO模式。FIFO的内容依次送到transmit buffer register
FIFO,即先进先出,是一种数据结构。这个大的缓冲区也叫FIFO是因为他类似这种数据结构。我12345传进去,那么出的时候也是12345。
DMA模式
与FIFO要解决的是同一个问题,发送接收折腾CPU的问题,本来是一种DSP技术。
核心是:交换数据时,不需要CPU参与,模块可以自己完成。FIFO是轻量级解决方式,DMA则更根本。
FIFO的寄存器是有限的 可以设置成64Bit 或者128
DMA可以干脆设置个1M,根本解决串口去烦CPU的问题。
DMA就是专门解决大量数据的转移的。
IrDA模式
红外模式
红外编码的原理:发送方固定时间发送或者不发生,因此表示0或者1。接收方固定时间看有无红外来判断1或者0
1 0 0 0 0 1 1
有无无无无有有
这种原理非常类似于串口。
210上的某个串口有一个红外模式(IR mode),开启之后我们只需要向串口方式写数据,这些数据就以红外光的方式发射出去,接收方接收后即可解码得到发送信息。
其实是通过串口实现红外通信的。
(可以兼容红外芯片哦!集成哦!可以收遥控器红外信号哦!!)
============================================================
7.6 串口通信与中断
中断:单片机讲过了
发送方“一般”不需要中断 接收方“必须”使用中断,极端状态也可以不使用(轮询式…)
所以我们使用串口通信是需要中断的
RXD置1 说明串口控制器收到了1帧数据,告诉cpu
TXD中断 1帧数据发出去后,告诉cpu没东西发了,赶紧给我东西
不设置中断是,
发送方设置中断的情景: 允许cpu干别的
1发送方先设置一个中断,绑定处理函数
2发送方丢一帧数据给transmitter
3transmitter发送,发送方cpu去做别的事情
4发送完成返回一个TXD中断
5cpu切换回来给transmitter一帧数据,切换离开;循环。。。
发送方不使用中断: 让cpu盯住他,像监工一样看着他发,不发不干别的
1发送方禁止TXD中断
2发送方给一帧数据到transmitter
3transmitter发送数据,这段时间cpu没有切换,一直在这等
4发送方发送完成后,cpu再给一帧数据直到发完
(状态寄存器:里面有一个 发送缓冲区空标志bit,cpu一直读这个位)
cpu search 1 or 0 to get buffer empty
串口通信是异步的,发送方占据主导权。接收方必须时刻等待,不然后来的数据会冲掉前面的,从而丢数据。
我们一般是让发送方轮询,接收方使用中断。因为发送方可以耗,接收方耗不起。
(2)串口通信的时钟问题
串口需要时钟,需要一个固有波特率,transmitter、receiver都需要一个时钟信号
时钟问题:主要是波特率发生器。时钟从APB总线来,到串口控制器内部给波特率发生器(实质是个分频器),分频后得到一个低频的时钟,这个时钟就是给串口transmitter和rcver的。
波特率在uart channel 波特率 Divison 寄存器中 ,设置16bit分频系数
DIV_VAL = UBRDIVn + SLOT中“1”的个数 /16
DIV_VAL = (PCLK / (bps x 16)) - 1 bps:波特率
…
串口通信中时钟的设置,主要还是看寄存器
重点:
寄存器源设置(为串口控制器选择源时钟,一般PCLK_PSYS,也可以SCLK_UART)
注意:UBRDIVn UDIVSLOTn 这两个寄存器很重要
设置波特率 精确辅助设置
51单片机没这个东西,但210太快了,出错了就GG,所以有这么个寄存器为他校准
波特率不准会导致接受方可能出错,波特率越大越有可能不准!
===============================================================
7.7串口通信编程
分析程序,做准备工作
1 初始化tx rx引脚,参考原理图搜索TX,得知分别在GPA0_1 和 GPA0_0
GPA0CON = 0x?
2 关键寄存器
ULCON0 0x3 //无校验位,8数据位,1停止位
UCON0 0x5 //暂不使用DMA,PCLK作时钟源(bit10 置0),中断类型(开始不弄)
//回环模式:我自己发,自己收。我们不使用
///break signal 一般我们也用不到。。。
//发送接受模式都是 中断或轮询模式(interrupt or polling)
UFCON0 //选择接收和发送FIFO,不用FIFO的时候选默认就行
UMCON0 //AFC,modem中断,我们全都不用
UBRDIV0
UDIVSLOT0
//需要计算
在c语言中定义寄存器对应的宏,很重要的哦
GPA0CON E0200000
UBRDIV0
UDIVSLOT0
ULCON0
…
UTXH0 //管发送的
URXH0 //管接收的
rGPA0CON (*(volatile uint *)GPA0CON)
其他依法炮制
======================================
猜想
uart.c 文件
void uart_sendchar()
{
while(tx缓冲池满); //卡死在这里,等待为空
rUTXH0 = 1 //发送1
}
void uart_init()
{
rGPA0CON =
ULCON0 0x3
UCON0 0x5
UFCON0
UMCON0
//计算bps
UBRDIV0
UDIVSLOT0
}
char uart_rcvchar()
{
while(rx缓冲池满); //卡死在这里,等待为空
return (rURXH0 & 0x0f);
}
void main()
{
while1
{
发送1
delay()
}
}
==================================================================
8.1按键中断
1.8.1.2、按键的电学原理(结合原理图bv3 cv3分析)
(1)硬件接法: SW5:GPH0_2 SW6:GPH0_3
SW78910:GPH2_0123
(3)总结:按键的工作方法:其实就是按键的按下与弹开,分别对应GPIO的两种电平状态(按下则GPIO为低电平,弹开则GPIO为高电平)。此时SoC内部可以通过检测这个GPIO的电平高低来判断按键有没有被按下,这个判断结果即可作为SoC的输入信号。
1)SoC处理按键有2种思路:轮询方式和中断方式。
(2)轮询方式,就是SoC主动的每隔一段时间去读取(按键所对应的)GPIO的电平高低,以此获得按键信息;缺点在于CPU要一直注意按键事件,会影响CPU做其他事情。
(3)中断方式,就是SoC事先设定好GPIO触发的中断所对应的中断处理程序ISR,当外部按键按下或弹开时会自动触发GPIO对应的外部中断,导致ISR执行,从而自动处理按键信息。
GPH0CON (0xE0200C00)
GPH2DAT (0xE0200C04)
GPH2CON (0xE0200C40)
GPH2DAT (0xE0200C44)
在CON寄存器中将GPIO设置为input模式,然后去读取DAT寄存器
8.2.轮询方式处理按键的程序流程
(1)第一步,先初始化GPIO模式为input;
(2)第二步,循环读取GPIO的电平值,然后判断有无按键按下
8.3.串口输出和按键消抖
1.8.3.1、基于串口标准输出的按键调试
(1)以之前的串口stdio的工程为基础来移植添加轮询方式按键处理。
(2)注意USB下载方式可能有错误(有可能不下载,也有可能下载了执行不对),解决方案是用SD卡启动来替代。【使用那个x210_fusing_tool即可】
简易代码逻辑:轮询按键点亮led+串口打印信息
【main】
include "stdio.h"
{
uart init();
key init();
key_polling();
}
【uart.c】
void uart_init(void) //串口初始化
void putc(char c) //串口输出
char getc(void) //串口输入
【key.c】
void key_init(void)
{
// 设置GPHxCON寄存器,设置为输入模式
}
void delay20ms(void)
{...}
void key_polling(void)
{
// 依次,挨个去读出每个GPIO的值,
//判断其值为1还是0.如果为1则按键按下,为0则弹起
while(1)
{
if(GPH0DAT&(1<<2) ) /// xxxx x1xx,代表无按键
{
led_off();
}
else
{
delay20ms(); //消抖
if(!GPH0DAT&(1<<2) ) //延时后,再次检验
{
led1(); //按下,亮
printf("key left"); //并且打印串口信息
}
}
}
}
【led.c】
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
// 亮1个led
void led1(void)
{
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (1<<4) | (1<<5));
}
================================================================
8.4.S5PV210的中断体系介绍
(1)中断的发明是用来解决宏观上的并行需要的。宏观就是从整体上来看,并行就是多件事情都完成了。
(2)微观上的并行,就是指的真正的并行,就是精确到每一秒甚至每一刻,多个事情都是在同时进行的。宏观上面的并行并不等于围观的并行,有时候宏观上是并行的,微观上是串行的。
但是因为CPU很快,所以在宏观看来可以并行。
(5)为什么需要中断?因为单核CPU实际无法并行的,但是通过中断机制,可以实现假并行(宏观上的并行,微观上实际还是串行的)。
SoC对中断的实现机制:异常向量表
(1)异常向量表是CPU中某些特定地址的特定定义。当中断发生的时候,中断要想办法通知CPU去处理中断,怎么做到?这就要靠异常向量表。
(2)在CPU设计时,就事先定义了CPU中一些特定地址作为特定异常的入口地址(譬如定义0x00000000地址为复位异常向量地址,则发生复位异常时CPU会自动跳转到0x00000000地址去执行指令。又譬如外部中断对应的异常向量地址为0x30000008,则发生外部中断后,CPU会硬件自动跳转到0x30000008地址去执行指令。)如
(3)以上讲的是CPU硬件设计时对异常向量表的支持,下来就需要软件支持了。硬件已经决定了发生什么异常CPU自动跳转PC到哪个地址去执行,软件需要做的就是把处理这个异常的代码的首地址填入这个异常向量地址。
S5PV210的异常向量表
(1)异常向量表在1.2.14节讲过,可以返回去听一下
(2)异常向量表中各个向量的相对位置是固定的,但是他们的起始地址是不固定的,各种SoC可以不一样,而且复杂ARM中还可以让用户来软件设置这个异常向量表的基地址。
(3)扩展到所有架构的CPU中:所有架构(譬如51单片机、PIC单片机)的CPU实现中断都是通过异常向量表实现的,这个机制是不变的;但是不同CPU异常向量表的构造和位置是不同的。
===================================================
8.5.异常向量表的编程处理
像内存一样去访问异常向量表
(1)S5PV210的异常向量表可以改变(在CP15协处理器中),以适应操作系统的需求。但是目前系统刚启动时,此时DRAM尚未初始化,程序都在SRAM中运行。210在iRAM中设置了异常向量表,供暂时性使用。
(2)查210的iROM application note文档中iRAM的地址分配,可知,iRAM中的异常向量表起始地址为0xD0037400。知道了异常向量表的起始地址后,各个异常对应的入口就很好知道了。
1.8.5.2、函数名的实质就是函数的首地址
拿C语言中的语法来讲,函数名就是这个函数的函数指针。
总结:当我们将异常处理程序的首地址和异常向量表绑定起来后,异常处理初步阶段就完成了。到目前可以保证相应异常发生后,硬件自动跳转到对应异常向量表入口去执行时,可以执行到我们事先绑定的函数。
为什么中断处理要先在汇编中进行??
(1)中断处理要注意保护现场(中断从SVC模式来,则保存SVC模式下的必要寄存器的值)和恢复现场(中断处理完成后,准备返回SVC模式前,要将保存的SVC模式下的必要寄存器的值恢复回去,不然到了SVC模式后寄存器的值乱了,SVC模式下原来正在进行的常规任务就被你搞坏了)
(2)保存现场包括:第一:设置IRQ栈;第二,保存LR;第三,保存R0~R12
(3)为什么要保存LR寄存器?
要考虑中断返回的问题。中断ISR执行完后如何返回SVC模式下去接着执行原来的代码。中断返回其实取决于我们进入中断时如何保存现场。
中断返回时关键的2个寄存器就是PC和CPSR。所以我们在进入IRQ模式时,应该将SVC模式下的下一句指令的地址(中断返回地址)和CPSR保存起来,将来恢复时才可以将中断返回地址给PC,将保存的CPSR给CPSR。
(4)中断返回地址就保存在LR中,而CPSR(自动)保存在(IRQ模式下的)SPSR中
(1)保护现场关键是保存:中断处理程序的返回地址,r0-r12(cpsr是自动保存的)
(2)恢复现场主要是恢复:r0-r12,pc,cpsr
代码编写
【start.s】
{
设置SVC栈
...
bl main //规则性函数:IRQ普通中断的现场保护函数
...
}
IRQ_handle:
ldr sp, =IRQ_STACK //设置IRQ下的栈
sub lr, lr, #4 //保存LR,因为流水线的原因要-4
// 01 | 02 03
//我们pc要返回的是02,在01结束后加载,03加载完
stmfd sp! , {r0-r12 , lr} //保存 r0-r12和lr 到IRQ模式下的栈上
bl irq_handler //调用真正的isr来处理中断
ldmfd sp! , {r0-r12 , pc}^ //这句是恢复现场,r0-r12,pc
// "^"的作用是恢复cpsr
【int.c】
r_exception_irq = (unsigned int)IRQ_handle; //地址赋值!
//IRQ函数的地址通过【异常向量表】绑定。这里运用了中断向量表基地址和偏移量!!
void irq_handler(void) //真正的中断处理函数在这里,不考虑现场保护、恢复
{
printf("irq_handler")
//如果有多个IRQ中断,那么都会到这个函数里来
//需要一种方法产生分支
}
注意:这些代码看懂即可,他们是开发中的定式,是套路,大家做都一个样子,不需要自己写
================================================================
1.8.6.S5PV210的向量中断控制器
1.8.6.1、异常处理的2个阶段
(1)可以将异常处理分为2个阶段来理解。
第一个阶段是异常向量表跳转;
第二个阶段就是进入了真正的异常处理程序irq_handler之后的部分。
1.8.6.2、回顾:中断处理的第一阶段(异常向量表阶段)处理。
(1)第一个阶段之所以能够进行,主要依赖于CPU设计时提供的异常向量表机制。第一个阶段的主要任务是从异常发生到响应异常并且保存/恢复现场、跳转到真正的异常处理程序处。
(2)第二个阶段的目的是识别4个中断源中究竟哪一个发生了中断,然后调用相应的中断处理程序来处理这个中断。
(1)第一阶段(异常向量表阶段)2440和210几乎是完全相同的。实际上几乎所有的CPU在第一阶段都是相同的。
(2)第二阶段就彼此不同了。各个SoC根据自己对实时性的要求,和支持的中断源的多少,各自发明了各自处理中断,找到中断编号,进一步找到对应isr地址的方式。
8.7.S5PV210中断处理的主要寄存器
8.7.1、
VICnINTENABLE和VICnINTENCLEAR
INTENABLE寄存器负责相应的中断的使能,INTENCLEAR寄存器负责相应的中断的禁止。
VICnINTSELECT
设置各个中断的模式为irq还是fiq。一般都设置成irq
CPU如何保证fiq比irq快?有2个原因:第一,fiq模式有专用的r8~r12,因此在fiq的isr中可以直接使用r8-r12而不用保存,这就能节省时间;第二,异常向量表中fiq是最后一个异常向量入口。因此fiq的isr不需要跳转,可以直接写在原地,这样就比其他异常少跳转一次,省了些时间。
VICnIRQSTATUS和VICnFIQSTATUS
中断状态寄存器,是只读的 靠查询这个寄存器来得到中断编号的
VICnVECTPRIORITY0~VICnVECTPRIORITY31
中断优先级设置寄存器
VICnVECTADDR0~VICnVECTADDR31、VICnADDR
这三个寄存器和210中断处理第二阶段的第二阶段有关。
=============================================================
8.8.S5PV210中断处理的编程实践1
整个中断的工作分为2部分:
第一部分是我们为中断响应而做的预备工作:
1. 初始化中断控制器,禁止所有中断、选择中断类型、清VICxADDR
void intc_init(void)
// 为什么在中断初始化之初要禁止所有中断?
// 因为中断一旦打开,因为外部或者硬件自己的原因产生中断后一定就会寻找isr
// 而我们可能认为自己用不到这个中断就没有提供isr,这时它自动拿到的就是乱码
// 则程序很可能跑飞,所以不用的中断一定要关掉。
// 一般的做法是先全部关掉,然后再逐一打开自己感兴趣的中断。一旦打开就必须
// 给这个中断提供相应的isr并绑定好。
2. 绑定写好的isr到中断控制器
// 绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理
void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
物理中断号 isr函数指针
==========================================-
//VIC0
if(intnum<32)
{
*( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) =(unsigned)handler;
}
//VIC1
else if(intnum<64)
{
*( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
}
//s5pv210的中断支持128个中断,使用VIC0~VIC3四个寄存器表示他们
物理中断号号最大设计为128,但是注意中断在寄存器中的port最大为32
3、相应中断的所有条件使能
// 通过传参的intnum来使能某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_enable(unsigned long intnum)
第二部分是当硬件产生中断后如何自动执行isr:
1. 第一步,经过异常向量表跳转入IRQ/FIQ的入口
2. 第二步,做中断现场保护(在start.S中),然后跳入isr_handler
3. 第三步,在isr_handler中先去搞清楚是哪个VIC中断了,然后直接去这个VIC的ADDR
寄存器中取isr来执行即可。
4. 第四步,isr执行完,中断现场恢复,直接返回继续做常规任务。
void irq_handler(void)
{
// 虽然硬件已经自动帮我们把isr放入了VICnADDR中,但是因为有4个,所以我们必须
// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICADDR寄存器中
unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
int i=0;
void (*isr)(void) = NULL;
for(i=0; i<4; i++)
{
// 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0
if(intc_getvicirqstatus(i) != 0)
{
isr = (void (*)(void)) vicaddr[i];
break;
}
}
(*isr)(); // 通过函数指针来调用函数
}
VIC0INTENABLE这个寄存器写1是使能,写0则没有影响,想要失能需要用VIC0INTENCLEAR
VIC0INTENCLEAR某一位置1,清除某一个中断使能
关于VIC0ADDR和VIC0VECTADDR0-31
前者是当前中断函数的地址,硬件自动获得
后者是VIC0中中断号对应的isr处理函数地址,由我们填写。
TIPS:unsigned long unsigned int unsigned
这三种写法在32位系统中是一样的
===============================================================
8.10.外部中断
1.8.10.1、什么是外部中断?数据手册在哪里?
(1)SoC支持的中断类型中有一类叫外部中断。内部中断就是指的中断源来自于SoC内部(一般是内部外设),譬如串口、定时器等部件产生的中断;外部中断是SoC外部的设备,通过外部中断对应的GPIO引脚产生的中断。
(2)按键在SoC中就使用外部中断来实现。具体实现方法是:将按键电路接在外部中断的GPIO上,然后将GPIO配置为外部中断模式。此时人通过按按键改变按键电路的电压高低,这个电压高低会触发GPIO对应的外部中断,通过引脚传进去给CPU处理。
(3)外部中断相关的介绍和寄存器都在2.2.6章节(属于GPIO部分)
)外部中断的触发模式主要有2种:电平触发和边沿触发。
关键寄存器:CON、PEND、MASK
(1)外部中断的主要配置寄存器有3个:EXT_CON、EXT_PEND、EXT_MASK
(2)EXT_CON 配置外部中断的触发方式。
触发方式就是说外部电平怎么变化就能触发中断
电平触发/边缘触发
(3)EXT_PEND 寄存器是中断挂起寄存器。
这个寄存器中每一位对应一个外部中断,平时没有中断时值为0。 平时0,发生中断时候置1。中断触发之后我们需要将其手动置0。
(4)EXT_MASK 寄存器就是各个外部中断的使能/禁止开关。
分析X210开发板的按键对应的EINT编号:
EINT2、EINT3、EINT16、EINT17、EINT18、EINT19
自己总结中断要注意的几个问题:
1 每次中断完记得清挂起,清中断标志位
这里的中断标志位不仅要清EINT模式的,总中断也要清
bug现场:按键一次后下一次就失效了
rEXT_INT_0_PEND |= (1<<3);
intc_clearvectaddr();
2 中断设置好(向量表、初始化中断控制器、绑定、使能中断、现场保护恢复)之后
若要使用key,我们还需要做:
1按键对应的引脚设置为 外部中断
2main函数中包含需要的函数声明,使用头文件
3编写各个按键对应的isr_eint(void)
===============================================================
9 定时器、看门狗和定时器
这三个放在一起讲是因为他们有内在联系:都与时间相关
9.1定时器
定时器也是个外设,常常和计数器纠缠在一起。
计数器每隔一定时间计一个数 计数值x时间周期 = 定时器值
其实定时器和计数器就是一回事
定时器用来实现定时执行代码的作用。他可以产生中断提醒CPU,并执行定时器中断isr
定时器内部有一个计数器,计数器根据时钟(APB总线)来工作。每隔一个时钟周期计数器+1
定时器内部有一个TCNT寄存器,存放计数值
“TCNT – IFTCNT == 0 THEN BL ISR ”
定时器变量:
1 TCNT计数值
2 时钟周期
system timer 即systick,系统定时器,为操作系统提供时间片。系统移植时由原厂给我们。
===============================================================
9.3 pwm定时器
pwm定时器叫这个名字,是因为他本身就设计为PWM作用的,也是普通定时器。
pwm定时器:
时钟源:PCLK_PSYS
T1/T0 共同使用一个预分频器
T234 共同使用一个预分频器
每个分频器有一个专用的 独立分频器
形成了二级分配系统
定时器输入频率 = PCLK / (预分频值+1)/ 分频值
预分频值+1是为了什么?
为了防止一种情况,编程人是个傻逼把0作为了除数,那么可能导致短路引起cpu烧毁
cpu设计的时候考虑了这种情况,为了程序员的容错。
很多地方都有这种思路
关键点: 时钟源 分频器 预分频器 分频系数分别在TCFG0 TCFG1中设定
两个寄存器:
TCMP TCNT 程序员不能访问,与B对应
TCMPB TCNTB 有地址的寄存器
dead-zone死区
(框图中的 三角+圆圈 的符号代表逻辑取反)
自动重载(自动装载“auto reload”)和双缓冲(double buffering)
分频器 实质是mux开关,最大16分频
预分频器 实质是8位寄存器,最大256分频
分频结果串联,即相乘。最小1分频,最大16x256
x210所能 定时 的最长的时间是266548s,远远够用,TCNTB = 2的32次方
为什么设计两套寄存器?
TCMP TCNT 没有寄存器地址,程序员不能访问
TCMPB TCNTB
tips : TCMP的系列作用是生成pwm波形
我们想给定时器里面 放 300
TCNT 负责减
TCNTB 负责让程序员往里面存数
在减的过程中TCNTB永远不变。如果不这样设计,你总不能一直减基准值吧?
全过程:
start >> init
TCNT = TCNTB = 300
start >> timer
loop(TCHT – )
这里要讲一下以后要讲的重装载。TCNT这种称为“影子寄存器”
TCNTO 是能访问的TCNT,observeable,可观察的
自动重载
定时器一次定时算一个工作循环,如果我们想要反复加载定时器的值:
最简单最笨的方法:人为用代码制造循环
现在的高级SoC:默认了内置自动装载,发生了一次中断,到了下一个周期会自动装载。
======================================================
PWM
两个重要参数:
周期 T
占空比 duty 高电平的时间 / 总周期时间
eg编程:拉高电平,定时到达T x duty时间,把电平拉低,T x (1 - duty),把电平拉高。
以前的51单片机是没有专用的pwm定时器的,需要自己结合GPIO和定时器中断编程。
现在我们不用中断就可以生成pwm波形了,而是使用两个寄存器:TCNTB TCMPB。
TCNTB 决定周期
TCMPB 决定占空比
TCNTB x 时钟周期 可以得到pwm波形的周期
输出电平翻转器
TCMPB 可以制造一次电平翻转
可以规定:当TCNT > TCMPB 时为高电平
当TCNT < TCMPB 时为低电平
基于上述,如果duty从30%变到70%,TCMPB随之要从210变化到90.
在这两种规定下,TCMPB中的值会发生变化。于是x210为我们设计了输出电平翻转器。
在编程上反映为一个寄存器位!TCON中的output inverter就可以开启电平翻转
输出电平一翻转,30%的占空比就变成70%了
死区生成器
死区的设计时为了pwm应用,用在我们的功率电路中,给电路进行整流
交流整成直流
整流需要两路电流,两路分别在正电平和负电平导通工作,两路不能同时导通,否则会短路。大功率的开关电源、逆变器广泛使用了整流技术。“这边高那边低”,两路如果同时改变的话很有可能导致短暂的“处于同一个电平”,这个时候就短路了。
死区就是为了解决这种情况,让另一流滞后一点,有点像“按键消抖”,为了安全
死区在设计产品的时候不能多也不能少。多了精度小,没有则很危险。
我们的210提供了自带的死区生成器,用户直接用死区控制功能就好了=v=