本帖最后由 JackTang1994 于 2022-2-8 10:43 编辑
#申请原创# #技术资源#
软件环境:MPLAB X IDE v5.5,XC8编译器
硬件环境:ATmega4809 Curiosity Nano开发板
参考资料
AVR指令集文档:https://www.microchip.com/DS40002198
ATmega4809 Curiosity Nano开发板:https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega4809_Curiosity_Nano_Schematics.pdf
AVR编译器用户手册:http://ww1.microchip.com/downloads/en/DeviceDoc/50002750D.pdf
示例代码工程:
Startup_LED.X.zip
(105.69 KB)
存储器介绍
存储器类型介绍RAM:随机存储器,速度快。可以按字节访问,掉电后数据丢失。
常用类型:SRAM、SDRAM、DDR等。
ROM:只读存储器,速度相对于RAM来说更慢。一般不能按照字节访问,基本访问单位一般为page(页)而且需要先擦除后才能写,掉电后数据依然保持,不会丢失。
常用类型:Flash、EEPROM、NandFlash、NorFlash等
总结:根据存储器特性,程序代码会被设计存储到ROM中,在MCU中ROM一般叫做Flash;而变量数据则会被设计存储在RAM中。
编译与链接是什么?
编译就是将源代码(C代码或者汇编代码)翻译成机器码(只包含01的二进制数)。根据不同的架构位数的MCU相应的编译器生成后的机器码长度也不一样。一般有16位、32位的机器码。
链接就是将生成的机器码进行组装,将其他源代码中的数据、代码放置在不同的内存区域。然后下载器再根据工程中设置下载地址,将生成的机器码镜像下载到相应的存储地址处。
MCU源代码编译链接及操作流程
说明:编译器和链接器以及代码下载软件工具都集成在了开发工具MPLAB X IDE中了
什么是启动代码?
启动代码就是芯片复位后最开始运行的代码。功能如下:- 设置栈(给C语言代码运行用的,用于程序的调用及局部变量)
- 设置堆(需要使用动态内存时设置)
- 中断向量表设计
- 调试器相关配置(有些调试器需要MCU处于相应调试状态时才能进行调试,比如:Trace,在进行trace时需要设置MCU中控制trace的相关寄存器)
- 对存储器的操作(代码搬运、RAM清零、以及将非0全局变量从ROM中搬移到RAM中)等操作。
启动代码的基本功能:设置栈、设置中断向量表
为什么MCU开发中,我们没有写过启动代码
因为这些启动代码,一般芯片公司已经写好了,已经放置在了下载的MCU pack包中了,我们创建工程后会自动复制到我们工程中。或者有些启动代码是自动生成的,不需要我们再写了。
编译选项设置
默认情况下,MPLAB中会使用默认的启动代码(MicroChip写好的)。如果想要在工程中使用我们编写的启动代码,则需要设置下编译选项:
查看预定义的section
目的:查看我们代码应该放置在哪个预定的区,因为我们没有修改默认的链接脚本。
查看MPLAB安装目录下的MPLAB_XC8_C_Compiler_User_Guide_for_AVR.pdf文档,从4.9章节可以知道,MPLAB链接脚本中已经预定义了10个在main调用之前运行代码的section(.init0-.init9)、10个在main调用之后运行代码的section(.fini0-.fini9)。
注:默认链接脚本路径:XXX\Microchip\xc8\v2.31\avr\avr\lib\ldscripts
MCU中断函数处理
在没有跳转语句时,代码是从头开始一条一条运行的。当发生中断时程序会自动跳转到相应的中断入口地址处而中断入口地址是连续的地址,一般我们把这连续的中断入口地址也叫做中断向量表。因为中断入口地址对应的内存空间只有16bit(2字节),这个空间根本无法保存我们的中断服务函数代码。所以我们会在中断入口地址处放置一条跳转语句,跳转到我们中断服务函数入口地址,从而来执行我们的中断服务函数。
启动代码编写——汇编版
; 说明:此文件不是用的AVRASM2.exe汇编器。此文件用的是gnu汇编器as - the GNU assembler.
; 版权申明:此代码仅供学习使用,没有经过严格测试,不便用于商业产品中。
; QQ:275424510
; Author:jack.tang
;修改说明:将rjmp指令更换成jmp
#include <xc.h>
.extern main ; 引用Main.c文件中的main函数
//中断向量表.放置跳转指令,当中断发生时跳转到相应的中断服务函数中执行中断函数
.section .vectors
jmp __ctors_end ;
jmp CRCSCAN_NMI
jmp BOD_VLM
jmp RTC_CNT_MEGA4809
jmp RTC_PIT
jmp CCL_CCL
jmp PORTA_PORT
jmp TCA0_OVF_vect
//在main函数运行前运行的代码存储区
.section .init2
clr r1 ; 清零r1寄存器。有些编译器要求这样的做
clr r16 ; 清零SREG状态寄存器
out SREG, r16
; Init the STACK Pointer. 初始化栈.用于子函数的调用
ldi r16,(RAMEND & 0xff) ; initialize
out CPU_SPL,r16 ; stack pointer.
ldi r16,(RAMEND >> 8) ; to RAMEND
out CPU_SPH,r16
.section .init9
rcall main ; 跳转到main函数中
jmp 0x00 ; main函数运行结束后执行的函数.直接跳转到0x00处即复位MCU
// 代码空间
.section .text
//中断服务函数
.weak CRCSCAN_NMI ;使用.weak伪指令来定义函数.当有外部定义此函数时,使用外部定义的函数
.global CRCSCAN_NMI
CRCSCAN_NMI:
reti
.weak BOD_VLM
.global BOD_VLM
BOD_VLM:
reti
.weak RTC_CNT_MEGA4809
.global RTC_CNT_MEGA4809
RTC_CNT_MEGA4809:
reti
.weak RTC_PIT
.global RTC_PIT
RTC_PIT:
reti
.weak CCL_CCL
.global CCL_CCL
CCL_CCL:
reti
.weak PORTA_PORT
.global PORTA_PORT
PORTA_PORT:
reti
.end
测试代码
在main函数中编译此启动文件的测试代码,检测我们编写的启动是否可以进入TCA0溢出中断
#include "mcc_generated_files/mcc.h"
volatile uint8_t flag = 0;
int main(void)
{
/* Initializes MCU, drivers and middleware */
SYSTEM_Initialize();
/* Replace with your application code */
// protected_write_io(&(CLKCTRL.MCLKCTRLB),IO_KEY,0x01);
PORTF.DIR = 0x20;
TCA0.SINGLE.PERH = 0x4C;
TCA0.SINGLE.PERL = 0x4B;
TCA0.SINGLE.CTRLA = 0x0D;
TCA0.SINGLE.INTCTRL = 0x01;
ENABLE_INTERRUPTS();
while(1)
{
if(flag == 1)
{
flag = 0;
PORTF.OUTTGL = 0x20;
}
}
}
ISR(TCA0_OVF_vect)
{
flag = 1;
TCA0.SINGLE.INTFLAGS = 0x01;//??????
}
启动代码编写——C语言版
C语言编写启动代码,可以参考我前面写的**:https://bbs.21ic.com/icview-3189168-1-1.html按照此**的启动代码仿写即可。
注:不同的编译器启动代码所使用的语言不同,汇编语言编写是常用的形式。
|