ARM波形控制

1.1 波形模拟UART

设备间 通讯实现 可分为串行通信 和并行通讯两种。

常用的串行总线协议可分为:

1-wire  单线    一条数据线                                   单工   异步

uart    双线    一条数据输入线,一条数据输出线                 全双工 异步

i2c     双线    一条时钟线,一条数据线                        半双工 同步
  
spi     三线    一条时钟线,一条数据输入线,一条数据输出线      全双工 同步

串口 UART 协议

uart 串口波形

波形实现方法:

  1. CPU 已实现,仅需设置寄存器 如:day5 ARM通讯接口种使用的UART通讯
  2. GPIO管脚模拟波形 比较老旧的设备型号用此方式传输数据

GPIO管脚模拟波形:

#define GPA1CON		0x11400020
#define ULCON2 		0x13820000
#define UCON2 		0x13820004
#define UBRDIV2 	0x13820028
#define UFRACVAL2 	0x1382002c
#define UTXH2 		0x13820020
#define UTRSTAT2 	0x13820010

#define rGPA1CON 	(*(volatile unsigned int*)GPA1CON)
#define rULCON2 	(*(volatile unsigned int*)ULCON2)
#define rUCON2 		(*(volatile unsigned int*)UCON2)
#define rUBRDIV2 	(*(volatile unsigned int*)UBRDIV2)
#define rUFRACVAL2 	(*(volatile unsigned int*)UFRACVAL2)
#define rUTXH2 		(*(volatile unsigned int*)UTXH2)
#define rUTRSTAT2 	(*(volatile unsigned int*)UTRSTAT2)

void uart_putc(char c);

int main(int argc, const char *argv[])
{
	/* 设置GPA1控制器为UART模式 */
	rGPA1CON &= ~(0xff<<0);   	//把寄存器的bit0~7全部清零
	rGPA1CON |= 0x22<<0; 			//Rx,Tx
	/* 设置串口协议 */
	rULCON2 = 0x03; 			//0校验位 ,8数据位,1停止位
	rUCON2 = 0x05; 				//轮询模式
	/*
	 * 设置波特率:
	 *UART时钟信号源的值为:
	 *100Mhz= 100 000khz = 100 000 000hz
	 *本实验波特率值位115200,DIV_VAL = 100000000/(115200*16) -1 = 54.25 -1 = 53.25
	 *UBRDIVn = 53
	 *UFRACVALn/16 = 0.25 ----> UFRACVALn = 4
	 */
	rUBRDIV2 = 53; 				
	rUFRACVAL2 = 4;
	/* 发送状态判断 */	
	while(1)
	{
		uart_putc('c');
		delay1s();
	}

	return 0;
}


void uart_putc(char c)
{	
	while(!(rUTRSTAT2&0x02));
	rUTXH2 = c;
	return;
}

void uart_tx(void)  //模拟发送
{
    rGPA1CON &= ~(0xf<<0);      //把寄存器的bit0~3全部清零
    rGPA1CON |= (0x1<<0); 		//置为Output模式
    /* 发送0x04 0000 0100      0010 0000*/
    rGPA1DAT &= ~(0x1<<0);      //0
    delay10ms();                
    rGPA1DAT &= ~(0x1<<0);      //0
    delay10ms();
    rGPA1DAT |= (0x1<<0)        //1
    delay10ms();
    rGPA1DAT &= ~(0x1<<0);      //0
    delay10ms();

    rGPA1DAT &= ~(0x1<<0);      //0
    delay10ms();
    rGPA1DAT &= ~(0x1<<0);      //0
    delay10ms();
    rGPA1DAT &= ~(0x1<<0);      //0
    delay10ms();
    rGPA1DAT &= ~(0x1<<0);      //0
    delay10ms();
}

void uart_rx(void)  //模拟接收
{
    unsigned char output=0;

    rGPA1CON &= ~(0xf<<4);      //把寄存器的bit0~3全部清零
    rGPA1CON |= (0x0<<4); 	    //置为Iutput模式

    while(1)
    {
    	 if(GPA1DAT&0x02==0)
    	 	break;
      delay10ms();
    }

    if(GPADAT&0x02==0)
        output &= ~(1<<0);
    else
        output |= (1<<0);
    delay10ms();    
    if(GPADAT&0x02==0)
        output &= ~(1<<1);
    else
        output |= (1<<1);
    delay10ms();        
    if(GPADAT&0x02==0)
        output &= ~(1<<2);
    else
        output |= (1<<2);
    delay10ms();        
    if(GPADAT&0x02==0)
        output &= ~(1<<3);
    else
        output |= (1<<3);
    delay10ms();    

    if(GPADAT&0x02==0)
        output &= ~(1<<4);
    else
        output |= (1<<4);
    delay10ms();    
    if(GPADAT&0x02==0)
        output &= ~(1<<5);
    else
        output |= (1<<5);
    delay10ms();       
    if(GPADAT&0x02==0)
        output &= ~(1<<6);
    else
        output |= (1<<6);
    delay10ms();        
    if(GPADAT&0x02==0)
        output &= ~(1<<7);
    else
        output |= (1<<7);
    
}

