i want to write a python-c-extension which should write a list of integers into a ram-area because my first version with python was a little bit to slow (30 ms).
In python this perfectly works with following code:
with open("/dev/mem", "r+b") as f: # open file
ddr_mem = mmap.mmap(f.fileno(), PRU_ICSS_LEN, offset=PRU_ICSS) # map pru shared-ram
while offset < ramSize:
ddr_mem[(sharedRam+offset):(sharedRam+offset+4)] = struct.pack('i', self.data[self.i])
offset += 4
self.i += 1
Because in the list are only long integer values (4 Bytes) the offset will be increased by 4 for every new value in the list.
As the mmap-area is 12 kB big it is possible to write 3072 values into it, because 12288 Byte / 4 Byte = 3072 or i am wrong?
Now within my c extension i tried the same with this piece of code:
if(ddrMem == NULL) {
//printf("\nopen: shared-ram...");
mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd < 0) {
printf("Failed to open /dev/mem (%s)\n", strerror(errno));
return NULL;
}
/* map the DDR memory */
ddrMem = mmap(0, 0x0FFFFFFF, PROT_WRITE | PROT_READ, MAP_SHARED, mem_fd, PRU_ICSS + OFFSET_DDR + 0xE000); //TODO: weird offset - 0xE000
if (ddrMem == NULL) {
printf("Failed to map the device (%s)\n", strerror(errno));
close(mem_fd);
return NULL;
}
for (i = 0; i < d_len; i++) {
PyObject* temp = PySequence_Fast_GET_ITEM(seq, i);
elem = PyInt_AsLong(temp);
*((long*) DDR_regaddr+offset) = elem; // write to shared ram
offset = offset + 4;
if(offset >= (ramSize)){
offset = 0;
}
But now only every fourth address in the ram-area will get a new value. If i increase the offset with one it works but then i am able to write twice the data --> 6144 elements.
What is the trick ? What i am doing wrong ? Are theses calulations right ? I am not sure if i am right with my thougts.
Your offset is incorrect because you are performing pointer arithmetic, which already accounts for the size of the long in question.
Try incrementing your pointer by 1 instead of 4.
long *ptr = 0xff;
long *ptrOffset = ptr + 1; // Will access the next 'long' space in memory
long *ptrOffset2 = ptr + 4; // Will access the fourth next 'long' space in memory
Also, the size of the long is actually architecture and compiler dependent. Assuming that it is 4 bytes is not safe.
Ok sorry.. :(
Wrong memory mapping !
-> missed an offset of 0xF000
But many thanks #kevr - this was also very helpful :)
Related
I am currently working on making an arduino monitoring device. The data is collected in Python and then the string is sent via serial to the arduino.
In Python the string looks like this:
cpu1 = space_pad(int(my_info['cpu_load']), 2)
cpu2 = space_pad(int(my_info['cpu_temp']), 2)
cpu3 = space_pad(int(my_info['cpu_fan']), 5)
# Send the strings via serial to the Arduino
arduino_str = \
'A' + cpu1 + '|B' + cpu2 + '|C' + cpu3 + '|'
if serial_debug:
print(arduino_str)
else:
ser.write(arduino_str.encode())
Ideally I want to make this string as large as possible, to include 10 variables, which I want to send to the arduino.
The arduino code looks at the string and it is supposed to read parts of the string and place them neatly on a display, each in it's own reserved space.
The problem is that I get garbled results. When the string is only made out of just one variable, then it shows just fine, where it should, as it should.
When adding an additional variable to the string, the code breaks and it mixes the results or displays them chaotically. My variables are all clean, just numbers, nothing fancy.
Below is the code I use on the arduino
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // Library for LCD
LiquidCrystal_I2C lcd(0x27,20,4); // I2C address 0x27, 20 column and 4 rows
String inputString = ""; // String for buffering the message
boolean stringComplete = false; // Indicates if the string is complete
unsigned long previousUpdate = 0; // Long to keep the time since last received message
void printInitialLCDStuff() {
lcd.setCursor(0, 0);
lcd.print("CPU ");
lcd.setCursor(7, 0);
lcd.print("%");
lcd.setCursor(11, 0);
lcd.print("C");
lcd.setCursor(17, 0);
lcd.print("RPM");
lcd.setCursor(0, 1);
lcd.print("GPU ");
lcd.setCursor(7, 1);
lcd.print("%");
lcd.setCursor(11, 1);
lcd.print("C");
lcd.setCursor(17, 1);
lcd.print("RPM");
lcd.setCursor(0, 2);
lcd.print("MEM");
lcd.setCursor(8, 2);
lcd.print("MB");
lcd.setCursor(17, 2);
lcd.print("PWM");
lcd.setCursor(0, 3);
lcd.print("RAM ");
lcd.setCursor(8, 3);
lcd.print("GBU");
lcd.setCursor(17, 3);
lcd.print("GBF");
}
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
inputString += inChar;
if (inChar == '|') {
stringComplete = true;
}
}
}
void setup() {
// Setup LCD
lcd.init(); //initialize the lcd
lcd.backlight(); //open the backlight
lcd.setCursor(0, 0);
printInitialLCDStuff();
// Setup serial
Serial.begin(9600);
inputString.reserve(200);
}
void loop() {
serialEvent();
if (stringComplete) {
// CPU1
int cpu1StringStart = inputString.indexOf("A");
int cpu1StringLimit = inputString.indexOf("|");
String cpu1String = inputString.substring(cpu1StringStart + 1, cpu1StringLimit);
lcd.setCursor(4, 0);
lcd.print(cpu1String);
// CPU2
int cpu2StringStart = inputString.indexOf("B", cpu1StringLimit);
int cpu2StringLimit = inputString.indexOf("|", cpu2StringStart);
String cpu2String = inputString.substring(cpu2StringStart + 1, cpu2StringLimit);
lcd.setCursor(9, 0);
lcd.print(cpu2String);
// CPU3
int cpu3StringStart = inputString.indexOf("C", cpu2StringLimit);
int cpu3StringLimit = inputString.indexOf("|", cpu3StringStart);
String cpu3String = inputString.substring(cpu3StringStart + 1, cpu3StringLimit);
lcd.setCursor(13, 0);
lcd.print(cpu3String);
inputString = "";
stringComplete = false;
previousUpdate = millis();
}
}
My code is very dirty and it mostly an adaptation of another code, because while I can read code, I am terrible at writing it. Apologies if I made horrible mistakes that would make anybody cringe. I admit I am just dabbling with coding. This is why I made notes in the code often.
I expect my display to show like this:
CPU 60% 45C 900RPM
Where
cpu1=60
cpu2=45
cpu3=900
The "CPU" "%", "C" and "RPM" are written by the arduino on printInitialLCDStuff() { and not Python.
Instead I get this
CPU B45% B45|B45|C
and then the RPM is listed on line 3 at (0,0) as "900|"
Ideally I want to expand the string sorting to collect about 10 variables.
It looks to me like the problem is in the arduino code, since the Python script kinda checks out and outputs the string correctly. But I could be wrong.
The question is: am I using the wrong code to extract these variables and place them in their reserved space on the display?
Should I use something else to get the job done? I have been looking at documentation for the past 3 days but I couldn't find someone with a similar case. I found some questions here, but again, not quite what I am looking for.
Any help is appreciated. I am so frustrated with this code after trying hours daily for the past days that I am willing to reward anyone that can assist me with this code with a steam digital gift card as way to show my appreciation.
Best regards,
M
I figured out. Instead of trying to separate the string and allocate it all in a preselected space I just made one single string and just edited the string format itself. Now I have a 80 char string and is auto arranged by (0, 20), (20, 40), (40, 60) and (60, 80).
Because this is just a simple resource monitor I didn't really need anything fancy, just to display the info on the screen.
Here is what I did
# Prepare CPU string line #1
cpu1 = space_pad(int(my_info['cpu_load']), 3) + '% '
cpu2 = space_pad(int(my_info['cpu_temp']), 2) + 'C '
cpu3 = space_pad(int(my_info['cpu_fan']), 4) + 'RPM'
CPU = 'CPU ' + cpu1 + cpu2 + cpu3
# Prepare GPU string line #2
gpu1 = space_pad(int(my_info['gpu_load']), 3) + '% '
gpu2 = space_pad(int(my_info['gpu_temp']), 2) + 'C '
gpu3 = space_pad(int(my_info['gpu_fan']), 4) + 'RPM'
GPU1 = 'GPU ' + gpu1 + gpu2 + gpu3
# Prepare GPU string line #3
gpu4 = space_pad(int(my_info['gpu_mem']), 4) + 'MB '
gpu5 = space_pad(int(my_info['gpu_pwm']), 3) + '% PWM'
GPU2 = 'MEM ' + gpu4 + gpu5
# Prepare RAM strng line #4
ram1 = space_pad(float(my_info['ram_used']), 4) + 'GBU '
ram2 = space_pad(float(my_info['ram_free']), 4) + 'GB'
RAM = 'RAM ' + ram1 + ram2
# Send the strings via serial to the Arduino
arduino_str = \
CPU + GPU1 + GPU2 + RAM + 'F'
if serial_debug:
print(arduino_str)
else:
ser.write(arduino_str.encode())
Because the '|' separator was constantly showing up as the last character of the string, I just switched to a letter that I wanted to appear instead, basically duck-taping it. I used the character 'F' which also acts as the letter and is shown in the string.
As for the Arduino code:
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // Library for LCD
LiquidCrystal_I2C lcd(0x27,20,4); // I2C address 0x27, 20 column and 4 rows
String inputString = ""; // String for buffering the message
boolean stringComplete = false; // Indicates if the string is complete
unsigned long previousUpdate = 0; // Long to keep the time since last received message
void printInitialLCDStuff() {
}
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
inputString += inChar;
if (inChar == 'F') {
stringComplete = true;
}
}
}
void setup() {
// Setup LCD
lcd.init(); //initialize the lcd
lcd.backlight(); //open the backlight
printInitialLCDStuff();
lcd.setCursor(0, 0);
lcd.print("Arduino PC Monitor");
lcd.setCursor(0, 1);
lcd.print("Waiting for data...");
lcd.setCursor(12, 3);
lcd.print("Ver 1.0");
// Setup serial
Serial.begin(9600);
inputString.reserve(200);
}
void loop() {
serialEvent();
if (stringComplete) {
// 1st line
String cpuString = inputString.substring(0, 20);
lcd.setCursor(0, 0);
lcd.print(cpuString);
// 2nd line
String gpu1String = inputString.substring(20, 40);
lcd.setCursor(0, 1);
lcd.print(gpu1String);
// 3rd line
String gpu2String = inputString.substring(40, 60);
lcd.setCursor(0, 2);
lcd.print(gpu2String);
// 4th line
String ramString = inputString.substring(60, 80);
lcd.setCursor(0, 3);
lcd.print(ramString);
inputString = "";
stringComplete = false;
previousUpdate = millis();
}
}
Yes, it is that lazy, it is almost as sophisticated as counting on your fingers. I love how simple it is. Anything can be edited on the fly and all it takes is minimal knowledge.
I can understand if I get banned for being this lazy.
Thank you for your help. I have noted the advice on string splits if I need to do something more complicated.
Best regards,
M
Your code can be changed to make it work, however, it would be like patching it up. I think it is better to take a different approach.
First, on your Python code, the prefix A, B are become redundant and not helpful on the receiving side to parse the data. If you format your data as a string with | as the separator, it make it easier to parse, and it is also much easier to create a string like that in Python.
arduino_str = "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}".format(
data0, data1, data2, data3, data4,
data5, data6, data7, data8, data9)
if serial_debug:
print(arduino_str)
else:
ser.write(arduino_str.encode())
On the Arduino side, Serial.readStringUntil() would make the reading entire string until a \n (end of the string) is encountered. Once the entire string is received, you can use the strtok() function in C++ to split the string by the delimiter (in this case it is |) into an array, so the splitted array would looks like this:
splitted[0] = data0;
splitted[1] = data1;
....
splitted[9] = data9;
You can then print the data in the array to the LCD.
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // Library for LCD
#define NUMBER_OF_DATA 10
LiquidCrystal_I2C lcd(0x27,20,4); // I2C address 0x27, 20 column and 4 rows
String incomingString = "";
void setup()
{
// Setup LCD
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.setCursor(0, 0);
lcd.print("CPU % C RPM");
lcd.setCursor(0, 1);
lcd.print("GPU % C RPM");
lcd.setCursor(0, 2);
lcd.print("MEM MB PWM");
lcd.setCursor(0, 3);
lcd.print("RAM GBU GBF");
// Setup serial
Serial.begin(9600);
}
void loop()
{
// read data from Serial until '\n' is received
while (Serial.available()) {
incomingString = Serial.readBytesUntil('\n');
}
if (incomingString) {
// convert the String object to a c_string
char *c_string = incomingString.c_str();
// make a copy of received data so that incoming data would not override the received data
char temp[strlen(c_string)+1];
strcpy(temp, c_string);
incomingString = "";
// parse the received string separated by '|' into an array
char splitted[NUMBER_OF_DATA][10] = {'\0'}; // initialise an array of 10-ch string
int i = 0;
char *p = strtok(temp, '|'); // parse first element in the string
while(p != NULL) { // loop through the string to fill the array
splitted[i++] = p;
p = strtok(NULL, '|');
}
// update LCD with the data in the array
lcd.setCursor(4, 0);
lcd.print(splitted[0]);
lcd.setCursor(9, 0);
lcd.print(splitted[1]);
lcd.setCursor(13, 0);
lcd.print(splitted[2]);
// print the rest of data
lcd.setCursor(13, 3);
lcd.print(splitted[9]);
}
}
I wrote this based on your code and have not debug on an Arduino yet, so it might need some debugging if it is not work out-of-the-box. I hope this help you in learn some trick and a little bit of C++ string and char array.
I was trying a little experiment in order to get the timestamps of the RTP packets using the VideoCapture class from Opencv's source code in python, also had to modify FFmpeg to accommodate the changes in Opencv.
Since I read about the RTP packet format.Wanted to fiddle around and see if I could manage to find a way to get the NTP timestamps. Was unable to find any reliable help in trying to get RTP timestamps. So tried out this little hack.
Credits to ryantheseer on github for the modified code.
Version of FFmpeg: 3.2.3
Version of Opencv: 3.2.0
In Opencv source code:
modules/videoio/include/opencv2/videoio.hpp:
Added two getters for the RTP timestamp:
.....
/** #brief Gets the upper bytes of the RTP time stamp in NTP format (seconds).
*/
CV_WRAP virtual int64 getRTPTimeStampSeconds() const;
/** #brief Gets the lower bytes of the RTP time stamp in NTP format (fraction of seconds).
*/
CV_WRAP virtual int64 getRTPTimeStampFraction() const;
.....
modules/videoio/src/cap.cpp:
Added an import and added the implementation of the timestamp getter:
....
#include <cstdint>
....
....
static inline uint64_t icvGetRTPTimeStamp(const CvCapture* capture)
{
return capture ? capture->getRTPTimeStamp() : 0;
}
...
Added the C++ timestamp getters in the VideoCapture class:
....
/**#brief Gets the upper bytes of the RTP time stamp in NTP format (seconds).
*/
int64 VideoCapture::getRTPTimeStampSeconds() const
{
int64 seconds = 0;
uint64_t timestamp = 0;
//Get the time stamp from the capture object
if (!icap.empty())
timestamp = icap->getRTPTimeStamp();
else
timestamp = icvGetRTPTimeStamp(cap);
//Take the top 32 bytes of the time stamp
seconds = (int64)((timestamp & 0xFFFFFFFF00000000) / 0x100000000);
return seconds;
}
/**#brief Gets the lower bytes of the RTP time stamp in NTP format (seconds).
*/
int64 VideoCapture::getRTPTimeStampFraction() const
{
int64 fraction = 0;
uint64_t timestamp = 0;
//Get the time stamp from the capture object
if (!icap.empty())
timestamp = icap->getRTPTimeStamp();
else
timestamp = icvGetRTPTimeStamp(cap);
//Take the bottom 32 bytes of the time stamp
fraction = (int64)((timestamp & 0xFFFFFFFF));
return fraction;
}
...
modules/videoio/src/cap_ffmpeg.cpp:
Added an import:
...
#include <cstdint>
...
Added a method reference definition:
...
static CvGetRTPTimeStamp_Plugin icvGetRTPTimeStamp_FFMPEG_p = 0;
...
Added the method to the module initializer method:
...
if( icvFFOpenCV )
...
...
icvGetRTPTimeStamp_FFMPEG_p =
(CvGetRTPTimeStamp_Plugin)GetProcAddress(icvFFOpenCV, "cvGetRTPTimeStamp_FFMPEG");
...
...
icvWriteFrame_FFMPEG_p != 0 &&
icvGetRTPTimeStamp_FFMPEG_p !=0)
...
icvGetRTPTimeStamp_FFMPEG_p = (CvGetRTPTimeStamp_Plugin)cvGetRTPTimeStamp_FFMPEG;
Implemented the getter interface:
...
virtual uint64_t getRTPTimeStamp() const
{
return ffmpegCapture ? icvGetRTPTimeStamp_FFMPEG_p(ffmpegCapture) : 0;
}
...
In FFmpeg's source code:
libavcodec/avcodec.h:
Added the NTP timestamp definition to the AVPacket struct:
typedef struct AVPacket {
...
...
uint64_t rtp_ntp_time_stamp;
}
libavformat/rtpdec.c:
Store the ntp time stamp in the struct in the finalize_packet method:
static void finalize_packet(RTPDemuxContext *s, AVPacket *pkt, uint32_t timestamp)
{
uint64_t offsetTime = 0;
uint64_t rtp_ntp_time_stamp = timestamp;
...
...
/*RM: Sets the RTP time stamp in the AVPacket */
if (!s->last_rtcp_ntp_time || !s->last_rtcp_timestamp)
offsetTime = 0;
else
offsetTime = s->last_rtcp_ntp_time - ((uint64_t)(s->last_rtcp_timestamp) * 65536);
rtp_ntp_time_stamp = ((uint64_t)(timestamp) * 65536) + offsetTime;
pkt->rtp_ntp_time_stamp = rtp_ntp_time_stamp;
libavformat/utils.c:
Copy the ntp time stamp from the packet to the frame in the read_frame_internal method:
static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{
...
uint64_t rtp_ntp_time_stamp = 0;
...
while (!got_packet && !s->internal->parse_queue) {
...
//COPY OVER the RTP time stamp TODO: just create a local copy
rtp_ntp_time_stamp = cur_pkt.rtp_ntp_time_stamp;
...
#if FF_API_LAVF_AVCTX
update_stream_avctx(s);
#endif
if (s->debug & FF_FDEBUG_TS)
av_log(s, AV_LOG_DEBUG,
"read_frame_internal stream=%d, pts=%s, dts=%s, "
"size=%d, duration=%"PRId64", flags=%d\n",
pkt->stream_index,
av_ts2str(pkt->pts),
av_ts2str(pkt->dts),
pkt->size, pkt->duration, pkt->flags);
pkt->rtp_ntp_time_stamp = rtp_ntp_time_stamp; #Just added this line in the if statement.
return ret;
My python code to utilise these changes:
import cv2
uri = 'rtsp://admin:password#192.168.1.67:554'
cap = cv2.VideoCapture(uri)
while True:
frame_exists, curr_frame = cap.read()
# if frame_exists:
k = cap.getRTPTimeStampSeconds()
l = cap.getRTPTimeStampFraction()
time_shift = 0x100000000
#because in the getRTPTimeStampSeconds()
#function, seconds was multiplied by 0x10000000
seconds = time_shift * k
m = (time_shift * k) + l
print("Imagetimestamp: %i" % m)
cap.release()
What I am getting as my output:
Imagetimestamp: 0
Imagetimestamp: 212041451700224
Imagetimestamp: 212041687629824
Imagetimestamp: 212041923559424
Imagetimestamp: 212042159489024
Imagetimestamp: 212042395418624
Imagetimestamp: 212042631348224
...
What astounded me the most was that when i powered off the ip camera and powered it back on, timestamp would start from 0 then quickly increments. I read NTP time format is relative to January 1, 1900 00:00. Even when I tried calculating the offset, and accounting between now and 01-01-1900, I still ended up getting a crazy high number for the date.
Don't know if I calculated it wrong. I have a feeling it's very off or what I am getting is not the timestamp.
As I see it, you receive a timestamp of type uint64 which contains to values uint32 in the high and low bits. I see that in a part of the code you use:
seconds = (int64)((timestamp & 0xFFFFFFFF00000000) / 0x100000000);
Which basically removes the lower bits and shifts the high bits to be in the lower bits. Then you cast it to int64. Here I only consider that it should be unsigned first of all, since it should not be negative in any case (seconds since epoch is always positive) and it should be uint32, since it is guarantee it is not bigger (you are taking only 32 bits). Also, this can be achieved (probably faster) with bitshifts like this:
auto seconds = static_cast<uint32>(timestamp >> 32);
Another error I spotted was in this part:
time_shift = 0x100000000
seconds = time_shift * k
m = (time_shift * k) + l
Here you are basically reconstructing the 64 bit timestamp, instead of creating the timestamp usable in other contexts. This means, you are shifting the lower bits in seconds to higher bits and adding the fraction part as the lower bits... This will end in a really big number which may not be useful always. You can still use it for comparison, but then all the conversions done in the C++ part are not needed. I think a more normal timestamp, which you can use with python datetime would be like this:
timestamp = float(str(k) + "." + str(l)) # don't know if there is a better way
date = datetime.fromtimestamp(timestamp)
If you don't care of the fractional part you can just use the seconds directly.
Another thing to consider is, that the timestamp of RTP protocols depends on the camera/server... They may use the clock timestamp or just some other clock like start of the streaming of start of the system. So it may or not be from epoch.
I want to send long values using python to an arduino board which runs c++. The serial communication breaks the 4 byte numbers up and sends them byte by byte. When I try to reassemble them on the back end, I only get a valid number for 2 bytes instead of the four bytes I sent.
Here is the python code sending instructions.
pos1 = int(input("pos1: "))
pos2 = int(input("pos2: "))
data = struct.pack('<ll', pos1, pos2)
ser.write(data)
Here is the arduino code to parse the bytes that it reads.
if(Serial.available()>0){
size_t numbytes = Serial.readBytes(data, 8);
for(int i=0; i<8; i++){
Serial.println(data[i], HEX);
}
pos1 = readfourbytes(data[0], data[1], data[2], data[3]);
pos2 = readfourbytes(data[4], data[5], data[6], data[7]);
Serial.println(pos1);
Serial.println(pos2);
}
long readfourbytes(byte fourthbyte, byte thirdbyte, byte thirdbyte, byte firstbyte){
long result = (firstbyte << 24) + (secondbyte << 16) + (thirdbyte << 8) + fourthbyte;
return result;
}
I guess this means the arduino is little endian? My problem is the second position value that is read is completely off. The python code seems to be the problem however I don't know why. when I send the int values of 100 for both, I get an output of
b'd\x00\x00\x00d\x00\x00\x00'
from the python code as the binary being sent in the data variable. But from the arduino, I recieve:
64
0
0
0
6D
2
0
0
100
621
So there is a disconnect between what I am sending and what I am recieving. The baudrates are the same and there is no other obvious fault that I am aware of.
All the expressions (<any>byte << <bits>) are evaluated as int that seems to be 16 bits on the arduino. Cast <any>byte into long, and you're done.
long readfourbytes(byte fourthbyte, byte thirdbyte, byte thirdbyte, byte firstbyte){
long result = ((long)firstbyte << 24) + ((long)secondbyte << 16) + (thirdbyte << 8) + fourthbyte;
return result;
}
I'm trying to send messages through the serial USB interface of my Arduino (C++) to a Raspberry Pi (Python).
On the Arduino side I define a struct which I then copy into a char[]. The last part of the struct contains a checksum that I want to calculate using CRC32. I copy the struct into a temporary char array -4 bytes to strip the checksum field. The checksum is then calculated using the temporary array and the result is added to the struct. The struct is then copied into byteMsg which gets send over the serial connection.
On the raspberry end I do the reverse, I receive the bytestring and calculate the checksum over the message - 4 bytes. Then unpack the bytestring and compare the received and calculated checksum but this fails unfortunately.
For debugging I compared the crc32 check on both the python and arduino for the string "Hello World" and they generated the same checksum so doesn't seem to be a problem with the library. The raspberry is also able to decode the rest of the message just fine so the unpacking of the data into variables seem to be ok as well.
Any help would be much appreciated.
The Python Code:
def unpackMessage(self, message):
""" Processes a received byte string from the arduino """
# Unpack the received message into struct
(messageID, acknowledgeID, module, commandType,
data, recvChecksum) = struct.unpack('<LLBBLL', message)
# Calculate the checksum of the recv message minus the last 4
# bytes that contain the sent checksum
calcChecksum = crc32(message[:-4])
if recvChecksum == calcChecksum:
print "Checksum checks out"
The Aruino crc32 library taken from http://excamera.com/sphinx/article-crc.html
crc32.h
#include <avr/pgmspace.h>
static PROGMEM prog_uint32_t crc_table[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
unsigned long crc_update(unsigned long crc, byte data)
{
byte tbl_idx;
tbl_idx = crc ^ (data >> (0 * 4));
crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4);
tbl_idx = crc ^ (data >> (1 * 4));
crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4);
return crc;
}
unsigned long crc_string(char *s)
{
unsigned long crc = ~0L;
while (*s)
crc = crc_update(crc, *s++);
crc = ~crc;
return crc;
}
Main Arduino Sketch
struct message_t {
unsigned long messageID;
unsigned long acknowledgeID;
byte module;
byte commandType;
unsigned long data;
unsigned long checksum;
};
void sendMessage(message_t &msg)
{
// Set the messageID
msg.messageID = 10;
msg.checksum = 0;
// Copy the message minus the checksum into a char*
// Then perform the checksum on the message and copy
// the full msg into byteMsg
char byteMsgForCrc32[sizeof(msg)-4];
memcpy(byteMsgForCrc32, &msg, sizeof(msg)-4);
msg.checksum = crc_string(byteMsgForCrc32);
char byteMsg[sizeof(msg)];
memcpy(byteMsg, &msg, sizeof(msg));
Serial.write(byteMsg, sizeof(byteMsg));
void loop() {
message_t msg;
msg.module = 0x31;
msg.commandType = 0x64;
msg.acknowledgeID = 0;
msg.data = 10;
sendMessage(msg);
Kind Regards,
Thiezn
You are making the classic struct-to-network/serial/insert communication layer mistake. Structs have hidden padding in order to align the members onto suitable memory boundaries. This is not guaranteed to be the same across different computers, let alone different CPUs/microcontrollers.
Take this struct as an example:
struct Byte_Int
{
int x;
char y;
int z;
}
Now on a basic 32-bit x86 CPU you have a 4-byte memory boundary. Meaning that variables are aligned to either 4 bytes, 2 bytes or not at all according to the type of variable. The example would look like this in memory: int x on bytes 0,1,2,3, char y on byte 4, int z on bytes 8,9,10,11. Why not use the three bytes on the second line? Because then the memory controller would have to do two fetches to get a single number! A controller can only read one line at a time. So, structs (and all other kinds of data) have hidden padding in order to get variables aligned in memory. The example struct would have a sizeof() of 12, and not 9!
Now, how does that relate to your problem? You are memcpy()ing a struct directly into a buffer, including the padding. The computer on the other end doesn't know about this padding and misinterprets the data. What you need a serialization function that takes the members of your structs and pasts them into a buffer one at a time, that way you lose the padding and end up with something like this:
[0,1,2,3: int x][4: char y][5,6,7,8: int z]. All as one lengthy bytearray/string which can be safely sent using Serial(). Of course on the other end you would have to parse this string into intelligible data. Python's unpack() does this for you as long as you give the right format string.
Lastly, an int on an Arduino is 16 bits long. On a pc generally 4! So assemble your unpack format string with care.
The char array I was passing to the crc_string function contained '\0' characters. The crc_string was iterating through the array until it found a '\0' which shouldn't happen in this case since I was using the char array as a stream of bytes to be sent over a serial connection.
I've changed the crc_string function to take the array size as argument and iterate through the array using that value. This solved the issue.
Here's the new function
unsigned long crc_string(char *s, size_t arraySize)
{
unsigned long crc = ~0L;
for (int i=0; i < arraySize; i++) {
crc = crc_update(crc, s[i]);
}
crc = ~crc;
return crc;
}
This is the code I am currently using to send and receive int values from a RaspberryPi to an Arduino using i2C. It works fine for values 0-255, but because of the 1 byte limit, anything larger fails.
To circumvent this, I'd like to send and receive string values instead, and then convert back to int if necessary.
What changes would I need to make in the following?
Here is my RPi Python code
import smbus
import time
# for RPI version 1, use "bus = smbus.SMBus(0)"
bus = smbus.SMBus(1)
# This is the address we setup in the Arduino Program
address = 0x04
def writeNumber(value):
bus.write_byte(address, value)
# bus.write_byte_data(address, 0, value)
return -1
def readNumber():
number = bus.read_byte(address)
# number = bus.read_byte_data(address, 1)
return number
while True:
try:
var = int(raw_input("Enter 1 - 9: "))
except ValueError:
print "Could you at least give me an actual number?"
continue
writeNumber(var)
print "RPI: Hi Arduino, I sent you ", var
# sleep one second
#time.sleep(1)
number = readNumber()
print "Arduino: Hey RPI, I received a digit ", number
print
And here is my Arduino code
#include <Wire.h>
#define SLAVE_ADDRESS 0x04
int number = 0;
int state = 0;
void setup() {
pinMode(13, OUTPUT);
Serial.begin(9600); // start serial for output
// initialize i2c as slave
Wire.begin(SLAVE_ADDRESS);
// define callbacks for i2c communication
Wire.onReceive(receiveData);
Wire.onRequest(sendData);
Serial.println("Ready!");
}
void loop() {
delay(100);
}
// callback for received data
void receiveData(int byteCount){
while(Wire.available()) {
number = Wire.read();
if (Wire.available() > 1) // at least 2 bytes
{
number = Wire.read() * 256 + Wire.read();
}
Serial.print("data received: ");
Serial.println(number);
//sendData();
if (number == 1){
if (state == 0){
digitalWrite(13, HIGH); // set the LED on
state = 1;
}
else{
digitalWrite(13, LOW); // set the LED off
state = 0;
}
}
}
}
// callback for sending data
void sendData(){
Wire.write(number);
}
This problem essentially has two parts: splitting an integer into its bytes and reassembling an integer from bytes. These parts must be replicated on both the Pi and Arduino. I'll address the Pi side first, in Python:
Splitting an integer:
def writeNumber(value):
# assuming we have an arbitrary size integer passed in value
for character in str(val): # convert into a string and iterate over it
bus.write_byte(address, ord(character)) # send each char's ASCII encoding
return -1
Reassembling an integer from bytes:
def readNumber():
# I'm not familiar with the SMbus library, so you'll have to figure out how to
# tell if any more bytes are available and when a transmission of integer bytes
# is complete. For now, I'll use the boolean variable "bytes_available" to mean
# "we are still transmitting a single value, one byte at a time"
byte_list = []
while bytes_available:
# build list of bytes in an integer - assuming bytes are sent in the same
# order they would be written. Ex: integer '123' is sent as '1', '2', '3'
byte_list.append(bus.read_byte(address))
# now recombine the list of bytes into a single string, then convert back into int
number = int("".join([chr(byte) for byte in byte_list]))
return number
Arduino Side, in C
Split an Integer:
void sendData(){
int i = 0;
String outString = String(number); /* convert integer to string */
int len = outString.length()+1 /* obtain length of string w/ terminator */
char ascii_num[len]; /* create character array */
outString.toCharArray(ascii_num, len); /* copy string to character array */
for (i=0; i<len); ++i){
Wire.write(ascii_num[i]);
}
}
Reassembling a received Integer:
Note: I'm having some trouble understanding what your other code in this routine is doing, so I'm going to reduce it to just assembling the integer.
void receiveData(int byteCount){
int inChar;
String inString = "";
/* As with the Python receive routine, it will be up to you to identify the
terminating condition for this loop - "bytes_available" means the same thing
as before */
while(bytes_available){
inChar = Wire.read();
inString += char(inChar);
}
number = inString.toInt();
}
I don't have the materials on hand to test this, so it's possible I've gotten the byte order flipped in one routine or another. If you find stuff coming in or out backwards, the easiest place to fix it is in the Python script by using the built-in function reversed() on the strings or lists.
References (I used some code from the Arduino Examples):
Arduino String objects
Arduino String Constructors
Python Built-ins chr() and ord()
Check the following Link:
[http://www.i2c-bus.org/][1]
When I was sending data back and forward using I2C I was converting the string characters to bytearrays and viceversa. So since you are always sending bytes. It will always work since you are sending numbers between 0-255.
Not sure this helps but at least may give you an idea.
You could convert the number to a string of digits like you said. But you could also send the raw bytes.
String of digits
Advantages
Number can have infinite digits. Note that when Arduino reads the number as a string, it is infinite, but you can't convert it all to integer if it overflows the 16-bit range (or 32-bit for Due).
Disadvantages
Variable size, thus requiring more effort in reading.
Waste of bytes, because each decimal digit would be a byte, plus the null-terminator totalizing (digits + 1) size.
Having to use decimal arithmetic (which really is only useful for human counting), note that a "number to string" operation also uses decimal arithmetic.
You can't send/receive negative numbers (unless you send the minus signal, wasting more time and bytes).
Raw bytes
Advantages
Number of bytes sent for each integer is always 4.
You can send/receive negative numbers.
The bitwise arithmetic in C++ for extracting each byte from the number is really fast.
Python already has the struct library which packs/unpacks each byte in a number to a string to send/receive, so you don't need to do the arithmetic like in C++.
Disadvantages
Number has a limited range (signed 32-bit integer in our case, which ranges from -2147483648 to 2147483647). But it doesn't matter because no Arduino can handle more than 32-bit anyways.
So I would use the raw bytes method, which I can provide some untested functions here:
import struct
# '<i' stands for litle-endian signed integer
def writeNumber(value):
strout = struct.pack('<i', value)
for i in range(4):
bus.write_byte(address, strout[i])
return -1
def readNumber():
strin = ""
for _ in range(4):
strin += bus.read_byte(address)
return struct.unpack('<i', strin)[0]
And the Arduino part:
void receiveData(int byteCount)
{
// Check if we have a 32-bit number (4 bytes) in queue
while(Wire.available() >= 4)
{
number = 0;
for(int i = 0; i < 32; i += 8)
{
// This is merging the bytes into a single integer
number |= ((int)Wire.read() << i);
}
Serial.print("data received: ");
Serial.println(number);
// ...
}
}
void sendData()
{
for(int i = 0; i < 32; i += 8)
{
// This is extracting each byte from the number
Wire.write((number >> i) & 0xFF);
}
}
I don't have any experience with I2C, but if its queue is a FIFO, then the code should work.