The `FreeBird'
So, you want to do something cool with your AT32AP7000 powered ATNGW100? Why not build a simple mobile robot (the `FreeBird')? That's what I did, and this document describes how to go about doing it.
Here is a video of FreeBird? wandering around:
MIT teaches an introductory EECS course ( http://courses.csail.mit.edu/6.01/ ) based on mobile robots programmed using Python. I hope to explore the possibility of doing something similar for students of my country. A completely hands-on course which combines programming (with Free Software tools), basic electronics and mathematics is surely going to be exciting!
ATNGW100 `Hello, World'
Here are some tips to get you started with hacking the ATNGW100 using a Debian Etch system.
Set up Debian properly
If you have a fresh Debian Etch installation, you will have to install many packages - most importantly, `build-essential'. You will also need tools like flex and development packages for libraries like `ncurses'. Use the `synaptic' package manager to install whatever is required.
Does the board boot OK?
As soon as you power on the board, a green LED should light up. Once the system has booted, a red LED will light up (or blink). It is said that the first time you boot the board, it might take a bit longer for the RED led to be on, but I didn't notice this. If you don't see the RED led lighting up even after a few minutes, don't jump to the conclusion that the flash has been corrupted - it may be the case that your 9V supply is not working properly. Do test it out!
I have been able to power the board using a 9V `Energizer' battery. The battery voltage drops significantly after a few minutes of usage - but, surprisingly, the board kept on working even at 7.7V. Once disconnected, the battery regains some of the voltage it had lost.
Getting a console running
The next thing you want to do is get a console up and running so that you can see the bootup messages and interact with the board. Use a 3 wire `straight' serial cable (ie, a serial cable with the pins connected this way: pin 2 to 2, pin 3 to 3 and pin 5 to 5. I had some frustration with a cable that was NOT connected in this way). You can run a terminal program like `minicom' on the GNU/Linux machine (with speed set to 115200 and data format 8N1) and everything should work OK.
Setting up an Ethernet connection
The ATNGW100 has two ethernet interfaces, labelled LAN and WAN. The LAN interface has an IP address of 10.0.0.1. Install a network card on your Debian machine and assign it an IP address (assuming that the card gets identified as eth1):
ifconfig eth1 10.0.0.99
You should be able to ping the ATNGW100 now! You should also be able to log on to the board using `telnet' and transfer files using `pftp'. When using `pftp', you can log in as user `ftp'. Don't forget to set write permission on the folder /home/ftp (on the board - that's the folder where you will be transferring files to).
Building the toolchain
It's time to set up a development environment and compile your first `hello,world' application for the ATNGW100. Read the
GettingStarted page for a description of the toolchain build process.
Get binutils-2.16.1-atmel.0.99.2.tar.bz2 from the BinutilsPatches page - the file has all the necessary patches applied to it - you just have to configure and build it as explained in GettingStarted.
Building GCC is the next step. This has to be done in two steps because a full-fledged build requires the C libraries. Download gcc-4.0.2.tar.gz from one of the mirrors listed at http://gcc.gnu.org. Then get gcc-4.0.2-atmel.0.99.2.patch.bz2 from GccPatches and follow the build instructions in GettingStarted.
The C library (uclibc) can't be built without installing the kernel headers. Note that you need not actually build the kernel at this point - you just have to configure it and install the headers.
Download linux-2.6.22 from http://kernel.org and apply the patch linux-2.6.22.atmel.3.patch.bz2. Configure the kernel by running the command:
make ARCH=avr32 CROSS_COMPILE=avr32-linux- menuconfig
I don't think it is essential to do a lot of tweaking at this point - just make sure that CPU and board names specified under `System Type and Features' is OK (CPU is AT32AP7000 and board is ATNGW100). Now, install the kernel headers under a folder say XYZ by typing:
make ARCH=avr32 CROSS_COMPILE=avr32-linux- headers_install INSTALL_HDR_PATH=XYZ
Take care to specify XYZ as the location of the kernel source when configuring uclibc.
The MicroClibcPatches page says that `the patches on this page are outdated'. So get the required files from the AVR32 Linux BSP CD image at http://www.atmel.com/dyn/products/tools_card.asp?tool_id=4102. The files are under the folder software/uclibc. A README in the same folder describes the build process. Don't forget to set the kernel header path to `XYZ'; you will not have to change most other parameters when doing a `make menuconfig'. You might also try building without shared library support as this simplifies things.
Once the C library is built succesfully, you can complete the GCC build as per the instructions given in GettingStarted.
Running `Hello, world'
Now, you are ready to run your first program on the ATNGW100. Compile the code using avr32-linux-gcc and transfer the resulting `a.out' to /home/ftp of the board using the `pftp' command (log in as user `ftp'). Don't forget to set execute permission on `a.out' after transferring it to the board!
Hacking the kernel
Building the kernel
Get linux-2.6.22.atmel.3.tar.bz2 from
LinuxPatches. I tried building the kernel by trying out configuration options on my own. Don't do it. The easy way is to simply do:
make ARCH=avr32 CROSS_COMPILE=avr32-linux- atngw100_defconfig
Now, you can do a `make ARCH=avr32 CROSS_COMPILE=avr-linux- menuconfig' and tweak some parameters. Be aware that changing certain config values can result in a kernel which doesn't boot properly. Build the kernel by running:
make ARCH=avr32 CROSS_COMPILE=avr32-linux-
You should get a file `uImage' in the folder arch/avr32/boot/images/. The `uImage' is built using a command called `mkimage' which is part of the u-boot bootloader package. You will have to get u-boot-1.2.0.atmel.1.tar.bz2 from UbootPatches and build it according to the instructions given in GettingStarted.
Installing a tftp server
You can use the `synaptic' package manager and install the packages `tftp' and `tftpd'. The TFTP server is configured to look under the folder `/srv/tftp' in Debian etch. Copy the `uImage' which you have built into this folder. Now, `uboot' will be able to load it into memory through the ethernet interface and boot it properly.
Configuring the ethernet interface
I am using 2 ethernet cards on my PC - one is for the local network and the other is for communicating with the atngw100. It seems that some programs running in the background like to do bizarre things with the network interface - so it might be better to stop those programs before you try any experiment with the board. You can do this by running:
sh /etc/init.d/dbus stop
You can now assign an IP address for the interface:
ifconfig eth1 10.0.0.99
Playing with `uboot'
Reboot the board (keep it connected to the PC using the serial port as well as an ethernet cable - also, keep a terminal program like `minicom' running) and keep the `space' key pressed. This will result in `uboot' (the bootloader) suspending the default boot process and dropping into interactive mode. At the uboot prompt, type the following commands:
setenv tftpip 10.0.0.99
setenv ipaddr 10.0.0.1
setenv bootfile uImage
Now, type:
tftpboot
and `uboot' will download the `uImage' from the PC and loads it into memory. Running
bootm
will result in this kernel booting!
Running your first kernel module
Create a `dummy' module, `a.c':
#include
int init_module(void) { printk("hello\n"); return 0;}
void cleanup_module(void) { printk("world\n"); }
Write a Makefile:
obj-m := a.o
default:
make ARCH=avr32 CROSS_COMPILE=avr32-linux- -C /home/pce/kernels/linux M=`pwd`
Run `make' and build the module - you will get a file a.ko. FTP this onto the board and load it into the kernel by running `insmod a.ko'.
Programming the PIO controller
Most of the pins on the AVR32 processor act as either general purpose I/O or can be assigned to one of two peripherals. The ATNGW100 board lets us access these pins through 3 sets of connectors - J5, J6 and J7. The placement of these connectors is shown in http://www.avrfreaks.net/wiki/index.php/Documentation:NGW/NGW100_Mechanical_dimensions. The pin assignment is shown here: http://www.avrfreaks.net/wiki/index.php/Documentation:NGW/NGW100_Expansion_connectors. The connectors are not actually present on the board - you will have to solder them yourselves. I wanted to get access to the PWM channels, so I decided to start with J5.
If you look at the pinout, you will see that each pin acts as either a I/O pin or as one of two `alternate' functions. For example, pin 20 on J5 acts as I/O port pin PA28 or as either PWM0 or TIMER1-A2. We will decide whether the pin is to act as a general purpose I/O pin or whether it is to be assigned to one of the `peripherals' by setting/clearing bits in certain configuration registers.
PIO controller registers, memory map
In the AVR32 processor's virtual memory map, peripheral registers are assigned a block of addresseses which are non-cacheable and can't be translated. PIO Controller A has a set of control/status/data registers starting at 0xffe02800. The registers we are interested in, at the moment, are - PER (at offset 0), PDR (offset 4), OER (offset 0x10), SODR (offset 0x30) and CODR (offset 0x34). PER/PDR decides whether the pin should be under the control of the PIO controller or a peripheral. If we wish to make say PA28 an I/O pin, we simply have to write a pattern to PER with the D28'th bit (LSB is D0) set and all other bits clear. If we wish to assign PA28 to a peripheral, we should write the same number to PDR. This seems to be a common pattern for the AVR32 processor - instead of just having one register whose bits can be set and cleared to perform some control operation, we have pairs of enable/disable registers whose bits can be SET to do the required control operation. The bits in the OER register decide whether a pin should act as output (SET a bit to make the corresponding pin an o/p pin). Then we have the registers SODR and CODR (pin set and clear registers) using which we can assign logic high and low to pins configured as outputs.
Here is a simple kernel module which when inserted will make an LED attached to PA28 light up. The LED goes off when the module is unloaded.
#include
#include
#define PORTA (void*)0xffe02800
#define PIO_PER 0x0000
#define PIO_PDR 0x0004
#define PIO_OER 0x0010
#define PIO_SODR 0x0030
#define PIO_CODR 0x0034
int init_module(void)
{
printk("hello\n");
__raw_writel(0x10000000, PORTA + PIO_PER); // PA28 is controlled by PIOA
__raw_writel(0x10000000, PORTA + PIO_OER); // PA28 is o/p
__raw_writel(0x10000000, PORTA + PIO_SODR); // SET PA28
return 0;
}
void cleanup_module(void)
{
printk("world\n");
__raw_writel(0x10000000, PORTA + PIO_CODR); // clear PA28 to low
}
Programming the PWM unit
The AT32AP7000 processor has a PWM unit with 4 independent outputs. The unit is clocked by the clock of the peripheral bus to which it is attached (peripheral bus B). Getting PWM outputs requires first programming the Power Manager so as to enable the clock to the PWM unit.
Programming the PM (power manager)
The PWM unit is attached to the peripheral bus B. Programming the PM so as to enable the clock to the PWM unit is simply a matter of setting the D5'th bit of the PBBMASK register. But it should be done in a systematic way by using the kernel `clock' API. The procedure is as follows.
Create a structure say pwm_clk in the file arch/avr32/mach-at32ap/at32ap7000.c:
static struct clk pwm_clk = {
.name = "pwm",
.parent = &pbb_clk,
.mode = &pbb_clk_mode,
.get_rate = &pbb_clk_get_rate,
.index = 5,
};
The field `index' is the bit to be set to enable the clock to the PWM module. The other fields (except `name') are pointers to pre-defined functions. Here is the body of `pbb_clk_mode':
static void pbb_clk_mode(struct clk *clk, int enabled)
{
unsigned long flags;
u32 mask;
spin_lock_irqsave(&pm_lock, flags);
mask = pm_readl(PBB_MASK);
if (enabled)
mask |= 1 << clk->index;
else
mask &= ~(1 << clk->index);
pm_writel(PBB_MASK, mask);
spin_unlock_irqrestore(&pm_lock, flags);
}
The address of the structure `pwm_clk' should be stored in an array called `at32_clock_list' present in the same file. The function `at32_clock_init' walks through the elements of this array and then turns on clocks to all modules whose clock structure field `users' has a non-zero value. Our `pwm_clk' has not assigned a value to `users', so it will be 0. This means the PWM clock is NOT enabled during system initialization. We have to explicitly enable the clock in our PWM driver.
Here is a small module which tests out the code which we have added to the kernel.
#include
#include
#include
#define PORTA (void*)0xffe02800
#define PIO_PER 0x0000
#define PIO_PDR 0x0004
#define PIO_OER 0x0010
#define PIO_SODR 0x0030
#define PIO_CODR 0x0034
#define PM (void*)0xfff00000
#define PBBMASK_OFFSET 0x14
#define PWM_CLK_EN_BIT 5
struct clk *c;
int init_module(void)
{
unsigned long u;
c = clk_get(NULL, "pwm");
printk("hello: c = %p\n", c);
if(c == 0) {
printk("no clk pbb\n");
return 0;
}
printk("rate = %lu\n", clk_get_rate(c));
u = __raw_readl(PM + PBBMASK_OFFSET);
printk("pwm before = %d\n", (u >> PWM_CLK_EN_BIT)&1);
clk_enable(c);
u = __raw_readl(PM + PBBMASK_OFFSET);
printk("pwm after = %d\n", (u >> PWM_CLK_EN_BIT)&1);
return 0;
}
void cleanup_module(void)
{
printk("world\n");
clk_disable(c);
}
I got a speed of 65MHz for Peripheral Bus B's clock when I tested this module.
Assigning PA28 to PWM0
PWM channel 0 output is available on pin PA28. We have to assign the pin to the PWM unit. We do this by defining a function (in at32ap7000.c):
void __init at32_map_pwm(void)
{
select_peripheral(PA(28), PERIPH_A, 0);
}
and then calling this function from `setup_board' defined in arch/avr32/boards/atngw100/setup.c:
void __init setup_board(void)
{
at32_map_usart(1, 0); /* USART 1: /dev/ttyS0, DB9 */
at32_setup_serial_console(0);
at32_map_pwm();
}
Blinking LED!
Once we make the above additions to the kernel and recompile it, we are ready to program the PWM unit. In this example, we are using channel 0.
The base PWM clock (called MCLK in the documentation) is, in our case, 65MHz. This clock can be divided by 2, 4, 8, ... 1024 before being given to the PWM unit. We decide the division factor by writing a 4 bit pattern to the `CPRE' field of register CMR0 (channel mode register 0). A pattern 1010 gives us a division factor of 1024. The CMR0 register also has two bits called CPOL (polarity. If 0, the o/p waveform starts at a low level) and CALG (alignment bit - 0 for left alignment). Both bits are zero by default.
Then there are two 20 bit registers, CPRD0 (period register) and CDTY0 (duty cycle register). If we write a number say 65535 to CPRDO, the period of our PWM signal will be:
(1024 * 65535)/65000000.0
That is, around 1 second. A count of 32767 (ie, CPRD0/2) in CDTY0 will give us 50% duty cycle. Here is a test module:
#include
#include
#include
#include
#define PORTA (void*)0xffe02800
#define PIO_PER 0x0000
#define PIO_PDR 0x0004
#define PIO_OER 0x0010
#define PIO_SODR 0x0030
#define PIO_CODR 0x0034
#define PM (void*)0xfff00000
#define PWM_BASE (void*)0xfff01400
#define PBBMASK_OFFSET 0x14
#define PWM_CLK_EN_BIT 5
#define CMR0_OFFSET 0x200
#define CPRD0_OFFSET 0x208
#define CDTY0_OFFSET 0x204
#define PWM_ENA_OFFSET 0x4
#define PWM_DIS_OFFSET 0x8
struct clk *c;
int init_module(void)
{
unsigned long u;
c = clk_get(NULL, "pwm");
printk("hello: c = %p\n", c);
if(c == 0) {
printk("no clk pbb\n");
return 0;
}
printk("rate = %lu\n", clk_get_rate(c));
u = __raw_readl(PM + PBBMASK_OFFSET);
printk("pwm before = %d\n", (u >> PWM_CLK_EN_BIT)&1);
clk_enable(c);
u = __raw_readl(PM + PBBMASK_OFFSET);
printk("pwm after = %d\n", (u >> PWM_CLK_EN_BIT)&1);
/* Now starts PWM init section */
__raw_writel(10, PWM_BASE + CMR0_OFFSET);
__raw_writel(65535, PWM_BASE + CPRD0_OFFSET);
__raw_writel(32767, PWM_BASE + CDTY0_OFFSET);
__raw_writel(1, PWM_BASE + PWM_ENA_OFFSET);
return 0;
}
void cleanup_module(void)
{
printk("world\n");
__raw_writel(1, PWM_BASE + PWM_DIS_OFFSET);
clk_disable(c);
}
Programming the TC (timer/counter)
The AT32AP7000 processor has two 16 bit timer/counter units each with three independent channels. TC0 (timer/counter unit 0) is used by the GNU/Linux kernel - so we will not touch it. We shall see how to program the timer/counter unit 1.
The TC units have multiple clock sources, both internal as well as external. We will choose an internal clock, which is the clock for the peripheral bus B (for my ATNGW100, this clock is 65MHz) optionally divided by either 4, or 8 or 16 or 32. Clock selection is done by writing to the LSB 3 bits of the CMR (channel mode register). We shall leave all the other bits of this register as 0.
The clock to the TC channel is enabled by writing to the CLKEN bit (bit 0) of the CCR (channel control register). The SWTRG (s/w trigger) bit of this register (bit 2) has to be set to clear the counter and `start' the clock. Reading the CV register (counter value) gives us the count.
Note that just like we did for the PWM unit, we have to set a bit in the power manager so that the timer/counter unit becomes enabled. This is done using the `clock api' provided by the kernel.
Here is a test module which verifies the working of the TC unit.
#include
#include
#include
#include
#include
#define PORTA (void*)0xffe02800
#define PIO_PER 0x0000
#define PIO_PDR 0x0004
#define PIO_OER 0x0010
#define PIO_SODR 0x0030
#define PIO_CODR 0x0034
#define PM (void*)0xfff00000
#define PWM_BASE (void*)0xfff01400
#define TC1_BASE (void*)0xfff01000
#define PBBMASK_OFFSET 0x14
#define PWM_CLK_EN_BIT 5
#define TC1_CLK_EN_BIT 4
#define CCR_OFFSET 0x00
#define CMR_OFFSET 0x04
#define CV_OFFSET 0x10
#define CMR0_OFFSET 0x200
#define CPRD0_OFFSET 0x208
#define CDTY0_OFFSET 0x204
#define PWM_ENA_OFFSET 0x4
#define PWM_DIS_OFFSET 0x8
#define CLOCK_TRIGGER __raw_writel(5, TC1_BASE + CCR_OFFSET)
struct clk *c;
int init_module(void)
{
unsigned long u;
c = clk_get(NULL, "tc1");
printk("hello: c = %p\n", c);
if(c == 0) {
printk("no clk tc1\n");
return 0;
}
printk("rate = %lu\n", clk_get_rate(c));
u = __raw_readl(PM + PBBMASK_OFFSET);
printk("tc1 before = %d\n", (u >> TC1_CLK_EN_BIT)&1);
clk_enable(c);
u = __raw_readl(PM + PBBMASK_OFFSET);
printk("tc1 after = %d\n", (u >> TC1_CLK_EN_BIT)&1);
/* Now starts TC1 section */
/* CLK is TIMER_CLOCK3, ie, PBB_CLK/8 */
/* Mode is compare mode */
__raw_writel(2, TC1_BASE + CMR_OFFSET);
/* enable the clock to the TC1 unit */
__raw_writel(1, TC1_BASE + CCR_OFFSET);
CLOCK_TRIGGER;
udelay(100);
u = __raw_readl(TC1_BASE + CV_OFFSET);
printk("Counter Value = %lu\n", u);
return 0;
}
void cleanup_module(void)
{
printk("world\n");
__raw_writel(1, PWM_BASE + PWM_DIS_OFFSET);
clk_disable(c);
}
Measuring pulse width
Configuring a PIO pin as input
Let's say we wish to configure pin PB00 as input (this is pin 24 on connector J5) with internal pull-ups disabled. Here is a segment of code which does the required initializations:
__raw_writel(0x1, PORTB + PIO_PER); // PB00 is controlled by PIOB
__raw_writel(0x1, PORTB + PIO_ODR); // PB00 is i/p
__raw_writel(0x1, PORTB + PIO_PUDR); // PB00 pull-up is disabled
Measuring width of a pulse applied on PB00
Let's say we wish to measure the ON time of a waveform produced by the PWM unit on pin PA28 (pin 20 of connector J5).
unsigned int high_period(void)
{
unsigned int u;
while(1) {
u = __raw_readl(PORTB + PIO_PDSR);
if((u & 1) == 0) break; // got a LOW
}
while(1) {
u = __raw_readl(PORTB + PIO_PDSR);
if(u & 1) break; // got a LOW->HIGH edge
}
CLOCK_TRIGGER;
while(1) {
u = __raw_readl(PORTB + PIO_PDSR);
if((u & 1) == 0) break; //Got a HIGH->LOW edge
}
u = __raw_readl(TC1_BASE + CV_OFFSET);
printk("high period = %lu\n", u);
return u;
}
The timer/counter unit TC1 should be initialized beforehand with proper pre-scaling.
// clock is PBB_CLK/32. PBB_CLK is 65MHz
__raw_writel(4, TC1_BASE + CMR_OFFSET);
__raw_writel(1, TC1_BASE + CCR_OFFSET);
Controlling a Parallax continuous rotation servo
The FreeBird? depends on two Parallax continuous rotation servo's to move around.
The Parallax continuous rotation servo has to be first `centred' by applying a signal with an ON time of 1.5ms and period of 20ms. The servo potentiometer has to be adjusted in such a way that the servo remains motionless. Once this calibration is over, a signal with an ON time of 1.7ms should rotate the servo in a counter-clockwise direction and a signal with an ON time of 1.3ms will make the servo rotate in a clockwise direction. Doing this is quite easy using the PWM units of the AVR32.
Here is how to do the timing calculation. The master clock to the PWM unit is 65MHz. Divide that by 64 to get a clock with a period of approximately 1 micro seconds (precisely 0.98 micro seconds). Then, write a count of 20408 to the PWM period register (20408 * 0.98 = 20000 micro seconds = 20 milli seconds) and a count of 1530 (1530 * .98 = 1500 micro seconds = 1.5 milli seconds) to the duty cycle register. Set the PWM polarity bit to 1 (signal starts HIGH).
Booting from SD Card
We will install all the tools required to run FreeBird? (including the patched kernel) on an SD card.
The `u-boot' boot loader can boot an image from an SD card. Once at the `u-boot' prompt, type `mmcinit' to initialize the card - this should also give you some information regarding the card (like its size). The command
ext2ls mmc 0:1 /
will show you all the files in the root directory of the card. Note that for the above command to work, the first partition on the card should be formatted as ext2.
The following commands will load and boot an image present on the card:
ext2load mmc 0:1 0x90400000 uImage
bootm 0x90400000
Once booting is over, you should see /dev/mmcblk0p1 mounted on /media/mmcblk0p1.
`u-boot' environment variables
By default, the ATNGW100 boots a kernel which resides on the on-board flash memory. The modified kernel, as well as all the programs required to run FreeBird? are on an SD card - so we have to make sure that when powered on, the ATNGW100 boots from the SD card. This is done by saving proper `environment' variables.
setenv tftpip 10.0.0.99
setenv ipaddr 10.0.0.1
setenv bootfile uImage
setenv ethact macb1
setenv bootcmd 'mmcinit;ext2load mmc 0:1 0x90400000 uImage;bootm'
saveenv
Writing an init script
As usual, /etc/init.d holds scripts which are executed during booting. These scripts have to be invoked explicitly from a file called rcS. Here is my init script:
insmod /media/mmcblk0p1/robodriver.ko
major=`grep freebird /proc/devices | awk '{print $1}'`
echo $major
mknod /tmp/freebird c $major 0
/media/mmcblk0p1/drive &
Add the name of this script to the end of rcS.
The script loads the pwm driver (robodriver.ko), finds out its major number, creates a device file and runs the control program.
Make the FreeBird? move around randomly
Here is a small program which makes FreeBird? wander aimlessly:
#include "robolib.h"
#include
main()
{
int i;
robo_init();
enable_motor(0);
enable_motor(1);
while(1) {
i = 1 + (int)((4.0 * rand())/(RAND_MAX + 1.0));
if(i == 1) {
forward();
sleep(3);
}
else if(i == 2) {
backward();
sleep(3);
}
else if(i == 3) {
left_turn();
sleep(1);
}
else if(i == 4){
right_turn();
sleep(1);
}
stop();
sleep(1);
}
disable_motor(0);
disable_motor(1);
}
This code is linked against a `robolib.c' which defines functions like enable_motor. The code in robolib.c in turn invokes an `ioctl' defined in the kernel module robodriver.ko for starting/stopping PWM generation, controlling the pulse width etc.
Running Lua
Lua is a powerful, lightweight, embeddable scripting language. Download the latest version from http://www.lua.org/download.html. After unpacking the distribution, change into the `src' directory and simply execute:
make all CC=avr32-linux-gcc AR="avr32-linux-ar rcu" RANLIB="avr32-linux-ranlib"
Copy the resulting executable (in my case, statically linked) to the SD card and have fun with it! Read http://www.lua.org/pil/ to know more about Lua!
Edit