For my final project, I'd like to do a pirate TV station. The idea would be to have a small chip, where the user could plug in a usb drive containing the media to be broadcast and the chip would broadcast it in a small, say 1-meter radius to a nearby CRT TV tuned into channel that I still need to decide after researching the frequencies of different analog TV channels in Finland.
Heavily inspired by this project. Check this link later when it works.
- Theres also this PDF I found laying around on a hard drive of mine. No Idea who is the original author, but all credit goes to them.
Visual inspiration/Enclosure design
For visual inspiration I've put up a are.na board here. I'm very much inspired by transparent acrylic encasings, where the pcb would be visible. On the contrary, I'd like to use as little material as possible, and even then, I'd like to use waste material. Sustainable but beautiful, which might prove to be difficult
As for the design of the PCB itself, the antenna needs to be exposed from the enclosure, and if possible, I'd like add a small screen for the user to see what is happening.
March 9th, 2022
I checked out this video about sending out NTSC video from an ATTiny chip. In it the author describes a resistor network that can be used as a DAC for sending out various voltages from a digital pin.
I talked about it with Kris, and discovered that the ATtiny412 already has a DAC pin. This should be enough for transmitting monochrome PAL video.
My next steps are to actually figure out how PAL works. Here are some links to get started.
I could also use a library such as arduino-tvout to maybe bypass the learning and test that transmission to TV even works from the ATtiny412. Regardless, it's probably a good idea to do some research into the source codes of these libraries (which requires reading up on PAL). Other libraries to check out are this and this (uses PWM harmonics)
Generating an analog video signal
To understand how we can output analog video, we first need to understand what a signal is. To put it simply, a signal is data encoded by varying a voltage over time. There can only be a single voltage during a single moment of time, but this voltage can vary slowly, or very quickly, resulting in different waveforms. Video data encoded into a signal in this manner is often called baseband video. The baseband video signal gets transmitted to a TV either by cable or via the air, parsed, and then displayed. But how does the TV know what to do with the signal? Enter PAL
PAL, or Phase Alternating Line, is a standard used in most of European countries to encode video data into a analog signal. The video signal in PAL is split into frames, frame being a single image to display on the screen. A frame is then further split into scanlines. A scanline describes a single line of the frame, or the image being drawn. In PAL, these scanlines are grouped into two fields, one for even lines, and the other for odd lines. When a frame is transmitted, first all the even lines get sent, and then all the odd lines get send, after which they are interlaced to form a whole image.
A scanline starts with a horizontal blanking period:
This blanking period lasts for about 12.05µs, and is divided into three sections: Front porch (1,65µs), H-Sync pulse (4,7µs) and Back porch (5,7µs). Apparently most microcontroller video outputs seem to use a H-Sync pulse of 4µs and it should work fine. After the blanking period comes the image data, lasting for total of 51,95µs, until a new blanking period follows, starting a new scanline.
As mentioned above, a frame consists of two fields, one for even lines and other for odds:
These fields get interlaced to form a whole picture. Apparently you can skip the second field by some hacking, which is easier than interlacing.
Transmission of video over radio
To transmit the baseband video signal, it needs to be modulated into a carrier signal, which depends on on the TV channel we're going to broadcast to. This means that the signals are combined so that carrier signal gets louder in the points the baseband gets louder. Below is a pretty bad diagram of this:
ATtiny412 dev board
To test outputting PAL signals, I designed a simple ATtiny412 development board that could be programmed through USB thanks to the FT230X chip, which is really small and a pain in the ass to solder. I designed the development board to also have headers for the DAC and Power pins for easier connectivity.
After this I read up on PAL timings, and programmed the MCU to generate a PAL signal through the DAC pin. More detail on the code can be found on the assignment page for the Output Devices -week.
Trying to display an actual Image
After generating a demo image of half black half white, it was time to try and generate a PAL signal from a real image.
Image to header
The first step was to generate a C header file that contains all the pixel data of a grayscale image. In retrospect, I realize that this is already where I kinda went wrong. I used both GIMP and ImageMagick to generate a .h file, but both generate a array with pixel values (in random bit-depths no less), while in reality I need an array with luminosity values between 0.3V - 1V (in a 8 bit integer of course). I'll need to program a script in Python to do this for me, but it really shouldn't be too big of a problem.
The second issue that arises from trying to display a image through PAL are the pixel timings. As the active display area (the actual image data) lasts for about 51.25µs, one needs to calculate how long one pixel of the image should take to draw. As we are going into nanosecodn accuracy here, it really gets tricky. I did a good amount of researching timers on the ATtiny412 only to be super confused of their actual accuracy and how to use them.
One way to solve this issue seems to be convert all PAL timings to CPU cycles, and use the internal counter to time things using interrupts. I didn't really have the motivation to delve into the counter part after struggling with timers, so I was pretty demotivated at this point.
After discussing a bit with Kris, he ended up ordering some STM32 black pills, one of which I could use for developing my project. The STM32 runs at about 100MHz, so it should have more than enough horsepower to generate a PAL signal on time.
After getting the STM32 up and running, and uploading some blinky code with PlatformIO, I started looking into how I could output analog signals with it. I found out that the STM32 does not have a built-in DAC (can't remember if Kris mentioned this), so I needed to use a external DAC chip and use I2C to communicate with it. I started wondering if I2C would add needless overhead (probably not), and in general I was not feeling like milling another board, so I decided to do some research into boards that would have fast enough clockspeed and built-in DACS.
After a bit of googling around, I found a ton of composite video projects for the ESP32/8266 MCUs. The 8266 doesn't have a onboard DAC though, so I decided to pick up a ESP32 development board from Uraltone, as it was only 15e and I live really close to Uraltone.
Diving deeper into the ESP32 programming.
To program the ESP32 development board, I used PlatformIO. Installing the libraries was more or less automatic, and I could get a blink code compiled fairly quickly. Programming it on the board proved to be harder though. Turns out you have to hold and release the reset button when uploading code. On my board this seems to work completely arbitrarily though, and I've yet to figure out how to press the button to make it consistent. Adding a capacitor between EN and GND pins should solve this though, so I need to try that next.
Displaying PAL video
With some code running, I decided to port over my previous PAL code to the ESP32. Everything went more or less smoothly, as the ESP32 documentation for using the DAC is pretty good. With the code uploaded and running, I tried to plug it into my TV, aand all I could get is a rolling and flickering image.
After a bit of debugging, it turns out that the Arduino function
delayMicroseconds only accepts integers, and thus ignores all fractions I passed to it, screwing up both vertical and horizontal sync timings. Back to the drawing board it was.
I2S + DMA
After researching existing projects for the ESP32, it seemed that a lot of them were using something called I2S in combination with DMA. Turns out that I2S is a protocol originally made for transfering audio signals. It uses three lines:
- Serial data line: Where actual signal data travels
- Bit clock line: Tells the I2S peripheral when there is a new bit to read
- Channel select line: Also known as the word select line. Tells the I2S peripheral when there is one words worth of data on the line.
The diagram above shows the timing for I2S lines. Image courtesy of Hackaday. There are a few different standards for transmitting data, mostly having to do whether data is sent on the first pulse of the bit clock or second. The image shows the Philips standard, where data is sent on the second pulse.
The benefit of I2S is that it is really fast in transmitting data, especially with a fast microcontroller like the ESP32. It's also a fairly loose spec, so that it can be used to transform arbitrary waveforms, not just sound.
Combined with DMA (Direct Memory Access), we can read image data direct from memory and output using I2S, bypassing the CPU completely.
Timing using the I2S
However, while many projects mention that they use I2S to transmit PAL data, none of them explain how they actually use to I2S to time the signals. I've seen the term PDM being thrown around, and while the ESP32 I2S module has PDM capabilites, no projects seem to actually use it.
I've come to the conclusion that the timing of the signal happens with the word select line, as it's what determines the sample outputrate of the I2S. When you know the rate of the sample output rate, you can count roughly how many samples are output for each part of the PAL signal, and time them by padding out the data.
To try out if PAL output even works, I grabbed bitluni's CompositeVideoSimple from GitHub to try out. I simplified the code so that it only outputs a image, and sure enough, it works.
However, the code is way too overcomplicated for my needs, so recently I've just been spending time trying to simplify it. Stripping down the graphics part was really quite easy, but the actual part that interests me, i.e. the composite output is really complicated, as it uses interlaced video and pretty peculiar timings for syncing. It also has magic numbers everywhere. But, I'm getting closer to understanding it, so pretty soon I should be able to roll out my own.