I am trying to create a system a part of which requires a microphone to be connected to an arduino. I haven't worked with microphones a lot.
I have connected a microphone (Adafruit Electret Microphone Amplifier - MAX9814 with Auto Gain Control ) to an arduino nano. I want to record audio data from this.
void setup() {
Serial.begin(9600);
pinMode(A2, INPUT);
}
void loop() {
if(Serial.available())
{
Serial.println(analogRead(A2));
}
}
I send the data to the computer and record it using a python script and converted it into a WAV file to make sure that the microphone is working properly. I tried multiple things, using the ADC value, scaling the ADC value between -1 and 1, converting into voltage and then scaling it, but nothing seems to work. When I play it back all I can hear is static with a few clicks where the voice should be.
Below is the python code i wrote for the configuration where I am sending the ADC value using println. Here I collect the data using pyserial library and convert it into a float. Then I normalize it between -1 and 1. Then I save it in a wav file.
import serial
import matplotlib.pyplot as plt
import sounddevice as sd
import numpy as np
from scipy.io.wavfile import write
import pyaudio
import wave
def audnorm(aud):
normaud= -1+2*((aud-np.amin(aud))/(np.amax(aud)-np.amin(aud)))
return normaud
ser = serial.Serial('/dev/ttyACM0',115200)
ser.flushInput()
sound=[]
sound2=[]
while True:
try:
ser_bytes = ser.readline()
ser_bytes2= float(ser_bytes)
sound.append(ser_bytes2)
sound2.append(ser_bytes)
print(ser_bytes+"\t"+str(ser_bytes2))
print(type(ser_bytes))
except:
print("Keyboard Interrupt")
break
print(str(len(sound)))
soundnp= np.asarray(sound)
soundnp= soundnp - np.mean(soundnp)
soundnorm= audnorm(soundnp)
soundnormstr= [str(x) for x in soundnorm]
plt.plot(soundnp)
plt.show()
plt.plot(soundnorm)
plt.show()
wf = wave.open("output.wav", 'wb')
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(10000)
wf.writeframes(b''.join(soundnormstr))
wf.close()
I have attached 2 images of the data I recorded using this code.
What am I doing wrong?
Raw Data
Normalized Data
To be recorded without distortion, the signal you're trying to record - I assume it's an audio signal - requires three things: 1) sampling at a uniform rate, 2) sampling at more than 8,000 samples per second to be able to barely understand a voice, and 3) transmitting or storing the data as fast as you acquire it.
re: 1 & 2) There is an instructable that goes into all the messy details of recording high-fidelity audio on the Arduino. It contains far more information than I could write here. See https://www.instructables.com/id/Arduino-Audio-Input/
If your application requires the Arduino to simply detect that there is a sound - such as a clapping pair of hands - you can get by with a lower and non-uniform sample rate. Search for "Arduino Clapper" to get some ideas.
I agree with Bradford. You will need to make a uniform sampling to acquire the audio signal and 8000 Hz is a minimum.
I think that you need to set a higher serial baud rate to achieve this sampling frequency. I have slightly modified your code to measure with an oscilloscope the "actual maximum frequency" of the serial "transmission" (plus the analogWrite).
void setup() {
// Serial.begin(9600);
Serial.begin(115200);
pinMode(A2, INPUT);
}
void loop() {
//if(Serial.available())
{
int val = analogRead(A2);
Serial.write(0);
}
}
On the oscilloscope, it is roughly 9 kHz frequency, sending simply zeros on the serial wire. see the attached figure. It might be doable (for speech, not for music).
Related
I recently started using the Arduino Due for a high-speed ADC project, and I need to be able to write ~3MBit/s to a PC with the overhead of the Arduino code to communicate with that ADC. I have been doing some testing of the native arduino speed using SerialUSB (arduino_USB_slow.ino), and have got communication to work with a simple python script using pySerial.
With increasing block size I am able to increase the data rate (from 1.22MBit/s writing one byte at a time to SerialUSB to 7.58Mbit/s using 8 bytes at a time). This is great, but I'm cycle-constrained and need to do better than this. However, if I try to send more than 8 bytes at a time with the SerialUSB.write() command, I get no data transferred over the serial port at all. My pySerial input buffer stays at 0 bytes after opening the port, which I check using port.in_waiting. There are no problems compiling the code on the arduino side, no problems uploading it, and no problems with either the arduino or pyserial when trying to write 8 bytes. The problem only exists when I try to write more than 8 bytes (I've tried 9, 10, 16, 64, none of them have worked).
There's no indication in the Serial library documentation of a limitation on the input byte array size, and as I understand it if the buffer isn't large enough, the SerialUSB library on the Arduino side will just block further execution until the hardware buffer is refilled enough times to get through all the data. Other people have managed to use much larger block size without difficulty.
This does appear to be a pySerial issue, because when I open the USB device with minicom and run:
sudo cat /dev/tty.usbmodem11414
I get a bunch of binary barf that isn't present when the device isn't transmitting. This tells me the problem isn't with the Arduino (it's sending over its data just fine), but with pySerial or how I am using it. Is there a word limitation on pyserial when receiving a stream of data? This seems really bizarre. The pyserial code is dead simple:
import serial
import numpy as np
maxBytes = 1000000
byteCounter = 0
dataBuffer = np.zeros(maxBytes + 1020)
arduino = serial.Serial()
arduino.port = '/dev/cu.usbmodem14141'
arduino.open()
while byteCounter < maxBytes:
bytesAvailable = arduino.in_waiting
dataBuffer[byteCounter:byteCounter+bytesAvailable] = np.frombuffer(arduino.read(bytesAvailable), dtype='uint8')
byteCounter += bytesAvailable
print(byteCounter)
And the Arduino code is below:
char adcData[16] = {'a', 'a', 'a', 'a','a', 'a', 'a', 'a','a', 'a', 'a', 'a','a', 'a', 'a', 'a'};
void setup() {
SerialUSB.begin(9600);
}
void loop() {
SerialUSB.write(adcData);
}
I have an arduino that reads in two ints over a serial connection when I send the data from the arduino serial monitor it works as it should, but no matter what I do I can't get it to work when I send the same data from python using pySerial, I have been at this hours and have gotten nowhere
I have tried encoding the data as utf8, different encodings flushing the output buffer and have read too many other similar stackoverflow Q&As to count
I am using python 3.7.1 in Windows 10 but the code will untimately be running on a Rpi
import os
import time
import serial
ser = serial.Serial('COM7', baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=5)
print("writting")
time.sleep(0.5)
ser.write(b'4,5')
ser.write(b'\r\n')
time.sleep(0.5)
ser.flushOutput()
ser.close()
#include <SoftwareSerial.h>
byte buttonPin = 9;
const int pin_led = LED_BUILTIN; // Pin for indication LED
int sensorPin = A0;
int sensorValue = 0;
int remotePower = 0;
SoftwareSerial mySerial(11, 12); // RX, TX
void setup()
{
pinMode(pin_led, OUTPUT); // Set LED pin as output
Serial.begin(9600);
mySerial.begin(9600);
pinMode(buttonPin, INPUT_PULLUP);
}
int oldremotePower = 0;
void loop()
{
// if there's any serial available, read it:
while (Serial.available() > 0) {
Serial.println("theres Data");
// look for the next valid integer in the incoming serial stream:
int mode = Serial.parseInt();
// do it again:
int action = Serial.parseInt();
// do it again:
//int blue = Serial.parseInt();
// look for the newline. That's the end of your sentence:
if (Serial.read() == '\n') {
// constrain the values to 0 - 255 and invert
// if you're using a common-cathode LED, just use "constrain(color, 0, 255);"
mode = constrain(mode, 1, 4);
action = constrain(action, 0, 100);
mySerial.print(mode);
mySerial.print(",");
mySerial.println(action);
}
}
oldremotePower = remotePower;
sensorValue = analogRead(sensorPin);
remotePower = map(sensorValue, 0, 1023, 1, 100);
if (oldremotePower != remotePower){
//Serial.println(oldremotePower);
//Serial.println(remotePower);
}
if (digitalRead(buttonPin) == LOW) {
mySerial.println(remotePower);
}
}
I send "1,100" from the arduino serial monitor and the uno responds with "theres Data" and on the software serial it prints the values it was just sent
this works but when I try to send "1,100\r" from my python script nothing happens the script runs without error the Rx led on the uno flashes but there is no output on the software serial port It must be something wrong with my python serial code.
You are missing the part where you read from the port.
Contrary to the terminal or serial monitor, where everything that arrives to the port is immediately and automatically displayed, with pyserial you need to explicitly read bytes from the RX buffer:
incoming = ser.read() #Read one byte
incoming = ser.read(n) #Read n bytes, whit n and INT
incoming = ser.readline() #Read everything until a \n is received
incoming = ser.read(ser.inWaiting()) #Read everything in the buffer
You need to choose one of those and add it to your code.
To make sure you read everything you can add a loop to read until nothing else is waiting in the buffer and maybe a certain amount of time elapses:
timeout=time.time()+1.0
while ser.inWaiting() or time.time()-timeout<0.0:
if ser.inWaiting()>0:
data+=ser.read(ser.inWaiting())
timeout=time.time()+1.0
print(data)
This will keep reading for 1 second after the buffer is detected to be empty. I took it from here.
EDIT: As you say in the comments, you were indeed missing the read commands but that was on purpose.
Your other potential problem is the way you handle Serial.parseInt() on your Arduino code.
Serial.parseInt() reads integer values up to the delimiter (in your case the colon) but you need to explicitly call Serial.read() to swallow the delimiter itself. So just try calling Serial.read() after every Serial.parseInt() and your code should work.
One other thing you can do is to compare the result of Serial.parseInt() with 0 to check if you got a timeout.
So I have managed to make it work. (for now, I think) I put back a 2 sec wait (which I had when I first wrote this code) after opening the port in my python code and changed my write command to ser.write(bytes(b'4,5\n'))
I also removed the ser.flushOutput() and it seems to be working okay. I didn't need to make any changes to the arduino code. I have no real idea why all of a sudden it now works as the code I now have is almost identical to what I started with before I started debugging it and trying to make it work, which is infuriating to me as I have no clue what I did to fix it :<
Thanks All
I am wondering is there any way to run python script via Arduino commands in Windows ?
I don't know if this answers your question, but you can download Vpython library to create some cool projects with it, or connect sensors and getting data back into python from arduino or viceversa
So for example:
int trigPin=13; //Sensor Trig pin connected to Arduino pin 13
int echoPin=11; //Sensor Echo pin connected to Arduino pin 11
float pingTime; //time for ping to travel from sensor to target and return
float targetDistance; //Distance to Target in inches
float speedOfSound=776.5; //Speed of sound in miles per hour when temp is 77 degrees.
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(trigPin, LOW); //Set trigger pin low
delayMicroseconds(2000); //Let signal settle
digitalWrite(trigPin, HIGH); //Set trigPin high
delayMicroseconds(15); //Delay in high state
digitalWrite(trigPin, LOW); //ping has now been sent
delayMicroseconds(10); //Delay in low state
pingTime = pulseIn(echoPin, HIGH); //pingTime is presented in microceconds
pingTime=pingTime/1000000; //convert pingTime to seconds by dividing by 1000000 (microseconds in a second)
pingTime=pingTime/3600; //convert pingtime to hourse by dividing by 3600 (seconds in an hour)
targetDistance= speedOfSound * pingTime; //This will be in miles, since speed of sound was miles per hour
targetDistance=targetDistance/2; //Remember ping travels to target and back from target, so you must divide by 2 for actual target distance.
targetDistance= targetDistance*63360; //Convert miles to inches by multipling by 63360 (inches per mile)
Serial.println(targetDistance);
delay(100); //delay tenth of a second to slow things down a little.
}
And in python
import serial #Import Serial Library
from visual import * #Import all the vPython library
arduinoSerialData = serial.Serial('com11', 9600) #Create an object for the Serial port. Adjust 'com11' to whatever port your arduino is sending to.
measuringRod = cylinder( radius= .1, length=6, color=color.yellow, pos=(-3,-2,0))
lengthLabel = label(pos=(0,5,0), text='Target Distance is: ', box=false, height=30)
target=box(pos=(0,-.5,0), length=.2, width=3, height=3, color=color.green)
while (1==1): #Create a loop that continues to read and display the data
rate(20)#Tell vpython to run this loop 20 times a second
if (arduinoSerialData.inWaiting()>0): #Check to see if a data point is available on the serial port
myData = arduinoSerialData.readline() #Read the distance measure as a string
print myData #Print the measurement to confirm things are working
distance = float(myData) #convert reading to a floating point number
measuringRod.length=distance #Change the length of your measuring rod to your last measurement
target.pos=(-3+distance,-.5,0)
myLabel= 'Target Distance is: ' + myData #Create label by appending string myData to string
lengthLabel.text = myLabel #display updated myLabel on your graphic
This will make graphics in python representing something you are holding in front of an ultrasonic sensor and you can see the object moving in real time
I took the code from this website:
Toptechboy
This is has really good tutorial how to hook up arduino to python! And is very simple
I believe that there won't be any Arduino library that support Python because python is interpreted and the Arduino doesn't have the memory for the entire of Python, if you're looking to program an Arduino using Python then maybe just try C the code you need to learn for programming the Arduino isn't too different to the code you would find in python most of the code you can find here :
https://www.arduino.cc/en/Reference/HomePage
but these are some of the python modules related to running Python on an Arduino : http://playground.arduino.cc/CommonTopics/PyMite
I am currently working on an easy-to-use audio capturing device for digitizing old casette tapes (i.e. low fidelity). This device is based on a raspberry pi with an usb sound card, which does nothinge else than starting the listed python script on bootup.
import alsaaudio
import wave
import os.path
import RPi.GPIO as GPIO
import key
import usbstick
import time
try:
# Define storage
path = '/mnt/usb/'
prefix = 'Titel '
extension = '.wav'
# Configure GPIOs
GPIO.setmode(GPIO.BOARD)
button_shutdown = key.key(7)
button_record = key.key(11)
GPIO.setup(12, GPIO.OUT)
GPIO.setup(15, GPIO.OUT)
GPIO.output(12, GPIO.HIGH)
# Start thread to detect external memory
usb = usbstick.usbstick(path, 13)
# Configure volume
m = alsaaudio.Mixer('Mic', 0, 1)
m.setvolume(100, 0, 'capture')
# Only run until shutdown button gets pressed
while not (button_shutdown.pressed()):
# Only record if record button is pressed and memory is mounted
if (button_record.pressed() and usb.ismounted()):
# Create object to read input
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, 'sysdefault:CARD=Device')
inp.setchannels(1)
inp.setrate(44100)
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
inp.setperiodsize(1024)
# Find next file name
i = 0
filename = ''
while (True):
i += 1
filename = path + prefix + str(i) + extension
if not (os.path.exists(filename)):
break
print 'Neue Aufnahme wird gespeichert unter ' + filename
# Create wave file
wavfile = wave.open(filename, 'w')
wavfile.setnchannels(1)
wavfile.setsampwidth(2)
wavfile.setframerate(44100)
# Record sound
while (button_record.pressed()):
l, data = inp.read()
wavfile.writeframes(data)
GPIO.output(15, GPIO.HIGH)
# Stop record an save
print 'Aufnahme beendet\n'
inp.close()
wavfile.close()
GPIO.output(15, GPIO.LOW)
# Record has been started but no memory is mounted
elif (button_record.pressed() and not usb.ismounted()):
print 'Massenspeichergeraet nicht gefunden'
print 'Warte auf Massenspeichergeraet'
# Restart after timeout
timestamp = time.time()
while not (usb.ismounted()):
if ((time.time() - timestamp) > 120):
time.sleep(5)
print 'Timeout.'
#reboot()
#myexit()
print 'Massenspeichergeraet gefunden'
myexit()
except KeyboardInterrupt:
myexit()
According to the documentation pyaudio, the routine inp.read() or alsaaudio.PCM.read() respectively should usually wait until a full period of 1024 samples has been captured. It then should return the number of captured samples as well as the samples itself. Most of the time it returns exactly one period of 1024 samples. I don't think that I have a performance problem, since I would expect it to return several periods then.
THE VERY MYSTERIOUS BEHAVIOR: After 01:30 of recording, inp.read() takes some milliseconds longer than normal to process (this is a useful information in my ignorant opinion) and then returns -32 and faulty data. Then the stream continues. After half a minute at 02:00 it takes about a second (i.e. longer than the first time) to process and returns -32 and faulty data again. This procedere repeats then every minute (02:30-03:00, 03:30-04:00, 04:30-05:00). This timing specification was roughly taken by hand.
-32 seems to result from the following code line in /pyalsaaudio-0.7/alsaaudio.c
return -EPIPE;
Some words about how this expresses: If the data stream is directly written into the wave file, i.e. including the faulty period, the file contains sections of white noise. These sections last 30 seconds. This is because the samples usually consist of 2 bytes. When the faulty period (1 byte) is written, the byte order gets inverted. With the next faulty period it gets inverted again and therefore is correct. If faulty data is refused and only correct data is written into the wave file, the file 'jumps' every 30 seconds.
I think the problem can either be found in
1. the sound card (but I tested 2 different)
2. the computing performance of the raspberry pi
3. the lib pyaudio
Further note: I am pretty new to the linux and python topic. If you need any logs or something, please describe how I can find them.
To cut a long story short: Could someone please help me? How can I solve this error?
EDIT: I already did this usb firmware updating stuff, which is needed, since the usb can be overwhelmed. BTW: What exactly is this EPIPE failure?
Upon further inverstigation, I found out, that this error is not python/pyaudio specific. When I try to record a wave file with arecord, I get the following output. The overrun messages are sent according to the timing described above.
pi#raspberrypi ~ $ sudo arecord -D sysdefault:CARD=Device -B buffersize=192000 -f cd -t wav /mnt/usb/test.wav
Recording WAVE '/mnt/usb/test.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
overrun!!! (at least -1871413807.430 ms long)
overrun!!! (at least -1871413807.433 ms long)
overrun!!! (at least -1871413807.341 ms long)
overrun!!! (at least -1871413807.442 ms long)
Referring to this thread at raspberrypi.org, the problem seems to be the (partly) limited write speed to the SD card or the usb storage device with a raspberry pi. Recording to RAM (with tmpfs) or compressing the audio data (e.g. to mp3 with lame) before writing it somewhere else could be a good solution in some cases.
I can't say why the write speed is to low. In my opinion, the data stream is 192 kByte/s for 48 kSamples/s, 16 Bit, stereo. Any SD card or usb mass storage should be able to handle this. As seen above, buffering the audio stream doesn't help.
I want to adjust the volume of the mp3 file while it is being playing by adjusting the potentiometer. I am reading the potentiometer signal serially via Arduino board with python scripts. With the help of pydub library i can able to read the file but cannot adjust the volume of the file while it is being playing. This is the code i have done after a long search
I specified only the portion of Pydub part. for your information im using vlc media player for changing the volume.
>>> from pydub import AudioSegment
>>> song = AudioSegment.from_wav("C:\Users\RAJU\Desktop\En_Iniya_Ponnilave.wav")
While the file is playing, i cannot adjust the value. Please, someone explain how to do it.
First you need decode your audio signal to raw audio and Split your signal in X frames, and you can manipulate your áudio and at every frame you can change Volume or change the Pitch or change the Speed, etc!
To change the volume you just need multiply your raw audio vector by one factor (this can be your potentiometer data signal).
This factor can be different if your vector are in short int or float point format !
One way to get raw audio data from wav files in python is using wave lib
import wave
spf = wave.open('wavfile.wav','r')
#Extract Raw Audio from Wav File
signal = spf.readframes(-1)
decoded = numpy.fromstring(signal, 'Float32');
Now you can multiply the vector decoded by one factor, for example if you want increase 10dB you need calculate 10^(DbValue/20) then in python 10**(10/20) = 3.1623
newsignal = decoded * 3.1623;
Now you need encode the vector again to play the new framed audio, you can use "from struct import pack" and pyaudio to do it!
stream = pyaud.open(
format = pyaudio.paFloat32,
channels = 1,
rate = 44100,
output = True,
input = True)
EncodeAgain = pack("%df"%(len(newsignal)), *list(newsignal))
And finally Play your framed audio, note that you will do it at every frame and play it in one loop, this process is too fast and the latency can be imperceptibly !
stream.write(EncodeAgain)
PS: This example is for float point format !
Ederwander,As u said I have treid coding but when packing the data, im getting total zero. so it is not streaming. I understand the problem may occur in converting the format data types.This is the code i have written. Please look at it and say the suggestion
import sys
import serial
import time
import os
from pydub import AudioSegment
import wave
from struct import pack
import numpy
import pyaudio
CHUNK = 1024
wf = wave.open('C:\Users\RAJU\Desktop\En_Iniya_Ponnilave.wav', 'rb')
# instantiate PyAudio (1)
p = pyaudio.PyAudio()
# open stream (2)
stream = p.open(format = p.get_format_from_width(wf.getsampwidth()),channels = wf.getnchannels(),rate = wf.getframerate(),output = True)
# read data
data_read = wf.readframes(CHUNK)
decoded = numpy.fromstring(data_read, 'int32', sep = '');
data = decoded*3.123
while(1):
EncodeAgain = struct.pack(h,data)
stream.write(EncodeAgain)