Linux GPIO Manipulation via Accessing Mapped Physical Memory
2017-08-20 03:36
836 查看
Form my previous post, I have demonstrated how to manipulate GPIO via sysfs and I have pointed out the insufficiency of this manipulation for sysfs
speed is very slow. Sysfs accessing speed is in order of milli-second(KHz), but the most peripheral chips communication speed are in the order of micro-second(MHz). That fact leads the sysfs accessing is limited for LEDs and buttons only.
In this post, I will compliment the incompletion of sysfs accessing in low speed but without writing a driver to manipulating GPIO still. The driver is the extension of operation system (not only for Linux, aslo for Windows ). Applying a driver implicate
the operation system has been patched. It is very natural that the code workers for developing applications should not modify the operation system as possible as they could. Even for the code workers in porting, they should install the driver as less as possible,
for the driver debugging makes the code worker in trepidation.
I demonstrate how to read DHT11 data via GPIO in here.
DHT11 is a sensor from 天朝 Celestial Empire widely adopted in the cheap electronic devices or the makers, to measure humidity and Temperature via ONE GPIO pin. The communication speed between the sensor and host is in the order of micro-sceond, a typical
middle-speed signal transmission rate.
The DHT11 I use has been modularized instead of a naked one, it integrates with the required capacity and resistor, so I could wave away the circuit issue.
the connection is extremely intuitive: for I adopt GPIO pin 18 as my the object of my accessing, I connect the data pin to GPIO pin 18, Vcc to Vcc, ground to ground.
My Device is an OpenWRT system :
一. Look up the
Atheros AR9330 document, in the page 65, The table :
That is :
0. One bit represents one GPIO pin for all address.
1. The address 0x1804000 is the base address of GPIOs, which's bits could be set to specified if the pins are ouput or not (input).
2. The bits in address 0x1804004 is for reading the input values, while the corresponding GPIO pins have been set as input pins.
3. The bits in address 0x1804008 is for setting the values for output, while the corresponding GPIO pins have been set as output pins.
4. (Synonym for 3). Set the bits in 0x180400C means the values in the output pins is 1.
5. (Synonym for 4). Set the bits in 0x1804010 means the values in the output pins is 0.
The function to achieve those operation in C are :
And do not forget to call
munmap while the mapping address would not be used.
If you do not use the same CPU as mine, you need to implement those functions by inquiring data sheet of the CPU you use.
二. Understand the DHT11 communication data pockets.
DHT11 data sheet :
漢文,
English.
As the datasheet's instrcution, the procedure of read data from DHT11 be :
三. Implementation :
The full code be :
Note : The function delay_micro_sec could not been replaced as usleep : while calling usleep, the system would current task to the others, that would entail the delaying time is not accurate enough, especially the time interval (sleep time) is in the order
of micro-second.
The makefile is
The code do not rely on non-default library, the Makefile call the cross-compiler( it should be called as parasitic compiler) for compilation only.
Run the binary :
It is the beginning month of Autumn, it's still very hot in Taiwan's midnight.
若爾欲曉何以內存射映之法 摯取DS18B20所揣之值 可見余人下文於咨
speed is very slow. Sysfs accessing speed is in order of milli-second(KHz), but the most peripheral chips communication speed are in the order of micro-second(MHz). That fact leads the sysfs accessing is limited for LEDs and buttons only.
In this post, I will compliment the incompletion of sysfs accessing in low speed but without writing a driver to manipulating GPIO still. The driver is the extension of operation system (not only for Linux, aslo for Windows ). Applying a driver implicate
the operation system has been patched. It is very natural that the code workers for developing applications should not modify the operation system as possible as they could. Even for the code workers in porting, they should install the driver as less as possible,
for the driver debugging makes the code worker in trepidation.
I demonstrate how to read DHT11 data via GPIO in here.
DHT11 is a sensor from 天朝 Celestial Empire widely adopted in the cheap electronic devices or the makers, to measure humidity and Temperature via ONE GPIO pin. The communication speed between the sensor and host is in the order of micro-sceond, a typical
middle-speed signal transmission rate.
The DHT11 I use has been modularized instead of a naked one, it integrates with the required capacity and resistor, so I could wave away the circuit issue.
the connection is extremely intuitive: for I adopt GPIO pin 18 as my the object of my accessing, I connect the data pin to GPIO pin 18, Vcc to Vcc, ground to ground.
My Device is an OpenWRT system :
root@GL-AR150:/tmp# cat /proc/cpuinfo system type : Atheros AR9330 rev 1 machine : TP-LINK TL-WR720N v3 processor : 0 cpu model : MIPS 24Kc V7.4 BogoMIPS : 265.42 wait instruction : yes microsecond timers : yes tlb_entries : 16 extra interrupt vector : yes hardware watchpoint : yes, count: 4, address/irw mask: [0x0000, 0x0008, 0x0020, 0x0000] isa : mips1 mips2 mips32r1 mips32r2 ASEs implemented : mips16 shadow register sets : 1 kscratch registers : 0 core : 0 VCED exceptions : not available VCEI exceptions : not available
一. Look up the
Atheros AR9330 document, in the page 65, The table :
Address | Name | Description |
---|---|---|
0x18040000 | GPIO_OE | General Purpose I/O Output Enable |
0x18040004 | GPIO_IN | General Purpose I/O Input Value |
0x18040008 | GPIO_OUT | General Purpose I/O Output Value |
0x1804000C | GPIO_SET | General Purpose I/O Bit Set |
0x18040010 | GPIO_CLEAR | General Purpose I/O Per Bit Clear |
0. One bit represents one GPIO pin for all address.
1. The address 0x1804000 is the base address of GPIOs, which's bits could be set to specified if the pins are ouput or not (input).
2. The bits in address 0x1804004 is for reading the input values, while the corresponding GPIO pins have been set as input pins.
3. The bits in address 0x1804008 is for setting the values for output, while the corresponding GPIO pins have been set as output pins.
4. (Synonym for 3). Set the bits in 0x180400C means the values in the output pins is 1.
5. (Synonym for 4). Set the bits in 0x1804010 means the values in the output pins is 0.
The function to achieve those operation in C are :
#include <sys/mman.h> #include <fcntl.h> #include <errno.h> #define GPIO_ADDR 0x18040000 // base address #define GPIO_MEM_BLOCK_SIZE (48) // memory block size int MappingGPIOToMemory(unsigned long **pp_gpio_address) { int mem_fd; if ((mem_fd = open("/dev/mem", O_RDWR)) < 0) { printf("Open /dev/mem fail\r\n"); return -1; }/*if */ *pp_gpio_address = (unsigned long*)mmap(NULL, GPIO_MEM_BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, GPIO_ADDR); close(mem_fd); if (*pp_gpio_address == MAP_FAILED) { printf("mapping memory fail\r\n"); printf("error message = %s\r\n", strerror(errno)); return -2; } }/*MappingGPIOToMemory*/ #define PIN_OUT (1) #define PIN_IN (0) void SetGPIODirection(unsigned long *p_gpio_address, int gpio_numer, int direction) { #define GPIO_OUTPUT_ENABLE_OFFSET (0) unsigned long value; value = *(p_gpio_address + GPIO_OUTPUT_ENABLE_OFFSET); if (PIN_IN == direction) value &= ~(1 << gpio_numer); else value |= (1 << gpio_numer); *(p_gpio_address + GPIO_OUTPUT_ENABLE_OFFSET) = value; }/*SetGPIODirection*/ void SetGPIOValue(unsigned long *p_gpio_address, int gpio_numer, int value) { #if(1) #define GPIO_OUT_VALUE_OFFSET (2) unsigned long full_register_value; full_register_value = *(p_gpio_address + GPIO_OUT_VALUE_OFFSET); if (0 == value) full_register_value &= ~(1 << gpio_numer); else full_register_value |= (1 << gpio_numer); *(p_gpio_address + GPIO_OUT_VALUE_OFFSET) = full_register_value; #else #define GPIO_SET_OFFSET (3) #define GPIO_CLEAR_OFFSET (4) if(0 == value) *(p_gpio_address + GPIO_CLEAR_OFFSET) = (1 << gpio_numer); else *(p_gpio_address + GPIO_SET_OFFSET) = (1 << gpio_numer); #endif }/*SetGPIOValue*/ void GetGPIOValue(unsigned long *p_gpio_address, int gpio_numer, int *p_value) { #define GPIO_INPUT_VALUE_OFFSET (1) unsigned long full_register_value; full_register_value = *(p_gpio_address + GPIO_INPUT_VALUE_OFFSET); *p_value = (full_register_value >> gpio_numer) & 0x01; }/*GetGPIOValue*/
And do not forget to call
munmap while the mapping address would not be used.
If you do not use the same CPU as mine, you need to implement those functions by inquiring data sheet of the CPU you use.
二. Understand the DHT11 communication data pockets.
DHT11 data sheet :
漢文,
English.
As the datasheet's instrcution, the procedure of read data from DHT11 be :
---Send init command to DHT11--- PULL_DOWN 18ms PULL_UP 30 us ---DHT11 init responding-------- Receive low for 80 us Receive high for 80 us --Receiving Data--------------- --One bit datum--receiving----- Receive low for 50 us if the datum is 0 receiving high for 28 us or (the datum is 1) receiving high for 70 us --To fetch next bit------------- ----Receiving total 5 byte------ --Check the sum of head 4 bytes equals with the last byte--
三. Implementation :
The full code be :
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <sys/time.h> #include <sys/mman.h> #include <fcntl.h> #include <errno.h> #define GPIO_ADDR (0x18040000) #define GPIO_MEM_BLOCK_SIZE (48) int MappingGPIOToMemory(unsigned long **pp_gpio_address) { int mem_fd; if ((mem_fd = open("/dev/mem", O_RDWR)) < 0) { printf("Open /dev/mem fail\r\n"); return -1; }/*if */ *pp_gpio_address = (unsigned long*)mmap(NULL, GPIO_MEM_BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, GPIO_ADDR); close(mem_fd); if (*pp_gpio_address == MAP_FAILED) { printf("mapping memory fail\r\n"); printf("error message = %s\r\n", strerror(errno)); return -2; } }/*MappingGPIOToMemory*/ #define PIN_OUT (1) #define PIN_IN (0) void SetGPIODirection(unsigned long *p_gpio_address, int gpio_numer, int direction) { #define GPIO_OUTPUT_ENABLE_OFFSET (0) unsigned long value; value = *(p_gpio_address + GPIO_OUTPUT_ENABLE_OFFSET); if (PIN_IN == direction) value &= ~(1 << gpio_numer); else value |= (1 << gpio_numer); *(p_gpio_address + GPIO_OUTPUT_ENABLE_OFFSET) = value; }/*SetGPIODirection*/ void SetGPIOValue(unsigned long *p_gpio_address, int gpio_numer, int value) { #if(1) #define GPIO_OUT_VALUE_OFFSET (2) unsigned long full_register_value; full_register_value = *(p_gpio_address + GPIO_OUT_VALUE_OFFSET); if (0 == value) full_register_value &= ~(1 << gpio_numer); else full_register_value |= (1 << gpio_numer); *(p_gpio_address + GPIO_OUT_VALUE_OFFSET) = full_register_value; #else #define GPIO_SET_OFFSET (3) #define GPIO_CLEAR_OFFSET (4) if(0 == value) *(p_gpio_address + GPIO_CLEAR_OFFSET) = (1 << gpio_numer); else *(p_gpio_address + GPIO_SET_OFFSET) = (1 << gpio_numer); #endif }/*SetGPIOValue*/ void GetGPIOValue(unsigned long *p_gpio_address, int gpio_numer, int *p_value) { #define GPIO_INPUT_VALUE_OFFSET (1) unsigned long full_register_value; full_register_value = *(p_gpio_address + GPIO_INPUT_VALUE_OFFSET); *p_value = (full_register_value >> gpio_numer) & 0x01; }/*GetGPIOValue*/ void delay_micro_sec(unsigned int delay_time_in_us) { struct timeval now; struct timeval period; struct timeval end; gettimeofday(&now, NULL); period.tv_sec = delay_time_in_us / 1000000; period.tv_usec = delay_time_in_us % 1000000; timeradd(&now, &period, &end); while(timercmp(&now, &end, < )) gettimeofday(&now, NULL); }/*delay_micro_sec*/ int ReadDHT11Byte(unsigned long *p_gpio_address, int gpio_numer, unsigned char *p_read_data) { int i; int pin_value; unsigned char one_byte_value, bit_value; unsigned int over_time_cnt; int is_the_headest_bit; int endurable_time_in_usec; is_the_headest_bit = 1; one_byte_value = 0; #define BUFFER_TIME_IN_USEC (5) for( i=0; i<8; i++) { #define DHT11_INIT_PULL_UP_DURATION_IN_USEC (80) #define PREVIOUS_ONE_IN_HIGH_EXTERNAL_DURATION_IN_USEC (70 - 28) if(0 != is_the_headest_bit) { endurable_time_in_usec = DHT11_INIT_PULL_UP_DURATION_IN_USEC + BUFFER_TIME_IN_USEC; is_the_headest_bit = 0; } else { endurable_time_in_usec = PREVIOUS_ONE_IN_HIGH_EXTERNAL_DURATION_IN_USEC + BUFFER_TIME_IN_USEC; }/*is_the_headest_bit == true */ over_time_cnt = 0; while(1) { GetGPIOValue(p_gpio_address, gpio_numer, &pin_value); delay_micro_sec(1); if(0 == pin_value) break; if( endurable_time_in_usec == over_time_cnt) break; over_time_cnt++; }/*wait 0 */ if(endurable_time_in_usec == over_time_cnt) { #if(0) printf("stage 1 fail\r\n"); #endif return -1; } #define DATA_HINT_SIGNAL_DURATION_IN_USEC (50) endurable_time_in_usec = DATA_HINT_SIGNAL_DURATION_IN_USEC + BUFFER_TIME_IN_USEC; over_time_cnt = 0; while(1) { GetGPIOValue(p_gpio_address, gpio_numer, &pin_value); delay_micro_sec(1); if(0 != pin_value) break; if(endurable_time_in_usec == over_time_cnt) { break; } over_time_cnt++; }/*wait 1*/ if(endurable_time_in_usec == over_time_cnt) { #if(0) printf("stage 2 fail\r\n"); #endif return -2; } #define SIGNAL_ZERO_DURATION_IN_USEC (28) delay_micro_sec(SIGNAL_ZERO_DURATION_IN_USEC); GetGPIOValue(p_gpio_address, gpio_numer, &pin_value); if(0 == pin_value) bit_value = 0; else bit_value = 1; one_byte_value <<= 1; one_byte_value |= bit_value; }/*for i */ *p_read_data = one_byte_value; return 0; }/*ReadDHT11OneByte*/ void InitDHT11(unsigned long *p_gpio_address, int gpio_numer) { unsigned int over_time_cnt; SetGPIODirection(p_gpio_address, gpio_numer, PIN_OUT); #define HOST_PULL_DOWN_DURATION_IN_USEC (20*1000) SetGPIOValue(p_gpio_address, gpio_numer, 0); delay_micro_sec(HOST_PULL_DOWN_DURATION_IN_USEC); #define HOST_PULL_UP_DURATION_IN_USEC (20) SetGPIOValue(p_gpio_address, gpio_numer, 1); delay_micro_sec(HOST_PULL_UP_DURATION_IN_USEC); SetGPIODirection(p_gpio_address, gpio_numer, PIN_IN); #define WAIT_DHT11_INIT_RESP_PULL_DOWN_IN_USEC (40) over_time_cnt = 0; while(1) { int pin_value; GetGPIOValue(p_gpio_address, gpio_numer, &pin_value); delay_micro_sec(1); if(0 == pin_value) break; if(WAIT_DHT11_INIT_RESP_PULL_DOWN_IN_USEC == over_time_cnt) break; over_time_cnt++; }/*wait 0*/ #define DHT11_INIT_RESP_PULL_DOWN_DURATION_IN_USEC (80) over_time_cnt = 0; while(1) { int pin_value; GetGPIOValue(p_gpio_address, gpio_numer, &pin_value); delay_micro_sec(1); if(0 != pin_value) break; if(DHT11_INIT_RESP_PULL_DOWN_DURATION_IN_USEC == over_time_cnt) break; over_time_cnt++; }/*wait 1*/ }/*InitDHT11*/ int ReadDHT11(unsigned long *p_gpio_address, int gpio_numer, int *p_humidity, int *p_temperature) { int i; int status; unsigned char raw_DHT11_data[8]; status = 0; memset(&raw_DHT11_data[0], 0, 5); InitDHT11(p_gpio_address, gpio_numer); for(i = 0; i < 5; i++) { if(0 != ReadDHT11Byte(p_gpio_address, gpio_numer, &raw_DHT11_data[i])) { status = -1; goto End_Of_Read_DHT11; } }/*for i*/ if(0 == raw_DHT11_data[0] && 0 == raw_DHT11_data[1] && 0 == raw_DHT11_data[2] && 0 == raw_DHT11_data[3] && 0 == raw_DHT11_data[4]) { status = -1; goto End_Of_Read_DHT11; }/*if all zero*/ status = -2; if(raw_DHT11_data[4] == raw_DHT11_data[0] + raw_DHT11_data[1] + raw_DHT11_data[2] + raw_DHT11_data[3]) { *p_humidity = (int)raw_DHT11_data[0]; *p_temperature = (int)raw_DHT11_data[2]; status = 0; }/*if match checking code*/ End_Of_Read_DHT11: return status; }/*ReadDHT11*/ unsigned long *p_gpio_address = NULL; void InterruptSignalHandlingRoutine(int sig) { if(NULL != p_gpio_address) munmap(p_gpio_address, GPIO_MEM_BLOCK_SIZE); p_gpio_address = NULL; exit(0); }/*InterruptSignalHandlingRoutine*/ void print_current_time(void) { time_t t; struct tm calendar_time; t = time(NULL); localtime_r(&t, &calendar_time); printf("now time: %d-%d-%d %d:%d:%d, %s(+%d)\n", calendar_time.tm_year + 1900, calendar_time.tm_mon + 1, calendar_time.tm_mday, calendar_time.tm_hour, calendar_time.tm_min, calendar_time.tm_sec, calendar_time.tm_zone, (int)calendar_time.tm_gmtoff/3600); }/*print_current_time*/ main(int argc, char *argv[]) { int humidity, temperature; int executed_count; int succeeded_count; int g_gpio_number; signal(SIGINT, InterruptSignalHandlingRoutine); if(2 > argc) { printf("%s should be followed by led_gpio_pin_number\r\n", argv[0]); return -1; } { char *p_temp; g_gpio_number = strtol(argv[1], &p_temp, 10); if(argv[1]== p_temp) { printf("%s is not a number for specifying gpio pin number\r\n", argv[1]); return -2; }/*not a number*/ }/*local variable*/ MappingGPIOToMemory(&p_gpio_address); #if(1) while(1) { humidity = temperature = 0; #if(1) do { if(0 == ReadDHT11(p_gpio_address, g_gpio_number, &humidity, &temperature)) { break; }/*if*/ }while(1); #else /*I do not like this style for while loop content does not exist*/ while(0 != ReadDHT11(p_gpio_address, g_gpio_number, &humidity, &temperature)); #endif printf("\r\n"); printf("humidity = %d, temperature = %d\r\n", humidity, temperature); print_current_time(); usleep(5*1000*1000); }/*while 1*/ #else succeeded_count = executed_count = 0; while(1) { humidity = temperature = 0; if(0 == ReadDHT11(p_gpio_address, g_gpio_number, &humidity, &temperature)) { #if(0) printf("humidity = %d, temperature = %d\r\n", humidity, temperature); #endif succeeded_count++; } executed_count++; if(100 == executed_count) { printf("yield rate = %3.2f\r\n", succeeded_count/(float)executed_count); succeeded_count = executed_count = 0; } }/*while 1*/ #endif if(NULL != p_gpio_address) munmap(p_gpio_address, GPIO_MEM_BLOCK_SIZE); p_gpio_address = NULL; return 0; }/*main*/
Note : The function delay_micro_sec could not been replaced as usleep : while calling usleep, the system would current task to the others, that would entail the delaying time is not accurate enough, especially the time interval (sleep time) is in the order
of micro-second.
The makefile is
OPENWRT_ROOT=/home/gaiger/openwrt-cc/staging_dir OPENWRT_TOOLCHAIN_PATH = $(OPENWRT_ROOT)/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2 CC = $(OPENWRT_TOOLCHAIN_PATH)/bin/mips-openwrt-linux-gcc CFLAGS := -O2 all: $(CC) $(CFLAGS) main.c -o dht11_measure clean: rm dht11_measure -f
The code do not rely on non-default library, the Makefile call the cross-compiler( it should be called as parasitic compiler) for compilation only.
Run the binary :
root@GL-AR150:/tmp# ./dht11_measure 18 humidity = 45, temperature = 27 now time: 2017-8-20 1:34:21, CST(+8) humidity = 32, temperature = 27 now time: 2017-8-20 1:34:26, CST(+8) humidity = 50, temperature = 27 now time: 2017-8-20 1:34:32, CST(+8) humidity = 20, temperature = 27 now time: 2017-8-20 1:34:37, CST(+8)
It is the beginning month of Autumn, it's still very hot in Taiwan's midnight.
若爾欲曉何以內存射映之法 摯取DS18B20所揣之值 可見余人下文於咨
相关文章推荐
- Linux GPIO Manipulation via System File System, sysfs
- MemoryMappedFile 在 Mono in Linux 的开发笔记
- MemoryMappedFile 在 Mono in Linux 的开发笔记
- Read DS18B20 Temperature via Mapping Memory Method in Linux
- 在Linux和Windows平台上操作MemoryMappedFile(简称MMF)
- 【嵌入式Linux+ARM】GPIO操作
- linux Memory
- linux下使用gpiolib操作/sys/class/gpio来控制gpio
- Windows核心编程——》第十七章 内存映射文件 (Memory-Mapped Files)
- Linux Find Out Virtual Memory PAGESIZE
- s3c2440基于linux的gpio led字符设备驱动实践 [转]
- 驱动移植pcDuino的linux移植五gpio驱动开发
- passwd: Authentication token manipulation error----linux
- linux out of memory分析(OOM)
- 2017_12_06-linux_memory_as_disk
- 【转】突破HIPS的防御思路之duplicate physical memory
- 33 全志GPIO口的脚本配置及超声波测距模块的linux驱动
- 有关linux下redis overcommit_memory的问题
- BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the Applic
- Memory-mapped I/O And port I/O