"阻塞--中断"驱动模型在i2c在子系统、uart驱动、spi子系统中的实现

本文深入探讨了I2C通信机制中的关键概念,包括进程阻塞的原因、阻塞后的唤醒时机及整个过程的实现细节。通过分析s3c24xx_i2c_xfer函数,解释了数据如何被传递给I2C从设备,并探讨了传输错误的重试机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近总结了这个kernel中的这个模型,下面我们 开始欣赏吧。

先看i2c中的实现

我们需要明白的是 :

     1. 进程为什么要阻塞
     2.  阻塞后又是 在什么时候被唤醒的?
     3. 从阻塞到唤醒 这中间的过程 是怎么样的 ? 

先上一符图
这里写图片描述

阻塞
这里写图片描述

唤醒
这里写图片描述

阻塞的过程
/**
    这个函数,核心就是调用了s3c24xx_i2c_doxfer来将数据i2c_msg传递给iic从设备
*/
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{
    struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
    /**
        传输错误的重发次数。
    */
    int retry; 
    int ret;
    /**
        这里的 adap->retries 被设置为 2.
        在哪里设置的?
        static int __init i2c_adap_s3c_init(void)
        {
            return platform_driver_register(&s3c24xx_i2c_driver);
        }

        static struct platform_driver s3c24xx_i2c_driver = {
            .probe      = s3c24xx_i2c_probe,
            .remove     = s3c24xx_i2c_remove,
            .id_table   = s3c24xx_driver_ids,
            .driver     = {
                .owner  = THIS_MODULE,
                .name   = "s3c-i2c",
                .pm = S3C24XX_DEV_PM_OPS,
                .of_match_table = s3c24xx_i2c_match,
            },
        };

        导致probe被调用

        static int s3c24xx_i2c_probe(struct platform_device *pdev)
        {
            strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
            i2c->adap.owner   = THIS_MODULE;
            i2c->adap.algo    = &s3c24xx_i2c_algorithm; //iic协议的实现。
            i2c->adap.retries = 2;                      //传递数据到从设备时,发生错误重传的次数
            i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
            i2c->tx_setup     = 50;                     //往寄存器写入数据的延时时间
        }
    */                  
    for (retry = 0; retry < adap->retries; retry++) {

        /**
            msgs : i2c_msg 组成的数组。
            num  : 数组中i2c_msg的个数
            下面看这个函数。
        */
        ret = s3c24xx_i2c_doxfer(i2c, msgs, num);  //传输函数,传输到iic设备的具体函数。

        if (ret != -EAGAIN) {   //传输成功,返回
            clk_disable(i2c->clk);
            pm_runtime_put_sync(&adap->dev);
            return ret;
            }                   
            /*    下面是传输失败执行的代码
                  延时100微秒后重新调用s3c24xx_i2c_doxfer()发送
            */
        dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

        udelay(100); //延时 100微秒。
    }

    clk_disable(i2c->clk); 
    pm_runtime_put_sync(&adap->dev);
    return -EREMOTEIO;     //没有成功传输数据.I/O错误
}

static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,struct i2c_msg *msgs, int num)
{
        ...
        /**
            你要往总线上放数据,总得看看总线是不是处于忙的状态把。
            会尝试400 次 检查 iicstat寄存器bit[5],
        */
        ret = s3c24xx_i2c_set_master(i2c);
        if (ret != 0) 
        {                   
            ...
        }
        /**
            这里上锁了。
            那也就是要说明 : 一次只允许一个进程进行数据的传输。
        */
        spin_lock_irq(&i2c->lock);
        /**
            填充iic_msg 消息 结构体

            详细请见我另外一片博文:
            http://blog.csdn.net/leesagacious/article/details/50488949
        */
        i2c->msg     = msgs;     
        i2c->msg_num = num;
        i2c->msg_ptr = 0;
        i2c->msg_idx = 0;
        i2c->state   = STATE_START; //总线状态 : 总线开始状态
        /**
            使能中断。为什么要使能中断?   明白这个很重要。

            iic设备是一个慢速设备,再读写过程中,进程休眠。
            当数据发送完成后,在中断处理函数中会唤醒该休眠的进程。
        */
        s3c24xx_i2c_enable_irq(i2c); 
        /**
            主角终于闪亮登场了....
            这个函数并没有进行数据的传输,数据的传输是放到了中断处理函数中的。
            它只做了两件事情
                        1  : 写从设备地址到寄存器
                        2  : 发送Start信号
            下面详细说。
        */
        s3c24xx_i2c_message_start(i2c, msgs);   
        spin_unlock_irq(&i2c->lock);
        /**
            看,进程休眠了吧..............。
            它在等待数据发送完成。

            具体的是再 s3c24xx_i2c_stop()函数中会调用wake_up()来唤醒该等待队列上的进程
        */
        timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
        ....
        ....    
    }   
}
唤醒的过程

