ARM体系结构与接口技术学习笔记(基于Exynos4412)(更新中)

GPIO

GPIO(General-purpose input/output)即通用型输入输出,GPIO可以控制连接在其之上的引脚实现信号的输入和输出
芯片的引脚与外部设备相连,从而实现与外部硬件设备的通讯、控制及信号采集等功能

实验步骤

1. 通过电路原理图分析 LED 的控制逻辑

通过分析电路原理图可得高电平点亮,低电平熄灭

2. 通过电路原理图查找 LED 与 Exynos4412 的连接关系

通过分析电路原理图可得 LED_2 连至 Exynos4412 的 GPX2_7 引脚上

3. 通过数据手册分析 GPIO 中哪些寄存器可以控制 LED

4. 通过程序去操控对应的寄存器完成对 LED 的控制

示例代码

一.汇编语言访问存储器
1.读存储器
    LDR R1,[R2]
2.写存储器
    STR R1,[R2]

二.C语言访问存储器
1.读存储器
    data = *ADDR
2.写存储器
    *ADDR = data

汇编版

led-asm.s


.text
_start:

MAIN:
    BL LED_CONFIG
LOOP:
    BL LED_ON
    BL DELAY
    BL LED_OFF
    BL DELAY
    B  LOOP

LED_CONFIG:
    LDR R2, =0x11000c40
    LDR R1, =0x10000000
    STR R1, [R2]
    MOV PC, LR

LED_ON:
    LDR R2, =0x11000c44
    LDR R1, =0x00000080
    STR R1, [R2]
    MOV PC, LR

LED_OFF:
    LDR R2, =0x11000c44
    LDR R1, =0x00000000
    STR R1, [R2]
    MOV PC, LR

DELAY:
    LDR R1, =100000000
L:
    SUB R1, R1, #1
    CMP R1, #0
    BNE L
    MOV PC, LR

STOP:
    B STOP

.end

Makefile

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

c语言裸机版

c工程模板

start.S

.text
.global _start
_start:
	/*
	 * Vector table
	 */ 
	b reset
	b .
	b .
	b .
	b .
	b .
	b .
	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

_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

启动代码功能

设置异常向量表,并修改异常向量表的位置

设置成 SVC 特权模式并关闭 FIQ/IRQ 中断,目的是进行一些高权限的设置并且不想被打断

使能运算浮点型数据的协处理器

关闭TLB、MMU等

TLB(Translation Lookaside Buffer): 为了提高地址映射的速度,MMU 中通常包含一个TLB,用于缓存最近使用的页表条目。TLB 的存在减少了对主存储器中实际页表的频繁访问。

初始化栈,使能 FIQ/IRQ,跳转至 main 函数
init_stack
...

给每个模式申请512字节的栈空间
stack_svc:
    .space 512
...

计算栈指针的起始位置,方便后续压栈(从高地址向低地址压栈)
_stack_svc_end:      
    .word stack_svc + 512
...


void myDelay(unsigned int time)
{
	while (time--);
}

typedef struct {
	unsigned int CON;
	unsigned int DAT;
	unsigned int PUD;
	unsigned int DRV;
}gpx2;
#define GPX2 (* (volatile gpx2 *)0x11000C40)

int main()
{
	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);

	while (1) {
		GPX2.DAT = GPX2.DAT | (1 << 7);
		myDelay(1000000);
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		myDelay(1000000);
	}
	return 0;
}

c语言驱动版

leddrv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
 
#include "leddrv.h"
 
#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44
 
int major = 11;
int minor = 0;
int myled_num = 1;
 
struct myled_dev
{
    struct cdev mydev;
 
    volatile unsigned long *pled2_con;
    volatile unsigned long *pled2_dat;

	struct class *pcls;
    struct device *pdev;
};
 
struct myled_dev *pgmydev = NULL;
 
int myled_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data =(void*)(container_of(pnode->i_cdev,struct myled_dev,mydev));
    return 0;
}
 
int myled_close(struct inode *pnode, struct file *pfile)
{
    return 0;
}
 
long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    struct myled_dev *pmydev = (struct myled_dev*)pfile->private_data;

	switch (cmd) {
		case MY_LED_ON:
			writel(readl(pmydev->pled2_dat) | (0x1 << 7), pmydev->pled2_dat);
			break;
		case MY_LED_OFF:
			writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)), pmydev->pled2_dat);
			break;
		default:
			return -1;
	}
 
    return 0;
}
 
struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = myled_open,
    .release = myled_close,
    .unlocked_ioctl = myled_ioctl,
};
 
void ioremap_ledreg(struct myled_dev *pmydev)
{
    pmydev->pled2_con = ioremap(GPX2CON, 4);
    pmydev->pled2_dat = ioremap(GPX2DAT, 4);
}
 
void iounmap_ledreg(struct myled_dev *pmydev)
{
    iounmap(pmydev->pled2_con);
    iounmap(pmydev->pled2_dat);
    pmydev->pled2_con = NULL;
    pmydev->pled2_dat = NULL;
}
 
void set_output_ledconreg(struct myled_dev *pmydev)
{
    writel((readl(pmydev->pled2_con) & (~(0xF << 28))) | (0x1 << 28), pmydev->pled2_con);
	writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)), pmydev->pled2_dat);
}
 
int __init myled_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);
 
	/* 申请设备号 */
	ret = register_chrdev_region(devno, myled_num, "myled");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, myled_num, "myled");
		if (ret) {
			printk("get devno faile\n");
			return -1;
		}
		major = MAJOR(devno);
	}
 
    pgmydev = (struct myled_dev*)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        unregister_chrdev_region(devno, myled_num);
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct myled_dev));
 
	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);
 
	/* 给struct cdev对象添加到内核对应的数据结构里 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, myled_num);

	/* ioremap */
	ioremap_ledreg(pgmydev);
 
	/* con-register set output */
    set_output_ledconreg(pgmydev);

	pgmydev->pcls = class_create(THIS_MODULE, "mymknod");
    if (IS_ERR(pgmydev->pcls)) {
        printk("class_create failed\n");
        cdev_del(&pgmydev->mydev);
	    unregister_chrdev_region(devno, myled_num);
        return -1;
    }

	pgmydev->pdev = device_create(pgmydev->pcls, NULL, devno, NULL, "leddev");
    if (NULL == pgmydev->pdev) {
        printk("device_create failed\n");
        class_destroy(pgmydev->pcls);
        cdev_del(&pgmydev->mydev);
	    unregister_chrdev_region(devno, myled_num);
        return -1;
    }
 
	return 0;
}
 
