rtthread套娃移植

article/2025/8/25 21:59:18

和大家分享下将基于rtthread的项目移植到其他平台的经验。

背景

最近做了一个物联网项目移植。原先的项目使用的硬件平台为stm32f401+sim800c(mcu + 2G modem),软件平台为rtthread 4.0.1。移植到的新平台为BC25(nb modem),软件平台为BC25 opencpu sdk,也跑了个RTOS,具体不详。BC25不支持rtthread,笔者也无法移植rtthread到BC25,因为BC25只提供了一套SDK接口,无源码,无芯片手册。

opencpu简介
可能有些同学不了解opencpu,这里简单解释下。传统的单片机联网平台是mcu+modem,mcu通过AT命令与modem交互。modem也是有cpu的,而且由于其要运行网络协议栈,RAM和FLASH资源比普通单片机丰富。因此有些modem提供了opencpu功能,让客户的业务代码能跑在modem中,这样就省去了mcu及周边远器件。

BC25 sdk提供了线程、线程间通信、驱动等接口。不过和rtthread的接口相比,其操作系统接口很不完备。比如:

  • 获取信号量的接口,只支持两种方式:无堵塞(获取不到,立刻返回失败),死等(获取不到就一直等)。缺少折中方案:设置等待的时间,超时则返回失败。
  • 最多可创建5个互斥量。。。
  • 用户程序最多可使用8个线程,而且是在代码中通过宏列表写死。不支持动态创建线程。

直接使用BC25的SDK的话,就需要对原来的业务代码做很多改动。比如用到信号量的地方,不仅仅是将rt_sem_take换成Ql_OS_TakeSemaphore。对于用到超时返回特性的rt_sem_take,得做特殊的修改。再说写死的线程,笔者在原项目中写了一个通用的状态机模块,其内部动态创建线程以维护状态机的流转。并且多处使用了该状态机模块,因为是通用的嘛。而现在不能动态创建线程,就很麻烦。

假移植决定

经过一番权衡,笔者决定实现rtthread的内核接口,这样做有三大好处:

  • 解决了BC25 SDK接口不完备的问题。
  • 业务层还是使用rtthread接口,所以业务代码改动量非常之少(主要改的是驱动代码)。
  • 这样的方案灵活机动,如果下次又用另一家的opencpu了,还是不用动业务层。

所谓实现rtthread接口而不是移植rtthread,是笔者基于现有的SDK接口来实现rtthread接口,即心是BC25 SDK,壳是rtthread。因此标题为:rtthread套娃移植。

笔者认为这种另类的移植不算常见,有很多细节要处理,因此写此篇文章和大家分享交流。请注意,本篇文章是分享些移植经验,并不是完整的移植指南。

移植的内容分为三大类:内核接口,非常常用的驱动(pin,i2c),finsh。

内核接口细分如下:

  • 基本类型(如rt_uint32_t)
  • rtt的内核库(如rt_memset,rt_vsprintf,rt_malloc)
  • 线程接口(如rt_thread_create)
  • 线程间同步与通信、中断管理(rt_mutex_t,rt_sem_t,rt_event_t,rt_mq_t,rt_enter_critical,rt_hw_interrupt_disable)
  • 定时器(rt_timer_t)

内核接口移植

接实现方法,分为三类:

  • 直接复制
  • 简单替换
  • 逻辑适配

直接复制

基本类型(rt_uint32_t),错误码(RT_EOK)以及基本宏(RT_ALIGN)定义在rtdef.h这中,可直接把该文件复制过来,删除不需要的东西(如rt_device_ops,rt_device)。rtdef.h中还定义了线程、线程间通信、定时器这些模块的相关结构体,这些也删掉,具体原因会在后面说。

简单替换

这个主要是针对rtt的内核库,比如rt_memset,其声明为:

void *rt_memset(void *s, int c, rt_ubase_t count)

BC25也提供了自己的C库,其声明为:

void* Ql_memset(void* dest, u8 value, u32 size)

这就可以通过宏来一对一替换:

#define rt_memset(dst, value, size)     Ql_memset(dst, value, size)

能简单替换的内容不多,也就这么些:

