Keil开发STM32单片机项目的三种方式

    STM32单片机相比51单片机,内部结构复杂很多,因此直接对底层寄存器编码,相对复杂,这个需要我们了解芯片手册,对于复杂项目,这些操作可能需要反复编写,因此出现了标准库的方式,对寄存器操作进行了封装,操作相对简单。随着项目复杂度提升,又出现了封装更厉害的一种库HAL,这个需要借助STM32CubeMx工具来生成代码。

    简单来说,STM32编码有三种方式:

    1、寄存器编码。

    2、标准库操作编码。

    3、HAL库操作编码。

    这三种方式各有优劣势,寄存器编码更偏向底层,方便我们熟悉单片机内部结构,开发效率相对较低,因为很多初始化,赋值操作需要反复编写,使用HAL库,可以减少大量代码,提高了开发效率,但是不便于理解底层逻辑,需要非常熟练STM32单片机工作逻辑之后才好上手。

    第一种寄存器编码方式,构建项目可以直接新建一个普通项目,如下所示:

 

    项目新建之后,什么也没有,我们需要手动添加一个启动文件和一个main.c文件到工程目录:

 

    我们在keil工具里面,通过添加现有项的方式将这两个文件加到Source Group 1 中,如下所示:

     这个时候,编译会报错:

    说是SystemInit符号未定义,其实是我们构建的项目代码里面(main.c)中缺少SystemInit()方法,添加上就可以了。

    这个项目只有两个文件,一个是启动文件,一个是主程序main.c。我们操作寄存器的代码就在main.c中编写,这里给出一个简单的实现LED灯闪烁的示例代码:

#define PERIPH_BASE ((unsigned int)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE+ 0x10000)
#define GPIOA_BASE (APB2PERIPH_BASE+0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE+0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE+0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE+0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE+0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE+0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE+0x2000)

#define GPIOA_ODR_Addr (GPIOA_BASE+12)
#define GPIOB_ODR_Addr (GPIOB_BASE+12)
#define GPIOC_ODR_Addr (GPIOC_BASE+12)
#define GPIOD_ODR_Addr (GPIOD_BASE+12)
#define GPIOE_ODR_Addr (GPIOE_BASE+12)
#define GPIOF_ODR_Addr (GPIOF_BASE+12)
#define GPIOG_ODR_Addr (GPIOG_BASE+12)

#define BITBAND(addr,bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr&0xFFFFF)<<5) + (bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define LED0 MEM_ADDR(BITBAND(GPIOA_ODR_Addr, 5))


typedef struct
{
  volatile unsigned int CR;
	volatile unsigned int CFGR;
	volatile unsigned int CIR;
	volatile unsigned int APB2RSTR;
	volatile unsigned int APB1RSTR;
	volatile unsigned int AHBENR;
	volatile unsigned int APB2ENR;
	volatile unsigned int APB1ENR;
	volatile unsigned int BDCR;
	volatile unsigned int CSR;
}RCC_TypeDef;

#define RCC ((RCC_TypeDef*)0x40021000)

typedef struct
{
	volatile unsigned int CRL;
	volatile unsigned int CRH;
	volatile unsigned int IDR;
	volatile unsigned int ODR;
	volatile unsigned int BSRR;
	volatile unsigned int BRR;
	volatile unsigned int LCKR;
}GPIO_TypeDef;

#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)

void LEDInit(void)
{
	RCC->APB2ENR|=1<<2;
	GPIOA->CRL &= 0xFF0FFFFF;
	GPIOA->CRL |= 0x00300000;
}

void Delay_ms(volatile unsigned int t)
{
  unsigned int i,j;
	for(j=0;j<t;j++)
	  for(i=0;i<800;i++);
}

void SystemInit(void)
{

}

int main(void)
{
	LEDInit();
	while(1)
	{
	  LED0 = 0;
		Delay_ms(500);
		LED0 = 1;
		Delay_ms(500);
	}
}


    项目构建成功,不报错就可以进行仿真,或者下载到单片机调试。

    第二种方式,直接在Keil工具中构建STM32标准库工程,不用额外拷贝标准库文件到项目文件夹,然后添加现有项的方式加入组中,构建项目,选择芯片系列之后,在弹出确认框这里可以选择需要的库:

    这里勾选CMSIS->CORE, Device->Startup , Device->StdPeriph Drivers->Framework,GPIO,RCC几项。

    自动生成的代码结构如下所示:

    这里面除了main.c是手动添加的,其余的都是通过keil自动生成的,如果你做过手动添加标准库,那么就会很熟悉这里面的一些文件,stm32f10x_gpio.c,stm32f10x_rcc.c,startup_stm32f10x_ld.s,system_stm32f10x.c。

    这里直接编译会报错:

    Error: L6218E: Undefined symbol assert_param (referred from misc.o).

    这个问题是找不到assert_param这个函数,而这个函数是在stm32f10x_conf.h中,从上面的工程结构,我们看到代码里面是有stm32f10x_conf.h这个头文件的。解决办法就是使用宏定义USE_STDPERIPH_DRIVER查找:

    点击魔法棒工具Options for Targets,在弹出框中选择c/c++,Define处输入USE_STDPERIPH_DRIVER,Include Paths指定当前项目路径下的RTE目录即可。

    最后可以编译成功。

    这里也给出一段让LED闪烁的代码。

#include "stm32f10x.h"

void Delay()
{
  unsigned int i,j;
	for(i=0;i<1000;i++)
	  for(j=0;j<1000;j++);
}

