LINUX外接TM1650键盘,由于TM1650的接口不是标准的I2C接口,只能通过操作GPIO方式模拟I2C通信,实现对TM1650的驱动;
问题1:通过linux的文件读写GPIO方式,是否支持微秒级别拉高拉低控制,通过示波器验证没有问题,完全支持微秒级别的gpio控制;
问题2:中断引脚如何控制;
当前解决方式是轮训查询中断引脚value值来判断是否有中断;另一种方式就是使用poll监听多路复用的方式监听是否有中断产生;
1 2 | 对于使用中断可以使用poll多路复用IO监测中断是否触发,poll事件有POLLIN、POLLOUT、POLLERR、POLLPRI 等,其中 POLLIN 和 POLLOUT 表示普通优先级数据可读、可写。中断就是一种高优先级事件,需要使用 POLLPRI ,当中断触发时表示有高优先级数据可被读取。 此外还可以使用信号方式监测GPIO是否触发中断。 |
TM1650的驱动代码参考链接实现,基于参考链接来操作GPIO,注意这个链接的几个方法没有close 文件句柄,需要注意修改;
key_pad.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | //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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 | //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; } |
有中断信号时,读键盘的按键值,主函数实现:
1 2 3 4 5 6 7 8 9 10 11 | 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