void __exit myled_exit(void)
{
	dev_t devno = MKDEV(major, minor);
 
	/* iounmap */
    iounmap_ledreg(pgmydev);

	device_destroy(pgmydev->pcls, devno);
    class_destroy(pgmydev->pcls);
	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, myled_num);
 
    kfree(pgmydev);
    pgmydev = NULL;
}
 
 
MODULE_LICENSE("GPL");
 
module_init(myled_init);
module_exit(myled_exit);

leddrv.h

#ifndef LED_DRIVER_H
#define LED_DRIVER_H

#define LED_DEV_MAGIC 'g'

#define MY_LED_OFF _IO(LED_DEV_MAGIC, 0)
#define MY_LED_ON  _IO(LED_DEV_MAGIC, 1)

#endif

testled.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>

#include "leddrv.h"

int main(int argc, char *argv[])
{
    int fd = -1;
    int onoff = 0;
    int no = 0;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 3;
    }

    while (1) {
        ioctl(fd, MY_LED_ON, no);
        sleep(1);
        ioctl(fd, MY_LED_OFF, no);
        sleep(1);
    }

    close(fd);
    fd = -1;
    return 0;
}

UART

UART(Universal Asynchronous Receiver Transmitter)即通用异步收发器,是一种通用的串行、异步通信总线该总线有两条数据线,可以实现全双工的发送和接收在嵌入式系统中常用于主机与辅助设备之间的通信

波特率
波特率用于描述UART通信时的通信速度,其单位为 bps(bit per second)即每秒钟传送的bit的数量

UART控制器
一般情况下处理器中都会集成 UART 控制器我们使用 UART 进行通信时候只需对其内部的相关寄存器进行设置即可

实验步骤

电路原理图

数据手册

GPA1CON


Register Map Summary


ULCON


UCON


UTRSTAT


UTXH / URXH


UBRDIV


UFRACVAL

115200波特率,100MHz:DIV_VAL = (100000000 / (115200 × 16)) - 1 = 53.25

UBRDIV2 = 53

UFRACVAL2 = 0.25 × 16 = 4


示例代码

typedef struct {
	unsigned int CON;
	unsigned int DAT;
	unsigned int PUD;
	unsigned int DRV;
	unsigned int CONPDN;
	unsigned int PUDPDN;
}gpa1;
#define GPA1 (* (volatile gpa1 *)0x11400020)

typedef struct {
	unsigned int ULCON2;
	unsigned int UCON2;
	unsigned int UFCON2;
	unsigned int UMCON2;
	unsigned int UTRSTAT2;
	unsigned int UERSTAT2;
	unsigned int UFSTAT2;
	unsigned int UMSTAT2;
	unsigned int UTXH2;
	unsigned int URXH2;
	unsigned int UBRDIV2;
	unsigned int UFRACVAL2;
	unsigned int UINTP2;
	unsigned int UINTSP2;
	unsigned int UINTM2;
}uart2;
#define UART2 (* (volatile uart2 *)0x13820000)

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;
}

char UART_Recv_Byte(void)
{
	char data = 0;
	/* 判断接收寄存器是否接收到了数据 UTRSTAT2[0] */
	if (UART2.UTRSTAT2 & 0x1 << 0) {
		/* 从接收寄存器中读取接收到的数据 URXH2 */
		data = UART2.URXH2;
		return data;
	} else {
		return -1;
	}
}

void UART_Send_Str(char* pstr)
{
	while (*pstr != '\0') {
		/* 等待发送寄存器为空,即上一个数据已经发送完成 UTRSTAT2[1] */
		while (!(UART2.UTRSTAT2 & 0x1 << 1));
		/* 将要发送的数据写入发送寄存器 UTXH2 */
		UART2.UTXH2 = *pstr++;
	}
}

int main()
{
	UART_Init();

	while (1) {
		/*
		UART2.UTXH2 = 'A';
		while (!(UART2.UTRSTAT2 & 0x1 << 1));
		UART2.UTXH2 = 'B';
		while (!(UART2.UTRSTAT2 & 0x1 << 1));
		UART2.UTXH2 = 'C';
		while (!(UART2.UTRSTAT2 & 0x1 << 1));
		UART2.UTXH2 = 'D';
		while (!(UART2.UTRSTAT2 & 0x1 << 1));
		*/

		// UART_Send_Str("Hello world\r\n");

		char recvData = UART_Recv_Byte();
		if (recvData != -1) {
			recvData += 1;
			UART_Send_Str(&recvData);
		}
	}

	return 0;
}

WDT

WDT(Watch Dog Timer)即看门狗定时器,其主要作用是当发生软件故障时可产生复位信号使SOC复位,其本质是一个计数器

实验步骤

数据手册

WTCON

WTCNT


示例代码

void myDelay(unsigned int time)
{
	while (time--);
}

typedef struct {
	unsigned int WTCON;
	unsigned int WTDAT;
	unsigned int WTCNT;
	unsigned int WTCLRINT;
}wdt;
#define  WDT (* (volatile wdt *)0x10060000)

int main()
{
	/* 设置一级分频 */
	WDT.WTCON = WDT.WTCON | (0xFF << 8);
	/* 设置二级分频 */
	/* WTCNT递减频率 = PLCK(100000000)/(0xFF + 1)/128 = 3052 */
	WDT.WTCON = WDT.WTCON | (0x3 << 3);
	/* 禁止WDT产生中断信号 */
	WDT.WTCON = WDT.WTCON & (~(0x1 << 2));
	/* 使能WDT产生复位信号 */
	WDT.WTCON = WDT.WTCON | (0x1 << 0);
	/* 设置计数器的初始值 */
	WDT.WTCNT = 5000;
	/* 使能WDT,计数器开始递减 */
	WDT.WTCON = WDT.WTCON | (0x1 << 5);

	while (1) {
		printf("WDT.WTCNT = %d\n", WDT.WTCNT);
		/* 喂狗 */
		WDT.WTCNT = 5000;
		myDelay(100000);
	}
	return 0;
}

