ESP32 - Reading and playing back audio samples realtime (with I2S) - arduino

For a school project, I would like to get a bass-amplifier up-and-running with an ESP32. My idea for doing this is as follows:
I want to sample audio from AUX via I2S reading. Then I want to filter out the bass frequencies via a low-pass-filter. Next I will add a multiple of these frequency values to the original sample, and I will output them once again via I2S. I think I have the reading part down, however, I'm kind of stuck on writing with I2S. My code is as follows:
// Global defines
#define SERIAL_BAUDRATE 9600
// I2S variables
#define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0])) // Set the array size for the samples
#define ADC_INPUT_PIN ADC1_CHANNEL_4 // Pin we want to input from, this is pin 32
#define ADC_OUTPUT_PIN ADC1_CHANNEL_5 // Pin we want to output to, this is pin 33
const i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port used (we use I2S_NUM_0 because this supports ADC and DAC)
const double SAMPLING_FREQUENCY = 41000; // Sampling frequency
const int SAMPLE_BLOCK = 512; // Amount of samples we want to store in one block
const uint16_t OFFSET = (int)ADC_INPUT_PIN * 0x1000; // Offset for the input pin
// Setup part
I2SManager::I2SManager() {
Serial.println("Configuring I2S...");
esp_err_t err;
// The I2S configuration
const i2s_config_t i2s_config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX | I2S_MODE_ADC_BUILT_IN),
.sample_rate = SAMPLING_FREQUENCY,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = SAMPLE_BLOCK
};
// Set the driver for the I2S port
err = i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
if (err != ESP_OK) {
Serial.printf("Failed installing driver: %d\n", err);
while (true)
;
}
static const i2s_pin_config_t pin_config = {
.bck_io_num = -1,
.ws_io_num = -1,
.data_out_num = ADC_OUTPUT_PIN,
.data_in_num = ADC_INPUT_PIN
};
// Set pin config
i2s_set_pin(I2S_PORT, &pin_config);
// Enable DAC mode
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
// Set input pin to ADC mode
err = i2s_set_adc_mode(ADC_UNIT_1, ADC_INPUT_PIN);
if (err != ESP_OK) {
Serial.printf("Failed setting up adc mode: %d\n", err);
while (true)
;
}
Serial.println("I2S driver installed.");
}
// Loop part
void I2SManager::getSamples() {
// Read samples from I2S buffer
size_t bytesRead = 0;
i2s_read(I2S_PORT, (void*)samples, sizeof(samples), &bytesRead, portMAX_DELAY);
// Check if all samples are filled
if (bytesRead != sizeof(samples)) {
Serial.printf("Could only read %u bytes of %u in FillBufferI2S()\n", bytesRead, sizeof(samples));
return;
}
// This prints some data, I don't know if its correct because I can't check it yet
for (uint16_t i = 0; i < ARRAYSIZE(samples); i++) {
Serial.printf("%7d\n", samples[i]);
}
int bytesWritten;
i2s_write(I2S_PORT, (void*)samples, sizeof(samples), (size_t*)&bytesWritten, portMAX_DELAY);
}
If anyone is able to help me that would be very much appreciated. If you have any questions for me, do not hesitate to ask, I'll be monitoring this post as much as I can :).
P.S. I've also uploaded this question to the ESP32 official forum.
Thanks in advance and kind regards,
Thom

Related

Send Arduino serial commands while plotting

