int main (void)
{
while(1)
{
sing();
dance();
play();
}
}
sing
执行的时间比较长的话,函数dance
就不能很快的被执行。任何一个函数死掉的话就会影响整个系统。在使用 51、AVR、STM32 单片机裸机的时候一般都是在main
函数里面用while(1)
做一个大循环来完成所有的处理,即应用程序是一个无限的循环,循环中调用相应的函数完成所需的处理。有时候我们也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环while(1)
作为后台程序。
对应的编程代码大概是这样的:
void EXTI_IRQHandler()
{
flag = 1;
}
int main (void)
{
while(1)
{
if (flag = 1)
{
do_something();
flag = 0;
}
}
}
有什么问题?
前后台系统的实时性差,前后台系统各个任务(应用程序)都是排队等着轮流执行,不管你这个程序现在有多紧急,没轮到你就只能等着!相当于所有任务(应用程序)的优先级都是一样的。但是前后台系统简单啊,资源消耗也少啊!在稍微大一点的嵌入式应用中前后台系统就明显力不从心了。
我们有过51 32等的单片机芯片的学习经历,很自然的直到硬件的底层控制来自于程序对寄存器的操作,也就意味着寄存器是软件和硬件之间交流的桥梁。
通过学习了像stm32,gd32等芯片,我们知道了稍微高级一点的芯片包含的寄存器实在是太多太复杂了,原因是包含的片下外设太多太复杂了,所以官方为了简化我们开发使用的难度,于是就有了标准库HAL库,LL库等库函数,这些库函数也叫做驱动函数,是直接驱动底层寄存器的操作的。
所以我们可以把这一层叫做驱动层。
应用层就是基于驱动层的函数接口,根据我们使用者的需求从而开发的。这一层需要开发者自己开发。比如实现I2C_SOFT时序的实现,我们可以调用DRIVER库中的GPIO驱动接口。
//置位与清零SCL管脚
#define SET_SCL HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_SET)
#define CLR_SCL HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_RESET)
//置位与清零SDA管脚
#define SET_SDA HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_SET)
#define CLR_SDA HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_RESET)
//读SDA管脚状态
#define READ_SDA HAL_GPIO_ReadPin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin)
static void Start(void)
{
//SCL为高电平,SDA的下降沿为I2C起始信号
SET_SDA;
SET_SCL;
I2C_Delay_us(1);
CLR_SDA;
I2C_Delay_us(10);
CLR_SCL;
I2C_Delay_us(1);
}
static ACK_Value_t Write_Byte(uint8_t WR_Byte)
{
uint8_t i;
ACK_Value_t ACK_Rspond;
//SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
//数据按8位传输,高位在前,利用for循环逐个接收
for(i=0;i<8;i++)
{
//SCL清零,主机SDA准备数据
CLR_SCL;
I2C_Delay_us(1);
if((WR_Byte&BIT7) == BIT7)
{
SET_SDA;
}
else
{
CLR_SDA;
}
I2C_Delay_us(1);
//SCL置高,传输数据
SET_SCL;
I2C_Delay_us(10);
//准备发送下一比特位
WR_Byte <<= 1;
}
CLR_SCL;
//释放SDA,等待从机应答
SET_SDA;
I2C_Delay_us(1);
SET_SCL;
I2C_Delay_us(10);
ACK_Rspond = (ACK_Value_t)READ_SDA;
CLR_SCL;
I2C_Delay_us(1);
//返回从机的应答信号
return ACK_Rspond;
}
/*
* @name Write_Byte
* @brief I2C写字节
* @param ACK_Value -> 主机回应值
* @retval 从机返回值
*/
static uint8_t Read_Byte(ACK_Value_t ACK_Value)
{
uint8_t RD_Byte = 0,i;
接收数据
//SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
//数据按8位传输,高位在前,利用for循环逐个接收
for(i=0;i<8;i++)
{
//准备接收下一比特位
RD_Byte <<= 1;
//SCL清零,从机SDA准备数据
CLR_SCL;
I2C_Delay_us(10);
//SCL置高,获取数据
SET_SCL;
I2C_Delay_us(10);
RD_Byte |= READ_SDA;
}
//SCL清零,主机准备应答信号
CLR_SCL;
I2C_Delay_us(1);
//主机发送应答信号
if(ACK_Value == ACK)
{
CLR_SDA;
}
else
{
SET_SDA;
}
I2C_Delay_us(1);
SET_SCL;
I2C_Delay_us(10);
//Note:
//释放SDA数据线
//SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号
CLR_SCL;
SET_SDA;
I2C_Delay_us(1);
//返回数据
return RD_Byte;
}
如上的两段代码,我们可以看到用宏定义的形式重命名了底层GPIO驱动函数, 然后在我们的逻辑代码中添加。从而实现时序层接口的完成 。
我们想的是通过在主函数调用应用层的函数接口,从而直接获取温度。所以我们只需要在SHT30的读取温度函数里面直接调用I2C数据读取
static ACK_Value_t Write_Byte(uint8_t WR_Byte)
从而获取数据,并根据手册的数据处理要求,对这个数据进行处理得出最终温度值