Read variable length messages over SPI using Low Level (LL) api on STM32 MCU - interrupt

My system is composed by an STM32NUCLEO board and a slave device connected over SPI. The slave device sends commands with a variable length: possible lengths are 4, 8, 10, 14 bits.
I'm trying to detect these messages on my nucleo board using the LL APIs and interrupts.
The solution I'm currently working on is based on setting the SPI with a data-width of 4 bits (SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_4BIT) and then counting the number of words (1 word = 4 bits) that I receive. In this way, if I receive 1 word then it means that I have received a 4 bit command, 2 word --> 8 bit command. If I receive 3 words, it should mean that I have received a 10bit command (2 bits should be discarded), and so on.
Unfortunately, I have noticed that the LL APIs provides functions only for reading 8 bits or 16 bits at a time and currently I'm having issue in receiving a 4 bit command, since the function LL_SPI_ReceiveData8 expects to receive 8 bits.
Here is my implementation for the IRQ handler and for the callback:
IRQ Handler:
void SPI1_IRQHandler(void)
{
/* Check RXNE flag value in ISR register */
if(LL_SPI_IsActiveFlag_RXNE(SPI1))
{
/* Call function Slave Reception Callback */
SPI1_Rx_Callback();
}
/* Check STOP flag value in ISR register */
else if(LL_SPI_IsActiveFlag_OVR(SPI1))
{
/* Call Error function */
SPI1_TransferError_Callback();
}
}
Callback
void SPI1_Rx_Callback(void)
{
/* Read character in Data register.
RXNE flag is cleared by reading data in DR register */
aRxBuffer[ubReceiveIndex++] = LL_SPI_ReceiveData8(SPI1);
}
As said before in my opinion, the problem seems that I'm using the LL_SPI_ReceiveData8 function to read since I could not find something like LL_SPI_ReceiveData4.
Do you have some suggestions?
Furthermore, is it possible to set the SPI to use 2 bit datawidth instead of 4? Something like SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_2BIT: in this way it should be easier to detect the commands since 4, 8, 10 and 14 are multiples of 2.
Thank you.

With the new information about the used controller:
It supports SPI data transfer length between 4 and 16 bit. So your fist try seems not so bad.
Your "problem" is that there is no 4 bit read function. This is caused by the receive data register that will always contain 16 bit but there are only 4 bit valid data in your case. the other bits are '0'.
Your callback function
aRxBuffer[ubReceiveIndex++] = LL_SPI_ReceiveData8(SPI1);
will write values from 0..15 to the aRxBuffer and you don't need a
ReceiveData4() to get your answer :-)
So also the Reference manual for the STM32L4 series Reference Manual at page 1193ff.

The minimal addresable chunk of data is byte. So even if you receive the 4 bits the read value is 8 bits.
BTW wht is this secret slave device which have varing word length?

Related

STM32F4 UART HAL driver 'save string in variable buffer'

