STM32F103 Proteus 仿真 编译用GCC
原理图只要一个MCU就可以了,
双击MCU,编辑固件,选择GCC for ARM,由模板创建默认工程。
会遇到两个错误:
1.STM32 GCC ARM 编译 _STATIC_INLINE 出错,
在第一个出错的位置钱加上 #define __STATIC_INLINE static inline 定义成小写
2.仿真缺少模拟电源网配置错误
在原理图菜单: 设计/电源线配置中 把未连接的网络标号加进右边对应的网络中即可。
基本环境做好后,就可以开始搞了:
1.看一下编译过程,就是工程中的5个C文件,没有汇编。
2. 进入仿真看一下启动,停在中断文件的复位句柄处,再结合ld文件看一下程序入口,
文件vtable.c 定义的函数指针表,在.vectors,在ld文件中是在FLASH内存中,从0x08000000开始。
启动入口就是vtable.c的中断函数表,复位Reset_Handler(void)中调用main(),没有初始化过程。
3.改写main.c进行编程仿真调试。在网上找一些合适的资料做为参考,选用了STM32不完全手册-寄存器篇作为参考。
如果是工作中用,用库函数更方便维护和移植,对业余爱好和初学者,我觉得用寄存器最合适。
4.首先是初始化系统时钟,不过有个问题,PLL倍频似乎不起作用,最终的系统时钟还是在原理图的MCU晶振频率中设置的6M.
5.配置GPIO,配合虚拟示波器,观察IO输出波形,输入要外接上拉电阻,配置的内部上拉没起作用。
6.编写串口,用虚拟终端观察输出。开始不清楚系统时钟,可用数字示波器根据TXD波形和外接激励源波形推算系统时钟。
7.串口增加中断接收。
8.配置通用定时中断。
原理图:
测试程序:
/* Main.c file generated by New Project wizard * * Created: 2023-1-6 * Processor: STM32F103T6 * Compiler: GCC for ARM */ #include <stm32f1xx.h> #define u8 unsigned char #define u16 unsigned short #define u32 unsigned int void delay(int k) { int i; for(i = 0; i<k; i++); } //初始化系统时钟,PLL工作不正常(修改PLL倍频没效果), void clk_init(void) { RCC->CR&=~(1<<24); //关闭PLL后才能配置PLL //用8M晶振作为PLL时钟源,9倍频72M PLLCLK, AHB不分频72M HCLK系统时钟, APB1 2分频 32M低速外设, APB2 不分频 72M高速外设 RCC->CR|=(1<<16); //外部高速时钟使能 HSEON 。 //复位值是83,用内部时钟 while(!(RCC->CR>>17)& 0x1);//等待外部时钟就绪 RCC->CFGR |= (0b100<<8); //APB2[13:11]=DIV1 000不分频; APB1[10:8]=DIV2 100 :2分频; AHB[7:4]=DIV1 000不分频; //复位值是0 RCC->CFGR|=(0b0111<<18); //设置 PLL[21:18] 值 2~16 9倍频 RCC->CFGR|=1<<16; // PLLSRC: HSE 作为 PLL的时钟源 //RCC->CFGR &=~(1<<16); // PLLSRC: HSI/2 作为 PLL的时钟源 //FLASH->ACR|=0x32; //FLASH 2 个延时周期 RCC->CR|=(1<<24); //PLLON[24] 打开PLL while(!(RCC->CR>>25));//等待 PLL 锁定 RCC->CFGR|=0x00000002;//SW[1:0] 10 PLL 作为系统时钟 while((RCC->CFGR & 0x0000000C) !=0x00000008); // 等待系统时钟状态为 PLL。 SWS[3:2] 10 PLL作为系统时钟设置成功 } //SCB->AIRCR 优先级寄存器,AIRCR[10:8] 配置分组 ,由分组确定NVIC->IP[240][7:4] 的5种方式的优先级配置 //分组在设置时,没有涉及中断号,应该是全局的,在系统初始化时配置比较合适,单个中断不需要配置分组 int XGZ_NVIC_PriorityGroupConfig(u8 NVIC_Group) { u32 temp; temp = SCB->AIRCR;//读取先前的设置 temp&=0X0000F8FF; //清空先前分组 switch(NVIC_Group) { case 0: temp|= (0b111<<8); break; case 1: temp|= (0b110<<8); break; case 2: temp|= (0b101<<8); break; case 3: temp|= (0b100<<8); break; case 4: temp|= (0b011<<8); break; default: return -1; //组号不能大于4 break; } temp|=0X05FA0000; //写入钥匙 SCB->AIRCR=temp; //设置分组 return 1; } //抢占优先级,响应优先级, 中断号, void XGZ_NVIC_Init(u8 NVIC_PreemptionPriority,u8 NVIC_SubPriority,u8 NVIC_Channel) { u32 temp; temp = SCB->AIRCR;//读取先前的设置 temp&=0X00000700; //读取先前分组 NVIC->IP[NVIC_Channel]&=0x0F; //高4位清0 switch(temp) { case 0X00000700: //0 NVIC->IP[NVIC_Channel]|= (NVIC_SubPriority&0xf)<<4; break; case 0X00000600: //1 NVIC->IP[NVIC_Channel]|= (NVIC_PreemptionPriority&0x1)<<7; NVIC->IP[NVIC_Channel]|= (NVIC_SubPriority&0x7)<<4; break; case 0X00000500: //2 NVIC->IP[NVIC_Channel]|= (NVIC_PreemptionPriority&0x3)<<6; NVIC->IP[NVIC_Channel]|= (NVIC_SubPriority&0x3)<<4; break; case 0X00000400: //3 NVIC->IP[NVIC_Channel]|= (NVIC_PreemptionPriority&0x7)<<5; NVIC->IP[NVIC_Channel]|= (NVIC_SubPriority&0x1)<<4; break; case 0X00000300: //4 NVIC->IP[NVIC_Channel]|= (NVIC_PreemptionPriority&0xF)<<4; break; default: return; //会不会没有分组号 break; } NVIC->ISER[NVIC_Channel/32]|=(1<<NVIC_Channel%32); //中断使能,ISER[8] 每一位是一个中断,103的头文件中42个中断 //NVIC->ICER[NVIC_Channel/32]|=(1<<NVIC_Channel%32); //中断清除使能,因为寄存器是写1有效 //NVIC->ISPR[NVIC_Channel/32]|=(1<<NVIC_Channel%32); //中断挂起, //NVIC->ICPR[NVIC_Channel/32]|=(1<<NVIC_Channel%32); //中断清除挂起, //NVIC->IABR[NVIC_Channel/32] == 1;//中断激活标志,只读,表示中断正在执行 } void USART1_IRQHandler(void) { unsigned char res; res=USART1->DR; USART1->DR=res; while((USART1->SR&0X40)==0);//等待发送结束 } //初始化串口 void uart_init(int bound) { int pclk2 = 6000000; //怎么配置都是HSI float temp; unsigned short mantissa; unsigned short fraction; temp=(float) pclk2 / (bound*16);//得到 USARTDIV mantissa=temp; //得到整数部分 fraction=(temp-mantissa)*16; //得到小数部分 mantissa<<=4; mantissa+=fraction; RCC->APB2ENR|=1<<2; //使能 PORTA 口时钟 RCC->APB2ENR|=1<<14; //使能串口时钟 GPIOA->CRH&=0XFFFFF00F; //IO 状态设置 GPIOA->CRH|=0X000008B0; //IO 状态设置 RCC->APB2RSTR|=1<<14; //复位串口 1 RCC->APB2RSTR&=~(1<<14);//停止复位 //波特率设置 USART1->BRR=mantissa; // 波特率设置 USART1->CR1|=0X200C; //1 位停止,无校验位. USART1->CR1|=1<<8; //PE 中断使能 USART1->CR1|=1<<5; //接收缓冲区非空中断使能 XGZ_NVIC_Init(3,3,USART1_IRQn);// 抢占3,响应3, } void send(char c) { USART1->DR=c; while((USART1->SR&0X40)==0);//等待发送结束 } void sendstring(char* s) { while(*s) { send(*s++); } } int t3cnt=0; void TIM3_IRQHandler(void) { if(TIM3->SR&0X0001) { //send('T'); t3cnt++; if(t3cnt>1) { GPIOA->ODR|=1<<8; t3cnt = 0; } else { GPIOA->ODR&=~(1<<8); } } TIM3->SR&=~(1<<0);//清除中断标志位 } void TIM3_Int_Init(u16 arr,u16 psc) { RCC->APB1ENR|=1<<1; //TIM3 时钟使能 TIM3->ARR=arr; //设定计数器自动重装值 TIM3->PSC=psc; //预分频器 TIM3->DIER|=0x01; //允许更新中断 //TIM3->CR1&=~((1<<4)); //向上和下计数,应该都是0 和 装载值之差 TIM3->CR1|=(1<<7)|(1<<0); //自动装载?, 使能定时器 3, XGZ_NVIC_Init(1,3,TIM3_IRQn);//抢占 1,子优先级 3, } void gpio_init(void) { RCC->APB2ENR |= (0b01<<2); RCC->APB2ENR |= (0b01<<4); //PP50M 输出 0011; 上拉输入1000 GPIOA->CRL &= 0xFFFF00FF; //PB2 4 清除原配置 GPIOA->CRL |= 0x00003800; //PB2 上拉输入 PB3 PP50M 出 GPIOA->ODR|=1<<2; GPIOA->CRH &= 0xFFFFFFF0; GPIOA->CRH |= 0x00000003; } int main (void) { //初始化系统时钟,则上电默认HSI作为系统时钟 //SystemInit (); clk_init(); //PLL 不正常,仿真不必配置 XGZ_NVIC_PriorityGroupConfig(2); //为系统配置中断分组2 gpio_init(); uart_init(9600); sendstring("This is a test 123\r\n"); TIM3_Int_Init(1000, 6-1); // 1k: 6M/2 x2 APB1 2分频 , TMR时钟源是分频APB的2倍,ABP1不分频的话直接用 char data = 0; while (1) { if(!(GPIOA->IDR&0x04)) //好像必须外部上拉 { send('K'); } delay(10000); } return 0; }
运行效果
调试界面: