Serial Debugging without a UART

attiny24I sometimes have projects using AVR microcontrollers – specifically, I use the ATTiny24 quite a lot since it has a fair number of IO lines and has most of the basic features you would expect from an AVR.   The only problem I have with this chip is that it doesn’t seem to have a full UART on it.  I’ve looked through the datasheet at the universal serial IO it has, but it doesn’t strike me as being useful for RS-232 communications.  Besides that, if you have the code I’m going to show you in this post, you can do RS-232 output on any unused pin without the need for a UART, which in my opinion, is a more flexible solution.

(Yes, you can use JTAG if you have the tools for it, but it’s not ideal when all you really need is simple feedback to know what/how your program is behaving.  I actually have the JTAG ICE mkII and I mostly just use it as a programmer if I’m writing my program in C.)

To do this, you need to know about two basic approaches to structuring a program for an embedded system.  First, we’ll be implementing an interrupt driven scheduler that uses a timer.  The timer helps to ensure that the serial output line is changing at the correct rate so that the remote system can understand the data being sent.  In this example, we’ll be trying to communicate at 9600 baud, so we’ll need to have the timer interrupt occur every 0.104mS (1/9600 = 0.104mS).  Each time the interrupt occurs, we’ll change the state of the output pin to signal the next bit to be transmitted.

The second concept we’ll need to implement is a state machine.  Basically, we need to keep track of where we are in the current byte to be sent as well as where we are in the output string.  As each bit is sent, we need to determine if the current byte is  just beginning and we need to send a space (start) bit or if the current byte is ended and we need to send a mark (stop) bit.  (See Wikipedia for an in-depth discussion of the RS-232 protocol.)  The state machine is built around the use of a few variables and control logic so that the information sent complies with the protocol.  Here is the code:

/* Headers needed for this module */
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
volatile char msg[32]; // message buffer
//Sample message - note how the string is null terminated so we can use strcpy()
const char testmsg PROGMEM = "Testing Serial IO!\r\n";
// This line is our output for serial data
#define SEROUT PB2
// Scheduler interrupt frequency.  This directly affects the baud rate
//  for serial communications.  Do not change this.
// This was approximately calculated as follows (some tweaking was needed):
//    ((0xFFFF) - ((1 / BAUD) / (1 / F_CPU)))
#define TCNT1_CNT  0xFD00
void init_timer(void)
{
 TCCR1A &= ~(1<<COM1A1) & ~(1<<COM1A0) & ~(1<<COM1B1) & ~(1<<COM1B0) &
    ~(1<<WGM11) & ~(1<<WGM10);
 TCCR1B &= ~(1<<ICNC1) & ~(1<<WGM13) & ~(1<<WGM12) &
    ~(1<<CS12) & ~(1<<CS11);
 TCCR1B |= (1<<CS10);
 // WGM13:0 = 0 for normal mode
 TIMSK1 |= (1<<TOIE1);  // interrupt on overflow
 TCNT1 = TCNT1_CNT;
}
ISR(TIM1_OVF_vect)  // TimerCounter1 overflow interrupt handler
{
 /*  Some of the frequency calculations may need to be tweaked to make
  it so that the routines execute with an acceptable amount of error.
  Since we are bit banging serial communication, the amount of error
  we can have before the serial stream turns to garbage is fairly
  small */
 TCNT1 = TCNT1_CNT; // reset counter for next time
 // This must be done first because the timing is critical
 do_serial_comm();
// ... do other things if need be....
  }
}
void do_serial_comm(void)
{
 /* State machine to output serial data.  This function must be
  called with the proper frequency to output serial data at the correct
  rate.  If the rate varies slightly, the baud rate won't be stable
  enough to ensure the data can be understood by the remote host.
 */
 static volatile uint8_t bitPos = 8;
 static volatile uint8_t charPos = 0;
 static volatile uint8_t needStopStart = 0;
 static volatile char curChar;
 /* The order of the following blocks is critical.  For this
  state machine to work correctly, we have to do something
  with the transmit line every time this function is called.
  The operation to be performed depends on where we are in
  current character and the message itself.
 */
 // send serial data
 if (needStopStart > 1)
 {
  PORTB |= (1<<SEROUT);    // logical mark output
  needStopStart--;
 }
 else if (needStopStart > 0)
 {
  PORTB &= ~(1<<SEROUT);   // output a start bit
  needStopStart--;
 }
 else if (bitPos < 8)        // LSB first
 {
  if ((curChar & (1<<bitPos++)) == 0) // bit is 0
   PORTB &= ~(1<<SEROUT);
  else       // bit is 1
   PORTB |= (1<<SEROUT);  
 }
 else if (charPos < strlen(msg))
 {       // move to the next byte
  curChar = msg[charPos++];
  bitPos = 0;
  needStopStart = 3;    // Sends 2 idle bits + 1 start bit
  PORTB |= (1<<SEROUT);   // Idle the line
 }
 else  // only get here if bitpos is 0 and curPos = strlen(msg)
 { // end of string
  strcpy(msg,"");         // blank the message
  bitPos = 8;
  charPos = 0;
  needStopStart = 0;
  PORTB |= (1<<SEROUT);   // Idle the line
 }
}
/* Code */
int main(void)
{
 init_timer();
 strcpy(msg, testmsg); // initialize output message
 sei();    // enable interrupts
 
 for (;;) {}  // loop
}

