{"id":499421,"date":"2026-10-20T14:39:47","date_gmt":"2026-10-20T11:39:47","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=499421"},"modified":"2026-10-20T14:39:47","modified_gmt":"2026-10-20T11:39:47","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=499421","title":{"rendered":"Implementation of the HDA Link interface on RP2040"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1092376\/\" rel=\"noopener nofollow\">\u0420\u0443\u0441\u0441\u043a\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f<\/a><\/p>\n<p>When it comes to outputting or inputting audio to or from a microcontroller \u2014 if the system is even slightly more complex than a simple beeper \u2014 <strong>I\u00b2S<\/strong> is almost always the standard. Many microcontrollers have the necessary peripherals, and a wide range of compatible audio DACs and ADCs is available. However, this typically means a maximum of 2 channels for audio output and 2 for input (and even then, not always simultaneously).<\/p>\n<p>On the other hand, there is a whole world called <a href=\"https:\/\/en.wikipedia.org\/wiki\/Intel_High_Definition_Audio\" rel=\"noopener nofollow\">High Definition Audio<\/a>, developed by Intel in 2004. It is highly likely that the sound on the PC or laptop you are using to read this article is transmitted via this interface. HDA supports 15 input channels and 15 output channels, with a bit depth of up to 32 bits and a sampling rate of up to 192\u00a0kHz. Admittedly, not all at once \u2014 the channel bandwidth is limited to just over 46 Mbit\/s per audio output channel; nevertheless, its capabilities are much broader than those of I\u00b2S.<\/p>\n<p>But I haven\u2019t come across any microcontrollers that natively support HDA. For some reason, it\u2019s generally assumed that only chipsets (south bridges) \u2014 that is, the \u201cfull-fledged\u201d system logic of a PC \u2014 can act as hosts. You could also cobble something together using an FPGA, but for many \u201cknee-on-the-floor DIY\u201d enthusiasts, that\u2019s too high a hurdle.<\/p>\n<p>I thought that way for a long time, too. And at first, the news about the release of the RP2040 just went right over my head \u2014 I figured, \u201cJust another microcontroller; there are thousands of them.\u201d That is, until I stumbled upon the <a href=\"https:\/\/github.com\/AlexEkb4ever\/ZX_RGBI2VGA-HDMI\" rel=\"noopener nofollow\">ZX_RGBI2VGA-HDMI<\/a> project, where this microcontroller captures an RGB video signal from a ZX Spectrum in real time and outputs it as an HDMI (or rather, pseudo-HDMI) signal required by modern monitors.<\/p>\n<p>And I thought: Why not try an HDA link on it? <s>Well, if not, then not, I told myself,<\/s> and got to work \u2014 luckily, I still had a board with that codec, the ALC889, left over from my previous attempt.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/36\/e7\/0c\/36e70c374c9cafbdf017579419a0b46c.png\" alt=\"What's Left of the Previous Project\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/36\/e7\/0c\/36e70c374c9cafbdf017579419a0b46c.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/36\/e7\/0c\/36e70c374c9cafbdf017579419a0b46c.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>What&#8217;s Left of the Previous Project<\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>HDA-Link Basics<\/h3>\n<h4>Lines and Data Transmission<\/h4>\n<p><strong>HDA link<\/strong>, also known as <em>Azalia<\/em>, is a simple interface that uses only 4 wires to transmit audio data, but it\u2019s tricky and has specific requirements. The first problem is that the <strong>BCLK<\/strong> clock signal must be exactly 24\u00a0MHz \u2014 the PLL inside the codec relies on it. This means you can\u2019t just generate a clock signal via GPIO, as you would for SPI or I\u00b2C, because you need a stable, meander waveform, like the one from a generator.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/66\/f5\/73\/66f573cd67df0cad475b62f361279edd.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/66\/f5\/73\/66f573cd67df0cad475b62f361279edd.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/66\/f5\/73\/66f573cd67df0cad475b62f361279edd.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>The next two signals \u2014 <strong>SYNC<\/strong> and <strong>SDO<\/strong> \u2014 operate synchronously, and their state can change on every BCLK edge, that is, at a frequency of 48\u00a0MHz (pure DDR). The output is divided into frames of 1,000 bits each, resulting in a frame rate of 48\u00a0kHz \u2014 this is the base audio sampling rate, with each frame representing a single sample. The other frequencies, 96 and 192, are obtained by doubling or quadrupling the number of samples per frame. As for the frequencies derived from CDs and based on 44.1\u00a0kHz, they are calculated the same way, except that for every 160 frames, 13 empty frames (without audio data) must be transmitted, and these 13 empty frames must be evenly distributed throughout the 160 frames.<\/p>\n<p>The start of the frame \u2014 or, more precisely, its end \u2014 is determined by the SYNC signal; at that moment, a sequence of 8 ones is transmitted there. <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/d5\/41\/92\/d54192243106b0018ac7aa242548b7c7.png\" alt=\"Start of Frame\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/d5\/41\/92\/d54192243106b0018ac7aa242548b7c7.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/d5\/41\/92\/d54192243106b0018ac7aa242548b7c7.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Start of Frame<\/figcaption><\/div>\n<\/figure>\n<p> The first 40 bits of each frame on the SDO line are reserved for control sequences, known as \u201cverbs.\u201d When the host needs to configure the codec or request something from it, such a sequence is inserted into the stream; otherwise, zeros are transmitted there. Only one verb can be transmitted in each 48\u00a0kHz frame, but this needs to be done infrequently, so there are no issues here. <\/p>\n<p>The SYNC signal also contains markers that indicate the numbered streams for each stereo channel. After each such SYNC marker, data transmission for that channel begins on the SDO line.<\/p>\n<p>The last signal, <strong>SDI<\/strong>, differs from SDO. Its state changes only on the rising edge of BCLK, meaning its bandwidth is half that of SDO. In addition, the tags for each stereo channel are carried within the signal itself, which further narrows the bandwidth. And at the beginning of each frame, the first 36 bits are reserved for responses. A response may or may not be present \u2014 this is determined by the \u201cvalid\u201d bit. Responses can be either solicited (a response to a command) or unsolicited (a reaction, for example, to plugging a jack into a socket).<\/p>\n<p>The specified SDO bandwidth allows for the tight fit of 10 channels of 24-bit audio at 192\u00a0kHz \u2014 which is exactly how many (5 stereo channels) are supported by the ALC889 codec. The situation with SDI is a bit more disappointing \u2014 3 stereo channels at 24\/192 don\u2019t fit within the bandwidth, so something has to be sacrificed \u2014 either the sample rate, the bit depth, or the number of channels. Why Intel did this is a mystery to me. Apparently, they decided that <s>640 KB<\/s> 4 channels would be enough for everyone.<\/p>\n<h4>Codec address<\/h4>\n<p>For the codec to work, it\u2019s not enough to generate all the signals. You need to take the penultimate step and assign it an address on the bus, which will then be used in all verb requests. Without this, the codec won\u2019t respond to them.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/1e\/1b\/03\/1e1b03915ffe099b81a68dd3e1a6cfb9.png\" alt=\"Codec initialisation: reset, codec address (CAD)\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/1e\/1b\/03\/1e1b03915ffe099b81a68dd3e1a6cfb9.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/1e\/1b\/03\/1e1b03915ffe099b81a68dd3e1a6cfb9.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Codec initialisation: reset, codec address (CAD)<\/figcaption><\/div>\n<\/figure>\n<p>Here\u2019s how it works:<\/p>\n<ul>\n<li>\n<p>At a specific moment, after power is applied or a reset is triggered, the codec generates a pulse on the SDI line, then sets it to a high-impedance state. This is indicated by the number (1) in the figure. The host must detect this moment.<\/p>\n<\/li>\n<li>\n<p>The host begins to drive this line and, in sync with the next SYNC signal, outputs a 1 on it \u2014 indicated by number (3) in the diagram.<\/p>\n<\/li>\n<li>\n<p>After 4 BCLK cycles (\u201cstart of frame\u201d on SYNC), the host can extend this 1 on the SDI line for several more cycles \u2014 the address that the codec will adopt as its own depends on this. If it is not extended (i.e., SDI goes to 0 simultaneously with SYNC), the codec will accept address 0, as shown by number (4) in the figure.<\/p>\n<\/li>\n<li>\n<p>The host releases the SDI line, and starting with the next frame, the codec takes control of it again (8).<\/p>\n<\/li>\n<\/ul>\n<p>After that, the codec begins to respond to commands accompanied by the specified address, and you can request its configuration and set it up for operation.<\/p>\n<h4>Configuring nodes and signal paths<\/h4>\n<p>This is the final step in codec initialization. A node is a small component of the codec that performs a single function. ADCs, DACs, mixers, and input\/output pins are all nodes.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/d1\/6d\/af\/d16dafdf86d4ff8ad89973cb7990ab13.png\" alt=\"Structure of the ALC889. The numbers in the ovals are the node numbers.\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/d1\/6d\/af\/d16dafdf86d4ff8ad89973cb7990ab13.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/d1\/6d\/af\/d16dafdf86d4ff8ad89973cb7990ab13.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Structure of the ALC889. The numbers in the ovals are the node numbers.<\/figcaption><\/div>\n<\/figure>\n<p>For example, if we want to output audio from DAC No. 1 \u201cFront\u201d (node 02h) to the \u201cFront\u201d pins (also known as Port-D), node 14h, we must:<\/p>\n<ul>\n<li>\n<p>configure the DAC (node 02) \u2014 specify the stream number, sampling rate, bit depth, etc., which is done using the 2h and 706h commands;<\/p>\n<\/li>\n<li>\n<p>configure intermediate node 0Ch \u2014 the volume control\/mixer;<\/p>\n<\/li>\n<li>\n<p>configure pin 14h as an output, specifying that it should take the signal from node 0Ch. The output pin contains a multiplexer switch that allows it to take a signal from one of the sources. I\u2019ll explain how to configure all of this below, in the project description.<\/p>\n<\/li>\n<\/ul>\n<p>Once the codec is configured, you can send it an audio stream specifying the channel number, and the signal will appear at the output.<\/p>\n<hr\/>\n<h3>Implementing HDA Link on the RP2040<\/h3>\n<p>A distinctive feature of this microcontroller \u2014 one that sets it apart from \u201cjust another Cortex-M0+ microcontroller\u201d \u2014 is the presence of <strong>PIO<\/strong> (programmable input\/output) blocks. In short: in addition to classic control via GPIO or built-in peripherals, the microcontroller\u2019s pins can interact with the PIO blocks, which contain several state machines (SMs) that operate independently of the central processor. Each SM runs its own short program with a small set of instructions and processed data.  Each instruction is executed in a single clock cycle, which allows for precise timing and synchronous operation of multiple SMs. Each SM has input and output shift registers, a FIFO for communication with the CPU\/DMA, a pair of 32-bit registers, and several other useful features. Most of these are used in the implementation of the HDA link.<\/p>\n<h4>Demo project<\/h4>\n<p>A <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\" rel=\"noopener nofollow\">project<\/a> has been created to pair the RP2040 with the HDA Link codec. All references to source code primarily pertain to this project. (There will be a lot of links to the relevant code snippets in the repository below. I think this is better than pasting them here. Let me know if you think I\u2019m wrong.)<\/p>\n<p><strong>Signals I\/O<\/strong><\/p>\n<p>HDA link signals are generated and received using PIO controllers that are started simultaneously and operate in sync. The source assembly code for them is contained in <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/main\/src\/hda.pio\" rel=\"noopener nofollow\">hda.pio<\/a>.<\/p>\n<p>The SMs are used as follows:<\/p>\n<p><strong>SM0<\/strong> \u2014 SDO signal output. This is handled by the <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda.pio#L53-L72\" rel=\"noopener nofollow\">out_1000bit<\/a> program. At the appropriate moment, the SM <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/aead76362b3104e387b5f3c0554288f8fa273ceb\/src\/hda.pio#L66-L67\" rel=\"noopener nofollow\">outputs<\/a> the next bit to the outside using the output shift register. The bit sequence itself is generated in software (by the main processor) in a buffer dedicated for this purpose and is transferred to the SM FIFO using the two DMA channels alternately. Auto-pull is used to load the shift register. Since the frame length is 1,000 bits and is not a multiple of 32, an output bit counter is implemented on the SM register. Loading a constant longer than 5 bits into the SM registers is problematic, so it <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/aead76362b3104e387b5f3c0554288f8fa273ceb\/src\/hda.pio#L57\" rel=\"noopener nofollow\">is loaded<\/a> from the main program and stored in another register. After calculating the required number of bits, the remainder in the shift register (24 bits) is output <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/aead76362b3104e387b5f3c0554288f8fa273ceb\/src\/hda.pio#L71\" rel=\"noopener nofollow\">externally<\/a>, which causes the following sequence to be loaded into it.<\/p>\n<p><strong>SM1<\/strong> \u2014 the SYNC signal output; it operates similarly to SM0 and executes the exact same code.  The difference is that it uses a different buffer and DMA channels.<\/p>\n<p><strong>SM2<\/strong> \u2014 SDI input, <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda.pio#L104-L121\" rel=\"noopener nofollow\">in_500bit<\/a> program. Input shift register <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/aead76362b3104e387b5f3c0554288f8fa273ceb\/src\/hda.pio#L118\" rel=\"noopener nofollow\">is filled<\/a> one bit at a time. As it is filled, an autopush occurs, and upon reaching the 499th cycle, a <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/aead76362b3104e387b5f3c0554288f8fa273ceb\/src\/hda.pio#L120\" rel=\"noopener nofollow\">shift<\/a> is performed immediately across the entire length of the register (<em>in pins, 32<\/em>), which triggers an out-of-order autopush. Next, the FIFO\u2013DMA\u2013RAM buffer chain takes over. This same code uses a so-called \u201cside-set\u201d to generate the BCLK signal and the auxiliary <em>Start of Frame<\/em> (SoF) signal (which is useful for synchronizing an oscilloscope during debugging).<\/p>\n<p><strong>SM3<\/strong> \u2014 Assigning an address to a codec, program <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda.pio#L128-L155\" rel=\"noopener nofollow\">codec_address<\/a>. The circuit waits for a pulse on the SDI line, sets it to the active state, counts down the required interval, generates a pulse of the required length, and sets the line to the inactive state. After completing its task, it <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda.pio#L151\" rel=\"noopener nofollow\">sends<\/a> some garbage to the CPU and enters an infinite loop. After that, it may be <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/aead76362b3104e387b5f3c0554288f8fa273ceb\/src\/main.c#L458\" rel=\"noopener nofollow\">stopped<\/a> and used for other tasks. The CPU, <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda_link_rp2040.c#L89-L94\" rel=\"noopener nofollow\">upon receiving<\/a> the garbage that was sent, realizes that the address assignment phase has been successfully completed and that it can begin <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/aead76362b3104e387b5f3c0554288f8fa273ceb\/src\/main.c#L455\" rel=\"noopener nofollow\">initialization<\/a> of the codec. This SM starts up a little later than the others, after the reset; otherwise, it might trigger prematurely due to noise on the SDI line when the BCLK appears.<\/p>\n<p>This is shown in the oscillogram below. The SDI line is pulled up to a level of approximately +0.9\u00a0V by a resistive divider, and you can see how it transitions to a high-impedance state and back: <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/27\/8e\/81\/278e8155f42303375e21427a60ea4643.png\" alt=\"Codec address (CAD)\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/27\/8e\/81\/278e8155f42303375e21427a60ea4643.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/27\/8e\/81\/278e8155f42303375e21427a60ea4643.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Codec address (CAD)<\/figcaption><\/div>\n<\/figure>\n<p>The following oscillogram shows the entire codec initialization sequence, including the reset, address assignment, and receipt of responses to commands. <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/22\/5e\/a7\/225ea73102bd1e63f0632374b374e32f.png\" alt=\"Codec initialization sequence\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/22\/5e\/a7\/225ea73102bd1e63f0632374b374e32f.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/22\/5e\/a7\/225ea73102bd1e63f0632374b374e32f.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Codec initialization sequence<\/figcaption><\/div>\n<\/figure>\n<p> The numbers indicate: <\/p>\n<ol>\n<li>\n<p>Noise on the SDI line that appears after BCLK is applied (the codec was running, then the microcontroller was reset and the BCLK signal disappeared; when it reappeared, the codec sent the ADC samples that had accumulated in the buffer).<\/p>\n<\/li>\n<li>\n<p>CAD.<\/p>\n<\/li>\n<li>\n<p>Responses to commands (the commands themselves are not shown on SDO because there weren\u2019t enough channels).<\/p>\n<\/li>\n<\/ol>\n<p><strong>Assembly and Disassembly of Digital Sequences<\/strong><\/p>\n<p>The DAC\/ADC samples transmitted over the SDO\/SDI lines are packed tightly together and are not aligned to a 32-bit boundary. To make them easier to work with (each sample in its own <code>int32_t<\/code>), software repackaging is performed; core #1 is dedicated to this task (the RP2040 is a dual-core microcontroller). Upon receiving an <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda_link_rp2040.c#L254\" rel=\"noopener nofollow\">interrupt<\/a> from the DMA \u2014 that is, when the next batch of data has been sent out to the SDO and received from the SDI \u2014 the kernel begins to <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda_link_rp2040.c#L352-L394\" rel=\"noopener nofollow\">pack<\/a> a set of samples into a sequence that the codec can understand. If necessary, a control verb is added to it.<\/p>\n<p>Next, the CPU <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda_link_rp2040.c#L401-L471\" rel=\"noopener nofollow\">unpacks<\/a> the data received via SDI from the ADC, simultaneously separating <s>the wheat from the chaff<\/s> the audio data from the responses, storing the former in a single buffer and the latter in their respective response queues (depending on whether the response was requested or not). All the packing logic is described in the <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/main\/src\/data_pack.c\" rel=\"noopener nofollow\">data_pack.c<\/a> file; the appropriate functions are called depending on the current mode.<\/p>\n<p><strong>Sine Wave source<\/strong><\/p>\n<p>It\u2019s included in the project as well. For maximum performance, it\u2019s organized as a table \u2014 1,024 points per quadrant \u2014 followed by piecewise-linear <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/get_sine_approx_1025_0_q1.c#L172-L202\" rel=\"noopener nofollow\">interpolation<\/a>. Not <a href=\"https:\/\/habr.com\/ru\/articles\/577256\/\" rel=\"noopener nofollow\">Chebyshev polynomials<\/a>, of course (I tried those too, but they run very slowly on a Cortex-M0 without DSP instructions), and there are harmonics, but the strongest one \u2014 the 3rd \u2014 doesn\u2019t exceed \u2212123\u00a0dB in simulation, which is more than enough for a demo project. You can also connect a larger sine wave table, with 4,096 points and \u2212142\u00a0dB \u2014 I have one of those, too. To do this, you need to <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/CMakeLists.txt#L19-L20\" rel=\"noopener nofollow\">edit<\/a> CMakeLists.txt. The code size will increase, but we have the resources to handle it. The benefit of this, however, is practically zero \u2014 the analog parts of the codec introduce far more distortion, and the improvement will most likely be imperceptible.<\/p>\n<p><strong>Using the DACs<\/strong><\/p>\n<p>The ALC889 contains 5 stereo DACs, meaning 10 independent channels. As you\u2019ve probably already guessed, this project uses them to output sinusoidal signals with <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/main.c#L325-L339\" rel=\"noopener nofollow\">varying<\/a>, starting at 880\u00a0Hz at a sampling rate of 192\u00a0kHz and increasing by a factor of 1.5 in each subsequent channel. That makes them fifths, then. The last two channels output sine waves with frequencies equal to 0.333 and 0.400 times the sampling rate.<\/p>\n<p><strong>Using the ADC<\/strong><\/p>\n<p>All 6 ADC channels (3 stereo inputs) are connected to an LED on the RP2040 board: as soon as a signal slightly above the noise level appears at any of the inputs, the LED begins to <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/main.c#L368-L393\" rel=\"noopener nofollow\">flash<\/a>. My imagination took a break at that point, and I couldn\u2019t come up with anything more interesting.<\/p>\n<p><strong>Overclocking<\/strong><\/p>\n<p>You can achieve a sampling rate higher than 192\u00a0kHz by running the microcontroller at a higher frequency. To do this, in the <code>main<\/code> function, in the <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/main.c#L401\" rel=\"noopener nofollow\">line<\/a> <code>set_sys_clock_pll(24000000 * LINK_BCLK_LEN*2 * 6, 3, 2)<\/code>, replace the number 24000000 with another value \u2014 the microcontroller\u2019s system frequency and BCLK will change proportionally. The coefficients are chosen such that 24000000 is, in fact, the BCLK signal frequency (Hz). I successfully overclocked it to 32\u00a0MHz (the sampling rate was 256\u00a0kHz) \u2014 the codec worked, but it got so hot that I could barely keep my finger on it.<\/p>\n<p>Well, if you\u2019ve added calculations to your project and you\u2019re finding that the performance per sample is a little lacking, you can increase <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/hda.pio#L18\" rel=\"noopener nofollow\">the constant<\/a> <code>LINK_BCLK_LEN<\/code>; by default, it is 4. The MCU\u2019s system clock frequency will increase proportionally, while the BCLK frequency will remain unchanged.<\/p>\n<p><strong>Signal Paths<\/strong><\/p>\n<p>The codec can be flexibly configured. You can specify signal paths, pin orientations, volume levels, and more. Here, I\u2019ll briefly explain how path search and configuration are implemented.<\/p>\n<p>If a node can receive a signal from other nodes, then there is a list of those source nodes for it. You can query the codec for their number using the verb F00h:0Eh, and for the list using F02h verb. In the <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040_alc889_usb_audio\/blob\/main\/src\/ALC889_init_result.txt\" rel=\"noopener nofollow\">table<\/a> showing the initialization results, this information is found in the corresponding columns, closer to the right edge. Any node can be connected to virtually any other node, and sometimes there is more than one such path.<\/p>\n<p>In this project, the pins are hardwired to the DAC\/ADC; the connections are described in a small <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/726c251c367727f0d312b0d332efb4904c127ea9\/src\/hda_codec.c#L101-L124\" rel=\"noopener nofollow\">table<\/a>.<\/p>\n<p>After <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040_alc889_usb_audio\/blob\/a514eca0455d782980af9b437ed18f4be36d38db\/src\/hda_codec.c#L246\" rel=\"noopener nofollow\">receiving<\/a> the properties of the codec and its nodes, the <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040_alc889_usb_audio\/blob\/a514eca0455d782980af9b437ed18f4be36d38db\/src\/hda_codec.c#L1048\" rel=\"noopener nofollow\">function<\/a>, which recursively searches for the path to the destination node. <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040_alc889_usb_audio\/blob\/a514eca0455d782980af9b437ed18f4be36d38db\/src\/hda_codec.c#L978\" rel=\"noopener nofollow\">If<\/a> one is found, then <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040_alc889_usb_audio\/blob\/a514eca0455d782980af9b437ed18f4be36d38db\/src\/hda_codec.c#L814\" rel=\"noopener nofollow\">the following are configured<\/a> all nodes participating in it.<\/p>\n<h4>USB Audio Device Project<\/h4>\n<p>Another <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040_alc889_usb_audio\" rel=\"noopener nofollow\">project<\/a> demonstrates the ALC889 and RP2040 working together as a UAC\u00a02.0 device. I should note right away that the project is still in its early stages, and its codebase lags significantly behind the one described above. You can use it, but there are some caveats\u2026<\/p>\n<p>I won\u2019t go into detail about it here; anyone who needs to can figure it out on their own. I\u2019ll just mention that the <a href=\"https:\/\/github.com\/cherry-embedded\/CherryUSB\" rel=\"noopener nofollow\">CherryUSB<\/a> library is used for the USB stack, although it probably would have been better to choose tinyUSB from the Pico SDK. Well, it is what it is.<\/p>\n<p>The <code>bin<\/code> folder contains several compiled files for the firmware, each offering its own trade-off among \u201cbit depth, sampling rate, and number of channels.\u201d I had to resort to this because the USB bus bandwidth in Full-Speed mode is significantly lower than what\u2019s needed to take full advantage of the HDA link. Each firmware version has a unique USB DID hardcoded into it to avoid the hassle of dealing with Windows when switching to the next one.<\/p>\n<details class=\"spoiler\">\n<summary>Hidden text<\/summary>\n<div class=\"spoiler__content\">\n<p>I hate USB\u2026<\/p>\n<\/div>\n<\/details>\n<hr\/>\n<h3>Conclusions, Observations, and Comments<\/h3>\n<p><strong>1.<\/strong> The codec\u2019s digital filters cut off, among other things, the DC component. They behave like a first-order high-pass filter with a cutoff frequency of about 4\u00a0Hz at 192\u00a0kHz SR (I haven\u2019t tried it at lower sample rates, but it will most likely be proportional). I don\u2019t think this can be disabled. The filters are located both before the DAC and after the ADC. Therefore, it won\u2019t be possible to use the codec either as a regulated DC voltage source or for digitizing, for example, the positions of potentiometers. Only AC, only hardcore.<\/p>\n<p><strong>2.<\/strong> When in operation, the ALC889 consumes a significant amount of power and converts it into heat. The table below shows how many mA the codec board draws from the +3.3\u00a0V supply, depending on the sampling rate and the number of active channels. (The codec requires two voltages \u2014 +3.3\u00a0V and +5.0\u00a0V. The first is supplied externally, while the second is derived from it via a step-up DC-DC converter.)<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">SR, kHz<\/p>\n<\/th>\n<th>\n<p align=\"left\">3+5<\/p>\n<\/th>\n<th>\n<p align=\"left\">1+1<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">256<\/p>\n<\/td>\n<td>\n<p align=\"left\">271<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">192<\/p>\n<\/td>\n<td>\n<p align=\"left\">244<\/p>\n<\/td>\n<td>\n<p align=\"left\">198<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">176<\/p>\n<\/td>\n<td>\n<p align=\"left\">237<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">96<\/p>\n<\/td>\n<td>\n<p align=\"left\">218<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">88<\/p>\n<\/td>\n<td>\n<p align=\"left\">215<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">48<\/p>\n<\/td>\n<td>\n<p align=\"left\">204<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">44<\/p>\n<\/td>\n<td>\n<p align=\"left\">202<\/p>\n<\/td>\n<td>\n<p align=\"left\">187<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">idle<\/p>\n<\/td>\n<td>\n<p align=\"left\">147<\/p>\n<\/td>\n<td>\n<p align=\"left\">\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>Here, <strong>3+5<\/strong> and <strong>1+1<\/strong> represent the number of DAC+ADC stereo channels that were active.<\/p>\n<p><strong>3.<\/strong> A previous attempt to implement the HDA codec on a microcontroller was made using a combination of an STM32F103, a Spartan-3, and an ALC889. However, the USB peripheral buffer size was limited to 512 bytes for all USB endpoints and dual buffers, which resulted in zero USB bandwidth, so that project was successfully scrapped and buried (though the board with the codec remained). The attempt mentioned in this article turned out to be slightly better, as the limitations are of a different nature \u2014 the total volume for all isochronous transfers within a 1-ms USB frame is limited to 1 KB (which is close to the 1.5 KB bandwidth of Full-Speed USB).<\/p>\n<p>For a codec like this to really shine, it requires bandwidth that\u2019s roughly an order of magnitude higher \u2014 which falls into the \u201cHigh-Speed\u201d category \u2014 and that involves entirely different microcontrollers. Maybe RP will treat us to something like that someday.<\/p>\n<p><strong>4.<\/strong> It\u2019s worth mentioning how samples are organized into streams, since the images alone may not immediately make it clear how this actually works. Let\u2019s say we\u2019re playing back 3 stereo channels at 48\u00a0kHz. The sequence of samples looks like this:<\/p>\n<p><sup>(1)<\/sup> <strong>R L<\/strong> <sup>(2)<\/sup> <strong>R L<\/strong>  <sup>(3)<\/sup> <strong>R L<\/strong>,<\/p>\n<p>where the number in parentheses is the stereo channel number (its starting point), and R and L are its right and left halves (or vice versa \u2014 it doesn\u2019t matter here). At 48\u00a0kHz, everything is clear and logical. But at 192\u00a0kHz, where there are four times as many samples, the order will be as follows:<\/p>\n<p><sup>(1)<\/sup> <strong>R L R L R L R L<\/strong> <sup>(2)<\/sup> <strong>R L R L R L R L<\/strong>  <sup>(3)<\/sup> <strong>R L R L R L R L<\/strong><\/p>\n<p>In other words, all the samples for the first DAC are processed first, then those for the second, and so on. This must be taken into account when filling the DAC buffers and flushing the ADC buffers. In the sine wave generator, this <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/d6e0dcbf169d180372d4e5c60b5a006cb8b8a383\/src\/main.c#L347-L361\" rel=\"noopener nofollow\">is handled<\/a> 3 nested loops; in the USB project, similar <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040_alc889_usb_audio\/blob\/a514eca0455d782980af9b437ed18f4be36d38db\/src\/main.c#L164-L175\" rel=\"noopener nofollow\">code<\/a> repackages the stream before sending it over USB.<\/p>\n<p><strong>5.<\/strong> The USB audio version of this project was in danger of being put on indefinite hold due to the peculiarities of the RP2040. The problem was as follows: when two DMA channels were operating simultaneously (transferring data from RAM to the PIO FIFO) and USB communication began \u2014 especially while a command was being fetched from the XIP (SPI FLASH), one of the DMA channels would send an extra word to the already full PIO FIFO, causing it to overflow, resulting in the loss of 32 bits and, consequently, desynchronization on the HDA link between the SYNC signal and the rest. Thanks to the guys at Raspberry Pi \u2014 they eventually found <a href=\"https:\/\/forums.raspberrypi.com\/viewtopic.php?p=2385863#p2386017\" rel=\"noopener nofollow\">the cause<\/a> and proposed <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040-alc889-sinegen-01\/blob\/aead76362b3104e387b5f3c0554288f8fa273ceb\/src\/hda_link_rp2040.c#L178\" rel=\"noopener nofollow\">a working<\/a> solution. It\u2019s working fine now, but just in case <s>because I didn\u2019t want to delete it<\/s>, there\u2019s still a <a href=\"https:\/\/github.com\/VictorAgarkov\/rp2040_alc889_usb_audio\/blob\/a514eca0455d782980af9b437ed18f4be36d38db\/src\/main.c#L412-L425\" rel=\"noopener nofollow\">piece<\/a> that monitors this and triggers the debug pin.<\/p>\n<p><strong>6.<\/strong> While learning the RP2040, I made extensive use of Google AI; it almost always provided useful advice and quite often generated working code that I was able to incorporate into my project with minimal changes \u2014 you can recognize it by its specific comments. To sum up \u2014 he does an excellent job as a mentor (I recommend him while he\u2019s still available).<\/p>\n<p><strong>7.<\/strong> What\u2019s the point of all this? My imagination refuses to come back from its vacation, so all I can suggest is: a) a multi-channel USB audio interface; b) a standalone signal generator. As a variation on the latter \u2014 a multi-channel signal generator with feedback: receive a signal (or signals), process it, and send it back. But in this case, it\u2019s better to use the RP2350 \u2014 it has a Cortex-M33 with DSP instructions and floating-point capabilities, and processing signals will be more fun. If you happen to have your own ideas, please share them.<\/p>\n<p>That\u2019s all for now. Thank you for reading to the end.<\/p>\n<p>Best regards, Victor. I\u2019d love to hear your comments.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1096724\/\">https:\/\/habr.com\/ru\/articles\/1096724\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0420\u0443\u0441\u0441\u043a\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044fWhen it comes to outputting or inputting audio to or from a microcontroller \u2014 if the system is even slightly more complex than a simple beeper \u2014 I\u00b2S is almost always the standard. Many microcontrollers have the necessary peripherals, and a wide range of compatible audio DACs and ADCs is available. However, this typically means a maximum of 2 channels for audio output and 2 for input (and even then, not always simultaneously).On the other hand, there is a whole world called High Definition Audio, developed by Intel in 2004. It is highly likely that the sound on the PC or laptop you are using to read this article is transmitted via this interface. HDA supports 15 input channels and 15 output channels, with a bit depth of up to 32 bits and a sampling rate of up to 192\u00a0kHz. Admittedly, not all at once \u2014 the channel bandwidth is limited to just over 46 Mbit\/s per audio output channel; nevertheless, its capabilities are much broader than those of I\u00b2S.But I haven\u2019t come across any microcontrollers that natively support HDA. For some reason, it\u2019s generally assumed that only chipsets (south bridges) \u2014 that is, the \u201cfull-fledged\u201d system logic of a PC \u2014 can act as hosts. You could also cobble something together using an FPGA, but for many \u201cknee-on-the-floor DIY\u201d enthusiasts, that\u2019s too high a hurdle.I thought that way for a long time, too. And at first, the news about the release of the RP2040 just went right over my head \u2014 I figured, \u201cJust another microcontroller; there are thousands of them.\u201d That is, until I stumbled upon the ZX_RGBI2VGA-HDMI project, where this microcontroller captures an RGB video signal from a ZX Spectrum in real time and outputs it as an HDMI (or rather, pseudo-HDMI) signal required by modern monitors.And I thought: Why not try an HDA link on it? Well, if not, then not, I told myself, and got to work \u2014 luckily, I still had a board with that codec, the ALC889, left over from my previous attempt.What&#8217;s Left of the Previous ProjectHDA-Link BasicsLines and Data TransmissionHDA link, also known as Azalia, is a simple interface that uses only 4 wires to transmit audio data, but it\u2019s tricky and has specific requirements. The first problem is that the BCLK clock signal must be exactly 24\u00a0MHz \u2014 the PLL inside the codec relies on it. This means you can\u2019t just generate a clock signal via GPIO, as you would for SPI or I\u00b2C, because you need a stable, meander waveform, like the one from a generator.The next two signals \u2014 SYNC and SDO \u2014 operate synchronously, and their state can change on every BCLK edge, that is, at a frequency of 48\u00a0MHz (pure DDR). The output is divided into frames of 1,000 bits each, resulting in a frame rate of 48\u00a0kHz \u2014 this is the base audio sampling rate, with each frame representing a single sample. The other frequencies, 96 and 192, are obtained by doubling or quadrupling the number of samples per frame. As for the frequencies derived from CDs and based on 44.1\u00a0kHz, they are calculated the same way, except that for every 160 frames, 13 empty frames (without audio data) must be transmitted, and these 13 empty frames must be evenly distributed throughout the 160 frames.The start of the frame \u2014 or, more precisely, its end \u2014 is determined by the SYNC signal; at that moment, a sequence of 8 ones is transmitted there. Start of Frame The first 40 bits of each frame on the SDO line are reserved for control sequences, known as \u201cverbs.\u201d When the host needs to configure the codec or request something from it, such a sequence is inserted into the stream; otherwise, zeros are transmitted there. Only one verb can be transmitted in each 48\u00a0kHz frame, but this needs to be done infrequently, so there are no issues here. The SYNC signal also contains markers that indicate the numbered streams for each stereo channel. After each such SYNC marker, data transmission for that channel begins on the SDO line.The last signal, SDI, differs from SDO. Its state changes only on the rising edge of BCLK, meaning its bandwidth is half that of SDO. In addition, the tags for each stereo channel are carried within the signal itself, which further narrows the bandwidth. And at the beginning of each frame, the first 36 bits are reserved for responses. A response may or may not be present \u2014 this is determined by the \u201cvalid\u201d bit. Responses can be either solicited (a response to a command) or unsolicited (a reaction, for example, to plugging a jack into a socket).The specified SDO bandwidth allows for the tight fit of 10 channels of 24-bit audio at 192\u00a0kHz \u2014 which is exactly how many (5 stereo channels) are supported by the ALC889 codec. The situation with SDI is a bit more disappointing \u2014 3 stereo channels at 24\/192 don\u2019t fit within the bandwidth, so something has to be sacrificed \u2014 either the sample rate, the bit depth, or the number of channels. Why Intel did this is a mystery to me. Apparently, they decided that 640 KB 4 channels would be enough for everyone.Codec addressFor the codec to work, it\u2019s not enough to generate all the signals. You need to take the penultimate step and assign it an address on the bus, which will then be used in all verb requests. Without this, the codec won\u2019t respond to them.Codec initialisation: reset, codec address (CAD)Here\u2019s how it works:At a specific moment, after power is applied or a reset is triggered, the codec generates a pulse on the SDI line, then sets it to a high-impedance state. This is indicated by the number (1) in the figure. The host must detect this moment.The host begins to drive this line and, in sync with the next SYNC signal, outputs a 1 on it \u2014 indicated by number (3) in the diagram.After 4 BCLK cycles (\u201cstart of frame\u201d on SYNC), the host can extend this 1 on the SDI line for several more cycles \u2014 the address that the codec will adopt as its own depends on this. If it is not extended (i.e., SDI goes to 0 simultaneously with SYNC), the codec will accept address 0, as shown by number (4) in the figure.The host releases the SDI line, and starting with the next frame, the codec takes control of it again (8).After that, the codec begins to respond to commands accompanied by the specified address, and you can request its configuration and set it up for operation.Configuring nodes and signal pathsThis is the final step in codec initialization. A node is a small component of the codec that performs a single function. ADCs, DACs, mixers, and input\/output pins are all nodes.Structure of the ALC889. The numbers in the ovals are the node numbers.For example, if we want to output audio from DAC No. 1 \u201cFront\u201d (node 02h) to the \u201cFront\u201d pins (also known as Port-D), node 14h, we must:configure the DAC (node 02) \u2014 specify the stream number, sampling rate, bit depth, etc., which is done using the 2h and 706h commands;configure intermediate node 0Ch \u2014 the volume control\/mixer;configure pin 14h as an output, specifying that it should take the signal from node 0Ch. The output pin contains a multiplexer switch that allows it to take a signal from one of the sources. I\u2019ll explain how to configure all of this below, in the project description.Once the codec is configured, you can send it an audio stream specifying the channel number, and the signal will appear at the output.Implementing HDA Link on the RP2040A distinctive feature of this microcontroller \u2014 one that sets it apart from \u201cjust another Cortex-M0+ microcontroller\u201d \u2014 is the presence of PIO (programmable input\/output) blocks. In short: in addition to classic control via GPIO or built-in peripherals, the microcontroller\u2019s pins can interact with the PIO blocks, which contain several state machines (SMs) that operate independently of the central processor. Each SM runs its own short program with a small set of instructions and processed data.  Each instruction is executed in a single clock cycle, which allows for precise timing and synchronous operation of multiple SMs. Each SM has input and output shift registers, a FIFO for communication with the CPU\/DMA, a pair of 32-bit registers, and several other useful features. Most of these are used in the implementation of the HDA link.Demo projectA project has been created to pair the RP2040 with the HDA Link codec. All references to source code primarily pertain to this project. (There will be a lot of links to the relevant code snippets in the repository below. I think this is better than pasting them here. Let me know if you think I\u2019m wrong.)Signals I\/OHDA link signals are generated and received using PIO controllers that are started simultaneously and operate in sync. The source assembly code for them is contained in hda.pio.The SMs are used as follows:SM0 \u2014 SDO signal output. This is handled by the out_1000bit program. At the appropriate moment, the SM outputs the next bit to the outside using the output shift register. The bit sequence itself is generated in software (by the main processor) in a buffer dedicated for this purpose and is transferred to the SM FIFO using the two DMA channels alternately. Auto-pull is used to load the shift register. Since the frame length is 1,000 bits and is not a multiple of 32, an output bit counter is implemented on the SM register. Loading a constant longer than 5 bits into the SM registers is problematic, so it is loaded from the main program and stored in another register. After calculating the required number of bits, the remainder in the shift register (24 bits) is output externally, which causes the following sequence to be loaded into it.SM1 \u2014 the SYNC signal output; it operates similarly to SM0 and executes the exact same code.  The difference is that it uses a different buffer and DMA channels.SM2 \u2014 SDI input, in_500bit program. Input shift register is filled one bit at a time. As it is filled, an autopush occurs, and upon reaching the 499th cycle, a shift is performed immediately across the entire length of the register (in pins, 32), which triggers an out-of-order autopush. Next, the FIFO\u2013DMA\u2013RAM buffer chain takes over. This same &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-499421","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/499421","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=499421"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/499421\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=499421"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=499421"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=499421"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}