一、 软件框架设计
1. 最底层:驱动层 (drv/drvp)
// drv (drivers) - 硬件驱动 // 例如:GPIO驱动、UART驱动、I2C驱动等 drv_gpio.c drv_uart.c drv_spi.c // drvp (drivers port) - 驱动移植层 // 针对具体芯片的适配代码 drvp_stm32f4.c // 针对STM32F4的移植 drvp_air7xx.c // 针对air7xx的移植
作用:直接操作硬件寄存器,提供底层硬件访问接口
1.1 软件包层 (softpack)
FreeRTOS - 实时操作系统内核
lwIP - 轻量级TCP/IP协议栈
FatFS - 文件系统
mbedtls - 加密库
...
作用:第三方开源软件,提供基础服务
1.2 BSP框架层 (bsp_framework)
// 板级支持包框架
bsp_init() // 板级初始化
bsp_clock_config() // 时钟配置
bsp_interrupt() // 中断管理
bsp_timer() // 定时器服务
作用:封装底层驱动,为上层提供统一的板级接口
2. 机器层 (machine)
作用:将整个设备抽象成一个"机器",提供统一的操作接口
2.1 进程管理部分
// 各个独立的任务
task_start() // 启动任务 - 系统初始化
task_listen() // 监听任务 - 等待命令/连接
task_ctrl() // 控制任务 - 执行具体控制逻辑
task_host() // 主机任务 - 与上位机通信
task_manager() // 管理任务 - 任务调度管理
2.2 网络部分
// 网络模块抽象
net_init() // 网络初始化
// 具体实现
air7xx.c - 4G模块驱动
esp8266.c - WiFi模块驱动
作用:提供统一的网络接口,屏蔽不同网络模块的差异
2.3 工作逻辑部分
// 具体板载外设功能
led_control() // LED控制
cycle_work() // 周期性工作(如LED闪烁)
event_report() // 事件上报
3. 整体框图
二、学习使用RTOS
打开官方例程
这是一个ADC采集数据的例子
cpp
#include "SWM320.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
QueueHandle_t queueADC;
void TaskADC(void *arg);
void TaskPWM(void *arg);
void SerialInit(void);
int main(void)
{
SystemInit();
SerialInit();
GPIO_Init(GPIOA, PIN5, 1, 0, 0); //调试指示信号
xTaskCreate(TaskADC, (const char *)"ADC", 128, NULL, 2, NULL);
xTaskCreate(TaskPWM, (const char *)"PWM", 128, NULL, 3, NULL);
queueADC = xQueueCreate(16, 2);
vTaskStartScheduler();
}
/******************************************************************************************************************************************
* 函数名称: TaskADC()
* 功能说明: 启动ADC采集任务
* 输 入: void *arg
* 输 出: 无
* 注意事项: 无
******************************************************************************************************************************************/
void TaskADC(void *arg)
{
ADC_InitStructure ADC_initStruct;
PORT_Init(PORTA, PIN12, PORTA_PIN12_ADC0_IN4, 0); //PA.12 => ADC.CH4
PORT_Init(PORTA, PIN11, PORTA_PIN11_ADC0_IN5, 0); //PA.11 => ADC.CH5
PORT_Init(PORTA, PIN10, PORTA_PIN10_ADC0_IN6, 0); //PA.10 => ADC.CH6
PORT_Init(PORTA, PIN9, PORTA_PIN9_ADC0_IN7, 0); //PA.9 => ADC.CH7
ADC_initStruct.clk_src = ADC_CLKSRC_VCO_DIV64;
ADC_initStruct.clk_div = 25;
ADC_initStruct.pga_ref = PGA_REF_INTERNAL;
ADC_initStruct.channels = ADC_CH4;
ADC_initStruct.samplAvg = ADC_AVG_SAMPLE1;
ADC_initStruct.trig_src = ADC_TRIGSRC_SW;
ADC_initStruct.Continue = 0; //非连续模式,即单次模式
ADC_initStruct.EOC_IEn = ADC_CH4;
ADC_initStruct.OVF_IEn = 0;
ADC_initStruct.HFULL_IEn = 0;
ADC_initStruct.FULL_IEn = 0;
ADC_Init(ADC0, &ADC_initStruct); //配置ADC
NVIC_SetPriority(ADC0_IRQn, 5);
ADC_Open(ADC0); //使能ADC
while(1)
{
ADC_Start(ADC0);
vTaskDelay(200);
}
}
void ADC0_Handler(void)
{
uint16_t val;
ADC0->IF = (1 << ADC_IF_CH4EOC_Pos);
val = ADC_Read(ADC0, ADC_CH4);
xQueueSendFromISR(queueADC, &val, 0);
GPIO_InvBit(GPIOA, PIN5);
}
/******************************************************************************************************************************************
* 函数名称: TaskPWM()
* 功能说明: 等待ADC转换结果,根据ADC转换结果设置PWM占空比
* 输 入: void *arg
* 输 出: 无
* 注意事项: 无
******************************************************************************************************************************************/
void TaskPWM(void *arg)
{
PWM_InitStructure PWM_initStruct;
PWM_initStruct.clk_div = PWM_CLKDIV_8; //F_PWM = 20M/8 = 2.5M
PWM_initStruct.mode = PWM_MODE_INDEP; //A路和B路独立输出
PWM_initStruct.cycleA = 10000; //2.5M/10000 = 250Hz
PWM_initStruct.hdutyA = 2500; //2500/10000 = 25%
PWM_initStruct.initLevelA = 1;
PWM_initStruct.cycleB = 10000;
PWM_initStruct.hdutyB = 5000; //5000/10000 = 50%
PWM_initStruct.initLevelB = 1;
PWM_initStruct.HEndAIEn = 0;
PWM_initStruct.NCycleAIEn = 0;
PWM_initStruct.HEndBIEn = 0;
PWM_initStruct.NCycleBIEn = 0;
PWM_Init(PWM0, &PWM_initStruct);
PORT_Init(PORTA, PIN4, FUNMUX0_PWM0A_OUT, 0);
PORT_Init(PORTA, PIN6, FUNMUX0_PWM0B_OUT, 0);
PWM_Start(PWM0, 1, 1);
while(1)
{
uint16_t duty;
if(xQueueReceive(queueADC, &duty, 10) == pdTRUE)
{
printf("%d,", duty);
if(duty < 100) duty = 100;
if(duty > 4000) duty = 4000;
PWM_SetHDuty(PWM0, PWM_CH_A, 10000 * duty / 4096);
PWM_SetHDuty(PWM0, PWM_CH_B, 10000 - PWM_GetHDuty(PWM0, PWM_CH_A));
}
}
}
void SerialInit(void)
{
UART_InitStructure UART_initStruct;
PORT_Init(PORTA, PIN2, FUNMUX0_UART0_RXD, 1); //GPIOA.2配置为UART0输入引脚
PORT_Init(PORTA, PIN3, FUNMUX1_UART0_TXD, 0); //GPIOA.3配置为UART0输出引脚
UART_initStruct.Baudrate = 57600;
UART_initStruct.RXThresholdIEn = 0;
UART_initStruct.TXThresholdIEn = 0;
UART_initStruct.TimeoutIEn = 0;
UART_Init(UART0, &UART_initStruct);
UART_Open(UART0);
}
/******************************************************************************************************************************************
* 函数名称: fputc()
* 功能说明: printf()使用此函数完成实际的串口打印动作
* 输 入: int ch 要打印的字符
* FILE *f 文件句柄
* 输 出: 无
* 注意事项: 无
******************************************************************************************************************************************/
int fputc(int ch, FILE *f)
{
while(UART_IsTXFIFOFull(UART0));
UART_WriteByte(UART0, ch);
return ch;
}
从main.c中我们可以学习到
- xTaskCreate(TaskADC, (const char *)"ADC", 128, NULL, 2, NULL);
- queueADC = xQueueCreate(16, 2);
- vTaskStartScheduler();
- vTaskDelay(200);
- xQueueSendFromISR(queueADC, &val, 0);
- xQueueReceive(queueADC, &duty, 10);
①xTaskCreate(TaskADC, (const char *)"ADC", 128, NULL, 2, NULL);

BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode, // 任务函数指针
const char * const pcName, // 任务名称
const uint16_t usStackDepth, // 任务堆栈大小
void * const pvParameters, // 任务参数
UBaseType_t uxPriority, // 任务优先级
TaskHandle_t * const pxCreatedTask // 任务句柄
);
②xQueueCreate(16, 2);

QueueHandle_t xQueueCreate(
UBaseType_t uxQueueLength, // 队列长度(能存储多少个项目)
UBaseType_t uxItemSize // 每个项目的大小(字节)
);
③ vTaskStartScheduler()

vTaskStartScheduler() 是 FreeRTOS 中启动任务调度器的函数。
这是整个 RTOS 运行的起点,调用这个函数后,控制权就交给了 FreeRTOS 内核。
它启动调度器后,就由 FreeRTOS 接管 CPU 控制权,开始按照优先级调度各个任务运行。
④vTaskDelay(200);
vTaskDelay(200) 中的 200 不是毫秒! 它是 系统时钟节拍数 (Ticks)。

⑤ xQueueSendFromISR(queueADC, &val, 0);
在中断服务程序(ISR)里向队列发送数据的函数

BaseType_t xQueueSendFromISR(
QueueHandle_t xQueue, // 队列句柄
const void *pvItemToQueue, // 要发送的数据指针
BaseType_t *pxHigherPriorityTaskWoken // 是否唤醒高优先级任务
);
⑥ xQueueReceive(queueADC, &duty, 10)
从 queueADC 队列接收数据,接收到的数据存入 duty 变量,最多等待 10 个系统节拍

