tvText is a library for use with Atmel AVR microcontrollers to provide text output on a television set using nothing more than two resistors and a 20MHz resonator.
It has been primarily developed for use with the ATmega168, but should be portable to other AVRs.
It outputs the picture signal on the most-significant bit of a user-defined I/O port. The remaining bits in the PORT register will be effectively filled with random data, but can be used as input pins (watch out for the internal pull-up resistors being enabled!) The sync signal is output on a single pin on one other I/O port – the rest of that port can be used as normal.
The source code is made available under the MIT licence. In short: do with it what you will.
Two resistors are used to form a 2-bit DAC to generate the voltages required for composite video – 0V for sync, 0.3V for black and 1V for white. Assuming a 75Ω resistance in the television, the ideal resistor values are 450Ω and 900Ω – however, 470Ω and 1KΩ work just as well. The AVR needs to be run at 20MHz, so use an external 20MHz resonator and suitable load capacitors. Remember to set the fuses to use an external resonator and disable CKDIV8!
A simplified circuit diagram is shown above, showing the resonator and resistor DAC connections. The smaller resistor (450Ω) needs to be connected to the most significant pin on the TVTEXT_PICTURE_PORT (PD7 by default). The larger resistor (900Ω) needs to be connected to the pin defined as the sync pin by the TVTEXT_SYNC_PORT and TVTEXT_SYNC_BIT settings (PB0 by default).
Consult the data sheet for your AVR for more detailed instructions.
This package contains the following files:
The various configuration options, functions and variables are discussed below.
The config.h file can be modified to change the pins that are used to output the video signal.
For performance reasons, an entire I/O port is used to output the picture data (a 1 bit for white pixels and a 0 bit for black pixels). The picture data is always output on the most significant bit of this port. You can change this port by changing the TVTEXT_PICTURE_PORT and TVTEXT_PICTURE_DDR definitions (both must refer to the same port).
The pin used to generate synchronisation pulses can have any pin number within the port (configured with TVTEXT_SYNC_BIT), and may be any port other than the one used for picture data (configured with TVTEXT_SYNC_PORT and TVTEXT_SYNC_DDR).
Every scanline (row) of the generated video signal is output twice. Enabling the TVTEXT_SKIP_ALTERNATE_ROWS configuration switch will cause the driver to skip alternate rows (outputting a plain black scanline instead), only outputting each scanline once. This greatly reduces the amount of time the driver needs to spend outputting the video signal, at the expense of picture quality.
The functions of the variables are summarised below.
Variable | Type | Description | Default |
---|---|---|---|
tvtext_flags | uint8_t |
This variable stores a number of flags that control the appearance of the behaviour of the text console:
|
TVTEXT_AUTO_SCROLL, TVTEXT_CURSOR_ENABLED and TVTEXT_CURSOR_VISIBLE are set. |
tvtext_buffer | char[] | This array stores every character on the screen in left-to-right, top-to-bottom order. The address of any particular character on the screen can be calculated with row * 32 + column. | Filled with the clear character (tvtext_cleared). |
tvtext_cursor_column tvtext_cursor_row |
int8_t | These variables store the column and row that the text cursor resides in. If you change the cursor position manually, please ensure that it is within the text viewport before calling any of the text output functions – it is usually safer and easier to call tvtext_cursor_move() instead. | 0 |
tvtext_cursor | char | This is the character used to represent the flashing cursor (if enabled). | '_' |
tvtext_cleared | char | This is the character used to clear the viewport when tvtext_clear() is called or to fill in new rows or columns when the viewport is scrolled. | ' ' |
tvtext_viewport_left | uint8_t | These four variables define the edges of the current text viewport. All coordinates are inclusive – a left edge of 0 and a right edge of 31 results in a viewport 32 characters wide. If changing these values manually, please ensure that the cursor is moved inside the viewport before calling any of the text output functions – it is usually safer and easier to call tvtext_set_viewport() instead. | 0 |
tvtext_viewport_bottom | 15 | ||
tvtext_viewport_right | 31 | ||
tvtext_viewport_top | 0 | ||
tvtext_cursor_flash_period | uint8_t | The period (in frames) that it takes the flashing cursor to toggle between being shown and being hidden. A value of 50 would show the cursor for 50 frames (one second) then hide it for 50 frames (another second). | 16 |
tvtext_cursor_flash_timer | uint8_t | This timer counts down every frame. It starts at tvtext_cursor_flash_period, counts down to zero, then inverts the cursor state and restarts. | N/A |
tvtext_scanline_counter | int16_t | This counter refers to a particular scanline during the active display and is used to calculate which row of characters and which row from the font data to use. It starts at −33, then counts up immediately before outputting each scanline. The 128-pixel high display is stretched to 256 rows on the screen by outputting each row of pixels twice. If the counter is below zero or greater than 256, no scanline is output. | N/A |
tvtext_frame_counter | int16_t | This counter is incremented every time a frame is completed and the rasteriser enters vertical sync period. Feel free to modify it and use it for your own timing purposes. | 0 |
The only function you need to call is tvtext_init() – you can output text by directly writing to the text buffer. Several other functions are provided to help you get text on the display more easily, however.
This function writes a single character to the text buffer and advances the cursor.
If the cursor moves off the side of the viewport, it is advanced to the next line. If the cursor was previously on the bottom line of the viewport and TVTEXT_AUTO_SCROLL is enabled, the viewport scrolls up and the cursor is placed back on the bottom line. If the cursor was previously on the bottom line of the viewport and TVTEXT_AUTO_SCROLL is disabled, it wraps back around to the top of the viewport.
for (char c = 'A'; c <= 'Z'; ++c) { tvtext_putc(c); }
If the character value is in the range 0~31 or is 127, a special function is carried out. These functions aim to replicate those found on the BBC Micro and in subsequent versions of BBC BASIC.
Code | Function |
---|---|
0 | Nothing. This value is ignored. |
4 |
Enables auto-scrolling mode (sets the TVTEXT_AUTO_SCROLL bit in tvtext_flags). When the cursor moves below the bottom of the viewport the viewport is scrolled up and the cursor is moved back to the bottom line. When the cursor moves above the top of the viewport the viewport is scrolled down and the cursor is moved back to the top line. |
5 |
Disables auto-scrolling mode (clears the TVTEXT_AUTO_SCROLL bit in tvtext_flags). When the cursor moves below the bottom of the viewport it is moved back to the top line. When the cursor moves above the top of the viewport it is moved back to the bottom line. |
8 |
Moves the cursor left one character. If the cursor moved beyond the left edge of the viewport it is moved up a line and to the right edge of the viewport. |
9 |
Moves the cursor right one character. If the cursor moved beyond the right edge of the viewport it is moved down a line and to the left edge of the viewport. |
10 |
Moves the cursor down one line. If the cursor moved beyond the bottom edge of the viewport then either the viewport scrolls up and the cursor is moved to the bottom line or the cursor is moved to the top edge of the viewport, depending on the setting of TVTEXT_AUTO_SCROLL. |
11 |
Moves the cursor up one line. If the cursor moved beyond the top edge of the viewport then either the viewport scrolls down and the cursor is moved to the top line or the cursor is moved to the bottom edge of the viewport, depending on the setting of TVTEXT_AUTO_SCROLL. |
12 | The viewport is cleared. The character used to clear the viewport is controlled with the tvtext_cleared variable. |
13 | The text cursor is moved to the left edge of the display. |
23 |
After writing this character, you must write a further nine bytes of data. The action performed depends on the values you have written.
|
26 | This command resets the viewport to fill the entire screen and moves the cursor to its top left. |
27 | The character written immediately after this one goes directly to the screen. This allows you to print special characters in the 0~31 or 127 range. |
28 |
The next four bytes written to the screen are interpreted as the new left, bottom, right and top edges of the viewport respectively. If the cursor is outside the new viewport, it is moved to its top left – otherwise, it is left alone. |
30 | The cursor is moved to the top left of the current viewport. |
31 |
The next two bytes written to the screen are interpreted as the new column and row of the cursor respectively. The cursor will be clipped inside the current viewport. |
127 | The cursor is moved one character left. The character under the new cursor position is then cleared using the tvtext_cleared variable. |
This function writes a NUL-terminated string held in SRAM to the display using the tvtext_putc() function.
tvtext_puts("Hello, world!");
This function writes a NUL-terminated string stored in program memory to the display using the tvtext_putc() function.
tvtext_puts_P(PSTR("Hello, world!"));
This function is strongly recommended over tvtext_puts() for constant strings as it doesn't require a copy of the string to be stored in SRAM.
Moves the cursor left one character.
If the cursor moved beyond the left edge of the viewport it is moved up a line and to the right edge of the viewport.
Moves the cursor right one character.
If the cursor moved beyond the right edge of the viewport it is moved down a line and to the left edge of the viewport.
Moves the cursor down one line.
If the cursor moved beyond the bottom edge of the viewport then either the viewport scrolls up and the cursor is moved to the bottom line or the cursor is moved to the top edge of the viewport, depending on the setting of TVTEXT_AUTO_SCROLL.
Moves the cursor up one line.
If the cursor moved beyond the top edge of the viewport then either the viewport scrolls down and the cursor is moved to the top line or the cursor is moved to the bottom edge of the viewport, depending on the setting of TVTEXT_AUTO_SCROLL.
The viewport is cleared. The character used to clear the viewport is controlled with the tvtext_cleared variable.
Scrolls the viewport contents right one character.
Scrolls the viewport contents left one character.
Scrolls the viewport contents down one line.
Scrolls the viewport contents up one line.
Resets the viewport to fill the entire screen and moves the cursor to its top left.
Sets the boundaries of the text viewport. If the cursor is outside the new viewport, it is moved to its top left – otherwise, it is left alone.
The cursor is moved to the top left of the current viewport.
Moves the cursor to a particular location on the screen. The cursor will be clipped inside the current viewport.
Waits for the rasteriser to enter the vsync period. During this period the library will be generating sequences that indicate that one frame is about to end and the next is about to begin. These vertical synchronisation sequences are fortunately quite simple and so the AVR has more time to spend running your program. Updating the text buffer between frames also reduces visual tearing, producing a smoother display. You may also use this function for timing purposes – at 50Hz, calling this function fifty times in a row will produce a one second delay.
This function sets TVTEXT_CURSOR_VISIBLE and resets tvtext_cursor_flash_timer to tvtext_cursor_flash_period. It is automatically called whenever the cursor is moved.
This function can be used to retrieve a row of pixels from a particular character in the font.
The returned value stores the first pixel in the most significant bit, the second pixel in the next most significant bit, the third in the next most significant bit after that and so on. A set bit indicates a white ("on") pixel.
tvtext_clear(); // Loop over every pixel in the character. for (uint8_t row = 0; row < 8; ++row) { // Fetch a row of pixels from the font for the '@' sign. uint8_t pixels = tvtext_get_font_row('@', row); for (uint8_t column = 0; column < 6; ++column) { // Is the bit set (on?) If so, draw it on the screen as a '*'. if (bit_is_set(pixels, 7 - column)) { tvtext_cursor_move(column, row); tvtext_putc('*'); } } }
The following links were extremely useful when writing this library.
Please visit http://www.benryves.com/ for the latest news and information, or contact me at benryves@benryves.com.