I have a simple PD Arduino controller to spin a motor. I want to use it to demonstrate system responses graphically. I have it working so I can give a target position using the serial monitor, but I want to be able to see the serial plot output at the same time. There seems to be a similar dialogue box in the Serial Plotter, but commands sent from there don't seem to be recognized. Is there a way to plot incoming serial data while also sending commands as described above? I don't mind if I need additional libraries, but I can't see why it shouldn't work natively since I can already send commands while receiving info using the Serial Monitor. Maybe I'm misunderstanding that process.
Any help would be very appreciated. See full code below:
// Clockwise rotation direction.
#define CW 1
// Counter clockwise rotation direction.
#define CCW 0
// Frequency of output PWM signal.
#define PWM_FREQ 25000
// Update rate in microseconds.
#define CYCLE_TIME 1000
// Rate of sending position data to PC.
#define PLOT_RATE 200
#define PLOT_COUNTER CYCLE_TIME/PLOT_RATE
// IO pins. //
// The pin connected to ENBble A on the driver.
const int ENB = 14;
// Pins connected to IN3 and IN4 on the driver (for controlling the rotation direction).
const int IN4 = 15;
const int IN3 = 16;
// Signal A wire of the encoder.
const int ENCA = 17;
// Signal B wire of the encoder.
const int ENCB = 18;
// Value of ENCA.
int enca = 0;
// Value of ENCB.
int encb = 0;
// Value of IN3.
int in3 = 0;
// Value of IN4.
int in4 = 0;
// Motors position measure by encoder.
volatile long int motorPos = 0;
// Communication variables. //
// The byte sent over serial to Teensy.
int incomingByte = 0;
// Input buffer for receiving user input over serial.
char inputBuffer[8];
int bufferCnt = 0;
// Counter for sending position over serial for plotting purposes.
int pltCounter = 0;
// Controller variables./ /
// Last motor position.
long int lastPos = 0;
// Target motor position.
int targetPos = 0;
// Position at the start of the control loop.
int currentPos = 0;
// Position at the start of the previous control loop.
int prevPos = 0;
// Change in position (for approximating the derivative).
int dP = 0;
// Position error.
int pError = 0;
// P term of the controller.
int pTerm = 0;
// D term of the controller.
int dTerm = 0;
// Speed (= voltage = duty cycle). Controller output mapped to duty cycle range.
int spd = 0;
// Controller output.
int contOut = 0;
// Ratio for transforming counts to degrees (1920 count / 360 deg)
float ratio = static_cast<float>(360)/static_cast<float>(1920);
// Controller tunable parameters. //
// P gain.
const int kP = 10;
// D gain.
const int kD = 0;
// Error in encoder pulses correponding to the minimum duty cycle.
const int minErr = 0;
// Error in encoder pulses corresponding to the maximum duty cycle.
const int maxErr = 1024;
// minDutyCycle and maxDutyCycle depend on PWM frequency and can be determined in dc_motor_speed_control . For example for frequency of 25k,
// minDutyCycle = 120 (Motor starts to move),
// maxDutyCycle = 190 (Motor speed reaches its maximum given the supplied voltage).
const int minDutyCycle = 120;
const int maxDutyCycle = 190;
// Controller update rate variables. //
// Difference in time between desired cycle period and its execution time (without any delay()s).
int cycleDiff;
// Control loop start time.
long int startTime;
// Control loop end time.
long int endTime;
// Plotting
float motorPosDeg = 0;
//Plotter p;
void setup() {
Serial.begin(9600);
// Initialize the pins.
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);
pinMode(ENB,OUTPUT);
pinMode(ENCA,INPUT);
pinMode(ENCB,INPUT);
analogWriteFrequency(ENB, PWM_FREQ);
// Set the initial rotation direction.
setDirection(CCW);
// Start with the motor at rest.
analogWrite(ENB,0);
// Encoder interrupt.
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISRising, RISING);
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISRising, RISING);
//p.Begin();
//p.AddTimeGraph("Position v Time", 1000, "Position", motorPosDeg);
}
// *** Encoder interrupt routines. See "Understanding Quadrature Encoded Signals" here: https://www.pjrc.com/teensy/td_libs_Encoder.html" *** //
void encoderAISRising(){
if(digitalRead(ENCB) == HIGH)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISFalling, FALLING);
}
void encoderAISFalling(){
if(digitalRead(ENCB) == LOW)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISRising, RISING);
}
void encoderBISRising(){
if(digitalRead(ENCA) == LOW)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISFalling, FALLING);
}
void encoderBISFalling(){
if(digitalRead(ENCA) == HIGH)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISRising, RISING);
}
// *** ***//
// Default rotation direction is CCW.
void setDirection(bool dir){
// CCW
if (dir == CCW){
digitalWrite(IN3,HIGH);
digitalWrite(IN4,LOW);
}else{
digitalWrite(IN3,LOW);
digitalWrite(IN4,HIGH);
}
}
void loop() {
if (Serial.available() > 0) {
// Read the incoming bytes, until a next line character (Enter) is encountered.
while (1){
incomingByte = Serial.read();
// We have read all the bytes.
if (incomingByte == '\n' || incomingByte == '\r'){
Serial.read();
break;
}else{
// Store the byte in the buffer and move on to the next.
inputBuffer[bufferCnt] = incomingByte;
bufferCnt++;
}
}
// Add a NULL character to the end of the array. Required for using atoi.
inputBuffer[bufferCnt] = '\0';
bufferCnt = 0;
// Convert string to integer.
targetPos = atoi(inputBuffer);
targetPos = targetPos / ratio;
}
// int i = 0;
// if (i % 2 == 0){
// targetPos = 360;
// } else {
// targetPos = 0;
// }
startTime = micros();
// Get the latest motor position.
currentPos = motorPos;
// Position error.
//pError = targetPos - motorPos;
pError = targetPos - currentPos;
// P term of the controller.
pTerm = kP * pError;
dP = currentPos - prevPos;
// D term of the controller. CYCLE_TIME/1000 normalizes the denominator, otherwise dTerm would always be zero (integer division).
dTerm = kD * (dP/(CYCLE_TIME/1000));
contOut = pTerm + dTerm;
// Set the target duty cycle (i.e. speed (i.e. voltage)).
// Error (in terms of encoder pulses) in the range minErr-maxErr is mapped to speed range corresponding to minDutyCycle-maxDutyCycle.
// 4 parameters to tune here.
spd = map(abs(contOut),minErr,maxErr,minDutyCycle,maxDutyCycle);
// Set the direction according to sign of position error (CCW is positive), and then speed.
// One optimization would be calling analogWrite(ENB,abs(spd)) at the start or end of the loop instead
// (at the expense of readibility).
if (pError > 0){
setDirection(CCW);
analogWrite(ENB,abs(spd));
}else if (pError < 0){
setDirection(CW);
analogWrite(ENB,abs(spd));
}
if (pltCounter == PLOT_COUNTER){
float mtrPos = static_cast<float>(motorPos);
motorPosDeg = mtrPos * ratio;
Serial.print(int(motorPosDeg));
Serial.println();
pltCounter = 0;
}
pltCounter++;
prevPos = currentPos;
cycleDiff = micros() - startTime;
// Adjust the update rate.
if (cycleDiff < CYCLE_TIME){
delayMicroseconds(CYCLE_TIME - cycleDiff);
}
//i++;
}
From what i understand of the plot function it utilizes the main arduino connexion to work. Based on how the arduino uart work you can only have 1 com port connexion per com port. This means you can either have the plot or command line open for each uart connexion. It is possible with different version of arduino to have multiple com ports. On the arduino uno there is only one com port "Serial". On the mega i think there are 3 uart ports. If you use a external FTDI UART board you can have the plot window open for serial0 and have the FTDI board connected on Serial1 to have the command line window open. You will have to change your code a little to send commands to serial1.
Here are a couple links to help you.
https://docs.arduino.cc/tutorials/communication/TwoPortReceive
https://docs.arduino.cc/built-in-examples/communication/MultiSerialMega
https://www.amazon.fr/AZDelivery-Adaptateur-FT232RL-s%C3%A9rie-book/dp/B01N9RZK6I?th=1

