Este artigo refere-se ao [Wildfire EmbedFire] "Implementação do kernel RT-Thread e desenvolvimento de aplicativos - com base no STM32", que é usado apenas como uma nota de estudo pessoal. Para obter conteúdo e etapas mais detalhados, verifique o texto original (você pode baixá-lo no Centro de download de dados do Wildfire)
Diretório de artigos
Conceitos Básicos de Eventos
Eventos são usados principalmente para sincronização entre threads, diferentemente dos semáforos, é caracterizado por poder realizar sincronização um-para-muitos e muitos-para-muitos. Ou seja, uma thread pode esperar pelo acionamento de vários eventos: pode ser qualquer um dos eventos para ativá-la para processamento de eventos; também pode ativá-la para processamento subsequente após a chegada de vários eventos; da mesma forma, eventos podem também ser múltiplo A thread sincroniza múltiplos eventos.Esta coleção de múltiplos eventos pode ser representada por uma variável inteira sem sinal de 32 bits.Cada bit da variável representa um evento.Os eventos são associados para formar um conjunto de eventos. O "OR lógico" dos eventos também é chamado de sincronização independente, o que significa que a thread está sincronizada com qualquer um dos eventos; o "AND lógico" dos eventos também é chamado de sincronização associativa, o que significa que a thread está sincronizada com vários eventos .
——Manual Chinês Oficial RT-Thread
Cenários de aplicativos de eventos
Eventos são usados principalmente para sincronização e não podem ser usados para transmissão de dados, então podemos usar eventos como sinalizadores. Em sistemas bare metal, geralmente usamos variáveis globais como bits de sinalização, mas em sistemas operacionais em tempo real, o uso de sinalizadores globais pode facilmente causar problemas como baixa legibilidade do programa e gerenciamento de código difícil.
Eventos são usados mais amplamente do que semáforos, que podem realizar controle de um para um, de um para muitos e de muitos para muitos.
Como funcionam os eventos
Na implementação de RT-Thread, cada thread possui um sinalizador de informação de evento, que possui três atributos, a saber
RT_EVENT_FLAG_AND
(lógico AND),RT_EVENT_FLAG_OR
(lógico OR) eRT_EVENT_FLAG_CLEAR
(clear sinalizador). Quando o encadeamento aguarda a sincronização do evento, ele pode ser avaliado se o evento recebido atualmente satisfaz o ajuste de sincronização por meio de 32 sinalizadores de evento e esse sinalizador de informações do evento.
Conforme mostrado no diagrama de trabalho do evento acima, o segundo e o 29º bits no sinalizador de evento do thread # 1 são definidos. Se o bit do sinalizador de informações do evento estiver definido como AND lógico, isso significa que o thread # 1 não será ativado até que ambos os eventos 1 e o evento 29 ocorrer. será acionado para despertar Se o bit de sinalizador de informações do evento estiver definido como OR lógico, o evento 1 ou o evento 29 acionará o thread de despertar #1.
Se o sinalizador de informações definir o bit de sinalizador de limpeza ao mesmo tempo, quando o thread #1 for ativado, ele tomará a iniciativa de definir o evento 1 e o evento 29 como 0, caso contrário, o sinalizador de evento ainda existirá (para 1).——Manual Chinês Oficial RT-Thread
bloco de controle de eventos
O bloco de controle de eventos tem apenas um membro principal, o conjunto de eventos de 32 bits.
struct rt_event
{
struct rt_ipc_object parent; /**< 继承自ipc_object类 */
rt_uint32_t set; /**< 事件集合 */
};
interface de função de evento
Criar evento rt_event_create()
rt_event_t
rt_event_create
(const char* nome, rt_uint8_t sinalizador);
Ao chamar essa interface de função, o sistema alocará o objeto de evento do heap de memória dinâmica, inicializará o objeto, inicializará o objeto IPC e definirá o conjunto como 0. Para objetos IPC criados com o sinalizador de prioridade RT_IPC_FLAG_PRIO, quando vários encadeamentos estão aguardando recursos, o encadeamento com maior prioridade obterá os recursos primeiro. O objeto IPC criado usando o sinalizador FIFO RT_IPC_FLAG_FIFO obterá recursos por ordem de chegada quando vários encadeamentos estiverem aguardando recursos.
parâmetro | descrever |
---|---|
nome | O nome do evento |
bandeira | sinal de evento |
excluir evento rt_event_delete()
rt_err_t
rt_event_delete
(evento rt_event_t);
Ao chamar a função rt_event_delete para excluir um objeto de evento, você deve garantir que o evento não seja mais usado. Antes da exclusão, todos os threads suspensos no evento serão despertados (o valor de retorno do thread é -RT_ERROR) e, em seguida, o bloco de memória ocupado pelo objeto do evento será liberado.
parâmetro | descrever |
---|---|
evento | o identificador para o objeto de evento |
Inicialize o evento rt_event_init()
rt_err_t
rt_event_init
(evento rt_event_t, const char* nome, sinalizador rt_uint8_t);
Ao chamar essa interface, você precisa especificar o identificador do objeto de evento estático (ou seja, o ponteiro para o bloco de controle de evento) e, em seguida, o sistema inicializará o objeto de evento e o adicionará ao contêiner de objetos do sistema para gerenciamento. Para objetos IPC criados com o sinalizador de prioridade RT_IPC_FLAG_PRIO, quando vários encadeamentos estão aguardando recursos, o encadeamento com maior prioridade obterá os recursos primeiro. O objeto IPC criado usando o sinalizador FIFO RT_IPC_FLAG_FIFO obterá recursos por ordem de chegada quando vários encadeamentos estiverem aguardando recursos.
parâmetro | descrever |
---|---|
evento | o identificador para o objeto de evento |
nome | O nome do evento |
bandeira | sinal de evento |
Evento recebendo rt_event_recv()
rt_err_t
rt_event_recv
(evento rt_event_t, conjunto rt_uint32_t, opção
rt_uint8_t, tempo limite rt_int32_t, rt_uint32_t* recebido);
Quando o usuário chama esta interface, o sistema primeiro julga se o evento que deseja receber ocorreu de acordo com o parâmetro definido e a opção de recebimento. se RT_EVENT_FLAG_CLEAR está definido na opção de parâmetro e depois Return (onde o parâmetro recved retorna o evento recebido); se isso não acontecer, preencha o conjunto de espera e os parâmetros de opção na estrutura da própria thread e, em seguida, suspenda a thread em este objeto de evento até que o evento pelo qual ele está esperando satisfaça a condição Ou espere mais do que o tempo limite especificado. Se o timeout for igual a zero, significa que quando o evento a ser aceito pela thread não atende aos seus requisitos, ela não espera, mas retorna diretamente -RT_TIMEOUT.
parâmetro | descrever |
---|---|
evento | o identificador para o objeto de evento |
definir | Receber eventos de interesse para o thread |
opção | receber opções |
tempo esgotado | Especificar tempo limite |
recebido | aponta para o evento recebido |
Envio de evento rt_event_send()
rt_err_t
rt_event_send
(evento rt_event_t, conjunto rt_uint32_t);
Ao usar esta interface de função, defina o valor do sinalizador de evento do objeto de evento por meio do sinalizador de evento especificado pelo conjunto de parâmetros e, em seguida, percorra a lista de encadeamentos em espera no objeto de evento de evento para determinar se há uma solicitação de ativação de evento do encadeamento e o sinalizador de evento do objeto de evento atual. O valor corresponde e, se houver, ativa o encadeamento.
parâmetro | descrever |
---|---|
evento | o identificador para o objeto de evento |
definir | conjunto de eventos enviados |
experimento de evento
Para usar a comunicação de eventos no RT-Thread, você precisa rtconfigh
primeiro modificar o arquivo de configuração. Você pode usar os dois métodos a seguir:
- Descomente, abra a definição de macro,
- Ou use o
Configuration Wizard
assistente para configuração gráfica,
Este experimento precisa criar dois threads, um é o thread de recebimento e o outro é o thread de envio.
A thread receptora é responsável por receber dois eventos, KEY1_EVENT e KEY2_EVENT (KEY0 é pressionado e WK_UP é pressionado). o evento ocorre.
O encadeamento de envio é responsável por escanear as teclas, detectar o pressionamento de tecla e definir o sinalizador de evento correspondente como 1.
#include "board.h"
#include "rtthread.h"
// 定义线程控制块指针
static rt_thread_t recv_thread = RT_NULL;
static rt_thread_t send_thread = RT_NULL;
// 定义事件控制块
static rt_event_t test_event = RT_NULL;
#define KEY1_EVENT (0x01 << 0) //设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1) //设置事件掩码的位1
/******************************************************************************
* @ 函数名 : recv_thread_entry
* @ 功 能 : 接收线程入口函数
* @ 参 数 : parameter 外部传入的参数
* @ 返回值 : 无
******************************************************************************/
static void recv_thread_entry(void *parameter)
{
rt_uint32_t recved;
while(1)
{
// 等待接收事件标志
rt_event_recv(test_event, // 事件对象句柄
KEY1_EVENT | KEY2_EVENT, // 接收线程感兴趣的事件
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, // 接收选项
RT_WAITING_FOREVER, // 超时事件一直等
&recved); // 接收到的事件
if(recved == (KEY1_EVENT | KEY2_EVENT))
{
rt_kprintf("recv:KEY0与WK_UP都被按下\n");
LED0_TOGGLE; // LED0 反转
}
else
{
rt_kprintf("事件错误!\n");
}
}
}
/******************************************************************************
* @ 函数名 : send_thread_entry
* @ 功 能 : 发送线程入口函数
* @ 参 数 : parameter 外部传入的参数
* @ 返回值 : 无
******************************************************************************/
static void send_thread_entry(void *parameter)
{
while(1)
{
// KEY0 被按下
if(Key_Scan(KEY0_GPIO_PORT, KEY0_GPIO_PIN) == KEY_ON)
{
rt_kprintf("send:KEY0被单击\n");
// 发送事件1
rt_event_send(test_event, KEY1_EVENT);
}
// WK_UP 被按下
if(Key_Scan(WK_UP_GPIO_PORT, WK_UP_GPIO_PIN) == KEY_ON)
{
rt_kprintf("send:WK_UP被单击\n");
// 发送事件1
rt_event_send(test_event, KEY2_EVENT);
}
rt_thread_delay(20); //每20ms扫描一次
}
}
int main(void)
{
// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成
// 创建一个事件
test_event = // 事件控制块指针
rt_event_create("test_event", // 事件初始值
RT_IPC_FLAG_FIFO); // FIFO队列模式(先进先出)
if(test_event != RT_NULL)
rt_kprintf("事件创建成功!\n");
// 创建一个动态线程
recv_thread = // 线程控制块指针
rt_thread_create("recv", // 线程名字
recv_thread_entry, // 线程入口函数
RT_NULL, // 入口函数参数
255, // 线程栈大小
5, // 线程优先级
10); // 线程时间片
// 开启线程调度
if(recv_thread != RT_NULL)
rt_thread_startup(recv_thread);
else
return -1;
// 创建一个动态线程
send_thread = // 线程控制块指针
rt_thread_create("send", // 线程名字
send_thread_entry, // 线程入口函数
RT_NULL, // 入口函数参数
255, // 线程栈大小
5, // 线程优先级
10); // 线程时间片
// 开启线程调度
if(send_thread != RT_NULL)
rt_thread_startup(send_thread);
else
return -1;
}
Fenômenos experimentais
Quando KEY0 e WK_UP são pressionados, o receptor imprime duas "teclas pressionadas".
Neste experimento, não é comum detectar que dois botões são pressionados em sequência, sendo apenas para demonstrar o mecanismo de funcionamento do evento.