Some of the above code may look confusing, but it works quite well.  Our cpu is configured to run at 8MHz from the internal oscillator.  Since that timing source is going to drift from chip to chip, some tweaking of the counter overflow value may be needed to get the timing of the serial line changes to work correctly.

In the do_serial_comm() call, the needStopStart value is used to output the necessary stop, idle, and start bits that have to occur in between each byte.  Since the sequence in between each character is always the same – a stop bit, idle bit, and a start bit – we can simply decrement needStopStart each time the function is called to get the correct output.  When we are sending character bits, we simply test the current bit to see if it’s a 0 or a 1 and then send the proper logic level on the output pin.

When the message buffer is exhausted the message buffer is set to a blank string and the remaining state machine variables are reset.  The remaining program code can then test the length of the message buffer to figure out if the current message has been completely sent before placing a new message into the buffer.  Testing for a blank message buffer has to be done each time the program wants to send a new message.  If a new message is put into the buffer before the previous message has been completely sent, the output may be corrupted.

This technique works best if your program has a lot of time in between status updates.  In my case, I used this code in a NiCD battery charger I built and I wanted the charger to be able to log the battery voltage on the computer once per second.  So long as the message is short and the check for the battery voltage isn’t happening too quickly this method is very reliable.  If on the other hand, you’re doing something 1000 times per second, you’ll find this to be very limiting because you simply can’t send messages that fast at this baud rate.  Also the baud rate is also a limiting factor since the amount of acceptable timing error is reduced as you try to communicate at faster speeds.

Anyway – I hope this information was helpful.  If you spot an error, please let me know in the comments.

SQL Server 2005: Logon Error 18456, State 11

First, I would like to make a request of anyone reading this.  If you post in some forum somewhere about a problem you’ve been experiencing, please take a few moments to also post what the solution was after you’ve gotten your problem figured out.  I spent a good amount of time researching why I was getting the error in the title of this post and while I could easily find posts online describing the issue, it seemed like almost no one took the time to post the solution.  Those of you who do post your solutions, thank you.

This week we’ve been working on installing VMWare VDI/View so that we can do an evaluation of the product.  Part of that installation process involves setting up a SQL Server database for the vSphere and View pieces of the software and though we could have used MSDE or whatever MS is calling it these days, we wanted to mirror a production environment as closely as possible.  This means that we wanted to use SQL Server 2005 as the back end database and use an Active Directory service account to grant access to the database which is the best practice standard in our environment.

After configuring the account in the AD and setting group memberships, I proceeded to add the new service account to the database server, create a database for the user, make the service account the database owner, and grant further access to the msdb system database.  All of that seemed to work perfectly until it was time to create a data source so the vSphere could access the database.  Suddenly, the service account couldn’t connect and the above error was appearing in the activity log on our SQL Server.

Of course, the above error means that the server was able to verify the account as valid, but had denied the logon attempt.  (Note to the MS SQL Server Product Team: this error is useless without knowing WHY the logon was denied.  Makes me wonder who did the QA on this product…) I spent some time searching online forums for a solution and I actually found one post that mirrored my situation exactly.  The only problem was the original person who posted it didn’t mention if others had helped solve the problem.  Instead, they came to the forum, posted their problem, got some advice, and disappeared into the ether to never be heard from again.  I didn’t want to follow the advice in the thread because it seemed like I’d be going in circles.  After all, I just created the account, added it to the server, and granted rights to the account only moments prior to this.  How could anything be different?

In an attempt to isolate the issue, I added one of my test accounts to the server and made it the owner of the database.  I then went back to the vSphere server, added the test account to the local administrators group, and logged in under the test account.  When I then tested the connection to the database, it worked as expected. Since I couldn’t figure out what was different between the test account and the service account, I ended up deleting the credentials from the SQL Server management console, removing all of the rights I had granted, and then finally adding all of that information back to the server.  Strangely enough, that worked.

I have no explanation as to why that worked as a solution, but next time I run into it, this is the first thing I’m going to try.   Hope it helps.