My various experiments to make my RC Car (RC10B4) and a small tracked vehicle autonomous.
Friday, 17 June 2011
Secret Project: Error
During initial testing the secret project broke. I used balsa wood for part of the construction, this wasn't strong enough... The motor was ripped out and the arm broke. This project isn't as finished as I thought. The project won't be published until next Friday.
Secret Project
I have completed a secret project. It's not related to my car (Father's Day is very soon). Here is my very tidy workspace when I finished, I couldn't use my desk since its a mess. I will post a video of it working when it has been successfully deployed to it's target. See if you can guess what the project is...
Tuesday, 14 June 2011
Mobile version now available
I have just enabled a mobile version of my blog as well. I can get to it via the blogger app on android, but hopefully anyone using a mobile should see it.
Let me know if you can see the mobile version or if there are any problems. PC users should be unaffected.
I'm currently working on updated software for my car.
Monday, 13 June 2011
Arduino and Matlab
I wanted to use Matlab's optimisation tool to optimise the sweep timings of the infra red sensor. To do this required the Arduino and Matlab have to communicate. Now Matlab has a toolbox for this, but I wanted my Arduino code as unchanged as possible and to run as fast as it would in normal operating conditions. So I decided to get Matlab to only talk to the Arduino via the USB cable, not program it. The Matlab code I used is this
function y=scan_results(x)
try
dt_servo=x(1);
dt_ir=x(2);
n=x(3);
resolution=20;
%open serial port
s = serial('COM4','BaudRate',115200);
set(s,'DataBits', 8);
set(s,'StopBits', 1);
set(s,'Timeout', 60);
fopen(s);
%Read a line of data
line=fgetl(s);
fprintf('%s\n',line);
%send the dt_servo value
fwrite(s,int8(dt_servo));
%send the dt_ir value
fwrite(s,int8(dt_ir));
%send the n value
fwrite(s,int8(n));
%Read data from Arduino
angle=zeros(3,resolution+1);
old_map=zeros(3,resolution+1);
new_map=zeros(3,resolution+1);
scan_right=zeros(3,resolution+1);
scan_left=zeros(3,resolution+1);
for i=1:3
%Read angle data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
angle(i,:)=str2num(line);
%Read old_map data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
old_map(i,:)=str2num(line);
%Read new_map data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
new_map(i,:)=str2num(line);
%Read scan_right data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
scan_right(i,:)=str2num(line);
%Read scan_left data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
scan_left(i,:)=str2num(line);
% figure(i)
% polar((angle(i,:)/180)*pi,scan_right(i,:),'b')
% hold on
% polar((angle(i,:)/180)*pi,scan_left(i,:),'r')
% hold off
y=0;
for j=1:resolution+1
y=y+sqrt((scan_right(i,j)-scan_left(i,j))^2); %objective function
end
end
y=y+(dt_servo+dt_ir+n)*1; %objective function
catch
%close serial port
fclose(s);
delete(s);
clear s;
end
try
%close serial port
fclose(s);
delete(s);
clear s;
catch
end
fprintf('Results Obtained\n\n');
This function that opens a serial port to the Arduino with the same baud rate as the Serial.begin command on the Arduino. The databits and stopbits also need to be defined for it to work. It all needs to be in a try catch mechanism, so that the serial port is always closed. If the port isn't closed then you have to exit Matlab and reopen it. It was written to allow a Matlab optimiser to call it and it returns the value of my objective function. The rest of the program makes more sense when you can see what the Arduino code is doing.
#include <Servo.h>
#define resolution 20 //larger number means more accurate, but also more ram used
#define totalangle 100 //total angle that it turns
#define scannercentre 79 //the scanner servo centre location
#define scannermin 3 //the pwm value to give an angle of 0 degrees
#define scannermax 152 //the pwm value to give an angle of 180 degrees
//define servos
Servo scanner; // create servo object to control the sweeping servo
//variables
int res2=resolution*0.5;
boolean initial=true;
int i,j,k;
int dt_servo=10000; //delay for scanner servo to move to position in ms
int dt_ir=47; //delay for the ir sensor to refresh in ms
int n=10; //number of readings to average from ir sensor
int reading; // used for taking the averages
float dist; // used for calculating the distance in cm
int new_map[resolution+offset+1]; //store the distances from the ir sensor
int old_map[resolution+offset+1]; //store the distances from the ir sensor
int scan_left[resolution+offset+1]; //store the distances from the ir sensor
int scan_right[resolution+offset+1]; //store the distances from the ir sensor
int angle[resolution+1]; //stores the angles at which data was taken
boolean run=false;
void setup()
{
scanner.attach(6); // attaches the servo on pin 6 to the servo object
Serial.begin(115200); // sets the speed of the serial connection to the computer
scanner.write(scannercentre); // sets the scanner servo to the centre
analogReference(EXTERNAL); // Set the ADC reference voltage to external (only use if using 3V, read this first)
//calculate the angles to be used
for(i=0;i<res2;i++) { //right sweep
angle[i]=90-((res2-i)*(totalangle/resolution));
}
angle[res2]=90; //centre
for(i=res2+1;i<resolution+1;i++) { //left sweep
angle[i]=90+((i-res2)*(totalangle/resolution));
}
//for debugging...
// for(i=0;i<resolution+1;i++) {
// Serial.print(i);
// Serial.print(" ");
// Serial.println(angle[i]);
// }
pinMode(13,OUTPUT);
digitalWrite(13,HIGH);
delay(1000);
digitalWrite(13,LOW);
delay(1000);
}
// This runs continuously on the arduino
void loop() {
if(run==false) {
Serial.println("Waiting for inputs");
dt_servo=Serial.read();
while(dt_servo==-1) {
dt_servo=Serial.read();
delay(100);
}
Serial.print("dt_servo = ");
Serial.println(dt_servo);
dt_ir=Serial.read();
while(dt_ir==-1) {
dt_ir=Serial.read();
delay(100);
}
Serial.print("dt_ir = ");
Serial.println(dt_ir);
n=Serial.read();
while(n==-1) {
n=Serial.read();
delay(100);
}
Serial.print("n = ");
Serial.println(n);
Serial.println("Done Reading");
digitalWrite(13,HIGH);
run=true;
}
else {
for(k=0;k<3;k++) {
for(i=0;i<(resolution*2)+2;i++) {
//Look
look();
}
//print maps
Serial.println("Angle:");
for(i=0;i<resolution+1;i++) {
Serial.print(angle[i]);
if(i!=resolution) {
Serial.print(" ");
}
else {
Serial.println(" ");
}
}
Serial.println("Old Map:");
for(i=0;i<resolution+1;i++) {
Serial.print(old_map[i]);
for(i=0;i<resolution+1;i++) {
Serial.print(new_map[i]);
for(i=0;i<resolution+1;i++) {
Serial.print(scan_right[i]);
for(i=0;i<resolution+1;i++) {
Serial.print(scan_left[i]);
}
run=false;
digitalWrite(13,LOW);
}
}
void look() {
if(i==0) { //read front
//move servo to position
scanner.write(mapscanner(angle[res2]));
delay(dt_servo);
take_reading();
//update map
old_map[res2]=new_map[res2]; //update old map
new_map[res2]=(int)dist;
scan_right[res2]=(int)dist;
}
else if(i>0 && i<res2+1) { //sweep right
//move servo to position
scanner.write(mapscanner(angle[res2-i]));
delay(dt_servo);
take_reading();
//update map
old_map[res2-i]=new_map[res2-i]; //update old map
new_map[res2-i]=(int)dist;
scan_right[res2-i]=(int)dist;
}
else if(i>res2 && i<resolution+1) { //sweep left to centre
//move servo to position
scanner.write(mapscanner(angle[i-res2-1]));
delay(dt_servo);
take_reading();
//update map|
new_map[i-res2-1]+=(int)dist;
new_map[i-res2-1]*=0.5;
scan_left[i-res2-1]=(int)dist;
}
else if(i==resolution+1) { //read front
scanner.write(mapscanner(angle[res2]));
delay(dt_servo);
take_reading();
//update map by averaging the two values
new_map[res2]+=(int)dist;
new_map[res2]*=0.5;
scan_left[res2]=(int)dist;
}
else if(i>resolution+1 && i<resolution*1.5+2) { //sweep left
//move servo to position
scanner.write(mapscanner(angle[res2+1+i-resolution-2]));
delay(dt_servo);
take_reading();
//update map
old_map[res2+1+i-resolution-2]=new_map[res2+1+i-resolution-2]; //update old map
new_map[res2+1+i-resolution-2]=(int)dist;
scan_left[res2+1+i-resolution-2]=(int)dist;
}
else if(i>resolution*1.5+1 && i<resolution*2+2) { //sweep right to centre
//move servo to position
scanner.write(mapscanner(angle[resolution-(i-resolution-res2-2)]));
delay(dt_servo);
take_reading();
//update map|
new_map[resolution-(i-resolution-res2-2)]+=(int)dist;
new_map[resolution-(i-resolution-res2-2)]*=0.5;
scan_right[resolution-(i-resolution-res2-2)]=(int)dist;
}
}
//map the desired angle to the value required to move the servo to that angle
int mapscanner(int angle) {
//Serial.println(angle); //for debugging the code
float u=angle/180.0;
//Serial.println(u); //for debugging the code
return(scannermin+(u*(scannermax-scannermin)));
}
//take readings from ir sensor
void take_reading() {
delay(dt_ir); //wait for ir sensor to refresh
reading=0; // needs to start at 0 and then n readings will be added to it
for(j=0;j<n;j++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
reading/=n; // finally average the n reading
dist=12214.16*pow(reading*0.5,-1.08); // this calculates the distance
if(dist<10) {
dist=0;
}
else if(dist>80) {
dist=80;
}
}
function y=scan_results(x)
try
dt_servo=x(1);
dt_ir=x(2);
n=x(3);
resolution=20;
%open serial port
s = serial('COM4','BaudRate',115200);
set(s,'DataBits', 8);
set(s,'StopBits', 1);
set(s,'Timeout', 60);
fopen(s);
%Read a line of data
line=fgetl(s);
fprintf('%s\n',line);
%send the dt_servo value
fwrite(s,int8(dt_servo));
%Read a line of data
line=fgetl(s);
fprintf('%s\n',line);
%send the dt_ir value
fwrite(s,int8(dt_ir));
%Read a line of data
line=fgetl(s);
fprintf('%s\n',line);
%send the n value
fwrite(s,int8(n));
%Read a line of data
line=fgetl(s);
fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
fprintf('%s\n',line);
%Read data from Arduino
angle=zeros(3,resolution+1);
old_map=zeros(3,resolution+1);
new_map=zeros(3,resolution+1);
scan_right=zeros(3,resolution+1);
scan_left=zeros(3,resolution+1);
for i=1:3
%Read angle data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
angle(i,:)=str2num(line);
%Read old_map data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
old_map(i,:)=str2num(line);
%Read new_map data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
new_map(i,:)=str2num(line);
%Read scan_right data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
scan_right(i,:)=str2num(line);
%Read scan_left data
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
%Read a line of data
line=fgetl(s);
% fprintf('%s\n',line);
scan_left(i,:)=str2num(line);
% figure(i)
% polar((angle(i,:)/180)*pi,scan_right(i,:),'b')
% hold on
% polar((angle(i,:)/180)*pi,scan_left(i,:),'r')
% hold off
y=0;
for j=1:resolution+1
y=y+sqrt((scan_right(i,j)-scan_left(i,j))^2); %objective function
end
end
y=y+(dt_servo+dt_ir+n)*1; %objective function
catch
%close serial port
fclose(s);
delete(s);
clear s;
end
try
%close serial port
fclose(s);
delete(s);
clear s;
catch
end
fprintf('Results Obtained\n\n');
This function that opens a serial port to the Arduino with the same baud rate as the Serial.begin command on the Arduino. The databits and stopbits also need to be defined for it to work. It all needs to be in a try catch mechanism, so that the serial port is always closed. If the port isn't closed then you have to exit Matlab and reopen it. It was written to allow a Matlab optimiser to call it and it returns the value of my objective function. The rest of the program makes more sense when you can see what the Arduino code is doing.
#include <Servo.h>
#define resolution 20 //larger number means more accurate, but also more ram used
#define totalangle 100 //total angle that it turns
#define scannercentre 79 //the scanner servo centre location
#define scannermin 3 //the pwm value to give an angle of 0 degrees
#define scannermax 152 //the pwm value to give an angle of 180 degrees
//define servos
Servo scanner; // create servo object to control the sweeping servo
//variables
int res2=resolution*0.5;
boolean initial=true;
int i,j,k;
int dt_servo=10000; //delay for scanner servo to move to position in ms
int dt_ir=47; //delay for the ir sensor to refresh in ms
int n=10; //number of readings to average from ir sensor
int reading; // used for taking the averages
float dist; // used for calculating the distance in cm
int new_map[resolution+offset+1]; //store the distances from the ir sensor
int old_map[resolution+offset+1]; //store the distances from the ir sensor
int scan_left[resolution+offset+1]; //store the distances from the ir sensor
int scan_right[resolution+offset+1]; //store the distances from the ir sensor
int angle[resolution+1]; //stores the angles at which data was taken
boolean run=false;
void setup()
{
scanner.attach(6); // attaches the servo on pin 6 to the servo object
Serial.begin(115200); // sets the speed of the serial connection to the computer
scanner.write(scannercentre); // sets the scanner servo to the centre
analogReference(EXTERNAL); // Set the ADC reference voltage to external (only use if using 3V, read this first)
//calculate the angles to be used
for(i=0;i<res2;i++) { //right sweep
angle[i]=90-((res2-i)*(totalangle/resolution));
}
angle[res2]=90; //centre
for(i=res2+1;i<resolution+1;i++) { //left sweep
angle[i]=90+((i-res2)*(totalangle/resolution));
}
//for debugging...
// for(i=0;i<resolution+1;i++) {
// Serial.print(i);
// Serial.print(" ");
// Serial.println(angle[i]);
// }
pinMode(13,OUTPUT);
digitalWrite(13,HIGH);
delay(1000);
digitalWrite(13,LOW);
delay(1000);
}
// This runs continuously on the arduino
void loop() {
if(run==false) {
Serial.println("Waiting for inputs");
dt_servo=Serial.read();
while(dt_servo==-1) {
dt_servo=Serial.read();
delay(100);
}
Serial.print("dt_servo = ");
Serial.println(dt_servo);
dt_ir=Serial.read();
while(dt_ir==-1) {
dt_ir=Serial.read();
delay(100);
}
Serial.print("dt_ir = ");
Serial.println(dt_ir);
n=Serial.read();
while(n==-1) {
n=Serial.read();
delay(100);
}
Serial.print("n = ");
Serial.println(n);
Serial.println("Done Reading");
digitalWrite(13,HIGH);
run=true;
}
else {
for(k=0;k<3;k++) {
for(i=0;i<(resolution*2)+2;i++) {
//Look
look();
}
//print maps
Serial.println("Angle:");
for(i=0;i<resolution+1;i++) {
Serial.print(angle[i]);
if(i!=resolution) {
Serial.print(" ");
}
else {
Serial.println(" ");
}
}
Serial.println("Old Map:");
for(i=0;i<resolution+1;i++) {
Serial.print(old_map[i]);
if(i!=resolution) {
Serial.print(" ");
}
else {
Serial.println(" ");
}
}
Serial.println("New Map:");for(i=0;i<resolution+1;i++) {
Serial.print(new_map[i]);
if(i!=resolution) {
Serial.print(" ");
}
else {
Serial.println(" ");
}
}
Serial.println("Scan Right:");for(i=0;i<resolution+1;i++) {
Serial.print(scan_right[i]);
if(i!=resolution) {
Serial.print(" ");
}
else {
Serial.println(" ");
}
}
Serial.println("Scan Left:");for(i=0;i<resolution+1;i++) {
Serial.print(scan_left[i]);
if(i!=resolution) {
Serial.print(" ");
}
else {
Serial.println(" ");
}
}
}
run=false;
digitalWrite(13,LOW);
}
}
void look() {
if(i==0) { //read front
//move servo to position
scanner.write(mapscanner(angle[res2]));
delay(dt_servo);
take_reading();
//update map
old_map[res2]=new_map[res2]; //update old map
new_map[res2]=(int)dist;
scan_right[res2]=(int)dist;
}
else if(i>0 && i<res2+1) { //sweep right
//move servo to position
scanner.write(mapscanner(angle[res2-i]));
delay(dt_servo);
take_reading();
//update map
old_map[res2-i]=new_map[res2-i]; //update old map
new_map[res2-i]=(int)dist;
scan_right[res2-i]=(int)dist;
}
else if(i>res2 && i<resolution+1) { //sweep left to centre
//move servo to position
scanner.write(mapscanner(angle[i-res2-1]));
delay(dt_servo);
take_reading();
//update map|
new_map[i-res2-1]+=(int)dist;
new_map[i-res2-1]*=0.5;
scan_left[i-res2-1]=(int)dist;
}
else if(i==resolution+1) { //read front
scanner.write(mapscanner(angle[res2]));
delay(dt_servo);
take_reading();
//update map by averaging the two values
new_map[res2]+=(int)dist;
new_map[res2]*=0.5;
scan_left[res2]=(int)dist;
}
else if(i>resolution+1 && i<resolution*1.5+2) { //sweep left
//move servo to position
scanner.write(mapscanner(angle[res2+1+i-resolution-2]));
delay(dt_servo);
take_reading();
//update map
old_map[res2+1+i-resolution-2]=new_map[res2+1+i-resolution-2]; //update old map
new_map[res2+1+i-resolution-2]=(int)dist;
scan_left[res2+1+i-resolution-2]=(int)dist;
}
else if(i>resolution*1.5+1 && i<resolution*2+2) { //sweep right to centre
//move servo to position
scanner.write(mapscanner(angle[resolution-(i-resolution-res2-2)]));
delay(dt_servo);
take_reading();
//update map|
new_map[resolution-(i-resolution-res2-2)]+=(int)dist;
new_map[resolution-(i-resolution-res2-2)]*=0.5;
scan_right[resolution-(i-resolution-res2-2)]=(int)dist;
}
}
//map the desired angle to the value required to move the servo to that angle
int mapscanner(int angle) {
//Serial.println(angle); //for debugging the code
float u=angle/180.0;
//Serial.println(u); //for debugging the code
return(scannermin+(u*(scannermax-scannermin)));
}
//take readings from ir sensor
void take_reading() {
delay(dt_ir); //wait for ir sensor to refresh
reading=0; // needs to start at 0 and then n readings will be added to it
for(j=0;j<n;j++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
reading/=n; // finally average the n reading
dist=12214.16*pow(reading*0.5,-1.08); // this calculates the distance
if(dist<10) {
dist=0;
}
else if(dist>80) {
dist=80;
}
}
There aren't as many comments as usually, when I have a chance I will add some more. The code pretty much waits for 3 inputs from Matlab and then runs the sweep 3 times. Then it waits for another set of 3 parameters from Matlab and so on. It has an LED attached to pin13 to show when it is ready and when it is working. The sweep is different from my first attempts, it starts from the centre and goes right and then left and then right back to the centre.
At the end of the day the I couldn't get the optimiser to work as I desired. The optimiser fmincon doesn't allow integer parameters and the genetic algorithm is very slow and eventually cheated and converged to the minimum time I allowed. This was probably because the infra red sensor voltage changes every 32ms, so if it takes all the reading in this time it minimises the objective function... but doesn't give the optimised timings that I wanted.
At the end of the day the I couldn't get the optimiser to work as I desired. The optimiser fmincon doesn't allow integer parameters and the genetic algorithm is very slow and eventually cheated and converged to the minimum time I allowed. This was probably because the infra red sensor voltage changes every 32ms, so if it takes all the reading in this time it minimises the objective function... but doesn't give the optimised timings that I wanted.
I expect to edit this post when I have a chance (I have a PhD viva to prepare for) to clean it up and add more comment...
Friday, 10 June 2011
Current projects
I got distracted by Fortza motorsport 3 the last few days, but I now have a R3 class Lotus Exige. This means I can live happy and do other things... well until I can afford a real Lotus Exige!
Anyway I am currently taking apart a Sabichi bagless vacuum cleaner... as you do. I want to convert it into a robot vacuum cleaner, so I don't have to do the hoovering (vacuum cleaning in other countries) myself. I have got the wheels off it and all but one of the screws out. I don't have a screwdriver that is long enough to reach the last screw. When it is finished it should look abit like this
The viability of the project will depend on if the electrics inside it run on AC or if it converted internally to DC.
The second thing I have been working on is my car. I have found a way to connect the infra red sensor to the servo horn using some old parts I had lying around. I used the plastic ball and socket parts from my old Mardave Cobra car's suspension. Unfortunately I am still using blue tack to attach the servo for now.
I have also written new code for the sensor sweep, once I have optimised the sweep timings I will post the code here. I am thinking to use Matlab to optimise the timings using either a fastest decent or genetic algorithm, so I don't have to program the algorithm myself.
Anyway I am currently taking apart a Sabichi bagless vacuum cleaner... as you do. I want to convert it into a robot vacuum cleaner, so I don't have to do the hoovering (vacuum cleaning in other countries) myself. I have got the wheels off it and all but one of the screws out. I don't have a screwdriver that is long enough to reach the last screw. When it is finished it should look abit like this
The viability of the project will depend on if the electrics inside it run on AC or if it converted internally to DC.
The second thing I have been working on is my car. I have found a way to connect the infra red sensor to the servo horn using some old parts I had lying around. I used the plastic ball and socket parts from my old Mardave Cobra car's suspension. Unfortunately I am still using blue tack to attach the servo for now.
I have also written new code for the sensor sweep, once I have optimised the sweep timings I will post the code here. I am thinking to use Matlab to optimise the timings using either a fastest decent or genetic algorithm, so I don't have to program the algorithm myself.
Monday, 6 June 2011
Sharp Infra Red Sensor Timing Tests
This is a difficult one to write up and has been abit of a journey to get this far. I am using the 3.3V as the ADC reference voltage, my equation for distance (see last post) and a capacitor across the sensor power rails. What got me thinking about the sweep timings is this webpage. I have learnt alot doing this,
The code I used to do my tests is given below.
#include <Servo.h>
#define resolution 90 //larger number means more accurate, but also more ram used
#define totalangle 180 //total angle that it turns
#define offset 1 //result offset for second sweep
#define scanservocentre 79 //the scanner servo centre location
#define scanservomin 3 //the pwm value to give an angle of 0 degrees
#define scanservomax 152 //the pwm value to give an angle of 180 degrees
Servo scanner; // create servo object to control the sweeping servo
int distance1[resolution+offset+1]; // store the distance from the infra red sensor
int distance2[resolution+offset+1]; // store the distance from the infra red sensor
int dt=10*(180.0/resolution); //wait time for servo to move
int n=32; //number of readings of the sensor 32 is the max before the value in the int goes negative
void setup()
{
scanner.attach(6); // attaches the servo on pin 6 to the servo object
Serial.begin(115200); // sets the speed of the serial connection to the computer
scanner.write(mapscanner(90-totalangle/2)); // sets the scanner servo to the start of sweep
analogReference(EXTERNAL); // Set the ADC reference voltage to external (read last post before doing this line)
}
// This runs continuously on the arduino
void loop() {
// 10 second countdown to start
for(int i=10;i>0;i--) {
Serial.println(i); // print to the computer when the USB cable is connected
delay(1000); // 1 second delay
}
//Scan left and right
scan();
}
void scan() { // 180 degree scan in front of the car
Serial.println("Scanning...");
int reading; // used for taking the averages
float dist; // used for calculating the distance in cm
//read in first direction
for(int i=0;i<resolution+1;i++) {
scanner.write(mapscanner(i*(totalangle/resolution))); //set the servo position
//Serial.println(scanner.read()); //for debugging the code
delay(dt); //wait until done
delay(47); //wait 47ms for the sensor to refresh
reading=0; // needs to start at 0 and then n readings will be added to it
for(int i=0;i<n;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
reading/=n; // finally average the n reading
dist=12214.16*pow(reading,-1.08); // this calculates the distance
if(dist<10) { // the infra red sensor is unreliable below 10 cm so set it to 0cm
dist=0;
}
if(dist>80) { // the infra red sensor is unreliable above 80 cm so set it to 80cm
dist=80;
}
distance1[i]=(int)dist; // put the value dist in to the distance array which contains the actual distances in cm
}
delay(dt); //a slight pause before starting the sweep back
//reading in the second direction
for(int i=0;i<resolution+1;i++) {
scanner.write(mapscanner(totalangle-(i*(totalangle/resolution)))); //set the servo position
//Serial.println(scanner.read()); //for debugging the code
delay(dt); //wait until done
delay(47); //wait 47ms for the sensor to refresh
reading=0; // needs to start at 0 and then n readings will be added to it
for(int i=0;i<n;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
reading/=n; // finally average the n reading
dist=12214.16*pow(reading,-1.08); // this calculates the distance
if(dist<10) { // the infra red sensor is unreliable below 10 cm so set it to 0cm
dist=0;
}
if(dist>80) { // the infra red sensor is unreliable above 80 cm so set it to 80cm
dist=80;
}
distance2[resolution+offset-i]=(int)dist; // put the value dist in to the distance array which contains the actual distances in cm
}
//print out the distances for each sweep direction
for(int i=0;i<resolution+1;i++) {
Serial.print(distance1[i]);
Serial.print(", ");
Serial.println(distance2[i]);
}
}
//map the desired angle to the value required to move the servo to that angle
int mapscanner(int angle) {
//Serial.println(angle); //for debugging the code
float u=angle/180.0;
//Serial.println(u); //for debugging the code
return(scanservomin+(u*(scanservomax-scanservomin)));
}
I think this is right and will give an accurate sweep. Next I will modify my car code and see if these improvements help.
EDIT: As with the last post I am using 3.3V as the reference not 3V.
- Don't trust your servo, you need to find what values to send to the servo to get the desired angle.
- When using integers the sign flips when the value goes above 32,767 (see this), which cause problem if you try to average more than about 32 readings. This can be fixed by using longs, but I just stuck with integers.
- The timing for the Sharp infra red sensor is a maximum of around 47ms (see here).
- There is a limited amount of RAM on the Arduino and I hit the limit. I found that if you try to assign more memory than is available on the Arduino it fails to run. If you try to access memory that hasn't been assign the Arduino will stop running at that line.
I tried alot of different delays to allow for the servo to move to the next reading position, I tried offsetting the results from the sweep back to zero and finally the number of ADC reads I averaged. I read the source code for the Hyper Squirrel robot by John Palmisano of Society of Robots, which can be found here. I liked the idea of setting a resolution and total sweep angle, so I added this to my code. For all my tests I used a 180 degree sweep with a resolution of 90.
So a bad sweep looks like this,
The two lines correspond to the left-bound sweep and the right-bound sweep. This test used a delay of 10ms for the servo to move and the I took 6 readings of the sensor.
My final good sweep looks like this,
For this test I used a delay of 10*(180.0/resolution), so as the resolution reduces the delay increases. I added a 47ms delay to allow the sensor to update before I took 32 readings of the sensor. Finally I offset the readings on the right-bound sweep by one in the array, this means that there is no reading at 0 degrees.
The code I used to do my tests is given below.
#include <Servo.h>
#define resolution 90 //larger number means more accurate, but also more ram used
#define totalangle 180 //total angle that it turns
#define offset 1 //result offset for second sweep
#define scanservocentre 79 //the scanner servo centre location
#define scanservomin 3 //the pwm value to give an angle of 0 degrees
#define scanservomax 152 //the pwm value to give an angle of 180 degrees
Servo scanner; // create servo object to control the sweeping servo
int distance1[resolution+offset+1]; // store the distance from the infra red sensor
int distance2[resolution+offset+1]; // store the distance from the infra red sensor
int dt=10*(180.0/resolution); //wait time for servo to move
int n=32; //number of readings of the sensor 32 is the max before the value in the int goes negative
void setup()
{
scanner.attach(6); // attaches the servo on pin 6 to the servo object
Serial.begin(115200); // sets the speed of the serial connection to the computer
scanner.write(mapscanner(90-totalangle/2)); // sets the scanner servo to the start of sweep
analogReference(EXTERNAL); // Set the ADC reference voltage to external (read last post before doing this line)
}
// This runs continuously on the arduino
void loop() {
// 10 second countdown to start
for(int i=10;i>0;i--) {
Serial.println(i); // print to the computer when the USB cable is connected
delay(1000); // 1 second delay
}
//Scan left and right
scan();
}
void scan() { // 180 degree scan in front of the car
Serial.println("Scanning...");
int reading; // used for taking the averages
float dist; // used for calculating the distance in cm
//read in first direction
for(int i=0;i<resolution+1;i++) {
scanner.write(mapscanner(i*(totalangle/resolution))); //set the servo position
//Serial.println(scanner.read()); //for debugging the code
delay(dt); //wait until done
delay(47); //wait 47ms for the sensor to refresh
reading=0; // needs to start at 0 and then n readings will be added to it
for(int i=0;i<n;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
reading/=n; // finally average the n reading
dist=12214.16*pow(reading,-1.08); // this calculates the distance
if(dist<10) { // the infra red sensor is unreliable below 10 cm so set it to 0cm
dist=0;
}
if(dist>80) { // the infra red sensor is unreliable above 80 cm so set it to 80cm
dist=80;
}
distance1[i]=(int)dist; // put the value dist in to the distance array which contains the actual distances in cm
}
delay(dt); //a slight pause before starting the sweep back
//reading in the second direction
for(int i=0;i<resolution+1;i++) {
scanner.write(mapscanner(totalangle-(i*(totalangle/resolution)))); //set the servo position
//Serial.println(scanner.read()); //for debugging the code
delay(dt); //wait until done
delay(47); //wait 47ms for the sensor to refresh
reading=0; // needs to start at 0 and then n readings will be added to it
for(int i=0;i<n;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
reading/=n; // finally average the n reading
dist=12214.16*pow(reading,-1.08); // this calculates the distance
if(dist<10) { // the infra red sensor is unreliable below 10 cm so set it to 0cm
dist=0;
}
if(dist>80) { // the infra red sensor is unreliable above 80 cm so set it to 80cm
dist=80;
}
distance2[resolution+offset-i]=(int)dist; // put the value dist in to the distance array which contains the actual distances in cm
}
//print out the distances for each sweep direction
for(int i=0;i<resolution+1;i++) {
Serial.print(distance1[i]);
Serial.print(", ");
Serial.println(distance2[i]);
}
}
//map the desired angle to the value required to move the servo to that angle
int mapscanner(int angle) {
//Serial.println(angle); //for debugging the code
float u=angle/180.0;
//Serial.println(u); //for debugging the code
return(scanservomin+(u*(scanservomax-scanservomin)));
}
I think this is right and will give an accurate sweep. Next I will modify my car code and see if these improvements help.
EDIT: As with the last post I am using 3.3V as the reference not 3V.
Saturday, 4 June 2011
Sharp Infra Red Sensor Calibration
After reading SOR forum and Robot room I have decided to use a 10uF capacitor across the sensor power supply rails. I taped some paper on to my table and marked the distance from the sensor at 1cm increments to 80cm. I used a white box as a target.
I think it is important to do this, since the trend line given in my datasheet was distance = 12343.85 * (reading ^ -1.15). My trend line from the graph was distance = 7883.10 * (reading ^ -1.08). I plotted the datasheet equation and my new equation against the actual distance.
It show that the new equation has better agreement than the datasheet equation. I then changed the reference voltage from 5V to 3.3V to make it more accurate. I read this before changing the voltage, so I didn't break anything. I took the data in the same way as before.
This gave a new trend line, distance = 12214.16 * (reading ^ -1.08) and plotted against the actual distance.
I think I will use 3.3V as the reference voltage with the new equation and the 10uF across the sensor power rails. The program I used for the sensor calibration is...
void setup() {
analogReference(EXTERNAL); // Set the ADC reference voltage to external (only use if using 3V, read
// this first)
Serial.begin(115200); // sets the speed of the serial connection to the computer
}
void loop() {
// 10 second countdown to start
for(int i=10;i>0;i--) {
Serial.println(i); // print to the computer when the USB cable is connected
delay(1000); // 1 second delay
}
Serial.println("Taking reading...");
int reading; // used for taking the averages
//take the readings
reading=0; // needs to start at 0 and then 6 readings will be added to it
for(int i=0;i<6;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
reading/=6; // finally average the 6 reading
Serial.println(reading);
Serial.println("Done");
}
Next is to check for timing errors in the sensor sweeps.
EDIT: I have just realised that the Arduino generates 3.3V not 3V, so I have changed all the references to 3V to 3.3V.
I then took readings at every centimetre from 10cm to 80cm from the sensor and added a trend line.
I think it is important to do this, since the trend line given in my datasheet was distance = 12343.85 * (reading ^ -1.15). My trend line from the graph was distance = 7883.10 * (reading ^ -1.08). I plotted the datasheet equation and my new equation against the actual distance.
It show that the new equation has better agreement than the datasheet equation. I then changed the reference voltage from 5V to 3.3V to make it more accurate. I read this before changing the voltage, so I didn't break anything. I took the data in the same way as before.
This gave a new trend line, distance = 12214.16 * (reading ^ -1.08) and plotted against the actual distance.
I think I will use 3.3V as the reference voltage with the new equation and the 10uF across the sensor power rails. The program I used for the sensor calibration is...
void setup() {
analogReference(EXTERNAL); // Set the ADC reference voltage to external (only use if using 3V, read
// this first)
Serial.begin(115200); // sets the speed of the serial connection to the computer
}
void loop() {
// 10 second countdown to start
for(int i=10;i>0;i--) {
Serial.println(i); // print to the computer when the USB cable is connected
delay(1000); // 1 second delay
}
Serial.println("Taking reading...");
int reading; // used for taking the averages
//take the readings
reading=0; // needs to start at 0 and then 6 readings will be added to it
for(int i=0;i<6;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
reading/=6; // finally average the 6 reading
Serial.println(reading);
Serial.println("Done");
}
Next is to check for timing errors in the sensor sweeps.
EDIT: I have just realised that the Arduino generates 3.3V not 3V, so I have changed all the references to 3V to 3.3V.
Friday, 3 June 2011
A bit of fun
I love doing mechanical things, so I stripped down the differential and the gearbox as per these instructions. I have never been able to figure out how to set the differential, but I think it has worked well enough. Regardless of the success I had good fun anyway. With the diff and gearbox cleaned and setup I taped my camera to the top of the car and put it back to RC control. I have always wanted to be able to see what my car sees when I drive it.
I thought that the bubble wrap might help if it flipped... more fool me. This is the first test drive, I thought that the slipper clutch was too loose.
After tightening the slipper clutch I had another drive.
The third video cannot be posted after the car hit a bump and flipped. The good news is that the camera survived, but the video didn't. The bubble wrap didn't make much difference, the camera is nicely scratched now. It also turns out that tire glue is essential...
Also it might be a better idea not to use soft carpet tires outside as well. I'll get back to writing up the infra red sensor post now.
Thursday, 2 June 2011
Next Steps
After a bit of digging I think I will try the following
- Make my car drive and think at the same time (oople forum suggestion)
- Check timing errors for the infra red sensor (SOR forum)
- Check the noise levels and if possible reduce them (SOR forum and Robot room)
This is a really good website Society of Robots with lots of information on robot building. Also check out this website Esawdust.
Wednesday, 1 June 2011
First Test Videos
This is the first test video of the autonomous car using the previous post's code.
It doesn't quite drive in a straight line, so the steering servo centre position is slightly off. It doesn't turn exactly 90 degrees and there is no crash detection whilst turning.
I would like to try a longer range infra red sensor and an ultrasound sensor for navigation to see if these help, but first improving the coding will be the first priority.
The test program
This is the program I wrote and uploaded to the Arduino. For my car the steering servo has the range of 48 (full left) to 112 (full right) and 80 is about centre. The electronic speed controller has a range of 0 (full power) to 180 (full brake) and I found 80 makes the car drive at its slowest. Finally my servo to sweep the infra red sensor has a range from 0 to 160 to do a 180 degree sweep with 80 being centre. If you are doing this on your own car then you will need to figure these ranges out.
All Arduino programs need to have setup() and loop() functions (see the Arduino website). Since I am using an electronic speed controller I have treated it as a servo.
This code can be copied and pasted in to the Arduino software and uploaded to the Arduino.
#include <Servo.h>
Servo sweeper; // create servo object to control the sweeping servo
Servo motor; // create servo object to control the motor
Servo steering; // create servo object to control the steering
float distance[161]; // store the distance from the infra red sensor
boolean driving=false; // a flag so the car knows if it is moving or not
void setup()
{
sweeper.attach(6); // attaches the servo on pin 6 to the servo object
motor.attach(3); // attaches the servo on pin 3 to the servo object
steering.attach(9); // attaches the servo on pin 5 to the servo object
Serial.begin(115200); // sets the speed of the serial connection to the computer
sweeper.write(80); // sets the sweeper servo to the centre
motor.write(180); // 0 full power, 82 power starts to 180 full brake
steering.write(80); // sets the steering servo to the centre
// 10 second countdown to start
for(int i=10;i>0;i--) {
Serial.println(i); // print to the computer when the USB cable is connected
delay(1000); // 1 second delay
}
}
// This runs continuously on the arduino
void loop() {
// find the space in front of the car
check_front();
//decide what to do
if(distance[80]>79) { // if there is more than 79cm in front then...
if(driving==false) { // if the car thinks it is stopped then ...
Serial.println("Driving forwards...");
//drive forwards
if(steering.read()!=80) { // if the steering servo is not set in the centre then move it to centre
steering.write(80); // sets the steering servo to centre
delay(1000); // wait one second for this to happen
}
motor.write(80); // move very slowly
driving=true; // set the driving flag to true
Serial.println("driving");
}
}
else {
if(driving==true) {// if the car thinks it is moving then ...
Serial.println("Stopping...");
motor.write(180); // switch the brake on
driving=false; // set the drving flag to false
Serial.println("stopped");
}
//Scan left and right
scan();
if(distance[0]>(distance[160]-1) && distance[0]>79) { // if there is more space on the right than the left
Serial.println("Turning Right...");
if(steering.read()!=112) { // if the steering servo is not set to full right then
steering.write(112); // sets the steering servo to full right
delay(1000); // wait one second for this to happen
}
motor.write(80); // move very slowly
delay(1000); // wait one second for this to happen
steering.write(80); // sets the steering servo to centre
delay(1000); // wait one second for this to happen
driving=true; // set the driving flag to true
Serial.println("driving");
}
else if(distance[160]>(distance[0]-1) && distance[160]>79) {
Serial.println("Turning Left...");
if(steering.read()!=48) { // if the steering servo is not set to full left then
steering.write(48); // sets the steering servo to full left
delay(1000); // wait one second for this to happen
}
motor.write(80); // move very slowly
delay(1000); // wait one second for this to happen
steering.write(80); // sets the steering servo to centre
delay(1000); // wait one second for this to happen
driving=true; // set the driving flag to true
Serial.println("driving");
}
else {
// if it can't go forwards, left or right then wait
Serial.println("I'm Stuck!...");
}
}
}
void scan() { // 180 degree scan in front of the car
Serial.println("Scanning...");
int cw[161]; // this array will sort the ADC reading in one direction
int acw[161]; // this array will sort the ADC reading in the other direction
int reading; // used for taking the averages
float dist; // used for calculating the distance in cm
if(sweeper.read()!=0) { // if the servo is not right then
sweeper.write(0); // set the servo to full right
delay(sweeper.read()*10); // wait until this is done
}
//read in first direction
for(int pos=0;pos<161;pos++) {
sweeper.write(pos); //set the servo position
delay(10); //wait until done
reading=0; // needs to start at 0 and then 6 readings will be added to it
for(int i=0;i<6;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
cw[pos]=reading/6; // finally average the 6 reading and put in the first array
}
//reading in the second direction
for(int pos=160;pos>-1;pos--) {
sweeper.write(pos); //set the servo position
delay(10); //wait until done
reading=0; // needs to start at 0 and then 6 readings will be added to it
for(int i=0;i<6;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
acw[pos]=reading/6; // finally average the 6 reading and put in the second array
}
//take the average and convert to distance
for(int i=0;i<161;i++) {
dist=12343.85*pow(((cw[i]+acw[i])*0.5),-1.15); // this formula averages the values of the 2 arrays and
// calculates the distance
if(dist<10) { // the infra red sensor is unreliable below 10 cm so set it to 0cm
dist=0;
}
if(dist>80) { // the infra red sensor is unreliable above 80 cm so set it to 80cm
dist=80;
}
distance[i]=dist; // put the value dist in to the distance array which contains the actual distances in cm
Serial.print(i);
Serial.print(" distance = ");
Serial.println(distance[i]); // this is sent to the computer serial monitor to check
}
sweeper.write(80); // set the servo back to the centre
delay(1000); // wait one second until it is done
}
void check_front() { //just check in front of the car
Serial.println("Checking Front...");
int reading; // used for taking the averages
if(sweeper.read()!=80) { // if the steering servo is not set in the centre then move it to centre
sweeper.write(80);
delay(abs(sweeper.read()-80)*10);
}
//take the readings
reading=0; // this code is the same as in the scan() function with no sweep
for(int i=0;i<6;i++) {
reading+=analogRead(0);
delay(1);
}
float dist = 12343.85*pow((reading/6),-1.15);
if(dist<10) {
dist=0;
}
if(dist>80) {
dist=80;
}
distance[80]=dist; // but the result is put in the centre position in the distance array
Serial.print("80 distance = ");
Serial.println(distance[80]);
}
All Arduino programs need to have setup() and loop() functions (see the Arduino website). Since I am using an electronic speed controller I have treated it as a servo.
This code can be copied and pasted in to the Arduino software and uploaded to the Arduino.
#include <Servo.h>
Servo sweeper; // create servo object to control the sweeping servo
Servo motor; // create servo object to control the motor
Servo steering; // create servo object to control the steering
float distance[161]; // store the distance from the infra red sensor
boolean driving=false; // a flag so the car knows if it is moving or not
void setup()
{
sweeper.attach(6); // attaches the servo on pin 6 to the servo object
motor.attach(3); // attaches the servo on pin 3 to the servo object
steering.attach(9); // attaches the servo on pin 5 to the servo object
Serial.begin(115200); // sets the speed of the serial connection to the computer
sweeper.write(80); // sets the sweeper servo to the centre
motor.write(180); // 0 full power, 82 power starts to 180 full brake
steering.write(80); // sets the steering servo to the centre
// 10 second countdown to start
for(int i=10;i>0;i--) {
Serial.println(i); // print to the computer when the USB cable is connected
delay(1000); // 1 second delay
}
}
// This runs continuously on the arduino
void loop() {
// find the space in front of the car
check_front();
//decide what to do
if(distance[80]>79) { // if there is more than 79cm in front then...
if(driving==false) { // if the car thinks it is stopped then ...
Serial.println("Driving forwards...");
//drive forwards
if(steering.read()!=80) { // if the steering servo is not set in the centre then move it to centre
steering.write(80); // sets the steering servo to centre
delay(1000); // wait one second for this to happen
}
motor.write(80); // move very slowly
driving=true; // set the driving flag to true
Serial.println("driving");
}
}
else {
if(driving==true) {// if the car thinks it is moving then ...
Serial.println("Stopping...");
motor.write(180); // switch the brake on
driving=false; // set the drving flag to false
Serial.println("stopped");
}
//Scan left and right
scan();
if(distance[0]>(distance[160]-1) && distance[0]>79) { // if there is more space on the right than the left
Serial.println("Turning Right...");
if(steering.read()!=112) { // if the steering servo is not set to full right then
steering.write(112); // sets the steering servo to full right
delay(1000); // wait one second for this to happen
}
motor.write(80); // move very slowly
delay(1000); // wait one second for this to happen
steering.write(80); // sets the steering servo to centre
delay(1000); // wait one second for this to happen
driving=true; // set the driving flag to true
Serial.println("driving");
}
else if(distance[160]>(distance[0]-1) && distance[160]>79) {
Serial.println("Turning Left...");
if(steering.read()!=48) { // if the steering servo is not set to full left then
steering.write(48); // sets the steering servo to full left
delay(1000); // wait one second for this to happen
}
motor.write(80); // move very slowly
delay(1000); // wait one second for this to happen
steering.write(80); // sets the steering servo to centre
delay(1000); // wait one second for this to happen
driving=true; // set the driving flag to true
Serial.println("driving");
}
else {
// if it can't go forwards, left or right then wait
Serial.println("I'm Stuck!...");
}
}
}
void scan() { // 180 degree scan in front of the car
Serial.println("Scanning...");
int cw[161]; // this array will sort the ADC reading in one direction
int acw[161]; // this array will sort the ADC reading in the other direction
int reading; // used for taking the averages
float dist; // used for calculating the distance in cm
if(sweeper.read()!=0) { // if the servo is not right then
sweeper.write(0); // set the servo to full right
delay(sweeper.read()*10); // wait until this is done
}
//read in first direction
for(int pos=0;pos<161;pos++) {
sweeper.write(pos); //set the servo position
delay(10); //wait until done
reading=0; // needs to start at 0 and then 6 readings will be added to it
for(int i=0;i<6;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
cw[pos]=reading/6; // finally average the 6 reading and put in the first array
}
//reading in the second direction
for(int pos=160;pos>-1;pos--) {
sweeper.write(pos); //set the servo position
delay(10); //wait until done
reading=0; // needs to start at 0 and then 6 readings will be added to it
for(int i=0;i<6;i++) {
reading+=analogRead(0); // read from analog input 0
delay(1); // wait one millisecond
}
acw[pos]=reading/6; // finally average the 6 reading and put in the second array
}
//take the average and convert to distance
for(int i=0;i<161;i++) {
dist=12343.85*pow(((cw[i]+acw[i])*0.5),-1.15); // this formula averages the values of the 2 arrays and
// calculates the distance
if(dist<10) { // the infra red sensor is unreliable below 10 cm so set it to 0cm
dist=0;
}
if(dist>80) { // the infra red sensor is unreliable above 80 cm so set it to 80cm
dist=80;
}
distance[i]=dist; // put the value dist in to the distance array which contains the actual distances in cm
Serial.print(i);
Serial.print(" distance = ");
Serial.println(distance[i]); // this is sent to the computer serial monitor to check
}
sweeper.write(80); // set the servo back to the centre
delay(1000); // wait one second until it is done
}
void check_front() { //just check in front of the car
Serial.println("Checking Front...");
int reading; // used for taking the averages
if(sweeper.read()!=80) { // if the steering servo is not set in the centre then move it to centre
sweeper.write(80);
delay(abs(sweeper.read()-80)*10);
}
//take the readings
reading=0; // this code is the same as in the scan() function with no sweep
for(int i=0;i<6;i++) {
reading+=analogRead(0);
delay(1);
}
float dist = 12343.85*pow((reading/6),-1.15);
if(dist<10) {
dist=0;
}
if(dist>80) {
dist=80;
}
distance[80]=dist; // but the result is put in the centre position in the distance array
Serial.print("80 distance = ");
Serial.println(distance[80]);
}
Subscribe to:
Posts (Atom)