void LED_Config()
{
    GPIO_InitTypeDef GPIO_InitStructure;                 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;      
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;             
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

int main(void)
{
	LED_Config();
	while(1)
	{
	    GPIO_SetBits(GPIOA, GPIO_Pin_5);
		Delay();
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
		Delay();
	}
}

    PA5口作为电平输出, 时钟使能与GPIO初始化,都调用标准库中的方法。

    第三种构建STM32工程的方法需要借助STM32CubeMx工具,这个是免费的,安装之后,可以图形化界面操作,如下所示:

    1)打开工具之后,新建工程,来到选择芯片界面:

    这里在Commecial Part Number这里输入STM32F103会自动补全C6A,选中右侧面板中的一个芯片双击,

   2)进入配置界面。

    展开System Core菜单,默认选中SYS,我们点击,在中间模式这里选择Debug: Serial Wire。

    然后,选中RCC

    设置HSE/LSE为Crystal/Ceramic Resonator。改变之后,右侧芯片会有变化。

    接着,还要将芯片上的PA5端口作为GPIO_Output,点击PA5,就会出现菜单选项,直接选择即可。 

   3)工程位置及编译工具设置

    设置工程名,工程位置,Toolchain/IDE选择MDK-ARM,版本选择V5。

    4)代码生成。点击GENERATE CODE按钮。

    5)生成的代码,可以直接用Keil打开,结构如下:

 

    连main.c都写好了,直接编译也没有问题。我们要实现LED闪烁,只需要在while(1)循环体中增加如下代码:

  while (1)
  {
    /* USER CODE END WHILE */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
	HAL_Delay(200);
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
	HAL_Delay(200);
    /* USER CODE BEGIN 3 */
  }

    这种方式生成的代码,不仅帮我们做了很多工作,甚至连编译准备都做好了,我们看看Options for Targets的c/c++选项:

 

   宏定义查找设置了,不用我们手动设置,另外编译生成hex文件,也同样帮我们勾选好了。

   使用HAL库,自己编写的代码很少,但是理解起来需要花时间,默认操作做了什么,这里面隐含了哪些操作。   

  • 7
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
解耦控制器是一种常用的控制器设计方法,可以将多输入多输出系统分解为多个互相独立的单输入单输出系统进行控制,从而简化系统的控制问题。 在 keil 软件中实现解耦控制器需要进行以下步骤: 1. 定义控制系统的状态空间模型,包括状态变量、输入变量和输出变量。 2. 根据系统的状态空间模型,设计解耦控制器的状态反馈矩阵和输出反馈矩阵。 3. 将状态反馈矩阵和输出反馈矩阵转换为 keil 软件中的矩阵形式。 4. 在 keil 软件中编写控制程序,包括状态观测器、状态反馈控制器和输出反馈控制器等模块。 5. 将程序下载到 stm32 单片机上,并进行实验验证。 具体实现过程可以参考以下代码: ```c #include <stm32f10x.h> #include <math.h> #define PI 3.1415926 /* 定义状态变量 */ float x[2] = {0.0f, 0.0f}; float y[2] = {0.0f, 0.0f}; /* 定义输入变量 */ float u[2] = {0.0f, 0.0f}; /* 定义输出变量 */ float y1 = 0.0f; float y2 = 0.0f; /* 定义状态反馈矩阵 */ float K[2][2] = {{0.0f, 0.0f}, {0.0f, 0.0f}}; /* 定义输出反馈矩阵 */ float L[2] = {0.0f, 0.0f}; /* 定义控制器参数 */ float alpha = 0.5f; float beta = 0.5f; /* 定义控制器周期 */ float T = 0.01f; /* 定义控制器输入 */ float r1 = 0.0f; float r2 = 0.0f; /* 状态观测器 */ void state_observer(float y1, float y2, float u1, float u2) { x[0] = alpha * x[0] + beta * (y1 - L[0] * x[1] - L[1] * y2 + K[0][0] * u1 + K[0][1] * u2); x[1] = alpha * x[1] + beta * (y2 - L[1] * x[0] - L[0] * y1 + K[1][0] * u1 + K[1][1] * u2); } /* 状态反馈控制器 */ void state_feedback_controller(float r1, float r2) { u[0] = -K[0][0] * x[0] - K[0][1] * x[1] + r1; u[1] = -K[1][0] * x[0] - K[1][1] * x[1] + r2; } /* 输出反馈控制器 */ void output_feedback_controller(float y1, float y2) { y1 = L[0] * x[0] + L[1] * x[1]; y2 = L[1] * x[0] + L[0] * x[1]; } int main(void) { /* 初始化控制器 */ K[0][0] = 1.0f; K[0][1] = 0.0f; K[1][0] = 0.0f; K[1][1] = 1.0f; L[0] = 1.0f; L[1] = 1.0f; /* 循环控制 */ while(1) { /* 获取控制器输入 */ r1 = sin(2 * PI * T); r2 = cos(2 * PI * T); /* 获取控制器输出 */ output_feedback_controller(y1, y2); /* 获取系统状态 */ state_observer(y1, y2, u[0], u[1]); /* 进行状态反馈控制 */ state_feedback_controller(r1, r2); /* 延时控制器周期 */ for(uint32_t i=0; i<10000; i++); } } ``` 注意:以上代码仅为示例代码,实际应用中需要根据具体系统进行修改和优化,以满足系统要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luffy5459

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值