C++ how to query value outside the callback arduino

I want that the measurement interval and MQTT server settings can be changed from cellphone by BLE. I use LightBlue as a mobile application.
Here is my BLE code that works well with my mobile application
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.println("*********");
Serial.print("New value: ");
for (int i = 0; i < value.length(); i++)
Serial.print(value[i]);
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("1- Download and install an BLE scanner app in your phone");
Serial.println("2- Scan for BLE devices in the app");
Serial.println("3- Connect to MyESP32");
Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");
Serial.println("5- See the magic =)");
BLEDevice::init("MyESP32");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->setValue("Hello World");
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
This is MQTT code :
void loop() {
unsigned long currentMillis = millis();
// Every X number of seconds (interval = 10 seconds)
// it publishes a new MQTT message
if (currentMillis - previousMillis >= interval) {
// Save the last time a new reading was published
previousMillis = currentMillis;
// New DHT sensor readings
hum = dht.readHumidity();
// Read temperature as Celsius (the default)
temp = dht.readTemperature();
// Read temperature as Fahrenheit (isFahrenheit = true)
//temp = dht.readTemperature(true);
// Check if any reads failed and exit early (to try again).
if (isnan(temp) || isnan(hum)) {
Serial.println(F("Failed to read from DHT sensor!"));
return;
}
// Publish an MQTT message on topic esp32/dht/temperature
uint16_t packetIdPub1 = mqttClient.publish(MQTT_PUB_TEMP, 1, true, String(temp).c_str());
Serial.printf("Publishing on topic %s at QoS 1, packetId: %i", MQTT_PUB_TEMP, packetIdPub1);
Serial.printf("Message: %.2f \n", temp);
// Publish an MQTT message on topic esp32/dht/humidity
uint16_t packetIdPub2 = mqttClient.publish(MQTT_PUB_HUM, 1, true, String(hum).c_str());
Serial.printf("Publishing on topic %s at QoS 1, packetId %i: ", MQTT_PUB_HUM, packetIdPub2);
Serial.printf("Message: %.2f \n", hum);
}
}
Please how I can set the interval to whichever variable from the BLE code instead of 10000.
long interval = 10000;
You need to declare your variable interval as global variable to access it from everywhere in your file. A global variable can be declared outside of functions.
The value you receive is of type std::string and you want to use it as long. You can use toInt() to convert the variable from String to Long.
Try something like this:
long interval = 10000; // Set default value
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.println("*********");
Serial.print("New value: ");
for (int i = 0; i < value.length(); i++)
Serial.print(value[i]);
Serial.println();
Serial.println("*********");
interval = value.toInt(); // <-- Set received value
}
}
};

Can read, but cannot write serial ports on Ubuntu 16.04

