I want to use DAM transfer for calculating CRC with ATSAMC21G17A MCU (E-revision).
I have already implemented 2 independent DMA channels controlling UART transfer, they are working good, I have no problems with them.
Now it's time to get CRC calculating working to calculate CRC of the Flash memory. I just use one timer to generate triggering events for DMA, I know the size of my firmware and just want to get it working. Timer works with period of 100usec.
But it doesn't really work, I get errors with another channels after starting DMA channel with CRC calculation, transfer process of another channel can be interrupted and doesn't work afterwards. It means it could transfer only some bytes, but not all of them. What am I doing wrong?
I have already read the whole errata about my MCU and don't find anything similar. I don't use link descriptors.
Here is my source code:
DMA initialization:
volatile bool DMA_TransferComplete[DMA_CHANNELS_NUM];
static DmacDescriptor dma_descr[DMA_CHANNELS_NUM] __attribute__((aligned(16))) SECTION_DMAC_DESCRIPTOR;
static DmacDescriptor writeback[DMA_CHANNELS_NUM] __attribute__((aligned(16))) SECTION_DMAC_DESCRIPTOR;
static DmacDescriptor descriptor __attribute__((aligned(16))) SECTION_DMAC_DESCRIPTOR;
/** \brief Initialize DMA module
*
* \return Nothing
*
*/
void DMA_Init(void)
{
uint8_t i;
MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC;
DMAC->CTRL.reg &= ~DMAC_CTRL_DMAENABLE;
DMAC->CTRL.reg = DMAC_CTRL_SWRST;
DMAC->BASEADDR.reg = (uint32_t)dma_descr;
DMAC->WRBADDR.reg = (uint32_t)writeback;
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);
for (i = 0; i < DMA_CHANNELS_NUM; i++)
DMA_TransferComplete[i] = true;
NVIC_EnableIRQ(DMAC_IRQn);
NVIC_SetPriority(DMAC_IRQn, 1);
}
Setup for DMA channel:
/** \brief Setup DMA transfer for selected channel
*
* \param [in] channel Number of the channel to act
* \param [in] settings Pointer to TDmaSettings structure with DMA settings
* \return Nothing
*
*/
void DMA_SetupChannel(uint8_t channel, TDmaSettings *settings)
{
uint16_t btctrlVal;
if (channel >= DMA_CHANNELS_NUM)
return;
/**< Setup channel */
DMAC->CHID.reg = DMAC_CHID_ID(channel);
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << channel));
if (settings->trig_src == 0)
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(settings->priority) | DMAC_CHCTRLB_TRIGACT_BLOCK;
else
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(settings->priority) | DMAC_CHCTRLB_TRIGSRC(settings->trig_src) | DMAC_CHCTRLB_TRIGACT_BEAT;
/**< Enable interrupts */
DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK;
/**< Prepare descriptor block */
descriptor.DESCADDR.reg = 0; // only one block to transfer, so 0
descriptor.BTCNT.reg = settings->len;
btctrlVal = DMAC_BTCTRL_VALID | DMAC_BTCTRL_BEATSIZE(settings->beat_size);
descriptor.DSTADDR.reg = (uint32_t)settings->dst_addr;
if (settings->dst_inc == true)
{
descriptor.DSTADDR.reg += settings->len;
btctrlVal |= DMAC_BTCTRL_DSTINC;
}
descriptor.SRCADDR.reg = (uint32_t)settings->src_addr;
if (settings->src_inc == true)
{
descriptor.SRCADDR.reg += settings->len;
btctrlVal |= DMAC_BTCTRL_SRCINC;
}
descriptor.BTCTRL.reg = btctrlVal;
memcpy(&dma_descr[channel], &descriptor, sizeof(DmacDescriptor));
}
Interrupt function for detection of "transfer complete" event:
/**< Interrupt for DMA transfer completion */
void DMAC_Handler(void)
{
uint8_t channel;
uint8_t old_channel;
uint32_t res;
/**< Get old channel number */
old_channel = DMAC->CHID.reg;
res = DMAC->INTSTATUS.reg;
/**< Processing all affected channels */
for (channel = 0; channel < DMA_CHANNELS_NUM; channel++)
{
if (res & (1 << channel))
{
/**< Set transfer complete flag */
DMA_TransferComplete[channel] = true;
/**< Select channel to work with */
DMAC->CHID.reg = DMAC_CHID_ID(channel);
/**< Clear all flags for DMA channel */
DMAC->CHINTFLAG.reg = (DMAC_CHINTENCLR_TCMPL | DMAC_CHINTENCLR_TERR | DMAC_CHINTENCLR_SUSP);
/**< Disable DMA transfer */
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
}
}
DMAC->CHID.reg = old_channel;
}
Starting DMA transfer:
/** \brief Start DMA transfer, channel should be already initialized
*
* \param [in] channel Number of the channel to act
* \return Nothing
*
*/
void DMA_StartChannel(uint8_t channel)
{
if (channel >= DMA_CHANNELS_NUM)
return;
/**< Setup channel */
DMAC->CHID.reg = DMAC_CHID_ID(channel);
if (DMA_TransferComplete[channel] == false)
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
/**< Start DMA transfer */
DMA_TransferComplete[channel] = false;
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
}
Setup CRC calculation:
/** \brief Initialize CRC module
*
* \param
* \param
* \return Nothing
*
*/
void DMA_InitCRC(void)
{
/**< Power on timer */
MCLK->APBCMASK.reg |= DMA_CRC_TIMER_MCLK;
GCLK->PCHCTRL[DMA_CRC_TIMER_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK0_Val | (1 << GCLK_PCHCTRL_CHEN_Pos);
/**< Setup timer for 100usec events */
DMA_CRC_TIMER->COUNT8.CTRLA.reg = TC_CTRLA_SWRST;
while (DMA_CRC_TIMER->COUNT8.SYNCBUSY.reg & TC_SYNCBUSY_SWRST);
DMA_CRC_TIMER->COUNT8.CTRLA.reg |= (DMA_CRC_TIMER_PRESCALER << TC_CTRLA_PRESCALER_Pos) | TC_CTRLA_MODE_COUNT8;
DMA_CRC_TIMER->COUNT8.CTRLBSET.bit.DIR = 0;
DMA_CRC_TIMER->COUNT8.PER.reg = DMA_CRC_TIMER_TICK;
DMA_CRC_TIMER->COUNT8.COUNT.reg = 0;
DMA_CRC_TIMER->COUNT8.CTRLA.reg |= (TC_CTRLA_ENABLE);
DMAC->CTRL.reg &= ~DMAC_CTRL_CRCENABLE;
/**< Configure the CRC engine */
DMAC_CRCCTRL_Type crcctrl =
{
.bit.CRCSRC = DMAC_CRCCTRL_CRCSRC_IO_Val, /* I/O interface */
.bit.CRCPOLY = DMAC_CRCCTRL_CRCPOLY_CRC16_Val, /* CRC-16 (CRC-CCITT) */
.bit.CRCBEATSIZE = DMAC_CRCCTRL_CRCBEATSIZE_BYTE_Val, /* Byte bus access */
};
DMAC->CRCCTRL.reg = crcctrl.reg;
}
Start new CRC calculation:
/** \brief Start CRC conversion
*
* \param [in] crc_init CRC initial value
* \return Nothing
*
*/
void DMA_StartCRC(uint32_t crc_init)
{
/**< Clear the busy bit */
DMAC->CRCSTATUS.bit.CRCBUSY = 1;
DMAC->CTRL.bit.CRCENABLE = 0;
/**< Initialize start CRC value for the CRC16 */
DMAC->CRCCHKSUM.reg = crc_init;
/**< Enable the CRC engine */
DMAC->CTRL.bit.CRCENABLE = 1;
}
Get result of CRC calculation:
/** \brief Get CRC result from DMA module
*
* \return CRC as uint16_t
*
*/
uint16_t DMA_GetCRC(void)
{
return (uint16_t)DMAC->CRCCHKSUM.reg;
}
And now the source code of the thread with CRC calculation (I am using FreeRTOS):
/**< CRC initialization */
DMA_InitCRC();
/**< Setup DMA channel for transmission */
DmaSett.beat_size = DMAC_BTCTRL_BEATSIZE_BYTE_Val;
DmaSett.trig_src = TC1_DMAC_ID_OVF;
DmaSett.dst_addr = (void*)&DMAC->CRCDATAIN.reg;
DmaSett.src_addr = APP_ADDRESS;
DmaSett.src_inc = true;
DmaSett.dst_inc = false;
DmaSett.len = addr;
DmaSett.priority = 0;
DMA_SetupChannel(DMA_CHANNEL_CRC, &DmaSett);
DMA_StartCRC(CRC16_INIT_VAL);
DMA_StartChannel(DMA_CHANNEL_CRC);
while (1)
{
// some periodical actions
// ...
if (DMA_IsReady(DMA_CHANNEL_CRC) == true)
{
crc = DMA_GetCRC();
DMA_StartCRC(CRC16_INIT_VAL);
DMA_StartChannel(DMA_CHANNEL_CRC);
// do something with CRC here
// ...
}
}
Does anybody have any experiences with DMA module of ATSAMC21? Actually it runs normally with UART, but I can't solve this problem with CRC interaction for some days already.
I will be grateful for any ideas!
Related
I'm using ESP32 on VSPI in a half-duplex manner with a slave device.
I wish to first send the device a standard SPI transaction, and right after that to keep clocking out dummy pulses in the same VSPI interface (clocking VSPICLK pin with same frequency, with zeros on VSPID).
I managed to send the transaction itself, but I don't find the right way to keep the dummy clocks. Important to mention that the dummy pulses shouldn't block the code so I can handle other tasks.
Any thoughts?
Update:
Getting closer by calling a simple write bytes method
/**
* #param data uint8_t *
* #param size uint32_t
*/
void SPIClass::writeBytes(const uint8_t * data, uint32_t size)
{
if(_inTransaction){
return spiWriteNL(_spi, data, size);
}
spiSimpleTransaction(_spi);
spiWriteNL(_spi, data, size);
spiEndTransaction(_spi);
}
But this one blocks...
Previously:
I've tries to send empty bytes in a normal SPI transaction but this is a blocking method and got some lags between bytes (you can see it in the VSCLK blue channel).
dummy clocks with lags, and blocking code
void write(uint8_t data)
{
noInterrupts();
// Working transaction to the device
// _spi_dev is an SPIClass*
_spi_dev->beginTransaction();
_spi_dev->transfer(data);
_spi_dev->endTransaction();
delayMicroseconds(10); // Required delay by the slave device to latch the command
// naively sending dummy bytes - a blocking call
_spi_dev->beginTransaction();
for (uint8_t n = 0; n < 100; n++) {
for (int8_t c = 10; c > 0; c--) _spi_dev->transfer(0x0);
}
_spi_dev->endTransaction();
interrupts();
}
This code:
_spi_dev->transfer(data);
ends up with this:
uint8_t spiTransferByte(spi_t * spi, uint8_t data)
{
if(!spi) {
return 0;
}
SPI_MUTEX_LOCK();
spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
spi->dev->miso_dlen.usr_miso_dbitlen = 7;
spi->dev->data_buf[0] = data;
#if CONFIG_IDF_TARGET_ESP32C3
spi->dev->cmd.update = 1;
while (spi->dev->cmd.update);
#endif
spi->dev->cmd.usr = 1;
while(spi->dev->cmd.usr);
data = spi->dev->data_buf[0] & 0xFF;
SPI_MUTEX_UNLOCK();
return data;
}
In Addition this one:
_spi_dev->beginTransaction();
With this:
void SPIClass::beginTransaction(SPISettings settings)
{
//check if last freq changed
uint32_t cdiv = spiGetClockDiv(_spi);
if(_freq != settings._clock || _div != cdiv) {
_freq = settings._clock;
_div = spiFrequencyToClockDiv(_freq);
}
spiTransaction(_spi, _div, settings._dataMode, settings._bitOrder);
_inTransaction = true;
}
I am fairly new to I2C so please excuse my lack of knowledge. I am trying to read Data from an PAC1710 sensor using an Stm32H743ZI MCU with the HAL-Library and the cubemx code generator.
I can send the first part of my message but I don't get an acknowledge after sending the address. I am using a 2700 Ohm resistor to ground on the ADDR_SEL Pin, ALERT goes to ground over an 10 kOhm resistor.
As for my code, this is my intializiation:
void MX_I2C2_Init(void)
{
hi2c2.Instance = I2C2;
hi2c2.Init.Timing = 0x307075B1;
hi2c2.Init.OwnAddress1 = 0;
hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c2.Init.OwnAddress2 = 0;
hi2c2.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c2) != HAL_OK) {
Error_Handler();
}
/** Configure Analogue filter */
if (HAL_I2CEx_ConfigAnalogFilter(&hi2c2, I2C_ANALOGFILTER_ENABLE) != HAL_OK) {
Error_Handler();
}
/** Configure Digital filter */
if (HAL_I2CEx_ConfigDigitalFilter(&hi2c2, 0) != HAL_OK) {
Error_Handler();
}
}
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (i2cHandle->Instance == I2C2) {
/* USER CODE BEGIN I2C2_MspInit 0 */
/* USER CODE END I2C2_MspInit 0 */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**I2C2 GPIO Configuration
PB11 ------> I2C2_SDA
PF1 ------> I2C2_SCL
*/
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C2;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C2;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* I2C2 clock enable */
__HAL_RCC_I2C2_CLK_ENABLE();
}
}
My implementation is very simplistic:
uint8_t i2cbuffer[8];
uint16_t val = 0;
HAL_StatusTypeDef ret;
and in the main loop I have the following code:
i2cbuffer[0] = 254;
ret = HAL_I2C_Master_Transmit(&hi2c2, PAC1710Address << 1, i2cbuffer, 1, HAL_MAX_DELAY);
if (ret == HAL_OK) {
ret = HAL_I2C_Master_Receive(&hi2c2, PAC1710Address << 1 , i2cbuffer, 2, HAL_MAX_DELAY);
if (ret == HAL_OK) {
val = i2cbuffer[0];
}
}
HAL_Delay(500);
I chose 254 because I though the slave should be able to answer to that regardless of his measurement. However I don't even get as far as sending the register, after looking into it with an oscilloscope I send the address and get a NACK.
Is there anything obvious I am missing?
After some comments, there was more useful info, biggest part being that oscilloscope showed Start 1010100 0 1 Stop. While 1010100 is the 0x54 address, the W bit needs to be counted as well, so the 8-bit data was actually 10101000, instead of the expected 01010100. Address should not have been shifted left, as it was already correct.
This is a somewhat common mistake with I2C. Attention needs to be paid to the exact address and R/!W bits, as in some documents and APIs they're shifted left, in some they aren't.
A very helpful bit of info here was the oscilloscope trace, that really helped show what exactly is going on.
EDIT/SOLVED: The issue ended up being my Serial.print statements in the Arduino code which were slowing down to execution of the actual communication code. Basically, the Arduino was receiving bytes after the STM board had finished sending them, so it was only registering 'd'/100.
I'm currently enrolled in an online course and facing an issue with one of the exercises. The exercise seems pretty straightforward, but I can't seem to get everything to work. The exercise is to send a string from a master MCU STM32F407 to an Arduino Uno board, which is then supposed to print the message and it's length.
The Arduino board seemed to register the SS, but would not print anything, print an incomplete/incorrect message (e.g. "hello vorl"), or print gibberish. After some debugging Idiscovered I had incorrectly set my CPOL and CPHA registers, however, now it won't print anything close to "Hello world." After more debugging, it seems like the Arduino is getting stuck in it's SPI_SlaveReceive function. The SPI code for the STM is designed to first send the length of the message and then send the actual message. From what I can tell, the Arduino doesn't seem to be registering the correct length for the first transmission, which is messing up the loop for the SPI_SlaveReceive calls that are supposed to grab the actual message.
I am new to embedded and have been trying to learn it mostly on my own. I feel like there is probably something basic that I am missing, but don't know about. I also feel as if the code is poorly designed, but I am trying to stick to the course (the STM code is "written" following guidelines and the arduino code was just provided). I will post the code below, but would even just appreciate ideas about what may be going wrong/how I can debug. Below everything I will also post some error logging.
This is also my first stackoverflow post, so please let me know if there is anything I need to add (or remove). Thanks!
Update:
I think it might be something with the clock speed. I tried running through the code again in a debugger and found that I was successfully able to send the data if I put a break point on the code that modifies the Data Register of the SPI. It seems like the SPI might be sending data more quickly than the Arduino can process. If I step through the code that sends the length of the message "Hello world" then I can see that the Arduino successfully received 11. If I continue to step through the pSPIx->DR = *pTxBuffer; instruction of the second call to the SPI_SendData, I can successfully send the message. However, if after sending the length, I try to step through the entirety of the second call to SPI_SendData in one step. The arduino doesn't register the entire string. I tried modifying the SPI pre-scalar to be greater values and this seemed to help. i.e. The arduino was able to output more valid characters (resulting in "hellod"). This confuses me however as the APB1 that controls SPI2 has a maximum freq of 42MHz. The pre-scalar value that I found gave me the best results was 7, which means the PCLK is divided by 256 resulting in, if I'm thinking about this correctly, a frequency of 164 MHz at most. Based on the specs of the Ardunio, it should have no problem processing this communication. I'm thinking I might have a problem is how often the bits are sent? Maybe my SPI_SendData function isn't working correctly?
Other details:
4 wires connecting the boards: GND, CLK (STM: PB13 <-> UNO: 13), MOSI (STM: PB15 <-> A: 11), and NSS (STM: PB12 <-> A:10)
I don't have a logic analyzer, so I haven't been able to see what's actually being sent (which sucks)
I realize it may be hard to get the full context without posting all of the code, but I figured that would be too much, so tried to just post the relevant parts.
Logging:
16:01:42.461 -> Slave Initialized
16:01:42.494 -> Slave waiting for ss to go low
16:01:45.500 -> slave selected
16:01:45.500 -> Waiting to receive length
16:01:45.534 -> Length: 68
16:01:47.520 -> Received:
16:01:47.554 -> Received: d
16:01:48.968 -> Received:
16:01:48.968 -> Received: d
16:01:49.858 -> Received:
16:01:49.858 -> Received: d
16:01:50.499 -> Received:
16:01:50.499 -> Received: d
16:01:51.137 -> Received:
16:01:51.137 -> Received: d
16:01:51.707 -> Received:
16:01:51.741 -> Received: d
16:01:52.244 -> Received:
16:01:52.277 -> Received: d
16:01:52.782 -> Received:
16:01:52.782 -> Received: d
16:01:53.360 -> Received:
16:01:53.393 -> Received: d
16:01:53.866 -> Received:
16:01:53.899 -> Received: d
16:01:54.441 -> Received:
16:01:54.441 -> Received: d
16:01:54.946 -> Received:
16:01:54.980 -> Received: d
16:01:55.484 -> Received:
16:01:55.518 -> Received: d
16:01:55.994 -> Received:
16:01:56.027 -> Received: d
16:01:56.599 -> Received:
16:01:56.599 -> Received: d
16:01:57.105 -> Received:
16:01:57.138 -> Received: `
16:01:57.609 -> Received:
16:01:57.644 -> Received: d
16:01:58.085 -> Received:
16:01:58.118 -> Received: d
16:01:58.622 -> Received:
16:01:58.622 -> Received: d
16:01:59.128 -> Received:
16:01:59.161 -> Received: d
16:01:59.676 -> Received:
16:01:59.676 -> Received: d
16:02:00.146 -> Received:
16:02:00.180 -> Received: d
16:02:00.654 -> Received:
16:02:00.654 -> Received: d
16:02:01.127 -> Received:
16:02:01.127 -> Received: d
16:02:01.634 -> Received:
16:02:01.668 -> Received: d
16:02:02.105 -> Received:
16:02:02.139 -> Received: d
16:02:02.582 -> Received:
16:02:02.582 -> Received: d
16:02:03.052 -> Received:
16:02:03.052 -> Received: d
16:02:03.729 -> Received:
16:02:03.729 -> Received: d
16:02:04.236 -> Received:
16:02:04.236 -> Received: d
16:02:04.774 -> Received:
16:02:04.774 -> Received: d
16:02:05.336 -> Received:
16:02:05.336 -> Received: d
16:02:05.947 -> Received:
16:02:05.980 -> Received: d
16:02:06.485 -> Received:
16:02:06.485 -> Received: d
16:02:06.518 -> done receiving word
16:02:06.518 -> Rcvd:
16:02:06.553 -> ddddddddddddddd`dddddddddddddddddd
16:02:06.620 -> Length:68
16:02:06.620 -> Slave deselected
16:02:06.654 -> Slave waiting for ss to go low
16:02:07.025 -> slave selected
16:02:07.058 -> Waiting to receive length
16:02:07.092 -> Length: 100
Arduino code:
/* SPI Slave Demo
*
* SPI pin numbers:
* SCK 13 // Serial Clock.
* MISO 12 // Master In Slave Out.
* MOSI 11 // Master Out Slave In.
* SS 10 // Slave Select . Arduino SPI pins respond only if SS pulled low by the master
*
*/
#include <SPI.h>
#include<stdint.h>
#define SPI_SCK 13
#define SPI_MISO 12
#define SPI_MOSI 11
#define SPI_SS 10
char dataBuff[500];
//Initialize SPI slave.
void SPI_SlaveInit(void)
{
#if 0
// Initialize SPI pins.
pinMode(SPI_SCK, INPUT);
pinMode(SPI_MOSI, INPUT);
pinMode(SPI_MISO, OUTPUT);
pinMode(SPI_SS, INPUT);
// Enable SPI as slave.
SPCR = (1 << SPE);
#endif
// Initialize SPI pins.
pinMode(SCK, INPUT);
pinMode(MOSI, INPUT);
pinMode(MISO, OUTPUT);
pinMode(SS, INPUT);
//make SPI as slave
// Enable SPI as slave.
SPCR = (1 << SPE);
}
//This function returns SPDR Contents
uint8_t SPI_SlaveReceive(void)
{
/* Wait for reception complete */
while(!(SPSR & (1<<SPIF)));
/* Return Data Register */
return SPDR;
}
//sends one byte of data
void SPI_SlaveTransmit(char data)
{
/* Start transmission */
SPDR = data;
/* Wait for transmission complete */
while(!(SPSR & (1<<SPIF)));
}
// The setup() function runs right after reset.
void setup()
{
// Initialize serial communication
Serial.begin(9600);
// Initialize SPI Slave.
SPI_SlaveInit();
Serial.println("Slave Initialized");
}
uint16_t dataLen = 0;
uint32_t i = 0;
// The loop function runs continuously after setup().
void loop()
{
Serial.println("Slave waiting for ss to go low");
while(digitalRead(SS) );
Serial.println("slave selected");
// Serial.println("start");
//1. read the length
// dataLen = (uint16_t)( SPI_SlaveReceive() | (SPI_SlaveReceive() << 8) );
//Serial.println(String(dataLen,HEX));
i = 0;
Serial. println("Waiting to receive length");
dataLen = SPI_SlaveReceive();
for(i = 0 ; i < dataLen ; i++ )
{
dataBuff[i] = SPI_SlaveReceive();
Serial.print("Received: ");
Serial.println(dataBuff[i]);
}
Serial.println("done receiving word");
// Serial.println(String(i,HEX));
dataBuff[i] = '\0';
Serial.println("Rcvd:");
Serial.println(dataBuff);
Serial.print("Length:");
Serial.println(dataLen);
while(!digitalRead(SS));
Serial.println("Slave deselected");
}
Main STM code:
#include <string.h>
#include "stm32f407xx.h"
/*
* PB15 --> SPI2_MOSI
* PB14 --> SPI2_MISO
* PB13 --> SPI2_SCLK
* PB12 --> SPI2_NSS
* ALT Function mode: 5
*/
void SPI_GPIO_Setup(void);
void GPIO_ButtonInit(void);
void SPI_Setup(void);
void delay(int time){
for(uint32_t i=0; i<time; i++);
}
int main(void) {
char userData[] = "Hello world";
//initizalize button
GPIO_ButtonInit();
//This function is used to initialize the gpio pins to behave as spi pins
SPI_GPIO_Setup();
//This function is used to initialize the SPI peripheral
SPI_Setup();
//this makes NSS signal internally high and avoids MODF error
//NOTE: This should be taken care of in my SPI_Init function
// SPI_SSIConfig(SPI2, ENABLE);
//SSOE to 1 does NSS output enable
//I don't want to enable this in this way, but it is more simple due to the code design
//i.e. (using shifts instead of masks for registers)
SPI_SSOEConfig(SPI2, ENABLE);
while(1){
//wait for button to be pressed
while(! GPIO_ReadFromInputPin(GPIOA, GPIO_PIN_0));
delay(250000); //used for button debouncing
//enable the SPI2 peripheral --- according to generated code, this seems to be done in the transmit function
// I a SET_BIT macro in the transmit function if SPI isn't enabled, make sure to test this
SPI_PeripheralControl(SPI2, ENABLE);
//first send the length of data (Arduino expects this as 1 byte)
uint8_t dataLen = strlen(userData);
SPI_SendData(SPI2, &dataLen, 1);
//send data
SPI_SendData(SPI2, (uint8_t*)userData, dataLen);
//confirm the SPI is not busy
while(SPI_GetFlagStatus(SPI2, SPI_BSY_FLAG));
//disable the SPI2 peripheral --- according to Generated code, this seems to be done in abort functions
SPI_PeripheralControl(SPI2, DISABLE);
}
return 0;
}
void SPI_GPIO_Setup(void) {
GPIO_Handle_t SPIpins;
SPIpins.pGPIOx = GPIOB;
SPIpins.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_ALTFN;
SPIpins.GPIO_PinConfig.GPIO_PinAltFunMode = 5;
SPIpins.GPIO_PinConfig.GPIO_PinOPType = GPIO_OPTYPE_PP;
SPIpins.GPIO_PinConfig.GPIO_PinPuPdControl = GPIO_PU;
SPIpins.GPIO_PinConfig.GPIO_PinSpeed = GPIO_SPEED_FAST;
//MOSI
SPIpins.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_15;
GPIO_Init(&SPIpins);
//MISO
// SPIpins.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_14;
// GPIO_Init(&SPIpins);
//SCLK
SPIpins.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_13;
GPIO_Init(&SPIpins);
//NSS
SPIpins.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_12;
GPIO_Init(&SPIpins);
}
void GPIO_ButtonInit(void){
GPIO_Handle_t GpioBtn;
// configure button
//Set up PA0 to be an input for the button press
GpioBtn.pGPIOx = GPIOA;
GpioBtn.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_0;
GpioBtn.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_IN;
GpioBtn.GPIO_PinConfig.GPIO_PinSpeed = GPIO_SPEED_FAST;
GpioBtn.GPIO_PinConfig.GPIO_PinPuPdControl = GPIO_NO_PUPD;
GPIO_Init(&GpioBtn);
}
void SPI_Setup(void){
SPI_Handle_t hSPI;
hSPI.SPIx = SPI2;
hSPI.SPIConfig.SPI_BusConfig = SPI_BUS_CONFIG_FD;
hSPI.SPIConfig.SPI_DeviceMode = SPI_DEVICE_MODE_MASTER;
hSPI.SPIConfig.SPI_SclkSpeed = SPI_SCLK_SPEED_DIV8; //generate SCLK of 2 MHz
hSPI.SPIConfig.SPI_DFF = SPI_DFF_8BITS;
hSPI.SPIConfig.SPI_CPOL = SPI_CPOL_LOW;
hSPI.SPIConfig.SPI_CPHA = SPI_CPHA_LOW;
hSPI.SPIConfig.SPI_SSM = SPI_SSM_DI; //Hardware slave management disabled for NSS pin
SPI_Init(&hSPI);
}
SPI Driver code:
/*
* stm32f407xx_spi_driver.c
*
* Created on: Oct 17, 2019
* Author: pigmo
*/
#include "stm32f407xx_spi_driver.h"
/**************************************************************************
* APIs supported by this driver
* For more information about these APIs check the function description
**************************************************************************/
/*
* Peripheral clock setup
*/
/******************************************************
* #fn - SPI_PeriClockControl
*
* #brief - Enables or disables peripheral clock for the given SPI port
*
* #param[in] - base address of SPI peripheral
* #param[in] - ENABLE or DISABLE macros
*
* #return - none
*
* #Note - SPI4 is reserved and won't be affected by this function
*/
void SPI_PeriClockControl(SPI_RegDef_t* pSPIx, uint8_t enOrDi){
if(enOrDi == ENABLE){
if(pSPIx == SPI1){
SPI1_PCLK_EN();
} else if (pSPIx == SPI2){
SPI2_PCLK_EN();
} else if (pSPIx == SPI3){
SPI3_PCLK_EN();
}
} else {
if(pSPIx == SPI1){
SPI1_PCLK_DI();
} else if (pSPIx == SPI2){
SPI2_PCLK_DI();
} else if (pSPIx == SPI3){
SPI3_PCLK_DI();
}
}
}
/*
* Init and De-init
*/
/******************************************************
* #fn - SPI_Init
*
* #brief - Initializes a given GPIO peripheral with given configurations
*
* #param[in] - Struct holding base address and desired configurations of SPI peripheral
*
* #return - none
*
* #Note - I don't like the way this is done, but I'm doing it to be consistent with the course
* Instead, I think the SPIConfig should hold masks, not enum type values
*/
void SPI_Init(SPI_Handle_t *pSPIHandle){
//enable the spi clock
SPI_PeriClockControl(pSPIHandle->SPIx, ENABLE);
//disable the SPI (concept taken from STM generated code)
SPI_PeripheralControl(pSPIHandle->SPIx, DISABLE);
//first configure the SPI CR1 register
uint32_t tempReg = 0;
//1. configure the device mode
if(pSPIHandle->SPIConfig.SPI_DeviceMode == SPI_DEVICE_MODE_MASTER){
// if the device is master mode, then the SSI must also be 1 to avoid an error (unless Multi master?)
// this is only true if SSM is enabled, can we keep this functionality even is SSM is disabled?
// based on stm generated code, the SSI stays enabled for SSM enabled and disabled
tempReg |= pSPIHandle->SPIConfig.SPI_DeviceMode << SPI_CR1_MSTR;
tempReg |= (1 << SPI_CR1_SSI); // this should probably be done in another way
} else {
tempReg |= pSPIHandle->SPIConfig.SPI_DeviceMode << SPI_CR1_MSTR;
}
//2. configure the bus config
if(pSPIHandle->SPIConfig.SPI_BusConfig == SPI_BUS_CONFIG_FD){
//bdi should be cleared
tempReg &= ~(1 << SPI_CR1_BIDIMODE);
} else if(pSPIHandle->SPIConfig.SPI_BusConfig == SPI_BUS_CONFIG_HD){
//bdi should be set
tempReg |= ~(1 << SPI_CR1_BIDIMODE);
} else {
//bdi should be cleared
tempReg &= ~(1 << SPI_CR1_BIDIMODE);
//RXONLY should be set
tempReg |= (1 << SPI_CR1_RXONLY);
}
//3. configure the clock speed
tempReg |= (pSPIHandle->SPIConfig.SPI_SclkSpeed << SPI_CR1_BR);
//4. configure the Data frame format
tempReg |= (pSPIHandle->SPIConfig.SPI_DFF << SPI_CR1_DFF);
//5. configure the clock polarity
tempReg |= (pSPIHandle->SPIConfig.SPI_CPOL << SPI_CR1_CPOL);
//6. configure the clock phase
tempReg |= (pSPIHandle->SPIConfig.SPI_CPHA << SPI_CR1_CPHA);
//7. determine hardware or software slave management
tempReg |= (pSPIHandle->SPIConfig.SPI_SSM << SPI_CR1_SSM);
pSPIHandle->SPIx->CR1 = tempReg;
}
/******************************************************
* #fn - SPI_DeInit
*
* #brief - Deinitializes a given SPI periphal and resets register
*
* #param[in] - base address of SPI peripheral
*
* #return - none
*
* #Note - none
*/
void SPI_DeInit(SPI_RegDef_t* pSPIx){
if(pSPIx == SPI1){
SPI1_REG_RESET();
} else if (pSPIx == SPI2){
SPI2_REG_RESET();
} else if (pSPIx == SPI3){
SPI3_REG_RESET();
}
}
/*
* Data send and receive
*/
uint8_t SPI_GetFlagStatus(SPI_RegDef_t* pSPIx, uint32_t flag){
if(pSPIx->SR & flag){
return SET;
} else {
return RESET;
}
}
/******************************************************
* #fn - SPI_SendData
*
* #brief - sends data of length len to transmit buffer
*
* #param[in] - base address of SPI peripheral
* #param[in] - address of Tx buffer
* #param[in] - length of data to send
*
* #return - none
*
* #Note - This is a blocking call
*/
//actual code seems much more complex, check that out
void SPI_SendData(SPI_RegDef_t* pSPIx, uint8_t *pTxBuffer, uint32_t len){
//if SPi is not enabled, then enable it
if((pSPIx->CR1 & (1 << SPI_CR1_SPE)) != (1 << SPI_CR1_SPE)){
SET_BIT(pSPIx->CR1, (1 << SPI_CR1_SPE)); // this is repetitive, should just have a mask,
}
while(len > 0) {
//1. Wait until Tx is empty
while(SPI_GetFlagStatus(pSPIx, SPI_TXE_FLAG) == RESET);
//2. Check DFF in CR1
if (pSPIx->CR1 & (1 << SPI_CR1_DFF) ) {
//16 bit dff
//3. load data into DR
pSPIx->DR = *((uint16_t*)pTxBuffer);
len--;
len--;
(uint16_t*)pTxBuffer++;
} else {
//8 bit dff
//3. load data into DR
pSPIx->DR = *pTxBuffer;
len--;
pTxBuffer++;
}
}
}
/******************************************************
* #fn - SPI_ReceiveData
*
* #brief - Receives data of length len to Receive buffer
*
* #param[in] - base address of SPI peripheral
* #param[in] - address of Rx buffer
* #param[in] - length of data to Receive
*
* #return - none
*
* #Note - none
*/
void SPI_ReceiveData(SPI_RegDef_t* pSPIx, uint8_t *pRxBuffer, uint32_t len){
}
/*
* IRQ configuration and handling
*/
/******************************************************
* #fn - SPI_IRQITConfig
*
* #brief - enables or disables a SPI peripherals IRQ functionality
*
* #param[in] - IRQ number of SPI peripheral
* #param[in] - ENABLE or DISABLE macros
*
* #return - none
*
* #Note - none
*/
void SPI_IRQInterruptConfig(uint8_t IRQNumber, uint8_t enOrDi){
}
/******************************************************
* #fn - SPI_IRQPriorityConfig
*
* #brief - sets the priority for a given IRQ number
*
* #param[in] - IRQ priority of SPI peripheral
* #param[in] - IRQ number of SPI peripheral
*
* #return - none
*
* #Note - modifies the NVIC peripheral
*/
void SPI_IRQPriorityConfig(uint8_t IRQPriority, uint8_t IRQNumber){
}
/******************************************************
* #fn - SPI_IRQHandling
*
* #brief - deals with a interrupt triggered by a SPI pin
*
* #param[in] - Struct holding base address and desired configurations of SPI peripheral
*
* #return - none
*
* #Note - none
*/
void SPI_IRQHandling(SPI_Handle_t *pHandle){
}
/******************************************************
* #fn - SPI_PeripheralControl
*
* #brief - Enables or Disables SPI
*
* #param[in] - Pointer to a SPI register
*
* #return - none
*
* #Note - I'm implementing this in the transmit function
*/
void SPI_PeripheralControl(SPI_RegDef_t *pSPIx, uint8_t enOrDi){
if(enOrDi == ENABLE){
pSPIx->CR1 |= (1 << SPI_CR1_SPE);
} else {
pSPIx->CR1 &= ~(1 << SPI_CR1_SPE);
}
}
/******************************************************
* #fn - SPI_SSIConfig
*
* #brief - Sets or Resets SSI bit in SPI CR1 reg
*
* #param[in] - Pointer to a SPI register
*
* #return - none
*
* #Note - This code is used in the course I'm following,
* but I think it is redundant and bad practice. It will not be used.
* Instead, I implemented a check in the SPI init function to enable SSI w/ master
*/
void SPI_SSIConfig(SPI_RegDef_t *pSPIx, uint8_t enOrDi){
if(enOrDi == ENABLE){
pSPIx->CR1 |= (1 << SPI_CR1_SSI);
} else {
pSPIx->CR1 &= ~(1 << SPI_CR1_SSI);
}
}
/******************************************************
* #fn - SPI_SSOEConfig
*
* #brief - Sets or Resets SSOE bit in SPI CR2 reg
*
* #param[in] - Pointer to a SPI register
*
* #return - none
*
* #Note - This code is used in the course I'm following,
* but I think it is redundant and bad practice. I will use it bec of consistency
* Otherwise, in the STM generated code, this bit is determine by a NSS value in the init struct
* See: WRITE_REG(hspi->Instance->CR2, (((hspi->Init.NSS >> 16U) & SPI_CR2_SSOE) | hspi->Init.TIMode));
*/
void SPI_SSOEConfig(SPI_RegDef_t *pSPIx, uint8_t enOrDi){
if(enOrDi == ENABLE){
pSPIx->CR2 |= (1 << SPI_CR2_SSOE);
} else {
pSPIx->CR2 &= ~(1 << SPI_CR2_SSOE);
}
}
I am not able to retrieve data from i2c bus from an Arduino to a mDot LoRa node. Based on the ARMBed i2c library tried to get data from the Arduino (which is the slave) using this code:
/**
* Copyright (c) 2017, Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include "lorawan/LoRaWANInterface.h"
#include "lorawan/system/lorawan_data_structures.h"
#include "events/EventQueue.h"
// Application helpers
#include "DummySensor.h"
#include "trace_helper.h"
#include "lora_radio_helper.h"
using namespace events;
// documentacion de i2c
// https://os.mbed.com/docs/mbed-os/v5.12/apis/i2c.html
#include "mbed.h"
mbed::I2C i2c(I2C_SDA, I2C_SCL);
const int addr7bit = 0x29; // 7-bit I2C address (del ARDUINO)
const int addr8bit = addr7bit << 1; // 8-bit I2C address (shift a izquierda para el MDOT)
// Max payload size can be LORAMAC_PHY_MAXPAYLOAD.
// This example only communicates with much shorter messages (<30 bytes).
// If longer messages are used, these buffers must be changed accordingly.
uint8_t tx_buffer[30];
uint8_t rx_buffer[30];
/*
* Sets up an application dependent transmission timer in ms. Used only when Duty Cycling is off for testing
*/
#define TX_TIMER 10000
/**
* Maximum number of events for the event queue.
* 10 is the safe number for the stack events, however, if application
* also uses the queue for whatever purposes, this number should be increased.
*/
#define MAX_NUMBER_OF_EVENTS 10
/**
* Maximum number of retries for CONFIRMED messages before giving up
*/
#define CONFIRMED_MSG_RETRY_COUNTER 3
/**
* Dummy pin for dummy sensor
*/
#define PC_9 0
/**
* Dummy sensor class object
*/
DS1820 ds1820(PC_9);
/**
* This event queue is the global event queue for both the
* application and stack. To conserve memory, the stack is designed to run
* in the same thread as the application and the application is responsible for
* providing an event queue to the stack that will be used for ISR deferment as
* well as application information event queuing.
*/
static EventQueue ev_queue(MAX_NUMBER_OF_EVENTS *EVENTS_EVENT_SIZE);
/**
* Event handler.
*
* This will be passed to the LoRaWAN stack to queue events for the
* application which in turn drive the application.
*/
static void lora_event_handler(lorawan_event_t event);
/**
* Constructing Mbed LoRaWANInterface and passing it the radio object from lora_radio_helper.
*/
static LoRaWANInterface lorawan(radio);
/**
* Application specific callbacks
*/
static lorawan_app_callbacks_t callbacks;
/**
* Entry point for application
*/
int main(void)
{
// setup tracing
setup_trace();
// stores the status of a call to LoRaWAN protocol
lorawan_status_t retcode;
// Initialize LoRaWAN stack
if (lorawan.initialize(&ev_queue) != LORAWAN_STATUS_OK) {
printf("\r\n LoRa initialization failed! \r\n");
return -1;
}
printf("\r\n Mbed LoRaWANStack initialized \r\n");
// prepare application callbacks
callbacks.events = mbed::callback(lora_event_handler);
lorawan.add_app_callbacks(&callbacks);
// Set number of retries in case of CONFIRMED messages
if (lorawan.set_confirmed_msg_retries(CONFIRMED_MSG_RETRY_COUNTER)
!= LORAWAN_STATUS_OK) {
printf("\r\n set_confirmed_msg_retries failed! \r\n\r\n");
return -1;
}
printf("\r\n CONFIRMED message retries : %d \r\n",
CONFIRMED_MSG_RETRY_COUNTER);
// Enable adaptive data rate
if (lorawan.enable_adaptive_datarate() != LORAWAN_STATUS_OK) {
printf("\r\n enable_adaptive_datarate failed! \r\n");
return -1;
}
printf("\r\n Adaptive data rate (ADR) - Enabled \r\n");
retcode = lorawan.connect();
if (retcode == LORAWAN_STATUS_OK ||
retcode == LORAWAN_STATUS_CONNECT_IN_PROGRESS) {
} else {
printf("\r\n Connection error, code = %d \r\n", retcode);
return -1;
}
printf("\r\n Connection - In Progress ...\r\n");
// make your event queue dispatching events forever
ev_queue.dispatch_forever();
return 0;
}
/**
* Sends a message to the Network Server
*/
static void send_message()
{
uint16_t packet_len;
int16_t retcode;
float sensor_value;
// esta sería la variable que guardará el "paquete" leido del ARDUINO
char cmd[28];
// inicializamos en 0 cada valor del arreglo
for (size_t i = 0; i < 28; i++)
{
cmd[i] = 0x00;
}
if (ds1820.begin()) {
ds1820.startConversion();
sensor_value = ds1820.read();
printf("\r\n Esto es console log = %3.1f \r\n", sensor_value);
ds1820.startConversion();
packet_len = sprintf((char *) tx_buffer, "Esto es lo que se manda: %3.1f", sensor_value); //28 bytes
} else {
// printf("\r\n No sensor found \r\n");
// return;
// basicamente tengo que mandar cualquier cosa para que el maestro me conteste con los datos
// de los sensores.
i2c.write(addr8bit, cmd, 28);
printf("%d\n", addr7bit);
printf("%d\n", addr8bit);
wait(5); // 5 segundos
// y ahora leo los 28 bytes recibidos, el paquete enviado desde arduino deberia ser de 28 bytes
i2c.read(addr8bit, cmd, 28);
printf("%s\r\n", cmd);
packet_len = sprintf((char *) tx_buffer, "%s", cmd);
}
retcode = lorawan.send(MBED_CONF_LORA_APP_PORT, tx_buffer, packet_len, MSG_UNCONFIRMED_FLAG);
if (retcode < 0) {
retcode == LORAWAN_STATUS_WOULD_BLOCK ? printf("send - WOULD BLOCK\r\n")
: printf("\r\n send() - Error code %d \r\n", retcode);
if (retcode == LORAWAN_STATUS_WOULD_BLOCK) {
//retry in 3 seconds
if (MBED_CONF_LORA_DUTY_CYCLE_ON) {
ev_queue.call_in(3000, send_message);
}
}
return;
}
printf("\r\n %d bytes scheduled for transmission \r\n", retcode);
memset(tx_buffer, 0, sizeof(tx_buffer));
}
/**
* Receive a message from the Network Server
*/
static void receive_message()
{
uint8_t port;
int flags;
int16_t retcode = lorawan.receive(rx_buffer, sizeof(rx_buffer), port, flags);
if (retcode < 0) {
printf("\r\n receive() - Error code %d \r\n", retcode);
return;
}
printf(" RX Data on port %u (%d bytes): ", port, retcode);
for (uint8_t i = 0; i < retcode; i++) {
printf("%02x ", rx_buffer[i]);
}
printf("\r\n");
memset(rx_buffer, 0, sizeof(rx_buffer));
}
/**
* Event handler
*/
static void lora_event_handler(lorawan_event_t event)
{
switch (event) {
case CONNECTED:
printf("\r\n Connection - Successful \r\n");
if (MBED_CONF_LORA_DUTY_CYCLE_ON) {
send_message();
} else {
ev_queue.call_every(TX_TIMER, send_message);
}
break;
case DISCONNECTED:
ev_queue.break_dispatch();
printf("\r\n Disconnected Successfully \r\n");
break;
case TX_DONE:
printf("\r\n Message Sent to Network Server \r\n");
if (MBED_CONF_LORA_DUTY_CYCLE_ON) {
send_message();
}
break;
case TX_TIMEOUT:
case TX_ERROR:
case TX_CRYPTO_ERROR:
case TX_SCHEDULING_ERROR:
printf("\r\n Transmission Error - EventCode = %d \r\n", event);
// try again
if (MBED_CONF_LORA_DUTY_CYCLE_ON) {
send_message();
}
break;
case RX_DONE:
printf("\r\n Received message from Network Server \r\n");
receive_message();
break;
case RX_TIMEOUT:
case RX_ERROR:
printf("\r\n Error in reception - Code = %d \r\n", event);
break;
case JOIN_FAILURE:
printf("\r\n OTAA Failed - Check Keys \r\n");
break;
case UPLINK_REQUIRED:
printf("\r\n Uplink required by NS \r\n");
if (MBED_CONF_LORA_DUTY_CYCLE_ON) {
send_message();
}
break;
default:
MBED_ASSERT("Unknown Event");
}
}
// EOF
This is my Arduino sketch:
#include <DHT.h>
#include <Wire.h>
// Sensor temperatura, humedad DHT11 (test).
#define DHTPIN 10
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
// Comunicacion i2c con nodo lora.
#define SLAVE_ADDRESS 0x29
// Variables globales de sensores
float h;
float t;
void setup() {
Serial.begin(9600);
Wire.begin(SLAVE_ADDRESS);
Wire.onReceive(receiveEvent);
dht.begin();
}
void loop() {
delay(5000);
h = dht.readHumidity();
t = dht.readTemperature();
// Comprobamos si ha habido algún error en la lectura
if (isnan(h) || isnan(t)) {
Serial.println("Error obteniendo los datos del sensor DHT11");
return;
}
Serial.print("Humedad: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Temperatura: ");
Serial.print(t);
Serial.println(" *C ");
}
// Total de bytes del arreglo de datos a transmitir: 28 bytes.
void receiveEvent(int howMany) {
Serial.println("Recibido paquete desde MDOT LoRa Node");
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write("/Humedad:");
char hum[3];
String(h).toCharArray(hum, 3);
Wire.write(hum);
Wire.write("/Temperatura:");
char temp[3];
String(t).toCharArray(temp, 3);
Wire.write(temp);
Wire.endTransmission();
}
And I leave the repo with all the code I'm currently using: https://github.com/cipiasentini/lorai2c
Here's a picture of my physical setup (I tried it with and without the 10k pullup resistors)
I use WeMos D1 mini Pro with Bosche BME680 sensor shield. I get the data from sensor and put them frequently into Firebase database. Everything works, except that my device has random crashes.
The sensor library works in a way that for around 5 first minutes it is not showing IAQ data (it's returning iaq = 25 and accuracy = 0). ESP8266 crashes around 5th minute of working program - when IAQ data is available and some of correct reading had been made.
I think problem might be caused by bsec_iot_loop() which is working too long. I was trying to use yield() in random places of bsec_iot_loop(), but it didn't work. Program works perfectly, when I comment out Firebase set methods.
Huge part of my code is based on official Bosch documentation. Frankly speaking, it's extended copy-paste. Here's the docs: https://www.bosch-sensortec.com/bst/products/all_products/bsec
Here's the code:
/**********************************************************************************************************************/
/* header files */
/**********************************************************************************************************************/
#include "bsec_integration.h"
#include <Wire.h>
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <FirebaseArduino.h>
#include <time.h>
#define DEVICE_NAME "device1"
#define SSID "ssid"
#define PWD "pass"
#define FIREBASE_HOST "host"
#define FIREBASE_AUTH "auth"
#define UPDATE_INTERVAL 20
int startupTime;
/**********************************************************************************************************************/
/* functions */
/**********************************************************************************************************************/
/*!
* #brief Write operation in either Wire or SPI
*
* param[in] dev_addr Wire or SPI device address
* param[in] reg_addr register address
* param[in] reg_data_ptr pointer to the data to be written
* param[in] data_len number of bytes to be written
*
* #return result of the bus communication function
*/
int8_t bus_write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data_ptr, uint16_t data_len)
{
Wire.beginTransmission((uint8_t) 0x77);
Wire.write(reg_addr); /* Set register address to start writing to */
/* Write the data */
for (int index = 0; index < data_len; index++) {
Wire.write(reg_data_ptr[index]);
}
return (int8_t)Wire.endTransmission();
}
/*!
* #brief Read operation in either Wire or SPI
*
* param[in] dev_addr Wire or SPI device address
* param[in] reg_addr register address
* param[out] reg_data_ptr pointer to the memory to be used to store the read data
* param[in] data_len number of bytes to be read
*
* #return result of the bus communication function
*/
int8_t bus_read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data_ptr, uint16_t data_len)
{
int8_t comResult = 0;
Wire.beginTransmission((uint8_t) 0x77);
Wire.write(reg_addr); /* Set register address to start reading from */
comResult = Wire.endTransmission();
delayMicroseconds(150); /* Precautionary response delay */
Wire.requestFrom((uint8_t) 0x77, (uint8_t)data_len); /* Request data */
int index = 0;
while (Wire.available()) /* The slave device may send less than requested (burst read) */
{
reg_data_ptr[index] = Wire.read();
index++;
}
return comResult;
}
/*!
* #brief System specific implementation of sleep function
*
* #param[in] t_ms time in milliseconds
*
* #return none
*/
void sleep(uint32_t t_ms)
{
delay(t_ms);
}
/*!
* #brief Capture the system time in microseconds
*
* #return system_current_time current system timestamp in microseconds
*/
int64_t get_timestamp_us()
{
return (int64_t) millis() * 1000;
}
/*!
* #brief Load previous library state from non-volatile memory
*
* #param[in,out] state_buffer buffer to hold the loaded state string
* #param[in] n_buffer size of the allocated state buffer
*
* #return number of bytes copied to state_buffer
*/
uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer)
{
// ...
// Load a previous library state from non-volatile memory, if available.
//
// Return zero if loading was unsuccessful or no state was available,
// otherwise return length of loaded state string.
// ...
return 0;
}
/*!
* #brief Save library state to non-volatile memory
*
* #param[in] state_buffer buffer holding the state to be stored
* #param[in] length length of the state string to be stored
*
* #return none
*/
void state_save(const uint8_t *state_buffer, uint32_t length)
{
// ...
// Save the string some form of non-volatile memory, if possible.
// ...
}
/*!
* #brief Load library config from non-volatile memory
*
* #param[in,out] config_buffer buffer to hold the loaded state string
* #param[in] n_buffer size of the allocated state buffer
*
* #return number of bytes copied to config_buffer
*/
uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer)
{
// ...
// Load a library config from non-volatile memory, if available.
//
// Return zero if loading was unsuccessful or no config was available,
// otherwise return length of loaded config string.
// ...
return 0;
}
void connectToWiFi() {
Serial.print("Connecting to ");
Serial.println(SSID);
WiFi.begin(SSID, PWD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void configureFirebase() {
Serial.print("Connecting to ");
Serial.println(FIREBASE_HOST);
Serial.println("");
Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
delay(500);
}
void configureTime() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
Serial.println("\nWaiting for time");
while (!time(nullptr)) {
Serial.print(".");
delay(1000);
}
Serial.println("");
}
void configureSensor() {
return_values_init ret;
/* Init I2C and serial communication */
Wire.begin();
/* Call to the function which initializes the BSEC library
* Switch on low-power mode and provide no temperature offset */
ret = bsec_iot_init(BSEC_SAMPLE_RATE_LP, 5.0f, bus_write, bus_read, sleep, state_load, config_load);
if (ret.bme680_status)
{
/* Could not intialize BME680 */
Serial.println("Error while initializing BME680");
return;
}
else if (ret.bsec_status)
{
/* Could not intialize BSEC library */
Serial.println("Error while initializing BSEC library");
return;
}
Serial.println("Sensor success");
}
/*!
* #brief Handling of the ready outputs
*
* #param[in] timestamp time in nanoseconds
* #param[in] iaq IAQ signal
* #param[in] iaq_accuracy accuracy of IAQ signal
* #param[in] temperature temperature signal
* #param[in] humidity humidity signal
* #param[in] pressure pressure signal
* #param[in] raw_temperature raw temperature signal
* #param[in] raw_humidity raw humidity signal
* #param[in] gas raw gas sensor signal
* #param[in] bsec_status value returned by the bsec_do_steps() call
*
* #return none
*/
void output_ready(int64_t timestamp, float iaq, uint8_t iaq_accuracy, float temperature, float humidity,
float pressure, float raw_temperature, float raw_humidity, float gas, bsec_library_return_t bsec_status)
{
yield();
char startupTimeStr[32];
itoa(startupTime, startupTimeStr, 10);
//Get current time
time_t now = time(nullptr);
//Get last update time
int lastUpdate = Firebase.getInt("device1/lastUpdate");
if (Firebase.failed()) {
Serial.print("getting device1/lastUpdate failed:");
Serial.println(Firebase.error());
return;
}
if (lastUpdate + UPDATE_INTERVAL <= (int) now) {
//Set last update
Firebase.setInt("device1/lastUpdate", (int) now);
//Set the reading
char nowStr[32];
itoa(now, nowStr, 10);
String path = "device1/readings/" + String(nowStr);
// Firebase.setInt(path + "/iaq", iaq);
// Firebase.setFloat(path + "/temp", temperature);
// Firebase.setFloat(path + "/humid", humidity);
// Firebase.setFloat(path + "/press", pressure);
//Set uptime
int uptime = (int) now - startupTime;
//Firebase.setInt("device1/uptimes/" + String(startupTimeStr), uptime);
//Verbose data
Serial.print("Updated: ");
Serial.print((int) now);
Serial.print(" | Uptime: ");
Serial.print(uptime);
Serial.print(" | IAQ: ");
Serial.print(iaq);
Serial.print(" | Acc: ");
Serial.println(iaq_accuracy);
}
}
void setup()
{
Serial.begin(9600);
while (!Serial);
connectToWiFi();
configureFirebase();
configureTime();
configureSensor();
startupTime = (int) time(nullptr);
Serial.print("Startup time:");
Serial.println(startupTime);
/* Call to endless loop function which reads and processes data based on sensor settings */
/* State is saved every 10.000 samples, which means every 10.000 * 3 secs = 500 minutes */
bsec_iot_loop(sleep, get_timestamp_us, output_ready, state_save, 10000);
}
void loop()
{
}
And here's beginning of typical Serial monitor dump:
Soft WDT reset
ctx: cont
sp: 3fff0df0 end: 40101b51 offset: 01b0
>>>stack>>>
3fff0fa0: 3fff31f4 3fff70ec 3fff662c 3fff372c
3fff0fb0: 0002d5a7 3fff70ec 3fff662c 40208866
3fff0fc0: 3fff662c 00000000 3fff703c 40201952
3fff0fd0: 3fff703c 00001388 3fff3b04 3fff0680
3fff0fe0: 000001bb 3fff662c 3fff31f4 3fff0680
3fff0ff0: 000001bb 3fff662c 3fff31f4 402089fd
3fff1000: 3ffe9770 5561c923 3ffe9770 5561c923
3fff1010: 3fff367c 00000000 3fff3684 4020717c
3fff1020: 00000000 00000206 00000206 4020526c
3fff1030: fffffff4 00000000 3fff3684 40207980
3fff1040: 3ffe9584 00000046 3ffe96a9 40201ff3
3fff1050: 3fff36fc 3fff10c0 3fff367c 4020204c
3fff1060: 3fff3708 00000000 00000000 3ffe96a6
3fff1070: 3fff367c 3fff10a0 3ffe96a6 3ffe96a6
3fff1080: 3fff367c 3fff0680 3fff1188 402030fa
3fff1090: 3fff0660 3fff0680 3fff1188 40203b71
3fff10a0: 3fff3708 3e9b1316 3e9b1316 3d003b33
3fff10b0: 41ad99fb bf64685f 00000000 40212b8c
3fff10c0: 3fff39d0 af3cd700 0000d700 00000012
3fff10d0: 00000000 3fff11ac 3fff1198 3fff065c
3fff10e0: 3fff1128 3fff1128 402030e4 40202032
3fff10f0: 3fff112c 40590000 3ed1ca3e 3fff0660
3fff1100: 3fff11ac 3fff1120 3fff1188 40203e0a
3fff1110: 3fff1120 40fb6e3e 3fff2180 40219384
3fff1120: 3fff367c 3fff3944 3fff0680 41ad99fb
I am almost sure that this is happening because your code never reaches the loop() function, on ESP8266 Arduino library it resets the watchdog timer each interaction of loop function.
I think you can solve your problem by two ways, one is opening the function bsec_iot_loop() and put the calls inside the while(1) to loop() function, the other option is to call ESP.wdtFeed() inside the while(1) to reset watchdog Timer.
The link bellow has a good explanation about watchdog timer on ESP Arduino library.
https://techtutorialsx.com/2017/01/21/esp8266-watchdog-functions/
Okay, so I solved the problem. As I suspected there was a problem with watchdog timer. Specifically, the code never reach loop() function, because while(1) inside bsec_iot_loop(). Solution is simple. I put code inside while(1) into loop(). The rest of the code bsec_iot_loop() was declaration of variables and I made them global.
Not releasing system resources is a common problem in the arduino library. Especially on systems with a single core CPU like esp8266 where no native threading is available to the system. The underlying RTOS needs processing time to maintain its TCP-Stack and other stuff. Calling yield() every now and then is not a reliable solution.
The arduino standard library for the esp8266 mostly consists of blocking functions, which often leads the programmer into WDT-Pitfalls.
I always recommend people to use async libraries for the esp8266. There are plenty of them available. And always return control back to the operating system as much as possible because even simple delay()-Calls are capable of triggering WDT-Reset.