Shop OBEX P1 Docs P2 Docs Learn Events
QR Code generator — Parallax Forums

QR Code generator

ke4pjwke4pjw Posts: 1,169
edited 2024-07-09 15:27 in Propeller 2

Update#6: I have created a spin2 object for this code that sends the output via the builtin SEND() method, the output can be in ASCII, ANSI, or your own custom characters. An example file is provided that will display the QR code via debug or serial.

Hopefully I will have some C code to share soon for a QR Code generator. It does not rely on any 3rd party libraries.

Known bugs: It does not work with default optimization with flexprop.

Thanks for all the help in the C/C++ forum! Now that Chip has structures in spin2, I hope to convert this to spin2.

Update#3: The spin2 version is attached below. Runs with PropTool, has not been tested with anything else.

Updated#4: Updated the print_debug function to properly resize based on qr code version.

Update#5: Fixed print_debug to use bitmap RGB8 to display qr code

**Update#7: Added unicode. This makes for smaller QRCodes if the font for your displau supports it.

Update#8: Added code to display on an SSD1331

Update#9: Spin2 code is available in OBEX

Comments

  • evanhevanh Posts: 16,032

    Cool. Those blocks are one character wide and half height, right? So four combinations then.

  • ke4pjwke4pjw Posts: 1,169

    Yes sir! It also uses ANSI, so it will work with any output capable of interpreting ANSI codes.

  • ke4pjwke4pjw Posts: 1,169

    I found that inverting the colors made detection much faster on my phone.

  • ke4pjwke4pjw Posts: 1,169

    I have attached a zip with the code in the 1st post.

    Thanks to @Wuerfel_21 for helping me figure out some things with flexprop.

  • ke4pjwke4pjw Posts: 1,169

    I am attempting to convert this to spin2 and there is a "C-ism" here I don't understand. codeword_parameters is a multidimensional array of type char. What's going on here?

        message_parameters = codeword_parameters[qr_version];
    
  • JonnyMacJonnyMac Posts: 9,159
    edited 2024-07-01 00:38

    deleted

  • evanhevanh Posts: 16,032
    edited 2024-07-01 00:46

    That's a pointer assignment. It's what C is renowned for. message_parameters is declared as a pointer: unsigned char *message_parameters;
    Pointer assignment is same as "address of" in assembly, the "@" symbol in Spin/Pasm.

    Trivia:message_parameters = &codeword_parameters[qr_version][0]; would produce the same result, btw. The "&" is explicitly saying you want a pointer.

  • ke4pjwke4pjw Posts: 1,169

    @evanh Outstanding! Thank you! So if qr_version is 2, that would be the same as message_parameters = &codeword_parameters[2][0] ?

  • evanhevanh Posts: 16,032

    @ke4pjw said:
    @evanh Outstanding! Thank you! So if qr_version is 2, that would be the same as message_parameters = &codeword_parameters[2][0] ?

    Yes.
    The pointer, message_parameters, is being used as a layer step, to focus on the content of just one message.

  • ke4pjwke4pjw Posts: 1,169
    edited 2024-07-01 04:34

    Another weird "C ism". The is after the "for" statement, but before the open curly brace. Seems weird. Does this just run once, and then you enter the "for" loop?

    ``` for (i=0; i < message_length; i++)
    message[message_index++] := ((freetext[i] & 15) << 4) | ((freetext[i+1] & 240) >> 4);

    {
    Yadda Yadds Loop
    }
    ```

  • @ke4pjw said:
    Another weird "C ism". The is after the "for" statement, but before the open curly brace. Seems weird. Does this just run once, and then you enter the "for" loop?

    for (i=0; i < message_length; i++) message[message_index++] := ((freetext[i] & 15) << 4) | ((freetext[i+1] & 240) >> 4); { Yadda Yadds Loop }

    The loop includes only the line, that ends with the Semikolon.
    The contents of the curly braces has nothing to do with the loop.

  • ke4pjwke4pjw Posts: 1,169
    edited 2024-07-01 05:42

    Thanks @"Christof Eb." ! Though debugging, I just now figured that out. That set of curly braces isn't even needed. Oh boy. :smiley:

    I am at least making my way though it and debugging between my spin2 code agrees with the c debugging. Onward to the reedSolomon function! I am encouraged.

  • RaymanRayman Posts: 14,758
    edited 2024-07-01 21:15

    @ke4pjw This is neat, thanks for posting. I'd want to try it, but can't think of what do use it for :)... Maybe if the P2 is generating new web pages and you want share the link?
    Trying to find the use case here...

    You must have something in mind, right?

  • ke4pjwke4pjw Posts: 1,169

    Thanks @Rayman ! Yes, I have a specific use case. On my little light controllers, this will make for an easy to open the config web page with my phone. Just scan the qr code on the OLED display and it will connect you instantly to the controller.

    If you need an easy way to get up to 4K bytes of data out of the P2 into a mobile device, it would work for that as well.

  • RaymanRayman Posts: 14,758

    Data is interesting …. If I knew how to create a iPhone app that could read it. I imagine there is something on GitHub already for this…

  • JonnyMacJonnyMac Posts: 9,159

    If I knew how to create a iPhone app that could read it.

    MIT App Inventor (like Blockly for mobile apps) now works with iOS.

  • @JonnyMac said:

    If I knew how to create a iPhone app that could read it.

    MIT App Inventor (like Blockly for mobile apps) now works with iOS.

    Thank you Jon. That's incredibly helpful to me right now!

    Mike R...

  • ke4pjwke4pjw Posts: 1,169

    Yay, my spin code is now producing the qrcode finder pattern and I have a debug methodology to display it.

  • ke4pjwke4pjw Posts: 1,169

    Now the finder pattern, alignment pattern, timing pattern, dark module, and mask formatting produce the expected results. The "safe zones" around the qr code are not yet generated. In the original c code they are generated at display time. I am looking directly at the buffer in memory. I am having to duplicate the vertical lines to match the size of the horizontal lines, because for some reason that completely escapes me, they represent each black or white pixels as a byte. So wasteful!

  • ke4pjwke4pjw Posts: 1,169
    edited 2024-07-06 21:46

    Happy 4th, to those of us in the states! Well, it works! The first working spin2 version is attached in the first post.

    To Do:

    • Make it a proper object, not just a prototype program.
    • Add additional output types, such as the ANSI and "Unicode" output types in the original.
    • Restore comments from the original to acknowledge the work of B.J. Guillot that this is based upon.
    • Update style to be consistent. ( byte[@pointer][i] instead of byte[@pointer + i] )
    • Test it with SSD1331 and VGA/HDMI output
    • Stretch goal: Get the image buffer to use bits instead of bytes.

    Thank you all for your help! I have said it before and I will say it again. This is the best community forum on the Internet.

  • @ke4pjw said:
    because for some reason that completely escapes me, they represent each black or white pixels as a byte. So wasteful!

    On a PC-style processor, which the code you're using as a reference was likely written for, using a byte per pixel will likely run faster and use less memory overall than using bitfields. Thanks to how CPU instruction and data caching works, having fewer assembly instructions tends to result in a more performance improvement than having significantly more complex code that operates on slightly less data, and bit manipulations are significantly more complex than byte accesses. Thanks to virtual memory, programs can only allocate memory in multiples of 4KB or so, so allocating maybe 1 KB instead of an eighth of that often won't make any difference in how much virtual memory the OS actually allocates to the program. Thanks to how the disk cache works, since QR codes are generated relatively rarely compared to other operations, the QR generator program is more likely to remain paged in if its small, and anything is faster than fetching stuff from the disk again.

  • @Electrodude said:
    having fewer assembly instructions tends to result in a more performance improvement than having significantly more complex code that operates on slightly less data, and bit manipulations are significantly more complex than byte accesses.

    The opposite is actually true - most of the time the CPU execution ports are under-utilized because everything is waiting on memory transfers. You'd think the Read-Modify-Write cycle required to update a bit would be far slower than just writing a byte, but writing uncached memory actually causes the whole cache line to get read into cache first.

    The real reason more likely is that byte arrays are easier to work with and QR codes are so tiny that it doesn't matter (on platforms that aren't RAM-starved).

  • ke4pjwke4pjw Posts: 1,169

    @Electrodude I believe B.J. Guillot did develop this on a Mac, but the target system was a TI/99.

    In the original C code it allocates and deallocates the buffer.

    I updated the code in the first post. It will now render all 40 qrcode types in the print_debug() function.

  • ElectrodudeElectrodude Posts: 1,660
    edited 2024-07-04 20:27

    @Wuerfel_21 said:

    @Electrodude said:
    having fewer assembly instructions tends to result in a more performance improvement than having significantly more complex code that operates on slightly less data, and bit manipulations are significantly more complex than byte accesses.

    The opposite is actually true - most of the time the CPU execution ports are under-utilized because everything is waiting on memory transfers. You'd think the Read-Modify-Write cycle required to update a bit would be far slower than just writing a byte, but writing uncached memory actually causes the whole cache line to get read into cache first.

    The real reason more likely is that byte arrays are easier to work with and QR codes are so tiny that it doesn't matter (on platforms that aren't RAM-starved).

    Does it still have to read in the whole cache line if you're only writing? I would have expected it to do tricks with a bit-per-byte writeback mask or something to avoid having to wait for the cache line to come in if you never up reading anything.

    I remember reading something by perhaps Linus Torvalds about people underestimating the benefits of a whole chunk of code fitting in the i-cache (or perhaps only the page cache?), but I can't seem to find it now.

    I'm more trying to argue that using bytes rather than bits isn't necessarily "so wasteful" than whether it's actually any better vs. just not any worse.

    EDIT: And that premature optimization, when there are so many considerations that it's not at all clear what's actually better, is pointless.

    @ke4pjw said:
    @Electrodude I believe B.J. Guillot did develop this on a Mac, but the target system was a TI/99.

    While most of the considerations @Wuerfel_21 and I have been discussing don't apply to such a simple computer, unused memory is wasted memory, and that thing looks like it only runs one program at a time, meaning not using more memory that will be available whether or not it's actually used is worth the performance gain.

  • ke4pjwke4pjw Posts: 1,169
    edited 2024-07-04 21:10

    @Electrodude I understand what you are saying and I agree. In the context of spin2 and the P2, memory is precious, cycles aren't. You can't dynamically allocate memory in spin2.

    The way it works is it uses a set of pixel functions, img_set() and img_get(), to encode the message into the image buffer. It sets or gets bytes of 255 or 0. It must apply a mask based on what the pixels surrounding it are. It does not look at the message to do this, it looks at image buffer. (ie the reason img_get() exists.)

    I think I can simply add a layer there that just manipulates bits. Will it be slower? Probably. Will it be noticeable? Probably not. Will it reduce the allocated memory 8-fold? Yep. I think it is worth it. Seems silly to allocate 31K when you can get by with 4K at the cost of a few cycles. I can't imagine generating QR codes to be time sensitive.

  • @Electrodude said:
    Does it still have to read in the whole cache line if you're only writing? I would have expected it to do tricks with a bit-per-byte writeback mask or something to avoid having to wait for the cache line to come in if you never up reading anything.

    Yes, it always needs to read in the cache line before writing (there are special "non-temporal" vector store instructions that don't...). I think that makes it easier to keep everything in sync between multiple cores with their own cache. This doesn't actually cause any waiting usually, since multiple write instructions can be queued up and matching read instructions will read from this queue.

    While most of the considerations @Wuerfel_21 and I have been discussing don't apply to such a simple computer, unused memory is wasted memory, and that thing looks like it only runs one program at a time, meaning not using more memory that will be available whether or not it's actually used is worth the performance gain.

    I disagree. Unused memory is potential. If all your memory is used up, you can't add any more features. Memory, unlike time, is a finite resource.


    Spin2 has nice bit operations, so making it use bit arrays probably has small impact on overall code size and performance.

  • ke4pjwke4pjw Posts: 1,169

    I have created a spin2 object for this code that sends the output via the builtin SEND() method, the output can be in ASCII, ANSI, or your own custom characters. An example file is provided that will display the QR code via debug or serial. See the first post for the attachment. I will be adding this to the ObEx soon. Please feel free to take a look and throw rocks at my code. :)

  • ke4pjwke4pjw Posts: 1,169
    edited 2024-07-09 01:06

    I created an intermediate object that will allow the code to display on the SSD1331 using my driver. Most likely this could easily be adapted to work with Jon's driver as well. With the SSD1331 it supports QR Code versions 1-10. I will post code this evening. Code posted in the first post.


Sign In or Register to comment.