的档案

[学习笔记]FreeRTOS

FreeRTOS

Tasks and Queues

  • 任务管理

  • 新建任务设置

    • Task Name: 设置任务的名称,它会基于此名字建立一个指向任务的句柄

    • Priority: 设置任务的优先级,高优先级的任务会抢占低优先级的任务

    • Stack Size: 设置任务栈的大小,用于储存任务相关的变量,关于堆栈详细内容请参考

    • Entry_Function:设置任务的入口函数,这个函数便是开发者实现任务功能的地方

    • Code Generation Option: IDE自动生产初始代码的选项,这里选择默认设置

    • Parameter: 传递给任务的参数

    • Allocation: 设置任务所在的地方,选择动态的话是生成在堆中,选择静态的话则生成在静态区

  • 关于任务

    • 一个任务包含Stack区(用于储存任务相关的变量)和任务控制块TCB区(Task Control Block,用于储存任务本身的设置)
    • img
    • 任务占用字节数 = TCB_size + (4 x Task stack size)
  • 使用

    • 实现任务函数的功能

    • *osKernelStart();*

      语句启动FreeRTOS的任务调度程序,单片机就运行起来了。

内存管理

分配方案

  • FreeRTOS提供了五种方案

  • Heap_1

    • 适用于系统不用删除任务或者内核对象
  • Heap_2

    • 适用于重复添加和删除相同任务的系统
    • 它会对大的内存块进行分割,但无法合并相连的内存块,容易导致内存的碎片化
  • Heap_3

    • 使用的场景不多
  • Heap_4

    • 更具普适性,也是默认配置
    • 适用于通用的应用(系统会添加和删除不同大小的任务)
  • Heap_5

    • 用不同的数组空间对内存进行分配
    • 是4的升级版

相关函数

  • size_t xPortGetFreeHeapSize( void );

    • 回当前堆中的空闲空间大小(单位是字节),可以用来优化堆空间大小
  • size_t xPortGetMinimumEverFreeHeapSize( void );

    • 返回在系统运行过程中堆空间的最小空闲空间,如果最小空闲空间很小的话可以考虑提高堆大小configTOTAL_HEAP_SIZE的值
    • 这个函数只能在Heap_4或者Heap_5下调用
  • void vApplicationMallocFailedHook( void );

    • 这是一个回调函数,需要用户自己实现。
    • 如果配置文件中configUSE_MALLOC_FAILED_HOOK 设置为1的话,当堆分配内存失败时会调用此函数。
    • 用户可以在此函数中进行错误处理。

任务管理

优先级模式

  • configUSE_PORT_OPTIMISED_TASK_SELECTION

    • 为0时为通用方式
    • 为1时为架构优化方式

任务创建

  • BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            uint16_t usStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t 									*pxCreatedTask);
    
    • *pvTaskCode* 这是一个函数指针,指向执行任务的函数

    • pcName* 任务的描述名称,方便调试,不用的话可以设为Null

    • usStackDepth* 每个任务有自己的栈空间,这里根据任务占用需求设置栈空间的大小,单位是字(Word)

    • pvParameters 用于传递给任务的参数,不用的话可以设为Null

    • uxPriority* 设置任务的优先级,范围由0到(configMAX_PRIORITIES – 1)。数值越大,等级越高

    • pxCreatedTask* 任务的具柄(handle),通过具柄可以对任务进行设置,比如改变任务优先级等,不用可以设为Null

  • 函数的返回值有两个pdPasspdFailpdPass表示任务创建成功,相反pdFail表示创建失败,创建失败的原因大多是因为系统没有足够的堆空间来保存任务

空闲任务(Idle Task)

  • 空闲任务是调度器在vTaskStartScheduler函数调用启动后自动创建的一个任务。之前的任务调度演示图处于简化的目的并没有列出空闲任务。空闲任务具有最低的优先级0,当用户创建的任务都在阻塞状态或挂起状态时,空闲任务便得以执行。最低的优先级确保空闲任务不会抢占用户任务。同时空闲任务负责清理内核的资源,所以当有任务被删除后,应该保证空闲任务能运行清理和回收内核的资源。
  • 空闲任务可以绑定一个钩子任务(Task Hook),当空闲任务运行的时候钩子任务也会被自动调用。钩子任务里可以添加测量空闲任务运行时间的函数或者把系统放入低功耗模式的函数。***空闲任务运行的时间反映出了系统的可用计算资源,可以用于推算CPU的占用率。CPU的占用率过高的话可能会对系统的实时性有影响。
  • 钩子任务的函数原型是vApplicationIdleHook( void );