先看是在什么时候注册的中断处理函数

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
        ....
        ....
        i2c->irq = ret = platform_get_irq(pdev, 0);
        ...
        /**
            看注册了中断处理函数
        */
        ret = request_irq(i2c->irq, s3c24xx_i2c_irq, 0,  dev_name(&pdev->dev), i2c);
        ....
}

好,那么我们来看看这个中断处理函数是 怎么来 设计的
代码分析 请见 我以前的博文
http://blog.csdn.net/leesagacious/article/details/50488949

这里主要说 是怎么来实现的

static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{

}
实验验证
UART中的实现
实验验证
spi中的实现
实验验证
<think>好的,我现在需要帮助用户了解如何在开发板上使用SPII2CUART驱动OLED显示,并通过中断处理数据,同时实现一个多级菜单,包含谷歌小恐龙游戏、音乐切换和退出功能。这个问题涉及多个模块和通信协议,我需要逐步拆解每个部分,并确保回答结构清晰,符合用户的要求。 首先,我需要确认用户提到的开发板型号。用户提供的引用中提到了MM32F5270和STM32F401RCT6,可能用户使用的是类似型号的开发板。虽然不同开发板的配置可能略有不同,但基本思路是通用的。我需要指导用户配置这些通信接口,并确保使用中断模式而不是轮询。 接下来,关于通信接口驱动OLED显示。OLED通常使用I2CSPI接口,用户可能已经具备相关的硬件连接,但需要正确配置GPIO引脚和协议参数。例如,在I2C模式下,需要设置正确的时钟速度、地址,并实现数据传输函数。同时,用户希望使用中断处理数据,这意味着在数据传输过程中,应启用中断来处理发送和接收,而不是轮询状态寄存器。 然后是UART通信,用户可能需要与其他模块如蓝牙通信,引用中提到波特率需要设置为9600。这里需要配置UART中断,以便在接收到数据时触发中断服务程序,处理数据而不阻塞主程序。 多级菜单的实现需要状态机或菜单树结构。每个菜单选项对应不同的功能,比如进入游戏、切换音乐或退出。用户输入(如按键)需要通过中断处理,以实时响应操作。同时,音乐切换可能涉及PWM或DAC输出,需要配置定时器或DAC模块,并在中断中更新音频数据。 谷歌小恐龙游戏的移植需要图形库支持,可能涉及帧缓冲和按键中断处理。游戏逻辑需要在主循环中运行,同时响应中断事件,如恐龙的跳跃。这里需要确保游戏循环不与菜单系统冲突,可能通过状态标志来控制当前界面。 中断驱动实现是关键。每个外设(SPII2CUART、按键)都需要配置其中断优先级和中断服务例程。需要避免中断冲突,并确保高优先级的中断(如用户输入)能够及时响应。 最后,整合所有功能到多级菜单中。主程序应初始化所有外设,进入菜单循环,根据用户选择跳转到相应功能。退出选项需要清理资源,返回主菜单或关闭系统。 现在需要检查是否有遗漏的部分,比如各个模块的初始化顺序、中断优先级的设置、资源冲突的可能性。例如,SPII2C使用相同DMA通道可能导致问题,需要分开配置。此外,OLED的刷新率需要足够高以避免闪烁,中断处理函数应尽量简短,防止影响系统实时性。 可能还需要考虑内存管理,特别是在游戏和音乐播放时,需要足够的缓冲区。如果开发板资源有限,需优化代码结构,避免动态内存分配,使用静态变量。 总结步骤: 1. 配置SPI/I2C驱动OLED,使用中断传输数据。 2. 配置UART中断处理通信。 3. 实现多级菜单系统,处理用户输入中断。 4. 移植小恐龙游戏,处理图形和按键中断。 5. 实现音乐播放功能,使用定时器中断生成音频。 6. 整合所有功能,测试各模块协同工作。 需要确保每个部分的代码示例正确,特别是中断服务函数的编写,以及菜单状态机的转换。同时,引用用户提供的资料,如OLED引脚定义和UART配置,以增强参考性。</think>根据需求可分为硬件驱动层、中断管理、功能实现三部分,以下为分步实施方案: 一、通信协议配置(以STM32CubeMX为例) 1. SPI配置(OLED驱动) ```c //SPI1 Mode: Full-Duplex Master //Data Size: 8 bits //Baud Rate: 10 MHz //CPOL: Low, CPHA: 1 Edge //启用DMA传输和SPI中断 HAL_SPI_Transmit_IT(&hspi1, data, size); ``` 2. I2C配置(备用方案) ```c //I2C1 Mode: Fast Mode (400kHz) //启用DMA和I2C事件中断 HAL_I2C_Mem_Write_IT(&hi2c1, OLED_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data, size); ``` 3. UART配置(参考引用[3]) ```c //USART2 异步模式 //波特率9600(与BT04-E兼容) //启用接收中断 __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE); ``` 二、中断驱动实现 1. 创建中断优先级分组(NVIC配置): ``` | 中断源 | 优先级 | 子优先级 | |---------------|--------|----------| | 按键输入 | 0 | 0 | | SPI传输完成 | 1 | 1 | | UART接收 | 2 | 0 | | 系统定时器 | 3 | 0 | ``` 2. 中断服务例程模板: ```c void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi->Instance == SPI1) { oled_update_flag = 0; //传输完成标志 } } void USART2_IRQHandler(void) { if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE)) { uint8_t ch = (uint8_t)(USART2->DR); uart_buffer[uart_idx++] = ch; if(ch == '\n' || uart_idx >= BUF_SIZE) { process_uart_command(); uart_idx = 0; } } } ``` 三、多级菜单系统实现 1. 菜单数据结构设计: ```c typedef struct { char title[16]; void (*action)(void); MenuItem *submenu; } MenuItem; MenuItem main_menu[] = { {"游戏", enter_game, NULL}, {"音乐", NULL, music_menu}, {"设置", NULL, setting_menu}, {"退出", shutdown, NULL} }; MenuItem music_menu[] = { {"播放/暂停", toggle_music}, {"上一曲", prev_song}, {"下一曲", next_song}, {"返回", back_menu} }; ``` 2. 按键中断处理(参考引用[1]引脚定义): ```c void EXTI0_IRQHandler(void) { if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) { current_menu = current_menu->submenu; //进入子菜单 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); } } ``` 四、小恐龙游戏移植关键步骤(参考引用[1][2]) 1. 显示驱动适配: ```c //修改引用[1]的oled12864.h #define OLED_SCL_GPIO_Port GPIOB #define OLED_SDA_GPIO_Port GPIOB #define OLED_SCL_Pin GPIO_PIN_6 #define OLED_SDA_Pin GPIO_PIN_7 void oled_draw(uint8_t x, uint8_t y, const uint8_t *bitmap) { //使用DMA+SPI传输帧缓冲 HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)framebuffer, 1024); } ``` 2. 游戏主循环(非阻塞式): ```c void game_loop(void) { while(game_running) { update_dinosaur_position(); //基于定时器中断计数 check_collision(); refresh_display(); //使用双缓冲机制 HAL_Delay(33); //约30FPS } } ``` 五、音乐播放实现 1. PWM音频输出配置: ```c //TIM3 CH1 PWM配置 htim3.Instance = TIM3; htim3.Init.Prescaler = 84-1; //1MHz时钟 htim3.Init.Period = 255; 8位分辨率 HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); //DAC配置(高质量音频) hdac.Instance = DAC; hdac.DAC_Trigger = DAC_TRIGGER_T6_TRGO; //定时器触发 ``` 2. 音频解码中断: ```c void TIM6_DAC_IRQHandler(void) { if(__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE)) { uint16_t sample = decode_next_sample(); HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, sample); __HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE); } } ``` 六、系统整合建议 1. 使用RTOS任务划分(FreeRTOS示例): ```c xTaskCreate(menu_task, "Menu", 256, NULL, 1, NULL); xTaskCreate(game_task, "Game", 512, NULL, 2, NULL); xTaskCreate(audio_task, "Audio", 256, NULL, 3, NULL); ``` 2. 资源管理策略: - 显示使用双缓冲机制 - 音频数据使用DMA循环缓冲 - 游戏物理计算使用定时器中断 - 用户输入采用队列缓冲 完整工程应包含以下目录结构: ``` ├── Drivers ├── Inc │ ├── menu.h │ ├── oled.h │ └── game.h ├── Src │ ├── main.c # 主循环和任务调度 │ ├── interrupt.c # 中断服务程序 │ ├── spi_oled.c # 显示驱动(参考引用[1]) │ └── uart_ctrl.c # 串口控制(参考引用[3]) └── Assets ├── game_sprite.h # 游戏素材 └── music.bin # 音频资源 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值