中断

CPU与硬件的交互方式

轮询
CPU执行程序时不断地询问硬件是否需要其服务,若需要则给予其服务,若不需要一段时间后再次询问,周而复始

中断
CPU执行程序时若硬件需要其服务,对应的硬件给CPU发送中断信号,CPU接收到中断信号后将当前的程序暂停下来,转而去执行中断服务程序,执行完成后再返回到被打断的点继续执行

DMA
硬件产生数据后,硬件控制器可将产生的数据直接写入到存储器中,整个过程无需CPU的参与


中断控制器作用

  • 多个中断同时产生时可对这些中断挂起排队,然后按照优先级依次发送给CPU处理
  • 可以为每一个中断分配一个优先级
  • 一个中断正在处理时若又产生其它中断,可将新的中断挂起,待CPU空闲时再发送
  • 可以为每一个中断选择一个CPU处理
  • 可以为每一个中断选择一个中断类型(FIQ或IRQ)
  • CPU接收到中断信号后并不能区分是哪个外设产生的,此时CPU可查询中断控制器来获取当前的中断信号是由哪个硬件产生的,然后再进行对应的处理
  • 可以打开或禁止每一个中断

异常

概念

处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生,这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件,异常事件处理完成之后再返回到被异常打断的点继续执行程序

异常处理机制

不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制

 ARM异常源

FIQ                               快速中断请求引脚有效        
IRQ                               外部中断请求引脚有效
Reset                            复位电平有效
Software Interrupt         执行swi指令
Data Abort                    数据终止
Prefetch Abort               指令预取终止
Undefined Instruction    遇到不能处理的指令

异常模式

在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切换成对应的异常模式

异常源

FIQ

IRQ

Reset

SWI

Data Abort

Prefetch Abort

Undef Instruction

异常模式

FIQ

IRQ

SVC

Abort

Undef

ARM产生异常后的动作(自动完成)

1.拷贝CPSR中的内容到对应异常模式下的SPSR_<mode>
2.修改CPSR的值
        2.1.修改中断禁止位禁止相应的中断
        2.2.修改模式位进入相应的异常模式
        2.3.修改状态位进入ARM状态
3.保存返回地址到对应异常模式下的LR_<mode>
4.设置PC为相应的异常向量(异常向量表对应的地址)

异常向量表

    > 异常向量表的本质是内存中的一段代码
    > 表中为每个异常源分配了四个字节的存储空间
    > 遇到异常后处理器自动将PC修改为对应的地址
    > 因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳转指令使其跳转到指定的异常处理程序的入口

注:ARM的异常向量表的基地址默认在0x00地址但可以通过配置协处理器来修改其地址

ARM异常返回的动作(自己编写)

1.将SPSR_<mode>的值复制给CPSR使处理器恢复之前的状态
2.将LR_<mode>的值复制给PC使程序跳转回被打断的地址继续执行

注:整个过程CPSR保存的永远是当前程序运行状态SPSR只是异常时对原来的CPSR进行备份

ARM寄存器


在某个特定模式下只能使用当前模式下的寄存器,一个模式下特有的寄存器其他模式下不可使用

LR寄存器

R14(LR,Link Register)
链接寄存器,一般有以下两种用途:
    > 执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址,程序需要返回时将LR的值复制到PC即可实现
    > 产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下一条指令的地址,异常处理结束后将LR的值复制到PC可实现程序返回

原理
当执行跳转指令或产生异常时,LR寄存器中不会凭空产生一个返回地址,其原理是当执行跳转指令或产生异常时,处理器内部会将PC寄存器中的值拷贝到LR寄存器中,然后再将LR寄存器中的值自减4


BL

当执行BL指令时,指令执行过程中处理器内部就会将PC寄存器的值拷贝到LR寄存器,然后再将LR寄存器中的值自减4, 所以LR寄存器中保存的就是BL指令下一条指令的地址

该时刻PC=N+8 LR=N+4


IRQ中断

当执行一条指令时产生了一个IRQ中断,执行这条指令过程中处理器不会保存返回地址,而是执行完成后才会保存,但执行完成后PC的值又会自动增4,所以对于IRQ来说LR中保存的是被中断打断的指令的下下条指令的地址

该时刻PC=N+12 LR=N+8


FIQ比IRQ快的三个原因

  • FIQ优先级高
  • FIQ在异常向量表的最后,可以不经跳转直接写中断处理程序
  • FIQ有自己的私有寄存器

按键中断实验

电路原理图

数据手册

外设层次
GPX1CON


GPX1DAT

EXT_INT41CON


EXT_INT41_MASK


EXT_INT41_PEND

用于中断处理后手动清零挂起位表示中断处理结束,写1清零


中断控制器层次
GIC Interrupt Table

外部中断9使用的中断号是57


ICDDCR

This register enables forwarding of pending interrupts to the CPU interfaces.

GIC总开关


ICDISER_CPU

The ICDISERs provide a Set-enable bit for each interrupt supported by the GIC. Writing "1" to a Set-enable bit enables forwarding of the corresponding interrupt to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled. These registers are available in all configurations of GIC. If GIC implements the Security Extensions, these registers are Common.
The Distributor does not provide registers for INTIDs less than 16 because SGIs are always enabled.

每个中断的开关,需要打开57号中断的开关


ICDIPTR_CPU

选择中断处理的CPU

将 INTID57 写入 0000 0001 意味着将57号中断发送给 CPU0 处理


ICCICR_CPUn

使能中断控制器和CPU的连接


ICCIAR_CPUn

用于区别是哪个中断号触发的中断


ICCEOIR_CPUn

CPU通知中断控制器中断处理完毕


示例代码

轮询

typedef struct {
	unsigned int CON;
	unsigned int DAT;
	unsigned int PUD;
	unsigned int DRV;
}gpx1;
#define GPX1 (* (volatile gpx1 *)0x11000C20)

int main()
{
	/* 将GPX1_1设置成输入功能 */
	GPX1.CON = GPX1.CON & (~(0xF << 4));

	while (1) {
		/* 判断GPX1_1引脚的状态,即判断按键是否按下 */
		if (!(GPX1.DAT & (0x1 << 1))) {
			printf("key2 pressed\n");
			/* 等待松手 */
			while (!(GPX1.DAT & (0x1 << 1)));
		}
	}

	return 0;
}

