Hi3515按键中断驱动程序(完整版)

摸索了一个星期,终于把海思HI3515开发板的按键中断程序搞出来了,hi3515的核心芯片与网上例子较多的s3c之类的有一些区别,以至于浪费了好些时间去琢磨。管脚配置方式不一样,中断的使用情况也不一样。而比较麻烦的是网上关于海思的资料太少了。对于水平不太高的人,老自己摸索还是会走不少弯路。现在就把本人写的能在开饭板测试运行通过的程序贴出来,但愿这个不会违反到保密协议的内容,代码可都是我自己写的哈。希望能给后来者带来些帮助,也希望大家多提意见,一起进步·,^_^

第一步,编写按键驱动程序,button.c代码如下:

/*所有模块都需要的头文件*/

#include<linux/module.h>

/*声明printk()这个内核态的函数*/

#include<linux/kernel.h>

/*文件系统有关的,结构体file_operations也在fs头文件定义*/

#include<linux/fs.h>

/*init和exit相关宏*/

#include<linux/init.h>

#include<linux/delay.h>

#include<linux/poll.h>

/*linux中断定义*/

#include<linux/irq.h>

/**/

#include<asm/irq.h>

/*包含与中断相关的大部分宏及结构体的定义,request_irq()等*/

#include<linux/interrupt.h>

/*linux中的用户态内存交互函数,copy_from_user(),copy_to_user()等*/

#include<asm/uaccess.h>

//#include<mach/regs-gpio.h>

//#include<mach/hardware.h>

#include<linux/platform_device.h>

#include<linux/cdev.h>

/*misc混合设备注册与注销*/

#include<linux/miscdevice.h>

#include <asm/io.h>

#include <asm/system.h>

#define BUTTON_READ 0x01

#define DEVICE_NAME "BUTTON_irq"

#define REG_WRITE(addr,value) ((*(volatile unsigned int *)(addr)) = (value))

#define REG_READ(Addr) (*(volatile unsigned int *)(Addr))

static unsigned int gpio3_virtual_addr = 0;

static unsigned int reg_virtual_addr = 0;

/*数组中是否有数据标志,0表示无数据可读,1表示有数字可读*/

static volatile char key;

/*定义和初始化一个等待队列头*/

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/*定义一个整形变量,判断按键是否按下*/

static volatile int ev_press = 0;

/*

*定义结构体类型,由它把按钮中断的信息综合起来

*/

struct button_irq_desc {

int irq;/*中断号*/

int pin;/*中断标志寄存器,有中断产生时为1,无中断时为0*/

int number;/*编号*/

char *name;/*名称*/

};

static struct button_irq_desc button_irqs[]={

{8,2,1,"KEY1"},

};

static void hi3515_button_pin_cfg(void)

{

/*配置作为普通输入*/

REG_WRITE(reg_virtual_addr + 0x08,0x1);/*reg2管脚复用配置gpio3_0,按键1*/

REG_WRITE(reg_virtual_addr + 0x0c,0x1);/*reg3管脚复用配置gpio3_1,按键1*/

REG_WRITE(reg_virtual_addr + 0x10,0x1);/*reg4管脚复用配置gpio3_2,按键1*/

REG_WRITE(reg_virtual_addr + 0x14,0x1);/*reg5管脚复用配置gpio3_3,按键2*/

/*管脚中断配置*/

REG_WRITE(gpio3_virtual_addr + 0x0400,0x3);/*dir设置管脚为0-1:输出,2-3:输入*/

REG_WRITE(gpio3_virtual_addr + 0x0404,0xc);/*is边沿触发中断*/

REG_WRITE(gpio3_virtual_addr + 0x040c,0x0);/*iev低电平触发*/

REG_WRITE(gpio3_virtual_addr + 0x041c,0xff);/*ic清除中断*/

REG_WRITE(gpio3_virtual_addr + 0x0410,0x04);/*ie启用中断*/

}

/*

*gpio地址映射

*/

static int virtual_addr_map(void)

{

reg_virtual_addr = (unsigned int)ioremap_nocache(0x200f0000,0x4000);

if(!reg_virtual_addr)

{

printk("0x200f0000 ioremap addr failed !\n");

return -1;

}

gpio3_virtual_addr = (unsigned int)ioremap_nocache(0x20180000,0x4000);

if(!gpio3_virtual_addr)

{

printk("0x20180000 ioremap addr failed !\n");

return -1;

}

}

/*取消地址映射*/

static void virtual_addr_unmap(void)

{

iounmap((void*)gpio3_virtual_addr);

iounmap((void*)reg_virtual_addr);

}

