Arduino代码机制-引脚读写
来源:互联网 发布:手机怎么申请淘宝直播 编辑:程序博客网 时间:2024/06/04 18:22
在写arduino代码时,pinMode, digitalWrite, digitalRead这些函数用起来是不是非常顺手呢?有了这些函数,我们就不用关心AVR单片机的那些令人头疼寄存器了。我们向函数传入引脚在Arduino开发板上的引脚号,就能对这个引脚进行读写和设置操作了。这些函数是如何实现的呢?
以上这三个函数,最终还是要通过设置PORT,PIN和DDR三个寄存器实现,要设置某个引脚,就必须知道这个引脚在哪个端口上,还必须知道在这个端口的哪一位上,这样就能通过设置寄存器来读写和设置引脚了。
宏 digitalPinToPort(P)
#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )
Arduino将端口定义为整形,后面将会领略到这样定义的妙处
#define PA 1#define PB 2#define PC 3#define PD 4#define PE 5#define PF 6#define PG 7#define PH 8#define PJ 10#define PK 11#define PL 12
再将每个引脚在哪个端口保存为数组,放在Flash中,通过查表得到引脚所在的端口。关于如何将数据放在Flash中这方面内容可以看我的上一篇博客。
const uint8_t PROGMEM digital_pin_to_port_PGM[] = {// PORTLIST // ------------------------------------------- PE , // PE 0 ** 0 ** USART0_RX PE , // PE 1 ** 1 ** USART0_TX PE , // PE 4 ** 2 ** PWM2 PE , // PE 5 ** 3 ** PWM3 PG , // PG 5 ** 4 ** PWM4 PE , // PE 3 ** 5 ** PWM5 PH , // PH 3 ** 6 ** PWM6 PH , // PH 4 ** 7 ** PWM7 PH , // PH 5 ** 8 ** PWM8 PH , // PH 6 ** 9 ** PWM9 PB , // PB 4 ** 10 ** PWM10 PB , // PB 5 ** 11 ** PWM11 PB , // PB 6 ** 12 ** PWM12 PB , // PB 7 ** 13 ** PWM13 PJ , // PJ 1 ** 14 ** USART3_TX PJ , // PJ 0 ** 15 ** USART3_RX PH , // PH 1 ** 16 ** USART2_TX PH , // PH 0 ** 17 ** USART2_RX PD , // PD 3 ** 18 ** USART1_TX PD , // PD 2 ** 19 ** USART1_RX PD , // PD 1 ** 20 ** I2C_SDA PD , // PD 0 ** 21 ** I2C_SCL PA , // PA 0 ** 22 ** D22 PA , // PA 1 ** 23 ** D23 PA , // PA 2 ** 24 ** D24 PA , // PA 3 ** 25 ** D25 PA , // PA 4 ** 26 ** D26 PA , // PA 5 ** 27 ** D27 PA , // PA 6 ** 28 ** D28 PA , // PA 7 ** 29 ** D29 PC , // PC 7 ** 30 ** D30 PC , // PC 6 ** 31 ** D31 PC , // PC 5 ** 32 ** D32 PC , // PC 4 ** 33 ** D33 PC , // PC 3 ** 34 ** D34 PC , // PC 2 ** 35 ** D35 PC , // PC 1 ** 36 ** D36 PC , // PC 0 ** 37 ** D37 PD , // PD 7 ** 38 ** D38 PG , // PG 2 ** 39 ** D39 PG , // PG 1 ** 40 ** D40 PG , // PG 0 ** 41 ** D41 PL , // PL 7 ** 42 ** D42 PL , // PL 6 ** 43 ** D43 PL , // PL 5 ** 44 ** D44 PL , // PL 4 ** 45 ** D45 PL , // PL 3 ** 46 ** D46 PL , // PL 2 ** 47 ** D47 PL , // PL 1 ** 48 ** D48 PL , // PL 0 ** 49 ** D49 PB , // PB 3 ** 50 ** SPI_MISO PB , // PB 2 ** 51 ** SPI_MOSI PB , // PB 1 ** 52 ** SPI_SCK PB , // PB 0 ** 53 ** SPI_SS PF , // PF 0 ** 54 ** A0 PF , // PF 1 ** 55 ** A1 PF , // PF 2 ** 56 ** A2 PF , // PF 3 ** 57 ** A3 PF , // PF 4 ** 58 ** A4 PF , // PF 5 ** 59 ** A5 PF , // PF 6 ** 60 ** A6 PF , // PF 7 ** 61 ** A7 PK , // PK 0 ** 62 ** A8 PK , // PK 1 ** 63 ** A9 PK , // PK 2 ** 64 ** A10 PK , // PK 3 ** 65 ** A11 PK , // PK 4 ** 66 ** A12 PK , // PK 5 ** 67 ** A13 PK , // PK 6 ** 68 ** A14 PK , // PK 7 ** 69 ** A15 };
这样,比如说我们想要知道13号引脚在哪个端口上,就可以用这样的代码:
uint8_t pin = 13;uint8_t port = digitalPinToPort(pin);
获取端口三个寄存器
同样的,还是通过查表法实现。
Arduino先将每个寄存器的地址保存为数组,放在Flash中,想要知道某个端口的相应寄存器地址,从数组中读取即可。Arduino提供了三个宏来读取数据:
#define portOutputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_output_PGM + (P))) )#define portInputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_input_PGM + (P))) )#define portModeRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_mode_PGM + (P))) )
寄存器地址保存在数组存放在Flash中:
const uint16_t PROGMEM port_to_mode_PGM[] = {NOT_A_PORT,(uint16_t) &DDRA,(uint16_t) &DDRB,(uint16_t) &DDRC,(uint16_t) &DDRD,(uint16_t) &DDRE,(uint16_t) &DDRF,(uint16_t) &DDRG,(uint16_t) &DDRH,NOT_A_PORT,(uint16_t) &DDRJ,(uint16_t) &DDRK,(uint16_t) &DDRL,};const uint16_t PROGMEM port_to_output_PGM[] = {NOT_A_PORT,(uint16_t) &PORTA,(uint16_t) &PORTB,(uint16_t) &PORTC,(uint16_t) &PORTD,(uint16_t) &PORTE,(uint16_t) &PORTF,(uint16_t) &PORTG,(uint16_t) &PORTH,NOT_A_PORT,(uint16_t) &PORTJ,(uint16_t) &PORTK,(uint16_t) &PORTL,};const uint16_t PROGMEM port_to_input_PGM[] = {NOT_A_PIN,(uint16_t) &PINA,(uint16_t) &PINB,(uint16_t) &PINC,(uint16_t) &PIND,(uint16_t) &PINE,(uint16_t) &PINF,(uint16_t) &PING,(uint16_t) &PINH,NOT_A_PIN,(uint16_t) &PINJ,(uint16_t) &PINK,(uint16_t) &PINL,};
有了这些宏定义和数据,假如我们想要获取PA端口的寄存器地址,就可以用下面代码:
uint8_t* pina = portInputRegister(PA);uint8_t* porta = portOutputRegister(PA);uint8_t* ddra = portModeRegister(PA);
如果仔细看这些宏定义的话,可能会有个疑问:如下图,假如读取output寄存器,再假定port_to_output_PGM为0,由于寄存器地址为16位的,所以PORTA的地址存放在数组的2,3两个字节的位置,由于PA是1,读取的地址为port_to_output_PGM + (P),使用pgm_read_word宏时,会不会读取1,2两个字节的数据呢?
是不会的,实际会读取2,3两个字节的数据。因为port_to_output_PGM是一个地址,将地址加1时,首先会检查指针指向的数据的大小,指针偏移的量是这种数据的大小。尽管对地址加的是1,实际上地址偏移了两个字节。进一步的,在C语言中, a[1] ,*(a + 1) , 1[a]都是一样的。
bitmask
要对引脚的操作还要知道这个引脚在端口的哪一位上,假如要对PORTA的第n位置1,可以用这样的代码:
PORTA |= (1<<n);
若将PORTA的第n为置0,则可以:
PORTA &= ~(1<<n);
后面移位操作结果部分就称为bitmask,可以看到,如果给出每一个引脚的bitmask, 操作起来将会更简单。
对于移位操作,Arduino提供了一个宏:
#define _BV(n) (1<<(n))
Arduino将每个引脚的bitmask保存在数组放在Flash中:
const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[] = {// PIN IN PORT // ------------------------------------------- _BV( 0 ) , // PE 0 ** 0 ** USART0_RX _BV( 1 ) , // PE 1 ** 1 ** USART0_TX _BV( 4 ) , // PE 4 ** 2 ** PWM2 _BV( 5 ) , // PE 5 ** 3 ** PWM3 _BV( 5 ) , // PG 5 ** 4 ** PWM4 _BV( 3 ) , // PE 3 ** 5 ** PWM5 _BV( 3 ) , // PH 3 ** 6 ** PWM6 _BV( 4 ) , // PH 4 ** 7 ** PWM7 _BV( 5 ) , // PH 5 ** 8 ** PWM8 _BV( 6 ) , // PH 6 ** 9 ** PWM9 _BV( 4 ) , // PB 4 ** 10 ** PWM10 _BV( 5 ) , // PB 5 ** 11 ** PWM11 _BV( 6 ) , // PB 6 ** 12 ** PWM12 _BV( 7 ) , // PB 7 ** 13 ** PWM13 _BV( 1 ) , // PJ 1 ** 14 ** USART3_TX _BV( 0 ) , // PJ 0 ** 15 ** USART3_RX _BV( 1 ) , // PH 1 ** 16 ** USART2_TX _BV( 0 ) , // PH 0 ** 17 ** USART2_RX _BV( 3 ) , // PD 3 ** 18 ** USART1_TX _BV( 2 ) , // PD 2 ** 19 ** USART1_RX _BV( 1 ) , // PD 1 ** 20 ** I2C_SDA _BV( 0 ) , // PD 0 ** 21 ** I2C_SCL _BV( 0 ) , // PA 0 ** 22 ** D22 _BV( 1 ) , // PA 1 ** 23 ** D23 _BV( 2 ) , // PA 2 ** 24 ** D24 _BV( 3 ) , // PA 3 ** 25 ** D25 _BV( 4 ) , // PA 4 ** 26 ** D26 _BV( 5 ) , // PA 5 ** 27 ** D27 _BV( 6 ) , // PA 6 ** 28 ** D28 _BV( 7 ) , // PA 7 ** 29 ** D29 _BV( 7 ) , // PC 7 ** 30 ** D30 _BV( 6 ) , // PC 6 ** 31 ** D31 _BV( 5 ) , // PC 5 ** 32 ** D32 _BV( 4 ) , // PC 4 ** 33 ** D33 _BV( 3 ) , // PC 3 ** 34 ** D34 _BV( 2 ) , // PC 2 ** 35 ** D35 _BV( 1 ) , // PC 1 ** 36 ** D36 _BV( 0 ) , // PC 0 ** 37 ** D37 _BV( 7 ) , // PD 7 ** 38 ** D38 _BV( 2 ) , // PG 2 ** 39 ** D39 _BV( 1 ) , // PG 1 ** 40 ** D40 _BV( 0 ) , // PG 0 ** 41 ** D41 _BV( 7 ) , // PL 7 ** 42 ** D42 _BV( 6 ) , // PL 6 ** 43 ** D43 _BV( 5 ) , // PL 5 ** 44 ** D44 _BV( 4 ) , // PL 4 ** 45 ** D45 _BV( 3 ) , // PL 3 ** 46 ** D46 _BV( 2 ) , // PL 2 ** 47 ** D47 _BV( 1 ) , // PL 1 ** 48 ** D48 _BV( 0 ) , // PL 0 ** 49 ** D49 _BV( 3 ) , // PB 3 ** 50 ** SPI_MISO _BV( 2 ) , // PB 2 ** 51 ** SPI_MOSI _BV( 1 ) , // PB 1 ** 52 ** SPI_SCK _BV( 0 ) , // PB 0 ** 53 ** SPI_SS _BV( 0 ) , // PF 0 ** 54 ** A0 _BV( 1 ) , // PF 1 ** 55 ** A1 _BV( 2 ) , // PF 2 ** 56 ** A2 _BV( 3 ) , // PF 3 ** 57 ** A3 _BV( 4 ) , // PF 4 ** 58 ** A4 _BV( 5 ) , // PF 5 ** 59 ** A5 _BV( 6 ) , // PF 6 ** 60 ** A6 _BV( 7 ) , // PF 7 ** 61 ** A7 _BV( 0 ) , // PK 0 ** 62 ** A8 _BV( 1 ) , // PK 1 ** 63 ** A9 _BV( 2 ) , // PK 2 ** 64 ** A10 _BV( 3 ) , // PK 3 ** 65 ** A11 _BV( 4 ) , // PK 4 ** 66 ** A12 _BV( 5 ) , // PK 5 ** 67 ** A13 _BV( 6 ) , // PK 6 ** 68 ** A14 _BV( 7 ) , // PK 7 ** 69 ** A15 };
又定义了宏来读取引脚的bitmask:
#define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) )
digitalWrite函数
函数原型为
void digitalWrite(uint8_t pin, uint8_t val);
当对某一引脚赋值是,就要先获得这个引脚的PORT寄存器地址和bitmask,然后对其赋值,代码可以是:
uint8_t port = digitalPinToPort(pin);uint8_t* reg = portOutputRegister(port);uint8_t bit = digitalPinToBitMask(pin);if(val == LOW) { *reg &= ~bit;} else { *reg |= bit;}
为了安全,在digitalWrite函数中还做了一些其他事情,具体代码就不贴出。正因为这样,digitalWrite的效率并不高。当需要反复对某一个引脚赋值时,使用digitalWrite就不合适了。使用下面的宏定义将能提高效率。
#define cbi(reg, bit) *reg &= ~bit;#define sbi(reg, bit) *reg |= bit;
reg是要赋值的引脚所在的PORT寄存器地址,bit是这个引脚的bitmask。使用这样的宏定义时,还需要先用之前给出的宏定义得到reg和bit。使用这样的宏定义,只需要进行一次Flash的读取得到reg和bit,reg和bit能反复使用,会大大提高效率。
令外两个函数能自己理解了吧?
更多代码可以参考源文件:
\hardware\arduino\avr\variants\…\pins_arduino.h
\hardware\arduino\avr\cores\arduino\Arduino.h
- Arduino代码机制-引脚读写
- arduino引脚
- Arduino代码机制-Arduino.h
- Arduino 代码机制
- Arduino代码机制-IO
- Arduino代码机制
- Arduino代码机制-WString.h
- Arduino代码机制-Serial上
- Arduino代码机制-Serial中
- Arduino代码机制-Serial下
- Arduino代码机制-main.cpp
- arduino 引脚图
- arduino引脚图
- arduino 引脚用法
- arduino引脚图
- Arduino代码机制-avr/pgmspace.h
- arduino双机通信 (解决引脚不够用)
- Arduino 000 — Arduino UNO R3 板子引脚定义
- poj 1088 滑雪
- 快速初始化 NSArray *viewControllers = @[viewController1, viewController2];
- 探索工作流(七)--流程实例持久化
- Python算24点
- 设计模式—单例模式
- Arduino代码机制-引脚读写
- Java Gis 拓扑图(Google 地图)
- 电商后台制作遇到的问题
- 财富说--财富背后的秘密
- C++类模板
- php 调试防止超时
- Android_SDK_Windows免费下载链接
- 观影小结
- NEU 2016年2月月赛总结