I have a strange problem with trying to write data to a serial port.
I am running Ubuntu 16.04 on a NUC7i7DNBE, and am trying to make a serial connection to an Arduino UNO. The Serial API that I am using is found here: http://docs.ros.org/kinetic/api/serial/html/classserial_1_1Serial.html
I have written a simple program which opens the serial port "ttyACM0" to communicate with the arduino. I have tested this code on another computer running Ubuntu 16.04, and everything worked fine, the only permissions I had to set where adding the user to the dialout group.
On the NUC, however, I have added the user to the dialout group. This allowed the program to read from the Arduino, but it still does not write to the Arduino. The Arduino IDE will write to the Arduino just fine, but my program will not.
I am assuming that I am having trouble with serial write permissions in Ubuntu.
Steps I have taken:
I have added the user to the dialout group
I have added a rule in /etc/udev/rules.d/ which states:
SUBSYSTEMS=="tty", GROUP="dialout", MODE="0666"
Afterward, I sent the commands:
sudo chown root:root /etc/udev/rules.d/50-AVCusb.rules
sudo chmod 0644 /etc/udev/rules.d/50-AVCusb.rules
udevadm control --reload-rules
I followed some information found on stack exchange to get to this point:
https://unix.stackexchange.com/questions/111593/allow-non-root-user-to-read-write-dev-files
I have tried using an FTDI device to write to the Arduino port. The FTDI device uses the ttyUSB0 port rather than the ttyACM0 port. The result was the same; can read, but can't write.
I have also run my external hard-drive on the NUC to see if there was any kind of hardware issue. When I ran the program from my external hard drive, I had no problem reading from and writing to the Arduino.
I have not dealt much with Ubuntu permissions or ports in general, please help me find and upload any other information that you may need in order to help me solve this problem.
Code on NUC:
#include <ros/ros.h>
#include <serial/serial.h>
using namespace serial;
Serial ser;
static const uint8_t MOTOR_ID = 0;
void writeMotor(uint8_t byte)
{
size_t size = 4;
uint8_t buffer[size];
buffer[0] = 'G'; //PID
buffer[1] = 'O';
buffer[2] = MOTOR_ID; //address
buffer[3] = byte; //data byte
ser.write(buffer, size);
}
int main() {
ros::init(argc, argv, "servo_esc_driver");
std::string port = "/dev/ttyACM0";
Timeout timeout = Timeout(0, 0, 0, 0, 0);
bytesize_t bytesize = eightbits;
parity_t parity = parity_none;
stopbits_t stopbits = stopbits_one;
flowcontrol_t flowcontrol = flowcontrol_none;
try{
ser.setPort(port);
ser.setBaudrate(115200);
ser.setTimeout(timeout);
ser.setBytesize(bytesize);
ser.setParity(parity);
ser.setStopbits(stopbits);
ser.setFlowcontrol(flowcontrol);
ser.open();
}
catch (SerialException e) {
ROS_FATAL_NAMED("Failed to connect to the Arduino UNO, %s.", e.what());
ros::shutdown();
return 0;
}
uint8_t byte = 90;
writeMotor(byte);
}
Full Code on Arduino
#include <Servo.h>
const byte N = 2;
//Servo esc;
//Servo servo;
Servo servo[N];
//int escPos = 90;
//int servoPos = 90;
int pos[N];
static const byte ESC_PIN = 7;
static const byte SERVO_PIN = 8;
static const byte RPM_FEEDBACK_PIN = 0; //interrpt 0, pin 2
static const byte SERVO_FEEDBACK_PIN = A0;
//const float MUL = 0.7058823529; //180/255
unsigned long lastTime_servoFeedback = 0;
static const byte MOTOR_ID = 0; //ID for differentiating data received and sent over serial connections
static const byte SERVO_ID = 1;
//added for motor data timeout safety feature
static const unsigned long MOTOR_DATA_TIMEOUT = 200; //4 x 50 ms (50 ms time period expected)
static unsigned long lastTimeMotorData = 0;
static const byte NEUTRAL = 90;
unsigned long last_rpm_pulse_update_ms = 0; //used for detecting a stopped car, and rejecting old data when writing to the serial port
unsigned long last_rpm_pulse_time_us = 0;//keeps track of rpms by comparing to system timer
static const long REV_PERIOD_MAX_US = 100000; //in us
unsigned long rev_period = REV_PERIOD_MAX_US; //100 ms is considered too long to be in motion
boolean forward = true;
/*Scratch that, I want these parameters set in ROS:
static const float wheel_radius = 0.05 // meters
static const float revs_to_mps_MUL = //assuming 2.85 gear ratio for brushless motor differential: https://forums.traxxas.com/showthread.php?9080733-Diff-gear-ratios
*/
//boolean rpm_period_updated = false; //rpms must be updated every 100 ms, otherwise the car has stopped, and velocity data should show 0 m/s
void rpm_feedback()
{
//Serial.println("in rpm_feedback");
last_rpm_pulse_update_ms = millis(); //notice the 'ms' here we want to use millisecond for checking whether or not data is valid. millis() can count up to 50 days while micros() only counts up to 70 minutes, thus millis() is used here.
unsigned long time_now = micros(); //use time now for accurate time calculations
unsigned long rev_period_temp = time_now - last_rpm_pulse_time_us; //get spur-gear revolution period
if(rev_period_temp > 0) rev_period = rev_period_temp; //revs are within
else rev_period = REV_PERIOD_MAX_US;
last_rpm_pulse_time_us = time_now; //using 'time_now' ensures that the time taken to get to this point in code does not interfere with rev_period accuracy - - - micros(); //reset time
if(pos[MOTOR_ID] < 90) //determine the direction that the vehicle is traveling in
{
forward = false;
}else forward = true;
//rpm_period_updated = true; not needed, only last_rpm_pulse_time_ms is needed for checking
}
void setup() {
// put your setup code here, to run once:
pinMode(RPM_FEEDBACK_PIN, INPUT_PULLUP);
attachInterrupt(RPM_FEEDBACK_PIN, rpm_feedback,FALLING); //arduino reference recommends using digitalPinToInterrupt(RPM_FEEDBACK_PIN) but the command is not recognized here
analogReference(EXTERNAL); //Using external reference for servo position
for(int i = 0; i < N; i++) //initialize
{
pos[i] = 90;
servo[i].attach(ESC_PIN + i);
}
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
if(Serial.available() >= 1)
{
if(Serial.read() == 'G')
{
unsigned long t = millis();
while((Serial.available() < 3) && ((millis() - t) < 10)); //wait for the rest of the package, or timeout
if(Serial.available() >= 3)
{
char buf[3];
Serial.readBytes(buf, 3);
if((buf[0] == 'O') && (buf[1] >= 0) && (buf[1] < 2))
{
pos[buf[1]] = byte(buf[2]);
if(buf[1] == MOTOR_ID) lastTimeMotorData = millis(); //time stamp of last motor data retrieval
//Serial.print("buf[2]: ");
//Serial.println(byte(buf[2]), DEC);
//Serial.print("pos: ");
//Serial.println(pos[buf[1]]);
}
}
}
}
if((millis() - lastTimeMotorData) > MOTOR_DATA_TIMEOUT) pos[MOTOR_ID] = NEUTRAL; //stop the motor if data is not being received
for(int i = 0; i < N; i++)
{
servo[i].write(pos[i]);
}
if((millis() - lastTime_servoFeedback) >= 50) // 20Hz 20) //50Hz matches current ROS driver settings
{
lastTime_servoFeedback = millis();
int servo_feedback = analogRead(SERVO_FEEDBACK_PIN);
Serial.write('G'); //PID
Serial.write('O');
Serial.write(SERVO_ID);
//Serial.print(servo_feedback);
Serial.write(lowByte(servo_feedback));
Serial.write(highByte(servo_feedback));
//Serial.println(servo_feedback);
float rev_frequency;
if((last_rpm_pulse_update_ms + 100) < millis()) rev_frequency = 0; //use millis() since it can count up to 50 days, and will not have a chance of a hiccup after 70 minutes of using micros()
//instead, correct period when slowing down, also stop when the maximum threshold is reached
//if((micros() - last_rpm_pulse_time_us) >= REV_PERIOD_MAX_US) rev_frequency = 0; //car is stopped in this case. I decided not to try correcting the period as mentioned above
else rev_frequency = (float) 1/rev_period*1000000;
byte *rev_freq_bytes_to_transmit = (byte *) &rev_frequency;
if(forward == false) rev_frequency = -rev_frequency; //a negative frequency is used for reverse
Serial.write('G'); //PID
Serial.write('O');
Serial.write(MOTOR_ID); //used for addressing
Serial.write(rev_freq_bytes_to_transmit, 4);
}
}
Some good information may be:
snuc#usuavc:~$ udevadm info -a -n /dev/ttyACM0
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0/tty/ttyACM0':
KERNEL=="ttyACM0"
SUBSYSTEM=="tty"
DRIVER==""
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0':
KERNELS=="1-4:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="cdc_acm"
ATTRS{authorized}=="1"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceClass}=="02"
ATTRS{bInterfaceNumber}=="00"
ATTRS{bInterfaceProtocol}=="01"
ATTRS{bInterfaceSubClass}=="02"
ATTRS{bNumEndpoints}=="01"
ATTRS{bmCapabilities}=="6"
ATTRS{supports_autosuspend}=="1"
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-4':
KERNELS=="1-4"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="02"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="8"
ATTRS{bMaxPower}=="100mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 2"
ATTRS{bcdDevice}=="0001"
ATTRS{bmAttributes}=="c0"
ATTRS{busnum}=="1"
ATTRS{configuration}==""
ATTRS{devnum}=="4"
ATTRS{devpath}=="4"
ATTRS{idProduct}=="0043"
ATTRS{idVendor}=="2341"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Arduino (www.arduino.cc)"
ATTRS{maxchild}=="0"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="removable"
ATTRS{serial}=="55330313635351207081"
ATTRS{speed}=="12"
ATTRS{urbnum}=="6990"
ATTRS{version}==" 1.10"
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1':
KERNELS=="usb1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{authorized_default}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="09"
ATTRS{bDeviceProtocol}=="01"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{bMaxPower}=="0mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bcdDevice}=="0415"
ATTRS{bmAttributes}=="e0"
ATTRS{busnum}=="1"
ATTRS{configuration}==""
ATTRS{devnum}=="1"
ATTRS{devpath}=="0"
ATTRS{idProduct}=="0002"
ATTRS{idVendor}=="1d6b"
ATTRS{interface_authorized_default}=="1"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Linux 4.15.0-32-generic xhci-hcd"
ATTRS{maxchild}=="12"
ATTRS{product}=="xHCI Host Controller"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="unknown"
ATTRS{serial}=="0000:00:14.0"
ATTRS{speed}=="480"
ATTRS{urbnum}=="76"
ATTRS{version}==" 2.00"
looking at parent device '/devices/pci0000:00/0000:00:14.0':
KERNELS=="0000:00:14.0"
SUBSYSTEMS=="pci"
DRIVERS=="xhci_hcd"
ATTRS{broken_parity_status}=="0"
ATTRS{class}=="0x0c0330"
ATTRS{consistent_dma_mask_bits}=="64"
ATTRS{d3cold_allowed}=="1"
ATTRS{dbc}=="disabled"
ATTRS{device}=="0x9d2f"
ATTRS{dma_mask_bits}=="64"
ATTRS{driver_override}=="(null)"
ATTRS{enable}=="1"
ATTRS{irq}=="122"
ATTRS{local_cpulist}=="0-7"
ATTRS{local_cpus}=="ff"
ATTRS{msi_bus}=="1"
ATTRS{numa_node}=="-1"
ATTRS{revision}=="0x21"
ATTRS{subsystem_device}=="0x2070"
ATTRS{subsystem_vendor}=="0x8086"
ATTRS{vendor}=="0x8086"
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""
I decided that the problem was with the ROS version of serial. I decided to try some native linux library, termios, and had success writing to the port!
I found this example code:
https://en.wikibooks.org/wiki/Serial_Programming/Serial_Linux
The problem lies in the ros serial installation somehow.
Don't know if you still want to solve this with serial/serial.h, but I think that your problem might be in the timeout settings.
I'm telling you this, 'cause I had the exact same problem, I could read the incoming data, but couldn't write.
The /dev/ttyUSB0 permission was ok, but not the timeout.
I found the following config on internet, gave a try and work. Now I can read and write.
try{
ser.setPort("/dev/ttyUSB0");
ser.setBaudrate(9600);
serial::Timeout to = serial::Timeout::simpleTimeout(10);
ser.setTimeout(to);
ser.open();
return true;
}
catch (SerialException e) {
return 0;

Arduino SD card fails to write when used with another SPI device

I have an ADXL355 accelerometer attached to an Adafruit Feather Adalogger. I can configure and read the sensor. I can also write binary values to the SD card. The problem occurs when I try to read from the sensor and then write that data to the SD card. The only thing I can think of is I'm somehow messing up the SPI communication but I can't see where. I looked through pins_arduino.h for my board and the SD Card (pin 4) is on a different register than pin 10 so I don't see how I'm breaking things.
My operations proceed like this. Global sensor creation, Serial.begin, SD.begin, SPI.begin, Test sensor connection, Create file for output on SD card, Initialize sensor, Read sensor FIFO, Write to file, repeat last 2 forever.
The file is created but remains at 0 file size, ie nothing is actually written to the card.
The sensor can operate at 4 kHz which was hard to achieve using the digitalWrite functions so I switched to using the port registers on the Feather. I do it like this:
#include <SM_ADXL355_SPI_fast.h>
#include <SPI.h>
#include <SD.h>
#define cardSelect 4
ADXL355_SPIF adxl355(&DDRB, &PORTB, _BV(6)); // values taken from pins_arduino.h, tested and working on pin 10
void setup() {
Serial.begin(57600);
while(!Serial){
// wait for Serial
}
SD.begin(cardSelect);
SPI.begin();
while(!adxl355.TestConnection()){
delay(1000);
}
adxl355.OpenFile("TestSPI.bin");
adxl355.Initialize(1, 10, 0); // set range to 2g's, frequency to 4 Hz and filter to off
}
void loop() {
while(true){ // avoid Arduino overhead of their loop function
adxl355.ReadFIFO();
adxl355.WriteFIFOToFile();
}
}
Here is the ADXL constructor
ADXL355_SPIF::ADXL355_SPIF(volatile uint8_t * outReg, volatile uint8_t * outPort, uint8_t bitValue) : sensorOutReg(outReg), sensorPort(outPort), sensorBitValue(bitValue){
*sensorOutReg |= sensorBitValue;
*sensorPort |= sensorBitValue;
sensorWriteCount = 0;
}
TestConnection tests that the DeviceID reads 0xAD. Initialize sets the G range, sample rate in Hz and filter. I have tested these with serial output and they work properly.
OpenFile looks like this:
bool ADXL355_SPIF::OpenFile(const String& fileName){
sensorFile = SD.open(fileName, FILE_WRITE);
if (!sensorFile){
Serial.print("Could not create file: ");
Serial.println(fileName);
return false;
}
return true;
}
After running this a file does get created on the SD card called "TESTSPI.BIN" with 0 file size.
ReadFIFO reads the numbers of entries in FIFO, stored as fifoCount and then populates a buffer (sensorFIFO[32][3]) with the values from the FIFO. I've printed this buffer to Serial to show that it's working. Here is that function
void ADXL355_SPIF::ReadFIFO(){
ReadRegister(ADXL355_RA_FIFO_ENTRIES, 1);
fifoCount = buffer[0];
ReadFIFOInternal();
return;
}
void ADXL355_SPIF::ReadFIFOInternal(){
SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
*sensorPort &= ~sensorBitValue;
uint8_t spiCommand = ADXL355_RA_FIFO_DATA << 1 | ADXL355_READ;
SPI.transfer(spiCommand);
int i = 0;
unsigned long tempV;
unsigned long value;
while(i < fifoCount){
for (int ptr = 0; ptr < 3; ++ptr){
buffer[0] = SPI.transfer(0x0);
value = buffer[0];
value <<= 12;
tempV = SPI.transfer(0x0);
tempV <<= 4;
value |= tempV;
tempV = SPI.transfer(0x0);
tempV >>=4;
value |= tempV;
if (buffer[0] & 0x80) {
value |= 0xFFF00000;
}
long lValue = static_cast<long>(value);
sensorFIFO[i][ptr] = scaleFactor * lValue;
}
i += 3;
}
SPI.endTransaction();
*sensorPort |= sensorBitValue;
return;
}
Here is WriteFIFOToFile:
void ADXL355_SPIF::WriteFIFOToFile(){
if (fifoCount > 0){
sensorFile.write(reinterpret_cast<const char *>(&sensorFIFO), 4 * fifoCount);
}
sensorWriteCount += fifoCount;
if (sensorWriteCount >= 100){
sensorFile.flush();
sensorWriteCount = 0;
}
}
After allowing this to run for a while the file size is always 0. I tried a simple binary write function just to test the card. It looks like this and it worked.
#include <SD.h>
#define cardSelectPin 4
const float pi=3.14159;
File oFile;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
while(!Serial){
// wait for serial
}
SD.begin(cardSelectPin);
oFile = SD.open("Test.bin", FILE_WRITE);
Serial.println(sizeof(int));
Serial.println(sizeof(float));
float testFloat[32][3];
for (int i = 0; i < 32; ++i){
for (int j = 0; j < 3; ++j){
testFloat[i][j] = pi * (i + 1) + j;
}
}
oFile.write(reinterpret_cast<const char *>(&testFloat), sizeof(float) * 96);
oFile.close();
Serial.println("Finished writing file.");
}
void loop() {
// put your main code here, to run repeatedly:
}
The problem was that flush was not being called correctly. I had created a buffer to hold data from the FIFO and it would flush the card when it would get full enough such that a subsequent read would overflow. At that time it would call flush. This is what was intended with the variable sensorWriteCount. This variable was of type uint8_t when it should have been a uint16_t.
Changing to the correct type fixed the problem. I would have deleted this question because it boils down to a typo, but once an answer has been posted the system doesn't allow that.
The only difference between the not-working sketch and the working one is the closing of the sd card. The sd card MUST be closed, I had the same problem you have and I assume that the file gets its boundaries written in its filesystem at file close call.
To solve your issue, use a push button. When you push it, it will close the file and stop reading/processing sensors. You can also use this button to start reading and recording sensors data on sd card again (toggle).

