概述:

Cortex-m0的integration_kit提供三个GPIO接口,其中GPIO0传输到外部供用户使用,为EXTGPIO;GPIO1是内核自己的信号,不能乱改,会崩掉;GPIO2是一些中断,这里没开中断,可以读写相应的寄存器。

1. 寄存器寻址,先看手册:

这里倒没什么特别,第10位为1正好对应地址0x400,为DIR寄存器,同理0x410对应中断寄存器。

对应硬件描述语言:

 // 本GPIO设备被选通、总线写、总线传输类型为连续或不连续时有效
wire write_trans = HSEL & HWRITE & HTRANS[1]; // 写信号有效时根据地址选择要写入的寄存器
wire nxt_gpiodata_o_wren = write_trans & (HADDR[10 ] == 1'b0);
wire nxt_gpiodir_wren = write_trans & (HADDR[10: 4] == 7'b1000000);
wire nxt_gpiointmask_wren = write_trans & (HADDR[10: 4] == 7'b1000001);
//-----------------------------------------------------------------------------
// AHB register read mux
//----------------------------------------------------------------------------- // Drive read mux next state from word address when selected
wire [10:4] nxt_read_mux = HSEL ? HADDR[10:4] : read_mux; always @(posedge HCLK or negedge HRESETn)
if(~HRESETn)
read_mux <= 7'b0; // Set select to input on reset
else if(HREADY) // When bus is ready
read_mux <= nxt_read_mux; // assign mux select next value wire [31:0] rdata = (read_mux[10: 4] == 7'b1000000)? gpiodir : ( //Offset 0x400 returns Direction Register
(read_mux[10: 4] == 7'b1000001)? gpiointmask : ( //Offset 0x410 returns Interrupt Mask Register
(read_mux[10 ] == 1'b0)? gpiodata_i : 32'b0)); //Offset 0x0 to 0x3ff returns Data Register

2. GPIODATA读写

这个特别麻烦,手册里说的特别简洁,对应verilog一大堆。

首先是这个Note当初没有仔细看,后来做实验出问题也没想到,直到写这篇总结才发现,人家还给配了图,特意用来表明:gpio模块用了两个寄存器,两个寄存器是独立的。

看图说话:

然后总线的读数据HRDATA永远只读取mask过以后的gpio_in寄存器,总线的HWDATA永远能向gpio_out寄存器写入数据,自画丑图如下:

verilog:

//-----------------------------------------------------------------------------
// IO Pad registers
//----------------------------------------------------------------------------- assign GPIOEN = gpiodir;
assign GPIOOUT = gpiodata_o; wire [:] nxt_gpiodata_i = GPIOIN;
// Combine bus and register values using mask for next state of Data Out Register
wire [:] nxt_gpiodata_o = gpiodata_o_wren ?
((HWDATA & type_mask2) | (gpiodata_o & ~type_mask2))
: gpiodata_o; always @(posedge HCLK or negedge HRESETn)
if(~HRESETn)
begin
gpiodir <= {{'b0}}; // Disable all buffers on reset
gpiointmask <= {{'b0}};
gpiodata_o <= {{'b0}};
end
else if (HREADY) // When bus is ready:
begin
gpiodir <= nxt_gpiodir; // update direction register
gpiointmask <= nxt_gpiointmask; // update interrupt mask register
gpiodata_o <= nxt_gpiodata_o; // updata data out register
end always @ (posedge FCLK or negedge HRESETn)
if(~HRESETn)
gpiodata_i <= {{'b0}}; // Reset all outputs to zero
else
gpiodata_i <= nxt_gpiodata_i; // Sample GPIOIN continuously
assign HRDATA = rdata & type_mask2;

并且gpiodata_i这个寄存器的赋值是在FCLK时钟域(HCLK是FCLK的门控时钟,二者同频同相),这么设计为什么暂时不太懂,是害怕漏掉信号的变化?

可以看到,这里对总线读数据HRDATA和gpiodata_o寄存器的写入用的是同一组mask信号:type_mask2,这个mask信号得来不易,先看代码:

//-----------------------------------------------------------------------------
// AHB register read/write mask for byte accesses on Data register
//----------------------------------------------------------------------------- wire nxt_hsize_zero = (HSIZE[1:0] == 2'b0) & HSEL; always @ (hsize_zero or haddr_r or mask8 or type_mask)
case({hsize_zero,haddr_r})
3'b100 : type_mask2 = {8'h00, 8'h00, 8'h00, mask8};
3'b101 : type_mask2 = {8'h00, 8'h00, mask8, 8'h00};
3'b110 : type_mask2 = {8'h00, mask8, 8'h00, 8'h00};
3'b111 : type_mask2 = {mask8, 8'h00, 8'h00, 8'h00};
3'b000,3'b001,3'b010,3'b011: type_mask2 = type_mask;
default : type_mask2 = {32{1'bx}};
endcase

type_mask2的真身,可以看到,当hsize_zero为0时type_mask2直接等于type_mask,hsize_zero顾名思义,就是HSIZE为0时有效,这里有点绕,总的来说就是,当HSIZE[1:0]不为0时(字或半字访问),type_mask2=type_mask,再看type_mask:

//-----------------------------------------------------------------------------
// AHB write byte address control
//----------------------------------------------------------------------------- // Decode term for access to least significant byte
wire nxt_byte0 = ( HSIZE[] ) |
( HSIZE[] & ~HADDR[] ) |
( ~HSIZE[] & ~HADDR[] & ~HADDR[] ); // Decode term for access to byte 1
wire nxt_byte1 = ( HSIZE[] ) |
( HSIZE[] & ~HADDR[] ) |
( ~HSIZE[] & ~HADDR[] & HADDR[] ); // Decode term for access to byte 2
wire nxt_byte2 = ( HSIZE[] ) |
( HSIZE[] & HADDR[] ) |
( ~HSIZE[] & HADDR[] & ~HADDR[] ); // Decode term for access to most significant byte
wire nxt_byte3 = ( HSIZE[] ) |               //整个字-32bit访问
( HSIZE[] & HADDR[] ) |        //半字访问--16bit访问的情况
( ~HSIZE[] & HADDR[] & HADDR[] ); //字节访问--8bit always @(posedge HCLK or negedge HRESETn)
// when bus is ready,byte[3:0] <= nxt_byte[3:0];(这里删去了,为了方便看) wire [:] type_mask = { {{byte3}}, {{byte2}}, {{byte1}}, {{byte0}} };

就是字节使能,HSIZE是总线传输规模,根据AMBA3 AHB Lite手册:

HSIZE[1]=1时是32bit传输,此时type_mask=32'hFFFF_FFFF

HSIZE[1]=0时,看HSIZE[0]

当HSIZE[0]=1时半字访问,再参考总线地址HADDR[1]的状态,看要访问上半字还是下半字,此时:

如果HADDR[1]=0,type_mask=32'h0000_FFFF

如果HADDR[1]=1,type_mask=32'hFFFF_0000

--而HADDR[0]的值是多少,是不care的。

当HSIZE[0]=0时字节访问,type_mask根据HADDR[1:0]的值分别令对应的字节为1:

如果HADDR[1:0]=00,type_mask=32'h0000_00FF

如果HADDR[1:0]=01,type_mask=32'h0000_FF00

如果HADDR[1:0]=10,type_mask=32'h00FF_0000

如果HADDR[1:0]=11,type_mask=32'hFF00_0000

以GPIO0的访问为例,如下:

总的来说就是:总线字(32bit)或半字(16bit)访问GPIO时,不进行位mask,只有字节(8bit)访问时mask8才有用武之地,下面是mask8:

// For byte accesses, the mask is in HADDR[9:2]
wire [:] nxt_mask8 = (nxt_hsize_zero)? HADDR[:] : {{'b0}}; always @(posedge HCLK or negedge HRESETn)
if(~HRESETn)
mask8 <= {{'b0}}; // Reset mask to FF
else if(HREADY)
mask8 <= nxt_mask8; // Update AHB mask with next

其实非常简单粗暴,直接等于HADDR[9:0],并且手册里也给出了说明:

但是好死不死,给的例子也太不恰当了,写0x40000009访问第9bit,那是不是写0x40000008访问第8bit,那不是一直访问到0x40000020就行了,GPIODATA范围可是0x00~0x3ff,留这么多空间是要做啥?

并且,提供的软件程序(cm0pikmcu.h)都是这样的定义:

 1 /*--------------------- General Purpose Input and Ouptut ---------------------*/
2 typedef union
3 {
4 __IO uint32_t WORD;
5 __IO uint16_t HALFWORD[2];
6 __IO uint8_t BYTE[4];
7 } GPIO_Data_TypeDef;
8
9 typedef struct
10 {
11 GPIO_Data_TypeDef DATA [256];
12 GPIO_Data_TypeDef DIR;
13 uint32_t RESERVED[3];
14 GPIO_Data_TypeDef IE;
15 } GPIO_TypeDef;

访问方式:

GPIO0->DATA[].WORD = 0x55;

没看verilog之前怎么也理解不上去这个定义,想着GPIO,不就一个寄存器吗,自己写呗,于是有了下面的代码:

*(u32 *)(0x40000400) = 0xffffffff;    //配置GPIO0->DIR寄存器为输出
*(u8 *)(0x40000000) = 0x55; //-现象:写不进去
*(u32 *)(0x40000000) = 0x12345678; //-现象:可以写,但总有几位写不进去。

事实上,GPIODATA的正确写入方式是:

不进行位屏蔽,按字节访问GPIO寄存器的正确方式:

C代码示例:

unsigned char c0, c1, c2, c3;
*(unsigned int *)(0x40001400) = 0xffffffff; //GPIO2设为输出
*(unsigned int *)(0x40001000) = 0xa5a5a5a5; //32bit写时无mask
*(unsigned short *)(0x40001000) = 0xff3c; //16bit写时无mask
*(unsigned short *)(0x40001002) = 0xc300; //16bit写时无mask
*(unsigned int *)(0x40001000) = 0x12345678; *(unsigned char *)(0x400013FC) = 0x55; //字节写,令mask=8,8bit全部写入
*(unsigned char *)(0x400013FD) = 0x55;
*(unsigned char *)(0x400013FE) = 0x55;
*(unsigned char *)(0x400013FF) = 0x55; c0 = *(unsigned char *)(0x400013FC);
c1 = *(unsigned char *)(0x400013FD);
c2 = *(unsigned char *)(0x400013FE);
c3 = *(unsigned char *)(0x400013FF); *(unsigned char *)(0x400013FD) = 0xaa;
c0 = *(unsigned char *)(0x400013FC);
c1 = *(unsigned char *)(0x400013FD);
c2 = *(unsigned char *)(0x400013FE);
c3 = *(unsigned char *)(0x400013FF);

无论是memory查看还是读取到变量都没问题。

并且,由于GPIO模块内部的输入、输出寄存器没有互联,如果硬件上不在GPIO模块外把输出给到输入,你是永远也无法在KEIL里看到刚刚写进去的数据的。integration_kit里对例化的三个GPIO配置如下:

通过COMPIKMCU.v里32个buffer实现的:

  bufif1 (EXTGPIO[], mcu_extgpioout[], mcu_extgpioen[]);

  assign mcu_extgpioin = EXTGPIO;

GPIO1和GPIO2没有引到外部,在MCU.v的上一层,cm0p_ik_sys.v里一个赋值语句实现:

  assign sys_gpio2in           = sys_gpio2out;

其实GPIO1还比较复杂,并不全是输出给输入,反正是core的信号,没事先别乱动,比如像这样:

  assign sys_gpio1in[:]    = sys_gpio1out[:];

  assign sys_gpio1in[]       = sys_halted;

  assign sys_gpio1in[]       = sys_lockup;

  assign sys_gpio1in[]       = SLEEPDEEP;

3. GPIO模块对总线的响应:

//-----------------------------------------------------------------------------
// AHB tie offs
//----------------------------------------------------------------------------- assign HREADYOUT = 'b1; // All accesses to GPIO are zero-wait
assign HRESP = 'b0; // Generate OK responses only

也就是说GPIO模块永远响应OK,且完全按AHB-Lite定义的读写时序,第一个HCLK写地址,第二个HCLK读或写数据

arm cortex-m0plus源码学习(三)GPIO的更多相关文章

  1. mybatis源码学习(三)-一级缓存二级缓存

    本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点 1.一级缓存配置信息 2.一级缓存源码学习笔记 3.二级缓存配置信息 4.二级缓存源码 5.一级缓存.二级缓存总结 1.一级缓存配 ...

  2. Vue源码学习三 ———— Vue构造函数包装

    Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...

  3. spring源码学习(三)--spring循环引用源码学习

    在spring中,是支持单实例bean的循环引用(循环依赖)的,循环依赖,简单而言,就是A类中注入了B类,B类中注入了A类,首先贴出我的代码示例 @Component public class Add ...

  4. [spring源码学习]三、IOC源码——自定义配置文件读取

    一.环境准备 在文件读取的时候,第9步我们发现spring会根据标签的namespace来选择读取方式,联想spring里提供的各种标签,比如<aop:xxx>等应该会有不同的读取和解析方 ...

  5. vue 源码学习三 vue中如何生成虚拟DOM

    vm._render 生成虚拟dom 我们知道在挂载过程中, $mount 会调用 vm._update和vm._render 方法,vm._updata是负责把VNode渲染成真正的DOM,vm._ ...

  6. java集合类源码学习三——ArrayList

    ArrayList无疑是java集合类中的一个巨头,而且或许是使用最多的集合类.ArrayList继承自AbstractList抽象类,实现了List<E>, RandomAccess, ...

  7. Vue源码学习二 ———— Vue原型对象包装

    Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  9. Java并发包源码学习之AQS框架(三)LockSupport和interrupt

    接着上一篇文章今天我们来介绍下LockSupport和Java中线程的中断(interrupt). 其实除了LockSupport,Java之初就有Object对象的wait和notify方法可以实现 ...

  10. Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习

    package cc.aa; import android.os.Environment; import android.view.MotionEvent; import android.view.V ...

随机推荐

  1. 转: js实现全角半角检测的方法

    //全角半角校验 function issbccase(strTmp) { for (var i=0; i<strTmp.length; i++) { if (strTmp.charCodeAt ...

  2. VS2013 opencv配置

    有三个地方需要配置,在配置之前首先要将platform配置好,下面的例子是x64 Release的“ 然后需要将include.lib的路径配置好 然后将dll拷贝至编译生成的Release文件夹中即 ...

  3. 什么是Docker?(一)

    Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,它是基于 dotCloud 公司多年云服务技术的一次革新,并于 2013 年 3 月以 ...

  4. logback 常用配置详解(序)logback 简介

    转自:http://aub.iteye.com/blog/1101260 logback 简介 Ceki Gülcü在Java日志领域世界知名.他创造了Log4J ,这个最早的Java日志框架即便在J ...

  5. private static final Logger logger= LoggerFactory.getLogger(WhMainBusi.class);

    LoggerFactory.getLogger(WhMainBusi.class):指定类初始化日志对象,在日志输出的时候,将会打印日志信息所在的类.如: logger.info("日志信息 ...

  6. state访问状态对象

    状态对象赋值给内部对象,也就是把stroe.js中的值,赋值给我们模板里data中的值.我们有三种赋值方式: 1.通过computed的计算属性直接赋值 Count.vue {count} <s ...

  7. Oracle(2)之多表查询&子查询&集合运算

    多表查询 笛卡尔积 同时查询多张表时,每张表的每条数据都要和其它表的每条数据做组合.如下栗子,我们发现产生的总记录数是 56 条,还发现 emp 表是 14 条,dept 表是 4 条,56 条正是 ...

  8. [py]py异常应用

    异常执行路径 代码参考 try: text = input('请输入 --> ') except EOFError: print('为什么你按下了EOF?') except KeyboardIn ...

  9. node 图片验证码生成

    var captchapng = require('captchapng'); var http = require("http") var server = http.creat ...

  10. centos make error: fatal error: curses.h: No such file or directory

    yum install ncurses.x86_64 yum install ncurses-devel.x86_64 yum install ncurses-libs.x86_64 yum inst ...