#define rt_memset(dst, value, size)     Ql_memset(dst, value, size)
#define rt_memcpy(dst, src, size)       Ql_memcpy(dst, src, size)
#define rt_memcmp(dst, src, size)       Ql_memcmp(dst, src, size)
#define rt_memmove(dst, src, size)      Ql_memmove(dst, src, size)
#define rt_strcpy(dst, src)             Ql_strcpy(dst, src)
#define rt_strncpy(dst, src, size)      Ql_strncpy(dst, src, size)
#define rt_strcmp(s1, s2)               Ql_strcmp(s1, s2)
#define rt_strncmp(s1, s2, size)        Ql_strncmp(s1, s2, size)
#define rt_strchr(src, ch)              Ql_strchr(src, ch)
#define rt_strlen(str)                  Ql_strlen(str)
#define rt_strstr(s1, s2)               Ql_strstr(s1, s2)#define rt_vsprintf(s, fmt, arg)        Ql_vsprintf(s, fmt, arg)
#define rt_sprintf(s, fmt, ...)         Ql_sprintf(s, fmt, ##__VA_ARGS__)
#define rt_snprintf(s, size, fmt, ...)  Ql_snprintf(s, size, fmt, ##__VA_ARGS__)
#define rt_sscanf(s, fmt, ...)          Ql_sscanf(s, fmt, ##__VA_ARGS__)

除了用宏的方式,也可以用函数来封装。

void *rt_malloc(rt_size_t size)
{return Ql_MEM_Alloc(size);
}

起初笔者也是用宏来替换rt_malloc的,但是这样一来cJSON软件包的代码编译不过,因为其用函数指针来指向rt_malloc。而笔者定义的是带参数的宏,此处就不会替换,从而提示rt_malloc未被定义。

int cJSON_hook_init(void)
{cJSON_Hooks cJSON_hook;cJSON_hook.malloc_fn = (void *(*)(size_t sz))rt_malloc;cJSON_hook.free_fn = rt_free;cJSON_InitHooks(&cJSON_hook);return RT_EOK;
}

逻辑适配

逻辑适配才是本次移植的主要工作,所以另起一章进行说明。

关于线程接口、线程间同步与通信、中断管理、定时器等模块,BC25 SDK也提供了相关接口,不过在功能、参数列表和返回值方面与rtthread接口肯定是不一致的,需要做一些适配工作。
rtthread中rt_mutex_t之类的内核结构体使用了面向对象的概念,继承关系如下:
在这里插入图片描述

不过本次移植,是使用BC25的接口来填充rtthread接口,用不到这层关系,只要在功能上保持一致即可。所以关于rt_mutex_t之类的类型定义,由笔者自行定义。这就是之前复制rtdef.h时要删掉它们的原因。
rtthread中的定义

struct rt_mutex
{struct rt_ipc_object parent;                        /**< inherit from ipc_object */rt_uint16_t          value;                         /**< value of mutex */rt_uint8_t           original_priority;             /**< priority of last thread hold the mutex */rt_uint8_t           hold;                          /**< numbers of thread hold the mutex */struct rt_thread    *owner;                         /**< current owner of mutex */
};

笔者的定义

struct rt_mutex
{char name[RT_NAME_MAX];rt_sem_t sem;rt_thread_t owner;rt_uint32_t hold;
};

有些BC25的接口与rtthread比较相似,如定时器接口,适配起来很容易。有些BC25接口不完备,比如线程间同步的接口不能设置超时时间,不能动态创建线程,这些就需要费些工夫。下面笔者挑一些有代表性的来介绍适配方法。

定时器

BC25创建定时器和启动定时器的接口如下:

typedef void(*Callback_Timer_OnTimer)(u32 timerId, void* param);
s32 Ql_Timer_Register(u32 timerId, Callback_Timer_OnTimer callback_onTimer, void* param);
s32 Ql_Timer_Start(u32 timerId, u32 interval, bool autoRepeat);

rttthread相关接口为:

rt_timer_t rt_timer_create(const char *name,void (*entry)(void *parameter),void       *parameter,rt_tick_t   timeout,rt_uint8_t  flag);
rt_err_t rt_timer_start(rt_timer_t timer);

