单片机/嵌入式软件架构分层思想

以STM32裸机开发为例。

软件分层
应用层
驱动层
硬件层
固件层

①最底层为固件层,Firmware
这一层通常是官方给的库,库函数对寄存器进行操作,例如:

/**
  * @brief  Transmits a Data through the SPIx/I2Sx peripheral.
  * @param  SPIx: where x can be
  *   - 1, 2 or 3 in SPI mode 
  *   - 2 or 3 in I2S mode
  * @param  Data : Data to be transmitted.
  * @retval None
  */
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
{
  /* Check the parameters */
  assert_param(IS_SPI_ALL_PERIPH(SPIx));
  
  /* Write in the DR register the data to be sent */
  SPIx->DR = Data;
}

②往上一层为硬件层,Hardware。
这一层的函数是在库函数的基础上进一步封装,比如根据STM32 SPI的特性,SPI读写一个数据我们封装为一个函数,例如:

u8 SPI2_ReadWriteByte(u8 TxData)
{		
	u8 retry = 0;	
	
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
	{
		retry++;
		if(retry > 200)return 0;
	}			  
	SPI_I2S_SendData(SPI2, TxData); 
	retry = 0;

	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) 
	{
		retry++;
		if(retry > 200)return 0;
	}	  						    
	return SPI_I2S_ReceiveData(SPI2);					    
}

这个函数的也只读写送一个数据,我们通常不会只读写一个数据,而是连续读写送N个数据,所以在这个函数的基础上再封装出一个函数,例如:

void SPI2_ReadWriteData(u8 *sendData, u8 *recvData, u32 length)
{
	u32 i = 0;

	for(i = 0; i < length; i++)
	{
		recvData[i] = SPI2_ReadWriteByte(sendData[i]);
	}
}

③再往上一层是驱动层,drive。
前面两层都是对STM32进行操作,而这一层是对与STM32连接的元器件、模块、模组进行操作,通常称为调试驱动,比如调试STM32和外部FLASH W25Q128的SPI驱动。
这一层的函数通常是对W25Q128进行操作,例如:

u16 W25QXX_ReadID(void)
{
	u16 Temp = 0;	  
	W25QXX_CS = 0;				    
	SPI2_ReadWriteByte(0x90);	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	 			   
	Temp |= SPI2_ReadWriteByte(0xFF) << 8;  
	Temp |= SPI2_ReadWriteByte(0xFF);	 
	W25QXX_CS = 1;				    
	return Temp;
}

④再往上一层是应用层,APP。
有很多人往往在main.c里面做功能应用,导致main.c内容又多又长,main函数又长又臭,大多数公司的代码规范要求是不允许的。最好把功能应用剥离出来,单独放在应用层,让main函数简洁清晰。
例如:

int main(void)
{   
    STM32_Sys_Init();
    
    W25Q128_Init();//如果这些初始化超过3个,就要考虑封装成一个函数

	UART_Init();//如果这些初始化超过3个,就要考虑封装成一个函数
    
	while(1)
	{
        switch(g_main_process)
        {
            case MAIN_UART_RECV_HANDLE:
                //APP_func1();
                break;
            
            case MAIN_XXX_HANDLE:
				//APP_func2();
				break;
				
            //.............
            
			default:
				//APP_funcN();
                break;
        }
	}
}

总结:
用分层思想不仅提高代码阅读性(方便新员工接手辞职员工代码,而不是一团糟让人一头雾水),还提高可移植性,如果项目需要换一颗MCU,替换固件层,替换硬件层几个库函数即可,其他层不需要动。

  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Dr_Haven

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

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

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

打赏作者

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

抵扣说明:

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

余额充值