以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,替换固件层,替换硬件层几个库函数即可,其他层不需要动。