I've been developing an interest in bushcraft lately. While on a recent excursion the members of a group I was with I became interested in a lightweight chopping tool. Some internetting lead to reading about tomahawks. The Cold Steel Trail Hawk seems to be pretty popular. I ordered mine from that online retail giant that everyone loves to hate.
As you can see, it doesn't look like much from the factory. With that said, I am happy that it fits in my Goruck.
The first thing that I did was take it apart, sand the polyurethane off the handle, and coat the head in paint stripper. After that, the Elder Sign was added to the handle for protection from the Great Old Ones.
After the paint was removed, I soaked the head in vinegar for two days to put a patina on the metal. I did the same with my Mora Compantion.
After a light coat of stain and some tung oil on the handle, she's looking great.
Monday, December 21, 2015
On the air
I got my ham license about two years ago, but never really did much with it despite intending to do otherwise. I managed to finally reach a turning point regarding that when I picked up a gently used Kenwood TM261A (and a power supply) from a local Craigslist seller.
As a budget-minded individual, I decided to try constructing my own antenna before buying one. The basic ground plane antenna described in the ARRL Operator's Manual seemed like a pretty easy place to start, so I went with that. My build consists of three pieces of brazing wire connected to a SO-239 connector.
First I went about making loops so that the ground plane wires could be connected to the SO-239. I bent small L shapes with a bench vise, then squished them into something I could connect with a screw.
I then soldered a third section of wire to the part of the SO-239 that connects to the core of the coax using a propane torch. I neglected to take a picture, but I doubt the complexity of the operation will astound you.
After running some 50 ohm coax up to the attic, I secured my new antenna in place with zip ties.
I am now able to hit a repeater about 15 miles away and am getting reports of good audio. As time (or money) allows, I'll probably either build or buy a J-Pole. Regardless of this, I'm happy for the time being.
As a budget-minded individual, I decided to try constructing my own antenna before buying one. The basic ground plane antenna described in the ARRL Operator's Manual seemed like a pretty easy place to start, so I went with that. My build consists of three pieces of brazing wire connected to a SO-239 connector.
First I went about making loops so that the ground plane wires could be connected to the SO-239. I bent small L shapes with a bench vise, then squished them into something I could connect with a screw.
I then soldered a third section of wire to the part of the SO-239 that connects to the core of the coax using a propane torch. I neglected to take a picture, but I doubt the complexity of the operation will astound you.
After running some 50 ohm coax up to the attic, I secured my new antenna in place with zip ties.
I am now able to hit a repeater about 15 miles away and am getting reports of good audio. As time (or money) allows, I'll probably either build or buy a J-Pole. Regardless of this, I'm happy for the time being.
Tuesday, January 20, 2015
E36 BMW Refresh Part 2 - Leaks and Alerts
A while ago I changed the drive belts. The tight engine compartment makes getting good photos hard, so I'll just say that it is easiest to swap them out from underneath the car with your head by the driver's side wheel.
On top of that, while changing the spark plugs I noticed that some of the spark plug holes were full of oil when I was changing the plugs (not inside the cylinder, the recess the plugs go into). There are gaskets for these, but changing them requires that the valve cover be removed. This means that the valve cover gasket must be swapped out too, leaking or not. It is important to mention that the gasket is different on pre-VANOS and VANOS equipped cars. Be sure to get the correct gasket.
Just as before I removed the decorative cover from the engine, the coils, and the spark plugs. Next the valve cover came off. I should mention that the rear driver's side bolt for the valve cover is a little bit hard to reach. Be careful that you don't drop it someplace that it cannot be found, because it is a special item that they don't carry at the hardware store. A decent auto parts store will be able to order one for you.
With the cover off, we can see the old gaskets. They can simply be removed by pulling them off.
The old gasket is on the right, and the new one is on the left. The old gasket has hardened significantly and doesn't even feel like it's made of rubber. It is not surprising at all that it was leaking. Re-installation was a simple but unphotographed process. I did use a small amount of RTV at the corners of the gasket and inside the half moon cutouts at the back of the engine.
Another problem I was having is that the car was claiming it was low on coolant, even though it was not. The issue in this case was a bad coolant level sensor, located at the bottom of the radiator overflow tank.
There are two hoses that connect the overflow tank to the radiator. First I unhooked the top one from the radiator (driver's side).
After that I carefully worked the hose free from the fan shroud.
The next step was to remove the bleeder screw and the retaining clip from the tank.
At this point in time you can move the tank around freely. I disconnected the other end of the top hose from the tank and pumped as much coolant out as I could with a hand pump. There is a second hose on the bottom and an electrical connector for the sensor. I removed both of those as well.
When I removed the old sensor, it came out in pieces. Reinstallation was the same job, but in reverse order. I am happy to say that I no longer have a stupid warning about being low on coolant when I in fact have plenty.
On top of that, while changing the spark plugs I noticed that some of the spark plug holes were full of oil when I was changing the plugs (not inside the cylinder, the recess the plugs go into). There are gaskets for these, but changing them requires that the valve cover be removed. This means that the valve cover gasket must be swapped out too, leaking or not. It is important to mention that the gasket is different on pre-VANOS and VANOS equipped cars. Be sure to get the correct gasket.
Just as before I removed the decorative cover from the engine, the coils, and the spark plugs. Next the valve cover came off. I should mention that the rear driver's side bolt for the valve cover is a little bit hard to reach. Be careful that you don't drop it someplace that it cannot be found, because it is a special item that they don't carry at the hardware store. A decent auto parts store will be able to order one for you.
With the cover off, we can see the old gaskets. They can simply be removed by pulling them off.
The old gasket is on the right, and the new one is on the left. The old gasket has hardened significantly and doesn't even feel like it's made of rubber. It is not surprising at all that it was leaking. Re-installation was a simple but unphotographed process. I did use a small amount of RTV at the corners of the gasket and inside the half moon cutouts at the back of the engine.
Another problem I was having is that the car was claiming it was low on coolant, even though it was not. The issue in this case was a bad coolant level sensor, located at the bottom of the radiator overflow tank.
There are two hoses that connect the overflow tank to the radiator. First I unhooked the top one from the radiator (driver's side).
After that I carefully worked the hose free from the fan shroud.
The next step was to remove the bleeder screw and the retaining clip from the tank.
At this point in time you can move the tank around freely. I disconnected the other end of the top hose from the tank and pumped as much coolant out as I could with a hand pump. There is a second hose on the bottom and an electrical connector for the sensor. I removed both of those as well.
When I removed the old sensor, it came out in pieces. Reinstallation was the same job, but in reverse order. I am happy to say that I no longer have a stupid warning about being low on coolant when I in fact have plenty.
Sunday, December 7, 2014
E36 BMW Refresh - Part 1: Spark Plugs
Back in the Summer a friend sold me his E36 325is for a very low price. Normally you'd expect to pay very little for an 22 year old car in the first place, but this one is a bit different. It's original owner was a motorsports enthusiast of some kind or other. He'd added a bunch of fun stuff to the car like a cam kit, catback exhuast and header, strut tower braces, aluminum flywheel, an aftermarket ECU ROM, and all other types of goodies. Needless to say the car is pretty quick. With that said, it has a lot of small irritating problems.
When I bough the thing the first thing I noticed was that the brake rotors were warped quite badly. I wasn't thinking about bloggy things at the time, so I didn't document the process of replacing them. I'll just simply say that for the most part the hardest part was getting the calipers off. This was because the person who last worked on the brakes torqued the bolts far tighter than the manual calls for. My wife's Mini has essentially the same brakes, and changing those was much easier.
I have more free time lately, so I've started paying attention to getting the car back in fighting shape. Today I squared away the spark plugs.
Here we see the engine compartment. There isn't exactly a lot of excess room under there. The ignition goodies are hidden under the black plastic covers seen atop the valve cover.
Now we've removed the strut tower brace and the first of the covers, exposing an attractively blocky fuel rail.
Once the other plastic cover has been removed, the ignition system is exposed. Each cylinder has its own ignition coil. There is no spark plug wire, instead the coil connects directly to the plug. The coils are easily disconnected from the wiring harness without tools, and detached from the valve cover with a 10mm socket.
Once the plugs were pulled, they showed only normal wear. Given that the car ran pretty well anyway, this wasn't surprising. It was still pleasant to see.
After the plugs were replaced, I simply reversed the steps I took. As I continue to repair the car I will continue to make updates.
When I bough the thing the first thing I noticed was that the brake rotors were warped quite badly. I wasn't thinking about bloggy things at the time, so I didn't document the process of replacing them. I'll just simply say that for the most part the hardest part was getting the calipers off. This was because the person who last worked on the brakes torqued the bolts far tighter than the manual calls for. My wife's Mini has essentially the same brakes, and changing those was much easier.
I have more free time lately, so I've started paying attention to getting the car back in fighting shape. Today I squared away the spark plugs.
Here we see the engine compartment. There isn't exactly a lot of excess room under there. The ignition goodies are hidden under the black plastic covers seen atop the valve cover.
Now we've removed the strut tower brace and the first of the covers, exposing an attractively blocky fuel rail.
Once the other plastic cover has been removed, the ignition system is exposed. Each cylinder has its own ignition coil. There is no spark plug wire, instead the coil connects directly to the plug. The coils are easily disconnected from the wiring harness without tools, and detached from the valve cover with a 10mm socket.
Once the plugs were pulled, they showed only normal wear. Given that the car ran pretty well anyway, this wasn't surprising. It was still pleasant to see.
After the plugs were replaced, I simply reversed the steps I took. As I continue to repair the car I will continue to make updates.
Tuesday, July 22, 2014
Cheap VHF Communications
The high power rocketry club I am a member of uses two meter amateur radios for communications. We do this on the drive to the launch range and while walking around and doing ground crew duties as well. My money has a variety of places to go right now, so for the time being I needed to simply find the least expensive setup that works. I've found that. Serious amateur radio enthusiasts probably won't care much for my choices, but from a purely utilitarian perspective they work well enough for me, at least for the time being.
The transceiver itself is the (in)famous Baofeng UV5R-A. A lot of the complaints people have about the menus being poorly laid out are frankly true. With that said, when I talk into the radio people hear me and are able to understand what I said. I am also able to understand them. I programmed the repeaters in my area into it with CHIRP, bypassing the obnoxious menu.
One common (and in my experience valid) complaint about the Baofengs is the shit-tacular antenna they come with. I picked up a knockoff of a Nagoya antenna from eBay for about $5 a while back (they don't seem to be available any longer). After doing this, I was able to pick up a lot more traffic than I could previously.
For just walking around, this is really all that you need. However, if you want to use your radio in a vehicle this won't quite do it.
This is a Nagoya magnetic mount antenna. It has a reasonably strong magnet at the bottom, which sticks to the roof of my truck quite nicely. I simply ran the wire in through the passenger side wing window.
Picking up the transceiver while driving and it having a big wire coming off of the end of it is no fun, but fortunately a remote microphone/speaker combo is available. Some users have complained about poor audio quality from the speaker, and of others not being able to hear them well when speaking. Personally, I've had no problems with bad received audio quality. I have personally had no problem with the received audio quality. With that said, I have noticed that I need to speak directly into the microphone to be heard well.
I don't want to risk draining the battery while on a long drive, so I picked up a cigarette lighter adapter. It is clearly made from a hollowed out battery (it even says "Li-ion Battery" on the thing), but despite its chintzy appearance, it works. Some users have complained about their radios heating up when they use it, but I haven't experienced this.
This gear allowed me to communicate with other people a few miles away. It is true that I had some issues while crossing the Cascades (line of sight stuff), I wouldn't be surprised if people with nicer radios had similar problems. At the end of the day, the Baofeng gives you a lot to work with for very little money. I would like to get something nicer at some point, but this will do for the time being.
Sorry, the radio still has desert all over it.
The transceiver itself is the (in)famous Baofeng UV5R-A. A lot of the complaints people have about the menus being poorly laid out are frankly true. With that said, when I talk into the radio people hear me and are able to understand what I said. I am also able to understand them. I programmed the repeaters in my area into it with CHIRP, bypassing the obnoxious menu.
One common (and in my experience valid) complaint about the Baofengs is the shit-tacular antenna they come with. I picked up a knockoff of a Nagoya antenna from eBay for about $5 a while back (they don't seem to be available any longer). After doing this, I was able to pick up a lot more traffic than I could previously.
For just walking around, this is really all that you need. However, if you want to use your radio in a vehicle this won't quite do it.
This is a Nagoya magnetic mount antenna. It has a reasonably strong magnet at the bottom, which sticks to the roof of my truck quite nicely. I simply ran the wire in through the passenger side wing window.
Picking up the transceiver while driving and it having a big wire coming off of the end of it is no fun, but fortunately a remote microphone/speaker combo is available. Some users have complained about poor audio quality from the speaker, and of others not being able to hear them well when speaking. Personally, I've had no problems with bad received audio quality. I have personally had no problem with the received audio quality. With that said, I have noticed that I need to speak directly into the microphone to be heard well.
I don't want to risk draining the battery while on a long drive, so I picked up a cigarette lighter adapter. It is clearly made from a hollowed out battery (it even says "Li-ion Battery" on the thing), but despite its chintzy appearance, it works. Some users have complained about their radios heating up when they use it, but I haven't experienced this.
This gear allowed me to communicate with other people a few miles away. It is true that I had some issues while crossing the Cascades (line of sight stuff), I wouldn't be surprised if people with nicer radios had similar problems. At the end of the day, the Baofeng gives you a lot to work with for very little money. I would like to get something nicer at some point, but this will do for the time being.
Tuesday, May 28, 2013
KE Jetronic Fuel Mixture Adjustment
In the mid 1970s, Robert Bosch GmbH developed a mechanical fuel injection system called K (konstant) Jetronic. It was a very clever system, which could compensate for variables like engine temperature and air density without the use of electronics. As time marched on and emissions requirements became more strict, the folks at Bosch added electronic controls to the system to extend its life. This was called KE Jetronic.
My daily driver (a W124) is equipped with KE-Jet. Recently I began having problems with the spark plugs fouling. The car doesn't burn oil, and various sensors used by the fuel system tested okay. Some research lead me to discover that as these cars age, they end up running too rich. The fuel distributor can be adjusted to correct the problem. The problem with this is that federal regulations required anti-tamper equipment be put in place to keep the fuel mixture from being trivially adjusted. I suppose there was a belief that some shithead hammer mechanic would think they could get more power out of their car by dumping more gasoline into the engine. Fortunately, it is pretty easy to get around the anti tamper stuff.
Here we see the throttle body and fuel distributor. The little metal tower at the base of the fuel distributor covers the adjustment screw. The cap at the top of it is actually an extremely thin piece of metal. If you drill through it slowly and carefully, you'll find a small metal disk and a couple of pieces of felt. Under that is the mixture screw.
After you drill through this, reassemble the air cleaner box. Start the car, allow it to warm up, and connect a multimeter in duty cycle mode to the X11 diagnostic connector. The negative lead goes to pin 2, and the positive to pin 3. There is a small hole in the top of the air cleaner box. Carefully insert a long #3 Allen bit into this hole. Pressing down and turning will adjust the mixture (counter clockwise is lean, clockwise is rich). Make your adjustments slowly, no more than a quarter turn at a time. You will want to wait a little while between each adjustment. The initial article I read said you wanted to wait at least 10 seconds. I found it to be more like 30. When the duty cycle is alternating between 45% and 55% (it will move), it is set properly.
Since making this adjustment, I have found that my car idles much better. I expect to see improved fuel economy (since I'm not sending unburned fuel out the exhaust pipe) too, but time will tell on that one.
This is yet another example why I consider attempts to restrict access to a person's own property to be hostile. I have a right to repair my car. The anti-tampering stuff was put in place with the assumption that I am some kind of moron who would dump more gasoline than can be burned into the engine. Bypassing this created a small but real risk of drilling through something other than what I intended to. The risk paid off for me, but what if it didn't?
My daily driver (a W124) is equipped with KE-Jet. Recently I began having problems with the spark plugs fouling. The car doesn't burn oil, and various sensors used by the fuel system tested okay. Some research lead me to discover that as these cars age, they end up running too rich. The fuel distributor can be adjusted to correct the problem. The problem with this is that federal regulations required anti-tamper equipment be put in place to keep the fuel mixture from being trivially adjusted. I suppose there was a belief that some shithead hammer mechanic would think they could get more power out of their car by dumping more gasoline into the engine. Fortunately, it is pretty easy to get around the anti tamper stuff.
Here we see the throttle body and fuel distributor. The little metal tower at the base of the fuel distributor covers the adjustment screw. The cap at the top of it is actually an extremely thin piece of metal. If you drill through it slowly and carefully, you'll find a small metal disk and a couple of pieces of felt. Under that is the mixture screw.
After you drill through this, reassemble the air cleaner box. Start the car, allow it to warm up, and connect a multimeter in duty cycle mode to the X11 diagnostic connector. The negative lead goes to pin 2, and the positive to pin 3. There is a small hole in the top of the air cleaner box. Carefully insert a long #3 Allen bit into this hole. Pressing down and turning will adjust the mixture (counter clockwise is lean, clockwise is rich). Make your adjustments slowly, no more than a quarter turn at a time. You will want to wait a little while between each adjustment. The initial article I read said you wanted to wait at least 10 seconds. I found it to be more like 30. When the duty cycle is alternating between 45% and 55% (it will move), it is set properly.
Since making this adjustment, I have found that my car idles much better. I expect to see improved fuel economy (since I'm not sending unburned fuel out the exhaust pipe) too, but time will tell on that one.
This is yet another example why I consider attempts to restrict access to a person's own property to be hostile. I have a right to repair my car. The anti-tampering stuff was put in place with the assumption that I am some kind of moron who would dump more gasoline than can be burned into the engine. Bypassing this created a small but real risk of drilling through something other than what I intended to. The risk paid off for me, but what if it didn't?
Sunday, March 24, 2013
ChibiOS, lwIP, UDP, and You
Introduction
It certainly has been a while since I posted anything. I increased my course load in hopes of actually finishing college some day, and have had some family issues which ate up a great deal of my time. Hopefully, this post will be interesting enough make up for that fact.
I have recently joined a high power rocketry club, and am involved in the development of avionics software for the rocket. Since I don't work in aerospace (telecom guy here) and know very little about embedded software development, I have been scrambling up the learning curve.
The club I have joined is in the process of converting their flight control systems from communicating over USB to ethernet. The goal for this is reduced latency and better throughput. We're using a bunch of Olimex STM32-E407 boards to relay sensor data back to the flight computer and to control various actuators. The boards run ChibiOS/RT. IP stuff is handled by lwIP. I'm going to spend a little bit of time going over what has to be done in order to get everything up and running. The code I've been working on does other stuff in addition to networking, so I'm just going to be posting network related snippets, instead of just doing a code dump. If I miss something, I sincerely apologize.
main.c
The high level logic of the application lives in main.c. The following includes are added:
#include <lwip/ip_addr.h>
#include "data_udp.h"
#include "lwipopts.h"
#include "lwipthread.h"
The <lwip/ipaddr.h> and "lwipthread.h" headers are part of lwIP and have not been modified. The data_udp.h file contains function prototypes and defines for UDP stuff that comes later. The lwipopts.h header contains the configuration for lwIP. Due to the simplicity of ChibiOS, all configuration is done at compile time, so you will probably want to edit that file.
The other relevant stuff in main.c is in the main() function. Here, we declare the lwip configuration and configure the ethernet interface on the board:
struct lwipthread_opts ip_opts;
static uint8_t macAddress[6] = {0xC2, 0xAF, 0x51, 0x03, 0xCF, 0x46};
struct ip_addr ip, gateway, netmask;
IP4_ADDR(&ip, 10, 0, 0, 2);
IP4_ADDR(&gateway, 10, 0, 0, 254);
IP4_ADDR(&netmask, 255, 255, 255, 0);
ip_opts.address = ip.addr;
ip_opts.netmask = netmask.addr;
ip_opts.gateway = gateway.addr;
ip_opts.macaddress = macAddress;
Now we fire off a thread for the UDP listener. We are using a nice 32 bit ARM chip, so we can have multiple threads doing multiple things on our board. Since we have a metric shitton of GPIO pins and will probably want to do stuff to more than one of them at a time, I think this is the way to go.
chThdCreateStatic(wa_data_udp_receive_thread, sizeof(wa_data_udp_receive_thread), NORMALPRIO, data_udp_receive_thread, NULL);
data_udp.c
In this one, we again have some includes:
#include "lwip/opt.h"
#include "lwip/arch.h"
#include "lwip/api.h"
#include "lwip/ip_addr.h"
#include "data_udp.h"
All of these except data_udp.h are standard lwIP stuff. The data_udp.h header has some defines that we need to get stuff working that I'll cover shortly.
First we set up a working area for the receive thread that gets started in main().
WORKING_AREA(wa_data_udp_receive_thread, DATA_UDP_SEND_THREAD_STACK_SIZE);
This function spins in the thread we created. It spins, and lets the function data_udp_rx_serve() handle what packets come in.
msg_t data_udp_receive_thread(void *p) {
void * arg __attribute__ ((unused)) = p;
struct netconn *conn;
chRegSetThreadName("data_udp_receive_thread");
chThdSleepSeconds(2);
IP4_ADDR(&ip_addr_fc, 10,0,0,2);
/* Create a new UDP connection handle */
conn = netconn_new(NETCONN_UDP);
LWIP_ERROR("data_udp_receive_thread: invalid conn", (conn != NULL), return RDY_RESET;);
netconn_bind(conn, &ip_addr_fc, DATA_UDP_RX_THREAD_PORT);
while(1) {
data_udp_rx_serve(conn);
}
return RDY_OK;
}
The data_udp_rx_serve function looks like this:
static void data_udp_rx_serve(struct netconn *conn) {
BaseSequentialStream *chp = (BaseSequentialStream *)&SDU1;
struct netbuf *inbuf;
struct pbuf *buf;
char cmdbuf[64];
uint16_t buflen = 0;
uint16_t i = 0;
err_t err;
/*fill buffer with nulls*/
for (i = 0; i < 64; i ++) {
cmdbuf[i] = 0;
}
/* Read the data from the port, blocking if nothing yet there.
We assume the request (the part we care about) is in one netbuf */
chprintf(chp, ".w.\r\n");
err = netconn_recv(conn, &inbuf);
chprintf(chp, ".+.\r\n");
if (err == ERR_OK) {
/*netbuf_data(inbuf, (void **)&buf, &buflen);*/
/*int bytesCopied = netbuf_copy(inbuf, (void **)&buf, &buflen); */
int bytesCopied = netbuf_copy(inbuf, cmdbuf, 64);
chprintf(chp, "\r\ndata_udp_rx: %s", cmdbuf);
chprintf(chp, "\r\n");
chprintf(chp, "copied %d bytes\n", bytesCopied);
if (strncmp("GETPWMWIDTH", (const char *) cmdbuf, 11) == 0) {
char respBuf[64];
unsigned int pulseWidth = getPulseWidth();
sprintf(respBuf, "PULSE WIDTH %d", pulseWidth);
sendResponsePacket(respBuf);
} else {
sendResponsePacket("CMDUNDEF");
};
}
/*fill buffer with nulls*/
for (i = 0; i < 64; i ++) {
cmdbuf[i] = 0;
}
netconn_close(conn);
/* Delete the buffer (netconn_recv gives us ownership,
so we have to make sure to deallocate the buffer) */
netbuf_delete(inbuf);
}
Essentially, we block until we read a UDP packet. After that we copy it out into a buffer and do a standard string comparison against the buffer's contents. We respond to the contents by sending another packet with sendResponsePacket().
void sendResponsePacket( char payload[]) {
struct netconn *conn;
char msg[DATA_UDP_MSG_SIZE] ;
struct netbuf *buf;
char* data;
struct ip_addr addr;
addr.addr = UDP_TARGET;
conn = netconn_new( NETCONN_UDP );
netconn_bind(conn, NULL, 35001 ); //local port
netconn_connect(conn, &addr , DATA_UDP_REPLY_PORT );
buf = netbuf_new();
data = netbuf_alloc(buf, sizeof(msg));
sprintf(msg, "%s", payload);
memcpy (data, msg, sizeof (msg));
netconn_send(conn, buf);
netbuf_delete(buf); // De-allocate packet buffer
netconn_disconnect(conn);
}
This essentially works as expected. We create a new connection, and set our source port as 35001. We then fire a packet off to the target, and call it a day.
data_udp.h
There are few interesting things in this file. The port numbers are simply added as #define s, as are the stack sizes. The only thing really worth mentioning is that the IP addresses must be defined in hex, and stored in network bit order. The former can be accomplished by writing the hex representation of each octet in order, without the periods. The latter is handled by the htonl() function:
#define UDP_TARGET (htonl(0xA000001)) //10.0.0.1
Conclusion
I'm a complete noob when it comes to embedded things, and a bit rusty with regard to C. None the less, I seem to be able to get this board doing stuff. Hopefully there will be epic rocket fun in my future.
It certainly has been a while since I posted anything. I increased my course load in hopes of actually finishing college some day, and have had some family issues which ate up a great deal of my time. Hopefully, this post will be interesting enough make up for that fact.
I have recently joined a high power rocketry club, and am involved in the development of avionics software for the rocket. Since I don't work in aerospace (telecom guy here) and know very little about embedded software development, I have been scrambling up the learning curve.
The club I have joined is in the process of converting their flight control systems from communicating over USB to ethernet. The goal for this is reduced latency and better throughput. We're using a bunch of Olimex STM32-E407 boards to relay sensor data back to the flight computer and to control various actuators. The boards run ChibiOS/RT. IP stuff is handled by lwIP. I'm going to spend a little bit of time going over what has to be done in order to get everything up and running. The code I've been working on does other stuff in addition to networking, so I'm just going to be posting network related snippets, instead of just doing a code dump. If I miss something, I sincerely apologize.
main.c
The high level logic of the application lives in main.c. The following includes are added:
#include <lwip/ip_addr.h>
#include "data_udp.h"
#include "lwipopts.h"
#include "lwipthread.h"
The <lwip/ipaddr.h> and "lwipthread.h" headers are part of lwIP and have not been modified. The data_udp.h file contains function prototypes and defines for UDP stuff that comes later. The lwipopts.h header contains the configuration for lwIP. Due to the simplicity of ChibiOS, all configuration is done at compile time, so you will probably want to edit that file.
The other relevant stuff in main.c is in the main() function. Here, we declare the lwip configuration and configure the ethernet interface on the board:
struct lwipthread_opts ip_opts;
static uint8_t macAddress[6] = {0xC2, 0xAF, 0x51, 0x03, 0xCF, 0x46};
struct ip_addr ip, gateway, netmask;
IP4_ADDR(&ip, 10, 0, 0, 2);
IP4_ADDR(&gateway, 10, 0, 0, 254);
IP4_ADDR(&netmask, 255, 255, 255, 0);
ip_opts.address = ip.addr;
ip_opts.netmask = netmask.addr;
ip_opts.gateway = gateway.addr;
ip_opts.macaddress = macAddress;
Now we fire off a thread for the UDP listener. We are using a nice 32 bit ARM chip, so we can have multiple threads doing multiple things on our board. Since we have a metric shitton of GPIO pins and will probably want to do stuff to more than one of them at a time, I think this is the way to go.
chThdCreateStatic(wa_data_udp_receive_thread, sizeof(wa_data_udp_receive_thread), NORMALPRIO, data_udp_receive_thread, NULL);
data_udp.c
In this one, we again have some includes:
#include "lwip/opt.h"
#include "lwip/arch.h"
#include "lwip/api.h"
#include "lwip/ip_addr.h"
#include "data_udp.h"
All of these except data_udp.h are standard lwIP stuff. The data_udp.h header has some defines that we need to get stuff working that I'll cover shortly.
First we set up a working area for the receive thread that gets started in main().
WORKING_AREA(wa_data_udp_receive_thread, DATA_UDP_SEND_THREAD_STACK_SIZE);
This function spins in the thread we created. It spins, and lets the function data_udp_rx_serve() handle what packets come in.
msg_t data_udp_receive_thread(void *p) {
void * arg __attribute__ ((unused)) = p;
struct netconn *conn;
chRegSetThreadName("data_udp_receive_thread");
chThdSleepSeconds(2);
IP4_ADDR(&ip_addr_fc, 10,0,0,2);
/* Create a new UDP connection handle */
conn = netconn_new(NETCONN_UDP);
LWIP_ERROR("data_udp_receive_thread: invalid conn", (conn != NULL), return RDY_RESET;);
netconn_bind(conn, &ip_addr_fc, DATA_UDP_RX_THREAD_PORT);
while(1) {
data_udp_rx_serve(conn);
}
return RDY_OK;
}
The data_udp_rx_serve function looks like this:
static void data_udp_rx_serve(struct netconn *conn) {
BaseSequentialStream *chp = (BaseSequentialStream *)&SDU1;
struct netbuf *inbuf;
struct pbuf *buf;
char cmdbuf[64];
uint16_t buflen = 0;
uint16_t i = 0;
err_t err;
/*fill buffer with nulls*/
for (i = 0; i < 64; i ++) {
cmdbuf[i] = 0;
}
/* Read the data from the port, blocking if nothing yet there.
We assume the request (the part we care about) is in one netbuf */
chprintf(chp, ".w.\r\n");
err = netconn_recv(conn, &inbuf);
chprintf(chp, ".+.\r\n");
if (err == ERR_OK) {
/*netbuf_data(inbuf, (void **)&buf, &buflen);*/
/*int bytesCopied = netbuf_copy(inbuf, (void **)&buf, &buflen); */
int bytesCopied = netbuf_copy(inbuf, cmdbuf, 64);
chprintf(chp, "\r\ndata_udp_rx: %s", cmdbuf);
chprintf(chp, "\r\n");
chprintf(chp, "copied %d bytes\n", bytesCopied);
if (strncmp("GETPWMWIDTH", (const char *) cmdbuf, 11) == 0) {
char respBuf[64];
unsigned int pulseWidth = getPulseWidth();
sprintf(respBuf, "PULSE WIDTH %d", pulseWidth);
sendResponsePacket(respBuf);
} else {
sendResponsePacket("CMDUNDEF");
};
}
/*fill buffer with nulls*/
for (i = 0; i < 64; i ++) {
cmdbuf[i] = 0;
}
netconn_close(conn);
/* Delete the buffer (netconn_recv gives us ownership,
so we have to make sure to deallocate the buffer) */
netbuf_delete(inbuf);
}
Essentially, we block until we read a UDP packet. After that we copy it out into a buffer and do a standard string comparison against the buffer's contents. We respond to the contents by sending another packet with sendResponsePacket().
void sendResponsePacket( char payload[]) {
struct netconn *conn;
char msg[DATA_UDP_MSG_SIZE] ;
struct netbuf *buf;
char* data;
struct ip_addr addr;
addr.addr = UDP_TARGET;
conn = netconn_new( NETCONN_UDP );
netconn_bind(conn, NULL, 35001 ); //local port
netconn_connect(conn, &addr , DATA_UDP_REPLY_PORT );
buf = netbuf_new();
data = netbuf_alloc(buf, sizeof(msg));
sprintf(msg, "%s", payload);
memcpy (data, msg, sizeof (msg));
netconn_send(conn, buf);
netbuf_delete(buf); // De-allocate packet buffer
netconn_disconnect(conn);
}
This essentially works as expected. We create a new connection, and set our source port as 35001. We then fire a packet off to the target, and call it a day.
data_udp.h
There are few interesting things in this file. The port numbers are simply added as #define s, as are the stack sizes. The only thing really worth mentioning is that the IP addresses must be defined in hex, and stored in network bit order. The former can be accomplished by writing the hex representation of each octet in order, without the periods. The latter is handled by the htonl() function:
#define UDP_TARGET (htonl(0xA000001)) //10.0.0.1
Conclusion
I'm a complete noob when it comes to embedded things, and a bit rusty with regard to C. None the less, I seem to be able to get this board doing stuff. Hopefully there will be epic rocket fun in my future.
Subscribe to:
Posts (Atom)