/*

*read调用的具体函数,由它读取键盘输入的结果,

*实质上就是读取key_values数组的值

*完成键盘输入设备的核心功能,根据标志位ev_press判断是否可读

*如果可读,则读取数据到用户buffer中,如果不可读,

*则进程进入等待队列等待,直到数组可读为止

*等待队列机制,所中断管理中常用的机制。

*/

static int button_irq_read(struct file *filp,

char __user *buff,

size_t count,loff_t *offp)

{

unsigned long err;

#if 1

if(!ev_press) /*ev_press=0,则表示没有数据可读*/

{

if(filp->f_flags & O_NONBLOCK)

return -EAGAIN;

else /*无数据可读时,进程休眠,放进button_waitq等待队列*/

wait_event_interruptible(button_waitq,ev_press);

/*

*wait_event_interruptible()函数将进程置为可中断的挂起状态

*反复检查ev_press=1是否成立,如果不成立,则继续休眠。

*条件满足后,即把本程序置为运行态,

*/

}

/*ev_press=1之后,进程退出等待队列。从此处开始运行*/

ev_press = 0;/*置0标志位,表明本次中断已经处理*/

err = copy_to_user(buff,&key,sizeof(key));

/*把按键值传会用户空间*/

#endif

return 0;

}

static irqreturn_t irq_interrupt(int irq,void *dev_id)

{

#if 1

/*对传入的中断资源进行处理,获得中断控制寄存器的值(即是否有数据)

取反后赋值给down,为0时说明有数据,

注意按下依次按钮有两次中断,

对数组可读标志位进行设置,ev_press=1表示数组已经可以读了*/

//struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;

int down;

down = 0x0c & REG_READ(gpio3_virtual_addr + 0x0414);

REG_WRITE(gpio3_virtual_addr + 0x041c,0xff);/*ic清除按键中断*/

mdelay(5);/**/

if(down != 0x0c )

{

key = (char)down;

ev_press = 1; /*置1标志位,唤醒等待队列进程,在read函数中使用*/

/*

*唤醒休眠的进程,用户空间程序使用调用read函数时,

*如果没有产生中断,进程就会进入休眠状态,一直等待,直到产生中断

*中断产生后,通过wake_up_interruptible()函数唤醒休眠进程

*/

wake_up_interruptible(&button_waitq);

}

#endif

REG_WRITE(gpio3_virtual_addr + 0x0410,0x0c);/*ie启用按键中断*/

return IRQ_HANDLED; //IRQ_HANDLED=1

}

/*

*poll调用的具体函数,poll实质上是select的调用函数

*如果有按键数据,则select会立刻返回

*如果没有按键数据,则等待

*实质上这是键盘等待输入的机制

*poll_wait()会监测进程队列button_waitq里的进程

*例如button_irq_read所在的进程的标志ev_press置为1了

*那么就不再等待,这实质上就所select函数的运行机制

*/

static unsigned int button_irq_poll(struct file *file, struct poll_table_struct *wait)

{

#if 1

/*

*poll调用的具体函数,poll实质上是select的调用函数

*如果有按键数据,则select会立刻返回

*如果没有按键数据,则等待

*实质上这是键盘等待输入的机制。

*select调用是用户程序里面使用的。

*/

unsigned int mask = 0;

poll_wait(file,&button_waitq,wait);

/*poll_wait会检测button_waitq里的进程*/

if(ev_press)

mask |=POLLIN | POLLRDNORM;

return mask;

#endif

}

static int button_irq_open(struct inode *inode,struct file *file)

{

#if 1

int err;/*中断注册返回值*/

virtual_addr_map(); /*地址映射*/

hi3515_button_pin_cfg();/*管脚配置,要先进行地址映射*/

/*注册中断*/

err = request_irq(8,irq_interrupt,IRQF_SHARED,\

"KEY",(void *)&button_irqs);

if(err)/*如果注册中断失败,则释放已经成功注册的中断*/

{

return -EBUSY;

}

ev_press = 1;

#endif

return 0;

}

static int button_irq_close(struct inode *inode,struct file *file)

{

free_irq(8,(void *)&button_irqs);

virtual_addr_unmap();/*取消地址映射*/

return 0;

}

int button_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

{

unsigned int __user *argp = (unsigned int __user *)arg;

int value;

value = *(unsigned int *)arg;

switch (cmd)

{

case 1:

if(value == 0) /*led1亮*/

REG_WRITE(gpio3_virtual_addr +0x4,0);

else if(value == 1) /*led1灭*/

REG_WRITE(gpio3_virtual_addr +0x4,1);

break;

case 2:

if(value == 0) /*led2亮*/

REG_WRITE(gpio3_virtual_addr +0x8,0);

else if(value == 1) /*led2灭*/

REG_WRITE(gpio3_virtual_addr +0x8,2);

break;

default:

return -1;

}

return 0;

}