void delay10ms(voidvoid)    //延迟x ms 根据波特率计算出具体数值,这里不做具体讨论 
{
    for(i=0;i<100000<i++);
}

1.2 I2C波形

1.2.1 I2C串行总线的组成及工作原理

1.I2C总线是PHLIPS公司推出的一种串行总线,它只有两根双向信号线。一根是数据线SDA(serial data I/O),另一根是时钟线SCL(serial clock)。
2.如下图所示,IIC一条总线上可以挂多个器件,而每个器件都有唯一的地址,这样可以标识通信目标。数据的通信的方式采用主从方式,主机负责主动联系从机,而从机则被动回应数据并及时响应。

1.2.2 I2C总线传输协议

  1. 数据位的有效性规定
    SCL为高电平期间,数据线上的数据必须保持稳定,只有SCL信号为低电平期间,SDA状态才允许变化。如图所示

  2. I2C的起始和终止信号
    SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;
    SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号;
    响应信号( ACK ):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA电平。即接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。
    CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传 递信号的判断。若未收到应答信号,由此可判断为受控单元出现故障;如图所示:

下列三种情况不会有ACK信号:
A、当从机不能响应从机地址时(从机忙于其他事无法响应IIC总线操作或这个地址没有对应从机),在第9个SCL周期内SDA线没有被拉低,即没有ACK信号。这时,主机发送一个P信号终止传输或者重新发送一个S信号开始新的传输
B、从机接收器在传输过程中不能接收更多的数据时,也不会发出ACK信号。主机意识到这点,从而发出一个P信号终止传输或者从新发送一个S信号开始新的传输
C、主机接收器在接收到最后一个字节时,也不会发出ACK信号,于是,从机发送器释放SDA线,允许主机发送P信号结束传输

  1. I2C字节的传送与应答
    每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位).如图所示

  2. 应答位的作用
    主机在发送数据时,每次发送一字节数据,都需要读取从机应答位,当从机空闲可以接收该字节数据时,从机会发出应答(一帧数据的第9位为“0”),当从机正忙于其他工作的处理来不及接收主机发送的数据时,从机会发出非应答(一帧数据的第9位为“1”)主机则应发出终止信号以结束数据的继续传送,主机通过从机发出的应答位来判断从机是否成功接收数据.

当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。
贴个IIC总线在传送数据过程中信号时序图.好好研究好时序图,一切都可以轻松解决.

  1. I2C是如何通信的
    s3C4412发送或接受数据是如何区分多个子设备?可以看下图:开始通信以后,主设备首先会发送7bit位的slave device地址,和1bit位的read或者write命令,
    (1)如果为write命令,则主设备free SDA通信线(If the I2C-bus is free, both SDA and SCL lines should be both at High level.如果I2C-bus是空闲的,那么SDA和SCL线都应该处于高电平。),即SDA为高位。然后从设备先ACK主设备(拉低SDA)表示收到命令(S)。然后主设备在发送8bit数据,从设备在ACK(A)。然然后结束(P)。
    (2)如果为read命令,则从设备先ACK主设备(拉低SDA),然后发送8bit数据,主设备ACK从设备(拉低SDA),从设备在发送,直到主设备停止接收。
    如下图所以,白色bit位为主设备发送,灰色bit位为从设备发送。

  2. 栗子

Watchdog 看门狗

工作原理:在系统运行以后也就启动了看门狗的计数器,看门狗就开始自动计数,如果到了一定的时间还不去清看门狗,那么看门狗计数器就会溢出从而引起看门狗中断,造成系统复位

1.4 PWM控制(Pulse Width Modulation 脉宽调制(PWM)定时器)

PWM(Pulse Width Modulation) : 脉冲宽度调制 。常见应用有:电机控制,DAC输出等

1.4.1 占空比:就是输出的PWM中,高电平保持的时间 与该PWM的时钟周期的时间之比

1.4.2 PWM 蜂鸣器

1.4.2 PWM 控制框图


