środa, 27 lutego 2019

LPC1769 - MPC28018

W tym poście chciałbym opisać sposób podłączenia ekspandera portów MPC28018 do mikrokontrolera LPC1769.

Znalezione obrazy dla zapytania lpc1769
[Źródło: https://www.nxp.com]

MCP23018:

Układ MCP23018 jest ekspanderem portów wykorzystujący do komunikacji interfejs I2C. Pozwala rozszerzyć ilość portów do 16.

Konfiguracja adresu urządzenia polega na odpowiednim dobraniu dzielnika napięć na linii ADDR.

Maksymalnie na jednej linii można obsłużyć do 8 ekspanderów. Po zasileniu układu adres jest odczytywany i zapisywany w urządzeniu. Po wyłączeniu zasilania adres przez urządzenie jest zapominany. Co oznacza, że można dowolnie modyfikować adres urządzenia za pomocą doboru odpowiednich wartości rezystorów w dzielniku.

Dostępne adresy układów to 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27.

W przypadku zwarcia linii ADDR do masy układ otrzymuje adres o wartości 0x20.

Domyślnie adres urządzenia to 0x20 . Dodatkowe adresy ustala się poprzez dobór odpowiedniego dzielnika na linii ADDR.

Biblioteka:


Na samym początku należy rozpocząć od ustawienia I2C:

  1. static void Expander_Init(void) {
  2.     PINSEL_CFG_Type PinCfg;
  3.     PinCfg.Funcnum = EXPANDER_FUNC;
  4.     PinCfg.Pinnum = EXPANDER_I2C_PIN_SDA;
  5.     PinCfg.Portnum = EXPANDER_I2C_PORT_SDA;
  6.     PINSEL_ConfigPin(&PinCfg);
  7.     PinCfg.Portnum = EXPANDER_PORT_I2C_SCL;
  8.     PinCfg.Pinnum = EXPANDER_PIN_I2C_SCL;
  9.     PINSEL_ConfigPin(&PinCfg);
  10.     I2C_Init(EXPANDER_I2C, 10);
  11.     I2C_Cmd(EXPANDER_I2C, ENABLE);
  12. }

Kolejnym elementem jest konfiguracja pinu Resetu. Domyślnie podciągam go VCC. Zresetowanie układu odbywa się przez wysłanie impulsu do układu:

  1. static void ConfigResetPin()
  2. {
  3.     EXPANDER_PORT_RESET->FIODIR |= EXPANDER_PIN_RESET;
  4.     EXPANDER_PORT_RESET->FIOSET |= EXPANDER_PIN_RESET;
  5. }

Inicjalizacja pinów wejściowych jako przerwania. Podłączone one zostały pod linię INTA oraz INTB:

  1. static void ConfigPinsForInterrupt()
  2. {
  3.     EXPANDER_PORT_INTA_2->FIODIR &= ~EXPANDER_PIN_INTA_2;
  4.     EXPANDER_PORT_INTB_2->FIODIR &= ~EXPANDER_PIN_INTB_2;
  5.     PINSEL_CFG_Type PinCfg;
  6.     PinCfg.Funcnum = PINSEL_FUNC_0;
  7.     PinCfg.OpenDrain = PINSEL_PINMODE_NORMAL;
  8.     PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
  9.     PinCfg.Portnum = EXPANDER_PORT_NUMER_INTA_2;
  10.     PinCfg.Pinnum = EXPANDER_PIN_NUMER_INTA_2;
  11.     PINSEL_ConfigPin(&PinCfg);
  12.     PinCfg.Portnum = EXPANDER_PORT_NUMER_INTB_2;
  13.     PinCfg.Pinnum = EXPANDER_PIN_NUMER_INTB_2;
  14.     PINSEL_ConfigPin(&PinCfg);
  15.     LPC_GPIOINT->IO0IntEnF |= EXPANDER_PIN_INTA_2;
  16.     LPC_GPIOINT->IO0IntEnF |= EXPANDER_PIN_INTB_2;
  17.     LPC_SC->EXTPOLAR |= 0x00000000;     /* falling */
  18.     NVIC_SetPriority(EINT3_IRQn, 2);
  19.     NVIC_EnableIRQ(EINT3_IRQn);
  20. }

Odczyt jednego bajtu danych przeprowadza się za pomocą następującej komendy:

  1. static int mcp23018_I2CRead(uint8_t addr, uint8_t* buf, uint32_t len) {
  2.     I2C_M_SETUP_Type rxsetup;
  3.     rxsetup.sl_addr7bit = addr;
  4.     rxsetup.tx_data = NULL; // Get address to read at writing address
  5.     rxsetup.tx_length = 0;
  6.     rxsetup.rx_data = buf;
  7.     rxsetup.rx_length = len;
  8.     rxsetup.retransmissions_max = 3;
  9.     if (I2C_MasterTransferData(EXPANDER_I2C, &rxsetup, I2C_TRANSFER_POLLING) == SUCCESS) {
  10.         return (0);
  11.     } else {
  12.         return (-1);
  13.     }
  14. }

Zapis jednego bajtu danych wykonuje się w następujący sposób:

  1. static int mcp23018_I2CWrite(uint8_t addr, uint8_t* buf, uint32_t len) {
  2.     I2C_M_SETUP_Type txsetup;
  3.     txsetup.sl_addr7bit = addr;
  4.     txsetup.tx_data = buf;
  5.     txsetup.tx_length = len;
  6.     txsetup.rx_data = NULL;
  7.     txsetup.rx_length = 0;
  8.     txsetup.retransmissions_max = 3;
  9.     if (I2C_MasterTransferData(EXPANDER_I2C, &txsetup, I2C_TRANSFER_POLLING) == SUCCESS) {
  10.         return (0);
  11.     }
  12.     else {
  13.         return (-1);
  14.     }
  15. }

Funkcja odczytująca dany rejestr:

  1. static int8_t mcp23018_readFromRegister(uint8_t deviceAddress, uint8_t registerAddress)
  2. {
  3.     uint8_t buffer = 0x00;
  4.     mcp23018_I2CWrite(deviceAddress, &registerAddress, 1);
  5.     if(mcp23018_I2CRead((deviceAddress), &buffer, 0x01) == -1){
  6.         return -1;
  7.     }
  8.     return buffer;
  9. }

W celu odczytu danych z układu należy do niego przesłać adres rejestru z którego zostaną dokonane a następnie odebrać przesłane przez układ dane.

Zapis danych do danego rejestru:

  1. static uint8_t mcp23018_writeToRegister(uint8_t devAddress, uint8_t regAddress, uint8_t data)
  2. {
  3.     int returnValue = mcp23018_I2CWrite(devAddress, &regAddress, 0x01);
  4.    
  5.     if(returnValue == -1) {
  6.         return 0x01;
  7.     }
  8.    
  9.     returnValue = mcp23018_I2CWrite(devAddress, &data, 0x01);
  10.    
  11.     if(returnValue == -1) {
  12.         return 0x02;
  13.     }
  14.    
  15.     return 0;
  16. }

Ustawienie portów jako wejście lub wyjście wykonuje się poprzez ustawienie odpowiednich danych w rejestrze IODIRx. Domyślnie adresy rejestrów to 0x00(IODIRA) oraz 0x01(IODIRB).

Domyślnie wszystkie piny w układzie są skonfigurowane jako wejścia.

  1. uint8_t MCP23018_SetGPIODirection(MCP23018_TD_Structure *MCP23018_Td_Ptr, const MCP23018_PortNumber_E portNumberRegister)
  2. {
  3.     uint8_t dataToSend[2] = {portNumberRegister, 0x00};
  4.     if(portNumberRegister == MPC23018_IODIRB) {
  5.         dataToSend[1] = MCP23018_Td_Ptr->pinConfig_GPB;
  6.     }
  7.     else{
  8.         dataToSend[1] = MCP23018_Td_Ptr->pinConfig_GPA;
  9.     }
  10.     mcp23018_I2CWrite(MCP23018_Td_Ptr->deviceAddress, &dataToSend[0], 2);
  11.     uint8_t readedValueFromSelectedRegister = mcp23018_readFromRegister(MCP23018_Td_Ptr->deviceAddress, dataToSend[0]);
  12.     if(readedValueFromSelectedRegister == dataToSend[1]) {
  13.         return 0x00;
  14.     }
  15.     return 0x01;
  16. }

Ustaw pull up:


Domyślnie pull-Up dla każdego z pinów jest wyłączony i rejestr przyjmuje wartość 0x00. W celu uruchomienia tej funkcji należy

  1. uint8_t MCP23018_SetPullUp(MCP23018_TD_Structure *MCP23018_Td_Ptr, const MCP23018_PortNumber_E portNumberRegister)
  2. {
  3.     uint8_t dataToWrite[2] = {0x00, 0x00};
  4.     if(MCP23018_GPA == portNumberRegister) {
  5.         dataToWrite[0] = MPC23018_GPPUA;
  6.         dataToWrite[1] = MCP23018_Td_Ptr->pullUpPins_GPA;
  7.     }
  8.     else{
  9.         dataToWrite[0] = MPC23018_GPPUB;
  10.         dataToWrite[1] = MCP23018_Td_Ptr->pullUpPins_GPB;
  11.     }
  12.     mcp23018_I2CWrite(MCP23018_Td_Ptr->deviceAddress, &dataToWrite[0], 2);
  13.     uint8_t checkWriteOperation = mcp23018_readFromRegister(MCP23018_Td_Ptr->deviceAddress, dataToWrite[0]);
  14.     if(checkWriteOperation != dataToWrite[1])
  15.     {
  16.         return 0x02;
  17.     }
  18.     return 0x00;
  19. }

W celu wysterowania pinem wyjściowym należy wykorzystać komendę GPIOA oraz GPIOB.


Przy uruchomionym układzie wszystkie piny są domyślnie ustawione nisko czyli wartość rejestry GPIOx wynosi 0x00.

  1. uint8_t MCP23018_SetPinValue(MCP23018_TD_Structure *MCP23018_Td_Ptr, const MCP23018_PortNumber_E portNumberRegister, constMCP23018_PinValue_E pinStateOnOff, uint8_t pinNumber)
  2. {
  3.     if(pinNumber > 7) {
  4.         return 0x01; /* Error */
  5.     }
  6.     uint8_t dataToWrite[2] = {0x00, 0x00};
  7.     if(MCP23018_GPA == portNumberRegister) {
  8.         dataToWrite[0] = MPC23018_GPIOA;
  9.     }
  10.     else{
  11.         dataToWrite[0] = MPC23018_GPIOB;
  12.     }
  13.     dataToWrite[1] = mcp23018_readFromRegister(MCP23018_Td_Ptr->deviceAddress, dataToWrite[0]);
  14.     dataToWrite[1] |= (pinStateOnOff << pinNumber);
  15.     mcp23018_I2CWrite(MCP23018_Td_Ptr->deviceAddress, &dataToWrite[0], 2);
  16.     uint8_t checkWriteOperation = mcp23018_readFromRegister(MCP23018_Td_Ptr->deviceAddress, dataToWrite[0]);
  17.     if(checkWriteOperation != dataToWrite[1]){
  18.         return 0x02;
  19.     }
  20.     return 0x00;
  21. }

Na samym początku sprawdzam czy numer pinu nie jest z poza zakresu. Następnie odczytuje stan rejestru, do odczytanych danych ustawia się bądź kasuje bit odpowiadający danemu wyprowadzeniu po czym wysłana zostaje nowa informacja z zapisem danych.

Uruchomienie przerwań dla zadanych pinów:

  1. uint8_t MCP23018_EnableInterrupt(MCP23018_TD_Structure *MCP23018_Td_Ptr, const MCP23018_PortNumber_E portNumberRegister)
  2. {
  3.     uint8_t dataToWrite[2] = {0x00, 0x00};
  4.     if(MCP23018_GPA == portNumberRegister) {
  5.         dataToWrite[0] = MCP23018_GPINTENA;
  6.         dataToWrite[1] = MCP23018_Td_Ptr->pullUpPins_GPA;
  7.     }
  8.     else{
  9.         dataToWrite[0] = MCP23018_GPINTENB;
  10.         dataToWrite[1] = MCP23018_Td_Ptr->pullUpPins_GPB;
  11.     }
  12.     mcp23018_I2CWrite(MCP23018_Td_Ptr->deviceAddress, &dataToWrite[0], 2);
  13.     uint8_t checkWriteOperation = mcp23018_readFromRegister(MCP23018_Td_Ptr->deviceAddress, dataToWrite[0]);
  14.     if(checkWriteOperation != dataToWrite[1]){
  15.         return 0x01;
  16.     }
  17.     return 0x00;
  18. }

Ustawienie zadanego bitu w rejestrze:

  1. static uint8_t MCP23018_ControlBitValueInRegister(const uint8_t deviceAddress, const uint8_t registerToWrite, const uint8_t bitValue, constuint8_t bitPosition)
  2. {
  3.     uint8_t dataToWrite[2] = { registerToWrite, 0x00 };
  4.     dataToWrite[1] = mcp23018_readFromRegister(deviceAddress, dataToWrite[0]);
  5.     dataToWrite[1] |= (bitValue << bitPosition);
  6.     mcp23018_I2CWrite(deviceAddress, &dataToWrite[0], 2);
  7.     uint8_t checkWriteOperation = mcp23018_readFromRegister(deviceAddress, dataToWrite[0]);
  8.     if(checkWriteOperation != dataToWrite[1]){
  9.         return 0x02;
  10.     }
  11.     return 0x00;
  12. }

Ustawienie bądź wyłączenie kasowania przerwania przez odczyt rejestru INTCAP.

  1. uint8_t MCP23018_ControlInterruptClearingIInIOCON(MCP23018_TD_Structure *MCP23018_Td_Ptr, const uint8_t setDisableINTCC)
  2. {
  3.     if(setDisableINTCC > 1){
  4.         return 0x01;
  5.     }
  6.     return MCP23018_ControlBitValueInRegister(MCP23018_Td_Ptr->deviceAddress, MCP23018_IOCON, setDisableINTCC, 0);
  7. }

Wprowadzenie wartości 1 powoduje kasowanie wygenerowanego przerwania po odczycie rejestru INTCAP, dla wartości 0 kasowanie następuje po odczycie rejestru GPIO.

Ustawienie poziomu wyzwalania sygnału przerwania. Wartość 0 oznacza wyzwolenie sygnałem niskim, wartość 1 powoduje wyzwalanie sygnałem wysokim.

  1. uint8_t MCP23018_SetINTPolarity(MCP23018_TD_Structure *MCP23018_Td_Ptr, const MCP23018_INTPOL_E activeLevel)
  2. {
  3.     /*
  4.       bit 1 INTPOL: Sets the polarity of the INT output pin.
  5.                             1 = Active-high.
  6.                             0 = Active-low
  7.     */
  8.     return MCP23018_ControlBitValueInRegister(MCP23018_Td_Ptr->deviceAddress, MCP23018_IOCON, activeLevel, 1);
  9. }

Konfiguracja bitu ODR:

  1. uint8_t MCP23018_ODRConfigPinAsOpenDrain(MCP23018_TD_Structure *MCP23018_Td_Ptr, const uint8_t odrBitValue)
  2. {
  3.     /*
  4.       bit 2 ODR: Configures the INT pin as an open-drain output.
  5.                     1 = Open-drain output (overrides the INTPOL bit).
  6.                     0 = Active driver output (INTPOL bit sets the polarity).
  7.      */
  8.    
  9.     return MCP23018_ControlBitValueInRegister(MCP23018_Td_Ptr->deviceAddress, MCP23018_IOCON, odrBitValue, 2);
  10. }

Konfiguracja bitu SEQOP:

  1. uint8_t MCP23018_SEQOPSequentialOperation(MCP23018_TD_Structure *MCP23018_Td_Ptr, const uint8_t seqOperaEnDis)
  2. {
  3.     /*
  4.         bit 5 SEQOP: Sequential Operation mode bit.
  5.                     1 = Sequential operation disabled, address pointer does not increment.
  6.                     0 = Sequential operation enabled, address pointer increments.
  7.      */
  8.     return MCP23018_ControlBitValueInRegister(MCP23018_Td_Ptr->deviceAddress, MCP23018_IOCON, seqOperaEnDis, 5);
  9. }

Konfiguracja bitu Mirror:

  1. uint8_t MCP23018_MIRRORIntPins(MCP23018_TD_Structure *MCP23018_Td_Ptr, const uint8_t mirrorBit)
  2. {
  3.     /*
  4.         bit 6 MIRROR: INT pins mirror bit
  5.                 1 = The INT pins are internally connected in a wired OR configuration
  6.                 0 = The INT pins are not connected. INTA is associated with Port A and INTB is associated with Port B
  7.      */
  8.     return MCP23018_ControlBitValueInRegister(MCP23018_Td_Ptr->deviceAddress, MCP23018_IOCON, mirrorBit, 6);
  9. }

Konfiguracja bitu Bank:

  1. uint8_t MCP23018_BANKControlsAdresses(MCP23018_TD_Structure *MCP23018_Td_Ptr, const uint8_t bankSelected)
  2. {
  3.     /*
  4.         bit 7 BANK: Controls how the registers are addressed (see Figure 1-4 and Figure 1-5)
  5.                 1 = The registers associated with each port are separated into different banks
  6.                 0 = The registers are in the same bank (addresses are sequential)
  7.      */
  8.     return MCP23018_ControlBitValueInRegister(MCP23018_Td_Ptr->deviceAddress, MCP23018_IOCON, bankSelected, 7);
  9. }

Cały projekt można pobrać z dysku Google pod tym linkiem.

Brak komentarzy:

Prześlij komentarz