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

0 0
原创粉丝点击