@martin-kudláček Ok, I just finished to prepare the SD card based on latest UniPian. I will update this thread when I will have some news. Thank you for the support so far.
Best posts made by ntd
-
RE: Internal ModBUS problem
-
RE: Need Some sort of Jumpstart
@knebb said in Need Some sort of Jumpstart:
I am unsure about the "status".
According to the documentation,
status
is the new value of the output and can beTRUE
(to enable it) orFALSE
(to disable it).Here I have no clue for what I would need the nb... does it mean it will read nb bits? To be stored in nb*dest? I would always use the value 1, wouldn't I? If using 2 it would return the value of the second DI 2.2 in the second bit, correct?
Yes to all. This mimics the behavior of the underlying protocol: ModBUS has a Read coils function but does not have Read single coil. You can easily provide your own macro for that, if you really want:
#define read_bit(bus,addr,dst) modbus_read_bits((bus),(addr),1,(dst))
-
RE: How to Read M103 Modbus Doc?
@knebb TL/DR: just use
uint8_t
. This is the right thing to do.There is no differences (because of integer promotion) only when you use that variable in an expression. Incidentally, and for different reasons [1], reading a single bit will work with whatever 0-initialized integer you throw at
modbus_read_bits
, although conceptually wrong. But I can assure you that, when reading multiple channels, using anything butuint8_t
will give you wrong results.[1] Raspberry Pi is little-endian. Setting the first byte of any 0-initialized
uint*
variable to a specific value (e.g.,TRUE
), makes that very same variable initialized to that value, regardless of its type.Here is an example that hopefully will shed some light:
#include <assert.h> #include <stdint.h> #include <string.h> #define TRUE 12 int main() { // Using an union to be able to set the first byte of everything // with only one instruction (m.byte = ...) union { uint8_t byte; uint16_t word; uint32_t dword; uint64_t qword; } m; // Let's set the first byte of 0 initialized memory memset(&m, 0, sizeof m); m.byte = TRUE; assert(m.byte == TRUE); // Ok assert(m.word == TRUE); // Ok on little-endian machines assert(m.dword == TRUE); // Ok on little-endian machines assert(m.qword == TRUE); // Ok on little-endian machines // Now let's try with random initialized memory memset(&m, 3, sizeof m); m.byte = TRUE; assert(m.byte == TRUE); // Ok assert(m.word == TRUE); // Error! assert(m.dword == TRUE); // Error! assert(m.qword == TRUE); // Error! return 0; }
-
RE: How to Read M103 Modbus Doc?
@knebb said:
Would both work?
None of them will. You should really read a ModBUS introduction... I think the wikipedia page should suffice.
The counter is an input register, not a bit, so:
union { uint16_t word[2]; uint32_t dword; } counter; if (modbus_read_registers(bus, 103, 2, counter.word) != 2) { printf("Error\n"); } else { printf("DI2.1 counter is %u\n", counter.dword); }
Not sure which is the less and the most significant word, so if the above gives wrong results just swap
counter.word[0]
andcounter.word[1]
.
Latest posts made by ntd
-
RE: Sound card support
I managed to get what I need by compiling a new (hopefully) compatible set of modules and installing by hands only the needed ones. Here are the steps I followed, executed directly on the AXON as root:
v=$(uname -r) cd /usr/src wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-$v.tar.xz tar xf linux-$v.tar.xz cd linux-$v gzip -dc /proc/config.gz > .config apt install libncurses5-dev bc # Customize the kernel to your needs. In my case I enabled the # snd-usb-audio module that, in cascade, enabled other sound stuff. make menuconfig # Build and install *only* the new modules. make modules mkdir -p /lib/modules/$v/kernel/sound/usb cp sound/usb/*.ko /lib/modules/$v/kernel/sound/usb/ cp sound/core/snd-*.ko /lib/modules/$v/kernel/sound/core/ depmod # Not strictly needed, but forbidding future axon-kernel upgrades # could avoid potential hard-to-debug problems. apt-mark hold axon-kernel
-
RE: Need Some sort of Jumpstart
@knebb said in Need Some sort of Jumpstart:
it writes the state as 0 or 1 into each of the uint8_t .
Or, using semantic constants, as
FALSE
andTRUE
.Directly using bits would be really slow and error prone (and a nightmare for language bindings).
-
RE: How to Read M103 Modbus Doc?
@knebb said:
Would both work?
None of them will. You should really read a ModBUS introduction... I think the wikipedia page should suffice.
The counter is an input register, not a bit, so:
union { uint16_t word[2]; uint32_t dword; } counter; if (modbus_read_registers(bus, 103, 2, counter.word) != 2) { printf("Error\n"); } else { printf("DI2.1 counter is %u\n", counter.dword); }
Not sure which is the less and the most significant word, so if the above gives wrong results just swap
counter.word[0]
andcounter.word[1]
. -
RE: How to Read M103 Modbus Doc?
@knebb TL/DR: just use
uint8_t
. This is the right thing to do.There is no differences (because of integer promotion) only when you use that variable in an expression. Incidentally, and for different reasons [1], reading a single bit will work with whatever 0-initialized integer you throw at
modbus_read_bits
, although conceptually wrong. But I can assure you that, when reading multiple channels, using anything butuint8_t
will give you wrong results.[1] Raspberry Pi is little-endian. Setting the first byte of any 0-initialized
uint*
variable to a specific value (e.g.,TRUE
), makes that very same variable initialized to that value, regardless of its type.Here is an example that hopefully will shed some light:
#include <assert.h> #include <stdint.h> #include <string.h> #define TRUE 12 int main() { // Using an union to be able to set the first byte of everything // with only one instruction (m.byte = ...) union { uint8_t byte; uint16_t word; uint32_t dword; uint64_t qword; } m; // Let's set the first byte of 0 initialized memory memset(&m, 0, sizeof m); m.byte = TRUE; assert(m.byte == TRUE); // Ok assert(m.word == TRUE); // Ok on little-endian machines assert(m.dword == TRUE); // Ok on little-endian machines assert(m.qword == TRUE); // Ok on little-endian machines // Now let's try with random initialized memory memset(&m, 3, sizeof m); m.byte = TRUE; assert(m.byte == TRUE); // Ok assert(m.word == TRUE); // Error! assert(m.dword == TRUE); // Error! assert(m.qword == TRUE); // Error! return 0; }
-
RE: How to Read M103 Modbus Doc?
@knebb said in How to Read M103 Modbus Doc?:
*dest is supposed to be uint16_t.
No, it is an uint8_t array. Every element of the array contains the value of a single digital channel, set to
TRUE
(that is, IIRC,1
) orFALSE
(that is surely0
).Here is my untested snippet:
// Reading one channel uint8_t my_digital; modbus_read_bits(bus, 108, 1, &my_digital); if (my_digital) { ... } // Reading multiple channels uint8_t my_digitals[2]; modbus_read_bits(bus, 108, 2, my_digitals); if (my_digitals[0]) { ... }
-
RE: Need Some sort of Jumpstart
@knebb said in Need Some sort of Jumpstart:
I am unsure about the "status".
According to the documentation,
status
is the new value of the output and can beTRUE
(to enable it) orFALSE
(to disable it).Here I have no clue for what I would need the nb... does it mean it will read nb bits? To be stored in nb*dest? I would always use the value 1, wouldn't I? If using 2 it would return the value of the second DI 2.2 in the second bit, correct?
Yes to all. This mimics the behavior of the underlying protocol: ModBUS has a Read coils function but does not have Read single coil. You can easily provide your own macro for that, if you really want:
#define read_bit(bus,addr,dst) modbus_read_bits((bus),(addr),1,(dst))
-
RE: Need Some sort of Jumpstart
@knebb The relevant function is
fieldbus_thread
, that basically runs a polling loop every 20 ms. If you do not need threads, you can consider that function asmain()
and drop all the locking stuff (i.e. a good 50% of code initeration
).#include "lappaseven.h" #include "fieldbus.h" #include <errno.h> #include <string.h> #define MODBUS_ID 0 #define AI1_ADDRESS 3 #define DI1_ADDRESS 0 #define DI2_ADDRESS 100 #define DI3_ADDRESS 200 #define DO1_ADDRESS 1 #define DO2_ADDRESS 101 #define DO3_ADDRESS 201 #define AO1_ADDRESS 2 #define VREFINT_ADDRESS 5 #define VREF_ADDRESS 1009 static IO image = { 0 }; void fieldbus_free(modbus_t *modbus) { if (modbus != NULL) { modbus_close(modbus); modbus_free(modbus); } } modbus_t * fieldbus_new(void) { modbus_t *modbus; gchar *host; int port; host = g_settings_get_string(gs.settings, "modbus-host"); port = g_settings_get_uint(gs.settings, "modbus-porta"); modbus = modbus_new_tcp(host, port); if (modbus == NULL || modbus_connect(modbus) == -1 || modbus_set_slave(modbus, MODBUS_ID) == -1) { g_warning("Unable to initialize communication on %s:%d: %s", host, port, modbus_strerror(errno)); fieldbus_free(modbus); modbus = NULL; } g_free(host); return modbus; } gboolean fieldbus_get_ai(modbus_t *modbus) { uint16_t vrefint, vref, ai; if (modbus_read_registers(modbus, VREFINT_ADDRESS, 1, &vrefint) == -1 || modbus_read_registers(modbus, VREF_ADDRESS, 1, &vref) == -1 || modbus_read_registers(modbus, AI1_ADDRESS, 1, &ai) == -1) { g_warning("fieldbus_get_ai error: %s", modbus_strerror(errno)); return FALSE; } /* Linearization to volts as described by Neuron manual, page 14. * AI1vdev and AI1voffset are set to supposedly 0. */ image.AI1 = 3.3 * vref / vrefint * 3 * ai / 4096; return TRUE; } gboolean fieldbus_get_di(modbus_t *modbus) { uint16_t word1, word2, word3; int n; if (modbus_read_registers(modbus, DI1_ADDRESS, 1, &word1) == -1 || modbus_read_registers(modbus, DI2_ADDRESS, 1, &word2) == -1 || modbus_read_registers(modbus, DI3_ADDRESS, 1, &word3) == -1) { g_warning("fieldbus_get_di error: %s", modbus_strerror(errno)); return FALSE; } for (n = 0; n < G_N_ELEMENTS(image.DI1); ++n) { image.DI1[n] = (word1 & (1 << n)) > 0; } for (n = 0; n < G_N_ELEMENTS(image.DI2); ++n) { image.DI2[n] = (word2 & (1 << n)) > 0; } for (n = 0; n < G_N_ELEMENTS(image.DI3); ++n) { image.DI3[n] = (word3 & (1 << n)) > 0; } return TRUE; } gboolean fieldbus_set_do(modbus_t *modbus) { uint16_t word1, word2, word3; int n; word1 = 0; word2 = 0; word3 = 0; for (n = 0; n < G_N_ELEMENTS(image.DO1); ++n) { word1 |= image.DO1[n] << n; } for (n = 0; n < G_N_ELEMENTS(image.DO2); ++n) { word2 |= image.DO2[n] << n; } for (n = 0; n < G_N_ELEMENTS(image.DO3); ++n) { word3 |= image.DO3[n] << n; } if (modbus_write_register(modbus, DO1_ADDRESS, word1) == -1 || modbus_write_register(modbus, DO2_ADDRESS, word2) == -1 || modbus_write_register(modbus, DO3_ADDRESS, word3) == -1) { g_warning("fieldbus_set_do error: %s", modbus_strerror(errno)); return FALSE; } return TRUE; } gboolean fieldbus_set_ao(modbus_t *modbus) { /* Analog output must be between 0 .. 5 V, hence the division */ if (modbus_write_register(modbus, AO1_ADDRESS, image.AO1 / 2) == -1) { g_warning("fieldbus_set_ao error: %s", modbus_strerror(errno)); return FALSE; } return TRUE; } #if THREADING static gboolean iteration(modbus_t *modbus) { static gboolean workaround = FALSE; gboolean AI, DI, AO, DO, quit; AI = fieldbus_get_ai(modbus); DI = fieldbus_get_di(modbus); g_mutex_lock(&gs.fieldbus_lock); AO = gs.hal.AO1 != gs.share.AO1; DO = memcmp(gs.hal.DO1, gs.share.DO1, sizeof(gs.hal.DO1)) != 0 || memcmp(gs.hal.DO2, gs.share.DO2, sizeof(gs.hal.DO2)) != 0 || memcmp(gs.hal.DO3, gs.share.DO3, sizeof(gs.hal.DO3)) != 0; if (DI) { memcpy(gs.share.DI1, image.DI1, sizeof(image.DI1)); memcpy(gs.share.DI2, image.DI2, sizeof(image.DI2)); memcpy(gs.share.DI3, image.DI3, sizeof(image.DI3)); } if (AI) { gs.share.AI1 = image.AI1; } if (DO) { memcpy(image.DO1, gs.share.DO1, sizeof(image.DO1)); memcpy(image.DO2, gs.share.DO2, sizeof(image.DO2)); memcpy(image.DO3, gs.share.DO3, sizeof(image.DO3)); } if (AO) { image.AO1 = gs.share.AO1; } quit = gs.quit; g_mutex_unlock(&gs.fieldbus_lock); if (DI) { memcpy(gs.hal.DI1, image.DI1, sizeof(image.DI1)); memcpy(gs.hal.DI2, image.DI2, sizeof(image.DI2)); memcpy(gs.hal.DI3, image.DI3, sizeof(image.DI3)); } if (DO && fieldbus_set_do(modbus)) { memcpy(gs.hal.DO1, image.DO1, sizeof(image.DO1)); memcpy(gs.hal.DO2, image.DO2, sizeof(image.DO2)); memcpy(gs.hal.DO3, image.DO3, sizeof(image.DO3)); } /* XXX Workaround for a unknown fieldbus error that seems to reset the * analog output: if this happens, force a AO1 refresh cycle */ if (! DI || ! AI) { workaround = TRUE; } AO = AO || workaround; if (AO && fieldbus_set_ao(modbus)) { g_debug("AO1 set to %d", image.AO1); gs.hal.AO1 = image.AO1; workaround = FALSE; } return ! quit; } gpointer fieldbus_thread(gpointer user_data) { modbus_t *modbus; gint64 period, scan, now, span, limit; if (gs.dry_run) { return NULL; } modbus = fieldbus_new(); if (modbus == NULL) { return NULL; } period = g_settings_get_uint(gs.settings, "periodo"); if (period == 0) { g_warning("Period set to 0: fallback to 20000"); period = 20000; } scan = g_get_monotonic_time(); limit = period; do { g_main_context_invoke_full(NULL, G_PRIORITY_HIGH, cycle_iteration, NULL, NULL); now = g_get_monotonic_time(); span = now - scan; if (span > limit) { limit = span; g_warning("Period exceeded: %.3f ms > %.3f ms" , limit / 1000., period / 1000.); } scan += period; if (now < scan) { g_usleep(scan - now); } } while (iteration(modbus)); fieldbus_free(modbus); return NULL; } #endif
The IO struct is just an image of the I/O status:
typedef struct { uint8_t DI1[4]; uint8_t DI2[16]; uint8_t DI3[16]; uint8_t DO1[4]; uint8_t DO2[14]; uint8_t DO3[14]; double AI1; uint16_t AO1; } IO;
-
RE: Sound card support
I would be really interested in the configuration used to build the
axon-kernel
, so I could try to rebuild the kernel and include the modules I need. Is there a place where I can get it? -
RE: Need Some sort of Jumpstart
Hi @knebb
I developed a fistful of programs in plain C on Neuron. I just linked againstlibmodbus
and called the loopback ModBUS server at 127.0.0.1, port 502 (hence no need to enable the EVOK service).
If you are not familiar I can send you some code to help you getting started. -
RE: Sound card support
@martin-kudláček said in Sound card support:
https://askubuntu.com/questions/28176/how-do-i-run-pulseaudio-in-a-headless-server-installation
Yes, this is exactly what I followed before writing the previous post. The only sink listed is a dummy output that I suppose you see too:
$ pacmd list-sinks 1 sink(s) available. * index: 0 name: <auto_null> driver: <module-null-sink.c> ... properties: device.description = "Dummy Output" device.class = "abstract" device.icon_name = "audio-card"
As stated above I'm pretty sure this is because of the sound kernel modules not loaded.
An alternative approach would be to install the 4.9.0 image without enabling it and try to force-load the needed sound modules with the current kernel. Do you think it is worth a try?
Don't be mistaken: I really appreciate your help so far and I know this could be considered a kind of corner case.