tag 标签: freertos

相关帖子
相关博文
  • 2025-6-29 20:40
    37 次阅读|
    0 个评论
    14. ESP32开发之ST7789显示
    SPI概述 ESP IDF中SPI LCD的相关API 简单使用LVGL 完整代码 总结 SPI概述 当进入嵌入式行业开始,SPI总线是最需要且基础的知识了,它是高速全双工串行总线,可做到同时收发数据。时序和控制根据各家的芯片或者屏幕等设备的数据手册进行阅读和进行编程,比如总线模式就有四种,通过寄存器CPOL和CPHA配置 模式 CPOL CPHA 数据采样时刻 适用场景 0 0 0 SCK下降沿,第1个跳变沿采样 多数传感器(如BME280) 1 0 1 SCK下降沿,第2个跳变沿采样 部分ADC芯片 2 1 0 SCK上升沿,第1个跳变沿采样 特殊存储器 3 1 1 SCK上升沿,第2个跳变沿采样 某些RF模块 更多的SPI相关基础,可以在网上查询 ESP IDF中SPI LCD相关API 在ESP32开发库ESP IDF中有完整的SPI LCD实现的代码,我们只需要合适的调用相关API函数就可以了,在此案例中用到如下函数和结构体 1. 配置SPI总线 SPI总线结构体 typedef struct { union { int mosi_io_num; //spi的mosi接口,不用赋值-1 int data0_io_num; /// GPIO pin for spi data0 signal in quad/octal mode, or -1 if not used. }; union { int miso_io_num; /// GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used. int data1_io_num; /// GPIO pin for spi data1 signal in quad/octal mode, or -1 if not used. }; int sclk_io_num; /// GPIO pin for SPI Clock signal, or -1 if not used. union { int quadwp_io_num; /// GPIO pin for WP (Write Protect) signal, or -1 if not used. int data2_io_num; /// GPIO pin for spi data2 signal in quad/octal mode, or -1 if not used. }; union { int quadhd_io_num; /// GPIO pin for HD (Hold) signal, or -1 if not used. int data3_io_num; /// GPIO pin for spi data3 signal in quad/octal mode, or -1 if not used. }; int data4_io_num; /// GPIO pin for spi data4 signal in octal mode, or -1 if not used. int data5_io_num; /// GPIO pin for spi data5 signal in octal mode, or -1 if not used. int data6_io_num; /// GPIO pin for spi data6 signal in octal mode, or -1 if not used. int data7_io_num; /// GPIO pin for spi data7 signal in octal mode, or -1 if not used. int max_transfer_sz; /// Maximum transfer size, in bytes. Defaults to 4092 if 0 when DMA enabled, or to `SOC_SPI_MAXIMUM_BUFFER_SIZE` if DMA is disabled. uint32_t flags; /// Abilities of bus to be checked by the driver. Or-ed value of ``SPICOMMON_BUSFLAG_*`` flags. esp_intr_cpu_affinity_t isr_cpu_id; /// Select cpu core to register SPI ISR. int intr_flags; /** Interrupt flag for the bus to set the priority, and IRAM attribute, see * ``esp_intr_alloc.h``. Note that the EDGE, INTRDISABLED attribute are ignored * by the driver. Note that if ESP_INTR_FLAG_IRAM is set, ALL the callbacks of * the driver, and their callee functions, should be put in the IRAM. */ } spi_bus_config_t ; 初始化SPI总线函数 esp_err_t spi_bus_initialize ( spi_host_device_t host_id, //使用哪个主机SPI,有SPI0_HOST、SPI1_HOST、SP2_HOST const spi_bus_config_t *bus_config, //使用spi_bus_config_t创建的结构体 spi_dma_chan_t dma_chan //dma通道,设置SPI_DMA_DISABLED则不开启 ) 2. 创建LCD SPI句柄,并与SPI总线相连 LCD SPI面板相关io结构体 typedef struct { int cs_gpio_num; /*! GPIO used for CS line */ int dc_gpio_num; /*! GPIO used to select the D/C line, set this to -1 if the D/C line is not used */ int spi_mode; /*! Traditional SPI mode (0~3) */ unsigned int pclk_hz; /*! Frequency of pixel clock */ size_t trans_queue_depth; /*! Size of internal transaction queue */ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*! Callback invoked when color data transfer has finished */ void *user_ctx; /*! User private data, passed directly to on_color_trans_done's user_ctx */ int lcd_cmd_bits; /*! Bit-width of LCD command */ int lcd_param_bits; /*! Bit-width of LCD parameter */ struct { unsigned int dc_low_on_data: 1 ; /*! If this flag is enabled, DC line = 0 means transfer data, DC line = 1 means transfer command; vice versa */ unsigned int octal_mode: 1 ; /*! transmit with octal mode (8 data lines), this mode is used to simulate Intel 8080 timing */ unsigned int quad_mode: 1 ; /*! transmit with quad mode (4 data lines), this mode is useful when transmitting LCD parameters (Only use one line for command) */ unsigned int sio_mode: 1 ; /*! Read and write through a single data line (MOSI) */ unsigned int lsb_first: 1 ; /*! transmit LSB bit first */ unsigned int cs_high_active: 1 ; /*! CS line is high active */ } flags; /*! Extra flags to fine-tune the SPI device */ } esp_lcd_panel_io_spi_config_t ; 创建LCD SPI面板相关io的句柄 esp_err_t esp_lcd_new_panel_io_spi ( esp_lcd_spi_bus_handle_t bus, //SPI总线 const esp_lcd_panel_io_spi_config_t *io_config, //填充完成的esp_lcd_panel_io_spi_config_t结构体变量 esp_lcd_panel_io_handle_t *ret_io //返回的LCD SPI面板句柄 ) //在使用这个函数之前需先创建个esp_lcd_panel_io_spi_config_t结构体变量,比如esp_lcd_panel_io_handle_t io_handle = NULL; LCD SPI面板驱动配置结构体 typedef struct { int reset_gpio_num; /*! GPIO used to reset the LCD panel, set to -1 if it's not used */ union { lcd_rgb_element_order_t color_space; /*! @deprecated Set RGB color space, please use rgb_ele_order instead */ lcd_rgb_element_order_t rgb_endian; /*! @deprecated Set RGB data endian, please use rgb_ele_order instead */ lcd_rgb_element_order_t rgb_ele_order; /*! Set RGB element order, RGB or BGR */ }; lcd_rgb_data_endian_t data_endian; /*! Set the data endian for color data larger than 1 byte */ unsigned int bits_per_pixel; /*! Color depth, in bpp */ struct { unsigned int reset_active_high: 1 ; /*! Setting this if the panel reset is high level active */ } flags; /*! LCD panel config flags */ void *vendor_config; /*! vendor specific configuration, optional, left as NULL if not used */ } esp_lcd_panel_dev_config_t ; 创建 LCD 面板句柄,并指定 SPI IO 设备句柄 以下函数是ESP IDF套件中已经实现的st7789相关函数,其内部还有很多适配的LCD,可以在如下路径找到 esp-idf/components/esp_lcd/src //其实就是将实现的st7789相关函数注册到LCD面板这个大的虚拟对象中,这是C语言的面向对象编程 esp_err_t esp_lcd_new_panel_st7789 ( const esp_lcd_panel_io_handle_t io, //创建过的lcd面板io句柄 const esp_lcd_panel_dev_config_t *panel_dev_config, //创建过的LCD面板驱动句柄 esp_lcd_panel_handle_t *ret_panel //返回一个LCD面板句柄 ) 3. 通过以上一步步完善结构体,和创建的LCD面板句柄,我们将可以调用关于LCD 面板的所有API函数,比如: //控制LCD复位 esp_err_t esp_lcd_panel_reset ( esp_lcd_panel_handle_t panel) //初始化LCD esp_err_t esp_lcd_panel_init ( esp_lcd_panel_handle_t panel) //开关屏幕 esp_err_t esp_lcd_panel_disp_on_off ( esp_lcd_panel_handle_t panel, bool on_off) //控制屏幕是否反色,白天模式暗黑模式的控制 esp_err_t esp_lcd_panel_invert_color ( esp_lcd_panel_handle_t panel, bool invert_color_data) 使用LVGL 1. 将LVGL的库加入到项目中 ESP IDF开发套件有完善的组件管理方式,在UBUNTU中我们可以如下操作 在项目根目录下使用如下命令,将会在main文件夹下生成一个idf_component.yml的文件 idf.py create-manifest 增加参数: --path:显示的指定在什么目录下创建组件清单文件 --component:在componets目录下,为相关组件创建(此参数所赋值)清单,比如--component=my_components 使用如下命令重新配置项目 idf.py reconfigure 这样就后面每次在构建项目时,则会跟踪上一步生成的idf_component.yml文件 编辑idf_component.yml ## IDF Component Manager Manifest File dependencies: ## Required IDF version idf: version: "=4.1.0" lvgl/lvgl: "~9.2.0" //这是我需要添加的LVGL库,版本时9.2.0 更多操作可以参考如下链接; https://docs.espressif.com/projects/esp-idf/zh_CN/release-v5.2/esp32s3/api-guides/tools/idf-component-manager.html 2. 使用LVGL在屏幕上显示"hello world" 初始化lvgl lv_init(); 创建lvgl显示器 lv_display_t * lv_display_create ( int32_t hor_res, int32_t ver_res) //参数为实际屏幕的分辨率 申请绘制使用的buffer void lv_display_set_buffers ( lv_display_t * disp, //上一步创建的显示器 void * buf1, //缓存1 void * buf2, //缓存2 uint32_t buf_size, //缓存大小 lv_display_render_mode_t render_mode //绘制图像的模式 ) 将LVGL与上面创建的LCD面板进行连接 void lv_display_set_user_data ( lv_display_t * disp, void * user_data) 设置显示的颜色格式 void lv_display_set_color_format ( lv_display_t * disp, lv_color_format_t color_format) 设置显示的方向 void lv_display_set_rotation ( lv_display_t * disp, lv_display_rotation_t rotation) 设置渲染回调函数 void lv_display_set_flush_cb ( lv_display_t * disp, lv_display_flush_cb_t flush_cb) //回调函数如果设置为NULL,则使用默认函数 为LVGL创建定时器 static void example_increase_lvgl_tick ( void *arg) { /* Tell LVGL how many milliseconds has elapsed */ lv_tick_inc ( 2 ); //这里是2ms,建议跟定时器定时时间同步 } const esp_timer_create_args_t lvgl_tick_timer_args = { .callback = example_increase_lvgl_tick, //定时事件,定时通知lvgl已经多久了 .name = "lvgl_tick" }; esp_timer_handle_t lvgl_tick_timer = NULL ; ESP_ERROR_CHECK ( esp_timer_create (lvgl_tick_timer_args, lvgl_tick_timer)); ESP_ERROR_CHECK ( esp_timer_start_periodic (lvgl_tick_timer, 2 * 1000 )); //2ms触发一次事件 为lvgl创建任务 //通过lv_timer_handler(void)持续更新lvgl,这是LVGL的心脏 static void example_lvgl_port_task ( void *arg) { ESP_LOGI (TAG, "Starting LVGL task" ); uint32_t time_till_next_ms = 0 ; while ( 1 ) { _lock_acquire(lvgl_api_lock); time_till_next_ms = lv_timer_handler (); _lock_release(lvgl_api_lock); // in case of triggering a task watch dog time out time_till_next_ms = MAX (time_till_next_ms, EXAMPLE_LVGL_TASK_MIN_DELAY_MS); // in case of lvgl display not ready yet time_till_next_ms = MIN (time_till_next_ms, EXAMPLE_LVGL_TASK_MAX_DELAY_MS); usleep ( 1000 * time_till_next_ms); } } xTaskCreatePinnedToCore (example_lvgl_port_task, "LVGL" , EXAMPLE_LVGL_TASK_STACK_SIZE, NULL , EXAMPLE_LVGL_TASK_PRIORITY, NULL , 1 ); 绘制显示内容,如下绘制“hello world" lv_obj_t *scr = lv_display_get_screen_active (disp); lv_obj_t *label1= lv_label_create (scr); lv_label_set_text (label1, "Hello\nworld" ); lv_obj_align (label1, LV_ALIGN_CENTER, 0 , 0 ); 完整代码 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-06-27 * @version v0.1 * * @revision history: * 2025-06-23 - Initial version. */ # include stdio.h # include unistd.h # include sys/lock.h # include sys/param.h # include freertos/FreeRTOS.h # include freertos/task.h # include esp_log.h # include driver/gpio.h # include driver/spi_master.h # include "driver/spi_common.h" # include "esp_timer.h" # include "esp_lcd_panel_io.h" # include "esp_lcd_panel_vendor.h" # include "esp_lcd_panel_ops.h" # include "esp_lcd_panel_commands.h" # include "esp_dma_utils.h" # include "lvgl.h" static const char *TAG = "ST7789" ; //LCD使用的SPI接口 # define LCD_SPI SPI2_HOST //SPI相关引脚定义 # define SPI_LCD_MOSI GPIO_NUM_12 # define SPI_LCD_MISO GPIO_NUM_18 # define SPI_LCD_SCLK GPIO_NUM_11 # define SPI_LCD_CS GPIO_NUM_15 # define SPI_LCD_DC GPIO_NUM_17 # define SPI_LCD_RST GPIO_NUM_21 # define SPI_LCD_BL GPIO_NUM_26 //LCD显示屏的大小 # define LCD_SIZE_WIDTH 240 # define LCD_SIZE_HIGHT 240 # define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1000/CONFIG_FREERTOS_HZ # define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500 # define EXAMPLE_LVGL_TASK_STACK_SIZE (4 * 1024) # define EXAMPLE_LVGL_TASK_PRIORITY 5 // LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it static _lock_t lvgl_api_lock; static void set_angle ( void * obj, int32_t v) { lv_arc_set_value (obj, v); } void example_lvgl_demo_ui ( lv_display_t *disp) { lv_obj_t *scr = lv_display_get_screen_active (disp); lv_obj_t *label1= lv_label_create (scr); lv_label_set_text (label1, "Hello\nworld" ); lv_obj_align (label1, LV_ALIGN_CENTER, 0 , 0 ); } static void example_lvgl_port_task ( void *arg) { ESP_LOGI (TAG, "Starting LVGL task" ); uint32_t time_till_next_ms = 0 ; while ( 1 ) { _lock_acquire(lvgl_api_lock); time_till_next_ms = lv_timer_handler (); _lock_release(lvgl_api_lock); // in case of triggering a task watch dog time out time_till_next_ms = MAX (time_till_next_ms, EXAMPLE_LVGL_TASK_MIN_DELAY_MS); // in case of lvgl display not ready yet time_till_next_ms = MIN (time_till_next_ms, EXAMPLE_LVGL_TASK_MAX_DELAY_MS); usleep ( 1000 * time_till_next_ms); } } /* Rotate display and touch, when rotated screen in LVGL. Called when driver parameters are updated. */ static void example_lvgl_port_update_callback ( lv_display_t *disp) { esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data (disp); lv_display_rotation_t rotation = lv_display_get_rotation (disp); switch (rotation) { case LV_DISPLAY_ROTATION_0: // Rotate LCD display esp_lcd_panel_swap_xy (panel_handle, false ); esp_lcd_panel_mirror (panel_handle, false , false ); break ; case LV_DISPLAY_ROTATION_90: // Rotate LCD display esp_lcd_panel_swap_xy (panel_handle, true ); esp_lcd_panel_mirror (panel_handle, true , true ); break ; case LV_DISPLAY_ROTATION_180: // Rotate LCD display esp_lcd_panel_swap_xy (panel_handle, false ); esp_lcd_panel_mirror (panel_handle, true , true ); break ; case LV_DISPLAY_ROTATION_270: // Rotate LCD display esp_lcd_panel_swap_xy (panel_handle, true ); esp_lcd_panel_mirror (panel_handle, true , false ); break ; } } static void example_increase_lvgl_tick ( void *arg) { /* Tell LVGL how many milliseconds has elapsed */ lv_tick_inc ( 1 ); } static void example_lvgl_flush_cb ( lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { example_lvgl_port_update_callback (disp); esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data (disp); int offsetx1 = area-x1; int offsetx2 = area-x2; int offsety1 = area-y1; int offsety2 = area-y2; // because SPI LCD is big-endian, we need to swap the RGB bytes order lv_draw_sw_rgb565_swap (px_map, (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1)); // copy a buffer's content to a specific area of the display esp_lcd_panel_draw_bitmap (panel_handle, offsetx1, offsety1, offsetx2 + 1 , offsety2 + 1 , px_map); } static esp_lcd_panel_handle_t st7789_init ( void ) { //spi bus的相关配置 spi_bus_config_t busconfig={ // .flags=SPICOMMON_BUSFLAG_MASTER, //设置SPI为主机模式 .sclk_io_num=SPI_LCD_SCLK, .miso_io_num=SPI_LCD_MISO, .mosi_io_num=SPI_LCD_MOSI, .quadwp_io_num = -1 , .quadhd_io_num = -1 , .max_transfer_sz=LCD_SIZE_HIGHT*LCD_SIZE_WIDTH* sizeof ( uint16_t ) //单次最多可传输80行像素 }; ESP_ERROR_CHECK ( spi_bus_initialize (LCD_SPI, busconfig, SPI_DMA_CH_AUTO)); // 启用 DMA //创建SPI LCD句柄 esp_lcd_panel_io_handle_t io_handle = NULL ; esp_lcd_panel_io_spi_config_t io_config = { .dc_gpio_num = SPI_LCD_DC, .cs_gpio_num = SPI_LCD_CS, .pclk_hz = 40 * 1000 * 1000 , .lcd_cmd_bits = 8 , .lcd_param_bits = 8 , .spi_mode = 0 , .trans_queue_depth = 10 }; // 将 LCD 连接到 SPI 总线 ESP_ERROR_CHECK ( esp_lcd_new_panel_io_spi (( esp_lcd_spi_bus_handle_t )LCD_SPI, io_config, io_handle)); esp_lcd_panel_handle_t panel_handle = NULL ; esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = SPI_LCD_RST, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, .bits_per_pixel = 16 , }; // 为 ST7789 创建 LCD 面板句柄,并指定 SPI IO 设备句柄 ESP_ERROR_CHECK ( esp_lcd_new_panel_st7789 (io_handle, panel_config, panel_handle)); ESP_ERROR_CHECK ( esp_lcd_panel_reset (panel_handle)); ESP_ERROR_CHECK ( esp_lcd_panel_init (panel_handle)); ESP_ERROR_CHECK ( esp_lcd_panel_disp_on_off (panel_handle, true )); esp_lcd_panel_invert_color (panel_handle, true ); ESP_LOGI (TAG, "Initialize LVGL library" ); return panel_handle; } void app_main ( void ) { esp_lcd_panel_handle_t panel_handle= st7789_init (); lv_init (); // create a lvgl display lv_display_t *display = lv_display_create ( 240 , 240 ); // alloc draw buffers used by LVGL // it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized size_t draw_buffer_sz = 240 * 240 * sizeof ( lv_color16_t ); void *buf1 = heap_caps_malloc (draw_buffer_sz, MALLOC_CAP_DMA); assert (buf1); void *buf2 = heap_caps_malloc (draw_buffer_sz, MALLOC_CAP_DMA); assert (buf2); // initialize LVGL draw buffers lv_display_set_buffers (display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL); // associate the mipi panel handle to the display lv_display_set_user_data (display, panel_handle); // set color depth lv_display_set_color_format (display, LV_COLOR_FORMAT_RGB565); // set the callback which can copy the rendered image to an area of the display lv_display_set_rotation (display,LV_DISPLAY_ROTATION_0); lv_display_set_flush_cb (display, example_lvgl_flush_cb); ESP_LOGI (TAG, "Install LVGL tick timer" ); // Tick interface for LVGL (using esp_timer to generate 2ms periodic event) const esp_timer_create_args_t lvgl_tick_timer_args = { .callback = example_increase_lvgl_tick, .name = "lvgl_tick" }; esp_timer_handle_t lvgl_tick_timer = NULL ; ESP_ERROR_CHECK ( esp_timer_create (lvgl_tick_timer_args, lvgl_tick_timer)); ESP_ERROR_CHECK ( esp_timer_start_periodic (lvgl_tick_timer, 2 * 1000 )); ESP_LOGI (TAG, "Create LVGL task" ); xTaskCreatePinnedToCore (example_lvgl_port_task, "LVGL" , EXAMPLE_LVGL_TASK_STACK_SIZE, NULL , EXAMPLE_LVGL_TASK_PRIORITY, NULL , 1 ); ESP_LOGI (TAG, "Display LVGL Meter Widget" ); // Lock the mutex due to the LVGL APIs are not thread-safe _lock_acquire(lvgl_api_lock); example_lvgl_demo_ui (display); _lock_release(lvgl_api_lock); } 显示结果: 总结 一定要注意以下几点: 缓存区大小的设定,如果设置不正确将会出现如下情况,偏移也会如此,所以碰到这个情况可以想想会不会是缓存区的问题 显示镜像,根据不同的实现方式,要仔细调试 //这里只是设置了标志 lv_display_set_rotation (display,LV_DISPLAY_ROTATION_0); //要通过如下函数进行具体实现 static void example_lvgl_port_update_callback ( lv_display_t *disp) { esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data (disp); lv_display_rotation_t rotation = lv_display_get_rotation (disp); switch (rotation) { case LV_DISPLAY_ROTATION_0: // Rotate LCD display esp_lcd_panel_swap_xy (panel_handle, false ); esp_lcd_panel_mirror (panel_handle, false , false ); break ; case LV_DISPLAY_ROTATION_90: // Rotate LCD display esp_lcd_panel_swap_xy (panel_handle, true ); esp_lcd_panel_mirror (panel_handle, true , true ); break ; case LV_DISPLAY_ROTATION_180: // Rotate LCD display esp_lcd_panel_swap_xy (panel_handle, false ); esp_lcd_panel_mirror (panel_handle, true , true ); break ; case LV_DISPLAY_ROTATION_270: // Rotate LCD display esp_lcd_panel_swap_xy (panel_handle, true ); esp_lcd_panel_mirror (panel_handle, true , false ); break ; } } static void example_lvgl_flush_cb ( lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { example_lvgl_port_update_callback (disp); esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data (disp); int offsetx1 = area-x1; int offsetx2 = area-x2; int offsety1 = area-y1; int offsety2 = area-y2; // because SPI LCD is big-endian, we need to swap the RGB bytes order lv_draw_sw_rgb565_swap (px_map, (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1)); // copy a buffer's content to a specific area of the display esp_lcd_panel_draw_bitmap (panel_handle, offsetx1, offsety1, offsetx2 + 1 , offsety2 + 1 , px_map); } 如下情况,就需要改example_lvgl_port_update_callback函数里的具体实现 esp idf开发套件里有大量的案例,完全可以参考相关代码进行设计,当然从零搭建也是可以的,如果时间充裕的话 lvgl的相关配置,可以使用idf.py menuconfig进行更改
  • 2025-6-13 16:28
    0 个评论
    在嵌入式系统领域,嵌入式实时操作系统(RTOS) 的应用正日益广泛,采用RTOS能够更合理、更高效地利用CPU资源,FreeRTOS作为一款轻量级且成熟的实时操作系统内核,其核心功能完备,包括任务管理、时间管理(如延时、定时器)、同步机制(信号量、互斥锁)、进程间通信(消息队列)等等。这些特性使其能够很好地满足资源相对有限的中小型嵌入式系统的需求。 i.MX 9352作为NXP 推出的新一代轻量级边缘AI处理器,集成2个Cortex-A55核和1个Cortex-M33实时核,其架构设计充分体现了对实时性与复杂任务处理能力的兼顾。为了帮助开发者充分利用i.MX 9352 M33核的实时能力,其配套的M核SDK包提供的FreeRTOS例程分为两类,一类介绍FreeRTOS系统组件特性,如信号量、互斥量、队列等,另一类是介绍外设接口如何在FreeRTOS使用,我们分别挑选这两类下的例程进行演示。 演示平台:飞凌嵌入式OK-MX9352-C开发板 1、FreeRTOS-generic 飞凌嵌入式OK-MX9352-C开发板支持FreeRTOS功能特性示例代码如下: freertos_event :任务事件演示例程 freertos_queue :队列消息实现任务间通信的演示例程 freertos_mutex :互斥锁使用例程 freertos_sem :信号量使用例程 freertos_swtimer :软件计数器及其回调的用法。 freertos_tickless :使用 LPTMR 延时唤醒或者硬件中断唤醒例程 freertos_generic :task、queue、swtimer、tick hook 、semaphore 组合利用演示例程。 因FreeRTOS_generic例程使用的FreeRTOS特性较多,我们重点分析此例程。 (1)软件实现 示例程序内容包括:任务创建、队列、软定时器、系统节拍时钟、信号量、异常处理。具体如下: 任务创建: 主函数创建了队列发送、接收,信号量三个任务。 // 创建队列接收任务if ( xTaskCreate ( prvQueueReceiveTask, "Rx" ,configMINIMAL_STACK_SIZE+ 166 , NULL ,mainQUEUE_RECEIVE_TASK_PRIORITY, NULL ) ! = pdPASS ) // 创建队列发送任务if ( xTaskCreate ( prvQueueSendTask, "TX" ,configMINIMAL_STACK_SIZE+ 166 , NULL , mainQUEUE_SEND_TASK_PRIORITY, NULL ) ! = pdPASS ) // 创建信号量任务if ( xTaskCreate ( prvEventSemaphoreTask, "Sem" ,configMINIMAL_STACK_SIZE+ 166 , NULL ,mainEVENT_SEMAPHORE_TASK_PRIORITY, NULL ) ! = pdPASS ) 队列: 队列发送任务,阻塞200ms后向队列发送数据;队列接收任务,任务阻塞读取队列,数据读取正确,则打印此时的队列接收数量。 // 队列发送任务,阻塞200ms后 向队列发送数据 static void prvQueueSendTask ( void *pvParameters) { TickType_t xNextWakeTime; const uint32_t ulValueToSend = 100UL ; xNextWakeTime = xTaskGetTickCount (); for (;;) { // 任务阻塞,直至200ms延时结束 vTaskDelayUntil (xNextWakeTime, mainQUEUE_SEND_PERIOD_MS); // 向队列发送数据,阻塞时间为0表示当队列满的时候就立即返回 xQueueSend (xQueue, ulValueToSend, 0 ); } } // 队列接收任务,任务阻塞读取队列,数据读取正确,则打印此时的队列接收数量。 static void prvQueueReceiveTask ( void *pvParameters) { uint32_t ulReceivedValue; for (;;) { // 任务一直阻塞,知道队列内读取到数据 xQueueReceive (xQueue, ulReceivedValue, portMAX_DELAY); // 队列数据和发送一致,队列接收数量+1 输出此时的队列接收数量 if (ulReceivedValue == 100UL ) { ulCountOfItemsReceivedOnQueue++; PRINTF ( "Receive message counter: %d.\r\n" , ulCountOfItemsReceivedOnQueue); } } } 软定时器: 设置软定时器周期1s,时间到后,调用回调函数,记录次数并串口打印。 // 创建软件定时器任务 时间为1s,周期循环 xExampleSoftwareTimer = xTimerCreate ( "LEDTimer" , mainSOFTWARE_TIMER_PERIOD_MS, pdTRUE, ( void *) 0 , vExampleTimerCallback); // 启动软件定时器 xTimerStart (xExampleSoftwareTimer, 0 ); // 回调函数 static void vExampleTimerCallback ( TimerHandle_t xTimer ) { // 每1s进入一次回调函数,计数增加 ulCountOfTimerCallbackExecutions++; PRINTF ( "Soft timer: %d s.\r\n" , ulCountOfTimerCallbackExecutions); } 系统节拍时钟: 通过设置文件 FreeRTOSConfig.h 中 configTICK_RATE_HZ 设置任务节拍中断频率, 在启动任务调度器时,系统会根据另一个变量CPU的频率configCPU_CLOCK_HZ计算对应写入节拍计数器的值,启动定时器中断。 // 设置系统时钟节拍为 1000/200=5ms # define configTICK_RATE_HZ ((TickType_t)200) 信号量: 每个系统节拍时钟中断中,调用函数vApplicationTickHook,累积500次即500*5ms=2.5s后,发送信号量。信号量任务获取信号后,计数并打印累积次数。 // 系统节拍为5ms,每个500*5ms=2.5s 释放事件信号量 void vApplicationTickHook ( void ) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; static uint32_t ulCount = 0 ; ulCount++; if (ulCount = 500UL) { // 在中断中释放事件信号量 xSemaphoreGiveFromISR(xEventSemaphore, xHigherPriorityTaskWoken); ulCount = 0UL; } } // 任务阻塞等待信号量,收到后,接收次数增加,并通过串口打印 static void prvEventSemaphoreTask ( void *pvParameters) { for (;;) { // 任务阻塞,直到能获取信号量 if (xSemaphoreTake(xEventSemaphore, portMAX_DELAY) != pdTRUE) { PRINTF( "Failed to take semaphore.\r\n" ); } // 接收到信号量的次数累加 ulCountOfReceivedSemaphores++; PRINTF( "Event task is running. Get semaphore :%d \r\n" ,ulCountOfReceivedSemaphores); } } 异常处理: 当内存分配失败、堆栈发生错误或任务空闲时,进入相应的函数,用户可添加相应的处理函数。 // 内存分配失败函数,当内存分配失败时,进入此函数 void vApplicationMallocFailedHook ( void ) { for (;;) ; } // 堆栈错误检查函数,当堆栈发生溢出时,进入此函数 void vApplicationStackOverflowHook (TaskHandle_t xTask, char *pcTaskName) { ( void )pcTaskName; ( void )xTask; for (;;) ; } // 空闲任务,优先级最低,没有实际意义,只是让CPU有事情做,用户可以自己添加自己的函数 void vApplicationIdleHook ( void ) { volatile size_t xFreeStackSpace; xFreeStackSpace = xPortGetFreeHeapSize (); if (xFreeStackSpace 100 ) { } } (2)实验现象 ① 编译程序:在uboot手动加载M核程序。 ② 队列:每隔200ms,队列发送任务发送数据,队列接收任务获取数据,从阻塞态到运行态,打印计数。 ③ 软定时器:每隔1s,时间到达,调用回调函数,打印计数。 ④ 信号量:每隔5ms,系统时钟节拍中断调用函数,超过500次后,释放信号量。信号量任务获的信号量,从阻塞态到运行态,打印计数。 2、FreeRTOS-外设 飞凌嵌入式OK-MX9352-C开发板支持外设使用FreeRTOS完成相应功能,示例代码如下: freertos_uart:freertos串口演示例程 freertos_lpi2c_b2b:freertos I2C演示例程 freertos_lpspi_b2b:freertos SPI演示例程 因freertos_uart例程使用的FreeRTOS特性比较典型,我们重点分析此例程。 (1)软件实现 示例程序内容包括:串口初始化任务、串口发送任务、串口接收任务。具体如下: 串口初始化任务: 主要包含串口外设初始化,发送、接收互斥量,发送和接收事件组。串口外设初始化在裸跑串口例程中已展现,此处不再详述。 // 创建串口发送互斥量 handle - txSemaphore = xSemaphoreCreateMutex (); // 创建串口接收互斥量 handle - rxSemaphore = xSemaphoreCreateMutex (); // 创建发送事件标志组 handle - txEvent = xEventGroupCreate (); // 创建接收事件标志组 handle - rxEvent = xEventGroupCreate (); 串口发送: 发送前获取信号量,启动发送流程,在中断中置位发送完成事件标志。发送任务获取到事件后,释放发送信号量。 // 1 获取发送信号量 if (pdFALSE == xSemaphoreTake (handle - txSemaphore, 0 )) { return kStatus_Fail; } handle - txTransfer.data = (uint8_t *)buffer; handle - txTransfer.dataSize = (uint32_t)length; // 2 阻塞式发送 status = UART_TransferSendNonBlocking (handle - base, handle - t_state, handle - txTransfer); if (status != kStatus_Success) { (void) xSemaphoreGive (handle - txSemaphore); return kStatus_Fail; } // 3 等待发送完成的事件 ev = xEventGroupWaitBits (handle - txEvent, RTOS_UART_COMPLETE, pdTRUE, pdFALSE, portMAX_DELAY); // 等待并判断多个事件位 if ((ev RTOS_UART_COMPLETE) == 0 U) { retval = kStatus_Fail; } // 4 发送完成,释放发送信号量 if (pdFALSE == xSemaphoreGive (handle - txSemaphore)) // 释放信号量 { retval = kStatus_Fail; } 串口接收: 接收前获取信号量,调用串口接收函数,在中断中置位获取事件标志。接收任务获取到事件后,释放接收信号量。 // 1获取接收信号量 if (pdFALSE == xSemaphoreTake (handle - rxSemaphore, portMAX_DELAY)) { return kStatus_Fail; } handle - rxTransfer.data = buffer; handle - rxTransfer.dataSize = (uint32_t)length; // 2 串口接收函数 status = UART_TransferReceiveNonBlocking (handle - base, handle - t_state, handle - rxTransfer, n); if (status != kStatus_Success) { (void) xSemaphoreGive (handle - rxSemaphore); return kStatus_Fail; } // 3 获取接收事件 ev = xEventGroupWaitBits (handle - rxEvent,RTOS_UART_COMPLETE | RTOS_UART_RING_BUFFER_OVERRUN | RTOS_UART_HARDWARE_BUFFER_OVERRUN, pdTRUE, pdFALSE, portMAX_DELAY); // 等待并判断接收完成事件位 // 3.1 硬件接收错误 if ((ev RTOS_UART_HARDWARE_BUFFER_OVERRUN) != 0 U) { UART_TransferAbortReceive (handle - base, handle - t_state); (void) xEventGroupClearBits (handle - rxEvent, RTOS_UART_COMPLETE); // 将接收完成的事件位清零, retval = kStatus_UART_RxHardwareOverrun; local_received = 0 ; } // 3.2 接收缓冲区过载错误 else if ((ev RTOS_UART_RING_BUFFER_OVERRUN) != 0 U) { UART_TransferAbortReceive (handle - base, handle - t_state); (void) xEventGroupClearBits (handle - rxEvent, RTOS_UART_COMPLETE); // 将接收完成的事件位清零, retval = kStatus_UART_RxRingBufferOverrun; local_received = 0 ; } // 3.3 接收完成 else if ((ev RTOS_UART_COMPLETE) != 0 U) { retval = kStatus_Success; local_received = length; } else { retval = kStatus_UART_Error; local_received = 0 ; } // 4 释放接收信号量 if (pdFALSE == xSemaphoreGive (handle - rxSemaphore)) { retval = kStatus_Fail; } (2)实验现象 ① 编译程序,在uboot手动加载M核程序。 ② 装置上电后,串口打印程序信息,此时通过键盘输入4个字符,M核调试串口将回显,重复输入和回显字符,证明程序运行成功。 以上就是在飞凌嵌入式i.MX 9352开发板M核上软件设计FreeRTOS的例程演示,希望能够对各位工程师朋友有所帮助。
  • 热度 3
    2025-6-12 10:11
    403 次阅读|
    1 个评论
    ESP32开发之GPIO中断
    电路图 GPIO的中断类型 相关API函数 应用举例 总结 电路图 在ESP32中内部有完整的控制电路,比如上下拉以及滤波器等,所以我们这里可以直接用一个微动开关连接到地。 GPIO的中断类型 GPIO_INTR_DISABLE 不使能中断 GPIO_INTR_POSEDGE 上升沿触发 GPIO_INTR_NEGEDGE 下降沿触发 GPIO_INTR_ANYEDGE 上升沿和下降沿都触发 GPIO_INTR_LOW_LEVEL 低电平触发 GPIO_INTR_HIGH_LEVEL 高电平触发 在GPIO中断应用中,一般使用上升沿或者下降沿触发 相关API函数 GPIO配置结构体 typedef struct { uint64_t pin_bit_mask; /*gpio引脚的位掩码 ,设置为(1ULLGPIO_NUM)*/ gpio_mode_t mode; /*GPIO模式,中断设置为输入模式 */ gpio_pullup_t pull_up_en; /*GPIO上拉使能 */ gpio_pulldown_t pull_down_en; /*GPIO下拉使能 */ gpio_int_type_t intr_type; /*GPIO中断类型 */ # if SOC_GPIO_SUPPORT_PIN_HYS_FILTER gpio_hys_ctrl_mode_t hys_ctrl_mode; /*GPIO迟滞过滤控制模式 */ # endif } gpio_config_t ; GPIO配置函数 esp_err_t gpio_config ( const gpio_config_t *pGPIOConfig) //参数为填充过的gpio_config_t结构体 //返回值:成功返回ESP_OK // 失败返回ESP_ERR_INVALID_ARG,表示参数错误 注册中断 //步骤一:安装中断服务,匹配一个gpio中断 esp_err_t gpio_install_isr_service ( int intr_alloc_flags //用于分配中断的标志,一般为ESP_INTR_FLAG_*,在 esp_intr_alloc.h定义 ) /*返回值: ESP_OK :成功 ESP_ERR_NO_MEM :没有内存安装这个服务 ESP_ERR_INVALID_STATE :中断服务已安装 ESP_ERR_NOT_FOUND :没有找到指定标志的空闲中断 ESP_ERR_INVALID_ARG :gpio错误 */ //步骤二:给gpio中断添加服务函数 esp_err_t gpio_isr_handler_add ( gpio_num_t gpio_num, //gpio编号 gpio_isr_t isr_handler, //中断服务函数 void *args //传递给中断服务函数的参数 ) 应用举例 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-06-10 * @version v0.1 * * @revision history: * 2025-06-10 - Initial version. */ # include stdio.h # include driver/gpio.h # include freertos/FreeRTOS.h # include freertos/task.h # include freertos/event_groups.h # include esp_log.h # define KEY_GPIO GPIO_NUM_7 # define LED_GPIO GPIO_NUM_4 # define EVENT_BIT0 (10) static EventGroupHandle_t gpio_event= NULL ; static void IRAM_ATTR gpio_isr_handler ( void *pvparam) { BaseType_t pxHigherPriorityTaskWoken= pdFALSE; //先储存下来,在此例程中不做处理 xEventGroupSetBitsFromISR (gpio_event,EVENT_BIT0,pxHigherPriorityTaskWoken); } void event_task ( void *pvparam) { while ( 1 ){ if ( xEventGroupWaitBits (gpio_event,EVENT_BIT0,pdTRUE,pdFALSE,portMAX_DELAY)== 0x01 ){ gpio_set_level (LED_GPIO, 1 ); vTaskDelay ( pdMS_TO_TICKS ( 100 )); gpio_set_level (LED_GPIO, 0 ); ESP_LOGI ( "key" , "Event Wait Success!!!" ); } } } /** * @brief GPIO初始化 */ void gpio_init ( void ) { /*配置LED所连接的GPIO*/ gpio_config_t led_config={ .mode=GPIO_MODE_OUTPUT, .pin_bit_mask=( 1ULL LED_GPIO), .pull_up_en=GPIO_PULLUP_DISABLE, .pull_down_en=GPIO_PULLDOWN_DISABLE, .intr_type=GPIO_INTR_DISABLE }; gpio_config (led_config); /*配置key所连接的GPIO*/ gpio_config_t key_config={ .mode=GPIO_MODE_INPUT, .pin_bit_mask=( 1ULL KEY_GPIO), .pull_up_en=GPIO_PULLUP_ENABLE, .pull_down_en=GPIO_PULLDOWN_DISABLE, .intr_type=GPIO_INTR_NEGEDGE }; gpio_config (key_config); /*创建事件组,用于发出中断事件*/ gpio_event= xEventGroupCreate (); /*任务创建 */ xTaskCreatePinnedToCore (event_task, "event_task" , 2048 , NULL , 10 , NULL , 1 ); /*安装中断服务,此函数进行中断寄存器初始化,并注册中断*/ gpio_install_isr_service ( 0 ); /*给特定GPIO添加中断服务函数 */ gpio_isr_handler_add (KEY_GPIO,gpio_isr_handler, NULL ); } void app_main ( void ) { gpio_init (); } 当每次按键按下,将触发中断服务函数,中断服务函数设置事件组,任务函数获取并处理事件。将得到如下情况: 总结 在中断服务函数中不要使用ESP_LOGI或者printf等打印函数,将报错 不能直接使用gpio_isr_register()函数,会报段错误,因为在注册之前需要清除中断寄存器等操作。可以通过分析gpio_install_isr_service()函数原型了解其中缘由。 GPIO中断服务函数前一定要添加IRAM_ATTR关键字,表示将此函数放在内部RAM中,未添加则将放在flash中,中断触发后会反应很慢。
  • 2025-5-18 20:54
    315 次阅读|
    0 个评论
    7. ESP32开发之freeRTOS的互斥量
    什么是互斥量 互斥量的应用场合 互斥量的API函数 基本代码结构 互斥量使用举例 递归锁 递归锁举例 总结 什么是互斥量 在freeRTOS中,多个任务访问一块共享资源,会产生竞争现象。 比如马路上只有一个很早以前的电话亭,A、B都想要打电话,然后他们就开始打架了。但是如果A先进去了然后把门锁了,B想进去打电话的话只能在外面等,必须等到A把门锁打开。 互斥量的应用场合 像freeRTOS的多任务系统,任务A正在使用某个资源,还没用完的时候,任务B也来使用,就可能会导致问题。 就比如串口,任务A正在用串口发送数据,此时任务B也来用这个串口发送数据,这样就会导致数据混乱。 简而言之,多任务使用共享资源的情况下,就需要使用互斥量了。 这里有个特别强调的注意点 :按照正常的情况来说,只要任务A获取了互斥量,其他任务都无法释放互斥量才对。但是freeRTOS中并没有实现,其他任务也可释放互斥量。所以在freeRTOS中,大家默认谁获取互斥量,就该谁释放。 互斥量的API函数 创建互斥量 /* 返回值:正确返回SemaphoreHandle_t 变量,错误返回NULL */ SemaphoreHandle_t xSemaphoreCreateMutex( void ); 获取 /* 参数: xSemaphore : 创建的互斥量 xBlockTime : 获取锁等待时间,超时获取失败 返回值: 获取到,返回pdTRUE,反之,pdFALSE */ SemaphoreHandle_t xSemaphoreTake( xSemaphore, xBlockTime ) 释放 /* 参数: xSemaphore : 创建的互斥量 返回值: 释放成功,返回pdTRUE,反之,pdFALSE */ SemaphoreHandle_t xSemaphoreGive( xSemaphore, xBlockTime ) 基本代码结构 if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ /*资源处理代码,这里可以称之为临界区*/ xSemaphoreGive(xSemaphore); } 互斥量使用举例 首先举一个错误例子,这样会有个对比 void taskA(void* pvPram) { while(1){ for(int i=0;i5;i++){ num++; ESP_LOGI("taskA","num is %d",num); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void taskB(void* pvPram) { while(1){ num++; ESP_LOGI("taskB","num is %d",num); vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { xSemaphore=xSemaphoreCreateMutex(); xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,4,NULL,1); } 打印结果如下:代码目的是taskA执行过程中,num值是连续增加的,然而打印中会发现,taskA打印并不连续,会被taskB抢占。 使用mutex函数 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-05-07 * @version v0.1 * * @revision history: * 2025-05-07 - Initial version. */ #include stdio.h #include freertos/FreeRTOS.h #include freertos/task.h #include freertos/semphr.h #include esp_log.h SemaphoreHandle_t xSemaphore; BaseType_t num=0; void taskA(void* pvPram) { while(1){ /*如果在1S获取到互斥量,那么执行num++五次并打印 */ if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ for(int i=0;i5;i++){ num++; ESP_LOGI("taskA","num is %d",num); } xSemaphoreGive(xSemaphore); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void taskB(void* pvPram) { while(1){ if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ num++; ESP_LOGI("taskB","num is %d",num); xSemaphoreGive(xSemaphore); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { xSemaphore=xSemaphoreCreateMutex(); /*为了产生竞争,两个任务优先级设置一样 */ xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,4,NULL,1); } 打印结果: 分析: 从打印看,当任务A执行完五次num++后,才会轮到任务B执行一次。完全符合执行逻辑。 递归锁 递归锁的引出 假设有两个互斥量M1、M2,taskA获取了M1,taskB获取了M2。然而taskA需要再获取M2才能执行下一步,且taskB也需要再获取M1才能执行下一步。这样就导致了taskA、taskB都无法释放互斥量。导致了死锁。 再假设有一个互斥量M1,任务A获取了M1后,执行一个函数,然而此函数中也获取了M1,此时任务A还没释放M1,这将导致任务A自己把自己锁了。 然而递归锁,可以多次获取,可以解决以上问题 递归锁的函数 /*创建递归锁*/ SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void ); /*获得 */ BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait); /*释放 */ BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore ); 递归锁举例 死锁举例 SemaphoreHandle_t xSemaphore; BaseType_t num=0; void func(BaseType_t *a) { //获取互斥量等待时间设置为portMAX_DELAY,因为taskA为释放互斥量,导致此处一直等待 if(xSemaphoreTakeRecursive(xSemaphore,portMAX_DELAY)==pdTRUE){ *a=*a+1; xSemaphoreGiveRecursive(xSemaphore); } } void taskA(void* pvPram) { while(1){ /*如果在1S获取到互斥量,那么执行num++五次并打印 */ if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ func(num); ESP_LOGI("taskA","num is %d",num); xSemaphoreGive(xSemaphore); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { xSemaphore=xSemaphoreCreateMutex(); /*为了产生竞争,两个任务优先级设置一样 */ xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); } 打印如下:程序将一直锁着,无法执行 使用递归锁 SemaphoreHandle_t xSemaphore; BaseType_t num=0; void func(BaseType_t *a) { //获取互斥量等待时间设置为portMAX_DELAY,因为taskA为释放互斥量,导致此处一直等待 if(xSemaphoreTakeRecursive(xSemaphore,portMAX_DELAY)==pdTRUE){ *a=*a+1; xSemaphoreGiveRecursive(xSemaphore); } } void taskA(void* pvPram) { while(1){ /*如果在1S获取到互斥量,那么执行num++五次并打印 */ if(xSemaphoreTakeRecursive(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ func(num); ESP_LOGI("taskA","num is %d",num); xSemaphoreGiveRecursive(xSemaphore); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { xSemaphore=xSemaphoreCreateRecursiveMutex(); /*为了产生竞争,两个任务优先级设置一样 */ xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); } 打印如下:发现完全可以正常打印了 总结 为了避免死锁,在中断中不要使用互斥量 如果有嵌套使用互斥量,请使用递归锁,递归的层数由queueQUEUE_TYPE_RECURSIVE_MUTEX决定 虽然freeRTOS没有实现谁锁谁开,但是开发过程中还是默认这么操作了
  • 2025-5-18 20:48
    257 次阅读|
    0 个评论
    6. ESP32开发之freeRTOS的信号量
    什么是信号量 信号量能干啥 信号量的函数 实例举例 总结 什么是信号量 简而言之,就是发出通知,接收通知的任务获得通知后去干啥啥。通知有多有少。自定义通知数量的,叫计数型信号量;只有有无(即“0”,“1”)通知的,叫二进制信号量。 信号量能干啥 资源管理:控制多个任务对共享资源(如外设、内存块)的访问权限,避免竞争条件 任务同步 :实现任务间的时序协调(如等待某个事件完成) 中断与任务通信:在中断服务程序(ISR)中快速通知任务处理事件(需使用 xxxFromISR 版本的函数) 信号量的函数 创建二进制信号量函数原型 SemaphoreHandle_t xSemaphoreCreateBinary()//返回值为SemaphoreHandle_t结构体指针,即创建的信号量,不成功返回NULL 释放信号量,使信号量计数加1 xSemaphoreGive(xSemaphore) 获取信号量,使信号量计数减1 xSemaphoreTake(xSemaphore, //创建的信号量 xBlockTime //等待时间,超时则返回pdFulse ); 创建计数型信号量 SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, //最大计数值 UBaseType_t uxInitialCount//初始计数值 );//返回值为SemaphoreHandle_t结构体指针,即创建的信号量,不成功返回NULL 删除信号量 void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ); 实例 二进制信号量的使用 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-04-29 * @version v0.1 * * @revision history: * 2025-04-29 - Initial version. */ #include stdio.h #include freertos/FreeRTOS.h #include freertos/task.h #include freertos/semphr.h #include esp_log.h SemaphoreHandle_t semap=NULL; void taskA(void *pvParam) { while(1){ xSemaphoreGive(semap); vTaskDelay(pdMS_TO_TICKS(1000)); } } void taskB(void *pvParam) { BaseType_t num=0; while(1){ if(xSemaphoreTake(semap,pdMS_TO_TICKS(500))==pdPASS){ num++; ESP_LOGI("taskB","num is %d",num); }else{ ESP_LOGI("taskB","Take failed"); } } } void app_main(void) { semap=xSemaphoreCreateBinary(); xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1); } 结果解析:taskB任务每500ms获取一次信号量,当获取成功则num变量加一,然而taskA任务每1000ms发出一个信号量,所以taskB每间隔500ms会失败一次。 计数型信号量使用 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-04-30 * @version v0.1 * * @revision history: * 2025-04-30 - Initial version. */ #include stdio.h #include stdio.h #include freertos/FreeRTOS.h #include freertos/task.h #include freertos/semphr.h #include esp_log.h SemaphoreHandle_t semap=NULL; void taskA(void *pvParam) { while(1){ xSemaphoreGive(semap); vTaskDelay(pdMS_TO_TICKS(1000)); } } void taskB(void *pvParam) { BaseType_t num=0; while(1){ if(xSemaphoreTake(semap,pdMS_TO_TICKS(400))==pdPASS){ num++; ESP_LOGI("taskB","num is %d",num); }else{ ESP_LOGI("taskB","Take failed"); } } } void app_main(void) { semap=xSemaphoreCreateCounting(3,2); xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,3,NULL,1); } 结果解析:创建计数型信号量时,初始值为2,taskA优先级高些先执行,信号量计数值加1,信号量计数值为3,taskB每400ms获取信号量前三次时成功的,第四次时,由于taskA阻塞时间为1000ms,这个时候其还没发出信号量,信号量计数值为0,所以taskB获取失败。 总结 二进制信号量可以说是计数型信号量的特殊用法。 信号量相对于消息队列要简单很多,区分好应用场景。信号量只传通知,消息队列是要传整个消息结构体的。如果看源码的话,信号量的API背后用的也是队列的API。 二进制信号量对后面学习互斥量给予了启发,一个任务如果take了此信号量不give,另一个任务想take会是什么结果呢?
相关资源