Proteus内部编程仿真STM32:实现轻松的代码调试
目录
六、完整代码
七、总结
一、背景
想在没有开发板的基础上形象逼真仿真基于STM32的设计,除了Proteus无出之右了吧,但目前没有很好地指导直接在Proteus中C编程来仿真STM32设计的帖子供参考,绝大部分的帖子还是借助于Keil MDK或者STM32CubeMX之类的工具,编译成HEX文件之后导入Proteus仿真,程序需要修改时,就得来回切换,反复修改程序、编译程序、导入HEX文件,调试起来非常不方便,也没有充分利用Proteus的优势,费时费力,降低调试效率。
二、STM32开发简介
STM32开发可以分别基于HAL库、库函数或寄存器编程来进行,而基于Proteus链接的Keil for ARM编译器生成的代码(如图1),相比HAL和库函数不具有优势,但HAL(硬件抽象层)和库函数也无非是寄存器软件化,使软件编程更方便,可读性更强,但基于寄存器编程也有代码简略,可以清晰了解编程意图的优点,下面我们以寄存器编程为主导举一个例子来介绍一下在Proteus内部C语言编程仿真STM32设计的实际应用。
图1 Proteus Keil for ARM编译器生成的代码
三、STM32实例简述
选用STM32F103T6芯片,利用PA8/PB2的推挽输出以1秒为周期交替点亮熄灭白色LED灯,系统时钟采用PLL锁相环9倍频8MHz的HSE外部晶振源来获得,电路原理如下图2,Keil for ARM编译器的设置,可以参考Keil for C51的指导设置,详情请参看下面我之前的帖子,链接如下:
Proteus和Keil C51联调仿真完整解析(附程序)_宝玉飞的博客-CSDN博客
图2 STM32F103T6电路原理图
四、Proteus内部寄存器C语言编程
1、时钟函数Clock_Init()
熟悉一个芯片先从时钟开始,这话一点都不假,我们的设置也是先从时钟入手,分别使能内外时钟并等待就绪,APB1二分频,APB2和AHB不分频,PLL对外部时钟HSE先9倍频再赋给系统时钟,代码如图3:
图3 时钟设置
2、GPIO函数 LED_Init()
先使能GPIO时钟,初始化PA8/PB2之后,设置为推挽逻辑高初始输出。代码如图4:
图4 GPIO设置
3、延时函数 delay_nms()
外部晶振源是8MHz,9倍频后就是72MHz,延时0.5秒的话,需要delay_nms(250)。代码如图5:
图5 延时函数
4、主函数main()
调用Clock时钟和GPIO初始化函数,然后以1秒为周期循环点亮和熄灭LED灯。代码如图6:
图6 主函数
5、宏定义
紧跟头文件定义了GPIO操作的宏定义,位带操作可以像C51对GPIO进行位操作。代码如图7:
图7 GPIO宏定义
五、仿真结果图示
图8的仿真结果借助了GIF图,实际亮灭切换没这么快。
图8 仿真结果
六、完整代码
几乎每行代码都给出了注释,方便快速理解。
/* Main.c file generated by liyufei
*
* Created: 周六 4月 9 2022
* Processor: STM32F103T6
* Compiler: Keil for ARM
*/
#include <stm32f103x6.h>
#include <stm32f1xx.h>
//IO口操作宏定义,位带操作,实现51类似的GPIO控制功能
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //PA输出
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //PB输出
#define LED0 PAout(8) // PA8
#define LED1 PBout(2) // PB2
//时钟设置
//RCC_CR_HSION,RCC_CR_HSEON,RCC_CR_HSERDY,RCC_CFGR_PLLSRC等在头文件stm32f103x6.h有定义
void Clock_Init(unsigned char PLL)
{
unsigned char temp_value = 0;
RCC->CR|=RCC_CR_HSION; //使能内部高速时钟HSION
while(!(RCC_CR_HSIRDY>>1)); //等待PLL锁定
RCC->CR|=RCC_CR_HSEON; //外部高速时钟使能HSEON
while(!(RCC_CR_HSERDY>>17)); //等待外部时钟就绪
RCC->CFGR=0x00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
RCC->CFGR|=(PLL-2)<<18; //设置 PLL值 2~16,本例为9倍频
RCC->CFGR|=RCC_CFGR_PLLSRC; //PLLSRC选择外部时钟HSE为PLL输入源
RCC->CR|=RCC_CR_PLLON; //使能PLL
while(!(RCC_CR_PLLRDY>>25)); //等待 PLL锁定
RCC->CFGR|=RCC_CFGR_SW_PLL; //PLL作为系统时钟
while(temp_value!=RCC_CFGR_SWS_PLL) //等待PLL作为系统时钟设置成功
{
temp_value=RCC->CFGR;
temp_value&=0x0C;
}
}
//毫秒延时
void delay_nms(unsigned int time)
{
unsigned int i=0;
while(time--)
{
i=12000; // 250x12000 = 36000000,每隔0.5秒LED灯反转
while(i--) ;
}
}
//为LED亮灭进行GPIO口初始化
void LED_Init(void)
{
RCC->APB2ENR|= RCC_APB2ENR_IOPAEN; //使能 PORTA时钟
RCC->APB2ENR|= RCC_APB2ENR_IOPBEN; //使能 PORTB时钟
GPIOA->CRH&=0XFFFFFFF0; //初始化PA8
GPIOA->CRH|=0X00000003; //PA8 推挽输出
GPIOA->ODR|=1<<8; //PA8 初始输出高
GPIOB->CRL&=0XFFFFF0FF; //初始化PB2
GPIOB->CRL|=0X00000300; //PB2 推挽输出
GPIOB->ODR|=1<<2; //PB2 初始输出高
}
//main函数
int main (void)
{
Clock_Init(9); //设PLL为系统时钟,频率为HSE外部时钟8MHz的9倍
LED_Init(); //GPIO初始化
while (1)
{
LED0 = 0; //PA8输出0
LED1 = 1; //PB2输出1
delay_nms(250); //延时0.5S
LED0 = 1; //PA8输出1
LED1 = 0; //PB2输出0
delay_nms(250); //延时0.5S
}
}
七、总结
上面例子的仿真成功,至少说明利用Proteus内部C语言编程进行STM32的设计仿真是行得通的,STM32的仿真有别路可选。