中断

start.S

.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

key_int.h

typedef struct {
				unsigned int CON;
				unsigned int DAT;
				unsigned int PUD;
				unsigned int DRV;
}gpx1;
#define GPX1 (* (volatile gpx1 *)0x11000C20)

typedef struct {
				unsigned int ICDISER0_CPU0;
				unsigned int ICDISER1;	// (SPI[31:0])
				unsigned int ICDISER2;	// (SPI[63:32])
				unsigned int ICDISER3;
				unsigned int ICDISER4;
}iser;
#define ICDISER (* (volatile iser *)0x10490100)

typedef struct {
				unsigned int ICDIPTR0_CPU0;
				unsigned int ICDIPTR1_CPU0;
				unsigned int ICDIPTR2_CPU0;
				unsigned int ICDIPTR3_CPU0;
				unsigned int ICDIPTR4_CPU0;
				unsigned int ICDIPTR5_CPU0;
				unsigned int ICDIPTR6_CPU0;
				unsigned int ICDIPTR7_CPU0;
				unsigned int ICDIPTR8;
				unsigned int ICDIPTR9;
				unsigned int ICDIPTR10;
				unsigned int ICDIPTR11;
				unsigned int ICDIPTR12;
				unsigned int ICDIPTR13;
				unsigned int ICDIPTR14;
				unsigned int ICDIPTR15;
				unsigned int ICDIPTR16;
				unsigned int ICDIPTR17;
				unsigned int ICDIPTR18;
				unsigned int ICDIPTR19;
				unsigned int ICDIPTR20;
				unsigned int ICDIPTR21;
				unsigned int ICDIPTR22;
				unsigned int ICDIPTR23;
				unsigned int ICDIPTR24;
				unsigned int ICDIPTR25;
				unsigned int ICDIPTR26;
				unsigned int ICDIPTR27;
				unsigned int ICDIPTR28;
				unsigned int ICDIPTR29;
				unsigned int ICDIPTR30;
				unsigned int ICDIPTR31;
				unsigned int ICDIPTR32;
				unsigned int ICDIPTR33;
				unsigned int ICDIPTR34;
				unsigned int ICDIPTR35;
				unsigned int ICDIPTR36;
				unsigned int ICDIPTR37;
				unsigned int ICDIPTR38;
				unsigned int ICDIPTR39;
}iptr;
#define ICDIPTR  (* (volatile iptr *)0x10490800)

typedef struct {
				unsigned int ICCICR;	//CPU interface control register
				unsigned int ICCPMR;
				unsigned int ICCBPR;
				unsigned int ICCIAR;	//Interrupt acknowledge register
				unsigned int ICCEOIR;
				unsigned int ICCRPR;	//Running priority register
				unsigned int ICCHPIR;
				unsigned int ICCABPR;
				unsigned int INTEG_EN;
				unsigned int INTERRUPT_OUT;
}cpu0;
#define CPU0 (* (volatile cpu0 *)0x10480000)

typedef struct {
				unsigned int CON;
				unsigned int DAT;
				unsigned int PUD;
				unsigned int DRV;
}gpx2;
#define GPX2 (* (volatile gpx2 *)0x11000C40)

#define  	__REG(x)	(*(volatile unsigned int *)(x))  
#define		EXT_INT41_CON		__REG(0x11000E04)
#define		EXT_INT41_MASK		__REG(0x11000F04)
#define		EXT_INT41_PEND		__REG(0x11000F44)
#define		ICDDCR		__REG(0X10490000)

key_int.c

#include "key_int.h"

void myDelay(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 57:
			printf("key2 pressed\n");
			/* 清除GPIO控制器中GPX1_1的中断挂起标志位 */
			EXT_INT41_PEND = (0x1 << 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 & (~(0x1 << 1));

	/* 中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理 */
	/* 全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口 */
	ICDDCR = ICDDCR | 0x1;
	/* 在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口 */
	ICDISER.ICDISER1 = ICDISER.ICDISER1 | (0x1 << 25);
	/* 选择由CPU0来处理57号中断 */
	ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x1 << 8);
	/* 使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0 */
	CPU0.ICCICR = CPU0.ICCICR | 0x1;

	// LED2闪烁
	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
	while (1) {
		GPX2.DAT = GPX2.DAT | (1 << 7);
		myDelay(1000000);
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		myDelay(1000000);
	}

	return 0;
}

ADC

ADC(Analog to Digital Converter)即模数转换器,指一个能将模拟信号转化为数字信号的电子元件

分辨率

ADC的分辨率一般以输出二进制数的位数来表示,当最大输入电压一定时,位数越高,分辨率越高; n位的ADC能区分输入电压的最小值为满量程输入的1/2^n; 比如一个12位的ADC,最大输入电压为1.8v,那么该ADC能区分的最小电压为1.8v/2^12≈0.00044v,当转换的结果为m时,则实际的电压值为m*(1.8v/2^12);


电路原理图


数据手册



ADCCON


ADCDAT


ADCMUX


示例代码

c语言裸机版

#define  	__REG(x)	(*(volatile unsigned int *)(x))
#define		ADCCON		__REG(0x126C0000)
#define		ADCDAT		__REG(0x126C000C)
#define		ADCMUX		__REG(0x126C001C)


int main()
{
	unsigned int adcValue;

	/* 设置ADC精度为12bit */
	ADCCON = ADCCON | (0x1 << 16);
	/* 使能ADC分频器 */
	ADCCON = ADCCON | (0x1 << 14);
	/* 设置ADC分频值 ADC时钟频率=PLCK/(19+1)=5MHZ ADC转换频率=5MHZ/5=1MHZ */
	ADCCON = ADCCON & (~(0xFF) << 6) | (19 << 6);
	/* 关闭待机模式,使能正常模式 */
	ADCCON = ADCCON & (~(0x1) << 2);
	/* 关闭通过读使能AD转换 */
	ADCCON = ADCCON & (~(0x1) << 1);
	/* 选择转换通道,3通道 */
	ADCMUX = 0x3;

	while (1) {
		/* 开始转换 */
		ADCCON = ADCCON | (0x1 << 0);
		/* 等待转换完成 */
		while (!(ADCCON & (0x1 << 15)));
		/* 读取转换结果 */
		adcValue = ADCDAT & 0xFFF;
		/* 将结果转换成实际的电压值mv 1800/4095(0b 1111 1111 1111)*/
		adcValue = adcValue * 0.4396;
		/* 打印转换结果 */
		printf("adcValue = %d mv\n", adcValue);
	}
	return 0;
}

