vendredi 25 avril 2025

Get 4K 30fps from 4-lane IMX283 StarlightEye

Stock bookworm with imx283 kernel module will only get you up to this stage:
pi@Pi5Cam1:~/Src/Official/RaspberryPi/rpicam-apps $ libcamera-hello --list-cameras
Available cameras
-----------------
0 : imx283 [5472x3648 12-bit RGGB] (/base/axi/pcie@120000/rp1/i2c@80000/imx283@1a)
Modes: 'SRGGB10_CSI2P' : 5568x3094 [21.31 fps - (0, 285)/5472x3078 crop]
5568x3664 [17.99 fps - (0, 0)/5472x3648 crop]
'SRGGB12_CSI2P' : 2784x1828 [35.55 fps - (0, 0)/5472x3648 crop]
5568x3664 [17.99 fps - (0, 0)/5472x3648 crop]


Go to: https://www.willwhang.dev/Solar-eclipse-2024/

Modify the Device Tree Source: edited the file ./arch/arm/boot/dts/broadcom/rp1.dtsi.
Specifically, change RP1_PLL_SYS and RP1_CLK_SYS to 300000000.
Recompile and Install the Kernel:
After making the changes, recompiled the kernel to apply these new settings.
Update Libcamera Configuration:
In the Libcamera directory, modify ./src/ipa/rpi/controller/controller.cpp.
Under the “pisp” section, change:
.minPixelProcessingTime = 1.0us / 380
to
.minPixelProcessingTime = 1.0us / 580.
Recompile and Install Libcamera: After updating the settings, recompile and reinstalle Libcamera to ensure the changes were implemented.
Now you should get:

pi@Pi5Cam1:~/Src/Official/RaspberryPi/rpicam-apps $ libcamera-hello --list-cameras
Available cameras
-----------------
0 : imx283 [5472x3648 12-bit RGGB] (/base/axi/pcie@120000/rp1/i2c@80000/imx283@1a)
Modes: 'SRGGB10_CSI2P' : 5568x3094 [30.17 fps - (0, 285)/5472x3078 crop]
5568x3664 [25.48 fps - (0, 0)/5472x3648 crop]
'SRGGB12_CSI2P' : 2784x1828 [51.80 fps - (0, 0)/5472x3648 crop]
5568x3664 [21.40 fps - (0, 0)/5472x3648 crop]


lundi 17 mars 2025

Why IMX477 2x2 sensor binning mode is a crime and should be rendered illegal (now that we have a way around it).

 OK. That's a bold statement. So, we need proofs. Here we go !

This first picture is captured using the infamous 2028x1080 mode:

2028x1080 aka 2x2 sensor binning

 

Now, the same picture, with the same lens and same setting, but captured with the sensor full resolution and scaled down to 2028x1080:

4056x2160 scaled down to 2028x1080