调度器

  • 模式设置:configUSE_PREEMPTION

    • 为1时是抢占式模式

      • configUSE_PREEMPTION(允许抢占) 1

        configUSE_TIME_SLICING(采用时间片) 1

      • 高优先级任务会立即执行

      • 抢占式无时间片调度使用的时候需要很谨慎

    • 为0时是合作式模式

      • configUSE_PREEMPTION(允许抢占) 0

        configUSE_TIME_SLICING(采用时间片) 任意值

      • 高优先级任务会在当前任务完成后执行

  • 调度器执行间隔:configTICK_RATE_HZ

    • 通常设为100HZ(时间片大小相应为10ms)
  • 延时函数 vTaskDelayUntil和vTaskDelay的参数需要的是延时的节拍数,不能直接设置延时时间,因此使用 pdMS_TO_TICKS 函数将时间转换为节拍数。

代码实例

int main(void)
{
  // 创建任务
  xTaskCreate( vTask1, "get sensor data", 200, NULL, 3, NULL );
  xTaskCreate( vTask2, "show sensor data", 200, NULL, 2, NULL );
  xTaskCreate( vTask3, "get user input", 200, NULL, 1, NULL );
  // 启动调度器
  vTaskStartScheduler();
  while (1)
  {
      ...
  }
}

运行机制

  • FreeRTOS通过xTaskCreate()函数创建任务,相当于安卓的onCreate()函数;
  • FreeRTOS通过vTaskSuspend()函数挂起任务,相当于安卓的onPause()函数;
  • FreeRTOS通过vTaskResume()函数恢复任务到就绪状态,相当于安卓的onResume()函数;
  • FreeRTOS通过vTaskDelete()函数删除任务,相当于安卓的onDestroy()函数。

队列管理

类别

  • *复制队列(Queue by copy)* 表示写入队列的数据每个字节都被完整复制到队列中了
  • *引用队列(Queue by reference)* 表示写入队列的是要写入数据的引用(或者说是一个指针指向所引用的数据)而并不是数据本身

常见API

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize )
  • uxQueueLength 队列包含数据的最大长度
  • uxItemSize 每个数据占用的字节大小
  • 创建队列用到的函数。函数的返回值是QueueHandle_t具柄类型,表示的是对所创建队列的一个引用句柄
  • 如果堆中没有足够空间给队列使用的话函数的返回值会是NULL。
BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue,
TickType_t xTicksToWait )
  • xQueue 队列的句柄,来自于xQueueCreate()的返回值
  • pvItemToQueue 所发送数据的引用,然后这些数据会被复制到队列中
  • xTicksToWait 队列如果满时发送任务的阻塞时间(block time),上文已经介绍过,可以通过pdMS_TO_TICKS()把时间转换成节拍数。如果设置为portMAX_DELAY的话任务将永远等待下去(需要FreeRTOSConfig.h头文件中INCLUDE_vTaskSuspend参数设置为1)
  • 返回值 发送数据成功时返回pdPASS,失败时返回errQUEUE_FULL
  • 用于将数据发送到队列(具体一点就是队列的尾部)。如果要在中断程序调用的话需要使用xQueueSendFromISR()函数。
BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * pvItemToQueue,
TickType_t xTicksToWait )
  • 将数据发送至队列头部
BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void * pvItemToQueue,
TickType_t xTicksToWait )
  • 将数据发送至队列尾部
  • 等效xQueueSend()
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer,
TickType_t xTicksToWait )
  • xQueue 队列的具柄,来自于xQueueCreate()的返回值
  • pvBuffer 指向内存空间的一个引用,读取的数据会被复制到这片内存
  • xTicksToWait 队列如果空时接送任务的阻塞时间(block time),上文已经介绍过,可以通过pdMS_TO_TICKS()把时间转换成节拍数。如果设置为portMAX_DELAY的话任务将永远等待下去(需要FreeRTOSConfig.h头文件中INCLUDE_vTaskSuspend参数设置为1)
  • 返回值 接收数据成功时返回pdPASS,失败时返回errQUEUE_EMPTY
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue )
  • 用于获得队列中数据的数量

软件定时器

启用

  • 为了启用软件定时器,需要在头文件FreeRTOSConfig.h中设置configUSE_TIMERS的值为1.

分类

软件定时器在FreeRTOS中分为两个类型

  • 一次性定时器(One-shot timer)

    • 一次性定时器启动后只会执行一次回掉函数;
  • 自动重载定时器(Auto-reload timer)

    • 自动重载定时器会周期性地执行回调函数

种类

软件定时器的状态有以下两种

  • 静止装态(Dormant)
  • 运行状态(Running)

处于静止装态的定时器不会执行回调函数,可以通过调用定时器的具柄启用;

处于运行状态的定时器会在设定的时间间隔(相对于定时器进入运行状态后)到达后调用回调函数。

一次性定时器会在执行回调函数后进入静止状态

而自动重载定时器会在执行回调函数后重新进入运行状态。

时间服务任务

  • 优先级configTIMER_TASK_PRIORITY

    • 定时器的回调函数调用FreeRTOS的API函数的话会导致时间服务任务管理进入阻塞状态
  • 栈大小configTIMER_TASK_STACK_DEPTH

  • 针对软件定时器的命令会通过命令队列发送到时间服务任务。

CubeMX配置

  • type

    • 选择一次性定时器或者自动重载定时器