c语言驱动版

adcdrv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>

#define ADCCON 0x126C0000
#define ADCDAT 0x126C000C
#define ADCMUX 0x126C001C
 
int major = 12;
int minor = 0;
int myadc_num = 1;
 
struct myadc_dev
{
    struct cdev mydev;
 
    volatile unsigned long *adc_con;
    volatile unsigned long *adc_dat;
    volatile unsigned long *adc_mux;

	struct class *pcls;
    struct device *pdev;
};
 
struct myadc_dev *pgmydev = NULL;
 
int myadc_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data =(void*)(container_of(pnode->i_cdev, struct myadc_dev, mydev));
    return 0;
}
 
int myadc_close(struct inode *pnode, struct file *pfile)
{
    return 0;
}

ssize_t myadc_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct myadc_dev *pmydev = (struct myadc_dev *)pfile->private_data;
	unsigned int adcValue;
	int ret = 0;

    writel(readl(pmydev->adc_con) | (0x1 << 0), pmydev->adc_con);
    while (!(readl(pmydev->adc_con) & (0x1 << 15)));
    adcValue = (readl(pmydev->adc_dat) & 0xFFF);

	ret = copy_to_user(puser, &adcValue, count);
	if (ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	return count;
}

struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = myadc_open,
    .release = myadc_close,
    .read = myadc_read,
};
 
void ioremap_ledreg(struct myadc_dev *pmydev)
{
    pmydev->adc_con = ioremap(ADCCON, 4);
    pmydev->adc_dat = ioremap(ADCDAT, 4);
    pmydev->adc_mux = ioremap(ADCMUX, 4);
}
 
void iounmap_ledreg(struct myadc_dev *pmydev)
{
    iounmap(pmydev->adc_con);
    iounmap(pmydev->adc_dat);
    iounmap(pmydev->adc_mux);
    pmydev->adc_con = NULL;
    pmydev->adc_dat = NULL;
    pmydev->adc_mux = NULL;
}
 
void init_adc(struct myadc_dev *pmydev)
{
    writel(readl(pmydev->adc_con) | (0x1 << 16), pmydev->adc_con);
    writel(readl(pmydev->adc_con) | (0x1 << 14), pmydev->adc_con);
    writel((readl(pmydev->adc_con) & (~(0xFF) << 6)) | (19 << 6), pmydev->adc_con);
    writel(readl(pmydev->adc_con) & (~(0x1) << 2), pmydev->adc_con);
    writel(readl(pmydev->adc_con) & (~(0x1) << 1), pmydev->adc_con);
    writel(0x3, pmydev->adc_mux);
}
 
int __init myadc_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);
 
	/* 申请设备号 */
	ret = register_chrdev_region(devno, myadc_num, "myadc");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, myadc_num, "myadc");
		if (ret) {
			printk("get devno faile\n");
			return -1;
		}
		major = MAJOR(devno);
	}
 
    pgmydev = (struct myadc_dev*)kmalloc(sizeof(struct myadc_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        unregister_chrdev_region(devno, myadc_num);
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct myadc_dev));
 
	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);
 
	/* 给struct cdev对象添加到内核对应的数据结构里 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, myadc_num);
 
	/* ioremap */
	ioremap_ledreg(pgmydev);
 
	/* con-register set output */
    init_adc(pgmydev);
 
	pgmydev->pcls = class_create(THIS_MODULE, "mymknod");
    if (IS_ERR(pgmydev->pcls)) {
        printk("class_create failed\n");
        cdev_del(&pgmydev->mydev);
	    unregister_chrdev_region(devno, myadc_num);
        return -1;
    }
 
	pgmydev->pdev = device_create(pgmydev->pcls, NULL, devno, NULL, "adcdev");
    if (NULL == pgmydev->pdev) {
        printk("device_create failed\n");
        class_destroy(pgmydev->pcls);
        cdev_del(&pgmydev->mydev);
	    unregister_chrdev_region(devno, myadc_num);
        return -1;
    }
 
	return 0;
}
 
void __exit myadc_exit(void)
{
	dev_t devno = MKDEV(major, minor);
 
	/* iounmap */
    iounmap_ledreg(pgmydev);
 
	device_destroy(pgmydev->pcls, devno);
    class_destroy(pgmydev->pcls);
	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, myadc_num);
 
    kfree(pgmydev);
    pgmydev = NULL;
}
 
 
MODULE_LICENSE("GPL");
 
module_init(myadc_init);
module_exit(myadc_exit);

testadc.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    int fd = -1;
    unsigned int buf[1] = {0};
 
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 3;
    }

    while (1) {
        read(fd, buf, 4);
        buf[0] = buf[0] * 0.4396;
        printf("adcValue = %d\n", buf[0]);
        sleep(1);
    }

    close(fd);
    fd = -1;
    return 0;
}

RTC

RTC(Real Time Clock)即实时时钟,它是一个可以为系统提供精确的时间基准的元器件,RTC一般采用精度较高的晶振作为时钟源,有些RTC为了在主电源掉电时还可以工作,需要外加电池供电


数据手册

在BCD编码中,每个十进制数字都用4位二进制数表示。例如,十进制数的每一位可以用4位二进制数表示,形成BCD码。这使得BCD码比纯粹的二进制编码更容易转换为和从人类可读的十进制数字。

举例:

十进制数 12 的二进制表示和BCD码如下:

  1. 12 的二进制表示:1100

  2. 12 的BCD码表示:0001 0010

在BCD码中,十进制数 12 被分成两个部分,每一部分用4位二进制数表示。其中,高4位表示十位数(1),低4位表示个位数(2)。



示例代码

typedef	struct {
	unsigned int BCDSEC;
	unsigned int BCDMIN;
	unsigned int BCDHOUR;
	unsigned int BCDWEEK;
	unsigned int BCDDAY;
	unsigned int BCDMON;
	unsigned int BCDYEAR;
}rtcbcd;
#define 	RTC (* (volatile rtcbcd *)0X10070070)

#define  	__REG(x)	(*(volatile unsigned int *)(x))
#define		RTCCON			__REG(0X10070040)


int main()
{
	unsigned int oldSec = -1, newSec = 0;

	/* 使能RTC控制 */
	RTCCON = RTCCON | 0x1;
	/* 校准时间信息 */
	RTC.BCDYEAR = 0x023;
	RTC.BCDMON  = 0x12;
	/* 数据手册有误 两者颠倒 */
	RTC.BCDDAY  = 0x7; // WEEK
	RTC.BCDWEEK = 0x31; // DAY
	RTC.BCDHOUR = 0x23;
	RTC.BCDMIN  = 0x59;
	RTC.BCDSEC  = 0x50;
	/* 禁止RTC控制 */
	RTCCON = RTCCON & (~(0x1));

	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.BCDDAY,
											   RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
			oldSec = newSec;
		}
	}

	return 0;
}

Tips

经测试,设置日期时需要合法,否则将出现错误的日期

错误的:

正确的:


PWM

PWM(Pulse Width Modulation)即脉冲宽度调制,通过对脉冲的宽度进行调制,来获得所需要波形

蜂鸣器工作原理

有源蜂鸣器
    有源蜂鸣器只要接上额定电源就可以发出声音

无源蜂鸣器
    无源蜂鸣器利用电磁感应原理,为音圈接入交变电流后形成的电磁铁与永磁铁相吸或相斥而推动振膜发声


电路原理图


数据手册


示例代码

c语言裸机版

typedef struct {
    unsigned int CON;
    unsigned int DAT;
    unsigned int PUD;
    unsigned int DRV;
    unsigned int CONPDN;
    unsigned int PUDPDN;
}gpd0;
#define GPD0 (* (volatile gpd0 *)0x114000A0)

typedef struct {
    unsigned int	TCFG0;
    unsigned int	TCFG1;
    unsigned int	TCON;
    unsigned int	TCNTB0;
    unsigned int	TCMPB0;
    unsigned int	TCNTO0;
    unsigned int	TCNTB1;
    unsigned int	TCMPB1;
    unsigned int	TCNTO1;
    unsigned int	TCNTB2;
    unsigned int	TCMPB2;
    unsigned int	TCNTO2;
    unsigned int	TCNTB3;
    unsigned int	TCMPB3;
    unsigned int	TCNTO3;
    unsigned int	TCNTB4;
    unsigned int	TCNTO4;
    unsigned int	TINT_CSTAT;
}pwm;
#define PWM (* (volatile pwm *)0x139D0000)


void myDelay(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;
    /* 3.设置PWM0的二级分频	二级分频倍数设置为1倍 递减计数器递减频率 = PLCK / (99 + 1) / 1 = 1M */
    PWM.TCFG1 = PWM.TCFG1 & (~(0xF));
    /* 4.设置PWM0为自动重装载,使其能够产生连续的脉冲信号 */
    PWM.TCON = PWM.TCON | (0x1 << 3);
    /* 5.设置PWM0的频率为500HZ */
    PWM.TCNTB0 = 2000;
    /* 6.设置PWM0的占空比为50% */
    PWM.TCMPB0 = 1000;
    /* 7.将TCNTB0中的值手动更新到递减计数器 */
    PWM.TCON = PWM.TCON | (0x1 << 1);
    /* 8.关闭手动更新 */
    PWM.TCON = PWM.TCON & (~(0x1) << 1);
    /* 9.使能PWM0,递减计数器开始递减 */
    PWM.TCON = PWM.TCON | (0x1 << 0);

    while (1) {
        PWM.TCON = PWM.TCON | (0x1 << 0);
        myDelay(1000000);
        PWM.TCON = PWM.TCON & (~(0x1) << 0);
        myDelay(1000000);
    }
    return 0;
}

c语言驱动版

pwmdrv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>

#include "pwmdrv.h"
 
typedef struct {
    unsigned int CON;
    unsigned int DAT;
    unsigned int PUD;
    unsigned int DRV;
    unsigned int CONPDN;
    unsigned int PUDPDN;
}gpd0;
#define GPD0 (* (volatile gpd0 *)0x114000A0)

typedef struct {
    unsigned int	TCFG0;
    unsigned int	TCFG1;
    unsigned int	TCON;
    unsigned int	TCNTB0;
    unsigned int	TCMPB0;
    unsigned int	TCNTO0;
    unsigned int	TCNTB1;
    unsigned int	TCMPB1;
    unsigned int	TCNTO1;
    unsigned int	TCNTB2;
    unsigned int	TCMPB2;
    unsigned int	TCNTO2;
    unsigned int	TCNTB3;
    unsigned int	TCMPB3;
    unsigned int	TCNTO3;
    unsigned int	TCNTB4;
    unsigned int	TCNTO4;
    unsigned int	TINT_CSTAT;
}pwm;
#define PWM (* (volatile pwm *)0x139D0000)
 
int major = 13;
int minor = 0;
int mypwm_num = 1;
 
struct mypwm_dev
{
    struct cdev mydev;
 
    volatile unsigned long *gpd0_con;
    volatile unsigned long *pwm_tcfg0;
    volatile unsigned long *pwm_tcfg1;
    volatile unsigned long *pwm_tcon;
    volatile unsigned long *pwm_tcntb0;
    volatile unsigned long *pwm_tcmpb0;

	struct class *pcls;
    struct device *pdev;
};
 
struct mypwm_dev *pgmydev = NULL;
 
int mypwm_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data =(void*)(container_of(pnode->i_cdev, struct mypwm_dev, mydev));
    return 0;
}
 
int mypwm_close(struct inode *pnode, struct file *pfile)
{
    return 0;
}
 
long mypwm_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    struct mypwm_dev *pmydev = (struct mypwm_dev*)pfile->private_data;
 
    switch (cmd) {
        case MY_PWM_ON:
            writel(readl(pmydev->pwm_tcon) | (0x1 << 0), pmydev->pwm_tcon);
            break;
        case MY_PWM_OFF:
            writel(readl(pmydev->pwm_tcon) & (~(0x1) << 0), pmydev->pwm_tcon);
            break;
        default:
            return -1;
    }
 
    return 0;
}
 
struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = mypwm_open,
    .release = mypwm_close,
    .unlocked_ioctl = mypwm_ioctl,
};
 
void ioremap_ledreg(struct mypwm_dev *pmydev)
{
    pmydev->gpd0_con = ioremap((unsigned long)&GPD0.CON, 4);
    pmydev->pwm_tcfg0 = ioremap((unsigned long)&PWM.TCFG0, 4);
    pmydev->pwm_tcfg1 = ioremap((unsigned long)&PWM.TCFG1, 4);
	pmydev->pwm_tcon = ioremap((unsigned long)&PWM.TCON, 4);
	pmydev->pwm_tcntb0 = ioremap((unsigned long)&PWM.TCNTB0, 4);
	pmydev->pwm_tcmpb0 = ioremap((unsigned long)&PWM.TCMPB0, 4);
}
 
void iounmap_ledreg(struct mypwm_dev *pmydev)
{
    iounmap(pmydev->gpd0_con);
    iounmap(pmydev->pwm_tcfg0);
    iounmap(pmydev->pwm_tcfg1);
    iounmap(pmydev->pwm_tcon);
    iounmap(pmydev->pwm_tcntb0);
    iounmap(pmydev->pwm_tcmpb0);
    pmydev->gpd0_con = NULL;
    pmydev->pwm_tcfg0 = NULL;
    pmydev->pwm_tcfg1 = NULL;
    pmydev->pwm_tcon = NULL;
    pmydev->pwm_tcntb0 = NULL;
    pmydev->pwm_tcmpb0 = NULL;
}
 
void init_pwm(struct mypwm_dev *pmydev)
{
    writel((readl(pmydev->gpd0_con) & (~(0xF))) | (0x2), pmydev->gpd0_con);
	writel((readl(pmydev->pwm_tcfg0) & (~(0xFF))) | 99, pmydev->pwm_tcfg0);
    writel(readl(pmydev->pwm_tcfg1) & (~(0xF)), pmydev->pwm_tcfg1);
    writel(readl(pmydev->pwm_tcon) | (0x1 << 3), pmydev->pwm_tcon);
    writel(2000, pmydev->pwm_tcntb0);
    writel(1000, pmydev->pwm_tcmpb0);
    writel(readl(pmydev->pwm_tcon) | (0x1 << 1), pmydev->pwm_tcon);
    writel(readl(pmydev->pwm_tcon) & (~(0x1) << 1), pmydev->pwm_tcon);
    writel(readl(pmydev->pwm_tcon) & (~(0x1) << 0), pmydev->pwm_tcon);
}
 
int __init mypwm_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);
 
	/* 申请设备号 */
	ret = register_chrdev_region(devno, mypwm_num, "mypwm");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mypwm_num, "mypwm");
		if (ret) {
			printk("get devno faile\n");
			return -1;
		}
		major = MAJOR(devno);
	}
 
    pgmydev = (struct mypwm_dev*)kmalloc(sizeof(struct mypwm_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        unregister_chrdev_region(devno, mypwm_num);
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct mypwm_dev));
 
	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);
 
	/* 给struct cdev对象添加到内核对应的数据结构里 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, mypwm_num);
 
	/* ioremap */
	ioremap_ledreg(pgmydev);
 
    init_pwm(pgmydev);
 
	pgmydev->pcls = class_create(THIS_MODULE, "pwm_class");
    if (IS_ERR(pgmydev->pcls)) {
        printk("class_create failed\n");
        cdev_del(&pgmydev->mydev);
	    unregister_chrdev_region(devno, mypwm_num);
        return -1;
    }
 
	pgmydev->pdev = device_create(pgmydev->pcls, NULL, devno, NULL, "pwmdev");
    if (NULL == pgmydev->pdev) {
        printk("device_create failed\n");
        class_destroy(pgmydev->pcls);
        cdev_del(&pgmydev->mydev);
	    unregister_chrdev_region(devno, mypwm_num);
        return -1;
    }
 
	return 0;
}
 
void __exit mypwm_exit(void)
{
	dev_t devno = MKDEV(major, minor);
 
	/* iounmap */
    iounmap_ledreg(pgmydev);
 
	device_destroy(pgmydev->pcls, devno);
    class_destroy(pgmydev->pcls);
	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, mypwm_num);
 
    kfree(pgmydev);
    pgmydev = NULL;
}
 
 
MODULE_LICENSE("GPL");
 
module_init(mypwm_init);
module_exit(mypwm_exit);

testpwm.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
 
#include "pwmdrv.h"
 
int main(int argc, char *argv[])
{
    int fd = -1;
    int onoff = 0;

    sscanf(argv[2], "%d", &onoff);
 
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 3;
    }
 
    if (onoff) {
        ioctl(fd, MY_PWM_ON, 1);
    } else {
        ioctl(fd, MY_PWM_OFF, 1);
    }
 
    close(fd);
    fd = -1;
    return 0;
}

IIC

IIC总线是Philips公司在八十年代初推出的一种串行、半双工总线主要用于近距离、低速的芯片之间的通信;IIC总线有两根双向的信号线一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步;IIC总线硬件结构简单,成本较低,因此在各个领域得到了广泛的应用


IIC总线是一种多主机总线,连接在IIC总线上的器件分为主机和从机主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误产生;
每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;IIC总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器;


IIC总线通信过程

1.主机发送起始信号启用总线
2.主机发送一个字节数据指明从机地址(7bit)和后续字节的传送方向(1bit)(0:主→从 1:从→主)
3.被寻址的从机发送应答信号回应主机
4.发送器发送一个字节数据
5.接收器发送应答信号回应发送器
 … … (循环步骤4、5)
n.通信完成后主机发送停止信号释放总线


IIC总线寻址方式

IIC总线上传送的数据是广义的,既包括地址,又包括真正的数据
主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,'0'表示主机发送数据,'1'表示主机接收数据;总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器


起始信号和停止信号

SCL为高电平时,SDA由高变低表示起始信号
SCL为高电平时,SDA由低变高表示停止信号
起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态
停止信号产生后总线处于空闲状态


字节传送与应答

IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器即一帧共有9位


同步信号

