前面的文章介绍了freertos的一些相关知识,主要包含邮箱,消息队列,任务通知等,这里继续介绍freertos中的比较重要的信号量的内容。
记录一个很小的知识点,串口下载刷芯片的话boot0选择为1,boot1选择为0,然后按下复位键,就可以进入下载模式了,这里复位键比较重要,一定要记得,不然就会报错,报错信息如下:
这里我们已经连好了boot,但是还是会报错,就是因为还没有按下复位键的原因了,这之后我们再次点击连接,就可以看到连接成功了,效果如下所示:
之后我们在打开文件,然后就可以下载了,这里下载也是比较快的
最后一步一定切记,就是要把这个BOOT复原
1、信号量的概念和作用
1、基本概念
信号量的最初目的是为了给共享资源建立一个标志,该标志表示该共享资源被占用情况。这样, 当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
信号量常常用于控制对共享资源的访问和任务同步。信号量相当于在一个有限的资源空间里面能不能得到获取资源的权限,举个例子,就比如某宾馆里面由50间房间,每位房客只有凭借房卡才能进去,同时进去了一个房客(这里假设每间房只能住下一个房客)之后,可用的房间就会少一间。
在上面的例子中,当前的空余房间数量就是一个信号量,住了的房客就相当于信号量的值,如果不巧的话房间全部住满了就说明信号量满了,这个时候只有当房客出去才能空出来,这样就相当于信号量减一,然后又有新的房客住进来,这就相当于信号量加一了。
信号量用于控制共享资源访问的场景相当于一个上锁机制, 代码只有获得了这个锁的钥匙才能够执行。
2、信号量的常用用途
那么信号量的用途是什么呢,为什么要有信号量这样的一个机制呢,上面已经讲到了一点就是当资源不够的时候可以这样来分配信号量,信号量的另一个场合就是任务之间的同步。
具体的就比如当我们单片机 在执行中断服务函数的时候可以通过向任务发送信号量来通知任务它所期待的事件发生了, 当退出中断服务函数以后在任务调度器的调度下同步的任务就会执行。在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函数里面不能放太多的代码,否则的话会影响的中断的实时性。 裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使用 RTOS 系统的时候我们就可以借助信号量完成此功能, 当中断发生的时候就释放信号量,中断服务函数不做具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获取到信号量就说明中断发生了,那么就开始完成相应的处理,这样做的好处就是中断执行时间非常短。 当然任务与任务之间也可以使用信号量来完成同步。
上面提到的都是常规的信号量,FreeRTOS 中还有一些其他特殊类型的信号量,比如互斥信号量和递归互斥信号量,二值信号量,计数型信号量等。
2、二值信号量
上面介绍过了,信号量可以描述资源的占用情况,为描述这种资源的占用的情况可以分为二值和计数两种,那么什么叫二值信号量,因为信号量资源被获取了,信号量值就是 0, 信号量资源被释放,信号量值就是 1, 把这种只有 0和 1 两种情况的信号量称之为二值信号量。
我们首先在下面的图形化配置中添加信号量
这里只要修改个名字就行,没什么特别需要注意的
生成代码之后就可以看到我们生成的信号量了
下面开始配置信号量,在一个任务中使用轮询的方式来检测信号量,信号量的发送有其他事件(这里是串口中断)来产生
源码如下:
if (myBinary01Handle != 0)
{
err = xSemaphoreTake(myBinary01Handle, portMAX_DELAY); //获取信号量
if (err == pdPASS)
{
printf("收到消息\r\n:");
for (int i = 0; i < 8; i++)
printf("%c", RxBuff[i]);
printf("\n");
if (strncmp((char*) RxBuff, "LED1on", 6) == 0) //字符串进行比较,如果完全匹配,就认为收到了
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
else if (strncmp((char*) RxBuff, "LED1off", 6) == 0)
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
else if (strncmp((char*) RxBuff, "LED2on", 6) == 0)
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
else if (strncmp((char*) RxBuff, "LED2off", 6) == 0)
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
else
printf("输入的命令错误\r\n");
}
}
else
osDelay(10);
这里用的串口的方式产生的信号量,串口发送完成,这里是判断0X0A结尾就是结束,就是换行符,发送完成之后就发送信号量
源码如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
RxBuff[Rx_Count++] = RxByte; //每次接收一个字节数据,换行符为0X0D,0X0A,结尾就是0X0A
if ((RxByte == 0x0A) && (myBinary01Handle != 0))
{
xSemaphoreGiveFromISR(myBinary01Handle, NULL); //中断方式发送信号量
printf("成功接收到信号量\r\n");
Rx_Count = 0;
}
if (Rx_Count > 8)
{
printf("命令错误\r\n");
memset(RxBuff, 0, sizeof(RxBuff));
Rx_Count = 0;
}
while (HAL_UART_Receive_IT(&huart1, &RxByte, 1) == HAL_OK)
;
}
因此我们这里要使用串口的话就要在末尾加上换行符,效果如下所示
这里我们首先尝试打开LED的命令,发送下面指令
可以看到LED开启
下面来关闭LED试试,输入下面命令
可以看到LED被关闭
这样就实现了二值信号量了!
3、计数信号量
计数信号量常见用法诸如事件计数还有资源管理,事件计数为:首先产生一个计数信号量(此时信号量被初始化为0)事件处理函数释放信号量之后,计数信号量自增,其他的一些任务接收到信号量,这样信号量计数值就会自减,这样一来,计数信号量的值就是事件发生的数量和事件处理的数量差值。资源管理为,任务必须先获取信号量才能执行,任务获取之后信号量计数值自检,任务完成之后他就又会把信号量归还,这个时候就是自增。
在cubemx中配置如下所示,这里需要先使能这个宏,不然计数信号量那边就是灰色的,无法调用
然后就是这里就可以设定数值,这里信号量的数值设置为8
代码生成之后的可以看到我们生成的信号量如下所示
这里需要用一个周期函数来对按键进行扫描
按键扫面函数还是我们之前讲过的
下面是计数信号量的分配还有获取
源码如下
if(myCountingSem01Handle != NULL)
{
switch(key_flag)
{
case 1:
err = xSemaphoreTake(myCountingSem01Handle, portMAX_DELAY);
if (err == pdFALSE)
printf("获取信号量失败!\r\n");
else {
sem_value = uxSemaphoreGetCount(myCountingSem01Handle);
printf("获取信号量成功,信号量值为 = %d\r\n",
sem_value);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
}
break;
case 2:
err = xSemaphoreGive(myCountingSem01Handle);
if (err == pdFALSE)
printf("发送信号量失败!\r\n");
else {
sem_value = uxSemaphoreGetCount(myCountingSem01Handle);
printf("发送信号量成功,信号量值为 = %d\r\n",
sem_value);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
}
break;
}
}
信号量发布如下所示
信号量接收效果如下所示,最后我们可以看到接收超过了就会失败了