I am in the process of writing software for an STM32F4. The STM32 needs to pull in a string via a UART. This string is variable in length and comes in from a sensor every second. The string is stored in a fixed buffer, so the buffer content changes continuously.
The incoming string looks like this: "A12941;P2507;T2150;C21;E0;"
The settings of the UART:
Baud Rate: 19200
Word lengt: 8Bits
Parity: None
Stop Bids: 1
Over sampling: 16 Samples
Global interrupt: Enabled
No DMA settings
Part of the used code in the main.c function:
uint8_t UART3_rxBuffer[25];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(&huart3, UART3_rxBuffer, 25); //restart interrupt reception mode
int main(void)
{
HAL_UART_Receive_IT (&huart3, UART3_rxBuffer,25);
}
while (1)
{
}
}
Part of the code in stm32f4xx_it.c
void USART3_IRQHandler(void)
{
/* USER CODE BEGIN USART3_IRQn 0 */
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
It does work to fill the buffer with the variable strings in this way, but because the buffer is constantly being replenished, it is difficult to extract a beginning and an end of the string. For example, the buffer might look like this:
[0]'E' [1]'0' [2]'/n' [3]'A' [4]'1' [5]'2' [6]'9' [7]'4' [8]'1' [9]';' [10]'P' etc....
But I'd like to have a buffer that starts on 'A'.
My question is, how can I process incoming strings on the uart correctly so that I only have the string "A12941;P2507;T2150;C21;E0;"?
Thanks in advance!!
I can see three possibilities:
Do all of your processing in the interrupt. When you get to the end of a variable-length message then do everything that you need to do with the information and then change the location variable to restart filling the buffer from the start.
Use (at least) two buffers in parallel. When you detect the end of the variable-length message in interrupt context then start filling a different buffer from position zero and signal to main context that previous buffer is ready for processing.
Use two buffers in series. Let the interrupt fill a ring buffer in a circular way that takes no notice of when a message ends. In main context scan from the end of the previous message to see if you have a whole message yet. If you do, then copy it out into another buffer in a way that makes it start at the start of the buffer. Record where it finished in the ring-buffer for next time, and then do your processing on the linear buffer.
Option 1 is only suitable if you can do all of your processing in less than the time it takes the transmitter to send the next byte or two. The other two options use a bit more memory and are a bit more complicated to implement. Option 3 could be implemented with circular mode DMA as long as you poll for new messages frequently enough, which avoids the need for interrupts. Option 2 allows to queue up multiple messages if your main context might not poll frequently enough.
I would like to share a sample code related to your issue. However it is not what you are exactly looking for. You can edit this code snippet as you wish. If i am not wrong you can also edit it according to option 3.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
HAL_UART_Receive_IT(&huart2,&rData,1);
rxBuffer[pos++] = rData;
if (rData == '\n') {
pos = 0;
}
}
Before start, in the main function, before while loop you should enable interrupt for one byte using "HAL_UART_Receive_IT(&huart2,&rData,1);". If your incoming data has limiter like '\n', so you can save whole data which may have different length for each frame.
If you want data frame start with some specific character, then you can wait to save data until you get this character. In this case you can edit this code by changing '\n' as your character, and after you get that character, you should start to save following data to inside the buffer.

STM32 USB Custom HID only 1 byte per transaction