1.4.3 PWM控制代码 及 手册分析

  1. 查询手册,找到PWM外设引脚为:MOTOR_PWM
  2. 查询手册,找到芯片端引脚为:GPD0_0
  3. 查询手册 GPIO章节,找到GPD0CON
  4. 查询手册 PWM章节,找到章节简介,可知有4个寄存器对应三级分频。

Exynos 4412 SCP有5个32位脉宽调制(PWM)定时器。这些定时器为ARM子系统产生内部中断。此外,定时器0、1、2和3包括一个驱动外部I/O信号的PWM功能。

  1. 设置一级分频 TCFG0

  2. 设置二级分频 TCFG1

  3. 设置三级分频 TCNTB0

  4. TCMPB0 占空比
    占空比是指高电平在一个周期之内所占的时间比率
    TCNTBn是脉冲总宽度,TCMPBn是高电平宽度,占空比 = TCNTBn/TCNTBn

  5. TCON

//PWM timer
//#define GPX2CON 	0x11000C40
//#define rGPX2CON 	(*(volatile unsigned int*)GPX2CON)

#define TCMPB0      0x139D0010
#define TCON        0x139D0008


#define rTCMPB0     (*(volatile unsigned int*)TCMPB0)
#define rTCON       (*(volatile unsigned int*)TCON)



void do_pwm(void)
{
    rTCMPB0 = 100;      //占空比 = 100/200 = 0.5

    rTCON &= ~(0xF);    //先用TCON 更新定时器timer0的 TCMPB0和TCMPB0值
    rTCON |= 0xA;
    rTCON &= ~(0xF);    //再用TCON开启 定时器timer 0
    rTCON |= 0x9; 
}

PWM蜂鸣器示例代码

//uart
#define GPA1CON 	0x11400020
#define ULCON2 		0x13820000
#define UCON2 		0x13820004
#define UBRDIV2 	0x13820028
#define UFRACVAL2  	0x1382002c
#define UTXH2 		0x13820020
#define UTRSTAT2 	0x13820010

#define rGPA1CON 	(*(volatile unsigned int*)GPA1CON)
#define rULCON2 	(*(volatile unsigned int*)ULCON2)
#define rUCON2 		(*(volatile unsigned int*)UCON2)
#define rUBRDIV2 	(*(volatile unsigned int*)UBRDIV2)
#define rUFBRACVAL2 (*(volatile unsigned int*)UFRACVAL2)
#define rUTXH2 		(*(volatile unsigned int*)UTXH2)
#define rUTRSTAT2 	(*(volatile unsigned int*)UTRSTAT2)


//interrupt
#define GPX1CON 		0x11000c20
#define EXT_INT41CON 	0x11000E04
#define EXT_INT41_MASK 	0x11000F04
#define ICDISER1_CPU0 	0x10490104
#define ICDDCR 			0x10490000
#define ICCPMR_CPU0 	0x10480004
#define ICCICR_CPU0 	0x10480000
#define ICCIAR_CPU0 	0x1048000C
#define EXT_INT41_PEND 	0X11000F44
#define ICDICPR1_CPU0 	0x10490284
#define ICCEOIR_CPU0 	0x10480010
#define ICDIPTR14_CPU0 	0x10490838

#define rGPX1CON 		(*(volatile unsigned int*)GPX1CON)
#define rEXT_INT41CON 	(*(volatile unsigned int*)EXT_INT41CON)
#define rEXT_INT41_MASK (*(volatile unsigned int*)EXT_INT41_MASK)
#define rICDISER1_CPU0 	(*(volatile unsigned int*)ICDISER1_CPU0)
#define rICDDCR 		(*(volatile unsigned int*)ICDDCR)
#define rICCPMR_CPU0 	(*(volatile unsigned int*)ICCPMR_CPU0)
#define rICCICR_CPU0 	(*(volatile unsigned int*)ICCICR_CPU0)
#define rICCIAR_CPU0 	(*(volatile unsigned int*)ICCIAR_CPU0)
#define rEXT_INT41_PEND (*(volatile unsigned int*)EXT_INT41_PEND)
#define rICDICPR1_CPU0 	(*(volatile unsigned int*)ICDICPR1_CPU0)
#define rICCEOIR_CPU0 	(*(volatile unsigned int*)ICCEOIR_CPU0)
#define rICDIPTR14_CPU0 (*(volatile unsigned int*)ICDIPTR14_CPU0)


//PWM
#define GPD0CON 		0x114000A0
#define TCFG0 			0x139D0000
#define TCFG1 			0x139D0004
#define TCNTB0 			0x139D000C
#define TCMPB0      	0x139D0010
#define TCON        	0x139D0008