大体上是相似的,都是创建定时器传入回调函数和额外参数。哈哈,其实定时器接口肯定要这两个参数啦。不同之处为:

  • BC25通过ID来操作相关定时器,rtthread由模块创建并返回定时器对象,之后由该对象来操作相关定时器。这点上,rtthread接口更为易用,因为BC25需要防止ID冲突。
  • BC25是在启动定时器是指定定时间隔和模式(周期还是单次),rtthread是在创建定时器时指定,不过之后也可以修改。这点上,笔者还是觉得rtthread好用,嗯,笔者真不是马屁精。

适配方法很简单,在rt_timer_create函数中,动态获取id(自增即可),创建rt_timer_t对象,并将timeout和flag保存在对象中。也要保存entry和parameter,因为BC25的回调函数形式与rtthread不一致,由timer_callback中转。

static uint32_t timer_id_alloc(void)
{static uint32_t id = 0x100;uint32_t ret;rt_enter_critical();ret = id++;rt_exit_critical();return ret;
}static void timer_callback(u32 id, void* param)
{rt_timer_t timer = (rt_timer_t)param;RT_ASSERT(timer->handle == id);timer->entry(timer->parameter);
}rt_timer_t rt_timer_create(const char *name,void (*entry)(void *parameter),void       *parameter,rt_tick_t   timeout,rt_uint8_t  flag)
{int ret;rt_timer_t timer = (rt_timer_t)rt_malloc(sizeof(struct rt_timer));RT_ASSERT(timer);rt_memset(timer, 0, sizeof(*timer));rt_snprintf(timer->name, sizeof(timer->name), "%s", name);timer->handle = timer_id_alloc();ret = Ql_Timer_Register(timer->handle, timer_callback, timer);if(ret != QL_RET_OK){rt_free(timer);return RT_NULL;}timer->entry = entry;timer->parameter = parameter;timer->flag = flag;timer->timeout = timeout;return timer;
}rt_err_t rt_timer_start(rt_timer_t timer)
{int ret;ret = Ql_Timer_Start(timer->handle, rt_tick_to_millisecond(timer->timeout), (timer->flag & RT_TIMER_FLAG_PERIODIC) != 0);timer->flag |= RT_TIMER_FLAG_ACTIVATED;return ret == QL_RET_OK ? RT_EOK : -RT_ERROR;
}

顺便说下rt_enter_critical和rt_hw_interrupt_disable。前者是关调度器,后者是关中断。还记得BC25的线程都不能动态创建吗,更是不可能提供这些功能接口。巧妇难为无米之炊啊,笔者只能用BC25的互斥量(对,就是之前说的,最多可创建5个互斥量)来实现。

void rt_enter_critical(void)
{Ql_OS_TakeMutex(rtt_mutex);
}void rt_exit_critical(void)
{Ql_OS_GiveMutex(rtt_mutex);
}rt_base_t rt_hw_interrupt_disable(void)
{rt_enter_critical();return 0;
}void rt_hw_interrupt_enable(rt_base_t level)
{rt_exit_critical();
}

可能有的同学会不解,人家明明是要关调度器,你用互斥量有什么用。确实,这有一定的使用限制,那就是所有访问相关资源的地方,都要关调度器。比如说:
写ringbuffer时关调度器。

rt_enter_critical();
rt_ringbuffer_put_force(&stream->recv_rb, stream->tmp_buf, ret);
rt_exit_critical();

读ringbuffer时也关调度器。

rt_enter_critical();
ret = rt_ringbuffer_getchar(&stream->recv_rb, &data);
rt_exit_critical();

这样一来,调度器就是一个全局互斥量。关中断也是一样的原理。至于上述示例代码为什么不直接用rt_mutex,笔者说下自己关于何时用互斥量、何时关调度器的理解。如果是在访问资源的时间极短,关调度器比较合适;相反,比如通过i2c总线进行数据传输,尤其是硬件i2c,则应该用互斥量。因为在操作i2c的过程中,完全可以释放cpu资源给别的线程用。而且,访问不同的资源得使用不同的互斥量,因为操作i2c时不应该让spi资源也被锁定。
可能又有同学质疑:在中断函数里面怎么能使用互斥量呢。庆幸的是,业务代码就没用到真正的中断场景。BC25提供的大部分回调接口,比如串口、GPIO、定时器,都是在线程中进行回调,数据的缓存由BC25实现(比如串口)。这样也是合理的,享受不到相应的权力(底层的控制权限),不应该也无法履行相应的义务。最后再次声明,这是权宜之计,无奈之举啊。

