Este manual cubre desde los conceptos básicos hasta la implementación avanzada de drivers de impresora para Linux, compatible con cualquier modelo de impresora.
# Ejemplo de filtro básico (script shell)
#!/bin/sh
# Filtro para impresora genérica
# Leer cabecera CUPS
read -r job user title copies options
# Procesar cada copia
for ((i=1; i<=$copies; i++))
do
# Convertir PostScript a lenguaje de impresora
gs -q -dNOPAUSE -dBATCH -sDEVICE="pxlmono" \
-sOutputFile=- - | sed 's/^/PRINT:/'
done
# Enviar código de finalización
echo -e "\x1B\x45" # ESC E = Reset printer
exit 0
/* Ejemplo básico de driver en C para impresora térmica */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
int fd = open("/dev/usb/lp0", O_WRONLY);
if (fd < 0) {
perror("Error al abrir dispositivo");
return 1;
}
// Comandos de inicialización (ESC/POS)
char init_cmds[] = {0x1B, 0x40}; // Reset
write(fd, init_cmds, sizeof(init_cmds));
// Escribir texto
dprintf(fd, "Factura #1234\nProducto\tCantidad\n");
// Cortar papel (si la impresora lo soporta)
char cut_cmd[] = {0x1D, 0x56, 0x00}; // Comando de corte
write(fd, cut_cmd, sizeof(cut_cmd));
close(fd);
return 0;
}
// Estructura básica de un driver de impresora en C
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/ch9.h>
// Estructura para nuestra impresora
struct usb_printer {
struct usb_device *udev;
struct usb_interface *interface;
unsigned char *bulk_out_buffer;
size_t bulk_out_size;
int bulk_out_endpointAddr;
};
// Tabla de dispositivos soportados
static struct usb_device_id id_table[] = {
{ USB_DEVICE(0x04F9, 0x0042) }, // Brother HL-2140
{ } /* Terminador */
};
MODULE_DEVICE_TABLE(usb, id_table);
// Funciones del driver
static int printer_probe(struct usb_interface *interface,
const struct usb_device_id *id);
static void printer_disconnect(struct usb_interface *interface);
// Estructura principal del driver USB
static struct usb_driver printer_driver = {
.name = "usb_printer",
.probe = printer_probe,
.disconnect = printer_disconnect,
.id_table = id_table,
};
// Registro del módulo
module_usb_driver(printer_driver);
// Función probe - llamada cuando se conecta una impresora compatible
static int printer_probe(struct usb_interface *interface,
const struct usb_device_id *id) {
struct usb_device *udev = interface_to_usbdev(interface);
struct usb_printer *dev;
int retval = -ENOMEM;
// Asignar memoria para nuestra estructura
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->udev = udev;
dev->interface = interface;
// Configurar endpoints USB
struct usb_host_interface *iface_desc = interface->cur_altsetting;
struct usb_endpoint_descriptor *endpoint;
for (int i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
endpoint = &iface_desc->endpoint[i].desc;
if (usb_endpoint_is_bulk_out(endpoint)) {
dev->bulk_out_size = usb_endpoint_maxp(endpoint);
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
// Asignar buffer para transferencias
dev->bulk_out_buffer = kmalloc(dev->bulk_out_size, GFP_KERNEL);
if (!dev->bulk_out_buffer) {
kfree(dev);
return -ENOMEM;
}
}
}
// Guardar nuestra estructura en la interfaz
usb_set_intfdata(interface, dev);
printk(KERN_INFO "Impresora USB conectada\n");
return 0;
}
// Función disconnect - llamada al desconectar la impresora
static void printer_disconnect(struct usb_interface *interface) {
struct usb_printer *dev = usb_get_intfdata(interface);
// Liberar recursos
kfree(dev->bulk_out_buffer);
kfree(dev);
printk(KERN_INFO "Impresora USB desconectada\n");
}
Lenguaje | Descripción | Ejemplo de Comando |
---|---|---|
ESC/P | Epson Standard Code for Printers | \x1B\x40 (Reset) |
PCL | Printer Command Language (HP) | \x1B\x45 (Bold on) |
PostScript | Lenguaje de descripción de página | %!PS\nshowpage |
ESC/POS | Para impresoras térmicas | \x1D\x56\x00 (Cortar papel) |
// Función para enviar datos a la impresora
static ssize_t printer_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos) {
struct usb_printer *dev = file->private_data;
int retval;
int bytes_written = 0;
// Verificar que la impresora esté conectada
if (!dev->udev)
return -ENODEV;
// Copiar datos del espacio de usuario al kernel
if (copy_from_user(dev->bulk_out_buffer, user_buf, count))
return -EFAULT;
// Enviar datos a la impresora
retval = usb_bulk_msg(dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
dev->bulk_out_buffer, count, &bytes_written, 5000);
if (retval)
printk(KERN_ERR "Error al enviar datos: %d\n", retval);
return bytes_written;
}
*% Generic PostScript Printer Driver
*% Manufacturer: MyPrinter
*% ModelName: Generic PS
*% NickName: My Printer
*% FileVersion: 1.0
*PCFileName: "myprinter.ppd"
*LanguageVersion: English
*LanguageEncoding: ISOLatin1
*ColorDevice: False
*OpenUI *PageSize/Page Size: PickOne
*OrderDependency: 10 AnySetup *PageSize
*DefaultPageSize: Letter
*PageSize Letter/US Letter: "
*<</PageSize[612 792]>>setpagedevice
*End
*PageSize A4/A4: "
*<</PageSize[595 842]>>setpagedevice
*End
*DefaultFont: Courier
*Font AvantGarde-Book: Standard "(001.006)" Standard ROM
*Font Courier: Standard "(001.004)" Standard ROM
*% Filtro para nuestro driver
*cupsFilter: "application/vnd.cups-postscript 0 myprinter_filter"
1. Crear archivo de filtro en /usr/lib/cups/filter/myprinter_filter
:
#!/bin/bash
# Leer cabecera CUPS
read -r job user title copies options
# Convertir PostScript a lenguaje de impresora
if [ "$1" = "0" ]; then
# Usar Ghostscript para conversión
exec gs -q -dNOPAUSE -dBATCH -sDEVICE="pxlmono" \
-sOutputFile=- -
else
# Opción directa para impresoras PostScript
cat
fi
2. Hacer ejecutable el filtro:
3. Instalar el PPD:
4. Agregar la impresora:
Listar dispositivos USB conectados (ver ID de impresora)
Obtener información técnica de la impresora
Monitorear errores de CUPS en tiempo real
Probar el filtro sin imprimir
// Función mejorada con manejo de errores
static ssize_t printer_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos) {
struct usb_printer *dev = file->private_data;
int retval;
int bytes_written = 0;
// Verificar parámetros
if (!dev || !user_buf || count == 0)
return -EINVAL;
if (!dev->udev)
return -ENODEV;
if (count > dev->bulk_out_size)
count = dev->bulk_out_size;
// Copiar datos de manera segura
if (copy_from_user(dev->bulk_out_buffer, user_buf, count))
return -EFAULT;
// Enviar en bloques para impresoras lentas
while (bytes_written < count) {
int chunk_size = min(count - bytes_written, dev->bulk_out_size);
retval = usb_bulk_msg(dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
dev->bulk_out_buffer + bytes_written,
chunk_size, &chunk_size, 5000);
if (retval) {
printk(KERN_ERR "Error al enviar datos: %d\n", retval);
break;
}
bytes_written += chunk_size;
}
return bytes_written ? bytes_written : retval;
}
// Estructura extendida con buffer circular
struct usb_printer {
struct usb_device *udev;
struct usb_interface *interface;
// Buffers para transferencia
unsigned char *bulk_out_buffer;
size_t bulk_out_size;
int bulk_out_endpointAddr;
// Buffer circular
struct circular_buffer {
unsigned char *data;
size_t size;
size_t head;
size_t tail;
spinlock_t lock;
} print_buffer;
// Workqueue para envío asíncrono
struct work_struct printer_work;
};
// Función para agregar datos al buffer
static int buffer_write(struct usb_printer *dev,
const char *data, size_t count) {
unsigned long flags;
size_t space_avail;
spin_lock_irqsave(&dev->print_buffer.lock, flags);
space_avail = dev->print_buffer.size -
((dev->print_buffer.head - dev->print_buffer.tail) %
dev->print_buffer.size) - 1;
if (space_avail < count) {
spin_unlock_irqrestore(&dev->print_buffer.lock, flags);
return -ENOSPC; // Buffer lleno
}
for (size_t i = 0; i < count; i++) {
dev->print_buffer.data[dev->print_buffer.head] = data[i];
dev->print_buffer.head =
(dev->print_buffer.head + 1) % dev->print_buffer.size;
}
spin_unlock_irqrestore(&dev->print_buffer.lock, flags);
// Programar trabajo de envío
schedule_work(&dev->printer_work);
return count;
}
// Workqueue handler para envío asíncrono
static void printer_work_handler(struct work_struct *work) {
struct usb_printer *dev = container_of(work, struct usb_printer, printer_work);
unsigned long flags;
size_t bytes_to_send;
int retval;
spin_lock_irqsave(&dev->print_buffer.lock, flags);
// Calcular cantidad de datos a enviar
bytes_to_send = (dev->print_buffer.head - dev->print_buffer.tail) %
dev->print_buffer.size;
if (bytes_to_send == 0) {
spin_unlock_irqrestore(&dev->print_buffer.lock, flags);
return;
}
// Copiar datos al buffer de transferencia USB
if (dev->print_buffer.tail + bytes_to_send <= dev->print_buffer.size) {
memcpy(dev->bulk_out_buffer,
dev->print_buffer.data + dev->print_buffer.tail,
bytes_to_send);
} else {
size_t first_chunk = dev->print_buffer.size - dev->print_buffer.tail;
memcpy(dev->bulk_out_buffer,
dev->print_buffer.data + dev->print_buffer.tail,
first_chunk);
memcpy(dev->bulk_out_buffer + first_chunk,
dev->print_buffer.data,
bytes_to_send - first_chunk);
}
dev->print_buffer.tail =
(dev->print_buffer.tail + bytes_to_send) % dev->print_buffer.size;
spin_unlock_irqrestore(&dev->print_buffer.lock, flags);
// Enviar datos
retval = usb_bulk_msg(dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
dev->bulk_out_buffer, bytes_to_send, NULL, 5000);
if (retval)
printk(KERN_ERR "Error en envío asíncrono: %d\n", retval);
}
/* Driver para impresora térmica ESC/POS */
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
// Comandos ESC/POS
#define ESC 0x1B
#define GS 0x1D
#define RESET_PRINTER {ESC, 0x40}
#define CUT_PAPER {GS, 0x56, 0x00}
#define SET_LINE_SPACING {ESC, 0x33, 24} // 24/180 pulgadas
struct thermal_printer {
struct usb_device *udev;
struct usb_interface *interface;
unsigned char *bulk_out_buffer;
size_t bulk_out_size;
int bulk_out_endpointAddr;
struct mutex lock; // Para acceso concurrente
};
// Dispositivos soportados
static struct usb_device_id id_table[] = {
{ USB_DEVICE(0x0416, 0x5011) }, // WinPOS thermal printer
{ USB_DEVICE(0x04b8, 0x0e03) }, // Epson TM-T88V
{ } /* Terminador */
};
MODULE_DEVICE_TABLE(usb, id_table);
// Funciones del driver
static int thermal_probe(struct usb_interface *interface,
const struct usb_device_id *id);
static void thermal_disconnect(struct usb_interface *interface);
static int thermal_open(struct inode *inode, struct file *file);
static ssize_t thermal_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos);
static long thermal_ioctl(struct file *file, unsigned int cmd,
unsigned long arg);
// Operaciones de archivo
static const struct file_operations thermal_fops = {
.owner = THIS_MODULE,
.open = thermal_open,
.write = thermal_write,
.unlocked_ioctl = thermal_ioctl,
};
// Estructura principal del driver USB
static struct usb_driver thermal_driver = {
.name = "thermal_printer",
.probe = thermal_probe,
.disconnect = thermal_disconnect,
.id_table = id_table,
};
// Inicialización del módulo
module_usb_driver(thermal_driver);
// Implementación de funciones
static int thermal_probe(struct usb_interface *interface,
const struct usb_device_id *id) {
struct usb_device *udev = interface_to_usbdev(interface);
struct thermal_printer *dev;
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
int retval = -ENOMEM;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->udev = udev;
dev->interface = interface;
mutex_init(&dev->lock);
// Configurar endpoint bulk out
iface_desc = interface->cur_altsetting;
for (int i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
endpoint = &iface_desc->endpoint[i].desc;
if (usb_endpoint_is_bulk_out(endpoint)) {
dev->bulk_out_size = usb_endpoint_maxp(endpoint);
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_out_buffer = kmalloc(dev->bulk_out_size, GFP_KERNEL);
if (!dev->bulk_out_buffer)
goto error;
}
}
// Inicializar impresora
unsigned char init_cmds[] = RESET_PRINTER;
int bytes_sent;
retval = usb_bulk_msg(udev,
usb_sndbulkpipe(udev, dev->bulk_out_endpointAddr),
init_cmds, sizeof(init_cmds), &bytes_sent, 1000);
if (retval)
printk(KERN_WARNING "No se pudo inicializar impresora\n");
usb_set_intfdata(interface, dev);
printk(KERN_INFO "Impresora térmica conectada\n");
return 0;
error:
if (dev->bulk_out_buffer)
kfree(dev->bulk_out_buffer);
kfree(dev);
return retval;
}
// Implementación completa de thermal_write, thermal_ioctl, etc...
// (similar a ejemplos anteriores pero con comandos ESC/POS)
1. Crear Makefile:
obj-m := thermal_printer.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
2. Compilar:
3. Cargar módulo:
4. Verificar carga:
5. Probar impresión:
/* Ejemplo de aplicación para imprimir factura */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
// Comandos ESC/POS
void print_header(int fd) {
char cmd[] = {0x1B, 0x21, 0x30}; // Texto grande
write(fd, cmd, sizeof(cmd));
dprintf(fd, "MI EMPRESA\n");
cmd[2] = 0x00; // Reset tamaño
write(fd, cmd, sizeof(cmd));
dprintf(fd, "Factura #1234\n\n");
}
int main() {
int fd = open("/dev/thermal0", O_WRONLY);
if (fd < 0) {
perror("Error al abrir impresora");
return 1;
}
print_header(fd);
dprintf(fd, "Producto\tCant\tPrecio\n");
dprintf(fd, "----------------------------\n");
dprintf(fd, "Laptop\t1\t$1200.00\n");
dprintf(fd, "Mouse\t2\t$20.00\n");
dprintf(fd, "----------------------------\n");
dprintf(fd, "Total:\t\t$1240.00\n\n");
// Cortar papel
char cut_cmd[] = {0x1D, 0x56, 0x00};
write(fd, cut_cmd, sizeof(cut_cmd));
close(fd);
return 0;
}
1. Crear archivo de filtro /usr/lib/cups/filter/thermal_filter
:
#!/bin/bash
# Leer cabecera CUPS
read -r job user title copies options
# Convertir a texto simple y añadir comandos ESC/POS
{
# Reset printer
echo -ne "\x1B\x40"
# Procesar cada copia
for ((i=1; i<=$copies; i++)); do
# Convertir PostScript/PDF a texto
if [[ "$1" = "application/pdf" ]]; then
pdftotext -layout - -
elif [[ "$1" = "application/postscript" ]]; then
pstotext -
else
cat -
fi
done
# Corte de papel al final
echo -ne "\x1D\x56\x00"
} > /dev/thermal0
exit 0
2. Crear archivo PPD básico:
*% Basic thermal printer
*PPD-Adobe: "4.3"
*FormatVersion: "4.3"
*FileVersion: "1.0"
*LanguageVersion: English
*Manufacturer: "Generic"
*Product: "(Thermal Printer)"
*ModelName: "Generic Thermal"
*ShortNickName: "Thermal Printer"
*NickName: "Generic Thermal Printer"
*PCFileName: "thermal.ppd"
*LanguageEncoding: ISOLatin1
*ColorDevice: False
*DefaultPageSize: 80mm
*PageSize 80mm/80mm: "
*<</PageSize[280 10000]>>setpagedevice
*End
*cupsFilter: "application/pdf 0 thermal_filter"
*cupsFilter: "application/postscript 0 thermal_filter"
*cupsFilter: "text/plain 0 thermal_filter"
Este manual cubre los aspectos esenciales para desarrollar drivers de impresora en Linux, desde control básico hasta integración avanzada con CUPS. Para implementaciones específicas, consulta la documentación del fabricante de la impresora.