I welcome you to third part of my series as part of Upcycle IoT Design Challenge. Today, I will integrate HDC1080 to SAM E51 project. It was quite challenging for me, because I used SAM E51 microcontroller for first time. Even more challenging it was because HDC1080 implements very specific I2C interface which requires you to let measurement triggering transaction in progress and manually wait for data.
I implemented HDC1080 library like many other libraries which I implemented. I control underlaying I2C driver for communication with chip and also implemented driver UART driver for debugging.
UART
Both UART and I2C are on SAM E51 driven using universal peripheral named SERCOM (Serial communication).
The first step is enabling APB bus clock for the peripheral. I use SERCOM5 for UART:
REG_MCLK_APBDMASK |= MCLK_APBDMASK_SERCOM5;
Then you need to assign GPIOs. Group 0 is port A, Group 1 is port B. PMUX register contains value for two consecutive pins while PINCFG is separate for each pin. Thus, for attaching SERCOM5 (which is alternate function 2) to pins B16 and B17 (which are on Curiosity Nano board connected to on-board debugger), use the following commands.
PORT->Group[1].PMUX[8].bit.PMUXE = 0x02; PORT->Group[1].PMUX[8].bit.PMUXO = 0x02; PORT->Group[1].PINCFG[16].bit.PMUXEN = 1; PORT->Group[1].PINCFG[17].bit.PMUXEN = 1;
Next, you need to allocate and configure one of clock generator which is used for generating baudrate. You need to wait if it is busy. I will use generator number 11. Clock source 6 is internal 48 MHz oscillator and I will not divide it.
while (REG_GCLK_SYNCBUSY & GCLK_SYNCBUSY_GENCTRL11) {} REG_GCLK_GENCTRL11 = GCLK_GENCTRL_DIV(1) | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC(6);
At last, you need to configure SERCOM. You need to reset it, set UART signal polarity and bit order, enable transmitter (and received if used) and finally, set value corresponding to baudrate computed according to formula in datasheet. SERCOM uses multiple clocks, so synchronization between clock domain requires waiting when there is pending transaction after every register write. This is done by waiting for clearing SYNCBUSY bit. Some registers do not need it.
while (REG_SERCOM5_USART_SYNCBUSY) {} REG_SERCOM5_USART_CTRLA = 0; while (REG_SERCOM5_USART_SYNCBUSY) {} REG_SERCOM5_USART_CTRLA = SERCOM_USART_CTRLA_SWRST; while (REG_SERCOM5_USART_SYNCBUSY) {} REG_SERCOM5_USART_CTRLA = SERCOM_USART_CTRLA_DORD | SERCOM_USART_CTRLA_MODE(1); while (REG_SERCOM5_USART_SYNCBUSY) {} REG_SERCOM5_USART_CTRLB = SERCOM_USART_CTRLB_TXEN; while (REG_SERCOM5_USART_SYNCBUSY) {} REG_SERCOM5_USART_BAUD = 63019; REG_SERCOM5_USART_CTRLA |= SERCOM_USART_CTRLA_ENABLE; while (REG_SERCOM5_USART_SYNCBUSY) {}
That is all.
I2C
I2C is basically very similar. You need to enable APB peripheral clock, configure pin mapping and configure clock. This time you need two clock generators. One for bitrate and one low-frequency for timeouts. I2C configuratiuon is slightly different, but well described in datasheet. Baudrate (or bitrate in case of I2C) calculation is slightly different as well.
I2C transactions are controlled by writing to reg register for initiating transaction, waiting for flags or interrupt, writing command for special I2C operations and writing or reading data register for actual data transfer.
HDC1080 Library
After implementing my own I2C driver, I implemented library. Like in case of my previous libraries, I exposed all features of HDC1080 chip. Library has following functions:
HDC1080_Status HDC1080_Init(); HDC1080_Status HDC1080_Deinit(); HDC1080_Status HDC1080_GetDefaultConfiguration(HDC1080_Configuration* config); HDC1080_Status HDC1080_GetConfiguration(HDC1080_Configuration* config); HDC1080_Status HDC1080_SetConfiguration(HDC1080_Configuration* config); HDC1080_Status HDC1080_TriggerTemperatureAndHumidityConversion(); HDC1080_Status HDC1080_TriggerTemperatureConversion(); HDC1080_Status HDC1080_TriggerHumidityConversion(); HDC1080_Status HDC1080_GetRawTemperature(uint16_t* temperature); HDC1080_Status HDC1080_GetRawHumidity(uint16_t* humidity); HDC1080_Status HDC1080_GetRawTemperatureAndHumidity(uint16_t* temperature, uint16_t* humidity); HDC1080_Status HDC1080_GetTemperature(float* temperature); HDC1080_Status HDC1080_GetHumidity(float* humidity); HDC1080_Status HDC1080_GetTemperatureAndHumidity(float* temperature, float* humidity); HDC1080_Status HDC1080_EnableHeater(); HDC1080_Status HDC1080_DisableHeater(); HDC1080_Status HDC1080_GetBatteryStatus(HDC1080_BatteryStatus* batterySatus); HDC1080_Status HDC1080_GetManufacturerNumber(uint16_t* manufacturerNumber); HDC1080_Status HDC1080_GetDeviceId(uint16_t* deviceId); HDC1080_Status HDC1080_GetSerialNumber(uint64_t* serialNumber);
Simple program for configuring sensor and printing temperature and humidity continuously is following:
int main(void) { HDC1080_Status hStatus; SystemInit(); UART_Init(); UART_PrintString("\r\n"); hStatus = HDC1080_Init(); checkError(hStatus, "HDC1080_Init"); HDC1080_Configuration config; HDC1080_GetDefaultConfiguration(&config); hStatus = HDC1080_SetConfiguration(&config); checkError(hStatus, "HDC1080_SetConfiguration"); while (1) { hStatus = HDC1080_TriggerTemperatureAndHumidityConversion(); checkError(hStatus, "HDC1080_TriggerConversion"); for (volatile int i = 0; i < 50000; i++) {} float temp = 0; float humi = 0; hStatus = HDC1080_GetTemperatureAndHumidity(&temp, &humi); checkError(hStatus, "HDC1080_Get"); UART_PrintString("TEMP: "); UART_PrintFloat2(temp); UART_PrintString(" degC\tHUMI: "); UART_PrintFloat2(humi); UART_PrintString(" %\r\n"); for (volatile int i = 0; i < 1000000; i++) {} } }
Results
And here are the results from serial terminal:
Next blog: Blog #5: Completing up-cycling broken wireless earbuds
Top Comments