ESP_Sprite wrote:if you need portability, you can use std::thread
How far goes "portability" ? Let me explain my situation : I'm writing a new version of a C/C++ system I wrote before for Debian armhf (Beaglebone Black). I'm reusing portions of the beaglebones code and try to implement them on ESP32 platform. The first part I'm porting is a dedicated signal generator. It's implemented on the BBB by using their PRUSS subsystem, two independent and very predictable dedicated processors. On the ESP32 the signals can be generated by the rmt peripheral and two gpio pins. The RMT peripheral and gpios are controlled by ESP-IDF, which in turn is based on FreeRTOS. The timing (synchronisation) between the start of the waveform (generated by rmt) and the toggling of the gpio pins is very strict. A context switch can disrupt this synchronization, thereby making the system malfunction. I think I can solve the sync problem by elevating the thread's priority :
Code: Select all
TaskHandle_t taskHandle = (TaskHandle_t)(m_thread.native_handle()); // m_thread is an std::thread
UBaseType_t priority = uxTaskPriorityGet(taskHandle) + 1;
vTaskPrioritySet(taskHandle, priority);
So, how far goes portability as a) the BBB uses pruss-subsystems, which are dedicated and thus not portable and b) the ESP uses the rmt peripheral, which is AFAIK also dedicated.
ATM, my generator class look as follows:
Code: Select all
/*
* DCCGen.h
*
* Created on: Feb 26, 2018
* Author: paulvdbergh
*/
#include "driver/gpio.h"
#include "driver/rmt.h"
#include <thread>
using namespace std;
#ifndef MAIN_DCCGEN_H_
#define MAIN_DCCGEN_H_
#include "DCCMessage.h"
namespace TBTIoT
{
class DCCGen
{
public:
typedef enum
{
PowerOff = 0,
PowerOn
} PowerState_t;
DCCGen( gpio_num_t RailcomGPIONum = GPIO_NUM_4,
gpio_num_t PowerGPIONum = GPIO_NUM_2,
gpio_num_t DccGPIONum = GPIO_NUM_0,
rmt_channel_t channel = RMT_CHANNEL_0);
virtual ~DCCGen();
protected:
virtual DCCMessage* getNextDccMessage(void);
private:
void threadFunc(void);
thread m_thread;
volatile bool m_bContinue;
rmt_config_t m_rmtConfig;
PowerState_t m_PowerState;
gpio_num_t m_PowerGPIONum;
gpio_num_t m_RailcomGPIONum;
gpio_num_t m_DccGPIONum;
rmt_channel_t m_Channel;
};
} // namespace TBTIoT
#endif /* MAIN_DCCGEN_H_ */
Code: Select all
/*
* DCCGen.cpp
*
* Created on: Feb 26, 2018
* Author: paulvdbergh
*/
#include "DCCGen.h"
#include "freertos/task.h"
#include <cstring>
#include <chrono>
#include <iostream>
using namespace std;
namespace TBTIoT
{
DCCGen::DCCGen( gpio_num_t RailcomGPIONum /* = GPIO_NUM_4 */,
gpio_num_t PowerGPIONum /* = GPIO_NUM_2 */,
gpio_num_t DccGPIONum /* = GPIO_NUM_0 */,
rmt_channel_t channel /* = RMT_CHANNEL_0 */
)
: m_bContinue(true)
, m_PowerState(PowerOn)
, m_PowerGPIONum(PowerGPIONum)
, m_RailcomGPIONum(RailcomGPIONum)
, m_DccGPIONum(DccGPIONum)
, m_Channel(channel)
{
gpio_set_direction(m_PowerGPIONum, GPIO_MODE_OUTPUT);
gpio_set_direction(m_RailcomGPIONum, GPIO_MODE_OUTPUT);
ESP_ERROR_CHECK(gpio_set_level(m_PowerGPIONum, 0));
ESP_ERROR_CHECK(gpio_set_level(m_RailcomGPIONum, 0));
memset(&m_rmtConfig, 0, sizeof(m_rmtConfig));
m_rmtConfig.rmt_mode = RMT_MODE_TX;
m_rmtConfig.channel = m_Channel;
m_rmtConfig.clk_div = 0x01;
m_rmtConfig.gpio_num = m_DccGPIONum;
m_rmtConfig.mem_block_num = 1;
m_rmtConfig.tx_config.idle_output_en = 1;
m_rmtConfig.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
ESP_ERROR_CHECK(rmt_config(&m_rmtConfig));
ESP_ERROR_CHECK(rmt_driver_install(m_rmtConfig.channel, 0, 0));
m_thread = thread([this]{threadFunc();});
}
DCCGen::~DCCGen()
{
m_bContinue = false;
m_thread.join();
}
DCCMessage* DCCGen::getNextDccMessage()
{
// TODO write implementation
return nullptr;
}
void DCCGen::threadFunc()
{
const uint8_t _idleMsg[] = {0x03, 0xFF, 0x00, 0xFF};
DCCMessage IdleMessage(_idleMsg);
const rmt_item32_t preamble_items[PREAMBLE_NBR_CYCLES] =
{
DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT,
DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT,
DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT,
DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT, DCCMessage::DCC_ONE_BIT
};
rmt_item32_t pItems[64];
uint16_t ItemCount = 0;
DCCMessage* pNextMessage = nullptr;
TaskHandle_t taskHandle = (TaskHandle_t)(m_thread.native_handle());
UBaseType_t priority = uxTaskPriorityGet(taskHandle) + 1;
vTaskPrioritySet(taskHandle, priority);
while(m_bContinue)
{
// send the preamble_items
ESP_ERROR_CHECK(rmt_write_items(m_rmtConfig.channel, preamble_items, PREAMBLE_NBR_CYCLES, false));
// RailCom Gap.
// meanwhile (we have 1856 µS to spend...)
usleep(28);
// Shutdown power
gpio_set_level(m_PowerGPIONum, 0);
usleep(2);
// Short the outputs
gpio_set_level(m_RailcomGPIONum, 1);
usleep(439);
// un-Short outputs
gpio_set_level(m_RailcomGPIONum, 0);
usleep(2);
// Power up Power
if(m_PowerState == PowerOn)
{
gpio_set_level(m_PowerGPIONum, 1);
}
if(nullptr == (pNextMessage = getNextDccMessage()))
{
pNextMessage = &IdleMessage;
}
ItemCount = pNextMessage->getItemCount();
ItemCount = (ItemCount > 64) ? 64 : ItemCount;
memcpy(pItems, pNextMessage->getItems(), ItemCount * sizeof(rmt_item32_t));
// wait until preamble items finished
ESP_ERROR_CHECK(rmt_wait_tx_done(m_rmtConfig.channel, PREAMBLE_WAIT_TIME));
// send dcc data items and wait until end of transmission
ESP_ERROR_CHECK(rmt_write_items(m_rmtConfig.channel, pItems, ItemCount, true));
}
ESP_ERROR_CHECK(rmt_driver_uninstall(m_rmtConfig.channel));
}
} // namespace TBTIoT