diff --git a/Code/.gitkeep b/Code/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/Code/Huzzah32/server/server.ino b/Code/Huzzah32/server/server.ino new file mode 100644 index 0000000000000000000000000000000000000000..5eff0785d3f6935bf555cc252497e628fba2bd11 --- /dev/null +++ b/Code/Huzzah32/server/server.ino @@ -0,0 +1,354 @@ +// Load Wi-Fi library +#include <WiFi.h> +#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE +#include "esp_log.h" + +// Replace with your network credentials +const char* ssid = "MicHub"; +const char* password = "0123456789"; + +int lastBlink; +int blinkState; + +// Set web server port number to 80 +WiFiServer server(80); + +// Variable to store the HTTP request +String header; + +// Auxiliar variables to store the current output state +String recordingState = "off"; +String boardState = "not responding"; + +// Assign output variables to GPIO pins +const int ledPin = 13; + +#define BUF_SIZE (255) +uint8_t buf[BUF_SIZE]; + +#define BoardInit 0 /* Initializing */ +#define BoardReady 1 /* Ready to respond */ +#define BoardRecording 2 /* Recording */ +#define BoardTransmitting 3 /* Transmitting */ +#define BoardError 4 /* Error occurred, restart */ + +/* +// Set your Static IP address +IPAddress local_IP(192, 168, 1, 184); +// Set your Gateway IP address +IPAddress gateway(192, 168, 1, 1); + +IPAddress subnet(255, 255, 0, 0); +*/ + +void setup() { + Serial.begin(115200); + + //Serial1.begin(1843200); + Serial1.begin(2000000); + + Serial1.write(2);// Flush old data + while(Serial1.available() > 0) {Serial1.read();} + + // Initialize the output variables as outputs + pinMode(ledPin, OUTPUT); + digitalWrite(ledPin, HIGH); + lastBlink = 0; + blinkState = 0; + + connect(); + + server.begin(); + + readMode(); +} + +void connect() { + // Connect to Wi-Fi network with SSID and password + Serial.print("Connecting to "); + Serial.println(ssid); +/* + if (!WiFi.config(local_IP, gateway, subnet)) { + Serial.println("STA Failed to configure"); + } + */ + + WiFi.begin(ssid, password); + WiFi.setSleep(false); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); +} + +void readMode() { + // Flush + while(Serial1.available() > 0) {Serial1.read();} + + Serial1.println("status"); + int reqtime = millis(); + while(Serial1.available() == 0 && millis()-reqtime < 1000) { } + + Serial.println(Serial1.available()); + + if (Serial1.available() == 0) { + boardState = "not responding"; + } else { + byte boardMode = Serial1.read(); + Serial.println(boardMode); + + switch(boardMode) { + case BoardInit: boardState = "initializing"; break; + case BoardReady: boardState = "ready"; break; + case BoardRecording: boardState = "recording"; break; + case BoardTransmitting: boardState = "transmitting"; break; + case BoardError: boardState = "in error mode"; break; + default: boardState = "non-responsive"; break; + } + } + + // Flush + while(Serial1.available() > 0) {Serial1.read();} +} + +void loop() { + if (boardState == "ready") + { + digitalWrite(ledPin, HIGH); + lastBlink = 0; + blinkState = 0; + } else if (boardState != "recording") { + digitalWrite(ledPin, LOW); + lastBlink = 0; + blinkState = 0; + } else { /* Recording */ + if (blinkState == 0) { + digitalWrite(ledPin, LOW); + lastBlink = millis(); + blinkState = 1; + } else if (blinkState == 1 && millis()-lastBlink > 1000) { + digitalWrite(ledPin, HIGH); + lastBlink = millis(); + blinkState = 2; + } else if (blinkState == 2 && millis()-lastBlink > 1000) { + digitalWrite(ledPin, LOW); + lastBlink = millis(); + blinkState = 1; + } + } + if (!WiFi.isConnected()) { + Serial.print("Reconnecting..."); + connect(); + } + WiFiClient client = server.available(); // Listen for incoming clients + if (client && client.connected()) { // If a new client connects, + Serial.println("New Client."); // print a message out in the serial port + String currentLine = ""; // make a String to hold incoming data from the client + while (client.connected()) { // loop while the client's connected + if (client.available()) { // if there's bytes to read from the client, + char c = client.read(); // read a byte, then + //Serial.write(c); // print it out the serial monitor + header += c; + if (c == '\n') { // if the byte is a newline character + + // if the current line is blank, you got two newline characters in a row. + // that's the end of the client HTTP request, so send a response: + if (currentLine.length() == 0) { + readMode(); + + + int i = header.indexOf("/dl/"); + if (i >= 0 && boardState == "ready") { + int s = header.indexOf("\n",i); + String dlfile = header.substring(i+4,s-1); + s = dlfile.indexOf(" "); + if (s >= 0) { + dlfile = dlfile.substring(0,s); + } + String command = "get " + dlfile; + + Serial.println(dlfile); + + // Flush old data + while(Serial1.available() > 0) {Serial1.read();} + + Serial1.println(command); + String dlsize = ""; + while(true) + { + if (Serial1.available() > 0) + { + char c = Serial1.read(); + if (c == '\n') + { + break; + } else if (isDigit(c)) { + dlsize += c; + } + } + } + int idlsize = dlsize.toInt(); + Serial.println(idlsize); + + client.println("HTTP/1.1 200 OK"); + client.println("Content-type:binary/audio"); + client.println("Content-Length: " + dlsize); + client.println("Accept-Ranges: bytes"); + client.println("Connection: close"); + client.println(""); + + int count = 0; + int m = 0; + int timestamp = millis(); + while(count < idlsize) + { + int left = idlsize - count; + int expected = (left > BUF_SIZE ? BUF_SIZE : left); + + int n = Serial1.available(); + if (n > m) + { + timestamp = millis(); + m = n; + } + + if (n == expected) + { + n = Serial1.readBytes(buf, n); + Serial1.write(1); + client.write(buf,n); + count += n; + Serial.print(count); + Serial.print(", "); + m = 0; + } + else if (millis() - timestamp > 100) + { + // Flush old data + while(Serial1.available() > 0) {Serial1.read();} + Serial1.write(0); + Serial.println("Resend request"); + } + } + + Serial.println("Done"); + } else { + bool printList = false; + + // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) + // and a content-type so the client knows what's coming, then a blank line: + client.println("HTTP/1.1 200 OK"); + client.println("Content-type:text/html"); + client.println("Connection: close"); + client.println(); + + // turns the GPIOs on and off + if (header.indexOf("GET /rec/on") >= 0 && boardState == "ready") { + Serial.println("Start Recording"); + Serial1.println("start"); + recordingState = "on"; + delay(1000); + readMode(); + } else if (header.indexOf("GET /rec/off") >= 0 && boardState == "recording") { + Serial.println("Stop Recording"); + Serial1.println("stop"); + recordingState = "off"; + delay(1000); + readMode(); + } else if (header.indexOf("GET /list") >= 0 && boardState == "ready") { + Serial.println("List"); + printList = true; + } + + if(printList) + { + // Flush old data + while(Serial1.available() > 0) {Serial1.read();} + + Serial1.println("list"); + } + + // Display the HTML web page + client.println("<!DOCTYPE html><html>"); + client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); + client.println("<link rel=\"icon\" href=\"data:,\">"); + // CSS to style the on/off buttons + // Feel free to change the background-color and font-size attributes to fit your preferences + client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}"); + client.println("a:link {text-decoration: none;}"); + client.println(".button { background-color: white; border: none; color: red; padding: 16px 40px;"); + client.println("text-decoration: none; font-size: 80px; margin: 2px; cursor: pointer;}"); + client.println(".button2 {background-color: white; color: black;}"); + client.println(".button4 {background-color: white; color: green;}"); + client.println(".button3 {background-color: #white; color: black; font-size: 30px;}</style></head>"); + + // Web Page Heading + client.println("<body><h1>Audio Recording Headset</h1>"); + + // Display current state, and ON/OFF buttons for GPIO 26 + client.println("<p>Headset is " + boardState + "</p>"); + // If the recordingState is off, it displays the ON button + if (boardState == "ready") { + client.println("<p><a href=\"/rec/on\"><button class=\"button\">●</button></a></p>"); + } else if (boardState == "recording") { + client.println("<p><a href=\"/rec/off\"><button class=\"button button2\">■</button></a></p>"); + } + client.println("<p><a href=\"/\"><button class=\"button button4\">↻</button></a></p>"); + client.println("<p><a href=\"/list\"><button class=\"button button3\">List files</button></a></p>"); + + if (printList) + { + char c; + String filename = ""; + while(true) { + if (Serial1.available() > 0) + { + c = Serial1.read(); + Serial.print(c); + if ((char) c == '\n') + { + if (filename.length() == 0) { Serial.println("Broken out"); break;} + + String nameonly = filename.substring(0,filename.indexOf('\t')); + String filesize = "\t" + filename.substring(filename.indexOf('\t')) + " kb"; + client.println("<p><a href=\"/dl/" + nameonly + "\">" + nameonly + "</a>" + filesize + "</p>"); + filename = ""; + } + else if (c != '\r') + { + filename += (char) c; + } + } + } + } + client.println("</body></html>"); + + } + + // The HTTP response ends with another blank line + client.println(); + client.println(); + // Break out of the while loop + break; + } else { // if you got a newline, then clear currentLine + currentLine = ""; + } + } else if (c != '\r') { // if you got anything else but a carriage return character, + currentLine += c; // add it to the end of the currentLine + } + } + } + // Clear the header variable + header = ""; + // Close the connection + client.stop(); + Serial.println("Client disconnected."); + Serial.println(""); + } +} diff --git a/Code/Matlab/readData.m b/Code/Matlab/readData.m new file mode 100644 index 0000000000000000000000000000000000000000..127714af4d3d9e2f2565ab4ecd90c6bdfe19ca76 --- /dev/null +++ b/Code/Matlab/readData.m @@ -0,0 +1,85 @@ +function [audio, data] = readData(name, path) +%READDATA Read recorded sound and IMU data. +% name Name of files excluding extension +% path Path to read combined file from. + +%% Load sound +filename = fullfile(path, [name '.wav']); + +[audio, fs] = audioread(filename); + +% Map channels to positions +audio = audio(:,[5 7 4 1 2 3 8 6]); + +t = (1:size(audio,1))/fs*1000; +figure; title('Microphone signals'); +for i=1:8;subplot(2,4,i); plot(t,audio(1:end,i));title(num2str(i));end + +%% +ti = [650 71500]; +tc = t(t>= ti(1) & t <= ti(end)); +sndc = audio(t>= ti(1) & t <= ti(end),:); +for i=1:8;subplot(2,4,i); plot(tc,sndc(1:end,i));title(num2str(i));end +%% IMU Data +filename = fullfile(path, [name '.bin']); + +% Load entire file first for faster parsing +f = fopen(filename); +D = uint8(fread(f, inf, 'uint8=>uint8')'); +fclose(f); + +% Init data structures for parsing +struct_size = 64; +N = length(D)/struct_size; +timestamp = zeros(N,1); +temperature = zeros(N,1); +gyro = zeros(N,3); +accelerometer = zeros(N,3); +magnetometer = zeros(N,3); +orientation = zeros(N,3); + +% Parse data +p = 1; +for j = 1:N + if mod(j,500) == 0; clc; disp([num2str(round(j/N*100,0)) '%']); end + + timestamp(j,:) = typecast(D(p +(0:7)), 'uint64'); p = p + 8; + temperature(j,:) = typecast(D(p +(0:3)), 'single'); p = p + 4; + gyro(j,:) = typecast(D(p +(0:11)), 'single'); p = p + 12; + accelerometer(j,:) = typecast(D(p +(0:11)), 'single'); p = p + 12; + magnetometer(j,:) = typecast(D(p +(0:11)), 'single'); p = p + 12; + orientation(j,:) = typecast(D(p +(0:11)), 'single'); p = p + 12; + p = p + 4; % Dummy +end + +data = struct('Timestamp', timestamp, ... + 'Temperature', temperature, ... + 'Gyro', gyro, ... + 'Accelerometer', accelerometer, ... + 'Magnetometer', magnetometer, ... + 'Orientation', orientation); + +% Sync with sound recording (hopefully not needed +timestamp = timestamp - timestamp(1); + +% Remove outliers if missing data +timestampf = filloutliers(timestamp,'linear','movmedian',1000); +temperaturef = filloutliers(temperature,'linear','movmedian',1000); +gyrof = filloutliers(gyro,'linear','movmedian',1000); +accelerometerf = filloutliers(accelerometer,'linear','movmedian',1000); +magnetometerf = filloutliers(magnetometer,'linear','movmedian',1000); +orientationf = filloutliers(orientation,'linear','movmedian',1000); + + %% Plot data + +figure; title('Raw') +subplot(2,2,1); plot(timestamp/1000,orientation); title('Orientation'); xlabel('Time [s]'); ylabel('Attitude [deg]'); +subplot(2,2,2); plot(timestamp/1000,accelerometer); title('Accelerometer'); xlabel('Time [s]'); ylabel('Acceleration [g]'); +subplot(2,2,3); plot(timestamp/1000,magnetometer);title('Magnetometer'); xlabel('Time [s]'); ylabel('Magnetic Field [gauss]'); +subplot(2,2,4); plot(timestamp/1000,gyro);title('Gyro'); xlabel('Time [s]'); ylabel('Angular Velocity [deg/s]'); + +figure;title('Outliers removed'); +subplot(2,2,1); plot(timestamp/1000,orientationf); title('Orientation'); xlabel('Time [s]'); ylabel('Attitude [deg]'); +subplot(2,2,2); plot(timestamp/1000,accelerometerf); title('Accelerometer');xlabel('Time [s]'); ylabel('Acceleration [g]'); +subplot(2,2,3); plot(timestamp/1000,magnetometerf);title('Magnetometer');xlabel('Time [s]'); ylabel('Magnetic Field [gauss]'); +subplot(2,2,4); plot(timestamp/1000,gyrof);title('Gyro'); xlabel('Time [s]'); ylabel('Angular Velocity [deg/s]'); \ No newline at end of file diff --git a/Code/Matlab/splitFile.m b/Code/Matlab/splitFile.m new file mode 100644 index 0000000000000000000000000000000000000000..19e2ddfd73223674c93b1d9f864e1784f60135ed --- /dev/null +++ b/Code/Matlab/splitFile.m @@ -0,0 +1,110 @@ +function [audioBytes, dataBytes] = splitFile(name, outPath, inPath, magicWord, show) +%SPLITFILE Splits a combined sound and data file, by searching for a magic +%word that marks a data block and extract them into a separate file. +% name Name of file excluding extension +% outPath Path to place resulting wav and bin files +% inPath Path to read combined file from. +% magicWord Determine magic word (default fe ed ab ee de ad) +% show Show progress in console (default true). + +if nargin < 5 || isempty(show); show = true; end + if nargin < 4 || isempty(magicWord) + magicWord = hex2dec({'FE', 'ED', 'AB', 'EE', 'DE', 'AD'})'; %, 'BE', 'EF' + end + if nargin < 3 || isempty(inPath); inPath = 'E:\'; end + if nargin < 2 || isempty(outPath); outPath = 'D:\Data\AudioRecorder\'; end + + inFilename = fullfile(inPath,[name '.wav']); + outFilename = fullfile(outPath,[name '.wav']); + outData = fullfile(outPath,[name '.bin']); + i = 0; + while exist(outFilename, 'file') || exist(outData, 'file') + i = i + 1; + outFilename = fullfile(outPath,[name '_' num2str(i) '.wav']); + outData = fullfile(outPath,[name '_' num2str(i) '.bin']); + end + batchSize = 1024 * 1024; + sizeFieldSize = 2; + %frameSize = length(magicWord) + sizeFieldSize + structSize; + + fi = fopen(inFilename); + fa = fopen(outFilename, 'w'); + fd = fopen(outData, 'w'); + + totalBytes = dir(inFilename).bytes; + audioBytes = 0; + dataBytes = 0; + + D = []; + while(true) + if show + clc; + disp(['Processed: ' num2str(round((100*audioBytes+dataBytes)/totalBytes,1)) '%']); + end + + D = [D fread(fi, batchSize, 'uint8=>uint8')']; + + ind = strfind(D, magicWord); + + if isempty(ind) + fwrite(fa, D(1:end-length(magicWord))); + audioBytes = audioBytes + length(D(1:end-length(magicWord))); + D(1:end-length(magicWord)) = []; + else + p = 1; + for i = 1:length(ind)-1 + fwrite(fa, D(p:ind(i)-1)); + audioBytes = audioBytes + length(D(p:ind(i)-1)); + p = ind(i) + length(magicWord); + + frame_size = sum(double(D([p p+1])).*[1 256]); + p = p + sizeFieldSize; + + fwrite(fd, D(p + (0:frame_size-1))); + dataBytes = dataBytes + frame_size; + p = p + frame_size; + end + fwrite(fa, D(p:ind(end)-1)); + audioBytes = audioBytes + length(D(p:ind(end)-1)); + p = ind(end); + + q = ind(end); + if length(D) - q > length(magicWord) + sizeFieldSize + q = q + length(magicWord); + frame_size = sum(double(D([q q+1])).*[1 256]); + q = q + 2; + if length(D) - q > frame_size + fwrite(fd, D(q + (0:frame_size-1))); + dataBytes = dataBytes + frame_size; + p = q + frame_size; + else + D(1:p-1) = []; + continue; + end + else + D(1:p-1) = []; + continue; + end + if p < length(D) - length(magicWord) + 1 + fwrite(fa, D(p:end-length(magicWord))); + audioBytes = audioBytes + length(D(p:end-length(magicWord))); + + D(1:end-length(magicWord)) = []; + end + end + if feof(fi) == 1 + fwrite(fa, D); + audioBytes = audioBytes + length(D); + D = []; + if show + clc; + disp(['Processed: 100%']); + end + break; + end + end + fclose(fi); + fclose(fa); + fclose(fd); +end + diff --git a/Code/README.MD b/Code/README.MD new file mode 100644 index 0000000000000000000000000000000000000000..11369e0c5685c935cea23b20b095dcfdb1f6ccd8 --- /dev/null +++ b/Code/README.MD @@ -0,0 +1,27 @@ +Array Frame Source Code +======================= + +The main code executes on the Spresense board. +Recordings are started and stopped using a push button and the resulting files are stored on an SD card. + +Optionally, an AdaFruit HUZZAH32 board can be used to manage recordings over WiFi. + +Preparations +------------ +1. Install Arduino IDE for Spresense using the guide +https://developer.sony.com/develop/spresense/docs/arduino_set_up_en.html + +2. Open the Library Manager in Arduino IDE and install + - Sparkfun MPU-9250 9 DOF IMU Breakout + - SoftwareWire +3. Replace SoftwareWire files with the modified files in this repo to make it compatible with Spresense. +4. Update the MPU-9250 code to include SoftwareWire.h instead of Wire.h and replace TwoWire with SoftwareWire. + +Optional WebServer +------------------ +Install ESP32 library using the guide +https://learn.adafruit.com/huzzah32-esp32-breakout-board/using-with-arduino-ide + +Data Parsing +------------ +To parse the data, an m-script (splitFile.m) is provided for splitting the recorded file into a sound file and a data file. Another script (readData.m) is provided to parse the data into matlab. \ No newline at end of file diff --git a/Code/SoftwareWire/Licence.md b/Code/SoftwareWire/Licence.md new file mode 100644 index 0000000000000000000000000000000000000000..5f8b06fe3c76799cc5db75f8283f6417cb20b80a --- /dev/null +++ b/Code/SoftwareWire/Licence.md @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +<http://fsf.org/> + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see <http://www.gnu.org/licenses/>. + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read <http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/Code/SoftwareWire/SoftwareWire.cpp b/Code/SoftwareWire/SoftwareWire.cpp new file mode 100644 index 0000000000000000000000000000000000000000..42db261011ea3f79d1dba33da7c01ea097a1865a --- /dev/null +++ b/Code/SoftwareWire/SoftwareWire.cpp @@ -0,0 +1,890 @@ +// Signal differences +// ------------------ +// When the AVR microcontroller is set into hardware I2C mode, +// the pins follow the I2C specifications for voltage levels and current. +// For example the current to pull SDA or SCL low is maximum 3mA. +// +// With the Software I2C, a normal pin is used which can sink/source 40mA for a ATmega328P. +// That could increase the voltage spikes and could increase interference between sda and scl. +// The voltage levels are different. +// The timing of the software I2C is also different. +// +// In most cases the software I2C should work. +// With longer wires or with non-matching voltage levels, the result is unpredictable. +// +// +// +// Clock pulse stretching +// ---------------------- +// An I2C Slave could stretch the clock signal by keeping the SCL low. +// This happens for example when a Slave is an Arduino which can be +// busy doing other things like handling other interrupts. +// Adding a check for the clock stretching should make the transmission +// completely reliable without any loss. +// Only an Arduino as Slave would do clock stretching, normal devices +// like sensors and I2C EEPROM don't use clock stretching. +// The extra check for clock stretching slows down the transfer rate. +// +// Using millis() instead of micros() is faster. +// That is why millis() is used for the timeout of the clock pulse stretching. +// +// +// +// Arduino Stream class +// -------------------- +// The Arduino Stream class is used by many Arduino objects. +// For the I2C bus, the benefits are less obvious. +// For example the parseInt() function is not used with I2C. +// At this moment the Stream class is not used. +// +// +// Multiple Slaves with the same I2C address +// ----------------------------------------- +// The SoftwareWire can be declared more than once, +// to create a number of software i2c busses. +// That makes it possible to use a number of I2C devices, +// which have the same I2C address. +// Every software i2c bus requires 2 pins, +// and every SoftwareWire object requires 59 bytes at the moment. +// + + + +// added code to i2c_stop(), since a problem was reported here: +// http://forum.arduino.cc/index.php?topic=348337.0 +// Added lines have keyword "ADDED1". + + +// Use the next define to run a i2c_scanner inside the printStatus() function. +// #define ENABLE_I2C_SCANNER + +#include "SoftwareWire.h" + +#include "Arduino.h" + + +// Sets SDA low and drives output. +// The SDA may not be HIGH output, so first the output register is cleared +// (clearing internal pullup resistor), after that the SDA is set as output. +//#define i2c_sda_lo() \ +// *_sdaPortReg &= ~_sdaBitMask; \ +// *_sdaDirReg |= _sdaBitMask; +#define i2c_sda_lo() \ + digitalWrite(_sdaPin, LOW); + + +// sets SCL low and drives output. +// The SCL may not be HIGH output, so first the output register is cleared +// (clearing internal pullup resistor), after that the SCL is set as output. +//#define i2c_scl_lo() \ +// *_sclPortReg &= ~_sclBitMask; \ +// *_sclDirReg |= _sclBitMask; +#define i2c_scl_lo() \ + digitalWrite(_sclPin, LOW); + +// Set SDA high and to input (releases pin) (i.e. change to input, turn on pullup). +// The SDA may not become HIGH output. Therefore the pin is first set to input, +// after that, a pullup resistor is switched on if needed. +// #define i2c_sda_hi() \ + // *_sdaDirReg &= ~_sdaBitMask; \ + // if(_pullups) { *_sdaPortReg |= _sdaBitMask; } +// #define i2c_sda_hi() \ + // pinMode(_sdaPin, INPUT); \ + // if(_pullups) { pinMode(_sdaPin, INPUT_PULLUP); } +#define i2c_sda_hi() \ + pinMode(_sdaPin, INPUT_PULLUP); + +// set SCL high and to input (releases pin) (i.e. change to input, turn on pullup) +// The SCL may not become HIGH output. Therefore the pin is first set to input, +// after that, a pullup resistor is switched on if needed. +// #define i2c_scl_hi() \ + // *_sclDirReg &= ~_sclBitMask; \ + // if(_pullups) { *_sclPortReg |= _sclBitMask; } +// #define i2c_scl_hi() \ + // pinMode(_sclPin, INPUT); \ + // if(_pullups) { pinMode(_sclPin, INPUT_PULLUP);} +#define i2c_scl_hi() \ + pinMode(_sclPin, INPUT_PULLUP); + +// Read the bit value of the pin +// Note that is the pin can also be read when it is an output. +// #define i2c_sda_read() ((uint8_t) (*_sdaPinReg & _sdaBitMask) ? 1 : 0) +// #define i2c_scl_read() ((uint8_t) (*_sclPinReg & _sclBitMask) ? 1 : 0) +#define i2c_sda_read() ((uint8_t) digitalRead(_sdaPin)) +#define i2c_scl_read() ((uint8_t) digitalRead(_sclPin)) + + +// +// Constructor +// +// The pins are not activated until begin() is called. +// +SoftwareWire::SoftwareWire() +{ +} + +SoftwareWire::SoftwareWire(uint8_t sdaPin, uint8_t sclPin, boolean pullups, boolean detectClockStretch) +{ + + _sdaPin = sdaPin; + _sclPin = sclPin; + _pullups = pullups; + _stretch = detectClockStretch; + + setClock( 100000UL); // set default 100kHz + + // Set default timeout to 1000 ms. + // 1 second is very long, 10ms would be more appropriate. + // However, the Arduino libraries use often a default timeout of 1 second. + setTimeout( 1000L); + + // Turn Arduino pin numbers into PORTx, DDRx, and PINx + // uint8_t port; + + // port = digitalPinToPort(_sdaPin); + // _sdaBitMask = digitalPinToBitMask(_sdaPin); + // _sdaPortReg = portOutputRegister(port); + // _sdaDirReg = portModeRegister(port); + // _sdaPinReg = portInputRegister(port); // PinReg is the input register, not the Arduino pin. + + // port = digitalPinToPort(_sclPin); + // _sclBitMask = digitalPinToBitMask(_sclPin); + // _sclPortReg = portOutputRegister(port); + // _sclDirReg = portModeRegister(port); + // _sclPinReg = portInputRegister(port); +} + + +// +// The destructor releases the pins Software I2C bus for other use. +// +SoftwareWire::~SoftwareWire() +{ + end(); +} + + +// +// Release the pins of the Software I2C bus for other use. +// Also the internal pullup resistors are removed. +// +void SoftwareWire::end() +{ + // Remember the pullups variable. + // They will be used again when begin() is called. + boolean pullupsCopy = _pullups; + + _pullups = false; + i2c_sda_hi(); // release sda, remove any pullup + i2c_scl_hi(); // release scl, remove any pullup + + _pullups = pullupsCopy; +} + + +// begin(void) - enter master mode +// The pins are not changed until begin() is called. +void SoftwareWire::begin(void) +{ + rxBufPut = 0; // nothing in the rxBuf + rxBufGet = 0; + + i2c_init(); // release the sda and scl (the pullup resistors pull them high) + + // Some tests could be added here, to check if the SDA and SCL are really turning high. + // Even some tests for shortcuts could be done here. + + + // When a I2C transmission would start immediate, it could fail when only the internal pullup resistors + // are used, and the signals were just now turned high with i2c_init(). + if( _pullups) + delay(2); // 1ms didn't always work. +} + +// +// beginTransmission starts the I2C transmission immediate. +// +void SoftwareWire::beginTransmission(uint8_t address) +{ + // Reset error returned by endTransmission. + _transmission = SOFTWAREWIRE_NO_ERROR; + + // check return value of the start condition. + // It indicates if the i2c bus is okay. + if(i2c_start()) + { + uint8_t rc = i2c_write((address << 1) | 0); // The r/w bit is zero for write + + if( rc == 0) // a sda zero from Slave for the 9th bit is ack + { + _transmission = SOFTWAREWIRE_NO_ERROR; + } + else + { + _transmission = SOFTWAREWIRE_ADDRESS_NACK; + } + } + else + { + // If the bus was not okay, the scl or sda didn't work. + _transmission = SOFTWAREWIRE_OTHER; + } +} + + +// +void SoftwareWire::beginTransmission(int address) +{ + beginTransmission((uint8_t)address); +} + + +// +uint8_t SoftwareWire::endTransmission(boolean sendStop) +{ + if(sendStop) + i2c_stop(); + else + i2c_repstart(); + + return(_transmission); // return the transmission status that was set during writing address and data +} + + +// +// The requestFrom() read the data from the I2C bus and stores it in a buffer. +// +uint8_t SoftwareWire::requestFrom(uint8_t address, uint8_t size, uint8_t sendStop) +{ + uint8_t n=0; // number of valid received bytes. Start with 0 bytes. + + // The transmission status is set, although it is not returned. + // Start with the status : no error + _transmission = SOFTWAREWIRE_NO_ERROR; + + + // Clear the RX buffer + rxBufPut = 0; + rxBufGet = 0; + + boolean bus_okay = i2c_start(); + + if(bus_okay) + { + uint8_t rc = i2c_write((address << 1) | 1); // The r/w bit is '1' to read + + if( rc == 0) // a sda zero from Slave for the 9th bit is ack + { + _transmission = SOFTWAREWIRE_NO_ERROR; + + // TODO: check if the Slave returns less bytes than requested. + + for(; n<size; n++) + { + if( n < (size - 1)) + rxBuf[n] = i2c_read(true); // read with ack + else + rxBuf[n] = i2c_read(false); // last byte, read with nack + } + rxBufPut = n; + } + else + { + _transmission = SOFTWAREWIRE_ADDRESS_NACK; + } + } + else + { + // There was a bus error. + _transmission = SOFTWAREWIRE_OTHER; + } + + if(sendStop > 0 || _transmission != SOFTWAREWIRE_NO_ERROR) + i2c_stop(); + else + i2c_repstart(); + + return( n); +} + + +// +// uint8_t SoftwareWire::requestFrom(int address, int size, int sendStop) +// { + // return requestFrom( (uint8_t) address, (uint8_t) size, (uint8_t) sendStop); +// } + + +// must be called in: +// slave tx event callback +// or after beginTransmission(address) +size_t SoftwareWire::write(uint8_t data) +{ + // When there was an error during the transmission, no more bytes are transmitted. + if( _transmission == SOFTWAREWIRE_NO_ERROR) + { + if( i2c_write(data) == 0) // a sda zero from Slave for the 9th bit is ack + { + _transmission = SOFTWAREWIRE_NO_ERROR; + } + else + { + _transmission = SOFTWAREWIRE_ADDRESS_NACK; + } + } + + return(1); // ignore any errors, return the number of bytes that are written. +} + + +// +size_t SoftwareWire::write(const uint8_t* data, size_t quantity) +{ + for (size_t i=0; i<quantity; i++) + { + write(data[i]); + } + + return(quantity); // ignore any errors, return the number of bytes that are written. +} + + +// +int SoftwareWire::available(void) +{ + return(rxBufPut - rxBufGet); +} + + +// +int SoftwareWire::peek(void) +{ + int data; + + if( rxBufPut > rxBufGet) + { + data = rxBuf[rxBufGet]; + } + else + { + data = -1; + } + + return(data); +} + + +// +// The read() reads the buffer, not the I2C bus. +// +int SoftwareWire::read(void) +{ + int data; + + if( rxBufPut > rxBufGet) + { + data = rxBuf[rxBufGet]; + rxBufGet++; + } + else + { + data = -1; + } + + return(data); +} + + +int SoftwareWire::readBytes(uint8_t* buf, uint8_t size) +{ + int data; + int n; + + for( n=0; n<size; n++) + { + data = read(); + if( data == -1) + break; + else + buf[n] = (uint8_t) data; + } + + return(n); +} + + +// +int SoftwareWire::readBytes(char * buf, uint8_t size) +{ + return readBytes( (uint8_t *) buf, size); +} + + +// +int SoftwareWire::readBytes(char * buf, int size) +{ + return readBytes( (uint8_t *) buf, (uint8_t) size); +} + + +// +// Set the clock speed for the I2C bus. +// Default is 100000 (100kHz). +// A speed of 1Hz is possible with this software I2C library (but not with the Arduino Wire library). +// A speed of 200kHz or higher will remove the delay on an Arduino Uno. +// Without the delay, the functions are free running, using the execution timing of the code. +// +void SoftwareWire::setClock(uint32_t clock) +{ + // Tested values with an earlier version of this library. + // Value 0 is without delay, the result depends on the microcontroller and the cpu clock. + // 0=maxspeed=140kHz (tested on 328P@16MHz) + // 1=120kHz + // 2=100kHz (default) + // 7=50kHz + // 47=10kHz + // 97=5kHz + // 500=1kHz + // 5000=100Hz + // 16383=minspeed=30Hz - delayMicroseconds() max value reference arduino + // + + _i2cdelay = 2; + return; + + // The _i2cdelay is an uint16_t + _i2cdelay = ( (F_CPU / 32L) / clock ); // The delay in microseconds, '32' is for this code. + unsigned int delayByCode = (F_CPU / 5000000L); // Add some delay for the code, just a guess + + if( _i2cdelay > delayByCode) + _i2cdelay -= delayByCode; + else + _i2cdelay = 0; + +} + + +// +// Set the timeout in milliseconds. +// At this moment, it is only used for timeout when the Slave is stretching the clock pulse. +// +void SoftwareWire::setTimeout(long timeout) +{ + // 2017, fix issue #6. + // A signed long as parameter to be compatible with Arduino libraries. + // A unsigned long internal to avoid compiler warnings. + _timeout = (unsigned long) timeout; +} + + +// printStatus +// ----------- +// Print information to the Serial port +// Used during developing and debugging. +// Call it with the Serial port as parameter: +// myWire.printStatus(Serial); +// This function is not compatible with the Wire library. +// When this function is not called, it does not use any memory. +// +void SoftwareWire::printStatus( Print& Ser) +{ + Ser.println(F("-------------------")); + Ser.println(F("SoftwareWire Status")); + Ser.println(F("-------------------")); + Ser.print(F(" F_CPU = ")); + Ser.println(F_CPU); + Ser.print(F(" sizeof(SoftwareWire) = ")); + Ser.println(sizeof(SoftwareWire)); + Ser.print(F(" _transmission status = ")); + Ser.println(_transmission); + Ser.print(F(" _i2cdelay = ")); + Ser.print(_i2cdelay); + if( _i2cdelay == 0) + Ser.print(F(" (free running)")); + Ser.println(); + Ser.print(F(" _pullups = ")); + Ser.print(_pullups); + if( _pullups) + Ser.print(F(" (enabled)")); + Ser.println(); + Ser.print(F(" _timeout = ")); + Ser.print(_timeout); + Ser.println(F(" ms")); + + Ser.print(F(" SOFTWAREWIRE_BUFSIZE = ")); + Ser.println(SOFTWAREWIRE_BUFSIZE); + Ser.print(F(" rxBufPut = ")); + Ser.println(rxBufPut); + Ser.print(F(" rxBufGet = ")); + Ser.println(rxBufGet); + Ser.print(F(" available() = ")); + Ser.println(available()); + Ser.print(F(" rxBuf (hex) = ")); + for(int ii=0; ii<SOFTWAREWIRE_BUFSIZE; ii++) + { + if(rxBuf[ii] < 16) + Ser.print(F("0")); + Ser.print(rxBuf[ii],HEX); + Ser.print(F(" ")); + } + Ser.println(); + + // Ser.print(F(" _sdaPin = ")); + // Ser.println(_sdaPin); + // Ser.print(F(" _sclPin = ")); + // Ser.println(_sclPin); + // Ser.print(F(" _sdaBitMask = 0x")); + // Ser.println(_sdaBitMask, HEX); + // Ser.print(F(" _sclBitMask = 0x")); + // Ser.println(_sclBitMask, HEX); + // Ser.print(F(" _sdaPortReg = ")); + // Ser.println( (uint16_t) _sdaPortReg, HEX); + // Ser.print(F(" _sclPortReg = ")); + // Ser.println( (uint16_t) _sclPortReg, HEX); + // Ser.print(F(" _sdaDirReg = ")); + // Ser.println( (uint16_t) _sdaDirReg, HEX); + // Ser.print(F(" _sclDirReg = ")); + // Ser.println( (uint16_t) _sclDirReg, HEX); + // Ser.print(F(" _sdaPinReg = ")); + // Ser.println( (uint16_t) _sdaPinReg, HEX); + // Ser.print(F(" _sclPinReg = ")); + // Ser.println( (uint16_t) _sclPinReg, HEX); + + Ser.print(F(" line state sda = ")); + Ser.println(i2c_sda_read()); + Ser.print(F(" line state scl = ")); + Ser.println(i2c_scl_read()); + +#ifdef ENABLE_I2C_SCANNER + // i2c_scanner + // Taken from : http://playground.arduino.cc/Main/I2cScanner + // At April 2015, it was version 5 + Ser.println("\n I2C Scanner"); + byte error, address; + int nDevices; + + Ser.println(" Scanning..."); + + nDevices = 0; + for(address=1; address<127; address++ ) + { + // The i2c_scanner uses the return value of + // the Write.endTransmisstion to see if + // a device did acknowledge to the address. + beginTransmission(address); + error = endTransmission(); + + if (error == 0) + { + Ser.print(" I2C device found at address 0x"); + if (address<16) + Ser.print("0"); + Ser.print(address,HEX); + Ser.println(" !"); + + nDevices++; + } + else if (error==4) + { + Ser.print(" Unknow error at address 0x"); + if (address<16) + Ser.print("0"); + Ser.println(address,HEX); + } + } + if (nDevices == 0) + Ser.println(" No I2C devices found\n"); + else + Ser.println(" done\n"); +#endif +} + + +//-------------------------------------------------------------------- + + +// +// The i2c_writebit and i2c_readbit could be make "inline", but that +// didn't increase the speed, and the code size increases. +// +// The sda is low after the start condition. +// Therefore the sda is low for the first bit. +// +void SoftwareWire::i2c_writebit(uint8_t c) +{ + if(c==0) + { + i2c_sda_lo(); + } + else + { + i2c_sda_hi(); + } + + if (_i2cdelay != 0) // This delay is not needed, but it makes it safer + delayMicroseconds(_i2cdelay); // This delay is not needed, but it makes it safer + + i2c_scl_hi(); // clock high: the Slave will read the sda signal + + // Check if clock stretching by the Slave should be detected. + if( _stretch) + { + // If the Slave was stretching the clock pulse, the clock would not go high immediately. + // For example if the Slave is an Arduino, that has other interrupts running (for example Serial data). + unsigned long prevMillis = millis(); + while( i2c_scl_read() == 0) + { + if( millis() - prevMillis >= _timeout) + break; + }; + } + + // After the clock stretching, the clock must be high for the normal duration. + // That is why this delay has still to be done. + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + i2c_scl_lo(); + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); +} + + +// +uint8_t SoftwareWire::i2c_readbit(void) +{ + i2c_sda_hi(); // 'hi' is the same as releasing the line + i2c_scl_hi(); + + // Check if clock stretching by the Slave should be detected. + if( _stretch) + { + // Wait until the clock is high, the Slave could keep it low for clock stretching. + unsigned long prevMillis = millis(); + while( i2c_scl_read() == 0) + { + if( millis() - prevMillis >= _timeout) + break; + }; + } + + // After the clock stretching, this delay has still be done before reading sda. + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + uint8_t c = i2c_sda_read(); + + i2c_scl_lo(); + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + return(c); +} + + +// +// Initializes the Software I2C. +// +// The original i2c_init sets the SDA and SCL high at the same time. +// +// The code has been changed, since the first data to the software i2c did fail sometimes. +// Changed into SCL high first, with a delay. +// That would send a STOP if the SDA happens to be low. +// Any Slave that was busy, will detect the STOP. +// +// After both lines are high, the delay is changed into 4 times the normal delay. +// That did reduce the error with the first transmission. +// It was tested with Arduino Uno with clock of 100kHz (_i2cdelay=2). +// +void SoftwareWire::i2c_init(void) +{ + i2c_scl_hi(); + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + i2c_sda_hi(); + + for( uint8_t i=0; i<4; i++) // 4 times the normal delay, to claim the bus. + { + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + } +} + + +// +// Send a START Condition +// +// The SDA and SCL should already be high. +// +// The SDA and SCL will both be low after this function. +// When writing the address, the Master makes them high. +// +// Return value: +// true : software i2c bus is okay. +// false : failed, some kind of hardware bus error. +// +boolean SoftwareWire::i2c_start(void) +{ + i2c_sda_hi(); // can perhaps be removed some day ? if the rest of the code is okay + i2c_scl_hi(); // can perhaps be removed some day ? if the rest of the code is okay + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + // Both the sda and scl should be high. + // If not, there might be a hardware problem with the i2c bus signal lines. + // This check was added to prevent that a shortcut of sda would be seen as a valid ACK + // from a i2c Slave. + uint8_t sda_status = i2c_sda_read(); + uint8_t scl_status = i2c_scl_read(); + if(sda_status == 0 || scl_status == 0) + { + return(false); + } + else + { + i2c_sda_lo(); + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + i2c_scl_lo(); + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + } + return(true); +} + + +// +// Repeated START instead of a STOP +// +// TODO: check if the repeated start actually works. +// +void SoftwareWire::i2c_repstart(void) +{ + i2c_sda_hi(); +// i2c_scl_hi(); // ?????? + + i2c_scl_lo(); // force SCL low + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + i2c_sda_hi(); // release SDA + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + i2c_scl_hi(); // release SCL + + // Check if clock stretching by the Slave should be detected. + if( _stretch) + { + // If the Slave was stretching the clock pulse, the clock would not go high immediately. + // For example if the Slave is an Arduino, that has other interrupts running (for example Serial data). + unsigned long prevMillis = millis(); + while( i2c_scl_read() == 0) + { + if( millis() - prevMillis >= _timeout) + break; + }; + } + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); +} + + +// Send a STOP Condition +// +// The stop was not recognized by every chip. +// Some code has been added (with comment "ADDED1"), +// to be sure that the levels are okay with delays in between. +void SoftwareWire::i2c_stop(void) +{ + i2c_scl_lo(); // ADDED1, it should already be low. + i2c_sda_lo(); + + // ADDED1, wait to be sure that the slave knows that both are low + if (_i2cdelay != 0) // ADDED1 + delayMicroseconds(_i2cdelay); // ADDED1 + + // For a stop, make SCL high wile SDA is still low + i2c_scl_hi(); + + // Check if clock stretching by the Slave should be detected. + if( _stretch) + { + // Wait until the clock is high, the Slave could keep it low for clock stretching. + // Clock pulse stretching during a stop condition seems odd, but when + // the Slave is an Arduino, it might happen. + unsigned long prevMillis = millis(); + while( i2c_scl_read() == 0) + { + if( millis() - prevMillis >= _timeout) + break; + }; + } + + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + // complete the STOP by setting SDA high + i2c_sda_hi(); + + // A delay after the STOP for safety. + // It is not known how fast the next START will happen. + if (_i2cdelay != 0) + delayMicroseconds(_i2cdelay); +} + + +// +// Write a byte to the I2C slave device +// The returned bit is 0 for ACK and 1 for NACK +// +uint8_t SoftwareWire::i2c_write( uint8_t c ) +{ + for ( uint8_t i=0; i<8; i++) + { + i2c_writebit(c & 0x80); // highest bit first + c <<= 1; + } + + return(i2c_readbit()); +} + + +// +// read a byte from the I2C slave device +// +uint8_t SoftwareWire::i2c_read(boolean ack) +{ + uint8_t res = 0; + + for(uint8_t i=0; i<8; i++) + { + res <<= 1; + res |= i2c_readbit(); + } + + if(ack) + { + i2c_writebit(0); + } + else + { + i2c_writebit(1); + } + + if(_i2cdelay != 0) + delayMicroseconds(_i2cdelay); + + return(res); +} diff --git a/Code/SoftwareWire/SoftwareWire.h b/Code/SoftwareWire/SoftwareWire.h new file mode 100644 index 0000000000000000000000000000000000000000..5d18e27712dece69380d74e353984b4d27e7ea75 --- /dev/null +++ b/Code/SoftwareWire/SoftwareWire.h @@ -0,0 +1,86 @@ + +#ifndef SoftwareWire_h +#define SoftwareWire_h + +#include <Arduino.h> + + + +// Transmission status error, the return value of endTransmission() +#define SOFTWAREWIRE_NO_ERROR 0 +#define SOFTWAREWIRE_BUFFER_FULL 1 +#define SOFTWAREWIRE_ADDRESS_NACK 2 +#define SOFTWAREWIRE_DATA_NACK 3 +#define SOFTWAREWIRE_OTHER 4 + +#define SOFTWAREWIRE_BUFSIZE 32 // same as buffer size of Arduino Wire library + + +class SoftwareWire +{ +public: + SoftwareWire(); + SoftwareWire(uint8_t sdaPin, uint8_t sclPin, boolean pullups = true, boolean detectClockStretch = true); + virtual ~SoftwareWire(); + void end(); + + void begin(); + // Generate compile error when slave mode begin(address) is used + void __attribute__ ((error("I2C/TWI Slave mode is not supported by the SoftwareWire library"))) begin(uint8_t addr); + void __attribute__ ((error("I2C/TWI Slave mode is not supported by the SoftwareWire library"))) begin(int addr); + + void setClock(uint32_t clock); + void beginTransmission(uint8_t address); + void beginTransmission(int address); + uint8_t endTransmission(boolean sendStop = true); + uint8_t requestFrom(uint8_t address, uint8_t size, uint8_t sendStop = true); + size_t write(uint8_t data); + size_t write(const uint8_t* data, size_t quantity); + int available(void); + int read(void); + int readBytes(uint8_t* buf, uint8_t size); + int readBytes(char * buf, uint8_t size); + int readBytes(char * buf, int size); + int peek(void); + void setTimeout(long timeout); // timeout to wait for the I2C bus + void printStatus(Print& Ser); // print information using specified object class + + +private: + // per object data + + uint8_t _sdaPin; + uint8_t _sclPin; + uint8_t _sdaBitMask; + uint8_t _sclBitMask; + + volatile uint8_t *_sdaPortReg; + volatile uint8_t *_sclPortReg; + volatile uint8_t *_sdaDirReg; + volatile uint8_t *_sclDirReg; + volatile uint8_t *_sdaPinReg; + volatile uint8_t *_sclPinReg; + + uint8_t _transmission; // transmission status, returned by endTransmission(). 0 is no error. + uint16_t _i2cdelay; // delay in micro seconds for sda and scl bits. + boolean _pullups; // using the internal pullups or not + boolean _stretch; // should code handle clock stretching by the slave or not. + unsigned long _timeout; // timeout in ms. When waiting for a clock pulse stretch. 2017, Fix issue #6 + + uint8_t rxBuf[SOFTWAREWIRE_BUFSIZE]; // buffer inside this class, a buffer per SoftwareWire. + uint8_t rxBufPut; // index to rxBuf, just after the last valid byte. + uint8_t rxBufGet; // index to rxBuf, the first new to be read byte. + + // private methods + + void i2c_writebit( uint8_t c ); + uint8_t i2c_readbit(void); + void i2c_init(void); + boolean i2c_start(void); + void i2c_repstart(void); + void i2c_stop(void); + uint8_t i2c_write(uint8_t c); + uint8_t i2c_read(boolean ack); +}; + +#endif // SoftwareWire_h diff --git a/Code/Spresense/recorder/imu/communication.h b/Code/Spresense/recorder/imu/communication.h new file mode 100644 index 0000000000000000000000000000000000000000..8c8c2f0d3ed24ade0dabf46c1662f4d9466a6328 --- /dev/null +++ b/Code/Spresense/recorder/imu/communication.h @@ -0,0 +1,22 @@ +#define IMU_BUFFER_SIZE 32 +#define IMU_HALF_BUFFER_SIZE (IMU_BUFFER_SIZE/2) + +#define MSG_FIFO_ADDRESS 1 + +#define ACK 1 +#define NACK (-1) + +typedef struct IMUData { + uint64_t timestamp; + float temperature; + float gyro[3]; // z, y, x + float accelerometer[3]; // x, y, z + float magnetometer[3]; // x, y, z + float orientation[3]; // Yaw, pitch, roll +} IMUData; + +typedef struct IMUFIFO { + IMUData imu[IMU_BUFFER_SIZE]; + int head = 0; + int tail = 0; +} IMUFIFO; diff --git a/Code/Spresense/recorder/imu/imu.ino b/Code/Spresense/recorder/imu/imu.ino new file mode 100644 index 0000000000000000000000000000000000000000..23c214c8beede3ef0e96bbc017fd8150e4f62a29 --- /dev/null +++ b/Code/Spresense/recorder/imu/imu.ino @@ -0,0 +1,307 @@ +#include <MP.h> +#include <MPU9250.h> +#include <Watchdog.h> +#include "quaternionFilters.h" +#include "communication.h" +#include <SoftwareWire.h> + +int ledStatus = LED3; + +#define PIN_SDA 14 +#define PIN_SCL 15 +SoftwareWire softWire(PIN_SDA, PIN_SCL, false, false); + +char debugMsg[200]; +#define serialDebug false // Print debug data + +#define I2Cclock 100000 +#define I2Cport softWire +#define MPU9250_ADDRESS MPU9250_ADDRESS_AD0 +#define MPU9250_ID (0x71) +#define MPU9250_MAG_ID (0x48) +MPU9250 myIMU(MPU9250_ADDRESS, I2Cport, I2Cclock); +float rawmx, rawmy, rawmz; +IMUFIFO imuFifo; + +bool i2cCheckLineState() { + + sprintf(debugMsg, "Current state sda(%d)=%d, scl(%d)=%d", PIN_SDA, digitalRead(PIN_SDA), PIN_SCL, digitalRead(PIN_SCL)); + Serial.println(debugMsg); + + // I2C bus OK + if (digitalRead(PIN_SDA) && digitalRead(PIN_SCL)) { + return true; + } + + pinMode(PIN_SDA, INPUT_PULLUP); + pinMode(PIN_SCL, INPUT_PULLUP); + + sprintf(debugMsg, "Current state sda(%d)=%d, scl(%d)=%d", PIN_SDA, digitalRead(PIN_SDA), PIN_SCL, digitalRead(PIN_SCL)); + Serial.println(debugMsg); + + // I2C bus is busy, client keeps SDA high + if (!digitalRead(PIN_SDA) || !digitalRead(PIN_SCL)) { + sprintf(debugMsg, "invalid state sda(%d)=%d, scl(%d)=%d", PIN_SDA, digitalRead(PIN_SDA), PIN_SCL, digitalRead(PIN_SCL)); + Serial.println(debugMsg); + + // Manually cycle the clock until client releases SDA + pinMode(PIN_SCL, OUTPUT); + digitalWrite(PIN_SCL, HIGH); + for (uint8_t a = 0; a < 9; a++) { + delayMicroseconds(5); + digitalWrite(PIN_SCL, LOW); + delayMicroseconds(5); + digitalWrite(PIN_SCL, HIGH); + if (digitalRead(PIN_SDA)) { // bus recovered + sprintf(debugMsg, "Recovered after %d Cycles", a + 1); + Serial.println(debugMsg); + break; + } + } + + // Send start and stop condition to client + pinMode(PIN_SDA, OUTPUT); + delayMicroseconds(5); + digitalWrite(PIN_SDA, LOW); + delayMicroseconds(5); + digitalWrite(PIN_SDA, HIGH); + } + + pinMode(PIN_SCL, INPUT); + pinMode(PIN_SDA, INPUT); + pinMode(PIN_SCL, INPUT_PULLUP); + pinMode(PIN_SDA, INPUT_PULLUP); + + // Double check that the I2C bus is ok + if (!digitalRead(PIN_SDA) || !digitalRead(PIN_SCL)) { // bus in busy state + sprintf(debugMsg, "Bus Invalid State, TwoWire() Can't init sda=%d, scl=%d", digitalRead(PIN_SDA), digitalRead(PIN_SCL)); + Serial.println(debugMsg); + return false; // bus is busy + } + + return true; +} + +void setup() +{ + int ret = MP.begin(); + MP.RecvTimeout(MP_RECV_POLLING); + + Serial.begin(115200); + Serial.println("Subcore 1 started"); + + i2cCheckLineState(); + + softWire.begin(); + + byte c = myIMU.readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250); + + if (c == MPU9250_ID) + { + myIMU.MPU9250SelfTest(myIMU.selfTest); + + if(serialDebug) + { + Serial.print(F("x-axis self test: acceleration trim within : ")); + Serial.print(myIMU.selfTest[0], 1); Serial.println("% of factory value"); + Serial.print(F("y-axis self test: acceleration trim within : ")); + Serial.print(myIMU.selfTest[1], 1); Serial.println("% of factory value"); + Serial.print(F("z-axis self test: acceleration trim within : ")); + Serial.print(myIMU.selfTest[2], 1); Serial.println("% of factory value"); + Serial.print(F("x-axis self test: gyration trim within : ")); + Serial.print(myIMU.selfTest[3], 1); Serial.println("% of factory value"); + Serial.print(F("y-axis self test: gyration trim within : ")); + Serial.print(myIMU.selfTest[4], 1); Serial.println("% of factory value"); + Serial.print(F("z-axis self test: gyration trim within : ")); + Serial.print(myIMU.selfTest[5], 1); Serial.println("% of factory value"); + } + + myIMU.calibrateMPU9250(myIMU.gyroBias, myIMU.accelBias); + + myIMU.initMPU9250(); + Serial.println("MPU9250 online"); + + byte d = myIMU.readByte(AK8963_ADDRESS, WHO_AM_I_AK8963); + + if (d != MPU9250_MAG_ID) + { + Serial.print("Could not connect to AK8963: 0x"); + Serial.println(d, HEX); + + // Communication failed, stop here + Serial.println(F("Communication failed, abort!")); + Serial.flush(); + abort(); + } + + myIMU.initAK8963(myIMU.factoryMagCalibration); + + Serial.println("AK8963 online"); + + if (serialDebug) + { + Serial.println("Calibration values: "); + Serial.print("X-Axis factory sensitivity adjustment value "); + Serial.println(myIMU.factoryMagCalibration[0], 2); + Serial.print("Y-Axis factory sensitivity adjustment value "); + Serial.println(myIMU.factoryMagCalibration[1], 2); + Serial.print("Z-Axis factory sensitivity adjustment value "); + Serial.println(myIMU.factoryMagCalibration[2], 2); + } + + // Get sensor resolutions, only need to do this once + myIMU.getAres(); + myIMU.getGres(); + myIMU.getMres(); + + if (serialDebug) + { + Serial.print("Resolution value: "); + Serial.println(myIMU.mRes, 2); + + Serial.println("AK8963 mag biases (mG)"); + Serial.println(myIMU.magBias[0]); + Serial.println(myIMU.magBias[1]); + Serial.println(myIMU.magBias[2]); + + Serial.println("AK8963 mag scale (mG)"); + Serial.println(myIMU.magScale[0]); + Serial.println(myIMU.magScale[1]); + Serial.println(myIMU.magScale[2]); + + Serial.println("Magnetometer:"); + Serial.print("X-Axis sensitivity adjustment value "); + Serial.println(myIMU.factoryMagCalibration[0], 2); + Serial.print("Y-Axis sensitivity adjustment value "); + Serial.println(myIMU.factoryMagCalibration[1], 2); + Serial.print("Z-Axis sensitivity adjustment value "); + Serial.println(myIMU.factoryMagCalibration[2], 2); + } + } + else + { + Serial.print("Could not connect to MPU9250: 0x"); + Serial.println(c, HEX); + + // Communication failed, reboot + Serial.println(F("Communication failed, abort!")); + Serial.flush(); + + // Restart processor to try again + Watchdog.begin(); + Watchdog.start(1); + while (true) { + ; + } + abort(); + } + + // Share memory with main core + Serial.println("Sending FIFO address..."); + + uint32_t data; + int8_t msgid = 0; + ret = -1; + MP.RecvTimeout(500); + while (!(ret >= 0 && msgid == MSG_FIFO_ADDRESS && data == ACK)) { + MP.Send(MSG_FIFO_ADDRESS, &imuFifo); + Serial.print("."); + ret = MP.Recv(&msgid, &data); + } + MP.RecvTimeout(MP_RECV_POLLING); + Serial.println("ACK received"); + + pinMode(ledStatus, OUTPUT); + digitalWrite(ledStatus, HIGH); +} + +void loop() +{ + if (myIMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01) + { + myIMU.readAccelData(myIMU.accelCount); + + myIMU.ax = (float)myIMU.accelCount[0] * myIMU.aRes; + myIMU.ay = (float)myIMU.accelCount[1] * myIMU.aRes; + myIMU.az = (float)myIMU.accelCount[2] * myIMU.aRes; + + myIMU.readGyroData(myIMU.gyroCount); + + myIMU.gx = (float)myIMU.gyroCount[0] * myIMU.gRes; + myIMU.gy = (float)myIMU.gyroCount[1] * myIMU.gRes; + myIMU.gz = (float)myIMU.gyroCount[2] * myIMU.gRes; + + myIMU.readMagData(myIMU.magCount); + + rawmx = (float)myIMU.magCount[0] * myIMU.mRes * myIMU.factoryMagCalibration[0]; + rawmy = (float)myIMU.magCount[1] * myIMU.mRes * myIMU.factoryMagCalibration[1] ; + rawmz = (float)myIMU.magCount[2] * myIMU.mRes * myIMU.factoryMagCalibration[2]; + + // Use hardcoded calibration + myIMU.mx = (rawmx - myIMU.magBias[0]) * myIMU.magScale[0]; + myIMU.my = (rawmy - myIMU.magBias[1]) * myIMU.magScale[1]; + myIMU.mz = (rawmz - myIMU.magBias[2]) * myIMU.magScale[2]; + + } + + myIMU.updateTime(); + + // Sensors x (y)-axis of the accelerometer is aligned with the y (x)-axis of + // the magnetometer; the magnetometer z-axis (+ down) is opposite to z-axis + // (+ up) of accelerometer and gyro! We have to make some allowance for this + // orientationmismatch in feeding the output to the quaternion filter. For the + // MPU-9250, we have chosen a magnetic rotation that keeps the sensor forward + // along the x-axis just like in the LSM9DS0 sensor. This rotation can be + // modified to allow any convenient orientation convention. This is ok by + // aircraft orientation standards! Pass gyro rate as rad/s + MahonyQuaternionUpdate(myIMU.ax, myIMU.ay, myIMU.az, myIMU.gx * DEG_TO_RAD, + myIMU.gy * DEG_TO_RAD, myIMU.gz * DEG_TO_RAD, myIMU.my, + myIMU.mx, myIMU.mz, myIMU.deltat); + + myIMU.delt_t = millis() - myIMU.count; + + if (myIMU.delt_t > 10) + { + myIMU.count = millis(); + myIMU.sumCount = 0; + myIMU.sum = 0; + + // Read temperature and convert to Celsius + myIMU.tempCount = myIMU.readTempData(); + myIMU.temperature = ((float) myIMU.tempCount) / 333.87 + 21.0; + + // Convert to yaw, pitch, roll + const float * q = getQ(); + myIMU.yaw = atan2(2.0f * (q[1]*q[2] + q[0]*q[3]), q[0]*q[0] + q[1]*q[1] - q[2]*q[2] - q[3]*q[3]); + myIMU.pitch = -asin(2.0f * (q[1]*q[3] - q[0]*q[2])); + myIMU.roll = atan2(2.0f * (q[0]*q[1] + q[2]*q[3]), q[0]*q[0] - q[1]*q[1] - q[2]*q[2] + q[3]*q[3]); + myIMU.pitch *= RAD_TO_DEG; + myIMU.yaw *= RAD_TO_DEG; + + // Declination of LiU + // - http://www.ngdc.noaa.gov/geomag-web/#declination + myIMU.yaw -= 5.53; + myIMU.roll *= RAD_TO_DEG; + + if (((imuFifo.head + 1) % IMU_BUFFER_SIZE) != imuFifo.tail) + { + imuFifo.imu[imuFifo.head].timestamp = millis(); + imuFifo.imu[imuFifo.head].temperature = myIMU.temperature; + imuFifo.imu[imuFifo.head].gyro[0] = myIMU.gx; + imuFifo.imu[imuFifo.head].gyro[1] = myIMU.gy; + imuFifo.imu[imuFifo.head].gyro[2] = myIMU.gz; + imuFifo.imu[imuFifo.head].accelerometer[0] = myIMU.ax; + imuFifo.imu[imuFifo.head].accelerometer[1] = myIMU.ay; + imuFifo.imu[imuFifo.head].accelerometer[2] = myIMU.az; + imuFifo.imu[imuFifo.head].magnetometer[0] = rawmx; + imuFifo.imu[imuFifo.head].magnetometer[1] = rawmy; + imuFifo.imu[imuFifo.head].magnetometer[2] = rawmz; + imuFifo.imu[imuFifo.head].orientation[0] = myIMU.yaw; + imuFifo.imu[imuFifo.head].orientation[1] = myIMU.pitch; + imuFifo.imu[imuFifo.head].orientation[2] = myIMU.roll; + + imuFifo.head = (imuFifo.head + 1) % IMU_BUFFER_SIZE; + if (imuFifo.head == 0) digitalWrite(ledStatus, !digitalRead(ledStatus)); + } + } +} diff --git a/Code/Spresense/recorder/recorder.ino b/Code/Spresense/recorder/recorder.ino new file mode 100644 index 0000000000000000000000000000000000000000..89824f36e17d59dfa94009483cc388401a8ec32b --- /dev/null +++ b/Code/Spresense/recorder/recorder.ino @@ -0,0 +1,409 @@ +/* + * recorder_wav.ino - Recorder example application for WAV(PCM) + * Copyright 2018 Sony Semiconductor Solutions Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <MP.h> +#include <SDHCI.h> +#include <Audio.h> + +#include "imu/communication.h" +#include <arch/board/board.h> + +SDClass theSD; +AudioClass *theAudio; + +#define BoardInit 0 /* Initializing */ +#define BoardReady 1 /* Ready to respond */ +#define BoardRecording 2 /* Recording */ +#define BoardTransmitting 3 /* Transmitting */ +#define BoardError 4 /* Error occurred, restart */ +uint8_t boardMode; + +#define BUTTON_PIN PIN_D08 +bool buttonPressed = false; +int64_t last_blink = 0; +bool Recording = false; +bool ErrEnd = false; + +static const int32_t channels = 8; /* 2, 4, 8) */ +static const int32_t frequency = 48000; /* 16, 48, 64, 96, 128, 192 kHz */ + +const int subcore = 1; +IMUFIFO * imu_fifo =(IMUFIFO *) 0; + +#define SERIAL_BUFFFER_SIZE (255) +uint8_t serialBuffer[SERIAL_BUFFFER_SIZE]; +String command; +File myFile; +char filename[100]; + +#define IMU_FRAME_SIZE (sizeof(magicWord) + sizeof(dataSize) + sizeof(IMUData)) +#define BUFFER_SIZE (1024*28) +const uint8_t magicWord[] = {0xFE, 0xED, 0xAB, 0xEE, 0xDE, 0xAD};//, 0xBE, 0xEF +const uint16_t imuSize = sizeof(IMUData); +uint8_t writeBuffer[BUFFER_SIZE]; +uint16_t lastWrite = 0; +uint32_t writeSize = 0; + + +int32_t number; +static const char* fileformat = "Sound%03d.wav"; + +static void audio_attention_cb(const ErrorAttentionParam *atprm) +{ + puts("Attention!"); + + if (atprm->error_code >= AS_ATTENTION_CODE_WARNING) + { + ErrEnd = true; + } +} + +void setup() +{ + number = 1; + boardMode = BoardInit; + + puts("Initializing LEDs..."); + pinMode(LED0, OUTPUT); + pinMode(LED1, OUTPUT); + pinMode(LED2, OUTPUT); + + puts("Initializing Serial Port..."); + Serial.begin(115200); + + pinMode(BUTTON_PIN, INPUT_PULLUP); + buttonPressed = !digitalRead(BUTTON_PIN); + + puts("Initializing SD Card..."); + theSD.begin(); + + puts("Initializing Audio Library..."); + theAudio = AudioClass::getInstance(); + theAudio->begin(audio_attention_cb); + + puts("Setting Recorder Mode..."); + uint8_t input_device = AS_SETRECDR_STS_INPUTDEVICE_MIC; + int32_t input_gain = 0; + uint32_t buffer_size = 2*SIMPLE_FIFO_BUF_SIZE; + bool is_digital = true; + err_t res = theAudio->setRecorderMode(input_device, input_gain, buffer_size, is_digital); + + puts("Initializing Recorder..."); + theAudio->initRecorder(AS_CODECTYPE_WAV,"/mnt/sd0/BIN",frequency, channels); + + puts("Initializing SubCore 1..."); + void * data; + int8_t msgid = 0; + int ret = MP.begin(subcore); + if (ret < 0) { + printf("MP.begin error = %d\n", ret); + } + MP.RecvTimeout(MP_RECV_POLLING); + + Serial.println("Subcore started, waiting: "); + ret = -1; + MP.RecvTimeout(500); + while(!(ret >= 0 && msgid == MSG_FIFO_ADDRESS)) { + Serial.print("."); + ret = MP.Recv(&msgid, &data, subcore); + } + MP.RecvTimeout(MP_RECV_POLLING); + Serial.println("Pointer received"); + imu_fifo = (IMUFIFO *) data; + MP.Send(MSG_FIFO_ADDRESS, ACK, subcore); + + while(ret == 0) {ret = MP.Recv(&msgid, &data, subcore);} + + digitalWrite(LED0, HIGH); + boardMode = BoardReady; +} + +void ListFiles() +{ + if (boardMode != BoardReady) return; + boardMode = BoardTransmitting; + + puts("Listing Files..."); + + String path = ""; + File root = theSD.open(""); + + root.rewindDirectory(); + + while(true) + { + File entry = root.openNextFile(); + if (!entry) { + Serial2.println(""); + Serial2.println(""); + Serial2.println(""); + Serial2.println(""); + Serial2.println(""); + Serial2.println(""); + break; + } + String path = entry.name(); + int sep = path.lastIndexOf('/'); + path = path.substring(sep+1); + sep = path.lastIndexOf('.'); + if (path.substring(sep+1).equals("wav")) { + Serial2.println(path + "\t" + (entry.size()/1024)); + } + entry.close(); + } + root.close(); + + boardMode = BoardReady; +} + +void StartRecording() +{ + if (boardMode != BoardReady) + { + Serial.print("Wrong board mode: "); + Serial.println(boardMode); + return; + } + boardMode = BoardRecording; + + puts("Opening file..."); + + sprintf(filename, fileformat, number); + while(theSD.exists(filename)) // + { + sprintf(filename, fileformat, ++number); + } + puts(filename); + + myFile = theSD.open(filename, FILE_WRITE); + if (!myFile) + { + puts("File open error"); + ErrEnd = true; return; + } + + puts("Writing header..."); + theAudio->writeWavHeader(myFile); + + writeSize = 0; + + puts("Starting Recorder..."); + theAudio->startRecorder(); + + digitalWrite(LED1, HIGH); + Recording = true; + puts("Recording Started..."); + lastWrite = millis(); +} + +void StopRecording(bool error = false) +{ + if (boardMode != BoardRecording) + { + Serial.print("Wrong board mode: "); + Serial.println(boardMode); + return; + } + err_t err; + + puts("Stop Recording..."); + digitalWrite(LED1, LOW); + theAudio->stopRecorder(); + Recording = false; + + puts("Closing File..."); + theAudio->closeOutputFile(myFile); + + puts("Recording Stopped..."); + + boardMode = BoardReady; +} + +void TransmitFile(String file) +{ + if (boardMode != BoardReady) return; + boardMode = BoardTransmitting; + + puts("Opening file..."); + puts(file.c_str()); + + myFile = theSD.open(file, FILE_READ); + + /* Verify file open */ + if (!myFile) + { + puts("File for transmission not found..."); + } + + Serial.println(Serial2.availableForWrite()); + Serial2.println(myFile.size()); + Serial.println(myFile.size()); + + puts("Transmission start..."); + // Flush old data + while(Serial2.available() > 0) {Serial2.read();} + int n = myFile.read(&serialBuffer, SERIAL_BUFFFER_SIZE); + int send_ready = true; + while(n > 0) + { + if (send_ready) + { + Serial2.write(serialBuffer, n); + send_ready = false; + } + else if (Serial2.available()) + { + int ret = Serial2.read(); + if (ret == 1) + { + n = myFile.read(&serialBuffer, SERIAL_BUFFFER_SIZE); + send_ready = true; + } + else if (ret == 2) + { + break; + } + else + { + Serial.println("Resending"); + send_ready = true; + } + + } + + } + puts("Transmission end..."); + myFile.close(); + + boardMode = BoardReady; +} + +void loop() +{ + /* Blink status LED to show that it is alive */ + if (millis()-last_blink > 1000) + { + digitalWrite(LED2, !digitalRead(LED2)); + last_blink = millis(); + } + + /* Start and stop recording with the press of a button */ + bool buttonPressedNow = !digitalRead(BUTTON_PIN); + if (!buttonPressedNow && buttonPressed) + { + if(Recording) StopRecording(); else StartRecording(); + } + buttonPressed = buttonPressedNow; + + /* IMU data available, copy to SD write buffer */ + if (imu_fifo->tail != imu_fifo->head) + { + if (Recording) + { + // Copy IMU data to write buffer + memcpy(&writeBuffer[writeSize], magicWord, sizeof(magicWord)); + writeSize += sizeof(magicWord); + memcpy(&writeBuffer[writeSize], &imuSize, sizeof(imuSize)); + writeSize += sizeof(imuSize); + memcpy(&writeBuffer[writeSize], &imu_fifo->imu[imu_fifo->tail], imuSize); + writeSize += imuSize; + } + + imu_fifo->tail = (imu_fifo->tail + 1) % IMU_BUFFER_SIZE; + } + + if (Serial2.available() > 0) + { + char c = Serial2.read(); // read a byte, then + //Serial.write(c); // print it out the serial monitor + if(c == '\r') + { + puts(command.c_str()); + if (command.startsWith("start")) + { + StartRecording(); + } + else if (command.startsWith("stop")) + { + StopRecording(); + } + else if (command.startsWith("list")) + { + ListFiles(); + } + else if (command.startsWith("status")) + { + Serial.println(boardMode); + Serial2.write(boardMode); + } + else if (command.startsWith("get")) + { + command = command.substring(4); + TransmitFile(command); + } + + command = ""; + } + else if(c != '\n') + { + command += c; + } + } + + /* Copy audio data to SD write buffer */ + if (Recording) + { + uint32_t bytesWritten; + uint16_t tempTime; + + err_t err = AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA; + + while(err == AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA) + { + err = theAudio->readFrames(&writeBuffer[writeSize], sizeof(writeBuffer) - writeSize, &bytesWritten ); + writeSize += bytesWritten; + + //printf("dSize = %d\n", writeSize); + if(err == AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA) printf("IBA T = %d\n", millis()-lastWrite); + + /* Write combined stream to SD card */ + if (bytesWritten > 0) { + tempTime = millis(); + if(tempTime-lastWrite > 24) printf("T: %u\n", tempTime - lastWrite); + lastWrite = tempTime; + myFile.write(writeBuffer, writeSize); + writeSize = 0; + } + } + + if (err != AUDIOLIB_ECODE_OK) + { + printf("File End! =%d\n",err); + StopRecording(true); + } + + } + + if (ErrEnd) + { + printf("Error End\n"); + StopRecording(true); + ErrEnd = false; + } +} diff --git a/Code/Spresense/recorder_no_serial.ino b/Code/Spresense/recorder_no_serial.ino new file mode 100644 index 0000000000000000000000000000000000000000..b524f9f7078af76374e076eecdef126eca7ff51d --- /dev/null +++ b/Code/Spresense/recorder_no_serial.ino @@ -0,0 +1,268 @@ +/* + * recorder_wav.ino - Recorder example application for WAV(PCM) + * Copyright 2018 Sony Semiconductor Solutions Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <MP.h> +#include <SDHCI.h> +#include <Audio.h> + +#include "imu/communication.h" +#include <arch/board/board.h> + +SDClass theSD; +AudioClass *theAudio; + +#define BoardInit 0 /* Initializing */ +#define BoardReady 1 /* Ready to respond */ +#define BoardRecording 2 /* Recording */ +uint8_t boardMode; + +#define BUTTON_PIN PIN_D08 +bool buttonPressed = false; +int64_t last_blink = 0; +bool Recording = false; +bool ErrEnd = false; + +static const int32_t channels = 8; /* 2, 4, 8) */ +static const int32_t frequency = 48000; /* 16, 48, 64, 96, 128, 192 kHz */ + +const int subcore = 1; +IMUFIFO * imu_fifo =(IMUFIFO *) 0; + +File myFile; +char filename[100]; + +#define IMU_FRAME_SIZE (sizeof(magicWord) + sizeof(dataSize) + sizeof(IMUData)) +#define BUFFER_SIZE (1024*28) +const uint8_t magicWord[] = {0xFE, 0xED, 0xAB, 0xEE, 0xDE, 0xAD};//, 0xBE, 0xEF +const uint16_t imuSize = sizeof(IMUData); +uint8_t writeBuffer[BUFFER_SIZE]; +uint16_t lastWrite = 0; +uint32_t writeSize = 0; + +int32_t number; +static const char* fileformat = "Sound%03d.wav"; + +static void audio_attention_cb(const ErrorAttentionParam *atprm) +{ + puts("Attention!"); + + if (atprm->error_code >= AS_ATTENTION_CODE_WARNING) + { + ErrEnd = true; + } +} + +void setup() +{ + number = 1; + boardMode = BoardInit; + + puts("Initializing LEDs..."); + pinMode(LED0, OUTPUT); + pinMode(LED1, OUTPUT); + pinMode(LED2, OUTPUT); + + puts("Initializing Serial Port..."); + Serial.begin(115200); + + pinMode(BUTTON_PIN, INPUT_PULLUP); + buttonPressed = !digitalRead(BUTTON_PIN); + + puts("Initializing SD Card..."); + theSD.begin(); + + puts("Initializing Audio Library..."); + theAudio = AudioClass::getInstance(); + theAudio->begin(audio_attention_cb); + + puts("Setting Recorder Mode..."); + uint8_t input_device = AS_SETRECDR_STS_INPUTDEVICE_MIC; + int32_t input_gain = 0; + uint32_t buffer_size = 2*SIMPLE_FIFO_BUF_SIZE; + bool is_digital = true; + err_t res = theAudio->setRecorderMode(input_device, input_gain, buffer_size, is_digital); + + puts("Initializing Recorder..."); + theAudio->initRecorder(AS_CODECTYPE_WAV,"/mnt/sd0/BIN",frequency, channels); + + puts("Initializing SubCore 1..."); + void * data; + int8_t msgid = 0; + int ret = MP.begin(subcore); + if (ret < 0) { + printf("MP.begin error = %d\n", ret); + } + MP.RecvTimeout(MP_RECV_POLLING); + + Serial.println("Subcore started, waiting: "); + ret = -1; + MP.RecvTimeout(500); + while(!(ret >= 0 && msgid == MSG_FIFO_ADDRESS)) { + Serial.print("."); + ret = MP.Recv(&msgid, &data, subcore); + } + MP.RecvTimeout(MP_RECV_POLLING); + Serial.println("Pointer received"); + imu_fifo = (IMUFIFO *) data; + MP.Send(MSG_FIFO_ADDRESS, ACK, subcore); + + while(ret == 0) {ret = MP.Recv(&msgid, &data, subcore);} + + digitalWrite(LED0, HIGH); + boardMode = BoardReady; +} + +void StartRecording() +{ + if (boardMode != BoardReady) + { + Serial.print("Wrong board mode: "); + Serial.println(boardMode); + return; + } + boardMode = BoardRecording; + + puts("Opening file..."); + + sprintf(filename, fileformat, number); + while(theSD.exists(filename)) // + { + sprintf(filename, fileformat, ++number); + } + puts(filename); + + myFile = theSD.open(filename, FILE_WRITE); + if (!myFile) + { + puts("File open error"); + ErrEnd = true; return; + } + + puts("Writing header..."); + theAudio->writeWavHeader(myFile); + + writeSize = 0; + + puts("Starting Recorder..."); + theAudio->startRecorder(); + + digitalWrite(LED1, HIGH); + Recording = true; + puts("Recording Started..."); + lastWrite = millis(); +} + +void StopRecording(bool error = false) +{ + if (boardMode != BoardRecording) + { + Serial.print("Wrong board mode: "); + Serial.println(boardMode); + return; + } + err_t err; + + puts("Stop Recording..."); + digitalWrite(LED1, LOW); + theAudio->stopRecorder(); + Recording = false; + + puts("Closing File..."); + theAudio->closeOutputFile(myFile); + + puts("Recording Stopped..."); + + boardMode = BoardReady; +} + +void loop() +{ + /* Blink status LED to show that it is alive */ + if (millis()-last_blink > 1000) + { + digitalWrite(LED2, !digitalRead(LED2)); + last_blink = millis(); + } + + /* Start and stop recording with the press of a button */ + bool buttonPressedNow = !digitalRead(BUTTON_PIN); + if (!buttonPressedNow && buttonPressed) + { + if(Recording) StopRecording(); else StartRecording(); + } + buttonPressed = buttonPressedNow; + + /* IMU data available, copy to SD write buffer */ + if (imu_fifo->tail != imu_fifo->head) + { + if (Recording) + { + // Copy IMU data to write buffer + memcpy(&writeBuffer[writeSize], magicWord, sizeof(magicWord)); + writeSize += sizeof(magicWord); + memcpy(&writeBuffer[writeSize], &imuSize, sizeof(imuSize)); + writeSize += sizeof(imuSize); + memcpy(&writeBuffer[writeSize], &imu_fifo->imu[imu_fifo->tail], imuSize); + writeSize += imuSize; + } + + imu_fifo->tail = (imu_fifo->tail + 1) % IMU_BUFFER_SIZE; + } + + /* Copy audio data to SD write buffer */ + if (Recording) + { + uint32_t bytesWritten; + uint16_t tempTime; + + err_t err = AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA; + + while(err == AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA) + { + err = theAudio->readFrames(&writeBuffer[writeSize], sizeof(writeBuffer) - writeSize, &bytesWritten ); + writeSize += bytesWritten; + + //printf("dSize = %d\n", writeSize); + if(err == AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA) printf("IBA T = %d\n", millis()-lastWrite); + + /* Write combined stream to SD card */ + if (bytesWritten > 0) { + tempTime = millis(); + if(tempTime-lastWrite > 24) printf("T: %u\n", tempTime - lastWrite); + lastWrite = tempTime; + myFile.write(writeBuffer, writeSize); + writeSize = 0; + } + } + + if (err != AUDIOLIB_ECODE_OK) + { + printf("File End! =%d\n",err); + StopRecording(true); + } + + } + + if (ErrEnd) + { + printf("Error End\n"); + StopRecording(true); + ErrEnd = false; + } +} diff --git a/Design Files/.gitkeep b/Design Files/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/README.md b/README.md index 2393065a7acbb7e4bcf0a635d2add8fb782880bb..dd5970eb0afd3ea6ff2d2b19f347f23bd10fd906 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,6 @@ This project contains - Design files for the array frame -- Hardware descriptions +- List of hardware components +- Schematics - Source code \ No newline at end of file