关于keil编译cortex-m3纯汇编时为什么问题使用align地址问题

在编译下面一段代码时:

STACK_TOP EQU 0x20002000
AREA Reset,CODE,READONLY
DCD 0x20002000
DCD Start
ENTRY
; CODE16
Start
ldr r2,=Test
LDRD r0,r1,[r2,#4]
LDRD r0,r1,[r2]
LDRD r0,r1,[r2]
; movs r0,r0
; NOP
; align 4
Test
DCD 0x12345678
END

我发现,如果加上NOP或align4,程序就不会跑飞,否则程序就跑飞了。

经调试发现:    如果不加NOP或align 4的话产生的Test的标号地址就会产生错误,而LDRD 指令操作的地址必须是4字节对节的,如果使用的地址不是四字节对齐,那么程序就会产生异常,所以程序就跑飞了。  那么为什么不加NOP或align 4的话Test标号地址就会产生错误呢?  来看一段手册上的话:

4

 

也就是说DCD是需要标号地址按字对齐的,如果你没有对齐就可以看到如下的编译警告:test.asm(18): warning: A1581W: Added 2 bytes of padding at address 0x1a
这说明编译器会自动添加两个字节来帮你对齐,数据分布情况和下面很相似:

STACK_TOP EQU 0x20002000
AREA Reset,CODE,READONLY
DCD 0x20002000
DCD Start
ENTRY
; CODE16

Start
ldr r2,=Test
LDRD r0,r1,[r2,#4]
LDRD r0,r1,[r2]
LDRD r0,r1,[r2]
; movs r0,r0
; NOP
; align 4
Test
dcb 00 ;编译器自动添加
dcb 00 ;编译器自动添加,而movs r0,r0的机器码就是0x0000,会被
;编译器翻译成movs r0,r0,不是当作数据0x0000
DCDU 0x12345678
END

也许看来这样就完美了,但是程序依然会跑飞。原因有两点:
1.即使加了两个字节那么Test的标号地址依然不是四字节对齐。

2.这两个字节的零会被编译器当作指令来处理的,这也就是说Test标号会被编译器来当作代码标号来处理,看到了吧,我们的数据编译器一插手就变成代码了,实在无奈的很。再来看一段手册上的讲解:

5

 

也就是实际上LDR r2,=Test执行后,r2=Test+1这也解释了为什么不加NOP或align 4的话r2=0x8000017而加了NOP或Align 4就r2=0x8000018。那么来看一下,加nop或align 4后的效果:


STACK_TOP EQU 0x20002000
AREA Reset,CODE,READONLY
DCD 0x20002000
DCD Start
ENTRY
; CODE16

Start
ldr r2,=Test
LDRD r0,r1,[r2,#4]
LDRD r0,r1,[r2]
LDRD r0,r1,[r2]
; movs r0,r0
; 如果是align 4会被加两个节字的movs r0,r0(机器码为0x0000)
; 如果是nop 则会被加上nop的机器码(0xBF00)

; NOP

align 4
Test
DCDU 0x12345678
END
        需要说明的是,我总是把align 和nop 放在一块说,并不是说nop也具有对齐作用。是因为加上nop后刚好可以使Test标号地址放在4字节对齐的其他地方。在其他地方,nop也许并无此作用。

stm32的窗口看门狗的一点发现

今天看stm32的窗口看门狗,突然发现很不正常,于是做了各方面的实验得出以下结论,由于只是实验了得出的结论没有官方结论,所以如果有人有理论依据请告知我,非常感谢1
1.一旦使能窗口看门狗的时钟,SR寄存器的EWIF位就会被置1
2.一旦在定义的窗口范围外喂狗就会复位
3.在没有使能窗口看门狗定时器的情况下(即CR寄存器的WDGA寄存器置1来使能看门狗),SR不能被软件清0
结论:所以必须把窗口看门狗的清0,使能中断放在初始化的最后2句才能正常运行!

关于stm32 APB总线上的”接口时钟使能”与”外设时钟使能”

今天看<<例说stm32>>上的RTC时钟,发现他没有使能APB1的上BKP时钟,就能读出BKP寄存器里的内容.很是不解,又看了一遍发现APB总线上的时钟使能分为两种,一种是时接口时钟一种是外设时钟.
又仔细观查了一下时钟树发现在APB外设时钟使能寄存器上表现为接口时钟的都已经有了自己的时钟,再经实验证明所有这种不需要在APB上使能就有自已时钟的外设(即在APBxENR的某一位上代表的是这个外设的接口时钟的外设)在不需要使能接口时钟的情况下就能读出这种外设的寄存器的内容!

关于STM32单片机GPIO口上拉与下拉输入

      以前GPIO上接的电路都是低电平触发的那种,新画了个板子有一个按键设计的是高电平触发,结果IO口设置成上拉输入后,怎么读都不正确,按键电路如图1。无奈只能去调试一下,发现当设置为上拉输入后,其ODR(GPIO输出数据寄存器)相应的也置为1,百思不得其解。

图1 按键电路

     于是去看一下GPIO的结构图:

I/O引脚结构图

      由图可以发现其实输出寄存器与输入寄存器之间在I/O引脚处是线与状态,在GPIO设置成上位输入时,其上拉电阻闭合,这时如果输出寄存器设置为0那么在IO口内部就会自己损耗电流,而这些电流的损耗是会增加功耗的。因此当GPIO设置成上拉输入时,相应的输出寄存器也设置为1.

      将按键电路与I/O引脚结构图连起来看,就清晰多了,当GPIO设置为上位输入时,上拉电阻与按键的330欧、220K欧串联,这时IO口所读到的电平就是330欧与220K欧电阻分压的总合,由于这两个电阻阻值过大,因引读出的电压大于逻辑’1’的阀值。OK问题找到了,于是将IO设置成下拉输入,问题就解决了。因此这里得出一个结论,IO口是上拉还是下拉要根据,IO口外部接的空闲电平有关,如按键不按下时是低电平,则应该设置为下拉输入。

      在调试时还发现另一种方法可以让代码正常运行,就是当设置上拉输入时,将其输出值设置为0,这样电平就被直接拉低了,按键的接地电路基本就不起作用了,但这样做有一个坏处,前面已经说过了,这种情况会导致上拉电阻直接接地了,增加了不必要功耗,而且将3.3V电压直接加在了输出驱动器的N-MOS上,时间长了之后可能会造成芯片损坏!

关于stm32 APB总线上的”接口时钟使能”与”外设时钟使能”

今天看<<例说stm32>>上的RTC时钟,发现他没有使能APB1的上BKP时钟,就能读出BKP寄存器里的内容.很是不解,又看了一遍发现APB总线上的时钟使能分为两种,一种是时接口时钟一种是外设时钟.
又仔细观查了一下时钟树发现在APB外设时钟使能寄存器上表现为接口时钟的都已经有了自己的时钟,再经实验证明所有这种不需要在APB上使能就有自已时钟的外设(即在APBxENR的某一位上代表的是这个外设的接口时钟的外设)在不需要使能接口时钟的情况下就能读出这种外设的寄存器的内容!

stm32的窗口看门狗的一点发现

今天看stm32的窗口看门狗,突然发现很不正常,于是做了各方面的实验得出以下结论,由于只是实验了得出的结论没有官方结论,所以如果有人有理论依据请告知我,非常感谢1
1.一旦使能窗口看门狗的时钟,SR寄存器的EWIF位就会被置1
2.一旦在定义的窗口范围外喂狗就会复位
3.在没有使能窗口看门狗定时器的情况下(即CR寄存器的WDGA寄存器置1来使能看门狗),SR不能被软件清0
结论:所以必须把窗口看门狗的清0,使能中断放在初始化的最后2句才能正常运行!

使用STM32的USB模块中后对USB缓冲区的认识

最近在使用STM32的USB模块开发个项目,还以为挺简单,结果搞了快两天才把USB的包缓冲区的访问搞定,在此做个小总结吧。

  STM32的USB模块包缓冲区有512B,但是在STM32的参考手册中的存储器映像中却表明0x40006000-0x400063ff,整整多了512B,怎么会这样呢,同时在尝试着编程时也遇到了一个问题:

在usb_core.c文件的Setup0_Process(void)这个函数中,有这么一段:

uint16_t offset = 1; 
if (pInformation->ControlState != PAUSE)
  {
    pInformation->USBbmRequestType = *pBuf.b++; /* bmRequestType */
    pInformation->USBbRequest = *pBuf.b++; /* bRequest */
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwValue = ByteSwap(*pBuf.w++); /* wValue */
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwIndex  = ByteSwap(*pBuf.w++); /* wIndex */
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwLength = *pBuf.w; /* wLength */
  }  

这其中又不太明白为什么需要pBuf.w += offset;而且后面的解释也不太懂 /* word not accessed because of 32 bits addressing */
,我于是在这段之前加入调试以显示收到什么数
        #ifdef DEBUG
        UARTSend_String("***  端点0收到SETUP数据  ***rn");
        for(offset=0;offset<16;offset++)
        {
                UARTSend_Hex(*pBuf.b++);
        }
        #endif
结果串口调试显示如下:
***  USB总线复位  ***
***  USB总线CTR置位  ***
***  进入端点0  ***
***  端点0收到SETUP包  ***
***  端点0收到SETUP数据  ***
0x80 0x06 0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00

本来应该显示0x80 0x06 0x00 0x01 0x00 0x000x40 0x00才对,不太明白怎么0x80 0x06和0x00 0x01 后面多了两个 0x00 ,难道USB模块还会将收到的数据跳着放吗?

 后来反复看参考手册,问了些人,才知道原来STM32的USB缓冲区是一个双端口的RAM,CPU一端需要使用32位方式访问,但USB模块一端使用16位方式访问。也就是说每个USB模块中的地址*2才能对应到控制器中的实际地址,这样每四个字节地址空间后两个字节地址空间是空的。所以上面串口调试显示的数据每正确两个字节就会多出两个字节的0x00。

这里也对STM32的USB库函数中对缓冲区的操作函数做个说明:

在usb_men.c文件有这么一个函数

void PMAToUserBufferCopy(uint8_t *pbUsrBuf, uint16_t wPMABufAddr, uint16_t wNBytes)

  uint32_t n = (wNBytes + 1) >> 1;/* /2*/
  uint32_t i;
  uint32_t *pdwVal;
  pdwVal = (uint32_t *)(wPMABufAddr * 2 + PMAAddr);
  for (i = n; i != 0; i–)
  {
    *(uint16_t*)pbUsrBuf++ = *pdwVal++;
    pbUsrBuf++;
  }

它的作用是将缓冲区中的数据拷贝到你所定义的uint8_t Receive_Buffer[];数组中。其它地方都好理解,这里说说

  pdwVal = (uint32_t *)(wPMABufAddr * 2 + PMAAddr);
  for (i = n; i != 0; i–)
  {
    *(uint16_t*)pbUsrBuf++ = *pdwVal++;
    pbUsrBuf++;
  }
首先是pdwVal = (uint32_t *)(wPMABufAddr * 2 + PMAAddr);这里wPMABufAddr * 2之所以要*2就是前面所述的USB模块中的地址*2才能对应到控制器中的实际地址,在取得对应端点的缓冲区首地址后,将其(uint32_t*)强制指向uint32_t型,这样每次*pdwVal++,pdwVal的地址都增加4个字节,并且每次都会有四个字节的数据读出。

在for循环中,*(uint16_t*)pbUsrBuf++ = *pdwVal++;这句重点说说,++与指针*同优先级,结合顺序为至右向左结合,因此相当于*((uint16_t*)pbUsrBuf++),先*(uint16_t*)pbUsrBuf赋值只取*pdwVal++的32位数据的前16位,然后再将pbUsrBuf加上1个字节。到这里,前面定义的uint8_tReceive_Buffer[];数组中就有两个数组变量被赋值了,但这时地址还指增加一个字节,因此还需要pbUsrBuf++;让其指向Receive_Buffer[];的下下个数组变量。一次循环,知道读出所有数据。

转自http://blog.csdn.net/ringstart/article/details/6822377