[day]__arm基础知识
ARM底层 两个阶段: 第一阶段:学习操作硬件的基本的方式,GPIO的操作,SysTick,定时器,串口(USART/UART,IIC,SPI。。。),中断,ADC,PWM。。。 平台:OWL-IOT32开发板---STM32F103RCT6-----》意法半导体 第二阶段:了解和理解ARM寄存器,ARM指令集,汇编语言的基本编程,汇编语言操作Cortex-A8(S5PV210)的硬件资源,CPU的启动过程, bootloader和uboot的概念,uboot的启动过程,C嵌入汇编混合编程。----》理解CPU如何通过uboot/bootloader引导Linux内核启动。
1. ARM--是一个公司的名称,它也是一类处理器的代称。(ARM7(单片机的架构,应用于低端市场),ARM9(CPU,应用于高端市场),ARM11,Cortex-M(单片机架构---没有MMU内存管理单元的微处理器),cortex-A,cortex-R(实时方案的CPU,主要应用于航天,军工等)) 2. ARM这个公司,它只开发ARM的IP内核,不生产芯片。 3. 复杂指令集----CSIC 精简指令集-----RSIC S5PV210是基于ARM体系架构的Cortex-A8内核的CPU。
4.搭建开发环境 (1)安装MDK513,一路Next,记住安装路径(C:\Keil_v5),first name和邮箱任意输入 (2)破解Keil。复制CID,在破解软件上生成破解码,进行破解。 (3)添加资源包:Keil.STM32F1xx_DFP.2.1.0.pack,将其导入到MDK中。 (4)鼠标选择计算机--》右击选择属性--》高级系统设置--》环境变量 -----<1>在系统变量处创建一个新变量:ARMCC5LIB,变量值:C:\Keil_v5\ARM\ARMCC\lib -----<2>在Path变量中再添加路径:C:\Keil_v5\ARM\ARMCC\bin -----<3>从路径C:\Keil_v5\ARM\Pack\ARM\CMSIS\4.2.0中将CMSIS拷贝到C:\Keil_v5\ARM目录下
5.SWD接口与ST-Link的连接 不连接+3.3v,其他接口,pin对pin连接。
6.安装ST-Link确定和USB转串口驱动 (1)右击计算机---》管理----》设备管理器 (2)将ST-Link插入电脑的USB口,安装ST-Link驱动,选择stsw-link009.zip包,解压,安装dpinst_amd64.exe (3)使用MiniUSB线将板卡与PC连接,安装USB转串口驱动,因为OWL-IOT32板块所使用的TTL电平转换芯片为CH340,所有安装CH340相关的驱动。 进入目录tool\CH340-USB转串口驱动\CH341SER,运行SETUP.EXE程序。选择安装即可。
注:ST-Link作为MCU的烧录程序的工具,USB转串口作为程序调试的手段。
MCU---微处理器 CPU---处理器
7.在MDK513环境下建立STM32工程。 (1)创建一个新的工程,任意取名SWL_IOT32 (2)添加STM32F10系列的标准外设库 -----<1>将标准外设库的驱动文件添加到工程目录中,即将Libraries拷贝到工程目录中。 -----<2>在MDK工程中添加Startup,CMSIS,Libraries,User,Doc组 -----<3>添加启动文件,启动文件由汇编语言编写。存在的位置为:STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm 添加startup_stm32f10x_hd.s文件到MDK工程中。 -----<4>添加系统架构相关的文件system_stm32f10x.c到MDK工程 -----<5>创建mian.c文件,保存在User目录下,并添加到MDK工程中。 -----<6>添加中断相关的文件stm32f10x_it.c、stm32f10x_it.h和配置头文件stm32f10x_conf.h到工程中, (3)将工程相关连的所有头文件手动包含进来。 步骤为Options for Target---》C/C++----》Include Path (4)添加设备选择的全局宏定义:STM32F10X_HD,USE_STDPERIPH_DRIVER (5)在main.c文件中添加头文件:#include "stm32f10x.h" int main(void) { while(1) { } }
目标:遇到任何的硬件,都可以自由的操作。
8.STM32的两种库的区别。 (1)标准外设库:只直接提供MCU相关的外设的基本操作的代码。不向用户提供实现的软件方案。----》用户需要自己却配置驱动 (2)Cube库:尽可能的为用户提供软件方案,加快用户的开发速率。----》主要应用项目的开发。 (3)给出的库文件名分别为:en.stm32cubef1.zip和STM32F10x_StdPeriph_Lib_V3.5.0 (4)使用STM32F10x_StdPeriph_Lib_V3.5.0进行开发。
9.目标:操作MCU的GPIO端口 (1)根据板卡原理图得知所需要操作的GPIO口;板卡配置的可直接操作GPIO口就能看到现象的有:LED灯(RGB LED)和蜂鸣器 (2)操作RGB LED灯,其与MCU的硬件连接为:LED_R----》PC8,LED_G----》PC7,LED_B----》PC6 (3)当PC6~8三个引脚输出低电平时,LED亮,输出高电平时,LED灭。 (4)配置PC6~8三个引脚:配置为输出模式;设置I/O口输出高/低电平。 (5)初始化STM32F103RCT6的PC6~8口 -----<1> 打开GPIO的时钟---->(时钟:是单片机运行的频率的源头) 确定GPIO口在什么时钟下:APB2时钟。 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); -----<2>配置GPIO口为推挽输出 GPIO_InitStruct_info.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8; GPIO_InitStruct_info.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct_info.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOC, &GPIO_InitStruct_info); (6)设置I/O口的输出(高/低) 输出高电平:GPIO_SetBits(GPIOC, GPIO_Pin_8); 输出低电平:GPIO_ResetBits(GPIOC, GPIO_Pin_8);
10.目标:操作GPIO口,驱动蜂鸣器鸣叫。 (1)从原理图可以分析,当PNP三极管的基极(b)为低电平时,蜂鸣器鸣叫,当基极(b)为高电平时,蜂鸣器不鸣叫。 (2)蜂鸣器的控制引脚为PC3脚,且当PC3输出低电平时,蜂鸣器鸣叫;否则蜂鸣器不鸣叫。 (3)配置PC3口为:推挽输出模式, (4)初始化MCU的PC3口。 GPIO_InitTypeDef GPIO_InitStruct_info; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStruct_info.GPIO_Pin = GPIO_Pin_3; GPIO_InitStruct_info.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct_info.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOC, &GPIO_InitStruct_info); GPIO_SetBits(GPIOC, GPIO_Pin_3);
11.要学习操作一款MCU或者一款CPU,需要: (1)Datasheet------数据手册 (2)参考手册 (3)编程手册
12.对应配置外设而言,寄存器:是MCU或者CPU所存在的一个或多个32bit地址, 这个地址是MCU或者CPU映射给其外设资源使用。
13.我们所调用库函数来进行操作MCU的外设,实际上是在MCU的指定地址的寄存器上进行配置, 目标:通过操作寄存器的方式来点亮LED。 (1)分析参考手册得知,需要操作APB2 外设时钟使能寄存器(RCC_APB2ENR)的4位,并将其置1,就可以打开GPIOC口的时钟。 (2)按位操作RCC_APB2ENR寄存器: RCC->APB2ENR &= ~(0x10); RCC->APB2ENR |= 0x10; 或者 RCC->APB2ENR &= ~(0x01<<4); RCC->APB2ENR |= 0x01<<4; (3)分析参考手册,可知端口配置低寄存器(GPIOx_CRL) (x=A..E)控制着GPIOx0~7这8个引脚, 端口配置高寄存器(GPIOx_CRH) (x=A..E)控制着GPIOx8~15这8个引脚。 (4)初始化I/O #if 0 RCC->APB2ENR &= ~(0x10); RCC->APB2ENR |= 0x10; #else RCC->APB2ENR &= ~(0x01<<4); RCC->APB2ENR |= 0x01<<4; #endif
GPIOC->CRL &= ~(0xff000000); GPIOC->CRL |= 0x33000000; GPIOC->CRH &= ~(0x0f); GPIOC->CRH |= 0x03; #if 0 GPIOC->BSRR = 0x01C0; #else GPIOC->ODR &= ~0x01C0; GPIOC->ODR |= 0x01C0; #endif
14.STM32的GPIO口分为GPIOA,B,C,D,E,F,G共7组I/O口,每一组GPIO口都有对应的16个I/O引脚,分别为GPIOx0~15。
15.目标:使用板卡上的IO按键。 (1)从原理图分析出,按键K1~K4锁对应的GPIO口为PA0~PA3; 当IO按键没有按下时,IO口处于高电平状态;当按键按下之后,IO口处于低电平状态。 (2)将PA0~PA3这4个I/O口配置为上拉输入模式。 (3)初始化PA0~PA3 GPIO_InitTypeDef GPIO_InitStruct_info; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct_info.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStruct_info.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStruct_info); (4)编写按键捕获程序 typedef enum { MSG_KEY1 = 0, MSG_KEY2, MSG_KEY3, MSG_KEY4, }__KEY_TYPE; uint8_t Get_KeyStatus(void) { if(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_0)) { delay(); if(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_0)) { while(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_0)); return MSG_KEY1; } } if(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_1)) { delay(); if(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_1)) { while(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_1)); return MSG_KEY2; } } if(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_2)) { delay(); if(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_2)) { while(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_2)); return MSG_KEY3; } } if(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_3)) { delay(); if(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_3)) { while(!GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_3)); return MSG_KEY4; } } return 0xff; }
//根据不同的按键的状态来执行是否需要点灯。 void Key_Scan(uint8_t k_type) { switch(k_type) { case MSG_KEY1: Ctl_LED(RED_LED,ON); break; case MSG_KEY2: Ctl_LED(GREEN_LED,ON); break; case MSG_KEY3: Ctl_LED(BLUE_LED,ON); break; case MSG_KEY4: Ctl_LED(RED_LED,OFF); Ctl_LED(GREEN_LED,OFF); Ctl_LED(BLUE_LED,OFF); break; } }
16.RCC (1)Cortex-M3主要存在三种时钟源,HSI为MCU内嵌的RC振荡器;HSE为外部晶振;PLL为锁相环。 (2)配置时钟树(RCC) ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON); HSEStartUpStatus = RCC_WaitForHSEStartUp(); if(HSEStartUpStatus == SUCCESS)
{ FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); FLASH_SetLatency(FLASH_Latency_2);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{ } RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08)
{ } } 17.SySTick:又称为滴答定时器 做一个精确的延时:(1)使用通用定时器,(2)使用SysTick SysTick的作用:(1)做精确延时; (2)当MCU需要运行实时操作系统时(uCOS,FreeRTOS,RT-Thead,e-cos,飞鸟。。。。。), 作为操作系统的系统时钟时间片使用。
配置使用SysTick: //延时nms //注意nms的范围 //SysTick->LOAD为24位寄存器,所以,最大延时为: //nms<=0xffffff*8*1000/SYSCLK //SYSCLK单位为Hz,nms单位为ms //对72M条件下,nms<=1864
void delay_ms(u16 nms) { u32 temp;
SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit) SysTick->VAL =0x00; //清空计数器 SysTick->CTRL=0x01 ; //开始倒数
do { temp=SysTick->CTRL; } while(temp&0x01&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器 SysTick->VAL =0X00; //清空计数器 }
//延时nus //nus为要延时的us数. void delay_us(u32 nus) { u32 temp; SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器 SysTick->CTRL=0x01 ; //开始倒数
do { temp=SysTick->CTRL; } while(temp&0x01&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器 SysTick->VAL =0X00; //清空计数器 }
18.MCU或者CPU运行C语言程序的条件:存在堆和栈空间。 在嵌入式系统中,一个C程序 的运行可以不存在main函数。
19.通信方式:串口(USB,UART/USART(MAX232,SP2303,CH340,CP2012等,485(MAX485)), I2C,SPI,单总线,三总线。。。。。)和并口
20.UART/USART是什么?UART和USART有何区别? (1)UART表示异步串口通信,输入输出共用一个Buff或者FIFO。 (2)<1>USART同步/异步串口通信,<2>它支持流控输入输出。
什么是流控:就是设备之间在使用USART协议进行通信时,为了提高数据的准确度, 传输速率和大数据量的数据传输,而遵循的协议。 21.UART/USART应用场景: (1)设备之间的USART/UART通信。 (2)作为485通信的基础通信协议 (3)作为工程师调试程序的重要手段。 (4)UART/USART主要适用于慢速的数据交互。
22.UART和USART协议的接口是什么样的? (1)UART-----有数据发送口:TxD 数据接收口:RxD (2)USART----有数据发送口:TxD 数据接收口:RxD 流控输入:CTS 流控输出:RTS 时钟线:SCLK
23.目标:配置使用UART/USART。 (1)根据分析原理图可知,板卡所使用的UART/USART通信为MCU的USART1。 (2)如何配置MCU的USART1?? ------<1>需要配置串口通信的通信速率,使用波特率表示。 ------<2>需要配置串口通信数据的格式,常用的协议时:8N1 8:数据的长度为8个bit N:NO 校验位 1:1个停止位 (3)初始化USART1, GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure;
USART_DeInit(USART1); /* config USART1 clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); /* USART1 GPIO config */ /* Configure USART1 Tx (PA.09) as alternate function push-pull */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure USART1 Rx (PA.10) as input floating */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); /* USART1 mode config */ USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No ; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// USART_ClearFlag(USART1,USART_FLAG_TC); // USART1->SR &= ~(0x01<<6); USART_Cmd(USART1, ENABLE);
(4)使用USART1:将数据从开发板打印到PC机。 int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); return (ch); }
24.Printf的使用: (1)对于标准C库函数而言,类似于像Printf这样的库函数,在标准中为用户留下了一个重映射接口。 (2)举例:在Windows环境下编写C程序是,运行时都会有一个小窗口打印输出的原因是因为printf函数在PC机 环境下默认的被配置为打印到屏幕显示。 (3)在嵌入式设备的底层编程中,如果需要使用printf函数进行调试,那么通常我们需要重新编写printf的重映射接口函数。 int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); return (ch); }
25.什么是DMA?DMA是CPU的数据搬运工。
26.什么是中断?中断的作用是什么??为什么要使用中断? (1)当在执行事件A时,过程中因为紧急事件B是产生,使得先去执行事件B,处理完事件B之后,又回到事件A被打断的地方 开始重新处理A事件。这个过程就叫一次中断。如果在处理事件B的时候,比B更紧急的事件C产生,那么就会先去执行事件C, 处理完事件C之后,在回到事件B被打断的地方开始接着处理事件B。这个过程就叫做中断的嵌套。 如果在处理事件B的时候,过程中,事件C产生了,但是事件C并不是紧急事件,所以,通常可能会先处理完事件B,再去处理事件C, 当处理完事件C之后,回到事件A,继续处理事件A。这个过程就叫做中断等待。
(2)中断是作为处理紧急事件而存在的。 (3)为了在CPU在进行梳理各个事件的时候,可以根据事件的紧急性来选择此刻所需要处理的事件。使用中断机制之后, 可以很方面的让系统对紧急事件进行识别。也可以更便于我们在程序Demo中实现管理。
27.嵌套向量中断控制器(NVIC) (1)管理中断机制。 (2)管理中断的中断源。 (3)管理中断的触发方式。
28.中断的优先级 (1)在处理事件是,如果有更紧急的事件到来,CPU直接先处理紧急的事件,那么就说明这个紧急事件的优先级高。 (2)在处理事件A时,当事件B和事件C通常产生,并且事件B比事件C更紧急,那么会优先处理事件B,这就说明事件B 的优先级高。 (3)在同一时刻的事件端内,当有多个事件发生时,CPU就需要根据事件的优先级来优先处理所触发的优先级高事件。 (4)在一个极短的时间端内,如果有多个紧急事件在不同的时刻产生。那么CPU会有两种情况进行处理: -----<1>执行完当前所处理的事件,处理结束后,CPU再根据余下的事件的优先级的高低来判断优先处理的为当前优先级最高的事件。 这种情况叫做响应式优先级。响应式优先级不能产生中断嵌套。 -----<2>如果后续产生的事件的优先级比正在出力的事件的优先级高,那么就会打断当前在处理的事件,先去处理优先级高的事件, 高优先级的事件处理完毕之后,又重新回到断点继续处理事件。循环如此。这种情况叫做抢占式优先级。 抢占式优先级可以产生中断嵌套。 (5)规定:中断的优先级通常用一个8bit的数进行表示(存在负数)。数值越小,表示的优先级越高。 注意:在用户程序表示优先级时,只能用一个8bit的正数表示优先级,0为最高优先级。 负数所表示的优先级通常为处理器的异常处理模式。这个优先级不能改变。
29.在CPU当中,如何表示中断? (1)在CPU中,将CPU的0~4G的地址空间的最前面的部分作为存放中断的位置,从0x00000000地址开始。 (2)其使用中断号表示,通常是一个8bit的数据。CPU就是通过中断号来进行识别此刻产生了什么样的中断。 例如,识别中断号为44时,表示此时产生了USART1中断。
30.CPU是如何根据抢占式优先级和响应式优先级来处理中断事件的?? (1)抢占式优先级是主优先级;响应式优先级是副优先级;所有CPU主要依据主优先级来进行中断的响应处理。 (2)假设同时产生了两个事件A和B,其中事件A的抢占式优先级比事件B的抢占式优先级高。那么CPU会优先处理事件A。 (3)假设先产生了事件A,后产生事件B,其中事件B的抢占优先级比事件A的抢占式优先级高,那么CPU会打断正在处理的事件A, 转而优先处理事件B,这种现象就是中断嵌套的过程。 (4)如果同时产生事件A和事件B,其中事件A的和事件B的抢占式优先级相同。此时CPU会根据响应式优先级的高低来判断需要优先响应哪一个事件。 (5)假设先产生事件A,后产生事件B,其中事件A的和事件B的抢占式优先级相同,并且事件B的响应优先级比事件A的高。 此时,CPU不能打断事件A来优先响应事件B。只有当事件A处理完毕之后,才会响应事件B。--此种状态下的中断不可打断。 (6)如果同时产生事件A和事件B,其中事件A和事件B的响应式优先级和抢占式优先级都相同,那么需要根据这两个中断在中断向量表 中的位置来决定优先执行哪一个事件。如果事件A在中断向量表中的位置比事件B靠前,那么会优先响应事件A。 举例:如果ADC中断和USART1中断的触发情况与以上现象相同,那么CPU会优先处理ADC的中断。
31.中断的响应过程。 (1)保护现场 (2)查询异常向量表 (3)处理中断事件 (4)恢复现场,继续运行程序。
32.配置使用中断,举例:USART输出中断的使用。 (1)配置NVIC。 -----<1>选择中断优先级的设置方式 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); -----<2>配置外设的中断。 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); (2)打开USART1中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); (3)编写中断函数 -----<1>中断函数名必须为void USART1_IRQHandler(void) -----<2>中断函数的框架
void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }
33.NVIC的作用: <1>选择中断优先级的分组 <2>设置中断优先级 <3>选择中断源 <4>确定中断的处理。
34.如果要打开一个外设的中断,那么需要在于外设相关的寄存器当中打开中断。 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
35.中断的使用,可以多多益善吗? (1)在一个工程当中,中断不能滥用。
36.不同的中断,优先级可以相同。
37.什么是定时器?STM32有多少定时器??它们的区别是什么? (1)通过计数器来进行实现定时功能的外设。 (2)STM32存在11个定时器 (3)SysTick,2个高级控制定时器,4个通用定时器,2个基本定时器,2个看门狗
38.配置使用一个定时器。 (1)选择优先级组 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); (2)配置NVIC控制器 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); (3)打开定时器时钟(APB1) CC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE); (4)初始化定时器,需要计算分频因子和计数值 /*********************************************************************** **函数功能:定时器2初始化配置,产生1s中断一次的计时 **计 算:计数器的时钟频率CK_CNT = f(CK_PSC)/(PCS[15:0]+1) ** 计数器的计时频率CK_CNT = 36MHz/(7199+1) = 10KHz ** 单次计数时间 T(CNT) = 1/CK_CNT = 100us = 1/10ms ** 定时器溢出时间 Tout = ((CNT[15:0] + 1)*[PSC[15:0]+1])/Tclk ** 定时器溢出时间 Tout = ((9999 + 1)*(7199 + 1))/72MHz = 1s ** 定时器溢出时间 Tout = ((19 + 1)*(7199 + 1))/72MHz = 2*1/1000s = 2ms **********************************************************************/ TIM_TimeBaseStructure.TIM_Period = 9999; TIM_TimeBaseStructure.TIM_Prescaler = 7199; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
39.什么是I2C通信??I2C通信有什么特点? (1)I2C总线是一个2线制的全双工的通信总线。 (2)I2C通信在接口上只有两条线 (3)I2C通信支持一主多从的哦你关系模式 (4)每一个支持I2C通信接口的设备都在总线上存在一个唯一的地址 ()
40.I2C的接口 (1)时钟线--------------SCL (2)数据线--------------SDA
41.I2C的多机通信 (1)条件:如果在I2C总线上存在CPU处理器,那么这个作为从机的CPU处理器必须硬件支持I2C协议。 (2)且,从机使用到的必须是硬件的I2C接口。
42.I2C总线协议的通讯时序可以使用硬件I2C协议,也可以软件模拟I2C时序。但是,只能是主机可以软件模拟I2C时序。
43.I2C总线协议的时序: 前提:在I2C总线上通常需要挂载1~10K的上拉电阻,保证在总线处于空闲状态的时候,保持高电平。 (1)起始信号Start:在时钟线SCL为高电平的状态下,如果SDA产生一个下降沿, (2)结束信号Stop:在时钟线SCL为高电平的状态下,如果SDA产生一个上升沿, (3)应答信号ACK:在I2C通信中,当主机个从机发送一个Byte的数据(或者是主机接收到从机的一个Byte的数据)时, 如果主机接收到一个低电平的信号(或者是主机发送一个低电平信号给从机),表示数据已经成功 的发送给了从机(已经接收到从机的数据)。(应答信号ACK为低电平) (4)非应答信号NACK:当主机不想接收从机的数据了(或者是主机不想进行通信了),由主机发送一个非应答信号, 然后发送Stop信号,通信结束。(非应答信号ACK为高电平) 注:应答信号是由两个通信的设备相互响应的,所有,如果主机读取应答信号,通常SCL会处于高电平。 如果由主机发送应答信号,SCL必须为低电平。 (5)发送一个Byte数据:通信时,数据先发高位,后发低位。并且只有在SCL为低电平状态下才按位发送。 (6)接收一个Byte的数据:通信时,先接收数据高位,后接收数据低位。并且只有在SCL为低电平状态下才读。
44.编写I2C协议的时序
45.使用I2C协议操作设备:EEPROM (1)参看硬件原理图
46.SPI通信协议 (1)SPI通信协议存在四根线 (2)SPI通信协议可以用于一主多从的通信
47.SPI通信的接口: (1)CLK-----------时钟线 (2)MOSI----------主机输出,从机输入数据线 (3)MISO----------主机输入,从机输出数据线 (4)CE------------使能线
48.SPI通信的通信模式 (1)对应SPI通信,存在极性(CPOL)和相位(CPHA),它们将决定SPI通信的方式, 或者说SPI总线上的电平关系和数据读取方式。 (2)如果 CPOL=0,串行同步时钟的空闲状态为低电平 (3)如果 CPOL=1,串行同步时钟的空闲状态为高电平 (4)如果 CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样 (5)如果 CPHA=1, 在串行同步时钟的第二个跳变沿(上升或下降)数据被采样
49.SPI通信的线制 (1)3线制SPI接口包括:CLK,MOSI,MISO (2)2线制SPI接口包括:CLK,MOSI或者MISO
50.PWM的作用: (1)PWM通常作为直流,交流电机的驱动方式 (2)舵机,LED等
51.使用ADC的目的是,采集电压。
52.ARM 的寄存器
53.ARM的工作模式
54.搭建环境 /**********************************************************************************/ 1.我们使用什么样的编译器/工具进行编译程序?? (1)使用交叉编译工具链来进行ARM程序的编译。 <1>交叉编译工具链:能在某一平台架构上编译,生成另一个平台的可执行程序的编译器。 比如,在PC机中,编译出能够在ARM架构的平台上运行的可执行程序。 这个编译工具就叫做交叉编译工具链。 <2>交叉编译工具链中的工具: ---交叉编译器: ---交叉链接器: ---交叉转换器: ---交叉ELF文件工具: ---交叉反汇编器:
交叉工具链:toolchain, croostool 类似于gcc编译器,编译出的目标文件只能在arm的soc上运行 编译器是可以通过编译出来(内核源码,开源软件),专门的公司会去帮忙(原厂)制作 用法: 和gcc(x86)用法一模一样 gcc test.c -o test_elf arm-none-gnueabi-gcc test.c -o test_elf 2.将toolchain-4.5.1-farsight.tar.bz2拷贝到ubuntu /usr/local$ mkdir arm
tar -xvf toolchain-4.5.1-farsight.tar.bz2 -C /usr/local/arm/
3. 将/usr/local/arm/toolchain-4.5.1-farsight/bin放入到环境变量PATH 希望直接去使用arm-none-linux-gnueabi-gcc,就需要将路径放入到PATH
1, /etc/environment (只有在开机的时候会读取,修改后需要重启,所有用户都有效) PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/arm-linux-gcc/bin" 2, vim /home/farsight/.bashrc (每次打开一个终端,就会读取该配置) export PATH=$PATH:/usr/local/arm/arm-linux-gcc/bin
保存之后,更新.bashrc source /home/farsight/.bashrc 就可以使用了arm-none-linux-gnueabi-gcc
4.协处理器访问:MRC p15, op1, <Rd>, CRn, CRm, op2 ;
5.在FS-S5PV210板卡上点灯。 (1)了解板卡的资源、了解S5PV210和参看板卡的原理图。 S5PV210的GPIO口相关的寄存器为: GPA0CON, GPA0DAT, GPA0PUD, and GPA0DRV GPA0CONPDN, and GPA0PUDPDN
LED1:GPC0_3 LED2:GPC0_4, 当GPC0_3/GPC0_4输出高电平时,LED1/LED2亮;相反LED灭。
(2)编写汇编程序点亮LED GPC0CON[3] [15:12]== 0001 GPC0CON[4] [19:16]== 0001 GPC0DAT[4:3] == 11 ----->点亮LED GPC0DAT[4:3] == 00 ----->熄灭LED
GNU汇编的汇编文件的后缀必须是.S 其他汇编文件的后缀为.s
.text .global _start _start:
#define GPC0CON 0xE0200060 #define GPC0DAT 0xE0200064
ldr r0,=GPC0CON ;将GPC0CON寄存器地址装载到R1寄存器 ldr r1,=0x11000 ;将GPC0_3和GPC0_4引脚配置为输出模式 str r1,[r0]
ldr r0,=GPC0DAT ldr r1,=0x18 str r1,[r0]
stop: b stop
(3)板卡烧录裸机 <1>将板卡配置为nandflash启动模式:ON OFF OFF OFF <2>电源线、串口线、网线 <3>有些板卡打开电源键后,不能直接启动;需要长按KEYON按键启动。 <4>使用putty将com口打开,波特率为115200 <5>板卡启动后,在倒计时之内,按下回车或者空格键,进入uboot命令模式: <6>确定IP地址:WindowsIP:192.168.7.11 ubuntuIP:192.168.7.5 BoardIP :192.168.7.6
<7>使用putty通过串口设置板卡的IP、TFTP服务器的IP setenv serverip 192.168.7.5 //配置好TFTP的Ubuntu IP setenv ipaddr 192.168.7.6 //板卡的IP saveenv //保存设置 <8>在板卡上使用TFTP将Ubuntu中的/tftpboot目录中的lenasm.bin文件下载到板卡中。 tftp 20008000 ledasm.bin go 20008000
<9>uboot的烧录(路径:D:\ARM底层\s5pv210\s5pv210_arm\s5pv210_sd烧录\烧录工具\for_burn) -------通过tftp从ubuntu下载uboot: tftp 20008000 u-boot.bin -------擦除0x100000大小的nandflash: nand erase 0 100000 -------将uboot写入nandflash: nand write 0x20008000 0 100000
<10>mov指令只能操作8bit的数据,如果大于8bit,那么需要使用伪指令ldr
10.使用C语言编写S5Pv210板卡的程序 <1>关闭看门狗 -----看门狗就是CPU中的一个特殊的定时器。这个定时器一旦启用, 那么必须在设置的时间内进行“喂狗”,、。 喂狗: a.在定时器溢出之前,清空计数器 b.并且重新配置预装载寄存器 如果不喂狗,那么CPU就好在定时器溢出时复位
#define WTCON 0xE2700000 ldr r0,=WTCON ldr r1,=0x00 str r1,[r0]
<2>构建堆栈空间 调整sp指针指向相应的内存地址中 ldr sp, =0x40008000
sp<==>R13 <3>跳转到C语言函数运行 volatile:作为告诉编译器,对相应的声明不进行优化。从而使得可以使用一个长整型的数据表示内存地址 0xE0200060----长整型的32bit的数据 (volatile unsigned long *)0xE0200060---将其表示为一个地址 (*(volatile unsigned long *)0xE0200060)--表示一个地址中的存储的数据
led.c
#define GPC0CON (*(volatile unsigned long *)0xE0200060) #define GPC0DAT (*(volatile unsigned long *)0xE0200064)
int led(void) { unsigned long i; GPC0CON |= (0x1<<12)|(0x1<<16); //设置GPC0_3,4为输出口 GPC0DAT =0X00; //初始化GPC0_3,4引脚数据
while(1) { GPC0DAT |=(0x3<<3); for(i=0;i<=1000000;i++); GPC0DAT &= ~(0x3<<3); for(i=0;i<=1000000;i++); } }
11.汇编与C传参 规定:在汇编中r0,r1,r2,r3这四个寄存器作为为C语言的函数传参数,页只能使用者4个寄存器传参。 如果C函数的型参超过4个。那么前4个型参使用r0,r1,r2,r3这四个寄存器。后面的型参通过压栈来实现。 void led(unsigned long reg_cpc0con,unsigned long reg_cpc0dat) { unsigned long i; GPC0CON |= reg_cpc0con; //设置GPC0_3,4为输出口 GPC0DAT = reg_cpc0dat; //初始化GPC0_3,4引脚数据
while(1) { GPC0DAT |=(0x3<<3); for(i=0;i<=1000000;i++); GPC0DAT &= ~(0x3<<3); for(i=0;i<=1000000;i++); } }
.text .global _start _start:
#define WTCON 0xE2700000 ldr r0,=WTCON ldr r1,=0x00 str r1,[r0]
ldr sp,=0x40008000
ldr r0,=0x11000 ldr r1,=0x00
b led
stop: b stop
///////////////////////////////////////////////////////////////////////////// 如下例中:型参a,b,c,d由寄存器r0,r1,r2,r3传输。而对于其他型参,则通过压栈/入栈来进行传输。 void led(unsigned long a,unsigned long b,unsigned long c,unsigned long d, unsigned long reg_gpc0con,unsigned long reg_gpc0dat) { unsigned long i; GPC0CON |= reg_gpc0con; //设置GPC0_3,4为输出口 GPC0DAT = reg_gpc0dat; //初始化GPC0_3,4引脚数据
a=0; b=0; c=0; d=0; while(1) { GPC0DAT |=(0x3<<3); for(i=0;i<=1000000;i++); GPC0DAT &= ~(0x3<<3); for(i=0;i<=1000000;i++); } }
.text .global _start _start:
#define WTCON 0xE2700000 ldr r0,=WTCON ldr r1,=0x00 str r1,[r0]
ldr sp,=0x40008000
ldr r0,=0x30 ldr r1,=0x31 ldr r2,=0x32 ldr r3,=0x33
ldr r4,=0x00 str r4,[sp,#-4]!
ldr r4,=0x11000 str r4,[sp,#-4]!
b led
stop: b stop
12.ARM的堆栈 满栈:当堆栈指针SP总是指向最后压入堆栈的数据 空栈:当堆栈指针SP总是指向下一总是指向下一个将要放入数据的空位置 ---ARM采用满栈 升栈:随着数据的入栈, SP指针从低地址->高地址移动 降栈:随着数据的入栈, SP指针从高地址->低地址移动 ---ARM采用降栈! 栈帧:简单的讲,栈帧(stack frame)就是一个函数所使用的那部分栈, 所有函数的栈帧串起来就组成了一个完整的栈。栈帧的两个 边界分别由fp(r11)和sp(r13)来限定。
15.bootloader的GNU汇编模板: .text .global _start _start: b reset ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq
_undefined_instruction: .word undefined_instruction _software_interrupt: .word software_interrupt _prefetch_abort: .word prefetch_abort _data_abort: .word data_abort _not_used: .word not_used _irq: .word irq _fiq: .word fiq
undefined_instruction: nop
software_interrupt: nop
prefetch_abort: nop
data_abort: nop
not_used: nop
irq: nop
fiq: nop
reset: nop
16.烧录miniboot,引导Linux内核启动 <1>从nand/SD正常启动uboot,并且能够使用TFTP服务器。 <2>获取资源 D:\ARM底层\s5pv210\s5pv210_arm\miniboot- s5pv210项目\miniboot\boot.bin D:\ARM底层\s5pv210\s5pv210_arm\miniboot- s5pv210项目\内核镜像和文件系统镜像 目录下的 zImage308和rootfs.cramfs 以上文件均拷贝到/tftpboot <3>启动板卡在uboot的命令模式 烧写bootloader tftp 0x40008000 boot.bin nand erase 0x0 0x100000 nand write 0x40008000 0x0 0x100000
烧写Linux 内核 tftp 0x40008000 zImage308 nand erase 0x100000 0x400000 nand write 0x40008000 0x100000 0x400000
烧写根文件系统 tftp 0x40008000 rootfs.cramfs nand erase 0x400000 0x1400000 nand write 0x40008000 0x400000 0x1400000
浙公网安备 33010602011771号