欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

在Linux下掌握arm和操作系统(3)--点亮LED

程序员文章站 2022-03-27 21:45:25
说了那么多,我们终于开始写代码了.毫不例外,我们从世界上最著名的程序"helloword"开始,硬件的"helloword"就是点亮一个LED.硬件原理图:从原理图看出,LED (D1)连接电源和地,上电就亮. LED D2连接VCC和PC13,所以我们只要给PC13引脚一个低电平就可以点亮这个LED.准备点灯俗话说,让我点亮一个LED我就能上天. 看把你能的,LED 可不......

说了那么多,我们终于开始写代码了.毫不例外,我们从世界上最著名的程序"helloword"开始,硬件的"helloword"就是点亮一个LED.

硬件原理图:

在Linux下掌握arm和操作系统(3)--点亮LED

从原理图看出,LED (D1)连接电源和地,上电就亮.  LED D2连接VCC和PC13,所以我们只要给PC13引脚一个低电平就可以点亮这个LED.

准备点灯

俗话说,让我点亮一个LED我就能上天.  看把你能的,LED 可不是那么好点的,要准备准备.

如果你学过51,你会告诉我点灯很容易.

如果你学过STM32,也许你会告诉我很容易.(为什么我说也许?因为我知道你心虚,你用的是别人的库吧?你有很多不明白的地方吧.哈哈哈)

今天我们就从头来,明明白白的点一回灯.

其实这个STM32点灯的却不是那么容易,因为在真实点灯之前有很多配置,往往这些配置是最让人头疼的.

这也是用库编程的问题所在:虽然你用了库点亮了LED灯,干了很多别的事情,但是有些事情还是模模糊糊不清楚.

扯远了,其实那些烦人的配置是用来配置时钟的,STM32这样设计也是有原因的:默认所有外设时钟关闭,使用的时候再开启,这样可以省电啊.有地方省,就有地方多付出,比如芯片设计人员和软件开发人员--我们.幸好STM32有默认内部时钟,虽然不咋地,但是能用,我们先凑合着用内部时钟点亮我们的LED.

点灯

先使能portC的时钟(我们要用PC13),然后将PC13设置为输出模式.

while(1)循环设置PC13高低电平,就可以看见LED灯一闪一闪的了

/* main.c */
#define RCC_BASE 0x40021000
#define RCC_CR (*(volatile unsigned long *)(RCC_BASE))
#define RCC_APB2ENR (*(volatile unsigned long *)(RCC_BASE+0x18))

#define GPIOC_BASE 0x40011000
#define GPIOC_CRH   (*(volatile unsigned long *)(GPIOC_BASE+0x04))
#define GPIOC_ODR   (*(volatile unsigned long *)(GPIOC_BASE+0x0c))
int main(){
  int i;

  RCC_APB2ENR = 1<<4;  //portC 时钟使能

  GPIOC_CRH = 3<<20;  // pin13 输出模式
  GPIOC_ODR = 0;
  while(1){
    GPIOC_ODR  |= 1<<13;   //pin13 高电平
    for(i=0;i<1000000;i++);
    GPIOC_ODR &= ~(1<<13);  //pin13 低电平
    for(i=0;i<1000000;i++);
  }
  return 0;
}

好像本章应该结束了?可是想想还有很多疑问:为什么从mian()函数开始执行?怎么编译成APP.bin?

为什么从mian()函数开始执行?

很多写应用的人往往会忽略忽这点,因为平时的应用都是从main开始的所以就理所当然的认为STM32也认识main().

要回答这个问题,这就要说说cortexM3的启动流程(复位流程).

在Linux下掌握arm和操作系统(3)--点亮LED

如上图,CPU一上电就从0x00000000地址处取一个值赋给MSP(R13)寄存器.然后从接下来的一个32bit对齐的地址0x00000004处读取第一条要执行的指令执行.

那么我们的程序怎么保证0x00000004处就是main()函数呢?

/* start.S */
.text
.globl _start 

_start:
    .word 0x20000000+20480 /*sram form 0x20000000,size 20K*/
    .word main /*跳转到main函数*/

从_start开始,第一个 .word 后面就是放入MSP中的值,因为.word占用4个字节所有.word main  就0x00000004处了.

这就保证了程序一上电就执行main()函数.

其实,入口也不一定非要是main()函数,你写exit()也是可以的,只要你愿意,main只是约定俗成.

怎么编译?

//将start.S编译为start.o  但是不链接
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c start.S -o start.o

//将main.c编译为main.o  但是不链接
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c main.c -o main.o

//将start.o 和start.o 链接成nuttx.  第一个字节放在0x00000000 
//这样main就在0x00000004
arm-none-eabi-ld -Ttext=0x00000000 start.o main.o -o nuttx

//最后转换格式,确保CPU可以正常运行
arm-none-eabi-objcopy -O binary -S nuttx nuttx.bin

上面解释的很详细了,就不再多说,可是每次编译都敲这么多命令也太麻烦了,干脆写一个脚本(针对make的Makefle)

 

# file :Makefile
All:
	arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c start.S -o start.o
	arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c main.c -o main.o
	arm-none-eabi-ld -Ttext=0x08000000 start.o main.o -o nuttx
	arm-none-eabi-objcopy -O binary -S nuttx nuttx.bin
	arm-none-eabi-objdump -D -m arm  nuttx > nuttx.dis
clean:
	-rm *.o nuttx*

flash: 
	sudo stm32flash -w nuttx.bin -v -g 0 /dev/ttyUSB0

这样创建led目录,创建3个文件

main.c  Makefile  start.S   内容如上文.

这样以后编译烧写就方便多了.

//编译
PC#make

//清除编译产生的文件
PC#make clean

//烧写到STM32
PC#make flash

576648661@qq.com

  • 微信号:find_xiaohei
  • 名称:找小黑 

在Linux下掌握arm和操作系统(3)--点亮LED

本文地址:https://blog.csdn.net/yyyyyyyyyywwwwwwwwww/article/details/85932437