IIC总线在进行数据传送时,时钟线SCL为低电平期间发送器向数据线上发送一位数据,在此期间数据线上的信号允许发生变化,时钟线SCL为高电平期间接收器从数据线上读取一位数据,在此期间数据线上的信号不允许发生变化,必须保持稳定


典型IIC时序

主机向从机发送数据

从机向主机发送数据

主机先向从机发送数据,然后从机再向主机发送数据

注:阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送;A表示应答, A非表示非应答,S表示起始信号,P表示终止信号


MPU6050

MPU6050是一个运动处理传感器,其内部集成了3轴加速度传感器和3轴陀螺仪(角速度传感器),以及一个可扩展数字运动处理器


MPU6050主要参数

可测量X、Y、Z轴三个方向的角速度
可编程设置角速度测量范围为±250、±500、±1000、±2000°/sec
可测量X、Y、Z轴三个方向的加速度
可编程设置加速度测量范围为±2g、±4g、±8g、±16g
可编程设置低功耗模式
可编程设置采样频率


MPU6050通信接口

MPU6050可以使用IIC总线和其他器件进行数据交互,我们可以使用IIC总线向MPU6050中的控制寄存器写入数据来设置MPU6050的工作参数
也可以使用IIC总线从MPU6050中的数据寄存器读取数据来获取加速度、角速度等信息


向MPU6050的一个寄存器写一个字节的数据

1.主机(Exynos4412)发送起始信号
2.主机发送从机地址(MPU6050的地址)及读写方向(写)
3.从机(MPU6050)发送应答信号
4.主机发送一个字节数据(要写的寄存器的地址)
5.从机发送应答信号
6.主机发送一个字节数据(要写到寄存器的数据)
7.从机发送应答信号
8.主机发送停止信号


从MPU6050的一个寄存器读一个字节的数据

1.主机(Exynos4412)发送起始信号
2.主机发送从机地址(MPU6050的地址)及读写方向(写)
3.从机(MPU6050)发送应答信号
4.主机发送一个字节数据(要写的寄存器的地址)
5.从机发送应答信号
6.主机(Exynos4412)发送起始信号
7.主机发送从机地址(MPU6050的地址)及读写方向(读)
8.从机(MPU6050)发送应答信号
9.从机发送一个字节数据(要读的寄存器中的数据)
10.主机发送非应答信号(不再接收更多的数据)
11.主机发送停止信号


电路原理图


数据手册

IIC


MPU6050


示例代码

typedef struct {
	unsigned int I2CCON;
	unsigned int I2CSTAT;
	unsigned int I2CADD;
	unsigned int I2CDS;
	unsigned int I2CLC;
}i2c5;
#define  I2C5 (* (volatile i2c5 *)0x138B0000)

typedef struct {
	unsigned int CON;
	unsigned int DAT;
	unsigned int PUD;
	unsigned int DRV;
	unsigned int CONPDN;
	unsigned int PUDPDN;
}gpb;
#define GPB (* (volatile gpb *)0x11400040)

/****************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(unsigned int time)
{
	while (time--);
}

void iic_write(unsigned char slave_addr, unsigned char addr, unsigned char data)
{
	// 发送7bit地址和1bit传送方向(0:主→从)
	I2C5.I2CCON = I2C5.I2CCON | (1 << 6) | (1 << 5); // 512倍预分频 打开IIC中断
	I2C5.I2CSTAT = 0xD0; // D=1101 (11:Master transmit mode)(1:Enables Rx/Tx)
	I2C5.I2CDS = slave_addr << 1; // 从机地址和读写位0写入发送寄存器
	I2C5.I2CSTAT = 0xF0; // 发送起始信号启用总线 ((1 << 5): START signal generation)
	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; // 发送停止信号 结束本次通信 ((~(1 << 5)): START signal generation)
	I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
	myDelay(1000);
}

unsigned char iic_read(unsigned char slave_addr, unsigned char addr)
{
	unsigned char data = 0;

	// 发送7bit地址和1bit传送方向(0:主→从)
	I2C5.I2CCON = I2C5.I2CCON | (1 << 6) | (1 << 5); // 512倍预分频 打开IIC中断
	I2C5.I2CSTAT = 0xD0; // D=1101 (11:Master transmit mode)(1:Enables Rx/Tx)
	I2C5.I2CDS = slave_addr << 1;
	I2C5.I2CSTAT = I2C5.I2CSTAT | (1 << 5); // START signal generation
	while (!(I2C5.I2CCON & (1 << 4)));

	// 发送MPU6050内部寄存器的地址
	I2C5.I2CDS = addr;
	I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
	while (!(I2C5.I2CCON & (1 << 4)));

	// 清除中断挂起标志位 重新开始一次通信 改变数据传送方向(1:从→主)
	I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
	I2C5.I2CDS = slave_addr << 1 | 1; // 从机地址和读写位1写入发送寄存器
	I2C5.I2CSTAT = 0xB0; // B=1011 (10:Master receive mode)(1:START signal generation)(1:Enables Rx/Tx)
	while (!(I2C5.I2CCON & (1 << 4)));

	I2C5.I2CCON = I2C5.I2CCON & (~(1 << 7)); // 开启非应答
	I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
	while (!(I2C5.I2CCON & (1 << 4)));
	data = I2C5.I2CDS; // 读取从机发来的数据

	I2C5.I2CSTAT = 0x90; // 发送停止信号 结束本次通信 ((~(1 << 5)): START signal generation)
	I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
	myDelay(1000);

	return data;
}

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()
{
	unsigned char Zvalue_h, Zvalue_l;
	short Zvalue;

	// 设置GPB_2引脚和GPB_3引脚功能为I2C传输引脚
	GPB.CON = (GPB.CON & ~(0xF << 12)) | 0x3 << 12;
	GPB.CON = (GPB.CON & ~(0xF << 8))  | 0x3 << 8;

	uart_init(); // 初始化串口
	MPU6050_Init(); // 初始化MPU6050

	while (1) {
		Zvalue_h = iic_read(SlaveAddress, GYRO_ZOUT_H);
		Zvalue_l = iic_read(SlaveAddress, GYRO_ZOUT_L);
		Zvalue = (Zvalue_h << 8) | Zvalue_l;

		printf("GYRO_Z = %d\n", Zvalue);
		myDelay(1000000);
	}

	return 0;
}

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值