static struct file_operations dev_fops = {

.owner = THIS_MODULE,

.open = button_irq_open,

.release = button_irq_close,

.ioctl = button_ioctl,

.read = button_irq_read,

.poll = button_irq_poll,/*用户程序使用select调用的时候才会用到poll*/

};

/*

*misc混合设备注册和注销

*/

static struct miscdevice misc = {

.minor = MISC_DYNAMIC_MINOR,/*次设备号*/

.name = DEVICE_NAME,/*设备名*/

.fops = &dev_fops,/*设备文件操作结构体*/

};

static int __init button_init(void)

{

int ret;

ret = misc_register(&misc);

if(0 != ret)

{

printk("register device failed! !\n");

return -1;

}

printk("register device success !\n");

return 0;

}

static void __exit button_exit(void)

{

misc_deregister(&misc);

printk("unregister device success !\n");

}

module_init(button_init);

module_exit(button_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Dong");

第二步。,编写测试程序,test_button.c代码如下

/*

*按键中断测试程序

*按键被按下时,产生中断

*打印按下信息,切换led显示状态

*/

#include <stdio.h>

#include <ctype.h>

#include <sys/ioctl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include<linux/delay.h>

int main(int argc , char* argv[])

{

int fd = -1;

unsigned int led1;

unsigned int led2; ;

char key;

fd = open("/dev/BUTTON_irq", 0);

if (fd<0)

{

printf("Open BUTTON_irq dev error!\n");

return -1;

}

for(;;)

{

int ret;

ret = read(fd,&key,sizeof(key));

if(ret< 0)

{

perror("read button:");

return -1;

}

if(key == 4)/*按键1被按下*/

{

printf("K1 is press!\n");

led1 = (~led1)&0x1;

ioctl(fd, 0x01, &led1); /*切换led1的状态*/

}

if(key == 8)/*按键2被按下*/

{

printf("K2 is press!\n");

led2 = (~led2)&0x1;

ioctl(fd, 0x02, &led2); /*切换led2的状态*/

}

//printf("\n");

}

close(fd);

return 0;

}

第三步,makefile文件,代码如下:

LINUXROOT = /opt/Hi3515_SDK_V1.0.5.1/source/os/linux-2.6.24

#这是放内核的路径

CC = arm-hismall-linux-gcc

obj-m := button.o

default:

$(CC) -g -Wall -o test_button test_button.c

@make -C $(LINUXROOT) M=$(PWD) modules

rm -rf *.o *.mod.c *.symvers

clean:

@make -C $(LINUXROOT) M=$(PWD) clean

<think>好的,我现在需要帮用户解决关于STM32F103C8T6使用OLED显示界面的问题。首先,我需要回顾一下这个芯片和OLED模块的基本情况。STM32F103C8T6是ST的Cortex-M3微控制器,有足够的资源驱动小型OLED屏,通常用的是SSD1306驱动的0.96或1.3寸屏,SPI或I2C接口。用户可能想知道如何连接硬件、初始化、编写驱动以及设计界面。 首先,硬件连接部分。用户需要确定OLED模块的接口类型,常见的是4线SPI或I2C。如果是SPI,需要连接CS、DC、RES、DIN、CLK这些引脚;I2C的话则用SCL和SDA,可能还有RES和DC,具体看模块设计。要提醒用户注意模块的工作电压,有些是3.3V,有些是5V,STM32是3.3V电平,需要匹配。 然后是软件部分。用户需要初始化OLED,可能包括GPIO配置、SPI/I2C外设初始化、发送初始化命令序列。接着是驱动函数的编写,比如清屏、写数据、画点等。之后是界面设计,比如显示字符、汉字、图形,可能还需要菜单结构或动画效果。 需要考虑用户可能遇到的常见问题,比如显示乱码、通信失败、刷新慢等。可能的原因包括引脚配置错误、通信速率过高、初始化命令不正确,或者内存不足导致缓冲区溢出。这时候需要建议用户检查硬件连接,使用示波器或逻辑分析仪抓波形,或者分步骤调试初始化代码。 另外,用户可能希望使用现有的库来简化开发,比如使用ST的HAL库或者第三方开源驱动。这时候可以推荐一些常用的库,或者提供代码示例。比如,使用HAL库的I2C函数发送命令和数据,或者模拟GPIO的方式实现SPI通信,特别是在高速情况下可能需要优化时序。 界面显示方面,可能需要分层次设计,比如底层驱动函数、中间件(显示字符、图形)、上层应用(菜单系统)。用户可能需要了解如何管理显示内容,比如双缓冲机制避免闪烁,或者局部刷新提高效率。 还要提醒用户注意资源限制,STM32F103C8T6的Flash和RAM较小,如果界面复杂可能需要优化代码,或者使用压缩的字体和图像数据。同时,如果使用中文字库,需要考虑存储空间,可能需要外部存储或者精简字库。 最后,可能的扩展功能,比如通过按键切换界面,或者与传感器数据结合显示实时信息。这时候需要涉及中断处理、状态机设计等,可能需要用户进一步学习相关内容。 总结下来,回答的结构应该是硬件连接、软件驱动、界面设计、常见问题及优化建议,同时提供示例代码和资源推荐。需要确保步骤清晰,用户能逐步实现,避免遗漏关键点,比如初始化序列或通信协议的正确配置。同时,使用中文,保持口语化,但技术术语准确,确保用户理解。</think>在STM32F103C8T6上使用OLED实现界面显示可分为以下步骤: --- ### 一、硬件准备 1. **OLED模块** - 推荐0.96寸SSD1306驱动芯片的OLED,支持I2C/SPI接口 - 分辨率:128x64像素 - 工作电压:3.3V(与STM32电平兼容) 2. **接线方式(以I2C为例)** | OLED引脚 | STM32引脚 | |----------|-----------| | SDA | PB7 | | SCL | PB6 | | VCC | 3.3V | | GND | GND | --- ### 二、软件驱动开发 1. **CubeMX配置** - 启用I2C1(模式:I2C,速度:400kHz) - 配置PB6(SCL)和PB7(SDA)为复用开漏模式 2. **OLED初始化代码** ```c void OLED_Init(void) { HAL_Delay(100); OLED_WriteCmd(0xAE); // 关闭显示 OLED_WriteCmd(0xD5); // 设置时钟分频 OLED_WriteCmd(0x80); OLED_WriteCmd(0xA8); // 设置复用率 OLED_WriteCmd(0x3F); OLED_WriteCmd(0xD3); // 设置显示偏移 OLED_WriteCmd(0x00); OLED_WriteCmd(0x40); // 设置起始行 // ...完整初始化序列参考SSD1306手册 OLED_WriteCmd(0xAF); // 开启显示 } ``` --- ### 三、显示功能实现 1. **基础显示函数** ```c // 清屏函数 void OLED_Clear(void) { memset(OLED_Buffer, 0, sizeof(OLED_Buffer)); OLED_Refresh(); } // 刷新函数(将显存数据写入OLED) void OLED_Refresh(void) { for(uint8_t page=0; page<8; page++) { OLED_WriteCmd(0xB0 + page); // 设置页地址 OLED_WriteCmd(0x02); // 列地址低4位 OLED_WriteCmd(0x10); // 列地址高4位 HAL_I2C_Mem_Write(&hi2c1, OLED_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT, &OLED_Buffer[page*128], 128, 100); } } ``` 2. **显示汉字(需字库)** ```c void OLED_ShowChinese(uint8_t x, uint8_t y, uint8_t index) { uint8_t i; for(i=0; i<16; i++) OLED_Buffer[x + (y+i/8)*128] = ChineseLib[index][i]; } ``` --- ### 四、界面设计技巧 1. **分层架构** - 底层驱动:`OLED_WriteCmd()`/`OLED_WriteData()` - 中间层:字符/图形绘制函数 - 应用层:菜单系统、动画逻辑 2. **菜单系统示例** ```c typedef struct { char *text; void (*func)(void); } MenuItem; MenuItem mainMenu[] = { {"温度监控", ShowTemp}, {"设置参数", EnterSetting}, {"系统信息", ShowSysInfo} }; ``` --- ### 五、常见问题解决 1. **无显示** - 检查I2C地址(通常0x78或0x7A) - 确认初始化序列正确 - 测量SCL/SDA波形 2. **显示乱码** - 检查显存刷新逻辑 - 确认字库数据对齐 - 降低I2C时钟频率测试 --- ### 六、优化建议 1. 使用双缓冲机制减少闪烁 2. 采用局部刷新策略(仅更新变化区域) 3. 通过DMA传输提升刷新效率 完整工程建议参考STM32CubeIDE+HAL库实现,典型帧率可达30fps以上。实际开发时可借助现成的开源驱动库(如`Arduino_SSD1306`移植版)加快开发进度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值