#define rGPD0CON 		(*(volatile unsigned int*)GPD0CON)
#define rTCFG0 			(*(volatile unsigned int*)TCFG0)
#define rTCFG1 			(*(volatile unsigned int*)TCFG1)
#define rTCNTB0 		(*(volatile unsigned int*)TCNTB0)
#define rTCMPB0     	(*(volatile unsigned int*)TCMPB0)
#define rTCON       	(*(volatile unsigned int*)TCON)

void uart_putc(char c);
void do_irq(void);
void do_pwm(void);
void do_key3(void);
void do_int(void);

int main(int argc, const char *argv[])
{
	//key3
	do_key3();
	//pwm
	do_pwm();
	//int
	do_int();


	return 0;
}


void uart_putc(char c)
{	
	while(!(rUTRSTAT2&0X02));
	rUTXH2 = c;
	return;
}

void do_key3(void)
{
	//KEY3
	/* 设置GPA1控制器为UART模式 */
	rGPA1CON &= ~(0xff<<0);   	//把寄存器的bit0~7全部清零
	rGPA1CON |= 0X22<<0; 			//Rx,Tx
	/* 设置串口协议 */
	rULCON2 = 0x03; 			//0校验位 ,8数据位,1停止位
	rUCON2 = 0x05; 				//轮询模式
	/*
	 * 设置波特率:
	 *UART时钟信号源的值为:
	 *100Mhz= 100 000khz = 100 000 000hz
	 *本实验波特率值位115200,DIV_VAL = 100000000/(115200*16) -1 = 54.25 -1 = 53.25
	 *UBRDIVn = 53
	 *UFRACVALn/16 = 0.25 ----> UFRACVALn = 4
	 */
	rUBRDIV2 = 53; 				
	rUFBRACVAL2 = 4;
}	

void do_int(void)
{
	//INT
	/* 外设层次上,配置管脚工作模式 */
	rGPX1CON &= ~(0xf<<4); 	//设置为中断模式
	rGPX1CON |= (0xF<<4);

	rEXT_INT41CON &= ~(0X7<<4);
	rEXT_INT41CON |= (0X02<<4); 	//设置为下降沿触发

	rEXT_INT41_MASK &= ~(1<<1); 	//外设引脚使能中断,可以让中断发生
	/* GIC(通用中断控制器)层次上 */
	rICDISER1_CPU0 = rICDISER1_CPU0 | (1<<25); 	//使能当前中断
	rICDDCR |= (1<<0); 			//全局使能GIC 让所有中断可以通过GIC到达CPU
	rICCPMR_CPU0 |= (0xFF<<0); 	//设置中断优先级掩码级别为255
	/* CPU内核层次上 */
	rICCICR_CPU0 |= (1<<0); 		//CPU0的全局使能中断,所有中断都可以通过CPU接口到达内核
	
	rICDIPTR14_CPU0 = 0x01010101; //照抄,意义不明

	/* 发送状态判断 */	
	while(1)
	{
		uart_putc('c');
		delay1s();
	}
}

void do_irq(void) //参数中的void表示该函数不允许接受参数,理论上不写也可以,为了方便编译器通过,处于严谨考虑,还是要写上
{	
	int irq_num;
	irq_num = rICCIAR_CPU0&0x3FF; 	//获取中断号
	switch(irq_num) 
	{
	case 57:
		uart_putc('k');
		uart_putc('2');
		rEXT_INT41_PEND |= 1<<1; 	//清GPX1_1中断位
		rICDICPR1_CPU0 	|= 1<<25; 	//清GIC GPX1_1中断位
		break;
	default:
		uart_putc('x');
		break;
	}
	
	rICCEOIR_CPU0 &= (~0x3FF);
	rICCEOIR_CPU0 |= irq_num; 	//结束中断 将处理完成的中断ID号写入该寄存器 表示相应的中断处理完成
}

void do_pwm(void)
{
	rGPD0CON &= ~(0xF); 	//GPD0_0设置为PWM模式
	rGPD0CON |= 0x02;

	rTCFG0 &= ~(0xFF); 		//TCFG0[7:0] 设置1分频为100
	rTCFG0 |= 0x64;

	rTCFG1 &= ~(0xF); 		//TCFG1[3:0]设置2分频为8
	rTCFG1 |= 0x3; 			

	rTCNTB0 = 200; 			//TCNTB0 设置3分频为200
	rTCMPB0 = 100;      	//占空比 = 100/200 = 0.5

    rTCON &= ~(0xF);    	//先用TCON 更新定时器timer0的 TCMPB0和TCMPB0值
    rTCON |= 0xA;
    rTCON &= ~(0xF);    	//再用TCON开启 定时器timer 0
    rTCON |= 0x9; 	
}