I know that maximum speed of USB HID device is 64 kbps, but on oscilloscope I get transactions every 1 ms, which contain only ONE byte. My HID report descriptor listed below. What i must change to achieve 64Kbps? Currently my bInterval = 0x01 (1 ms polling for interrupt endpoint), but actual speed is 65 bytes/s, because it add reportID byte to my 64-byte data. I think, USB should not divide single 64+1 packet to 65 singlebyte packets. For experiment I use reportID=1 (from STM32 to PC). From PC side I use hidapi.dll to interact.
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
/* USER CODE BEGIN 0 */
USAGE_PAGE(USAGE_PAGE_UNDEFINED)
USAGE(USAGE_UNDEFINED)
COLLECTION(APPLICATION)
REPORT_ID(1)
USAGE(1)
LOGICAL_MIN(0)
LOGICAL_MAX(255)
REPORT_SIZE(8)
REPORT_COUNT(64)
INPUT(DATA | VARIABLE | ABSOLUTE)
REPORT_ID(2)
USAGE(2)
LOGICAL_MIN(0)
LOGICAL_MAX(255)
REPORT_SIZE(8)
REPORT_COUNT(64)
OUTPUT(DATA | VARIABLE | ABSOLUTE)
REPORT_ID(3)
USAGE(3)
LOGICAL_MIN(0)
LOGICAL_MAX(255)
REPORT_SIZE(8)
REPORT_COUNT(64)
OUTPUT(DATA | VARIABLE | ABSOLUTE)
REPORT_ID(4)
USAGE(4)
LOGICAL_MIN(0)
LOGICAL_MAX(255)
REPORT_SIZE(8)
REPORT_COUNT(64)
OUTPUT(DATA | VARIABLE | ABSOLUTE)
/* USER CODE END 0 */
0xC0 /* END_COLLECTION */
};
HID uses interrupt IN/OUT to convey reports. In USB, Interrupt transfers are polled by host every 1 ms. Every time endpoint is polled, it may yield a 64-byte report (for Low/Full speed). That's probably where you get the 64kB/s figure from. Actually, limit is 1k report / second. Also note these limits are different for High-speed and Super-speed devices.
Report descriptor is one thing. What you actually send as interrupt-IN is something else. It should match, but this is not enforced by anything. You should probably look into the code that builds the interrupt IN transfer payload.
Side note: all you seem interested in is to send arbitrary chunks of data, then HID is probably not the relevant profile. Using bulk endpoints looks more appropriate (and you'll not be limited by interrupt endpoint polling rate).

Can't get my DAC(PT8211) to work correctly using a PIC32MX uc and SPI

I'm just trying to learn to use external ADC and DAC (PT8211) with my PIC32MX534f06h.
So far, my code is just about sampling a signal with my ADC every time a timer-interrupt is triggered, then sending then same signal out to the DAC.
The interrupt and ADC part works fine and have been tested independently, but the voltages that my DAC outputs don't make much sens to me and stay at 2,5V (it's powered at 0 - 5V).
I've tried to feed the DAC various values ranging from 0 to 65534 (16bits DAC so i guess it should be the expected range of the values to feed to it, right?) voltage stays at 2.5V.
I've tried changing the SPI configuration, using different SPIs (3 and 4) and DACs (I have one soldered to my pcb, soldered to SPI3, and one one breadboard, linked to SPI4 in case the one soldered on my board was defective).
I made sure that the chip selection line works as expected.
I couldn't see the data and clock that are transmissed since i don't have a scope yet.
I'm a bit out of ideas now.
Chip selection and SPI configuration settings
signed short adc_value;
signed short DAC_output_value;
int Empty_SPI3_buffer;
#define Chip_Select_DAC_Set() {LATDSET=_LATE_LATE0_MASK;}
#define Chip_Select_DAC_Clr() {LATDCLR=_LATE_LATE0_MASK;}
#define SPI4_CONF 0b1000010100100000 // SPI on, 16-bit master,CKE=1,CKP=0
#define SPI4_BAUD 100 // clock divider
DAC output function
//output to external DAC
void DAC_Output(signed int valueDAC) {
INTDisableInterrupts();
Chip_Select_DAC_Clr();
while(!SPI4STATbits.SPITBE); // wait for TX buffer to empty
SPI4BUF=valueDAC; // write byte to TX buffer
while(!SPI4STATbits.SPIRBF); // wait for RX buffer to fill
Empty_SPI3_buffer=SPI4BUF; // read RX buffer
Chip_Select_DAC_Set();
INTEnableInterrupts();
}
ISR sampling the data, triggered by Timer1. This works fine.
ADC_input inputs the data in the global variable adc_value (12 bits, signed)
//ISR to sample data
void __ISR( _TIMER_1_VECTOR, IPL7SRS) Test_data_sampling_in( void)
{
IFS0bits.T1IF = 0;
ADC_Input();
//rescale the signed 12 bit audio values to unsigned 16 bits wide values
DAC_output_value = adc_value + 2048; //first unsign the signed 12 bit values (between 0 - 4096, center 2048)
DAC_output_value = DAC_output_value *16; // the scale between 12 and 16 bits is actually 16=65536/4096
DAC_Output(DAC_output_value);
}
main function with SPI, IO, Timer configuration
void main() {
SPI4CON = SPI4_CONF;
SPI4BRG = SPI4_BAUD;
TRISE = 0b00100000;
TRISD = 0b000000110100;
TRISG = 0b0010000000;
LATD = 0x0;
SYSTEMConfigPerformance(80000000L); //
INTCONSET = _INTCON_MVEC_MASK; /* Set the interrupt controller for multi-vector mode */
//
T1CONbits.TON = 0; /* turn off Timer 1 */
T1CONbits.TCKPS = 0b11; /* pre-scale = 1:1 (T1CLKIN = 80MHz (?) ) */
PR1 = 1816; /* T1 period ~ ? */
TMR1 = 0; /* clear Timer 1 counter */
//
IPC1bits.T1IP = 7; /* Set Timer 1 interrupt priority to 7 */
IFS0bits.T1IF = 0; /* Reset the Timer 1 interrupt flag */
IEC0bits.T1IE = 1; /* Enable interrupts from Timer 1 */
T1CONbits.TON = 1; /* Enable Timer 1 peripheral */
INTEnableInterrupts();
while (1){
}
}
I would expect to see the voltage at the ouput of my DAC to mimic those I put at the input of my ADC, instead the DAC output value is always constant, no matter what I input to the ADC
What am i missing?
Also, when turning the SPIs on, should I still manually manage the IO configuration of the SDI SDO SCK pins using TRIS or is it automatically taken care of?
First of all I agree that the documentation I first found for PT8211 is rather poor. I found extended documentation here. Your DAC (PT8211) is actually an I2S device, not SPI. WS is not chip select, it is word select (left/right channel). In I2S, If you are setting WS to 0, that means the left channel. However it looks like in the extended datasheet I found that WS 0 is actually right channel (go figure).
The PIC you've chosen doesn't seem to have any I2S hardware so you might have to bit bash it. There is a lot of info on I2S though ,see I2S bus specification .
There are some slight differences with SPI and I2C. Notice that the first bit is when WS transitions from high to low is the LSB of the right channel. and when WS transitions from low to high, it is not the LSB of the left channel. Note that the output should be between 0.4v to 2.4v (I2S standard), not between 0 and 5V. (Max is 2.5V which is what you've been seeing).
I2S
Basically, I'd try it with the proper protocol first with a bit bashing algorithm with continuous flip flopping between a left/right channel.
First of all, thanks a lot for your comment. It helps a lot to know that i'm not looking at a SPI transmission and that explains why it's not working.
A few reflexions about it
I googled Bit bashing (banging?) and it seems to be CPU intensive, which I would definately try to avoid
I have seen a (successful) projet (in MikroC) where someone transmit data from that exact same PIC, to the same DAC, using SPI, with apparently no problems whatsoever So i guess it SHOULD work, somehow?
Maybe he's transforming the data so that it works? here is the code he's using, I'm not sure what happens with the F15 bit toggle, I was thinking that it was done to manage the LSB shift problem. Here is the piece of (working) MikroC code that i'm talking about
valueDAC = valueDAC + 32768;
valueDAC.F15 =~ valueDAC.F15;
Chip_Select_DAC = 0;
SPI3_Write(valueDAC);
Chip_Select_DAC = 1;
From my understanding, the two biggest differences between SPI and I2S is that SPI sends "bursts" of data where I2S continuously sends data. Another difference is that data sent after the word change state is the LSB of the last word.
So i was thinking that my SPI is triggered by a timer, which is always the same, so even if the data is not sent continuously, it will just make the sound wave a bit more 'aliased' and if it's triggered regularly enough (say at 44Mhz), it should not be SO different from sending I2S data at the same frequency, right?
If that is so, and I undertand correctly, the "only" problem left is to manage the LSB-next-word-MSB place problem, but i thought that the LSB is virtually negligible over 16bit values, so if I could just bitshift my value to the right and then just fix the LSB value to 0 or 1, the error would be small, and the format would be right.
Does it sounds like I have a valid 'Mc-Gyver-I2S-from-my-SPI' or am I forgetting something important?
I have tried to implement it, so far without success, but I need to check my SPI configuration since i'm not sure that it's configured correctly
Here is the code so far
SPI config
#define Chip_Select_DAC_Set() {LATDSET=_LATE_LATE0_MASK;}
#define Chip_Select_DAC_Clr() {LATDCLR=_LATE_LATE0_MASK;}
#define SPI4_CONF 0b1000010100100000
#define SPI4_BAUD 20
DAaC output function
//output audio to external DAC
void DAC_Output(signed int valueDAC) {
INTDisableInterrupts();
valueDAC = valueDAC >> 1; // put the MSB of ValueDAC 1 bit to the right (becase the MSB of what is transmitted will be seen by the DAC as the LSB of the last value, after a word select change)
//Left channel
Chip_Select_DAC_Set(); // Select left channel
SPI4BUF=valueDAC;
while(!SPI4STATbits.SPITBE); // wait for TX buffer to empty
SPI4BUF=valueDAC; // write 16-bits word to TX buffer
while(!SPI4STATbits.SPIRBF); // wait for RX buffer to fill
Empty_SPI3_buffer=SPI4BUF; // read RX buffer (don't know why we need to do this here, but we do)
//SPI3_Write(valueDAC); MikroC option
// Right channel
Chip_Select_DAC_Clr();
SPI4BUF=valueDAC;
while(!SPI4STATbits.SPITBE); // wait for TX buffer to empty
SPI4BUF=valueDAC; // write 16-bits word to TX buffer
while(!SPI4STATbits.SPIRBF); // wait for RX buffer to fill
Empty_SPI3_buffer=SPI4BUF;
INTEnableInterrupts();
}
The data I send here is signed, 16 bits range, I think you said that it's allright with this DAC, right?
Or maybe i could use framed SPI? the clock seems to be continous in this mode, but I would still have the LSB MSB shifting problem to solve.
I'm a bit lost here, so any help would be cool

Using LWIP SNMP, errors occur when the second snmp_vabind_alloc is called

I'm trying to send a proper trap using the Light Weight Internet Protocol (LWIP) SNMP.
The SNMP Wiki states, a proper trap should have
a current sysUpTime value binding
an OID identifying the type of trap binding
an optional variable binding
However it errs with vb->value != NULL when the second snmp_varbind_alloc is called.
When only the variable binding is sent, and none other, the trap is sent to the Network Management Station ok.
If the structure is defined in RAM and the fields populated, effectively doing the allocate manually, then I can get two bindings to go out. It will single step but not run. So, now I need to look at making sure the RAM structures exists when it is being sent out, before I destroy them. So, I can add a delay, which is not ideal, or find a function which will tell me when the trap has been sent, so I can move on. I'm hesitant in posting code which doesnt work. When (if) I get it working, then I will show the code.
Here is the code for 3 bindings with opt.h changed from:
#define MEMP_NUM_SNMP_VALUE 3
to:
#define MEMP_NUM_SNMP_VALUE 9
struct snmp_obj_id sysupid = {9,{1,3,6,1,2,1,1,3,0}};
struct snmp_obj_id trapoid = {11,{1,3,6,1,6,3,1,1,4,1,0}};
struct snmp_obj_id pttnotifyoid = {8,{1,3,6,1,4,SNMP_ENTERPRISE_ID,3,18}};
static unsigned char trapOID[10] = { 0x2b, 6, 1, 4, 1, 0x82, 0xe4, 0x3d, 3, 18};
struct snmp_varbind *vb1, *vb2, *vb3;
u32_t *u32ptr, sysuptime;
void vSendTrapTaskDemo( void ){
snmp_varbind_list_free(&trap_msg.outvb);
vb1 = snmp_varbind_alloc(&sysupid,SNMP_ASN1_TIMETICKS, 4);
snmp_get_sysuptime(&sysuptime);
vb1->value_len=4;
vb1->value_type=0x43; //Timerticks
u32ptr=vb1->value;
*u32ptr=sysuptime;
snmp_varbind_tail_add(&trap_msg.outvb,vb1);
vb2 = snmp_varbind_alloc(&trapoid,SNMP_ASN1_OBJ_ID, 11);
memcpy (vb2->value, trapOID, 10);
snmp_varbind_tail_add(&trap_msg.outvb,vb2);
vb3 = snmp_varbind_alloc(&pttnotifyoid, SNMP_ASN1_COUNTER, 4);
vb3->value_len=4;
vb3->value_type=0x02; //Integer32
u32ptr=vb3->value;
*u32ptr=1;
snmp_varbind_tail_add(&trap_msg.outvb,vb3);
snmp_send_trap(SNMP_GENTRAP_ENTERPRISESPC, &sysupid,18);
snmp_varbind_list_free(&trap_msg.outvb);
}
The second binding has issues.
The value of the OID is 0 (itu-t) when it should be:
1.3.6.1.4.1.45629.3.18
However, since level 1 needs only one binding, I'm going to forget the 3 binding method for now, until told that level 2 is needed.
Your question has been posted a while ago, but I had the same issue as you, and couldn't find an answer...
I'm using LWIP on a STM32F107, and was completely unable to add a second varbind to my traps...
The solution was to increase the HEAP size of my µcontroller.
When using STM32CubeMX, it's located (for me) line 61 of the startup_stm32f107xc.s file, and has a default value of 0x200 (512 Bytes), I simply doubled that to be 0x400.
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x400
I hope this will help whoever is trying to use LWIP!

How I can fix this code to allow my AVR to talk over serial port?

I've been pulling my hair out lately trying to get an ATmega162 on my STK200 to talk to my computer over RS232. I checked and made sure that the STK200 contains a MAX202CPE chip.
I've configured the chip to use its internal 8MHz clock and divided it by 8.
I've tried to copy the code out of the data sheet (and made changes where the compiler complained), but to no avail.
My code is below, could someone please help me fix the problems that I'm having?
I've confirmed that my serial port works on other devices and is not faulty.
Thanks!
#include <avr/io.h>
#include <avr/iom162.h>
#define BAUDRATE 4800
void USART_Init(unsigned int baud)
{
UBRR0H = (unsigned char)(baud >> 8);
UBRR0L = (unsigned char)baud;
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
UCSR0C = (1 << URSEL0) | (1 << USBS0) | (3 << UCSZ00);
}
void USART_Transmit(unsigned char data)
{
while(!(UCSR0A & (1 << UDRE0)));
UDR0 = data;
}
unsigned char USART_Receive()
{
while(!(UCSR0A & (1 << RXC0)));
return UDR0;
}
int main()
{
USART_Init(BAUDRATE);
unsigned char data;
// all are 1, all as output
DDRB = 0xFF;
while(1)
{
data = USART_Receive();
PORTB = data;
USART_Transmit(data);
}
}
I have commented on Greg's answer, but would like to add one more thing. For this sort of problem the gold standard method of debugging it is to first understand asynchronous serial communications, then to get an oscilloscope and see what's happening on the line. If characters are being exchanged and it's just a baudrate problem this will be particularly helpful as you can calculate the baudrate you are seeing and then adjust the divisor accordingly.
Here is a super quick primer, no doubt you can find something much more comprehensive on Wikipedia or elsewhere.
Let's assume 8 bits, no parity, 1 stop bit (the most common setup). Then if the character being transmitted is say 0x3f (= ascii '?'), then the line looks like this;
...--+ +---+---+---+---+---+---+ +---+--...
| S | 1 1 1 1 1 1 | 0 0 | E
+---+ +---+---+
The high (1) level is +5V at the chip and -12V after conversion to RS232 levels.
The low (0) level is 0V at the chip and +12V after conversion to RS232 levels.
S is the start bit.
Then we have 8 data bits, least significant first, so here 00111111 = 0x3f = '?'.
E is the stop (e for end) bit.
Time is advancing from left to right, just like an oscilloscope display, If the baudrate is 4800, then each bit spans (1/4800) seconds = 0.21 milliseconds (approx).
The receiver works by sampling the line and looking for a falling edge (a quiescent line is simply logical '1' all the time). The receiver knows the baudrate, and the number of start bits (1), so it measures one half bit time from the falling edge to find the middle of the start bit, then samples the line 8 bit times in succession after that to collect the data bits. The receiver then waits one more bit time (until half way through the stop bit) and starts looking for another start bit (i.e. falling edge). Meanwhile the character read is made available to the rest of the system. The transmitter guarantees that the next falling edge won't begin until the stop bit is complete. The transmitter can be programmed to always wait longer (with additional stop bits) but that is a legacy issue, extra stop bits were only required with very slow hardware and/or software setups.
I don't have reference material handy, but the baud rate register UBRR usually contains a divisor value, rather than the desired baud rate itself. A quick google search indicates that the correct divisor value for 4800 baud may be 239. So try:
divisor = 239;
UBRR0H = (unsigned char)(divisor >> 8);
UBRR0L = (unsigned char)divisor;
If this doesn't work, check with the reference docs for your particular chip for the correct divisor calculation formula.
For debugging UART communication, there are two useful things to do:
1) Do a loop-back at the connector and make sure you can read back what you write. If you send a character and get it back exactly, you know that the hardware is wired correctly, and that at least the basic set of UART register configuration is correct.
2) Repeatedly send the character 0x55 ("U") - the binary bit pattern 01010101 will allow you to quickly see the bit width on the oscilloscope, which will let you verify that the speed setting is correct.
After reading the data sheet a little more thoroughly, I was incorrectly setting the baudrate. The ATmega162 data sheet had a chart of clock frequencies plotted against baud rates and the corresponding error.
For a 4800 baud rate and a 1 MHz clock frequency, the error was 0.2%, which was acceptable for me. The trick was passing 12 to the USART_Init() function, instead of 4800.
Hope this helps someone else out!