OK, at those sizes, depending on what device you are reading this, it might not be obvious (using a 28" 1080p TV as screen, at less than 1 meter, it is way too obvious), so here is a 480x270 crop inside both pictures to make my point clearer (pun intended):

2x2 binned zoom

full sensor scaled down zoomed

 

There is so much details lost on the 2x2 sensor binned version that it manages to hide the chromatic aberration from the lens. Both pictures where captured with an IMX477, connected to a 2GB Pi 5, with an HSs18x5.5BMD lens, with the following settings: Zoom=5.5mm, Iris=f/5.6, Focus=Infinity

Rule of thumb: always capture the full sensor resolution. The "debayer" process will be able to recover every possible detail. Then, scale down the image. By binning the "pixels" at Bayer's level, sharp edge details are lost forever.

OK, but if you are using the Pi HQ Camera for video on a Raspberry Pi (even the at-the-time-of-writing new Pi 5 with its full-blown 4-lane MIPI interface), you have no choice but to stick with the 2028x1080 mode, right ?
The basic 2-lane MIPI will clamp you to about 10 fps. But if you read the IMX477 datasheet, it can output 4K 60fps on 4 lanes, so 4K 30fps should , at least, be possible with only 2 lanes. Well is turns out the IMX477 does its magic thank to a 2.1Gbps speed on the MIPI lanes, while on Raspberry Pi the sensor CSI-2 interface is clocked much lower (900MBps, at least for EMC and power considerations). So, maybe someone had hacked its way through the speed limit.

Then, looking back at the Raspberry Pi forum for any news on 4-lane IMX477 support (I perfectly understand the Foundation has no reason to support it, because their HQ Camera has only 2 lanes), I indeed stumbled upon this: https://forums.raspberrypi.com/viewtopic.php?t=371216

The results are incredible:

pi@bookworm64:~ $ rpicam-hello --list-cameras
Available cameras
-----------------
0 : imx477 [4056x3040 12-bit RGGB] (/base/axi/pcie@120000/rp1/i2c@80000/imx477@1a)
    Modes: 'SRGGB10_CSI2P' : 1332x990 [254.45 fps - (696, 528)/2664x1980 crop]
                             2028x1080 [146.41 fps - (0, 440)/4056x2160 crop]
                             2028x1520 [104.05 fps - (0, 0)/4056x3040 crop]
                             4056x2160 [38.87 fps - (0, 0)/4056x3480 crop]
                             4056x3040 [27.62 fps - (0, 0)/4056x3040 crop]
           'SRGGB12_CSI2P' : 1332x990 [212.04 fps - (696, 528)/2664x1980 crop]
                             2028x1080 [122.00 fps - (0, 440)/4056x2160 crop]
                             2028x1520 [86.70 fps - (0, 0)/4056x3040 crop]
                             4056x2160 [32.39 fps - (0, 0)/4056x3480 crop]
                             4056x3040 [23.02 fps - (0, 0)/4056x3040 crop]

Now, using this overclocked version of the imx477 driver, you can get 4056x2160x12 at a stable 30fps ! Then scale down to 1080p and H264 encode the result, for as sharp a 1080p can be.
And if you don't care about image quality, and have a lot of light, you can even play with the 10-bit 1332x990 mode, running flawlessly at 240 fps (don't try to H264 encode this, though, use MJPEG instead).

mardi 5 janvier 2021

Circular Doubly Linked List: double the power, squared !

From linked list to doubly linked list to circular doubly linked list.


Intro


In the following article, we will introduce a powerful data-structure and provide implementations in C for the most common cases.


Linked List


Linked lists are structures with pointers from link to link (often in a field of the structure called next (for obvious reason)). The list is implemented by having a pointer to the first link, then each link keeps a reference to the next link. The last link typically has a NULL pointer as reference to the next, indicating the end has been reached. Insertion at the front of the list only cost 2 pointer manipulations. They can be useful but have a major limitation: removing a link requires to look for the link and maintain a reference to the previous link while doing so. This can prove very inefficient for long lists. The basic solution to this pitfall would be to have a reference to the previous link inside the link itself.

Doubly Linked List (aka DLL not to be confused with DLL or DLL).


As explained above, the linked list deletion operation (among other) can be made more efficient by keeping a reference to the previous link inside the link itself. In fact, its cost become O(1) instead of O(n). Each link now gets a prev field pointing to the previous link. The first link has a NULL pointer in prev because no link exists before it. Like before, the last link has a NULL in next. Now insertion typically costs 4 pointer manipulations, and removing a link only 2 (and has the side effect of being reversible with the same cost if you are backtracking or something equivalent), if the link is known (else it still needs to be looked for). While this is already a great improvement, the cases of empty list and first element removal / insertion still require special attention. This is both costly in term of pointer manipulation and prone to implementation errors. The solution to those issues is to avoid the empty list situation and more or less remove the 'first link' situation in the process. And the way to achieve this is rather simple.

Circular Doubly Linked List


Remember how we previously had a variable pointing to the first element in the list, with the other links keeping reference of one other. This requires special attention when the first element is involved or when the list is or becomes empty. To eliminate those requirements, we change the way the list is referenced: instead of using a pointer to the first link, we use a link as a reference. More specifically, we only use the next and prev fields of this link. This has the following positive side effects:

- the list if never empty (it always contains at least one link)
- no element is at any specific place in the chain
- insertion between already existing link is the way to go and costs 4 pointer operations
- removing a known link only costs 2 pointer operations
- the list can be searched in both direct and reverse order at the same cost
- a reference to the last item is always available
- full search can start at any link

Yet, to have all of this auto-magically working, some care needs to be taken:

- the new link referencing the rest of the list must be properly initialized
- the 'empty list' test must be changed (to list->next == list)
- a search is complete when next points to the start element of the search (remember searches don't have to begin at the start of the list any more (but be sure to have a special value in the fake list link))

Multidimensional Circular Doubly Linked List


The power of the CDLL can be extended to more than one dimension, by the virtue of insertion between links and removal of a known link being reversible (if carefully done, though). For example, a 3-dimension structure can be devised with next and prev being transformed to left/right, up/down and front/back. For a real use case of a 2-D CDLL, see Knuth Dancing Links paper https://arxiv.org/pdf/cs/0011047.pdf, or any Youtube video on the subject (for example the one-and-a-half hour https://www.youtube.com/watch?v=_cR9zDlvP88). You can even search for "dancing links Sudoku" for application to Sudoku solving.

So, how to do it ?


The structure for a CDLL is the same as for a "regular" DLL, excepts we use a link to keep reference to the list (specifically to the first and last element of the list). Let's first declare our link structure. Self pointing type declaration has already been the source of numerous religion wars, so it won't be discussed here (anyway, even simply linked list requires this). In the following examples, the term "node" will be used because not all links are born equal.

typedef struct node {
struct node *prev;
struct node *next;
const char *tag;
} node_s;

static void cdll_init(node_s *node){
node->prev = node;
node->next = node;
}

For example, declaring and initializing a CDLL (i.e. creating an empty list) is done like this:

node_s list;
cdll_init(&list);

Not that complicated. OK, time to test if the list is empty. No NULL-pointer here, simply test if the node points to itself.

int cdll_is_empty(node_s *node){
    return(node->next == node);
}

Not much more complicated than a NULL-pointer test. An empty list is not very useful, so let's add elements. Remember, the only actual insertion operation is "insert between". So we need to provide both the previous and next element to be able to do any insertion. This seems overly complicated, but isn't ! Let looks at the common insertions in a list and how to translate them to CDLL "between" insertion. We start with the basic insertion at head of list, so after the list node itself and the currently "first" element.

void cdll_insert_head(node_s *list, node_s *to_add){
    cdll_insert_between(list, to_add, list->next);
}

Pretty elegant. But what if the list was empty ? Well, in that case, list->next points to itself so we add between list and list. And that's as simple as that. Want to insert an element at the tail ? Just as simple.

void cdll_insert_tail(node_s *list, node_s *to_add){
    cdll_insert_between(list->prev, to_add, list);
}

Inserting before or after a known element is just as easy ! In fact it's the same as before with the provided node instead of the fake list node.

void cdll_insert_after(node_s *known, node_s *to_add){
    cdll_insert_between(known, to_add, know->next);
}

void cdll_insert_before(node_s *known, node_s *to_add){
    cdll_insert_between(known->prev, to_add, know);
}

And now, the actual implementation of insert_between().

void cdll_insert_between(node_s *before, node_s *to_add, node_s *after){
    to_add->next = before->next;
    to_add->prev = after->prev;
    before->next = to_add;
    after->prev  = to_add;
}

Removing a node is even simpler.

void cdll_remove(node_s *to_remove){
    to_remove->next->prev = to_remove->prev;
    to_remove->prev->next = to_remove->next;
}

Notice how we didn't initialize prev and next inside to_remove. That's the trick that allows to re-insert the node at the exact same place in the list.

void cdll_insert_back(node_s *to_insert){
    to_insert->prev->next = to_insert;
    to_insert->next->prev = to_insert;
}

jeudi 23 janvier 2020

Split-Ring Epicyclic Gear (or differential epicyclic gearset, compound-planet epicyclic, (or pick your own name))

Looking for a massive reduction gearbox, in a relatively small volume, 3D-printable, I googled for epicyclic gear on thingiverse. While achieving rather impressive reduction-radio in limited space, this was not enough. Fortunately, I came across a 3D-printable gear set that was a split ring arrangement, achieving massive reduction into the same size as 2 stacked regular epicyclic gearbox (1:246 in the proposed design, https://www.thingiverse.com/thing:7390).
OK, let's rewind a little ! First, what are epicyclic gearbox and how do they achieve good reduction-ratio in small space ? The basic part for such a gearbox are :
- an input gear called the "sun"
- 2 or more gears revolving around the sun, called "planets"
- a ring containing the "solar system" formed by the aforementioned gears.

In the following picture, the sun is yellow, the planets are blue and the ring is grey.

Figure 1: Epicyclic gear-set.

For my application, viewing through the shaft is mandatory, so the sun is a hollow gear, but it doesn't have to be. This gearbox has a 21-tooth sun, 24-tooth planets, and, as a consequence, a 69-tooth ring. Yes, as the gears are connected to each other, they share the same tooth-size (aka modulus), so the number of teeth of the ring is linked to the number of teeth of both the sun and the planets:

Nring = Nsun + 2 * Nplanets

where Nxxx is the number of teeth of gear xxx.

The usual arrangement for an epicyclic reduction gearbox is to connect the planets together and create a shaft co-linear with the input shaft running the sun, the ring is held fixed and the sun is driven. In that case, the reduction ratio would be:

Reduction Ratio = 1 + Nring / Nsun

So, with Nring = 69 and Nsun = 21, we get 1+69/21=90/21=30/7, so around 4.2857. This formula is linked to the number of teeth the sum need to move a planet so it make a complete revolution. The planet size doesn't seem to come into play, but remember the number of teeth of the ring is connected to the number of teeth of both the sun and the planet. The reduction ratio could be written as:

Reduction Ratio = 1 + (Nsun + 2 * Nplanet) / Nsun

Obtaining the same ratio with a regular gear-set would require a 90-tooth gear driven by the 21-tooth sun, which takes more space and has the added default of radial load on the bearing.
Yet, the reduction ratio is still not that big and the order of magnitude or reduction I'm looking for would require cascading 3 or 4 of these gear-set. While doable (and done in real world application), this is not the way I went.

If you look at Figure 1, you see that the teeth of a planet don't move relative to the ring when touching the ring (non-slipping contact). What if we now add another planet on top of each input planet ? If it has the same number of teeth as the input planet, this will do nothing, but, if it has a different number of teeth (and a different diameter because we keep the same tooth-size for all gears), there will be a small motion relative to the input ring. Let's assume the difference in tooth-count is -1 or +1 (higher values are possible but will defeat the purpose of obtaining massive reduction ratio). Whenever the input planet has done a complete turn on itself (not to be confused with a complete revolution around the sun), the output planet has done exactly the same, but its outer side has moved one-more or one-less tooth relative to the input ring. And that's the trick: for every turn of a planet on itself, and external ring will move one tooth (forward or backward depending upon the sign of delta). So the reduction ratio is now the product of sun-to-planet ratio and planet-to-output-ring ratio.


Animation 1: split-ring epicyclic

In the above animation, the grey bottom ring is fixed, the yellow sun is the driving gear, rotating the hard-to-see blue bottom "input" planet, attached to  the top red "output" planet (forming a compound-planet), driving the light-blue top ring. The compound-planet rotate around the sun is the same direction as the sun, but in the opposite direction around its own centre. With this configuration where the upper part of the compound has one more tooth than the lower part, the output ring (grey) goes in the opposite direction. This is the kind of trick also used with the Harmonic Drive system.

jeudi 28 novembre 2019

Build OpenSCAD on the Raspberry Pi 4 running Raspbian GNU/Linux 10 (buster)

Slighty improving upon the CPU power compared to the 3B+, the 4B is mostly of interest because of the amount of RAM available (up-to 4 GB). So I'm continuing on investigating the possibility to build and run all the software I need on a CAD journey. This article is a follow-up on the Pi 3B+ article, so both articles look very similar. First thing first, let's try to build openscad (no package are available for raspbian buster today (2019/11/28)). For reliable result, notice I switched to tag openscad-2019.05 (using "git checkout openscad-2019.05" inside openscad folder once the git clone and submodule init are done).

Long story, short: build against Qt4 ! Else you'll run into the Open GL ES include issue.

The basic instructions are available at https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_on_Linux/UNIX. This includes getting the dependencies for the project, using:

$ ./scripts/uni-get-dependencies.sh

But, checking the dependencies with:

$ ./scripts/check-dependencies.sh

shows errors. I had to manually install a few more packages. Basic method:

$ sudo apt-get install *name_of_missing_package*

will give you every packet with a name containing what you are looking for, just pick the right one (usually lib_something-dev). Go on until no error are given by the check-dependencies script as well as:

$ qmake-qt4 openscad.pro

Last step: I removed multimedia from the required package in openscad.pro. For this, open openscad.pro in your favorite text editor, look for "multimedia" and remove it.

Working with a 4GB version of the Pi 4, I disabled swap (sudo swapoff -a) and allowed up-to 3 parallel compile.

$ make -j 3 2>&1 | tee make.log

If everything went OK, you can run openscad directly from here (aka the build directory), or, better, install it machine-wide with:

$ sudo make install

And that's it. Please be aware that this will install things in /usr/local/.

That's it ! Faster said than done !

lundi 31 décembre 2018

Building OpenSCAD on a Raspberry Pi3B+ (because you can).

Having a 1.4 Ghz quad core ARM processor, the 3B+ version of the Raspberry Pi is pretty powerful (for a nano-computer that is).
So I'm investigating the possibility to build and run all the software I need on a CAD journey. The first articles will target the 3D printing suite I use: OpenSCAD (with GVim as external editor) for the modeling part and Slic3r for the slicing process (I own a P3Steel with a Marlin firmware).First thing first, let's try to build openscad (no package are available for raspbian Stretch today (2018/12/27)).

Long story, short: build against Qt4 !

The basic instructions are available at https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_on_Linux/UNIX. This includes getting the dependencies for the project, using:

$ ./scripts/uni-get-dependencies.sh

But, checking the dependencies with:

$ ./scripts/check-dependencies.sh

shows errors. I had to manually install a few more packages. Basic method:

$ sudo apt-get install *name_of_missing_package*

will give you every packet with a name containing what you are looking for, just pick the right one (usually lib_something-dev). Go on until no error are given by the check-dependencies script as well as:

$ qmake-qt4 openscad.pro

When trying to build such C++ monster as a CAD tool, having the maximum amount of available memory is mandatory. For that purpose, I selected a 64M GPU memory split within raspi-config, and increased the swap file size to 2048M (twice the amount of RAM). This is done by modifying the configuration file:

$ sudo vi /etc/dphys-swapfile

Changing the size parameter to:

CONF_SWAPSIZE=2048

Actual reconfiguration requires 3 steps:

$ sudo dphys-swapfile swapoff
$ sudo dphys-swapfile setup
$ sudo dphys-swapfile swapon

Of course you need at least 2GB available of the SD Card. Now you are ready to build. I like to get all the output in a log, for further investigation in case of failure, so I use the following command:

$ make 2>&1 | tee make.log

This will take time: C++ will eat up a LOT memory (and CPU as well), meaning swap will be used (yes 2GB would be the correct RAM size for that purpose), so extra slowness is to be expected.

If everything went OK, you can run openscad directly from here (aka the build directory), or, better, install it machine-wide with:

$ sudo make install

And that's it. Please be aware that this will install things in /usr/local/.

That's it ! Faster said than done !

Get 4K 30fps from 4-lane IMX283 StarlightEye

Stock bookworm with imx283 kernel module will only get you up to this stage: pi@Pi5Cam1:~/Src/Official/RaspberryPi/rpicam-apps $ libcamer...