信号量

BC25的信号量接口与rtthread比较相似,唯独缺少超时功能,只能选择不等或者死等。

u32 Ql_OS_TakeSemaphore(u32 semId, bool wait);
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout);

rt_sem_trytake的实现很简单,就是不等。

rt_err_t rt_sem_trytake(rt_sem_t sem)
{rt_uint32_t ret = Ql_OS_TakeSemaphore(sem->handle, false);return ret == OS_SUCCESS ? RT_EOK : -RT_ERROR;
}

至于rt_sem_take,分三种情况。若是死等或者不等,直接通过rt_sem_trytake来调用BC25接口。若是带超时的等待,只能搞个循环尝试了,牺牲实时性。

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout)
{if((rt_tick_t)timeout == RT_WAITING_FOREVER){rt_uint32_t ret = Ql_OS_TakeSemaphore(sem->handle, true);return ret == OS_SUCCESS ? RT_EOK : -RT_ERROR;}else if(timeout == 0){return rt_sem_trytake(sem);}else{timeout = rt_tick_get() + timeout;rt_err_t err;do{err = rt_sem_trytake(sem);if(err == RT_EOK){return RT_EOK;}rt_thread_delay(1);} while(rt_tick_get() < timeout);return -RT_ETIMEOUT;}
}

互斥量

BC25最多创建5个互斥量,这显然不够用。对了,它也没有超时版本。PS:BC25所有进程间同步与通信接口均无超时版本。所以这里打算重新设计互斥量模块,而不使用BC25的接口。之所以rt_enter_critical使用BC25的互斥量,是因为rt_enter_critical不存在超时场景,并且笔者设计的互斥量接口中还使用到了rt_enter_critical。
如何凭空创造互斥量呢,哈哈,显然不可能。笔者使用已实现的rt_sem来实现rt_mutex。互斥量与信号量本是用于两种不同的场景,不过信号量可以替代互斥量,而互斥量无法替代信号量。信号量常用的场景是用于发送通知,初始信号值为0,生产者调用rt_sem_release以让信号值加1,消费者调用rt_sem_take等待信号并减1。如果将初始信号值设置为1的话,那就可以用于互斥场景了。在访问资源前调用rt_sem_take,若此时信号值为1,则获取信号量,此后信号值为0。此时其他线程调用rt_sem_take将被堵塞。当访问完毕后,调用rt_sem_release恢复信号量值为1。
笔者最初的实现如下:

rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)
{return rt_sem_create(name, 1, flag);
}rt_err_t rt_mutex_delete(rt_mutex_t mutex)
{return rt_sem_delete(mutex);
}rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{return rt_sem_take(mutex, time);
}rt_err_t rt_mutex_release(rt_mutex_t mutex)
{return rt_sem_release(mutex);
}

简单测试下是没问题的,不过跑业务代码时发生了卡死。最终发现,这种实现不可重入。比如,函数A调用rt_mutex_take后调用函数B,函数B又调用了rt_mutex_take。处理方案:在获取互斥量时,若其已被上锁且持有者为当前线程,则直接放行。PS:此方案借(抄)鉴(袭)rtthread原接口的实现。

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout)
{rt_err_t err = -RT_ERROR;rt_thread_t cur_thread = rt_thread_self();RT_ASSERT(cur_thread);rt_enter_critical();if(mutex->owner == cur_thread){mutex->hold++;rt_exit_critical();return RT_EOK;}rt_exit_critical();err = rt_sem_take(mutex->sem, timeout);if(err != RT_EOK){return err;}rt_enter_critical();mutex->owner = cur_thread;mutex->hold = 1;rt_exit_critical();return RT_EOK;}rt_err_t rt_mutex_release(rt_mutex_t mutex)
{rt_thread_t cur_thread = rt_thread_self();RT_ASSERT(cur_thread && mutex->owner == cur_thread);rt_enter_critical();mutex->hold--;if(mutex->hold == 0){mutex->owner = RT_NULL;rt_sem_release(mutex->sem);}rt_exit_critical();return RT_EOK;
}

