<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>JRNitre&#039;s Blog - FreeRTOS</title>
<link>https://blog.atoery.cn/index.php/tag/freertos/</link>
<atom:link href="https://blog.atoery.cn/index.php/feed/tag/freertos/" rel="self" type="application/rss+xml" />
<language>zh-CN</language>
<description></description>
<lastBuildDate>Sat, 19 Apr 2025 18:57:48 +0800</lastBuildDate>
<pubDate>Sat, 19 Apr 2025 18:57:48 +0800</pubDate>
<item>
<title>[嵌入式] STM32 在 FreeRTOS 下实现 us 级别延时</title>
<link>https://blog.atoery.cn/index.php/2025/04/19/100.html</link>
<guid>https://blog.atoery.cn/index.php/2025/04/19/100.html</guid>
<pubDate>Sat, 19 Apr 2025 18:57:48 +0800</pubDate>
<dc:creator>JRNitre</dc:creator>
<description><![CDATA[1.0 前言与概述最近在编写软件实现的 I2C 和 SPI 协议库，需要稳定的 1us 延时用作心跳函数。在 FreeRTOS 下编写 us 延时逻辑不太一样，故在文章中作记录。与裸机实现 us...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<h1>1.0 前言与概述</h1><p>最近在编写软件实现的 I2C 和 SPI 协议库，需要稳定的 1us 延时用作心跳函数。在 FreeRTOS 下编写 us 延时逻辑不太一样，故在文章中作记录。</p><p>与裸机实现 us 延时方法相近，使用滴答定时器通过比较数值即可实现延时。</p><p>在 RTOS 环境下实现思路如下：</p><ol><li>计算指定延时时长所需要的计数器的值</li><li>关闭系统任务调度 - 防止延时器件被任务调度打断影响计数精度</li><li>获取当前重装载值</li><li>获取开始延时寄存器里面的计数值</li><li>不断获取当前计数器的值</li><li>如果当前计数器值大于设定的计数器的值，退出延时</li><li>释放系统任务调度</li></ol><h1>2.0 实现代码</h1><pre><code>void delay_us(uint32_t us) {
  volatile uint32_t tcnt = 0;
  uint32_t reload = SysTick-&gt;LOAD;
  uint32_t ticks = us * (SystemCoreClock / 1000000);

  vTaskSuspendAll();  // 阻止 OS 调度
  volatile uint32_t told = SysTick-&gt;VAL;
  while(1) {
    volatile uint32_t tnow = SysTick-&gt;VAL;
    if(tnow != told) {
      if(tnow&lt;told) {
        tcnt += told - tnow;
      }else {
        tcnt += reload - tnow + told;
      }
      told = tnow;
      if(tcnt &gt;= ticks) {
        break;
      }
    }
  }
  xTaskResumeAll(); // 恢复 OS 调度
}</code></pre><h2>3.0 实现过程分析</h2><h3>3.1 初始化部分</h3><ul><li><code>uint32_t tcnt = 0;</code>：用于累计已经经过的时间</li><li><code>uint32_t reload = SysTick-&gt;LOAD;</code>：获取 SysTick 的重装载值，这是定时器的最大值</li><li><code>uint32_t ticks = us * (SystemCoreClock / 1000000);</code>：计算需要延时的 SysTick 的值</li></ul><h3>3.2 暂停任务调度</h3><p>通过 FreeRTOS 提供的 <code>vTaskSupendAll();</code> 函数，暂停操作系统的任务调度，防止在延时期间发生任务切换，影响延时精度。</p><h3>3.3 获取初始计数器值</h3><p><code>uint32_t told = SysTick-&gt;VAL;</code>：获取 SysTick 定时器当前计数值</p><h3>3.4 主循环</h3><p>主循环中会一直循环，直到时间累计到目标值 <code>ticks</code></p><h3>3.4.1 获取当前计数器值</h3><p><code>uint32_t tnow = SysTick-&gt;VAL;</code>：获取当前 SysTick 计数器的值</p><h3>3.4.2 判断数值是否发生变化</h3><p><code>if(tnow != told)</code>：如果 tnow 和 told 的值不等，则 SysTick 的值发生了变化，更新累计时间 tcnt</p><h3>3.4.3 计算时间差</h3><p><code>if(tnow &lt; told)</code>：如果 tnow 小于 told，则说明计数器并未溢出，累加差值 <code>tcnt = told - tnow</code>；否则说明计数器发生了溢出，<code>reload - tnow</code> 得到 tnow 到 0 的剩余时间，再减去 told 计算出 reload 到 told 的时间。</p><h3>3.4.4 更新旧计数值</h3><p><code>told = tnow</code> 更新 told 的值，便于下次比较</p><h3>3.4.5 判断是否达到目标延时</h3><p>如果累计时间 tcnt 大于或等于 ticks 则达到了目标延时时长，退出循环。</p><h3>3.4.6 恢复任务调度</h3><p>FreeRTOS 提供的 <code>xTaskResumeAll();</code> 函数可以恢复操作系统的任务调度</p><h1>4.0 上述代码存在的问题</h1><p>上述的代码中，可以轻松的实现 us 级别的延时功能，但是存在一些问题；程序中为了保证延时的精度，在延时函数的核心功能代码中关闭了系统的任务调度。这会对其它线程的任务运行产生干扰。</p><p>因为在系统的任务调度被暂停后，所有的任务切换都会被禁止，相当于回到了裸机状态，直到调度器被恢复。</p><p>这种行为可能会对其它线程中要求实时性的任务产生负面影响，尤其是多任务系统。</p><h1>4.1 另一种实现 us 延时的方法</h1><p>通过 STM32 的硬件外设 TIM 定时器可以实现无需关闭任务调度实现 us 延时。</p><h2>4.2 实现代码</h2><pre><code>void tim_delay_us (uint32_t us) {
  uint32_t start = __HAL_TIM_GET_COUNTER(&amp;htim2);  // 获取当前计数值
  uint32_t elapsed = 0;

  while (elapsed &lt; us) {
    uint32_t now = __HAL_TIM_GET_COUNTER(&amp;htim2);  // 获取当前计数值
    if (now &gt;= start) {
      elapsed = now - start;  // 正常情况
    } else {
      elapsed = 0xFFFFFFFF - start + now;  // 处理溢出
    }
  }
}</code></pre><p>通过硬件定时器的实现可在不占用调度器的情况下实现同样的延时效果。</p><h1>END 参考与声明</h1><p><strong>[参考文章]</strong></p><ul><li><a href="https://blog.csdn.net/weixin_44098974/article/details/134464328">STM32在FreeRTOS下的us延时</a></li></ul><blockquote>本文部分内容和代码由 Ai 辅助生成</blockquote>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://blog.atoery.cn/index.php/2025/04/19/100.html#comments</comments>
<wfw:commentRss>https://blog.atoery.cn/index.php/feed/tag/freertos/</wfw:commentRss>
</item>
<item>
<title>[FreeRTOS] 堆和栈的基本概念&amp;amp;在FreeRTOS中的使用</title>
<link>https://blog.atoery.cn/index.php/2025/04/16/92.html</link>
<guid>https://blog.atoery.cn/index.php/2025/04/16/92.html</guid>
<pubDate>Wed, 16 Apr 2025 21:41:29 +0800</pubDate>
<dc:creator>JRNitre</dc:creator>
<description><![CDATA[1.0 堆1.1 基本概念堆的本质是在 RAM 中的一段连续的内存区域，开发者可以主动申请一些内存块，其中与栈不同的是，堆的声明周期是完全由程序员进行控制的。1.2 堆的管理机制动态性碎片化风险...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<h1>1.0 堆</h1><h1>1.1 基本概念</h1><p>堆的本质是在 RAM 中的一段连续的内存区域，开发者可以主动申请一些内存块，其中与栈不同的是，堆的声明周期是完全由程序员进行控制的。</p><h1>1.2 堆的管理机制</h1><ul><li>动态性</li><li>碎片化风险</li><li>手动管理</li></ul><h1>1.3 例子</h1><p>在 C/C++ 中，定义一个变量时，其存储位置取决于其定义位置上下文：</p><ul><li>如果在函数内定义，则其存储在栈中。</li><li>若在全局或静态作用域定义，则存储在静态存储区。</li></ul><p>真正的堆内存必须通过动态分配函数进行显示申请。</p><pre><code>char heap_buf[1024];    // 若在函数内进行申请，则存储在栈中，函数结束后自动释放</code></pre><p>-</p><pre><code>char* heap_buf = (char*)malloc(1024);   // 显式申请堆内存
</code></pre><h1>2.0 栈</h1><h2>2.1 基本概念</h2><p>栈是一种先进后出的数据结构，其基本操作通常包括：压栈和弹栈。</p><h2>2.2 栈的特点</h2><ul><li>后进先出 (LIFO)</li><li>高效性</li><li>有限容量 (固定大小栈)</li><li>自动管理：在嵌入式系统中，函数调用时使用的栈由编译器和处理器自动管理，无需程序员手动干预。</li></ul><h1>3.0 RTOS 如何使用栈</h1><h1>END 参考 & 声明</h1><p><strong>[参考]</strong></p><ul><li>FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS（FreeRTOS教程 基于STM32，以实际项目为导向） -&gt; <em>BV1Jw411i7Fz</em></li></ul><blockquote>本文部分内容由 Ai 辅助生成</blockquote>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://blog.atoery.cn/index.php/2025/04/16/92.html#comments</comments>
<wfw:commentRss>https://blog.atoery.cn/index.php/feed/tag/freertos/</wfw:commentRss>
</item>
<item>
<title>[FreeRTOS] 学习记录 - 创建一个多任务程序</title>
<link>https://blog.atoery.cn/index.php/2025/04/14/90.html</link>
<guid>https://blog.atoery.cn/index.php/2025/04/14/90.html</guid>
<pubDate>Mon, 14 Apr 2025 22:44:58 +0800</pubDate>
<dc:creator>JRNitre</dc:creator>
<description><![CDATA[HAL 库版本在使用 CubeMX 初始化的 FreeRTOS 工程后，可以选择默认创建一个线程（名称可配置），位于 freertos.c 中：/* creation of mainTask *...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<h1>HAL 库版本</h1><p>在使用 CubeMX 初始化的 FreeRTOS 工程后，可以选择默认创建一个线程（名称可配置），位于 <code>freertos.c</code> 中：</p><pre><code>/* creation of mainTask */
mainTaskHandle = osThreadNew(StartDefaultTask, NULL, &amp;mainTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */</code></pre><p>创建一个新的任务，则需要使用 <code>xTaskCreate</code> 函数，这个函数的声明如下：</p><pre><code>BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                        const char * const pcName,
                        const configSTACK_DEPTH_TYPE usStackDepth,
                        void * const pvParameters,
                        UBaseType_t uxPrioity,
                        TaskHandle_t * const pxCreateTask );</code></pre><ul><li><code>TaskFunction_t pxTaskCode</code>：指定的运行函数</li><li><code>const char * const pcName</code>：名字</li><li><code>const configSTACK_DEPTH_TYPE usStackDepth</code>：栈的深度</li><li><code>void * const pvParameters</code>：参数</li><li><code>UBaseType_t uxPrioity</code>：优先级</li><li><code>TaskHandle_t * const pxCreateTask</code>：句柄</li></ul><p>创建一个任务用于串口发送数据：</p><pre><code>xTaskCreate(usartTask, &quot;usartTask&quot;, 128, NULL, osPriorityNormal, NULL);</code></pre><pre><code>void usartTask(void *argument){
        while(1){
            printf(&quot;usart task is run!\n&quot;);
            HAL_Delay(500);
    }
}</code></pre><h1>标准库版本</h1>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://blog.atoery.cn/index.php/2025/04/14/90.html#comments</comments>
<wfw:commentRss>https://blog.atoery.cn/index.php/feed/tag/freertos/</wfw:commentRss>
</item>
</channel>
</rss>