Hi, Habr!
Last time I wrote about how it is easy to connect our nanocomputer to something executive (Christmas tree lights, for example) and deploy an environment to build C / C ++ programs for it. Today is the second part, on how to write a C program for OpenWRT and, accordingly, Black Swift.
- Garland, Black Swift connection and build environment under OpenWRT in C / C ++
- Control program in C and direct and fast work with GPIO
- Web interface and application for Android
')
I will note two points. First, I will not talk about the role of the main function and the #include directives - as I said before, now I am writing for people who are familiar with programming, but do not know which side to approach such a thing as a computer for embedded applications. More precisely, today's text will be more likely for those already approaching - so I will focus on a couple of interesting points, such as working with logical input-output lines (GPIO) and microsecond times.

Secondly, of course, it is not necessary to write on C. Under OpenWRT there is Perl, Python and even Node.js - with them you don’t need, obviously, no environment for building software, just fill in @ launch.
Quick and easy work with GPIO
We have on hand - a Christmas tree garland, controlled by four half-bridge drivers working in pairs: one pair sets the polarity of one branch of the garland, and the polarity determines the color of the glow. Obviously, to work with this, we need pulse-width modulation, and quite specific - asymmetric, that is, during each period each polarity can be switched on for different times (the simplest case - by giving only one polarity, we get only one color of the garland) .
This scheme prevents the use of a ready-made PWM module (there is one in OpenWRT) - it gives a symmetrical signal, that is, the maximum that can be achieved with it is to adjust the brightness of the whole garland in all colors simultaneously, without the ability to control colors independently. Therefore, the PWM must be done by yourself.
PWM is, at a basic level, just a quick jerking of the processor, in the simplest case, in this cycle:
while (true) { if (value > 0) gpioSet(GPIO_NUMBER, 1); for (int i=1; i<=100; i++) if (value == i) gpioSet(GPIO_NUMBER, 0); }
The meaning, I think, is obvious: with the value “value” we set the required duty cycle, which in our case is expressed in the brightness of the LED lights of the string (changing polarity and color management until we touch).
The standard content of the gpioSet function (int gpio, int level) in Linux is GPIO jerking through the standard sysfs interface: to set output N to one, write one to the / sys / class / gpio / gpioN / value pseudo file. Before that, you must write the N number in / sys / class / gpio / export so that gpioN is available to the user, and also the word “out” in / sys / class / gpioN / direction to set the mode of this GPIO to “output”.
Descriptions of this procedure - thousands, here, for example,
one of them .
void gpioSet(int gpio, int value) { sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio); fd = open(buf, O_WRONLY); sprintf(buf, "%d", value); write(fd, buf, 1); }
The method is simple, universal (it is difficult to find a language from which it is not possible to write to the file), but it is
very slow . If using it to control our garland, then as a result, pulling four legs (two pairs of half-bridges) will take so much time that the PWM frequency on the Black Swift with its 400-MHz processor will be about 100 Hz, and at a small brightness the LEDs will be clearly and it is unpleasant to flicker - there in general will begin the pass of steps.
Fortunately, there is a better way. Even more fortunately, it’s not that difficult, it’s simpler. Rectilinear.
void gpioSet(int gpio, int value) { if (value == 0) *(GPIO_ADDR + 4) = (1 << gpio); else *(GPIO_ADDR + 3) = (1 << gpio); }
The trick is that GPIOs in the processor are controlled through registers — all operations with them are done through them. And the registers are displayed in normal memory. And the usual memory is available to us directly through the usual device / dev / mem. Accordingly, all we need is to display the necessary small piece of / dev / mem in memory (memory in memory ... well, okay) with the help of mmap, and then tug the necessary bits in this piece.
if ((m_mfd = open("/dev/mem", O_RDWR) ) < 0) return -1; m_base_addr = (unsigned long*)mmap(NULL, GPIO_BLOCK, PROT_READ|PROT_WRITE, MAP_SHARED, m_mfd, GPIO_ADDR); close(m_mfd);
The mapping of registers to memory is described in the datasheet per processor, in the case of the AR9331, the addresses start with GPIO_ADDR = 0x18040000 (
page 65 ). We are also interested in the addresses at +3 and +4 to the base address - writing the unit to the bit corresponding to the GPIO number sets the GPIO to 1 at the first address, and resets to 0 at the second address (if anything, there is also a register at +2, which maybe reset, and install GPIO - you need to write either 0 or 1 to the correct bit in it). The GPIO direction is set by the bits at the base address - 1 for the output, 0 for the input.
Nota bene: some GPIOs are multifunctional, this is defined by a separate register - and until, for example, you turn off the UART on GPIO 9, it will not work as a regular input / output. In addition, GPIO from 13 to 17 can not be used as inputs.
Speed? The twitching of four GPIOs in dual asymmetric PWM is about 4.5 kHz. Against about 100 Hz when working with sysfs, I remind you.
PWM period adjustment with nanosleep
Obviously, we don’t need such a speed of controlling the garland - everything that is above 100 Hz is perfect for us, especially if it doesn’t “lose” clock cycles at small brightness (and it will not work with direct work with GPIO). It is necessary to introduce a delay. Standardly, short delays are entered using the nanosleep (& tw, NULL) function:
struct timespec tw; tw.tv_sec = 0;
Theoretically, here we should get a delay of 10 µs for each PWM clock, a total of 100 clock cycles - a total of 1 ms, or a PWM frequency of 1 kHz (not taking into account the overhead of pulling the leg). Compile, run ... and get about 140-150 Hz.
The problem is that the minimum regularly serviced nanosleep period in OpenWRT and on such a processor is about 60 μs. That is, even if you pass tw.tv_nsec = 0 to the function, it will still slow down the thread for 60 µs.
Fortunately, there is a primitive, not very accurate, but working way to deal with this: calling nanosleep (NULL, NULL) takes about 3 µs.
void nsleep(unsigned long nsecs) { if(nsecs == 0) { nanosleep(NULL,NULL); return; } struct timespec ts; ts.tv_sec=nsecs / 1000000000L; ts.tv_nsec=nsecs % 1000000000L; nanosleep(&ts,NULL); } const int nsleep0_factor=3000; const int nsleep1_factor=70; void _usleep(unsigned long usecs) { if (usecs == 0) return; unsigned long value = (usecs*1000) / nsleep0_factor; if (value > nsleep1_factor) { nsleep((value-nsleep1_factor) * nsleep0_factor); value = value % nsleep1_factor; } for (unsigned long i=0; i < value; ++i) nsleep(0); }
As a result, having called _usleep for less than 70 µs, we will call nanosleep not in a regular way, but simply scroll through the nanosleep (NULL, NULL) many times, each call of which will take 3 µs. Roughly, but we’re more precise for our purposes and not necessary (if you need high-quality PWM, you need to do it all the same in hardware or software on a system where you guarantee real-time mode - for example, hook up a normal ATTiny to Black Swift via UART).
Well, in general, the basic building blocks are ready - we can make quite stable software PWM with a frequency of a hundred or two hertz.
We can also recall that with this PWM we control two pairs of half-bridges, but this is already trivial:
for (int i = 0; i < 100; i++) { for (int j = 0; j < 2; j++) { if (i == floor((float)gpioValuePos/brightness)) { gpioSet(gpio[2*j], GPIO_OFF); gpioSet(gpio[2*j+1], GPIO_OFF); } if (i == (100 - floor(((float)gpioValueNeg/brightness)))) { gpioSet(gpio[2*j], GPIO_OFF); gpioSet(gpio[2*j+1], GPIO_ON); } } _usleep(PWM_PERIOD); }
Where gpioValuePos and gpioValueNeg are the values ​​of the two polarities (with the condition that their sum should not exceed 100, of course), brightness - this is what we laid beforehand on the possibility of adjusting the brightness of the whole garland at once. Installation on two GPIOs of the same level is equivalent to disabling the garland.
Bells and whistles
What else do we need from the app controlling the garland?
First , the brightness should somehow vary in time, and it is better that there are several programs. You can make your own scripting language, I entered easier - drove a three-dimensional array into the code, inside which 6 programs are stored, in each 4 stages, in each two pairs of numbers - the initial brightness value of one polarity and the speed of its change. Yes, both branches of the garland work synchronously for me.
int prg[PROGRAMS][4][4] = { { {0, 1, 0, 0}, {99, -1, 0, 0}, {0, 0, 0, 1}, {0, 0, 99, -1} }, { {0, 1, 0, 0}, {0, 0, 0, 1}, {0, 1, 0, 0}, {0, 0, 0, 1} }, { {99, -1, 0, 1}, {0, 1, 99, -1}, {99, -1, 0, 1}, {0, 1, 99, -1} }, { {99, 0, 0, 0}, {99, 0, 0, 0}, {99, 0, 0, 0}, {99, 0, 0, 0} }, { {0, 0, 99, 0}, {0, 0, 99, 0}, {0, 0, 99, 0}, {0, 0, 99, 0} }, { {49, 0, 50, 0}, {49, 0, 50, 0}, {49, 0, 50, 0}, {49, 0, 50, 0} } };
That is, for example, the first program - the brightness of the channel "+" gradually increases, then it gradually decreases, then the brightness of the channel "-" also increases and decreases. Simply put - one color lights up and goes out, then the second lights up as well. And in the latter all LEDs of all colors are constantly shining.
Secondly , since we have a whole Black Swift here, let's make control via Wi-Fi? And then the smartphone application, in the end, what kind of geek are you, if you even have a Christmas tree garland without your IP address? In general, you need to make an interface to a regular web.
Technically, the easiest way to do management is through UNIX-socket, a pseudo-file in which commands can be pushed even from PHP, even from the command line (with the socat utility).
mode_t mask = umask(S_IXUSR | S_IXGRP | S_IXOTH); int s, s2, len; unsigned int t; struct sockaddr_un local, remote; if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { printf("Socket error\n"); return -1; } int flags = fcntl(s, F_GETFL, 0); fcntl(s, F_SETFL, flags | O_NONBLOCK); local.sun_family = AF_UNIX; strcpy(local.sun_path, "/tmp/treelights.sock"); unlink(local.sun_path); len = strlen(local.sun_path) + sizeof(local.sun_family); if (bind(s, (struct sockaddr *)&local, len) == -1) { printf("Socket bind failed\n"); return -1; } if (listen(s, 5) == -1) { printf("Socket listen failed\n"); return -1; } umask(mask);
Everything here is actually a template, I will only explain that operations with umask at the beginning and end are needed on systems where the web server and our application work under different users. In OpenWRT, by default this is not the case, everything is under root. The non-blocking work of a socket is also fundamental - otherwise the very first access to it will result in everyone getting up and waiting until something falls into the socket.
Inside our PWM we insert the processing code that falls into the socket:
s2 = accept(s, (struct sockaddr *)&remote, &t); if (s2 > 0) { int i = recv(s2, sockinput, 25, 0); sockinput[i] = 0;
The idea, I think, is understandable. We throw in the socket "brightness 2" - we get halving the brightness of the garland. Processing of any other commands is added in the same way.
What else to add? For the convenience of manual launch when debugging - let the program respond correctly to Ctrl-C and other requests to move:
do_exit = 0; static struct sigaction act; sigemptyset (&act.sa_mask); act.sa_flags = 0; act.sa_handler = SIG_IGN; sigaction (SIGHUP, &act, NULL); act.sa_handler = cleanup; sigaction (SIGINT, &act, 0); act.sa_handler = cleanup; sigaction (SIGTERM, &act, 0); act.sa_handler = cleanup; sigaction (SIGKILL, &act, 0); while (!do_exit) {
And add the function:
void cleanup(int sig) { do_exit = 1; }
Now the signal arriving from the OS with a request to interrupt will switch the do_exit variable to 1 - and the PWM cycle will end (do_exit, of course, would be nice to declare as a global variable).
In general, that's all. We have ready-made bricks for building asymmetrical PWM with brightness control, several work programs and web control.
The result is here:
https://github.com/olegart/treelights , including a Makefile for OpenWRT and for the project itself (this one is no different at all from the usual Makefiles for building software under any Linux). Video on the applications of our readers will be in the third part (although that you, the garlands have not seen, or what?).
Of course, everything said applies to any router and nanocomputer on OpenWRT, with one caveat: for Black Swift, we assemble a core with a 1000 Hz timer against the usual 100 Hz and with preemptive multitasking enabled. Without this - on standard cores - PWM may be slower or less stable under any load on the system.
However, about the assembly of its firmware for OpenWRT - another time.