线程

笔者已吐槽多次,BC25的线程是在代码中写死的,像下面这样,proc_main_task、proc_ril_task是线程入口函数,第二个参数是线程ID。

TASK_ITEM(proc_main_task,       MAIN_THREAD_ID,   10*1024, DEFAULT_VALUE1, DEFAULT_VALUE2) //main task
TASK_ITEM(proc_ril_task,        ril_task_id,    5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)  //RIL task
TASK_ITEM(proc_urc_task,        urc_task_id,    5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)  //URC task

不过BC25提供了一个非常重要的线程接口,也仅仅提供了这一个接口:返回当前线程的ID。

s32 Ql_OS_GetActiveTaskId(void);

可以用此实现rt_thread_self。真是万幸啊,这不之前的rt_mutex_take的重入功能还用到它的嘛。

rt_thread_t rt_thread_self(void)

至于如何实现动态创建,待我慢慢道来。

BC25允许用户最多创建8个线程,笔者将它们纳入线程池。

线程对象的定义:

struct rt_thread
{char name[RT_NAME_MAX];int ql_id;void (*entry)(void *parameter);void *parameter;rt_uint32_t stack_size;rt_uint8_t  priority;rt_bool_t in_use;rt_sem_t sem;
};

线程池定义:

static struct rt_thread thread_lst[THREAD_NUM];

死写的线程列表:

TASK_ITEM(thread_entry,  THREAD1_ID, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(thread_entry,  THREAD2_ID, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(thread_entry,  THREAD3_ID, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(thread_entry,  THREAD4_ID, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(thread_entry,  THREAD5_ID, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(thread_entry,  THREAD6_ID, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(thread_entry,  THREAD7_ID, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(thread_entry,  THREAD8_ID, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)

这8个线程的入口函数都指向同一个thread_entry。该函数通过id找到线程池中自己的对象,等待对象被激活。激活后,运行真正的线程入口函数。

void thread_entry(int id)
{rt_thread_t thread;/** 等待本模块初始化,因为线程是写死的,* 可能在系统加载时就开始运行了。*/wait_thread_init();thread = get_thread(id);RT_ASSERT(thread);/** 等待线程被激活。* 即用户rt_thread_create并rt_thread_create。*/RT_ASSERT(rt_sem_take(thread->sem, RT_WAITING_FOREVER) == RT_EOK);RT_ASSERT(thread->entry);thread->entry(thread->parameter);/** 其实这里还可以做回收的,不过笔者没用到这个场景。*/
}

rt_thread_create从线程池中获取空闲的线程对象,标记为使用中(in_use),记录相关参数(主要是入口函数,入参),返回该对象。

/** stack_size和priority是预留参数,可用于后期优化。*/
static rt_thread_t alloc_thread(rt_uint32_t stack_size, rt_uint8_t priority)
{rt_thread_t thread = RT_NULL;rt_enter_critical();for(int i = 0; i < THREAD_NUM; i++){rt_thread_t tmp = thread_lst + i;if(!tmp->in_use){tmp->in_use = RT_TRUE;thread = tmp;break;}}rt_exit_critical();return thread;
}rt_thread_t rt_thread_create(const char *name,void (*entry)(void *parameter),void       *parameter,rt_uint32_t stack_size,rt_uint8_t  priority,rt_uint32_t tick)
{rt_thread_t thread = alloc_thread(stack_size, priority);if(!thread){rt_kprintf("No available thread for app_thread(name:%s, stack_size:%d, priority:%d)\r\n",name, stack_size, priority);return RT_NULL;}rt_snprintf(thread->name, sizeof(thread->name), "%s", name);thread->entry = entry;thread->parameter = parameter;return thread;
}

上述是最关键的实现。未实现的功能有:

  • 线程的释放与回收。这个笔者的项目中用不到,也就没做嘿嘿。
  • 线程栈空间大小及优先级的设定。栈空间大小是在线程列表里面列写的,这倒可以视使用场景优化一下:在列表中设定不同空间大小的线程,alloc_thread选择刚刚满足需求的空闲线程。至于优先级,BC25不支持:(。

再次说明,本篇文章是分享些移植经验,并不是完整的移植指南。做这种系统级移植,得对系统有深刻的了解,至少得明白各接口的功能、使用场景,以及自己需要哪些接口(毕竟工具有限,也不是所有接口都能实现,比如真正的关中断)。所以,这种移植工作,因人而异,因项目而异。
先写这么多,关于rt_event_t,rt_mq_t,pin,i2c,请待下回分解。

转载请注明出处:https://blog.csdn.net/wenbodong/article/details/109056606
未经允许请勿用于商业用途。


http://chatgpt.dhexx.cn/article/FCZbkOoB.shtml

相关文章

关于RT thread系统节拍时钟的配置

关于RT thread系统节拍时钟的配置 -----本文基于rt-thread-3.1.3版本编写 首先&#xff0c;使用RTthread OS时&#xff0c;要配置&#xff08;或者明白&#xff09;它的系统节拍rt_tick&#xff08;划重点&#xff09;。 系统节拍 系统节拍是特定的周期中断&#xff0c;可以…

rtthread学习

RT-Thread 内核实现与应用开发实战指南 1、数据类型rtdef.h 中的数据类型 在裸机系统中&#xff0c;他们统统放在一个叫栈的地方&#xff0c;栈是单片机 RAM 里面一段连续的内存空间&#xff0c;栈的大小一般在启动文件或者链接脚本里面指定&#xff0c; 最后由 C 库函数_m…

Rtthread 内存管理

Rtthread 堆内存管理 #define HEAP_MAGIC 0x1ea0 struct heap_mem {/* magic and used flag */rt_uint16_t magic; //魔数&#xff0c;固定值rt_uint16_t used; //使用标记&#xff0c;1为该内存已经被使用rt_size_t next, prev; //双向链表偏移 }; #define MIN_SIZE 12 //一…

RT-Thread学习

一、入门 RT-Thread官网  官网文档   Rt-thread学习文档  RT-Thread官方bilibili视频号   GD32官网 教你动手移植RT-Thread到国产MCU    如何移植RT-Thread到GD32单片机上&#xff08;非studio版&#xff09; 东方青讲RT-Thread  RT-Thread内核入门指南 RT-Thread…

RT Thread之ADC电压读取

官网连接&#xff1a;https://docs.rt-thread.org/#/rt-thread-version/rt-thread-standard/programming-manual/device/adc/adc 一、配置步骤&#xff1a; 1、用cubemx配置底层&#xff1b; 2、cubemx配置好的文件替换之前的配置文件&#xff1b; 3、修改Kconfig文件&…

rtthread mqtt

rtthread 以太网 (LAN8720A) 基于以太网的应用mqtt&#xff0c;在**rtthread 以太网 (LAN8720A)**中已经实现了tcp/ip通信正常&#xff0c;接下需要启用mqtt模块&#xff0c; 嵌入式mqtt设备 rtthread 启用mqtt 在rtthread中田间 pahomqtt 软件包&#xff0c;并右键详细配置…

【RTThread】修改Finsh打印串口波特率

这里需要注意得是一定要在hw_board_init初始化完成之后修改串口波特率。 /* 串口设备句柄 */static rt_device_t uart_device RT_NULL;/* 查找系统中的串口设备 */uart_device rt_device_find("uart1"); // 这里/* 串口配置结构体&#xff0c;使用serial.h的宏定义…

RT Thread之 Uart2 操作

官网连接&#xff1a;https://docs.rt-thread.org/#/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart 通过前面的学习&#xff0c;基本上RT Thread操作步骤都是&#xff0c;先配置单片机底层&#xff0c;然后再通过应用层映射到底层&#xff0c;最…

rtthread

链表 初始化双向链表 rt_inline void rt_list_init(rt_list_t *l) {l->next l->prev l; }插入 rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n) {l->next->prev n;n->next l->next;l->next n;n->prev l; }在NODE1后面插入节…

RT Thread根据开发板制作BSP方法

之前一直不懂怎么使用RT Thread的软件包&#xff0c;感谢网上的大神&#xff0c;看了你们的博客后大概了解一些&#xff0c;在此做下记录。用RT Thread软件包需要RT Thread的系统&#xff0c;但是RT Thread和RT Thread nano不一样&#xff0c;具体区别见 RT Thread官网&#xf…

rtthread开关中断

1 rtthread开关中断函数(cortex-m) /** rt_base_t rt_hw_interrupt_disable();*/ .global rt_hw_interrupt_disable .type rt_hw_interrupt_disable, %function rt_hw_interrupt_disable:MRS r0, PRIMASKCPSID IBX LR/** void rt_hw_interrupt_enable(rt_base_t le…

RTThread入门

RT-Thread入门 1.初识RT-Thread 嵌入式系统是一种完全嵌入在装置或设备内部&#xff0c;为满足特定需求而设计的计算机系统&#xff0c;譬如生活中常见的嵌入式系统就有&#xff1a;电视机顶盒、路由器、电冰箱、微波炉与移动电话等。 嵌入式操作系统是应用于嵌入式系统的软…

什么是RT-Thread?

一、RT-Thread的定义 RT-Thread&#xff0c;全称是 Real Time-Thread&#xff0c; 是一款主要由中国开源社区主导开发的开源实时操作系统&#xff08;许可证GPLv2&#xff09;&#xff0c;包含了实时、嵌入式系统相关的各个组件&#xff1a;TCP/IP协议栈、图形用户界面等。 相…

Redis启动失败的原因及解决方法

跑了近半年的Redis,今天早上来开启电脑运行程序的时候发现提示无法连接redis,暗想自己明明设置了开机自启的阿,以前也一直没问提,今天怎么就连不上了重启了下redis就提示如下错误 网上搜了好久都没找到解决办法,后来想起来去查看了下redis的日志文件 发现提示当前版本的redis无…

redis启动、获取密码及修改密码

一、启动redis服务的两种方式 查看密码是以redis服务已启动的前提下进行的&#xff0c;可直接在服务中右键启动redis或者安装根目录运行cmd输入《redis-server.exe》(不推荐不推荐不推荐&#xff0c;说三遍&#xff0c;命令行启动好像有bug&#xff0c;启动后redis能用&#x…

CentOS安装Redis及redis启动与关闭、配置(详细)

在项目使用redis过程中&#xff0c;在centos7上部署redis&#xff0c;查找相关资料并总结、记录&#xff0c;以备后续查看。 目录 一、Redis介绍 二、在CentOS上部署Redis 1、Redis安装包可以从官网上下载或者直接命令下载 升级到gcc 9.3&#xff1a; 3、Redis配置文件…

Redis启动和连接

一&#xff09;Redis简介 Redis不是简单的键值存储&#xff0c;它实际上是一个数据结构服务器&#xff0c;支持不同类型的值。 备注&#xff1a;由于我电脑是32位操作系统&#xff0c;所有就不提供redis软件下载地址了&#xff0c;请到官网下载使用。 软件解压之后&#xff0…

windows下Redis启动闪退问题解决经验汇总

最近使用Redis又遇到启动闪退的问题&#xff0c;之前记录的解决办法也失败了&#xff0c;一番研究后总算得到解决&#xff0c;感觉已经遇到了网上常见的各种问题&#xff0c;下面总结下。 我下载的是免安装版&#xff0c;解压便可使用。 官网下载传送门&#xff1a;Releases …

Windows下redis启动那些事儿

本文章主要描述我遇到的Windows下redis启动成功但Java项目无法连接问题 1.使用redis可视化工具可以连接&#xff0c;但是到Java项目中就报错连接失败 经过我的多方琢磨&#xff0c;还是密码没有配置正确&#xff0c;虽然是在redis.windows.conf配置文件中配置了 requirepass 密…

redis启动失败问题完美解决

1.输入启动命令redis-server.exe redis.windows.conf启动redis&#xff0c;发现启动失败报错&#xff1a;[8072] 07 May 09:28:52.241 # Creating Server TCP listening socket 127.0.0.1:6379: bind: No error D:\a\Main\redis> redis-server.exe redis.windows.conf[8072]…