BaseType_t xQueueReceive(
QueueHandle_t xQueue, // 队列句柄
void *pvBuffer, // 接收数据的缓冲区指针
TickType_t xTicksToWait // 等待时间(单位:系统节拍)
);
接下来,看一下RTOS配置
CPU工作频率: SWM320RET7经过倍频后,工作在110592000 Hz
系统滴答:1秒钟产生多少次系统滴答 。没有别的需求,设置1000即可配置堆大小,
SWM320RET7拥有128KB大小的SRAM,80KB拿出来给freertos管理。
创建freertos任务
cpp
//静态分配一个任务句柄
static TaskHandle_t TaskHandle_start;
//任务入口函数,相当于main函数
void task_start_entry(void *arg)
{
printf("%s\r\n",__FUNCTION__);
while(1)
{
}
}
//创建任务函数
void create_task_of_start( void )
{
//创建任务
xTaskCreate( task_start_entry, (const char *)"start", 128, NULL, 2, &TaskHandle_start );
}
FUNCTION 是一个预定义宏,它会被编译器自动替换为当前函数的名称。
使用互斥锁

把它设为1

cpp
#include "semphr.h"
static SemaphoreHandle_t cslock; //保存锁对象
static int flag_init = 0; //描述锁是否已经初始化了
//初始化锁
void cslock_init(void)
{
if( flag_init == 1 ) return;
cslock = xSemaphoreCreateMutex();
if( cslock == NULL )
{//说明不成功
printf( "err:%s\r\n",__func__ );
while(1);
}
flag_init = 1;
}
//获取锁
void cslock_get(void)
{
if( flag_init == 0 ) return;
//获取cslock信号量,最多等待0xFFFFFFFF个tick(几乎无限等待)
int ret = xSemaphoreTake( cslock,0xFFFFFFFF );
if( ret == pdTRUE )
{//说明,成功获取了
}
else
{//失败
}
}
//释放锁
void cslock_free(void)
{
if( flag_init == 0 ) return;
xSemaphoreGive( cslock );
}
//实现自己的打印函数
void myprintf( char* format, ... )
{
if( flag_init == 0 ) return;
char buf[256]; //最大可以打印的字符串长度
va_list v_args;
va_start( v_args,format );
(void)vsnprintf( buf,sizeof(buf),format,v_args );
va_end( v_args );
//获取锁
cslock_get();
//使用互斥资源,因为printf最终调用到uart,指向同一个硬件资源,好几个任务同时使用,是不行的。
printf("%s",buf);
//释放锁
cslock_free();
}
链表
根节点数据结构

typedef struct xLIST
{
/* 1. 链表完整性检查(调试用) */
listFIRST_LIST_INTEGRITY_CHECK_VALUE
/* 2. 链表中项目的数量 */
configLIST_VOLATILE UBaseType_t uxNumberOfItems;
/* 3. 当前索引指针(用于遍历链表) */
ListItem_t * configLIST_VOLATILE pxIndex;
/* 4. 链表结束标记(最小值/最大值) */
MiniListItem_t xListEnd;
/* 5. 第二个完整性检查 */
listSECOND_LIST_INTEGRITY_CHECK_VALUE
} List_t;
节点数据结构

创建一个链表的根节点

初始化一个节点

在链表尾部插入新元素

在链表排序插入新元素

在链表中移除一个元素

演示FreeRTOS链表操作的示例代码
cpp
List_t mylist; //链表根节点
void task_start_entry(void *arg)
{
uint32_t tick = 0;
printf("%s\r\n",__FUNCTION__);
//初始化 myprintf
cslock_init();
//创建其他任务
create_task_of_host();
create_task_of_listen();
create_task_of_ctrl();
//在栈中(函数内)分配链表节点
ListItem_t node0;
ListItem_t node1;
ListItem_t node2;
//①初始化链表的根节点
vListInitialise( &mylist ) ;
//②初始化 所有节点
vListInitialiseItem( &node0 );
vListInitialiseItem( &node1 );
vListInitialiseItem( &node2 );
node0.xItemValue = 3;
node1.xItemValue = 5;
node2.xItemValue = 2;
//③把这些节点插入到链表里面去
#if ( 0 )
//排序插入
vListInsert( &mylist,&node0 );
vListInsert( &mylist,&node1 );
vListInsert( &mylist,&node2 );
#else
//插入到尾部
vListInsertEnd( &mylist,&node0 );
vListInsertEnd( &mylist,&node1 );
vListInsertEnd( &mylist,&node2 );
#endif
//④遍历链表,把所有元素都打印出来
ListItem_t* node = listGET_HEAD_ENTRY( &mylist );
while( 1 )
{
//如果当前的节点,就是链表的根节点 pxIndex ,说明就是表尾,表尾即退出循环
if( node == mylist.pxIndex ) break;
myprintf(" val = %08x\r\n",node->xItemValue );
node = node->pxNext;
}
while(1)
{
vTaskDelay( pdMS_TO_TICKS( 1000 ) );
}
}

ListItem_t* node = listGET_HEAD_ENTRY(&mylist);
// node 指向第一个数据节点 node0
if(node == mylist.pxIndex) break; // node == xListEnd, pxIndex == xListEnd
// 条件成立!退出循环




