LINUX外接TM1650键盘,由于TM1650的接口不是标准的I2C接口,只能通过操作GPIO方式模拟I2C通信,实现对TM1650的驱动;
问题1:通过linux的文件读写GPIO方式,是否支持微秒级别拉高拉低控制,通过示波器验证没有问题,完全支持微秒级别的gpio控制;
问题2:中断引脚如何控制;
当前解决方式是轮训查询中断引脚value值来判断是否有中断;另一种方式就是使用poll监听多路复用的方式监听是否有中断产生;
对于使用中断可以使用poll多路复用IO监测中断是否触发,poll事件有POLLIN、POLLOUT、POLLERR、POLLPRI 等,其中 POLLIN 和 POLLOUT 表示普通优先级数据可读、可写。中断就是一种高优先级事件,需要使用 POLLPRI ,当中断触发时表示有高优先级数据可被读取。 此外还可以使用信号方式监测GPIO是否触发中断。
TM1650的驱动代码参考链接实现,基于参考链接来操作GPIO,注意这个链接的几个方法没有close 文件句柄,需要注意修改;
key_pad.h
//key_pad.h #ifndef KEY_PAD_ #define KEY_PAD_ #include <stdio.h> #include <stdint.h> #include <stdlib.h> /*************************************宏定义********************/ /* J8707 键盘 1 SCK 144 2 SDA 145 3 INT 输入中断 194 4 Light 背光控制,IO,无驱动能力 196 */ #define TM1650_SCK_GPIO 144 #define TM1650_SDA_GPIO 145 #define TM1650_IRQ_GPIO 194 /***********************键盘丝印值定义*****************************/ typedef enum KEY_VALUE_{ INPUT_KEY_INVALID=-1, INPUT_KEY_0 = 0, INPUT_KEY_1, INPUT_KEY_2, INPUT_KEY_3, INPUT_KEY_4, INPUT_KEY_5, INPUT_KEY_6, INPUT_KEY_7, INPUT_KEY_8, INPUT_KEY_9, INPUT_KEY_STAR = 20, INPUT_KEY_PROUND = 21, INPUT_KEY_PAGE_UP = 22, INPUT_KEY_PAGE_DOWN = 23, INPUT_KEY_BACK = 24, INPUT_KEY_OK = 25, }KEY_VALUE; /*************************************函数定义********************/ extern void TM1650_init(void); extern uint8_t TM1650_Irq_Set(void); extern void i2c_read_data(uint8_t *key_value); //将按键值转换为键盘丝印值 extern int get_input_key_value(uint8_t key_value); #endif//
key_pad.c
//key_pad.c #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <sys/types.h> #include <sys/select.h> #include <unistd.h> #include <fcntl.h> #include "key_pad.h" #include "gpio.h" /*************************************函数定义********************/ static inline int gpio_input_bit_get(int gpio, uint8_t switch_in_mode); static inline void gpio_bit_set_mode(int gpio, uint8_t in_mode); static void TM1650_IIC_start(void); static void TM1650_IIC_write_byte(uint8_t dat); static uint8_t TM1650_IIC_wait_ack(void); static void TM1650_IIC_stop(void); static uint8_t TM1650_IIC_read_byte(void); #define TRUE 1 #define FALSE 0 //========【配置IIC总线的信号读写和时序】======= //主机拉高SCL #define TM1650_IIC_SCL_HIGH gpio_bit_set(TM1650_SCK_GPIO) //主机拉低SCL #define TM1650_IIC_SCL_LOW gpio_bit_reset(TM1650_SCK_GPIO) //输入 #define TM1650_IIC_SDA_SET_IN gpio_bit_set_mode(TM1650_SDA_GPIO, TRUE) #define TM1650_IIC_SDA_SET_OUT gpio_bit_set_mode(TM1650_SDA_GPIO, FALSE) //主机拉高SDA #define TM1650_IIC_SDA_HIGH gpio_bit_set(TM1650_SDA_GPIO) //主机拉低SDA #define TM1650_IIC_SDA_LOW gpio_bit_reset(TM1650_SDA_GPIO) //参数b为0时主机拉低SDA,非0则拉高SDA #define TM1650_IIC_SDA_WR(b) do{ \ if(b) gpio_bit_set(TM1650_SDA_GPIO); \ else gpio_bit_reset(TM1650_SDA_GPIO); \ }while(0) //主机读取SDA线电平状态,返回值为0为低电平,非0则为高电平 #define TM1650_IIC_SDA_RD() gpio_input_bit_get(TM1650_SDA_GPIO, FALSE) //软件延时2us #define TM1650_IIC_DELAY_2US do{for(int ii_=0;ii_<22;ii_++);}while(0) //软件延时4us #define TM1650_IIC_DELAY_4US do{sleep_inner(0, 4);}while(0) #define TM1650_IIC_DELAY_5US do{sleep_inner(0, 5);}while(0) /*************************************函数实现********************/ void sleep_inner (long sec, long usec) { struct timeval timeout = {sec, usec}; int ret = 0; if ((0 == timeout.tv_sec) || (timeout.tv_usec < 20)) { //printf("local sleep_inner error! input sleep_inner time must greater than 20ms !\n"); //timeout.tv_usec = 20; } ret = select(0, NULL, NULL, NULL, &timeout); if ((-1 == ret) || (ret)) { printf("local sleep_inner error!\n"); } } void i2c_read_data(uint8_t *key_value){ TM1650_IIC_start(); TM1650_IIC_write_byte(0x49); //起始地址 0x49 TM1650_IIC_wait_ack(); *key_value = TM1650_IIC_read_byte(); TM1650_IIC_stop(); printf("%s exit test \r\n",__FUNCTION__); } void i2c_write_data(uint8_t address, uint8_t data){ TM1650_IIC_start(); TM1650_IIC_write_byte(address); TM1650_IIC_wait_ack(); //显存起始地址为0x68 TM1650_IIC_write_byte(data); TM1650_IIC_wait_ack(); //发送段码 TM1650_IIC_stop(); } static inline void key_gpio_init(int gpio){ gpio_unexport(gpio); #if 1 char cmd[255] = ""; sprintf(cmd, "echo %d > /sys/class/gpio/export", gpio); system(cmd); #else gpio_export(gpio); #endif } static int tm1650_init_inner() { TM1650_IIC_start(); TM1650_IIC_write_byte(0x48); TM1650_IIC_wait_ack(); TM1650_IIC_write_byte(0x08|0x00|0x01); //0x08:7段模式;0x00:正常工作模式;0x01:开屏(开启键扫描) TM1650_IIC_wait_ack(); TM1650_IIC_stop(); return 0; } //TM1650初始化 void TM1650_init(void) { key_gpio_init(TM1650_SCK_GPIO); key_gpio_init(TM1650_SDA_GPIO); key_gpio_init(TM1650_IRQ_GPIO); gpio_direction_input(TM1650_IRQ_GPIO); //gpio_edge_falling(TM1650_IRQ_GPIO); gpio_direction_output(TM1650_SCK_GPIO); gpio_direction_output(TM1650_SDA_GPIO); sleep_inner(0,5000);// tm1650_init_inner(); sleep_inner(0,5000);// printf("TM1650_init end\r\n"); } uint8_t TM1650_Irq_Set(void){ int ret = gpio_get_value(TM1650_IRQ_GPIO); if (ret == 0){ return TRUE; } return FALSE; } static inline void gpio_bit_set_inner(int gpio, uint8_t high){ #if 0 char cmd[255] = ""; sprintf(cmd, "echo out > /sys/class/gpio/gpio%d/direction", gpio); //printf("cmd1:%s\r\n", cmd); system(cmd); sprintf(cmd, "echo %d > /sys/class/gpio/gpio%d/value", high?1:0, gpio); //printf("cmd2:%s\r\n", cmd); system(cmd); #else gpio_set_value(gpio, high); #endif } void gpio_bit_set(int gpio){ gpio_bit_set_inner(gpio, 1); } void gpio_bit_reset(int gpio){ gpio_bit_set_inner(gpio, 0); } static inline void gpio_bit_set_mode(int gpio, uint8_t in_mode){ #if 0 char cmd[255] = ""; if (in_mode){ sprintf(cmd, "echo in > /sys/class/gpio/gpio%d/direction", gpio); system(cmd); }else{ sprintf(cmd, "echo out > /sys/class/gpio/gpio%d/direction", gpio); system(cmd); } //printf("gpio_bit_set_mode, cmd:%s\r\n", cmd); #else if (in_mode){ gpio_direction_input(gpio); }else{ gpio_direction_output(gpio); } #endif } static inline int gpio_input_bit_get(int gpio, uint8_t switch_in_mode){ #if 1 if (switch_in_mode){ gpio_bit_set_mode(gpio, 1); } return gpio_get_value(gpio); #else FILE *fp = NULL; int rc = 0; // 用于接收命令返回值 int value =0; char cmd[255] = ""; char result_buf[255] = ""; sprintf(cmd, "echo in > /sys/class/gpio/gpio%d/direction", gpio); if (switch_in_mode){ //printf("gpio_input_bit_get:%s\r\n", cmd); system(cmd); } sprintf(cmd, "cat /sys/class/gpio/gpio%d/value", gpio); fp = popen(cmd, "r"); if(NULL == fp) { printf("popen执行失败!"); return (0); } while(fgets(result_buf, sizeof(result_buf), fp) != NULL) { value = atoi(result_buf); } rc = pclose(fp); if(-1 == rc) { printf("关闭文件指针失败\r\n"); return (0); } return value; #endif } //产生IIC总线起始信号 static void TM1650_IIC_start(void) { TM1650_IIC_SDA_SET_OUT; TM1650_IIC_SCL_HIGH; //SCL=1 TM1650_IIC_SDA_HIGH; //SDA=1 TM1650_IIC_DELAY_5US; TM1650_IIC_SDA_LOW; //SDA=0 TM1650_IIC_DELAY_5US; TM1650_IIC_SCL_LOW; //SCL=0 } //通过IIC总线发送一个字节 static void TM1650_IIC_write_byte(uint8_t dat) { uint8_t i; TM1650_IIC_SDA_SET_OUT; TM1650_IIC_SCL_LOW; for(i=0;i<8;i++) { TM1650_IIC_SDA_WR(dat&0x80); dat<<=1; TM1650_IIC_DELAY_5US; TM1650_IIC_SCL_HIGH; TM1650_IIC_DELAY_5US; TM1650_IIC_SCL_LOW; TM1650_IIC_DELAY_5US; } } //通过IIC总线读一个字节 static uint8_t TM1650_IIC_read_byte(void) { uint8_t i; uint8_t read_key = 0; //printf("%s enter test \r\n",__FUNCTION__); TM1650_IIC_SDA_SET_IN; for(i=0;i<8;i++) { TM1650_IIC_SCL_LOW; TM1650_IIC_DELAY_5US; TM1650_IIC_SCL_HIGH; read_key <<=1; int value = gpio_input_bit_get(TM1650_SDA_GPIO, FALSE); if (value == 1){ read_key++; } TM1650_IIC_DELAY_5US; } TM1650_IIC_SDA_SET_OUT; //printf("%s exit test \r\n",__FUNCTION__); return read_key; } //通过IIC总线接收从机响应的ACK信号 static uint8_t TM1650_IIC_wait_ack(void) { uint8_t ack_signal = 0; int times_wait = 0; TM1650_IIC_SDA_SET_OUT; TM1650_IIC_SDA_HIGH; //SDA=1 TM1650_IIC_DELAY_5US; TM1650_IIC_SCL_HIGH; TM1650_IIC_DELAY_5US; TM1650_IIC_SDA_SET_IN; do{ if(TM1650_IIC_SDA_RD()) { ack_signal = 1; //如果读取到的是NACK信号 break; } }while(times_wait++ < 300); TM1650_IIC_SCL_LOW; TM1650_IIC_DELAY_2US; return ack_signal; } //产生IIC总线结束信号 static void TM1650_IIC_stop(void) { TM1650_IIC_SDA_SET_OUT; TM1650_IIC_SCL_LOW; //SCL=0 TM1650_IIC_SDA_LOW; //SDA=0 TM1650_IIC_DELAY_5US; TM1650_IIC_SCL_HIGH; //SCL=1 TM1650_IIC_DELAY_5US; TM1650_IIC_SDA_HIGH; //SDA=1 } //将按键值转换为键盘丝印值 int get_input_key_value(uint8_t key_value){ switch(key_value){ case 0x64: return INPUT_KEY_OK; case 0x65: return INPUT_KEY_PROUND; case 0x66: return INPUT_KEY_0; case 0x67: return INPUT_KEY_STAR; case 0x4C: return INPUT_KEY_BACK; case 0x4D: return INPUT_KEY_9; case 0x4E: return INPUT_KEY_8; case 0x4F: return INPUT_KEY_7; case 0x44: return INPUT_KEY_INVALID; case 0x45: return INPUT_KEY_INVALID; case 0x46: return INPUT_KEY_INVALID; case 0x47: return INPUT_KEY_INVALID; default: return INPUT_KEY_INVALID; } return INPUT_KEY_INVALID; }
有中断信号时,读键盘的按键值,主函数实现:
TM1650_init(); sleep_ms(20); while(1){ if (TM1650_Irq_Set()) { i2c_read_data(&input_key); printf("input_key:%02x:%d\r\n", input_key, get_input_key_value(input_key)); } sleep_ms(10); }
几个报错处理:
1、开始通过文件方式操作export文件,发现总是失败;
[failed]gpio_export path:/sys/class/gpio/gpio145
open error: Permission denied
[failed]gpio_export path:/sys/class/gpio/gpio194
open error: Permission denied
[failed]gpio_cfg_attr path:/sys/class/gpio/gpio145/value
open error: No such file or directory
解决:使用如下命令执行后,再操作gpio就没有问题:
system("echo 199 > /sys/class/gpio/export");
2、sh: write error: Device or resource busy
未做处理,应该是权限控制问题;
3、write error: Operation not permited.
问题原因:direction是in时,写value失败
root@fsu:/home/fsu# echo 199 > /sys/class/gpio/export
root@fsu:/home/fsu# echo in > /sys/class/gpio/gpio199/direction
root@fsu:/home/fsu# echo 1 > /sys/class/gpio/gpio199/value
ash: write error: Operation not permitted
root@fsu:/home/fsu#
4、too many open file
文件打开没有close,网上找的gpio文件操作的代码(参考链接)有问题,操作完文件没有close;
-------------------广告线---------------
项目、合作,欢迎勾搭,邮箱:promall@qq.com
本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com