指令集和处理器架构
指令集 目前,常见的处理器架构有ARM、x86、MIPS、RISC-V等,按照指令集类型可分为CISC(复杂指令集)和RISC(精简指令集)两种。
复杂指令集指令庞杂,指令字长不固定,单一指令可完成复杂操作。结果就是编译器设计简单,但硬件十分复杂。目前似乎只有x86还在使用CISC。
精简指令集指令少,指令字长固定,复杂操作靠多条指令来完成。结果就是硬件十分简单,但对编译器设计要求更高。ARM、MIPS都采用RISC,RISC-V就更不用说了,看名字就知道。
处理器架构1. x86架构 x86架构的处理器采用了CISC指令集,分为x86和x86-64两类,目前主流的是x86-64,即64位处理器。 桌面电脑以及服务器,大部分采用了x86架构的处理器,以Intel和AMD的处理器为主。国产x86架构处理器目前还在追赶阶段,性能距离x86头号玩家Intel和AMD的器件型号差距还较大。
2. ARM架构 ARM架构凭借出色的功耗控制和在低功耗下的优秀性能表现,几乎已经霸占了整个移动设备市场,小到靠电池供电能工作数年的传感器,大到华为麒麟处理器、苹果A系列处理器、 ...
自己写RTOS(二)启动和基本驱动
本节有两个目标:
板子启动
基本驱动,printf重定向
1. 板子启动 一般来讲,MCU启动之后运行的第一段程序叫做BootRom,是厂商固化在芯片内部的,主要功能是检测启动方式和做一些必要的准备工作。大多数MCU都支持从多种存储器中启动,如NorFlash、NandFlash、EMMC等。对于STM32F1来说,可以选择3种存储器作为启动源分别是:flash,系统存储,片上RAM。flash的大小是512KB;片上RAM的大小是64KB;系统存储区存储内嵌的自举程序,由ST在生产线上写入,用于通过可用的串口对flash进行编程。我们的代码最终要放在flash上运行,但是前期调试阶段可以先放在RAM上跑,这样下载比较快速,方便调试,在RAM上跑的方法可以参考我写的分散加载相关的文章。
BootROM之后就是编程人员可以控制的部分了,程序会从地址0开始执行,但并不是执行0地址处的指令,而是取出0地址处的值赋给SP,然后取4地址处的值(其实是复位处理函数的地址)赋给PC,接着就会运行复位处理函数进行系统初始化,调用main()等一系列操作。4地址往后是一系列的异常服务函 ...
自己写RTOS(一)概述
概述 为了巩固对RTOS以及ARM Cortex-M的理解,打算从零写一个操作系统(内核)。相关知识背景:C语言精通;能写简单的汇编;使用过主流的RTOS;看过一些Linux内核相关的书籍。
计划实现的功能:
线程管理:这里没有使用RTOS中常用的“任务”这一名称,而是借用Linux等操作系统中的线程这一名称,原因是想做一个可动态管理,数量无限制的线程管理机制。
调度:基本调度策略为:不同优先级抢占调度,相同优先级时间片轮转。
线程同步与通讯:互斥量,信号量,消息队列,邮箱。
中断后半部
此RTOS跑在STM32F1的一块开发板上,使用Keil作为IDE。
因为我时间有限,上面列出的功能最后不一定全部能实现。
工程目录结构首先给这个RTOS起个名字,叫做HYYOS,所有OS相关的代码都放到这个目录里面,且内部目录安排如下:
123456hyyos |-include # 系统级的头文件、函数定义 |-kernel # HYYOS内核代码 |-Arch # 体系结构相关代码,新增支持时在此目录下新建文件夹 ...
STM32F1学习(二)GPIO
GPIO功能描述 STM32F103ZET6共有112个IO可用于GPIO(部分IO也可用作其他功能,如USART、IIC等),每16个作为一组,共分为7组,分别记做GPIOA、GPIOB…GPIOG,每组中的各个GPIO分别记做Px0~Px15,如对于GPIOA,对应PA0、PA1…PA15,把每组GPIO的Px0-Px7称作GPIO的低8位端口,Px8-Px15称作高8位端口。每一组GPIO由一套共7个寄存器控制,分别是:
GPIOx_CRL:配置(低8位端口)
GPIOx_CRH:配置(高8位端口)
GPIOx_IDR:输入数据
GPIOx_ODR:输出数据或输入上下拉选择
GPIOx_BSRR:置位/复位(高16bit复位,低16bit置位)(复位即清零)
GPIOx_BRR:复位
GPIOx_LCKR:锁定
详细的寄存器地址映像如下图:
其中CRL和CRH寄存器中的CNF[1:0]和MODE[1:0]对应的意义如下表:
复位期间和刚复位后,复用功能未开启,IO端口被配置成浮空输入模式。所有端口都有外部中断功能。在输入模式下由ODR寄存器控制上拉还是下拉。
...
STM32F1学习(一)时钟和SysTick
时钟树时钟树很重要,必须梳理清楚,详细参考《STM32中文参考手册》第6章。
系统刚上电时用的是系团内部8M RC时钟。我使用的开发板外部晶振频率8M。
时钟配置首先把一些常用的宏抽出来单独放在common.h中,当前主要包含位操作和位带操作,代码如下:common.h
1234567891011121314151617181920212223242526272829303132333435363738#ifndef COMMON_H#define COMMON_H#include "stm32f10x.h"#define SETUP_BIT(data, bit) ((data) |= 1UL << (bit))#define CLR_BIT(data, bit) ((data) &= ~(1UL << (bit)))#define GET_BIT(data, bit) ((data >> (bit)) & 1UL)#define BITS_MASK(startbit, endbit) ((1UL <&l ...
分散加载(六)更改程序运行地址
我们先来做一个最简单的示例,然后逐步深入。
这里我们用STM32F103ZET6作为示例,我们先来看其默认的分散加载(位于Object目录下自动生成的.sct文件):
加载域地址从0x08000000地址开始,大小为0x80000(512KB),运行域(RO)从0x08000000开始,运行域(RW+ZI)从片内SRAM地址开始0x20000000。 有的同学可能会问,《Cortex-M3权威指南》里面不是说上电从0地址开始依次取两个值赋给SP和PC吗,为什么这里会从0x08000000开始呢?这是因为STM32F1有2个boot引脚,接不同的电平的时候会把不同的地址映射到0地址上,如下表:
我们尝试让程序从0x08001000地址上开始运行,先把加载域和执行域的地址改成0x08001000
由上面的STM32上电时地址映射机制可知,由于此时地址空间的0地址是映射到0x08000000的,我们虽然修改了分散加载文件把程序放到了0x08001000,但是上电时还是会从0x08000000处开始取SP和PC,所以应该是不能正常运行的,我们跑一下看看:
可以看到程序停在了0x0 ...
分散加载(五)语法
语法、枯燥的、烦人的语法……,但是有些时候木有办法,我本来也不想写这些东西,但确实绕不过去,我认为把它当成一种工具比较合适,了解大概结构以及基本的语法,一些细节没必要记的那么清楚,遇到问题的时候知道去哪里查就好了,这部分东西来自Keil的帮助文档,帮助文档中内容更加丰富也更加复杂,我节选出了一部分,了解这些基本可以搞定绝大多数应用,节选了部分关键内容,供参考。
1、加载域描述加载域
1234load_region_name (base_address | ("+" offset)) [attribute_list] [max_size]{ execution_region_description+}
其中:
1234567891011121314151617181920212223242526272829load_region_name: 加载域名称。base_address: 指定要在其中链接加载域中对象的地址。 base_address 必须是字对齐的。 +offset: 描述 ...
分散加载(四)分散加载的结构
1、我们先来解剖一只麻雀 很多人会说我做项目时没用过分散加载啊,可能有些人甚至都不知道它的存在。事实上,开发环境会默认生成一个分散加载文件(或者叫链接器描述文件),你使用的可能就是这个默认的分散加载文件,先来看一下Keil默认生成的分散加载文件,使用LPC54608随便找了一个示例代码用Keil生成了一个,如下图所示:
这个分散加载是keil自己生成的分散加载,可以说是最简单的分散加载;里面红色字体的是各个段的起始地址以及大小,这些段的起始地址以及大小是由下图的红框框定的部分决定的,你可以把上图以及下面这张图合起来看,地址是一致的。
这个分散加载文件的基本意思如下图:
这里面Flash以及SRAM的地址以及大小都是可以修改的,其他的也可以修改,但起始地址以及大小都要在芯片真实存在的有效物理地址上,这部分需要参看芯片的用户手册里面的Memory MAP一节的内容,如下图就是LPC54608的Memory MAP,参看分散加载里面的地址,Flash以及SRAM的起始地址以及大小都落在有效的空间上了。如果你定义的Flash起始地址在0x40000000,那么肯 ...
ARM局部变量入栈分析
写这篇是因为在另一篇博客中讲RO、RW、ZI的时候提到函数内部的局部变量存在栈里,然后刚好自己也没有亲自观察过局部变量到底是怎么在栈里面创建和回收的,所以在这里详细分析一下。
本文中使用的MCU是STM32F407IG,IDE是IAR Embedded Workbench7.7。既然是要看栈操作,肯定要看汇编代码,IAR默认是不输出C文件对应的汇编代码的,需要我们在工程属性设置中打开开关,找到:Options - C/C++ Compiler - List - Output assembler file,将其勾选,同时也把Include source勾选,方便对比C和汇编。 设置好后,重新编译一下,会在Debug/List目录下给每个.c文件生成对应的.s文件,这就是我们要看的汇编文件了。也可以在IDE里面直接查看,方法是点击c文件左侧的+号,再点击Output左侧的+号,就可以看到.s文件了。
1. 变量入栈顺序先贴出C文件代码,只有一个简单的函数:
1234567int main(void){ int c; int d = 2; c = d + 1; ...
分散加载(二)概念
理解RO、RW、ZI 要理解RO、RW、ZI需要先了解以下背景知识:
1.ARM程序的组成 此处所说的“ARM程序”是指在ARM系统中正在运行的程序,而非保存在ROM中的二进制映像文件(image),这点类似于进程和程序之间的关系。 一个ARM程序包含3部分:RO、RW和ZI:
RO是指令和常量
RW是具有初值的全局或静态变量
ZI是没有初值的全局或静态变量(也包括初值为0)
一般其他人都把RW和ZI解释为已初始化变量和未初始化变量,我认为这种描述方法容易产生这是一种程序是静态的错觉,所以我使用了有初值和没有初值这种描述方法。下面用一段代码做示例:
123456789101112char *p = "hello"; // p属于RW,"hello"属于ROint a = 3; // a属于RWint b = 0; // b属于ZIint func(void){ int c; int d = 2; static int e; ...