How to change current code to read from 2 different OneWire devices?

I've been given this code which reads from a OneWire temperature device DS18b20. I'd like to add another sensor to the same pin but not quite sure how to best do it. I didn't write this code myself. I'm using an NodeMCU devkit v0.9. The code below is just a section of the full code and there are separate scripts/tabs. Let me know if I should add anything else. Any help is greatly appreciated.
#include <Arduino.h> // not automatically included?
#include <OneWire.h> // for temp sensor
#include <Wire.h> // I2C for ADC & RTC
#include <DHT.h> // Humidity sensor
#include "sens.h"
#define TEMP_PIN D2 // Where DS18B20 is connected
#define ADDR_LEN 8 // 1-Wire address length // NOT SURE WHAT THESE DO!!
#define DATA_LEN 9 // 1-Wire data length // NOT SURE WHAT THESE DO!!
#define HUMI_PIN D1 // Where the DHT11 is
#define RTC_ADDR 0x68 // Clock's I2C address
#define ADC_ADDR 0x48 // ADC's I2C address
#define SDA D3 // I2C pins
#define SCL D4
OneWire ow(TEMP_PIN); // Setup 1-Wire
byte addr[ADDR_LEN]; // To store 1-Wire address
byte data[DATA_LEN]; // To store 1-Wire data
DHT dht(HUMI_PIN, DHT11); // Setup DHT
String leading0(const int c) {
// Add a leading zero when stringifying a byte, used for the date
return (c < 10) ? ("0" + String(c)) : String(c);
}
byte bin2bcd( const byte bin ) {
// does as the name suggests, RTC uses BCD
return (bin / 10 * 16) + (bin % 10);
}
byte bcd2bin( const byte bin ) {
// does as the name suggests, RTC uses BCD
return (bin / 16 * 10) + (bin % 16);
}
void senssetup() {
// Setup sensors, called in setup()
dht.begin();
Wire.begin(SDA, SCL);
}
float gettemp() {
int i = 0;
ow.reset_search();
do {} while (!ow.search(addr) && i++ < 0xff);
// Search for 1-Wire devices
if (i == 0x100) {
if (debug) Serial.println("No devices found!");
// Nothing connected
return 0;
}
if (OneWire::crc8(addr, 7) != addr[7]) {
if (debug) Serial.println("CRC 1 failed!");
// Checksum thing when getting device's address
return -1;
}
if (addr[0] != 0x10 && addr[0] != 0x28) {
if (debug) Serial.println("Not a DS18B20");
// Wrong 1-Wire device
return -2;
}
ow.reset();
ow.select(addr);
ow.write(0x44, 0);
// HEX 44 tells it to convert temperature to readable binary
delay(1000);
// It takes ~750ms to convert data, 1s is used to be safe (1s is used in the default library too)
if (!ow.reset()) {
if (debug) Serial.println("Device not present");
// Device has disconnected or broken during conversion?
return -3;
}
ow.select(addr);
ow.write(0xbe, 0);
// Tells it we're reading
for (i = 0; i < DATA_LEN; i++) {
data[i] = ow.read(); // Read data
}
if (debug && OneWire::crc8(data, 8) != data[8])
Serial.println( "CRC Check 2 failed" );
// Checksum on data; this fails sometimes, I don't know why
// temperature is always at the right value so ignore it
int dat = ((data[1] << 8) | data[0]);
if (dat > 32767)
dat -= 65536;
// 16 bit data in 2's complement has a sign
return dat / 16.0;
// last 4 binary digits are fractional
}
You should split the search part from the getting temperature part.
NOTE: I'm not into NodeMCU, so you'll have to adapt it to that language before using it. I'd use C.
For instance, you can use a function to get all addresses of the temperature sensors and put them in an address array:
#define MAX_DEVICES 5
byte addresses[MAX_DEVICES][ADDR_LEN];
byte numOfAddresses;
void getAllAddresses()
{
numOfAddresses = 0;
ow.reset_search();
byte address[ADDR_LEN];
while (ow.search(address) && (numOfAddresses < MAX_DEVICES))
{
if ( OneWire::crc8( address, 7 ) != address[7] )
continue; // CRC 1 failed
if ( address[0] != 0x10 && address[0] != 0x28 )
continue; // Not a DS18B20
byte i;
for (i = 0; i < ADDR_LEN; i++)
addresses[numOfAddresses][i] = address[i];
numOfAddresses++;
}
if (debug)
{
Serial.print("Found ");
Serial.print(numOfAddresses);
Serial.println(" temperature sensors");
}
}
Then you can modify your function to get the temperature of the i-th sensor:
float gettemp(byte index)
{
if (index >= numOfAddresses)
{
if (debug) Serial.println( "Index not valid" );
return -200; // Don't use 0, -1, ..., since they are valid temperatures
}
ow.reset();
ow.select(addresses[index]);
ow.write(0x44, 0); // HEX 44 tells it to convert temperature to readable binary
[...]
(just replace every call to addr with a call to addresses[index].
In your code, then, instead of calling gettemp you will have to call gettemp(0) for the first sensor, gettemp(1) for the second and so on.
You will need to call getAllAddresses before the first call to gettemp, otherwise it will always return -200. You can call it at startup or every X seconds or at each measurement, that's completely up to you

Resources