赞
踩
IO口分为通用口(gpio),和专用口,还有ADC,DAC口等
GPIO可以通过control寄存器来配置工作模式,其每个IO口支持的工作模式不一样:
• GPA0: 8 in/out port - 2xUART with flow control
• GPA1: 4 in/out port - 2xUART without flow control or 1xUART with flow control
• GPB: 8 in/out port - 2x SPI
• GPC0: 5 in/out port - I2S, PCM, AC97
• GPC1: 5 in/out port - I2S, SPDIF, LCD_FRM
• GPD0: 4 in/out port - PWM
• GPD1: 6 in/out port - 3xI2C, PWM, IEM
• GPE0,1: 13 in/out port - Camera I/F
• GPF0,1,2,3: 30 in/out port - LCD I/F
• GPG0,1,2,3: 28 in/out port - 4xMMC channel (Channel 0 and 2 support 4-bit and 8-bit mode, but channel 1, and channel 3 support only 4-bit mode)
• GPH0,1,2,3: 32 in/out port - Key pad, External Wake-up (up-to 32-bit). (GPH* groups are in Alive region)
• GPI: Low Power I2S, PCM (in/out port is not used), PDN configuration for power down is controlled by AUDIO_SS PDN Register.
• GPJ0,1,2,3,4: 35 in/out port - Modem IF, CAMIF, CFCON, KEYPAD, SROM ADDR[22:16]
• MP0_1,2,3: 20 in/out port - Control signals of EBI (SROM, NF, OneNAND)
• MP0_4,5,6,7: 32 in/out memory port - EBI (For more information about EBI configuration, refer to Chapter 5, and 6)
• MP1_0~8: 71 DRAM1 ports (in/out port is not used)
• MP2_0~8: 71 DRAM2 ports (in/out port is not used)
• ETC0, ETC1, ETC2, ETC4: 28 in/out ETC ports - JTAG, Operating Mode, RESET, CLOCK (ETC3 is reserved)
中断模式设置:
GPA0_INT_CON寄存器的前三位:
外部晶振+内部时钟发生器+内部PLL产生高频时钟+内部分频器分频
三个区域:
MSYS域:(main system) CPU(Cortex-A8内核)、DRAM控制器(DMC0和DMC1) 、IRAM&IROM······
DSYS域: (Display System)都是和视频显示、编解码等有关的模块
PSYS域:( Peripheral System)和内部的各种外设时钟有关,譬如串口,SD接口,I2C,AC97,USB.
MSYS域:
ARMCLK: 给cpu内核工作的时钟,也就是所谓的主频。
HCLK_MSYS: MSYS域的高频时钟,给DMC0和DMC1使用
PCLK_MSYS: MSYS域的低频时钟
HCLK_IMEM:给iROM和iRAM(合称iMEM)使用
DSYS域:
HCLK_DSYS:DSYS域的高频时钟
PCLK_DSYS:DSYS域的低频时钟
PSYS域:
HCLK_PSYS:PSYS域的高频时钟
PCLK_PSYS:PSYS域的低频时钟
SCLK_ONENAND:
各时钟典型值:
? freq(ARMCLK) = 1000 MHz
? freq(HCLK_MSYS) = 200 MHz
? freq(HCLK_IMEM) = 100 MHz
? freq(PCLK_MSYS) = 100 MHz
? freq(HCLK_DSYS) = 166 MHz
? freq(PCLK_DSYS) = 83 MHz
? freq(HCLK_PSYS) = 133 MHz
? freq(PCLK_PSYS) = 66 MHz
? freq(SCLK_ONENAND) = 133 MHz, 166 MHz
外部晶振有四种来源:
如上图,晶振用于RTC、system timer、clock
四个倍频电路:
APLL:Cortex-A8内核 MSYS域
MPLL&EPLL:DSYS PSYS
VPLL:Video视频相关模块
倍频分频图:
初始化clock
第1步:先选择不使用PLL。让外部24MHz原始时钟直接过去,绕过APLL那条路
第2步:设置锁定时间。默认值为0x0FFF,保险起见我们设置为0xFFFF
第3步:设置分频系统,决定由PLL出来的最高时钟如何分频得到各个分时钟
第4步:设置PLL,主要是设置PLL的倍频系统,决定由输入端24MHz的原始频率可以得到多大的输出频率。我们按照默认设置值设置输出为ARMCLK为1GHz
第5步:打开PLL。前面4步已经设置好了所有的开关和分频系数,本步骤打开PLL后PLL开始工作,锁定频率后输出,然后经过分频得到各个频率。
时钟相关寄存器:
// 时钟控制器基地址
#define ELFIN_CLOCK_POWER_BASE 0xE0100000
// 时钟相关的寄存器相对时钟控制器基地址的偏移值
#define APLL_LOCK_OFFSET 0x00
#define MPLL_LOCK_OFFSET 0x08
#define APLL_CON0_OFFSET 0x100
#define APLL_CON1_OFFSET 0x104
#define MPLL_CON_OFFSET 0x108
#define CLK_SRC0_OFFSET 0x200
#define CLK_SRC1_OFFSET 0x204
#define CLK_SRC2_OFFSET 0x208
#define CLK_SRC3_OFFSET 0x20c
#define CLK_SRC4_OFFSET 0x210
#define CLK_SRC5_OFFSET 0x214
#define CLK_SRC6_OFFSET 0x218
#define CLK_SRC_MASK0_OFFSET 0x280
#define CLK_SRC_MASK1_OFFSET 0x284
#define CLK_DIV0_OFFSET 0x300
#define CLK_DIV1_OFFSET 0x304
#define CLK_DIV2_OFFSET 0x308
#define CLK_DIV3_OFFSET 0x30c
#define CLK_DIV4_OFFSET 0x310
#define CLK_DIV5_OFFSET 0x314
#define CLK_DIV6_OFFSET 0x318
#define CLK_DIV7_OFFSET 0x31c
c:
#define rREG_CLK_SRC0 (*(volatile unsigned int *)(ELFIN_CLOCK_POWER_BASE + CLK_SRC0_OFFSET))
#define rREG_APLL_LOCK (*(volatile unsigned int *)(ELFIN_CLOCK_POWER_BASE + APLL_LOCK_OFFSET))
#define rREG_MPLL_LOCK (*(volatile unsigned int *)(ELFIN_CLOCK_POWER_BASE + MPLL_LOCK_OFFSET))
#define rREG_APLL_CON0 (*(volatile unsigned int *)(ELFIN_CLOCK_POWER_BASE + APLL_CON0_OFFSET))
#define rREG_MPLL_CON (*(volatile unsigned int *)(ELFIN_CLOCK_POWER_BASE + MPLL_CON_OFFSET))
#define rREG_CLK_DIV0 (*(volatile unsigned int *)(ELFIN_CLOCK_POWER_BASE + CLK_DIV0_OFFSET))
#define APLL_MDIV 0X7d
#define APLL_PDIV 0X3
#define APLL_SDIV 0X1
#define CLK_DIV0_MASK 0x7fffffff
#define MPLL_MDIV 0X29b
#define MPLL_PDIV 0Xc
#define MPLL_SDIV 0X1
#define set_pll(mdiv,pdiv,sdiv) (1<<31|mdiv<<16|pdiv<<8|sdiv)
#define APLL_VAL set_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VAL set_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)
void clock_init(void)
{
rREG_CLK_SRC0 = 0x0;
rREG_APLL_LOCK = 0x0000FFFF;
rREG_MPLL_LOCK = 0x0000FFFF;
rREG_CLK_DIV0 =0x14131440;
rREG_APLL_CON0 = APLL_VAL;
rREG_MPLL_CON = MPLL_VAL;
rREG_CLK_SRC0 = 0x10001111;
}
汇编:
.global clock_init
clock_init:
ldr r0, =ELFIN_CLOCK_POWER_BASE
// 1 设置各种时钟开关,暂时不使用PLL
ldr r1, =0x0
// 芯片手册P378 寄存器CLK_SRC:Select clock source 0 (Main)
str r1, [r0, #CLK_SRC0_OFFSET]
// 2 设置锁定时间,使用默认值即可
// 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间
ldr r1, =0x0000FFFF
str r1, [r0, #APLL_LOCK_OFFSET]
str r1, [r0, #MPLL_LOCK_OFFSET]
// 3 设置分频
// 清bit[0~31]
ldr r1, [r0, #CLK_DIV0_OFFSET]
ldr r2, =CLK_DIV0_MASK
bic r1, r1, r2
ldr r2, =0x14131440
orr r1, r1, r2
str r1, [r0, #CLK_DIV0_OFFSET]
// 4 设置PLL
// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHz
ldr r1, =APLL_VAL
str r1, [r0, #APLL_CON0_OFFSET]
// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHz
ldr r1, =MPLL_VAL
str r1, [r0, #MPLL_CON_OFFSET]
// 5 设置各种时钟开关,使用PLL
ldr r1, [r0, #CLK_SRC0_OFFSET]
ldr r2, =0x10001111
orr r1, r1, r2
str r1, [r0, #CLK_SRC0_OFFSET]
mov pc, lr
略
略
略
1.AXI:
S5PV210由12个高性能AXI互连组成。作用是将总线主设备互连到总线从设备。
寄存器:ASYNC_CONFIG0~10,ASYNC_CONFIG0的地址:0xE0F0_0000。只有第0位可设置:HALF_SYNC_SEL,决定同步器是使用半同步还是完全同步,它分隔了两个不同的时钟域。将此字段设置为“高”将选择半同步器,它比全同步器具有更好的性能。相反,由于跨时钟域,完全同步器具有更好的MTBF(平均无故障时间)。为了稳定运行,建议使用完全同步。
2.coresight:
debug相关
3.access controller(TZPC):
TZPC为信任区设计的安全系统中的保护位提供软件接口。它提供了系统灵活性,允许将不同的内存区域配置为安全或非安全。S5PV210由四个TZPC组成。有保护位和安全区域位:
保护位:这使您能够将最多32个内存区域编程为安全或非安全
安全区域位:这使您能够将内部RAM区域分为安全区域和非安全区域
此内存不是SRAM概念的内存,而是CPU的内存空间,4GB。我们可以规定不同的内存空间的安全区间来控制外设的安全性。4个TZCP负责不同的内存空间:
寄存器(以TZPC0为例):
TZPCR0SIZE(5位有效,最高128kb可设,0x20设置所有RAM空间为安全模式)、
TZPCDECPROT0Stat、TZPCDECPROT0Set、TZPCDECPROT0Clr、
TZPCDECPROT1Stat、TZPCDECPROT1Set、TZPCDECPROT1Clr、
TZPCDECPROT2Stat、TZPCDECPROT2Set、TZPCDECPROT2Clr、
TZPCDECPROT3Stat、TZPCDECPROT3Set、TZPCDECPROT3Clr、
TZPCPERIPHID0、TZPCPERIPHID1、TZPCPERIPHID2、TZPCPERIPHID3、
TZPCPCELLID0、TZPCPCELLID1、TZPCPCELLID2、TZPCPCELLID3
SoC对中断的实现机制:异常向量表,此文讲过
(1)异常向量表是CPU中某些特定地址的特定定义。当中断发生的时候,中断要想办法通知CPU去处理中断,怎么做到?这就要靠异常向量表。
(2)硬件已经决定了发生什么异常CPU自动跳转PC到哪个地址去执行,软件需要做的就是把处理这个异常的代码的首地址填入这个异常向量地址。
(3)异常的定义就是突发事件,打断了CPU的正常常规业务,CPU不得不跳转到异常向量表中去执行异常处理程序;中断是异常的一种,一般特指SoC内的内部外设产生的打断SoC常规业务,或者外部中断(SoC的GPIO引脚传回来的中断)。
(4)S5PV210的异常向量表可以改变(在CP15协处理器中),以适应操作系统的需求。但是目前系统刚启动时,此时DRAM尚未初始化,程序都在SRAM中运行。210在iRAM中设置了异常向量表,供暂时性使用。
(5)像内存一样去访问异常向量表
中断处理要先在汇编中进行,为什么?
(1)中断处理要注意保护现场(中断从SVC模式来,则保存SVC模式下的必要寄存器的值)和恢复现场(中断处理完成后,准备返回SVC模式前,要将保存的SVC模式下的必要寄存器的值恢复回去)
(2)保存现场包括:第一:设置IRQ栈;第二,保存LR;第三,保存R0~R12
(iram只有两个模式下的栈,其他模式下的栈目前还不知道在哪里,开机时,BL0会帮我们设置SVC模式下的栈,将栈指针指向SVC_STACK的地址,但是其他模式下的栈需要我们自己设置)
(3)为什么要保存LR寄存器?要考虑中断返回的问题。中断ISR执行完后如何返回SVC模式下去接着执行原来的代码。中断返回其实取决于我们进入中断时如何保存现场。中断返回时关键的2个寄存器就是PC和CPSR。所以我们在进入IRQ模式时,应该将SVC模式下的下一句指令的地址(中断返回地址)和CPSR保存起来,将来恢复时才可以将中断返回地址给PC,将保存的CPSR给SPSR。
(4)中断返回地址就保存在LR中,而CPSR(自动)保存在(IRQ模式下的)SPSR中
总结:
将现场保存至哪里? irq模式的栈中
怎么设置irq模式栈? 首先要进入irq模式,之后将sp指向IRQ_STACK就行
IRQ_STACK地址? sram的内存图中有,为0xd0037f80
怎么进入irq模式? 中断发生后自动进入
控制器
和其他外设一样,中断也有其控制器,硬件帮我们做了很多事情。
1.怎么找到具体是哪个中断:S5PV210中因为支持的中断源很多,所以直接设计了4个中断寄存器,每个32位,每位对应一个中断源。(理论上210最多支持128个中断,实际支持93个,有些位是空的);当中断发生时,在irq_handler中依次去查询4个中断源寄存器,看哪一个的哪一位被置1,则这个位对应的寄存器就发生了中断,即找到了中断编号。
2.怎么找到对应的isr的问题:210提供了很多寄存器来解决每个中断源对应isr的寻找问题,当发生相应中断时,硬件会自动的将相应isr推入一定的寄存器中,我们软件只要去这个寄存器中执行函数就行了。
控制器有VIC0、VIC1、VIC2、VIC3,其能支持的中断类型如下:
VIC3中的中断源:multimedia、audio、security、(ADC、TSI、MMC)
VIC2中的中断源:multimedia、audio、security、(LCD、JPEG、LCD)
VIC1中的中断源:ARM、power、memory、conncetivity storage(onenand、hsmmc、otg、i2c、spi、uart)
VIC0中的中断源:system、DMA、TIMER(RTC、WDT、timer、EINT0~31)
寄存器
VICnINTENABLE、VICnINTENCLEAR
使能,禁止寄存器
VICnINTSELECT
中断模式选择寄存器
VICnIRQSTATUS、VICnFIQSTATUS
中断状态寄存器
VICnVECTPRIORITY0~VICnVECTPRIORITY31
优先级设置
VICnVECTADDR0~VICnVECTADDR31、VICnADDR
存放isr函数地址,程序员在设置中断的时候,把这个中断的isr地址直接放入这个中断对应的VECTADDR寄存器即可
VICnADDR这个寄存器是只需要读的,它里面的内容是由硬件自动设置的。当发生了相应中断时,硬件会自动识别中断编号,并且会自动找到这个中断的VECTADDR寄存器,然后将其读出复制到VICnADDR中。
VICnIRQSTATUS、VICnFIQSTATUS
中断状态寄存器,只读。
当发生了中断时,硬件会自动将该寄存器的对应位置为1。
中断控制器初始化
主要工作有:第一阶段绑定异常向量表到异常处理程序;禁止所有中断源;选择所有中断类型为IRQ;清理VICnADDR寄存器为0.
中断的使能与禁止:
思路是先根据中断号判断这个中断属于VIC几,然后在用中断源减去这个VIC的偏移量(不是地址偏移量,而是中断号偏移量),得到这个中断号在本VIC中的偏移量,然后1<<x位,写入相应的VIC的INTENABLE/INTENCLEAR寄存器即可。
真正的中断处理程序如何获取isr
(1)当发生中断时,硬件会自动把相应中断源的isr地址从VICnVECTADDR寄存器中推入VICnADDR寄存器中,所以我们第二阶段的第二阶段isr_handler中,只需要到相应的VICnADDR中去拿出isr地址,调用执行即可。
总结:第4步绑定isr地址到VICnVECTADDR和第5步中断发生时第二阶段的第二阶段如何获取isr地址,这两步是相关的。这两个的结合技术,就是我们一直在说的210的硬件自动寻找isr的机制。
整个中断的流程梳理:
整个中断的工作分为2部分:
第一部分是我们为中断响应而做的预备工作:
1. 初始化中断控制器
2. 绑定写好的isr到中断控制器
3. 相应中断的所有条件使能
第二部分是当硬件产生中断后如何自动执行isr:
1. 第一步,经过异常向量表跳转入IRQ/FIQ的入口
2. 第二步,做中断现场保护(在start.S中),然后跳入isr_handler
3. 第三步,在isr_handler中先去搞清楚是哪个VIC中断了,然后直接去这个VIC的ADDR寄存器中取isr来执行即可。
4. 第四步,isr执行完,中断现场恢复,直接返回继续做常规任务。
外部中断
ext属于外部中断,其主要寄存器为:EXT_CON、EXT_PEND、EXT_MASK
EXT_PEND寄存器是中断挂起寄存器。这个寄存器中每一位对应一个外部中断,平时没有中断时值为0。当发生了中断后,硬件会自动将这个寄存器中该中断对应的位置1,我们去处理完这个中断后应该手工将该位置0。这个PEND寄存器的位就相当于是一个标志,如果发生了中断但是我们暂时忙来不及去处理时,这个位一直是1(这就是挂起),直到去处理了这个中断才会手工清除(写代码清除)这个挂起位表示这个中断被我处理了。
EINT编号是和GPIO绑定的,所以EINT需要查GPIO
外部中断代码:(int.h、int.c是通用的,我们只需要设置GPIO中断寄存器,绑定相应中断函数即可)
uart中断代码:(设置uart的寄存器为相应中断模式)
框架:
由于DMA_peri仅作为非安全设备运行,因此必须在信任区保护控制器(TZPC)模块中将所有外围设备设置为非安全设备。
PL330的总线接口是AXI,因此DMA_mem和DMA_peri分别连接到AXI_B0和AXI_B1。
特性:
Key Features | DMA_mem | DMA_peri |
---|---|---|
Supports Data Size | Up to double word (64-bit) | Up to word (32-bit) |
Supports Burst Size | Up to 16 burst | Word transfer: Up to 8 burst 、Byte or word transfer: Up to 16 burst |
Supports Channel | 8 channels at the same time | 16 channels at the same time |
三个DMA控制器:DMA_mem、DMA_peri0、DMA_peri1
(1)定时器计时其实是通过计数来实现的。定时器内部有一个计数器,这个计数器根据一个时钟(这个时钟源来自APB总线,然后经过时钟模块内部的分频器来分频得到)来工作。每隔一个时钟周期,计数器就计数一次,定时器的时间就是计数器计数值×时钟周期。
(2)定时器内部有1个寄存器TCNT,计时开始时我们会把一个总的计数值(譬如说300)放入TCNT寄存器中,然后每隔一个时钟周期(假设为1ms)TCNT中的值会自动减1(硬件自动完成,不需要CPU软件去干预),直到TCNT中减为0的时候,TCNT就会触发定时器中断。
(3)定时时间是由2个东西共同决定的:一个是TCNT中的计数值,一个是时钟周期。譬如上例中,定时周期就为300×1ms = 300ms。
看门狗其实就是一个定时器,只不过定时时间到了之后不只是中断,还可以复位CPU
(1)PCLK_PSYS经过两级分频后生成WDT(watchdog timer)的时钟周期,然后把要定的时间写到WTDAT寄存器中,刷到WTCNT寄存器中去减1,减到0时(定时时间到)产生复位信号或中断信号。
(2)典型应用中是配置为产生复位信号,我们应该在WTCNT寄存器减到0之前给WTDAT寄存器中重新写值以喂狗。
7.3、主要寄存器:WTCON WTDAT WTCNT WTCLRINT
wdt.c:
#define WTCON (0xE2700000)
#define WTDAT (0xE2700004)
#define WTCNT (0xE2700008)
#define WTCLRINT (0xE270000C)
#define rWTCON (*(volatile unsigned int *)WTCON)
//...
// 初始化WDT使之可以产生中断
void wdt_init_interrupt(void)
{
// 第一步,设置好预分频器和分频器,得到时钟周期是128us
rWTCON &= ~(0xff<<8);
rWTCON |= (65<<8); // 1MHz
rWTCON &= ~(3<<3);
rWTCON |= (3<<3); // 1/128 MHz, T = 128us
// 第二步,设置中断和复位信号的使能或禁止
rWTCON |= (1<<2); // enable wdt interrupt
rWTCON &= ~(1<<0); // disable wdt reset
// 第三步,设置定时时间
// WDT定时计数个数,最终定时时间为这里的值×时钟周期
//rWTDAT = 10000; // 定时1.28s
//rWTCNT = 10000; // 定时1.28s
// 其实WTDAT中的值不会自动刷到WTCNT中去,如果不显式设置WTCON中的值,它的值就是
// 默认值,然后以这个默认值开始计数,所以这个时间比较久。如果我们自己显式的
// 设置了WTCNT和WTDAT一样的值,则第一次的定时值就和后面的一样了。
rWTDAT = 1000; // 定时0.128s
//rWTCNT = 1000; // 定时0.128s
// 第四步,先把所有寄存器都设置好之后,再去开看门狗
rWTCON |= (1<<5); // enable wdt
}
// wdt的中断处理程序
void isr_wdt(void)
{
static int i = 0;
// 看门狗定时器时间到了时候应该做的有意义的事情
printf("wdt interrupt, i = %d...", i++);
// 清中断
intc_clearvectaddr();
rWTCLRINT = 1;
}
(2)RTC是SoC中一个内部外设,RTC有自己独立的晶振提供RTC时钟源(32.768KHz),内部有一些寄存器用来记录时间(年月日时分秒星期)。一般情况下为了在系统关机时时间仍然在走,还会给RTC提供一个电池供电。
图中的每个时间框都用一个寄存器控制
闹钟发生器
(1)可以定闹钟时间,到时间会产生RTC alarm interrupt,通知系统闹钟定时到了。
(2)闹钟定时是定的时间点,而timer定时是定的时间段。
实时时钟的主要寄存器
(1)INTP 中断挂起寄存器
(2)RTCCON RTC控制寄存器
(3)RTCALM ALMxxx 闹钟功能有关的寄存器
BCD码:
(1)RTC中所有的时间(年月日时分秒星期,包括闹钟)都是用BCD码编码的。
(2)BCD码本质上是对数字的一种编码。用来解决这种问题:由56得到0x56(或者反过来)。也就是说我们希望十进制的56可以被编码成56(这里的56不是十进制56,而是两个数字5和6).
(3)BCD码的作用在于可以将十进制数拆成组成这个十进制数的各个数字的编码,变成编码后就没有位数的限制了。譬如我有一个很大的数123456789123456789,如果这个数纯粹当数字肯定超出了int的范围,计算机无法直接处理。要想让计算机处理这个数,计算机首先得能表达这个数,表达的方式就是先把这个数转成对应的BCD码(123456789123456789)
(4)BCD码在计算机中可以用十六进制的形式来表示。也就是说十进制的56转成BCD码后是56,在计算机中用0x56来表达(暂时存储与运算)。
(5)需要写2个函数,一个是bcd转十进制,一个是十进制转bcd。当我们要设置时间时(譬如要设置为23分),我们需要将这个23转成0x23然后再赋值给相应的寄存器BCDMIN;当我们从寄存器BCDMIN中读取一个时间时(譬如读取到的是0x59),需要将之当作BCD码转成十进制再去显示(0x59当作BCD码就是59,转成十进制就是59,所以显示就是59分)。
设置时间与读取显示时间
(1)为了安全,默认情况下RTC读写是禁止的,此时读写RTC的时间是不允许的;当我们要更改RTC时间时,应该先打开RTC的读写开关,然后再进行读写操作,操作完了后立即关闭读写开关。
(2)读写RTC寄存器时,一定要注意BCD码和十进制之间的转换。
(3)年的问题。S5PV210中做了个设定,BCDYEAR寄存器存的并不是完整的年数(譬如今年2015年),而是基于2000年的偏移量来存储的,譬如今年2015年实际存的就是15(2015-2000).还有些RTC芯片是以1970年(貌似)为基点来记录的。
rtc.c:
struct rtc_time
{
unsigned int year;
unsigned int month;
unsigned int date; // 几号
unsigned int hour;
unsigned int minute;
unsigned int second;
unsigned int day; // 星期几
};
#define RTC_BASE (0xE2800000)
#define rINTP (*((volatile unsigned long *)(RTC_BASE + 0x30)))
#define rRTCCON (*((volatile unsigned long *)(RTC_BASE + 0x40)))
#define rTICCNT (*((volatile unsigned long *)(RTC_BASE + 0x44)))
#define rRTCALM (*((volatile unsigned long *)(RTC_BASE + 0x50)))
#define rALMSEC (*((volatile unsigned long *)(RTC_BASE + 0x54)))
#define rALMMIN (*((volatile unsigned long *)(RTC_BASE + 0x58)))
#define rALMHOUR (*((volatile unsigned long *)(RTC_BASE + 0x5c)))
#define rALMDATE (*((volatile unsigned long *)(RTC_BASE + 0x60)))
#define rALMMON (*((volatile unsigned long *)(RTC_BASE + 0x64)))
#define rALMYEAR (*((volatile unsigned long *)(RTC_BASE + 0x68)))
#define rRTCRST (*((volatile unsigned long *)(RTC_BASE + 0x6c))) //数据手册没有此寄存器
#define rBCDSEC (*((volatile unsigned long *)(RTC_BASE + 0x70)))
#define rBCDMIN (*((volatile unsigned long *)(RTC_BASE + 0x74)))
#define rBCDHOUR (*((volatile unsigned long *)(RTC_BASE + 0x78)))
#define rBCDDATE (*((volatile unsigned long *)(RTC_BASE + 0x7c)))
#define rBCDDAY (*((volatile unsigned long *)(RTC_BASE + 0x80)))
#define rBCDMON (*((volatile unsigned long *)(RTC_BASE + 0x84)))
#define rBCDYEAR (*((volatile unsigned long *)(RTC_BASE + 0x88)))
#define rCURTICCNT (*((volatile unsigned long *)(RTC_BASE + 0x90)))
#define rRTCLVD (*((volatile unsigned long *)(RTC_BASE + 0x94))) //数据手册没有此寄存器
// 函数功能:把十进制num转成bcd码,譬如把56转成0x56
static unsigned int num_2_bcd(unsigned int num)
{
// 第一步,把56拆分成5和6
// 第二步,把5和6组合成0x56
return (((num / 10)<<4) | (num % 10));
}
// 函数功能:把bcd码bcd转成十进制,譬如把0x56转成56
static unsigned int bcd_2_num(unsigned int bcd)
{
// 第一步,把0x56拆分成5和6
// 第二步,把5和6组合成56
return (((bcd & 0xf0)>>4)*10 + (bcd & (0x0f)));
}
void rtc_set_time(const struct rtc_time *p)
{
// 第一步,打开RTC读写开关
rRTCCON |= (1<<0);
// 第二步,写RTC时间寄存器
rBCDYEAR = num_2_bcd(p->year - 2000);
rBCDMON = num_2_bcd(p->month);
rBCDDATE = num_2_bcd(p->date);
rBCDHOUR = num_2_bcd(p->hour);
rBCDMIN = num_2_bcd(p->minute);
rBCDSEC = num_2_bcd(p->second);
rBCDDAY = num_2_bcd(p->day);
// 最后一步,关上RTC的读写开关
rRTCCON &= ~(1<<0);
}
void rtc_get_time(struct rtc_time *p)
{
// 第一步,打开RTC读写开关
rRTCCON |= (1<<0);
// 第二步,读RTC时间寄存器
p->year = bcd_2_num(rBCDYEAR) + 2000;
p->month = bcd_2_num(rBCDMON);
p->date = bcd_2_num(rBCDDATE);
p->hour = bcd_2_num(rBCDHOUR);
p->minute = bcd_2_num(rBCDMIN);
p->second = bcd_2_num(rBCDSEC);
p->day = bcd_2_num(rBCDDAY);
// 最后一步,关上RTC的读写开关
rRTCCON &= ~(1<<0);
}
void rtc_set_alarm(void)
{
rALMSEC = num_2_bcd(23);
rRTCALM |= 1<<0;
rRTCALM |= 1<<6;
}
void isr_rtc_alarm(void)
{
static int i = 0;
printf("rtc alarm, i = %d...", i++);
rINTP |= (1<<1);
intc_clearvectaddr();
}
(1)如图显示S5PV210有5个PWM定时器。其中0、1、2、3各自对应一个外部GPIO,可以通过这些对应的GPIO产生PWM波形信号并输出;timer4没有对应的外部GPIO(因此不是为了生成PWM波形而是为了产生内部定时器中断而生的);timer0可以支持大电流设备。
(2)S5PV210的5个PWM定时器的时钟源为PCLK_PSYS,timer0和timer1共同使用一个预分频器、timer2、3、4共同使用一个预分频器;每个timer有一个专用的独立的分频器;预分频器和分频器构成了2级分频系统,将PCLK_PSYS两级分频后生成的时钟供给timer模块作为时钟周期。
(3) 每个定时器都有自己的32位下行计数器,由定时器时钟驱动。向下计数器最初从定时器计数缓冲寄存器(TCNTBn)加载。如果向下计数器达到零,则生成计时器中断请求,以通知CPU计时器操作已完成。如果计时器下降计数器达到零,则相应TCNTBn的值将自动重新加载到下降计数器中以开始下一个循环。但是,如果计时器停止,例如,通过在计时器运行模式期间清除TCONn的计时器启用位,TCNTBn的值不会重新加载到计数器中。 PWM功能使用TCMPBn寄存器的值。如果下计数器值与定时器控制逻辑中比较寄存器的值匹配,定时器控制逻辑将更改输出电平。因此,比较寄存器确定PWM输出的开启时间(或关闭时间)。
TCNTBn和TCMPBn寄存器是双缓冲的,以便在周期的中间更新计时器参数。新值在当前计时器周期完成之前不会生效。
关键点:时钟源、预分频器、分频器、TCMPB&TCNTB、dead zone
PWM波形
(1)PWM(pulse wide modulation 脉宽调制)
(2)PWM波形是一个周期性波形,周期为T,在每个周期内波形是完全相同的。每个周期内由一个高电平和一个低电平组成。
(3)PWM波形有2个重要参数:一个是周期T,另一个是占空比duty(占空比就是一个周期内高电平的时间除以周期时间的商)。
(4)对于一个PWM波形,知道了周期T和占空比duty,就可以算出这个波形的所有细节。譬如高电平时间为Tduty,低电平时间为T(1-duty)。
(1)PWM波形其实就是用时间来控制电平高低,所以用定时器来实现PWM波形天经地义。
(2)早期的简单单片机里(譬如51单片机)是没有专用的PWM定时器的,那时候我们需要自己结合GPIO和定时器模块来手工生产PWM波形(流程是这样:先将GPIO引脚电平拉高、同时启动定时器定Tduty时间,时间到了在isr中将电平拉低,然后定时T(1-duty)后再次启动定时器,然后时间到了后在isr中将电平拉高,然后再定时T*duty时间再次启动定时器····如此循环即可得到周期为T,占空比为duty的PWM波形)。
(3)后来因为定时器经常和PWM产生纠结一起,所以设计SoC的时候就直接把定时器和一个GPIO引脚内部绑定起来了,然后在定时器内部给我们设置了PWM产生的机制,可以更方便的利用定时器产生PWM波形。此时我们利用PWM定时器来产生PWM波形再不用中断了。绑定了之后坏处就是GPIO引脚是固定的、死板的、不能随便换的;好处是不用进入中断isr中,直接可以生成PWM。
(4)在S5PV210中,PWM波形产生有2个寄存器很关键,一个是TCNTB、一个是TCMPB。其中,TCNTB决定了PWM波形的周期,TCMPB决定了PWM波形的占空比。
(5)最终生成的PWM波形的周期是:TCNTB×时钟周期(PCLK_PSYS经过两极分频后得到的时钟周期)。注意这个周期是PWM中高电平+低电平的总时间,不是其中之一。
(6)最终生成的PWM波形的占空比是:TCMPB/TCNTB
如图为占空比和波形的关系:
预分频器与分频器
(1)两级分频是串联(级联)的,所以两级分频的分频数是相乘的。
(2)两级分频的分频系数分别在TCFG0和TCFG1两个寄存器中设置。
(3)预分频器有2个,prescaler0为timer0&timer1共用;prescaler1为timer2、3、4共用;两个prescaler都是8个bit位,因此prescaler value范围为0~255;所以预分频器的分频值范围为1~256(注意实际分频值为prescaler value + 1)。
(4)分频器实质上是一个MUX开关,多选一开关决定了走哪个分频系数路线。可以选择的有1/1,1/2,1/4,1/8,1/16等。
(5)两级分频下来,分频最小为1/1(也可能是1/2,数据手册没有说明prescaler不能=0,只在1.3.1的表格中略讲了一下最小值是prescaler=1的时候,如果要验证就写个代码测试时间长短或者示波器看波形),最大分频为1/256×16(1/4096).
(6)在PCLK_PSYS为66MHz的情况下(默认时钟设置就是66MHz的),此时两级分频后的时钟周期范围为0.03us到62.061us;再结合TCNTB的值的设置(范围为1~2^32,可知能定出来的时间最长为266548.27s(折合74小时多)如下图:
主要寄存器
TCNT&TCMP、TCNTB&TCMPB、TCNTO(observe)
(1)TCNT和TCNTB是相对应的,TCNTB是有地址的寄存器,供程序员操作;TCNT在内部和TCNTB相对应,它没有寄存器地址,程序员不能编程访问这个寄存器。
(2)TCNT寄存器功能就是用来减1的,它是内部的不能读写;我们向TCNT中写要通过TCNTB往进写;读取TCNT寄存器中的值要通过读取相对应的TCNTO寄存器。
(3)工作流程就是:我们事先算好TCNT寄存器中开始减的那个数(譬如300),然后将之写入TCNTB寄存器中,在启动timer前,将TCNTB中的值刷到TCNT寄存器中(有一位寄存器专门用来操作刷数据过去的),刷过去后就可以启动定时器开始计时;在计时过程中如果想知道TCNT寄存器中的值减到多少了,可以读取相应的TCNTO寄存器来得知。
(4)定时功能只需要TCNT、TCNTB两个即可;TCNTO寄存器用来做一些捕获计时;TCMPB用来生成PWM波形。
自动重载和双缓冲(auto-reload and double buffering)
(1)定时器工作的时候,一次定时算一个工作循环。定时器默认是单个循环工作的,也就是说定时一次,计时一次,到期中断一次就完了。下次如果还要再定时中断,需要另外设置。
(2)但是现实中用定时器来做的时候往往是循环的,最笨的方法就是写代码反复重置定时器寄存器的值(在每次中断处理的isr中再次给TCNTB中赋值,再次刷到TCNT中再次启动定时器),早期的单片机定时器就是这样的;但是现在的高级SoC中的定时器已经默认内置了这种循环定时工作模式,就叫自动装载(auto-reload)机制。
rCON |= (1<<15); // 使能auto-reload
// 第一次需要手工将TCNTB中的值刷新到TCNT中去,以后就可以auto-reload了
rCON |= (1<<13); // 打开自动刷新功能
rCON &= ~(1<<13); // 关闭自动刷新功能
输出电平翻转器
(1)PWM定时器可以规定:当TCNT>TCMPB时为高电平,当TCNT<TCMPB时为低电平。也可以规定:当TCNT>TCMPB时为低电平,当TCNT<TCMPB时为高电平。在这两种规定下,计算时TCMP寄存器的值会变化。
(2)基于上面讲的,当duty从30%变到70%时,我们TCMPB寄存器中的值就要改(譬如TCNTB中是300时,TCMPB就要从210变化到90)。这样的改变可以满足需要,但是计算有点麻烦。于是乎210的PWM定时器帮我们提供了一个友好的工具叫做电平翻转器。
(3)电平翻转器在电路上的实质就是一个电平取反的部件,在编程上反映为一个寄存器位。(TCON,Timer Control Register的第10位Timer 1 Output Inverter on/off)写0就关闭输出电平反转,写1就开启输出电平反转。开启后和开启前输出电平刚好高低反转。(输出电平一反转30%的duty就变成70%了)。电平翻转器在原理图上: 。
死区生成器
(1)PWM有一个应用就是用在功率电路中用来对交流电压进行整流。整流时2路整流分别在正电平和负电平时导通工作,不能同时导通(同时导通会直接短路,瞬间的同时导通都会导致电路烧毁)。大功率的开关电源、逆变器等设备广泛使用了整流技术。特别是逆变器,用SoC的GPIO输出的PWM波形来分别驱动2路整流的IGBT。(不懂)
(2)PWM波形用来做整流时要求不能同时高或低,因为会短路。但是实际电路是不理想的,不可能同时上升/下降沿,所以比较安全的做法是留死区。
(3)死区这东西离不了也多不了。死区少了容易短路,死区多了控制精度低了不利于产品性能的提升。
(4)S5PV210提供了自带的死区生成器,只要开启死区生成器,生产出来的PWM波形就自带了死区控制功能,用户不用再自己去操心死区问题。(S5PV210提供的只能帮助完成不是很精确的工作,用在一些简单的应用中,在专业领域如专业的逆变器都是用dsp控制)
(5)大部分人工作是用不到这个的,直接关掉死区生成器即可。
上图是开启死区和关闭死区是时的区别。
驱动蜂鸣器
(1)查阅原理图可知,蜂鸣器通过GPD0_2(XpwmTOUT2)引脚连接在SoC上。
(2)GPD0CON(0xE02000A0),要把bit8~bit11设置为0b0010(即功能选择为TOUT_2,专门的为PWM输出功能)
(3)从GPD0_2引脚可以反推出使用的是timer2这个PWM定时器。
(4)相关的寄存器有TCFG0、TCFG1、CON、TCNTB2、TCMPB2、TCNTO2
代码:
#define GPD0CON (0xE02000A0)
#define TCFG0 (0xE2500000)
#define TCFG1 (0xE2500004)
#define CON (0xE2500008)
#define TCNTB2 (0xE2500024)
#define TCMPB2 (0xE2500028)
#define rGPD0CON (*(volatile unsigned int *)GPD0CON)
//...
// 初始化PWM timer2,使其输出PWM波形:频率是2KHz、duty为50%
void timer2_pwm_init(void)
{
// 设置GPD0_2引脚,将其配置为XpwmTOUT_2
rGPD0CON &= ~(0xf<<8);
rGPD0CON |= (2<<8);
// 设置PWM定时器的一干寄存器,使其工作
rTCFG0 &= ~(0xff<<8);
rTCFG0 |= (65<<8); // prescaler1 = 65, 预分频后频率为1MHz
rTCFG1 &= ~(0x0f<<8);
rTCFG1 |= (1<<8); // MUX2设置为1/2,分频后时钟周期为500KHz
// 时钟设置好,我们的时钟频率是500KHz,对应的时钟周期是2us。也就是说每隔2us
// 计一次数。如果要定的时间是x,则TCNTB中应该写入x/2us
rCON |= (1<<15); // 使能auto-reload,反复定时才能发出PWM波形
//rTCNTB2 = 250; // 0.5ms/2us = 500us/2us = 250
//rTCMPB2 = 125; // duty = 50%
rTCNTB2 = 50;
rTCMPB2 = 25;
// 第一次需要手工将TCNTB中的值刷新到TCNT中去,以后就可以auto-reload了
rCON |= (1<<13); // 打开自动刷新功能
rCON &= ~(1<<13); // 关闭自动刷新功能,打开一次后就可以将他关闭了
rCON |= (1<<12); // 开timer2定时器。要先把其他都设置好才能开定时器
}
//可使用蜂鸣器写歌
//小字一组音阶频率
//C - do - 261.6HZ;D - re - 293.6HZ;
//E - mi - 329.6HZ;F - fa - 349.2HZ;
//G - sol- 392HZ;A - la - 440HZ;
//B - si - 493.8HZ。
起始位+数据位(一般为8位)+奇偶校验位+停止位,每周期传输n个二进制位
s5pv210串口控制器图:
transmitter由发送缓冲区Transmit Buffer Register和发送移位器Transmit Shifter构成。我们要发送信息时,首先将信息进行编码(一般用ASCII码)成二进制流,然后将一帧数据(一般是8位)写入发送缓冲区(从这里以后程序就不用管了,剩下的发送部分是硬件自动的),发送移位器会自动从发送缓冲区中读取一帧数据,然后自动移位(移位的目的是将一帧数据的各个位分别拿出来)将其发送到Tx通信线上。
receiver类似
我们要做的事情:初始化串口(设置好我们需要的模式)+写发送缓冲区+读接收缓冲区。
其他包括移位、起始位定义、TTL电平定义等所有事情控制器以及其外围电路都帮我们做好了。
源时钟信号
外部APB总线(PCLK_PSYS,66MHz)
1.寄存器源设置(为串口控制器选择源时钟,一般选择为PCLK_PSYS,也可以是SCLK_UART),还有波特率发生器的2个寄存器。
2.波特率发生器有2个重要寄存器:UBRDIVn和UDIVSLOTn(其中UBRDIVn是主要的设置波特率的寄存器,UDIVSLOTn是用来辅助设置的,目的是为了校准波特率的。
波特率配置方法:
There are four UART baud rate divisor registers in the UART block, namely, UBRDIV0, UBRDIV1, UBRDIV2 and UBRDIV3.
The value stored in the baud rate divisor register (UBRDIVn) and dividing slot register(UDIVSLOTn) is used to determine the serial Tx/Rx clock rate (baud rate) as follows:
DIV_VAL = UBRDIVn + (num of 1’s in UDIVSLOTn)/16
DIV_VAL = (PCLK / (bps x 16)) −1
or
DIV_VAL = (SCLK_UART / (bps x 16)) −1
Where, the divisor should be from 1 to (216-1).
Using UDIVSLOT, you can generate the baud rate more accurately.
For example, if the baud-rate is 115200 bps and SCLK_UART is 40 MHz, UBRDIVn and UDIVSLOTn are:
DIV_VAL = (40000000 / (115200 x 16)) -1 = 21.7 -1 = 20.7
UBRDIVn = 20 ( integer part of DIV_VAL )
(num of 1’s in UDIVSLOTn)/16 = 0.7
then, (num of 1’s in UDIVSLOTn) = 11
so, UDIVSLOTn can be 16’b1110_1110_1110_1010 or 16’b0111_0111_0111_0101, etc.
It is recommended to select UDIVSLOTn as described in the following table:
简单的步骤:
1.UART引脚为TX,RX,查电路原理图,可知其引脚接的是哪个 GPIO口:
我们使用RXD0,TXD0那么GPIO为GPA0_0,GPA0_1。
UCON0 ULCON0 UMCON0 UFCON0 UBRDIV0 UDIVSLOT0
#define GPA0CON 0xE0200000
#define UCON0 0xE2900004
#define ULCON0 0xE2900000
#define UMCON0 0xE290000C
#define UFCON0 0xE2900008
#define UBRDIV0 0xE2900028
#define UDIVSLOT0 0xE290002C
#define UTRSTAT0 0xE2900010
#define UTXH0 0xE2900020
#define URXH0 0xE2900024
#define rGPA0CON (*(volatile unsigned int *)GPA0CON)
//...
// 串口初始化程序
void uart_init(void)
{
// 初始化Tx Rx对应的GPIO引脚
rGPA0CON &= ~(0xff<<0); // 把寄存器的bit0~7全部清零
rGPA0CON |= 0x00000022; // 0b0010, Rx Tx
// 几个关键寄存器的设置
rULCON0 = 0x3; //8位数据位,1位停止位,非红外模式,校验位默认
rUCON0 = 0x5; //transmit mode和receive mode都设置为interrupt或者polling mode,(支持DMA),其他都为默认设置,其中clock可选PCLK和SCLK_UART,我们选择pclk,
rUMCON0 = 0; //流控等,全部disable,也就是默认设置
rUFCON0 = 0; //和fifo有关的寄存器,fifo disable,
// 波特率设置 DIV_VAL = (PCLK / (bps x 16))-1
// PCLK_PSYS用66MHz算 余数0.8
//rUBRDIV0 = 34;
//rUDIVSLOT0 = 0xdfdd;
// PCLK_PSYS用66.7MHz算 余数0.18
// DIV_VAL = (66700000/(115200*16)-1) = 35.18
rUBRDIV0 = 35;
// (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18
// (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3
rUDIVSLOT0 = 0x0888; // 3个1,查官方推荐表得到这个数字
}
// 串口发送程序,发送一个字节
void uart_putc(char c)
{
while (!(rUTRSTAT0 & (1<<1)));
rUTXH0 = c;
}
// 串口接收程序,轮询方式,接收一个字节
char uart_getc(void)
{
while (!(rUTRSTAT0 & (1<<0)));
return (rURXH0 & 0x0f);
}
FIFO模式
不用此模式串口控制器本来的发送/接收缓冲区是固定的1字节长度。用此可扩展缓冲区。
DMA模式
中断/DMA请求生成(在数据手册的section8-1.3.7,1.6.1.13/14/15节)
轮询模式可通过while阻塞等待rUTRSTAT0的信号,这里使用中断模式,无需阻塞:
中断处理函数:和上面通过阻塞判断status寄存器不同,这里因为触发了中断,那缓存中肯定有数据,不需要while阻塞等待,直接取,如代码1所示:(但是rUTXH0有可能不为空,这个数据可能会被挤掉,这里的实现需要看工程需求,如果中断为紧急情况,那么挤掉前面的内容是ok的,但是大多数情况下,中断只是作为接收的手段,不涉及十分紧急的情况,那么我们需要等待rUTXH0缓冲中的内容发完,才开始往里填内容,以保证串口发送的正确性,如代码2所示)
//代码1:
void isr_uart(void)
{
if(rUERSTAT0 & (1<<3))
{
rUTXH0 = (rURXH0 & 0x0f);
}
intc_clearvectaddr();
}
//代码2:
void isr_uart(void)
{
char a;
if(rUERSTAT0 & (1<<3))
{
a = (rURXH0 & 0x0f);
while (!(rUTRSTAT0 & (1<<1)));
rUTXH0 = a;
}
intc_clearvectaddr();
}
每个UART由七个状态(Tx/Rx/Error)信号组成,即溢出错误、奇偶校验错误、帧错误、中断、接收缓冲区数据准备就绪、发送缓冲区清空和发送移位器清空。这些条件由相应的UART状态寄存器(UTRSTATn/UERSTATn)指示。
uart stdio移植
以上我们可以在串口终端打印了,写了个uart_getc却没法用,stdio中的scanf一般绑定键盘,printf一般绑定屏幕,也就是标准输入输出。我们可以将scanf绑定uart_getc,将printf绑定uart_putc。
printf函数工作时内部实际调用了2个关键函数:一个是vsprintf函数(主要功能是格式化打印信息,最终得到纯字符串格式的打印信息等待输出),另一个就是真正的输出函数putc(操控标准输出的硬件,将信息发送出去)。
可以移植linux内核中的printk稍微简单些的方法是从uboot中移植printf。
移植其实就是自己写的putc和getc函数让printf.c调用。
代码:uart_stdio_relocate.rar
链接:https://pan.baidu.com/s/1H2SaVY-wG13ttlOMUNn5GQ
提取码:rich
这个库挺好用的,makefile生成一个.a库文件。
RS232与RS485的区别:
一、接口形状
RS232接口形状固定,RS485无具体的物理形状,根据工程的实际情况而采用的接口。
二、接口的电子特性
1、RS232:传输电平信号接口的信号电平值较高(信号“1”为“-3V至-15V”,信号“0”为“3至15V”),易损坏接口电路的芯片,又因为与TTL电平(0“<0.8v”,1“>2.0V”)不兼容故需使用电平转换电路方能与TTL电路连接。另外抗干扰能力差。
2、RS485:传输差分信号逻辑“1”以两线间的电压差为+(2—6) V表示;逻辑“0”以两线间的电压差为-(2—6)V表示。接口信号电平比RS-232降低了,就不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便与TTL电路连接。
三、通讯距离长短
1、RS232:RS232传输距离有限,最大传输距离标准值为15米,且只能点对点通讯,最大传输速率最大为20kB/s。
2、RS485:RS485最大无线传输距离为1200米。最大传输速率为10Mbps,在100Kb/S的传输速率下,才可以达到最大的通信距离。
采用阻抗匹配、低衰减的专用电缆可以达到1800米!超过1200米,可加中继器(最多8只),这样传输距离接近10Km。
四、能否支持多点通讯
RS232:RS232接口在总线上只允许连接1个收发器,不能支持多站收发能力,所以只能点对点通信,不支持多点通讯。
RS485:RS485接口在总线上是允许连接多达128个收发器。即具有多站通讯能力,这样用户可以利用单一的RS485接口方便地建立起设备网络。
五、通讯线的差别
RS232:可以采用三芯双绞线、三芯屏蔽线等。
RS485:可以采用两芯双绞线、两芯屏蔽线等。在低速、短距离、无干扰的场合可以采用普通的双绞线,反之,在高速、长线传输时,则必须采用阻抗匹配(一般为120Ω)的RS485专用电缆(STP-120Ω(用于RS485 & CAN)一对18AWG),而在干扰恶劣的环境下还应采用铠装型双绞屏蔽电缆(ASTP-120Ω(用于RS485 & CAN)一对18AWG)。
通信协议的特性都和stm32类似,略
通信协议的特性都和stm32类似,略
存储器:
纯粹的Flash:NandFlash、NorFlash
(1)这些是最早出现的、最原始的Flash颗粒组成芯片。NandFlash、NorFlash芯片中只是对存储单元做了最基本的读写接口,然后要求外部的SoC来提供Flash读写的控制器和Flash进行读写时序。
(2)缺陷:1、读写接口时序比较复杂。2、内部无坏块处理机制,需要SoC自己来管理Flash的坏块;3、各家厂家的Flash接口不一致
(3)NandFlash分MLC和SLC两种。SLC技术比较早,可靠性高,缺点是容量做不大(或者说容量大了太贵,一般SLC Nand都是512MB以下);MLC技术比较新,不成熟,可靠性差,优点是容量可以做很大很便宜,现在基本都在发展MLC技术。
SD卡、MMC卡、MicroSD、TF卡
(1)这些卡其实内部就是Flash存储颗粒,比直接的Nand芯片多了统一的外部封装和接口。
(2)卡都有统一的标准,譬如SD卡都是遵照SD规范来发布的。这些规范规定了SD卡的读写速度、读写接口时序、读写命令集、卡大小尺寸、引脚个数及定义。
iNand、MoviNand、eSSD
(4)优势:1、向SD卡学习,有统一的接口标准(包括引脚定义、物理封装、接口时序)。2、向原始的Nand学习,以芯片的方式来发布而不是以卡的方式;3、内部内置了Flash管理模块,(小单片机)提供了诸如坏块管理等功能,让Nand的管理容易了起来。
SD卡和MMC卡的关系
(1)MMC标准比SD标准早,SD标准兼容MMC标准。
(2)MMC卡可以被SD读卡器读写,而SD卡不可以被MMC读卡器读写。
SD卡的编程接口:
SD卡的物理接口
SD卡由9个针脚与外界进行物理连接,这9个脚中有2个地,1个电源,6个信号线。
SD协议与SPI协议
(1)SD卡与SRAM/DDR/SROM之类的东西的不同:SRAM/DDR/SROM之类的存储芯片是总线式的,只要连接上初始化好之后就可以由SoC直接以地址方式来访问;但是SD卡不能直接通过接口给地址来访问,它的访问需要按照一定的接口协议(时序)来访问。
(2)SD卡虽然只有一种物理接口,但是却支持两种读写协议:SD协议和SPI协议。
SPI协议特点(低速、接口操作时序简单、适合单片机)
(1)SPI协议是单片机中广泛使用的一种通信协议,并不是为SD卡专门发明的。
(2)SPI协议相对SD协议来说速度比较低。
(3)SD卡支持SPI协议,就是为了单片机方便使用。
SD协议特点(高速、接口时序复杂,适合有SDIO接口的SoC)
(1)SD协议是专门用来和SD卡通信的。
(2)SD协议要求SoC中有SD控制器,运行在高速率下,要求SoC的主频不能太低。
S5PV210的SD/MMC控制器
(1)热插拔
(2)SD卡内部除了存储单元Flash外,还有SD卡管理模块,SoC和SD卡通信时,通过9针引脚以SD协议/SPI协议向SD卡管理模块发送命令、时钟、数据等信息,然后从SD卡返回信息给SoC来交互。工作时每一个任务(如初始化SD卡、读一个块、写、擦除····)都需要一定的时序来完成(这些任务有专门搞SD卡的人会研究,用的时候调用别人写好的库就可以)
量程(模拟量输入范围)
(1)AD转换器是一个电子器件,所以他只能输入电压信号。其他种类的模拟信号要先经过传感器(Sensor)(物联网很常见)的转换变成模拟的电压信号然后才能给AD。
现在很多都是将这些集成在一块芯片上:
(2)AD输入端的模拟电压要求有一个范围,一般是0~3.3或0~5或0~12V等等。模拟电压的范围是AD芯片本身的一个参数。实际工作时给AD的电压信号不能超过这个电压范围。
精度(分辨率resolution)
(1)AD转换输出的数字值是有一定的位数的(譬如说10位,意思就是输出的数字值是用10个二进制位来表示的,这种就叫10位AD)。这个位数就表示了转换精度。
(2)10位AD就相当于把整个范围分成了1024个格子,每个格子之间的间隔就是电压的表示精度。加入AD芯片的量程是0~3.3V,则每个格子代表的电压值是3.3V/1024=0.0032265V。如果此时AD转换后得到的数字量是447,则这个数字量代表的模拟值是:447×0.0032265V=1.44V。
转换速率(MSPS与conventor clock的不同)
(1)AD芯片进行AD转换是要耗费时间的。精度配置为10位时时间比精度配置为12位时要小,有些AD可以配转换时钟,时钟频率高则转换时间短
(2)转换速率用的单位是MSPS(第一个M是兆,S是sample,就是采样;PS就是per second,总的意思就是兆样本每秒,每秒种转出来多少M个数字值)
(3)AD工作都需要一个时钟。AD转换是在这个时钟下进行的,时钟的频率控制着AD转换的速率。注意:时钟频率和MSPS不是一回事,只是成正比不是完全相等。如S5PV210中的AD转换器,MSPS = 时钟频率/5
通道数
(1)AD芯片有多少路analog input通道,代表了将来可以同时进行多少路模拟信号的输入。
ADC控制器
ADC的工作时钟框图
从时钟框图可以看出,它是PCLK(当然是PCLK_PSYS)经过了一次分频后得到的。所以初始化ADC控制器时要初始化的分频器。
s5pv210ADC:
特征:
•分辨率:10位/12位(可选)
•微分非线性误差:±1.0 LSB(最大值)
积分非线性误差:±4.0 LSB(最大值)
•最大转换率:1MSPS
•低功耗
•电源电压:3.3V
•模拟输入范围:0~3.3V
•片上采样和保持功能
•正常转换模式
•单独的X/Y位置转换模式
•自动(顺序)X/Y位置转换模式
•等待中断模式
•IDLE、DIDLE、STOP和DSTOP模式唤醒源
•两个触摸屏接口
引脚:
ADCIN0,ADCIN1是只能用在ADC上的,其他的8个是和touch screen复用的,8个分为两组,可以供两个触摸屏使用
主要寄存器:
TSADCCON0
TSDATX0 TSDATY0 转出来的AD值存在这里,我们读也是读这里
CLRINTADC0 清中断
ADCMUX 选择当前正在操作的AD通道
(1) 等待触摸屏转换完毕的方法有2种:一种是检查标志位,(标志位在:TSADCCON0的第15位ECFLG)第二种是中断。第一种方式下我们先开启一次转换然后循环不停检查标志位直到标志位为1表明已经转换完可以去读了;第二种方式下就是设置好中断,写好中断isr来读取AD转换数据。然后开启中断后CPU就不用管了,等AD转换完成后会生成一个中断信号给CPU,就会进入中断处理流程。
(2) ADC最高的工作时钟频率是5Mhz,所以在PCLK_PSYS的66Mhz过来一定是要分频的,TSADCCON0的6~14位
(3) AD转换都是需要反复进行的,转完一次一般要立即开启下一次转换,所以需要有一种机制能够在一次转完时自动开启下一次。这个机制就叫start by read,这个机制的工作方法是:当我们读取本次AD转换的AD值后,硬件自动开启下一次AD转换。
开启start by read模式,第一次先读一次丢掉,这次读就能开启下一次AD转换,然后以后就可以不停的读取AD值了。
略
略
CORTEX-A8:和CORTEX-M3不同的是,其还有用于连接DRAM的专用引脚MPIO,其包括地址线,数据线,命令线,用于和DRAM进行通信。
CORTEX-A8:有四种时钟来源。分为MSYS、DSYS、PSYS3个时钟域。有AHB、APB总线(但是AMBA章节对此描述不清楚,不太清楚总线构成),APLL、EPLL、MPLL、VPLL四个倍频电路。(编程:基本只需要设置pll倍频,div分频参数,mux开关(确定时钟来源),即可使用)
CORTEX-M3:分为HCLK、PCLK1、PCLK2时钟总线。有HSI(内部)和HSE(外部)两种时钟来源。(编程:需要使能晶振,也就是时钟来源,使能各个时钟总线,设置分频及倍频因子)
CORTEX-A8:接在APB总线上(PCLK_PSYS),一共有UART0、UART1、UART2、UART3四个串口,其引脚分别在GPA0、GPA1(在第一章GPIO有介绍)。(编程:初始化GPIO为uart的TX和RX模式,配置校验位,起始位,停止位,字长,流控,fifo,红外,中断模式,DMA模式等,配置波特率。发送和接收都是在缓冲区中操作即可。)
CORTEX-M3:和CORTEX-A8类似
CORTEX-A8:需要绑定中断向量表和函数,中断号和中断处理函数,中断向量函数需要上下文切换,这些在CORTEX-M3上也应该是需要的,但是没有在编程中看到(我不知道为什么。)
CORTEX-M3:和CORTEX-A8一样,对于外部中断,中断之后都会有挂起位,在中断处理函数中都需要清挂起
CORTEX-A8:三个dma控制器
CORTEX-M3:两个dma控制器dma1,dma2(只存在与大容量cpu中),八个通道
CORTEX-A8:和CORTEX-M3区别比较大,RTC,wdt
CORTEX-M3:输出比较,输入捕获功能。分为基本定时器、通用定时器、高级定时器。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。