常用API

void ATimerCallback( TimerHandle_t xTimer );
  • xTimer 是软件定时器的具柄。

  • 注意

    • 回调函数的代码应该避免调用FreeRTOS的API函数防止进入阻塞状态
    • 一些FreeRTOS的API函数会使回调函数进入阻塞状态转而执行其他任务导致定时器的实时性不确定,容易产生意外错误或者超时
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction )
  • pcTimerName 定时器的名称,用于调试用
  • xTimerPeriodInTicks 定时器的周期,dMS_TO_TICKS() 可把时间转成节拍数
  • uxAutoReload 设置为pdTRUE为自动重载定时器,设置为pdFALSE为一次性定时器
  • pvTimerID 定时器的ID,可以用于各种目的
  • pxCallbackFunction 定时器执行的回调函数
  • 返回值 返回值为NULL表示创建失败,非NULL表示定时器的具柄
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait )
  • xTimer 要执行定时器的具柄
  • xTicksToWait 如果命令队列为满时函数进入阻塞状态等待命令队列有空的时间
  • 返回值 返回值为pdPASS表示启动定时器成功;返回值为pdFALSE表示失败
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID )
  • xTimer 要执行定时器的具柄
  • pvNewID 设定的pvTimerID的值
  • 用于设置定时器的pvTimerID 属性
void *pvTimerGetTimerID( TimerHandle_t xTimer );
  • xTimer 要执行定时器的具柄
  • 返回值 pvTimerID的值
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewTimerPeriodInTicks,
TickType_t xTicksToWait );
  • xTimer 要执行定时器的具柄
  • xNewTimerPeriodInTicks 新的定时器的周期,dMS_TO_TICKS() 可把时间转成节拍数
  • xTicksToWait 如果命令队列为满时函数进入阻塞状态等待命令队列有空的时间
  • 返回值 返回值为pdPASS表示成功;返回值为pdFALSE表示失败
  • 用于改变定时器的运行周期
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait ); 
  • xTimer 要执行定时器的具柄
  • xTicksToWait 如果命令队列为满时函数进入阻塞状态等待命令队列有空的时间
  • 返回值 返回值为pdPASS表示成功;返回值为pdFALSE表示失败
  • 用于重置定时器,定时器会重新运行并以此计算新的时间间隔

中断管理

  • void

资源管理

信号量

  • 种类

    • 二进制信号

    • 计数信号

    • 互斥量

      • 类似线程锁
      • 上锁保护资源,处理完资源后再释放锁
    • 关键区

      • 强制执行宏定义区间内的代码

常用API

SemaphoreHandle_t xSemaphoreCreateBinary( void )
  • 创建一个二进制信号量,如果返回值是NULL或所创建信号量的具柄
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );
  • 创建一个计数信号量,如果返回值是NULL或所创建信号量的具柄
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait )
  • *xSemaphore* 信号量的句柄
  • *xTicksToWait* 等待节拍数,设置为0的话如果信号量不可用的话会直接返回
  • 返回值为pdPASS表示成功获取了信号量,返回值为pdFALSE表示获取信号量失败
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
  • *xSemaphore* 信号量的句柄
  • 返回值为pdPASS表示成功给予信号量,返回值为pdFALSE表示给予信号量失败
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken )
  • 中断版本
SemaphoreHandle_t xSemaphoreCreateMutex( void )

END

[学习笔记]Socket通信

Socket通信

base on py,by laoair

2023/3/5

客户端(sender)

创建Socket

import socket   #for sockets
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket Created'
  • socket(地址簇,类型)

    • 地址簇:AF_INET ---- ipv4

    • 类型

      • SOCK_STREAM,TCP传输控制协议
      • SOCK_DGRAM ,UDP通讯协议

连接服务器

s.connect( (remote_ip , port) )

发送数据

  • s.sendall(message)

    • message:string类型
    • 返回值是发送字节的数量
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

接收数据

  • s.recv(bufsize)

    • bufsize指定最多可以接收的数量
    • 数据以字符串形式返回
reply = s.recv(4096)
  • 缓冲区是4096个字节

关闭

s.close()

服务器端 (recevier)

绑定 Socket

  • s.bind((HOST, PORT))

    • 使用元组(host, port) 的形式表示地址
  • bind函数 本地连接

import socket
import sys

HOST = ''  
PORT = 8888 

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

监听

  • s.listen(10)

    • 指定可以挂起的最大连接数量。这个参数的值最小为1,般设置为5。
    • 最多同时监听10个连接
s.listen(10)

接受连接

  • conn, addr = s.accept()

    • conn 是新的套接字对象,可以理解为会话实例
    • addr是连接客户端的地址,内容为[ip,port]
    • 一般是阻塞态
#wait to accept a connection - blocking call
conn, addr = s.accept()

#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

接收数据

#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)

关闭

conn.close()
s.close()

服务器代码

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data: 
        break

    conn.sendall(reply)

conn.close()
s.close()

END