目录
前言
1.GPIO实验
GPIO(General-purpose input/output)即通用型输入输出,GPIO可以控制连接在其之上的引脚实现信号的输入和输出
芯片的引脚与外部设备相连,从而实现与外部硬件设备的通讯、控制及信号采集等功能
LED 实验步骤
1. 通过电路原理图分析LED的控制逻辑 - 高电平点亮、低电平熄灭
2. 通过电路原理图查找LED与Exynos4412的连接关系 - GPX2_73. 通过数据手册分析GPIO中哪些寄存器可以控制LED - GPX2CON、GPX2DAT
4. 通过程序去操控对应的寄存器完成对LED的控制
1.1汇编代码:
实现LED等循环开闭
.text
_start:
MAIN:
BL LED_CONFIGURATE
LOOP:
BL LED_OFF
BL LED_DELAY
BL LED_ON
BL LED_DELAY
BL LOOP
LED_CONFIGURATE:
LDR R1, =0X10000000
LDR R2, =0X11000C40
STR R1,[R2]
MOV PC,LR
LED_ON:
LDR R1, =0X00000080
LDR R2, =0X11000C44
STR R1,[R2]
MOV PC,LR
LED_OFF:
LDR R1, =0X00000000
LDR R2, =0X11000C44
STR R1,[R2]
MOV PC,LR
LED_DELAY:
LDR R1, =1000000000
L:
SUB R1,R1,#1
CMP R1,#0
BNE L
MOV PC,LR
STOP:
B STOP
.end
1.2 Makefile:
汇编文件需要 -c 汇编成.o机器码文件,然后链接ld 成.elf文件
其中倒数第二条指令是因为目前开发板是裸机,为了方便实验加上的命令,生成.bin文件
TARGET = led-asm
CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
all:
$(CC) -c $(TARGET).s -o $(TARGET).o
$(LD) $(TARGET).o -Ttext 0x40008000 -o $(TARGET).elf
$(OBJCOPY) -O binary -S $(TARGET).elf $(TARGET).bin
clean:
rm $(TARGET).o $(TARGET).elf $(TARGET).bin
2.C工程
2.1普通寄存器操作方法
//可以使用宏定义或结构体,针对大量的寄存器时,使用结构体更方便。
//前提是寄存器的内存地址连续
#if 1
typedef struct {
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (*(gpx2 *)0x11000c40)
void delay(int x){
while(x--);
}
int main()
{
//引脚设置
GPX2.CON = 0x10000000;
while(1){
//关灯
GPX2.DAT = 0x00000000;
delay(1000000);
//开灯
GPX2.DAT = 0x00000080;
delay(1000000);
}
return 0;
}
#endif
2.2寄存器标准化操作
#include "exynos_4412.h"
void delay(int x){
while(x--);
}
int main()
{
//引脚设置
//标准化操作
GPX2.CON = GPX2.CON & (~(0xf << 28)) | (0x1 << 28);
//GPX2.CON = 0x10000000;
while(1){
//关灯
//目标位置0
GPX2.DAT = GPX2.DAT & ~(1 << 7);
// GPX2.DAT = 0x00000000
delay(1000000);
//开灯
//目标位置1
GPX2.DAT = GPX2.DAT | (1 << 7);
//GPX2.DAT = 0x00000080
delay(1000000);
}
return 0;
}
作业:LED2-5流水灯闪烁
#include "exynos_4412.h"
void delay(int x){
while(x--);
}
int main()
{
//引脚设置
//标准化操作
GPX2.CON = GPX2.CON & (~(0xf << 28)) | (0x1 << 28);
GPX1.CON = GPX1.CON & (~(0xf)) | (0x1);
GPF3.CON = GPF3.CON & (~(0xf << 16)) | (0x1 << 16);
GPF3.CON = GPF3.CON & (~(0xf << 20)) | (0x1 << 20);
while(1){
//****LED2开灯 GPX2_7****
//目标位置1
GPX2.DAT = GPX2.DAT | (1 << 7);
delay(1000000);
//LED2关灯
//目标位置0
GPX2.DAT = GPX2.DAT & ~(1 << 7);
delay(1000000);
//****LED3开灯 GPX1_0****
//目标位置1
GPX1.DAT = GPX1.DAT | (1);
delay(1000000);
//LED3关灯
//目标位置0
GPX1.DAT = GPX1.DAT & ~(1);
delay(1000000);
//****LED4开灯 GPF3_4****
//目标位置1
GPF3.DAT = GPF3.DAT | (1 << 4);
delay(1000000);
//LED4关灯
//目标位置0
GPF3.DAT = GPF3.DAT & ~(1 << 4);
delay(1000000);
//****LED5开灯 GPF3_5****
//目标位置1
GPF3.DAT = GPF3.DAT | (1 << 5);
delay(1000000);
//LED5关灯
//目标位置0
GPF3.DAT = GPF3.DAT & ~(1 << 5);
delay(1000000);
}
return 0;
}
3.UART
Universal Asynchronous Receiver Transmitter 即通用异步收发器,是一种通用的串行、异步通信总线。该总线有两条数据线,可以实现全双工的发送和接收在嵌入式系统中常用于主机与辅助设备之间的通信。
3.1完成串口通讯
#include "exynos_4412.h"
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 8位数据位 1位停止位 无校验 正常模式 ULCON2[6:0]*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:0]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 UBRDIV2/UFRACVAL2*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void UART_Send_Byte(char Dat)
{
/*等待发送寄存器为空,即上一个数据已经发送完成 UTRSTAT2[1]*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将要发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void)
{
char Dat = 0;
/*判断接收寄存器是否接收到了数据 UTRSTAT2[0]*/
if(UART2.UTRSTAT2 & 1)
{
/*从接收寄存器中读取接收到的数据 URXH2*/
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
void UART_Send_Str(char * pstr)
{
while(*pstr != '\0')
UART_Send_Byte(*pstr++);
}
int main()
{
char RecDat = 0;
UART_Init();
while(1)
{
RecDat = UART_Rec_Byte();
if(RecDat != 0)
{
RecDat = RecDat + 1;
UART_Send_Byte(RecDat);
}
/*
UART_Send_Str("Hello World\n");
*/
printf("Hello World\n");
}
return 0;
}
3.2输入输出重定向
在后面的学习中 linux内核层面,自己实现的printf()函数输出到串口。而应用层printf()函数是c库函数,输出定向到显卡
3.3练习
完成输入2,led亮,再次输入2,led灭
#include "exynos_4412.h"
//GPIO初始化
void GPIO_Init(){
/*GPX2_7*/
GPX2.CON = GPX2.CON & (~(0xf << 28)) | (1 << 28);
}
//开灯
void LED2_ON(){
GPX2.DAT = GPX2.DAT & (~(0x1 << 7)) | (0x1 << 7);
}
//关灯
void LED2_OFF(){
GPX2.DAT = GPX2.DAT & (~(0x1 << 7));
}
//UART串口初始化
void UART_Init(){
/*1.将GPA1_0和GPA1_1设置成UART2的接收引脚和发送引脚GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xf)) | (0x1 << 1);//接收引脚
GPA1.CON = GPA1.CON & (~(0xf << 4)) | (0x1 << 5);//发送引脚
/*2.ULCON2*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7f << 0)) | (0x3 << 0);
/*3.UCON2设置接收和发送模式为轮询模式*/
UART2.UCON2 = UART2.UCON2 & (~(0xf << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void UART_Send_Byte(char Dat){
while(!(UART2.UTRSTAT2 & (1 << 1)));//有数据就等待数据发送
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(){
char Dat = 0;
if(UART2.UTRSTAT2 & (1)){ //接收端有数据就返回接收并返回
Dat = UART2.URXH2;
return Dat;
}else{
return 0;
}
}
int main()
{
GPIO_Init();
UART_Init();
char ch = 0;
while(1){
ch = UART_Rec_Byte();
if(ch == '2' && !(GPX2.DAT & (0x1 << 7))){//开
LED2_ON();
}else if(ch == '2' && (GPX2.DAT & (0x1 << 7))){//关
LED2_OFF();
}
}
return 0;
}
4.WDT
Watch Dog Timer即看门狗定时器,其主要作用是当发生软件。故障时可产生复位信号使SOC复位,其本质是一个计数器。
只要定时喂狗,且cpu不出异常,那么计数器就永远不会减到0,那么cpu就不用复位
#include "exynos_4412.h"
//WDT初始化
void WDT_Init(){
//WDTCNT递减频率100000000/256/128 = 3052
/*一次分频256*/
WDT.WTCON = WDT.WTCON & (~(0xff << 8)) | (0xff << 8);
/*2次分频128和计时器使能*/
WDT.WTCON = WDT.WTCON & (~(0xff)) | (0x39);
WDT.WTCNT = 3052 * 5;//初始计数 可以使用5s的数
}
void Delay(int x){
while(x--);
}
int main()
{
WDT_Init();
while(1){
printf("WDT.WTCNT = %d\n",WDT.WTCNT);
//喂狗
WDT.WTCNT = 3052;
Delay(10000);
}
return 0;
}
5.轮询与中断
轮询
CPU执行程序时不断地询问硬件是否需要其服务,若需要则给予其服务,若不需要一段时间后再次询问,周而复始
中断
CPU执行程序时若硬件需要其服务,对应的硬件给CPU发送中断信号,CPU接收到中断信号后将当前的程序暂停下来,转而去执行中断服务程序,执行完成后再返回到被打断的点继续执行
DMA
硬件产生数据后,硬件控制器可将产生的数据直接写入到存储器中,整个过程无需CPU的参与
按下按钮key2,打印信息
5.1轮询法:
#include "exynos_4412.h"
int main()
{
GPX1.CON = GPX1.CON & (~(0xf << 4));
while(1){
if(!(GPX1.DAT & (0x1 << 1))){ //按下key2
printf("按下key2.\n");
while(!(GPX1.DAT & (0x1 << 1)));//如果一直按下就死循环
}
}
return 0;
}
5.2 中断法
完整代码:按住key2,打印 "key2 press!" 信息
采用混合编程
1.汇编启动代码
.text
.global _start
_start:
/*
* Vector table
*/
b reset
b .
b .
b .
b .
b .
/*
* 从异常向量表再跳转到IRQ的异常处理程序
*/
b irq_handler
b .
reset:
/*
* Set vector address in CP15 VBAR register
*/
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/*
* Set the cpu to SVC32 mode, Disable FIQ/IRQ
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr ,r0
/*
* Defines access permissions for each coprocessor
*/
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2
/*
* Invalidate L1 I/D
*/
mov r0, #0 @Set up for MCR
mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @Invalidate icache
/*
* Set the FPEXC EN bit to enable the FPU
*/
mov r3, #0x40000000
fmxr FPEXC, r3
/*
* Disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @Clear bits 13 (--V-)
bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)
orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache
orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* Initialize stacks
*/
init_stack:
/*svc mode stack*/
msr cpsr, #0xd3
ldr sp, _stack_svc_end
/*undef mode stack*/
msr cpsr, #0xdb
ldr sp, _stack_und_end
/*abort mode stack*/
msr cpsr,#0xd7
ldr sp,_stack_abt_end
/*irq mode stack*/
msr cpsr,#0xd2
ldr sp, _stack_irq_end
/*fiq mode stack*/
msr cpsr,#0xd1
ldr sp, _stack_fiq_end
/*user mode stack, enable FIQ/IRQ*/
msr cpsr,#0x10
ldr sp, _stack_usr_end
/*Call main*/
b main
/*
* IRQ的异常处理程序
*/
irq_handler:
/*
* 因为产生IRQ异常后ARM自动保存到LR中的返回地址是被IRQ打断的指令
* 的下一条再下一条指令的地址,所以我们需要人为的去修正一下
*/
sub lr, lr, #4
/*
* 因为IRQ模式下使用的R0-R12寄存器和USER模式下使用的是同一组
* 所以在处理异常之前需要先将之前寄存器中的值压栈保护
*/
stmfd sp!, {r0-r12,lr}
/*
* 跳转到do_irq处理异常
*/
bl do_irq
/*
* 异常返回
* 1.将R0-R12寄存器中的值出栈,使其恢复到被异常打断之前的值
* 2.将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前
* 3.将栈中保存的LR寄存器的值出栈给PC,使程序跳转回被异常打断的点继续执行
*/
ldmfd sp!,{r0-r12,pc}^
_stack_svc_end:
.word stack_svc + 512
_stack_und_end:
.word stack_und + 512
_stack_abt_end:
.word stack_abt + 512
_stack_irq_end:
.word stack_irq + 512
_stack_fiq_end:
.word stack_fiq + 512
_stack_usr_end:
.word stack_usr + 512
.data
stack_svc:
.space 512
stack_und:
.space 512
stack_abt:
.space 512
stack_irq:
.space 512
stack_fiq:
.space 512
stack_usr:
.space 512
2.C代码:实现功能
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
//IRQ异常处理
void do_irq(void)
{
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号*/
IrqNum = CPU0.ICCIAR & 0x3FF;
/*根据中断号处理不同的中断*/
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
/*
* ... ...
*/
case 57:
printf("Key2 Pressed\n");
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57);
break;
/*
* ... ...
*/
case 159:
//159号中断的处理程序
break;
default:
break;
}
}
int main()
{
/*外设层次 - 让外部的硬件控制器产生一个中断信号发送给中断控制器*/
/*将GPX1_1设置成中断功能*/
GPX1.CON = GPX1.CON | (0xF << 4);
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
/*使能GPX1_1的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
/*中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理*/
/*全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口*/
ICDDCR = ICDDCR | 1;
/*在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
/*选择由CPU0来处理57号中断*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0X01 << 8);
/*使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0*/
CPU0.ICCICR = CPU0.ICCICR | 1;
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*延时*/
Delay(1000000);
}
return 0;
}
6.ADC实验
1. 代码:
#include "exynos_4412.h"
/*使用混合编程*/
/*key3*/
void ADC_Init(){
/*1.设置精度12*/
ADCCON = ADCCON |(1 << 16);
/*2.预分频使能*/
ADCCON = ADCCON | (1 << 14);
/*3.预分频值 100*/
ADCCON = ADCCON & (~(0xff << 6)) | (39 << 6);
/*4.待机模式-正常*/
ADCCON = ADCCON & (~(1 << 2));
/*5.关闭通过读 使能*/
ADCCON = ADCCON & (~(1 << 1));
/*********************************/
/*模拟输入通道设置AIN3*/
ADCMUX = 3;
}
unsigned int res;
//使用中断
int main()
{
/****************一.ADC初始化****************/
ADC_Init();
while(1){
/*开始转换*/
ADCCON = ADCCON | (1);
while(!(ADCCON & (1 << 15)));//如果转换没有完成就一直循环
res = ADCDAT & (0xfff);
res = res*0.44;
printf("res = %dmv\n",res);//1.8/2^12 = 0.00044V = 0.44mv
}
return 0;
}
2.练习
电压在1501mv~1800mv时,LED2、LED3、LED4、LED5点亮
电压在1001mv~1500mv时,LED2、LED3、LED4点亮
电压在501mv~1000mv时,LED2、LED3点亮
电压在0mv~500mv时,LED2闪烁
#include "exynos_4412.h"
/*使用混合编程*/
/*adc设置*/
void ADC_Init(){
/*1.设置精度12*/
ADCCON = ADCCON |(1 << 16);
/*2.预分频使能*/
ADCCON = ADCCON | (1 << 14);
/*3.预分频值 100*/
ADCCON = ADCCON & (~(0xff << 6)) | (39 << 6);
/*4.待机模式-正常*/
ADCCON = ADCCON & (~(1 << 2));
/*5.关闭通过读 使能*/
ADCCON = ADCCON & (~(1 << 1));
/*********************************/
/*模拟输入通道设置AIN3*/
ADCMUX = 3;
}
/*led设置*/
void LED_Init(){
//引脚设置
//标准化操作
GPX2.CON = GPX2.CON & (~(0xf << 28)) | (0x1 << 28);
GPX1.CON = GPX1.CON & (~(0xf)) | (0x1);
GPF3.CON = GPF3.CON & (~(0xf << 16)) | (0x1 << 16);
GPF3.CON = GPF3.CON & (~(0xf << 20)) | (0x1 << 20);
}
void LED2_ON(){
//目标位置1
GPX2.DAT = GPX2.DAT | (1 << 7);
}
void LED2_OFF(){
//目标位置0
GPX2.DAT = GPX2.DAT & ~(1 << 7);
}
void LED3_ON(){
//目标位置1
GPX1.DAT = GPX1.DAT | (1);
}
void LED3_OFF(){
//目标位置0
GPX1.DAT = GPX1.DAT & ~(1);
}
void LED4_ON(){
//目标位置1
GPF3.DAT = GPF3.DAT | (1 << 4);
}
void LED4_OFF(){
//目标位置0
GPF3.DAT = GPF3.DAT & ~(1 << 4);
}
void LED5_ON(){
//目标位置1
GPF3.DAT = GPF3.DAT | (1 << 5);
}
void LED5_OFF(){
//目标位置0
GPF3.DAT = GPF3.DAT & ~(1 << 5);
}
void delay(int x){
while(x--);
}
unsigned int res;
//使用中断
int main()
{
/****************一.ADC初始化****************/
ADC_Init();
LED_Init();
while(1){
/*开始转换*/
ADCCON = ADCCON | (1);
while(!(ADCCON & (1 << 15)));//如果转换没有完成就一直循环
res = ADCDAT & (0xfff);
res = res*0.44;//1.8/2^12 = 0.00044V = 0.44mv
if(res <= 500){
//LED2_ON();
LED3_OFF();
LED4_OFF();
LED5_OFF();
while(res <= 500){
ADCCON = ADCCON | (1);
while(!(ADCCON & (1 << 15)));//如果转换没有完成就一直循环
res = ADCDAT & (0xfff);
res = res*0.44;
//****LED2开灯 GPX2_7****
LED2_ON();
delay(1000000);
//LED2关灯
LED2_OFF();
delay(1000000);
}
}else if(res > 500 && res <= 1000){
LED2_ON();
LED3_ON();
LED4_OFF();
LED5_OFF();
}else if(res > 1000 && res <= 1500){
LED2_ON();
LED3_ON();
LED4_ON();
LED5_OFF();
}else if(res > 1500 && res <= 1800){
LED2_ON();
LED3_ON();
LED4_ON();
LED5_ON();
}
}
return 0;
}
7.RTC实验
演示:每隔1s打印时间信息
#include "exynos_4412.h"
void RTC_Init(){
/*启动RTC控制*/
RTCCON = RTCCON | (1);
/*重置时间 以BCD码存储*/
RTC.BCDSEC = 0x18;
RTC.BCDMIN = 0x18;
RTC.BCDHOUR = 0x18;
RTC.BCDWEEK = 0x18;
RTC.BCDMON = 0x12;
RTC.BCDYEAR = 0x023;
RTC.BCDDAY = RTC.BCDDAY & (~(0x7)) | (0x6); //芯片手册的day和week地址反了
/*取消RTC控制*/
RTCCON = RTCCON & (~(1));
}
//使用中断
int main()
{
unsigned int oldsec = 0;
unsigned int newsec = 0;
RTC_Init();
while(1){
newsec = RTC.BCDSEC;
if(oldsec != newsec){
//获取时间
printf("20%x-%x-%x %x:%x:%x 星期%x\n",RTC.BCDYEAR,RTC.BCDMON,RTC.BCDWEEK,RTC.BCDHOUR,RTC.BCDMIN,RTC.BCDSEC,RTC.BCDDAY);
oldsec = newsec;
}
}
return 0;
}
练习:
编程实现通过LED状态显示当前电压范围,并打印产生低压警报时的时间
注:
电压在1501mv~1800mv时,LED2、LED3、LED4、LED5点亮
电压在1001mv~1500mv时,LED2、LED3、LED4点亮
电压在501mv~1000mv时,LED2、LED3点亮
电压在0mv~500mv时,LED2闪烁,且每隔一秒钟向终端打印一次当前的电压值及当前的时间
#include "exynos_4412.h"
/*adc设置*/
void ADC_Init(){
/*1.设置精度12*/
ADCCON = ADCCON |(1 << 16);
/*2.预分频使能*/
ADCCON = ADCCON | (1 << 14);
/*3.预分频值 100*/
ADCCON = ADCCON & (~(0xff << 6)) | (39 << 6);
/*4.待机模式-正常*/
ADCCON = ADCCON & (~(1 << 2));
/*5.关闭通过读 使能*/
ADCCON = ADCCON & (~(1 << 1));
/*********************************/
/*模拟输入通道设置AIN3*/
ADCMUX = 3;
}
/*RTC设置*/
void RTC_Init(){
/*启动RTC控制*/
RTCCON = RTCCON | (1);
/*重置时间 以BCD码存储*/
RTC.BCDSEC = 0x18;
RTC.BCDMIN = 0x18;
RTC.BCDHOUR = 0x18;
RTC.BCDWEEK = 0x18;
RTC.BCDMON = 0x12;
RTC.BCDYEAR = 0x023;
RTC.BCDDAY = RTC.BCDDAY & (~(0x7)) | (0x6); //芯片手册的day和week地址反了
/*取消RTC控制*/
RTCCON = RTCCON & (~(1));
}
/*led设置*/
void LED_Init(){
//引脚设置
//标准化操作
GPX2.CON = GPX2.CON & (~(0xf << 28)) | (0x1 << 28);
GPX1.CON = GPX1.CON & (~(0xf)) | (0x1);
GPF3.CON = GPF3.CON & (~(0xf << 16)) | (0x1 << 16);
GPF3.CON = GPF3.CON & (~(0xf << 20)) | (0x1 << 20);
}
void LED2_ON(){
//目标位置1
GPX2.DAT = GPX2.DAT | (1 << 7);
}
void LED2_OFF(){
//目标位置0
GPX2.DAT = GPX2.DAT & ~(1 << 7);
}
void LED3_ON(){
//目标位置1
GPX1.DAT = GPX1.DAT | (1);
}
void LED3_OFF(){
//目标位置0
GPX1.DAT = GPX1.DAT & ~(1);
}
void LED4_ON(){
//目标位置1
GPF3.DAT = GPF3.DAT | (1 << 4);
}
void LED4_OFF(){
//目标位置0
GPF3.DAT = GPF3.DAT & ~(1 << 4);
}
void LED5_ON(){
//目标位置1
GPF3.DAT = GPF3.DAT | (1 << 5);
}
void LED5_OFF(){
//目标位置0
GPF3.DAT = GPF3.DAT & ~(1 << 5);
}
void delay(int x){
while(x--);
}
unsigned int res;
//使用中断
int main()
{
//
/****************初始化****************/
RTC_Init();
ADC_Init();
LED_Init();
while(1){
/*开始转换*/
ADCCON = ADCCON | (1);
while(!(ADCCON & (1 << 15)));//如果转换没有完成就一直循环
res = ADCDAT & (0xfff);
res = res*0.44;//1.8/2^12 = 0.00044V = 0.44mv
if(res <= 500){
LED3_OFF();
LED4_OFF();
LED5_OFF();
//打印时间,led2闪烁
unsigned int oldsec = 0;
unsigned int newsec = 0;
while(res <= 500){
ADCCON = ADCCON | (1);
while(!(ADCCON & (1 << 15)));//如果转换没有完成就一直循环
res = ADCDAT & (0xfff);
res = res*0.44;
//每秒打印
newsec = RTC.BCDSEC;
if(oldsec != newsec){
//****LED2开灯****
LED2_ON();
//获取时间
printf("当前电压:%dmv 20%x-%x-%x %x:%x:%x 星期%x\n",res,RTC.BCDYEAR,RTC.BCDMON,RTC.BCDWEEK,RTC.BCDHOUR,RTC.BCDMIN,RTC.BCDSEC,RTC.BCDDAY);
oldsec = newsec;
}else{
//****LED2关灯****
LED2_OFF();
}
}
}else if(res > 500 && res <= 1000){
LED2_ON();
LED3_ON();
LED4_OFF();
LED5_OFF();
}else if(res > 1000 && res <= 1500){
LED2_ON();
LED3_ON();
LED4_ON();
LED5_OFF();
}else if(res > 1500 && res <= 1800){
LED2_ON();
LED3_ON();
LED4_ON();
LED5_ON();
}
}
return 0;
}
8.PWM实验
GPIO控制浪费大量CPU资源,不合适
PWM控制不浪费CPU资源
8.1PWM参数
代码演示:
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
/*1.将GPD0_0引脚设置成PWM0的输出引脚*/
GPD0.CON = GPD0.CON & (~(0xF)) | (0x2);
/*2.设置PWM0的一级分频 一级分频倍数设置为100倍*/
PWM.TCFG0 = PWM.TCFG0 & (~(0xFF)) | 99;
/*2.设置PWM0的二级分频 二级分频倍数设置为1倍 递减计数器递减频率 = PLCK / (99 + 1) / 1 = 1M*/
PWM.TCFG1 = PWM.TCFG1 & (~(0xF));
/*4.设置PWM0为自动重装载,使其能够产生连续的脉冲信号*/
PWM.TCON = PWM.TCON | (1 << 3);
/*5.设置PWM0的频率为500HZ*/
PWM.TCNTB0 = 2000;
/*6.设置PWM0的占空比为50%*/
PWM.TCMPB0 = 1000;
/*7.将TCNTB0中的值手动装载到递减计数器*/
PWM.TCON = PWM.TCON | (1 << 1);
/*8.关闭手动更新*/
PWM.TCON = PWM.TCON & (~(1 << 1));
/*9.使能PWM0,递减计数器开始递减*/
PWM.TCON = PWM.TCON | 1;
while(1)
{
PWM.TCON = PWM.TCON | 1;
Delay(1000000);
PWM.TCON = PWM.TCON & (~(1));
Delay(1000000);
}
return 0;
}
9.IIC总线协议
9.1 IIC总线简介
9.2 IIC总线通信过程
9.3SCL和SDA的起始/停止/字节传输信号
9.4典型IIC时序图
代码演示:
#include "exynos_4412.h"
/****************MPU6050内部寄存器地址****************/
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x18(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
#define SlaveAddress 0x68 //MPU6050-I2C地址
/************************延时函数************************/
void mydelay_ms(int time)
{
int i,j;
while(time--)
{
for(i=0;i<5;i++)
for(j=0;j<514;j++);
}
}
/**********************************************************************
* 函数功能:I2C向特定地址写一个字节
* 输入参数:
* slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* data:写入的数据
**********************************************************************/
void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)
{
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第三个字节数据(即要写入到MPU6050内部指定的寄存器中的数据)写入发送寄存器*/
I2C5.I2CDS = data;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*发送停止信号 结束本次通信*/
I2C5.I2CSTAT = 0xD0;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时*/
mydelay_ms(10);
}
/**********************************************************************
* 函数功能:I2C从特定地址读取1个字节的数据
* 输入参数: slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* 返回参数: unsigned char: 读取的数值
**********************************************************************/
unsigned char iic_read(unsigned char slave_addr, unsigned char addr)
{
unsigned char data = 0;
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即要读取的MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*清除中断挂起标志位 重新开始一次通信 改变数据传送方向*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+读位1)*/
I2C5.I2CDS = slave_addr << 1 | 0x01;
/*设置IIC为主机接收模式 发送起始信号 使能IIC收发*/
I2C5.I2CSTAT = 0xb0;
/*等待从机接收到数据后应答*/
while(!(I2C5.I2CCON & (1<<4)));
/*禁止主机应答信号(即开启非应答 因为只接收一个字节) 清除中断标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<7))&(~(1<<4));
/*等待接收从机发来的数据*/
while(!(I2C5.I2CCON & (1<<4)));
/*将从机发来的数据读取*/
data = I2C5.I2CDS;
/*直接发起停止信号结束本次通信*/
I2C5.I2CSTAT = 0x90;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时等待停止信号稳定*/
mydelay_ms(10);
return data;
}
/**********************************************************************
* 函数功能:MPU6050初始化
**********************************************************************/
void MPU6050_Init ()
{
iic_write(SlaveAddress, PWR_MGMT_1, 0x00); //设置使用内部时钟8M
iic_write(SlaveAddress, SMPLRT_DIV, 0x07); //设置陀螺仪采样率
iic_write(SlaveAddress, CONFIG, 0x06); //设置数字低通滤波器
iic_write(SlaveAddress, GYRO_CONFIG, 0x18); //设置陀螺仪量程+-2000度/s
iic_write(SlaveAddress, ACCEL_CONFIG, 0x0); //设置加速度量程+-2g
}
/**********************************************************************
* 函数功能:主函数
**********************************************************************/
int main(void)
{
unsigned char zvalue_h,zvalue_l; //存储读取结果
short int zvalue;
/*设置GPB_2引脚和GPB_3引脚功能为I2C传输引脚*/
GPB.CON = (GPB.CON & ~(0xF<<12)) | 0x3<<12; //设置GPB_3引脚功能为I2C_5_SCL
GPB.CON = (GPB.CON & ~(0xF<<8)) | 0x3<<8; //设置GPB_2引脚功能为I2C_5_SDA
uart_init(); //初始化串口
MPU6050_Init(); //初始化MPU6050
printf("\n********** I2C test!! ***********\n");
while(1)
{
zvalue_h = iic_read(SlaveAddress, GYRO_ZOUT_H); //获取MPU6050-Z轴角速度高字节
zvalue_l = iic_read(SlaveAddress, GYRO_ZOUT_L); //获取MPU6050-Z轴角速度低字节
zvalue = (zvalue_h<<8)|zvalue_l; //获取MPU6050-Z轴角速度
printf(" GYRO--Z :Hex: %d \n", zvalue); //打印MPU6050-Z轴角速度
mydelay_ms(100);
}
return 0;
}