The Hercules microcontroller family has a programmable timer coprocessor. In this post, I'm making it simulate an extra SCI / UART channel. With read and write interrupts. I did this exercise for a fellow member on the TI e2e forum, Praveen. He got most of it working, except the interrupts.It's 100% based on the application note UART Implementation Using the N2HET, but ported to one of the smallest controllers in the Hercules family - the TMS570LS04x - that has a single SCI / UART controller on board. |
Why Simulate a Serial Port?
In case you need one more than the amount supported by your controller.
The smallest Hercules controllers have one SCI module. The parts that have more SCIs are more expensive and larger.
This emulator is a valid option to get an extra UART without increasing BOM cost.
The performance is similar to a native peripheral.
The microcontroller only exchanges date with the NHET controller.
It can then take care of other business, while the timer performs the communication independently.
This is different than bitbanging a UART port in the microcontroller. In that case, the processor has to clock the data in and out over GPIO pins, bit per bit - and deal with the start and end conditions.
All of that is handled autonomous in the timer controller.
The High End Timer Script
The NHET controller has its own language. One I'm not fluent in.
On my own, I would not be able to develop this script.
Luckily, the application note comes with the NHET code. The only thing to do is adapt it to the frequency of the controller I'm using.
On two places in the script, the timer skips a few counts to precisely achieve 115200 baud.
You can find the script in the HET_EMU_SCI folder of the project I attached. Here are the two lines that are different from the application note example:
SCITX CNT { next= Lm10, reg = T, max= 10 irq = OFF,data=0}
; ....
State2 CNT { reg = A, max= 9, irq = OFF,data=0}
How is this loaded to the NHET controller?
The HET assembler converts the script into a C .h and .c file.
If you look at these files you'll see that they don't contain readable code, but an array of raw data:
HET_MEMORY const HET_INIT0_PST[34] = { /* TX1_PIN_0 */ { 0x00002CA0, 0x0000000A, 0x00000000, 0x00000000 }, /* Lm10_0 */ { 0x0000BA00, 0x00004080, 0x00000000, 0x00000000 }, /* Lm11_0 */ { 0x00007480, 0x0000A000,
Those are the machine language instructions for the HET timer.
At the startup of your firmware, this chunk of instructions is loaded into the HET memory.
From then on, the timer will constantly run through that code. Always. Even when you halt the main controller in debug mode (except if you take special measures).
Advantages of the HET timer
The code in the timer runs deterministic. Everything happens at exactly the programmed time - regardless of interrupts or other circumstances.
Ideal for state machine applications like this UART code or an i2c implementation, but also strong in quadrature and 3-phase (and more phase) pulse trains or measurements.
This comes at a price. I have serious difficulties getting a decent level in the language.
Hardware Configuration
That's done in the HALCoGen utility for this controller family. Here is an overview of the settings.
The timer module is enabled
The interrupt is enabled:
Configure HET to use the .het script:
Set the HET TX pin to output, high by default:
That's it
FirmWare
The init part is low level - including putting the ARM controller in a special mode.
Because this emulator uses not-so-common constructs, the hardware abstraction layer doesn't provide an API and you talk directly to registers.
For the ARM mode switch, we even have to go to assembler because there’s no C call for it.
int main(void) { /* USER CODE BEGIN (3) */ char CharReceive; hetInit(); asm(" cpsie i"); hetREG1->GCR = 0x01030001;
Then the UART emulator gets exercised:
//Send UART data using Polling hetREG1->INTENAC = 0xFFFFFFFF; hetREG1->FLG = 0xFFFFFFFF; HetUART1PutText("Send Data using Polling\n\r"); HetUART1Printf("BaudRate: %d bps.\n\r", 115200); //Clear the flags, Enable transmit interrupts while(hetRAM1->Instruction[2].Data != 0); //while(hetRAM1->Instruction[4].Data != 0);//There could be a small glitch when switching between interrupt and polling hetREG1->FLG = 0xFFFFFFFF; hetREG1->INTENAS = 0x8; //Send UART data using interrupts while(hetRAM1->Instruction[2].Data != 0); HetUART1PutChar('S'); while(Bit_Tran<27); //Clear the flags, Disable transmit interrupts hetREG1->INTENAC = 0x8; //disable the transmit while(hetRAM1->Instruction[2].Data != 0); hetREG1->FLG = 0xFFFFFFFF; //clear interrupt flags //Receive UART data using Polling HetUART1PutText("Type Any Letter to Echo using Polling:\n\r"); while((CharReceive=HetUART1GetChar()) == 0); HetUART1PutChar(CharReceive); //Receive UART data using interrupt hetREG1->INTENAS = 1<<23;//enable Receive interrupt HetUART1PutText("\n\rType Any Letter to Echo using interrupt:\n\r"); while(1);
The funny thing is that the UART keeps working, even when the controller is in the eternal while(1) loop.
This is because the UART engine is run by the HET engine and interrupts. Here is the interrupt code:
void hetNotification(hetBASE_t *het, uint32 offset) { //volatile int temp; char Rec_Data; if(offset==4) { Bit_Tran ++; if(Bit_Tran < 27) HetUART1PutChar(Output[Bit_Tran]); } else if(offset==24) { Rec_Data = hetRAM1->Instruction[25].Data; HetUART1PutChar(Rec_Data); } }
The end result is an additional serial port running at 115200 baud, where HET0 pin is TX and HET2 is RX.
In PuTTY, here is a capture of a session:
This blog may sound exoteric for non-Hercules-affectionados. It is a complex matter.
But it's about something different, there are not many examples around.
(hint: if you are looking for a niche automotive embedded job, proving expertise in this can be your entry)
Read the linked appnote anyway - it contains a thourough analysis of the serial protocol.
If you have a Hercules TMS570LS04 LaunchPad, download the attached project. Run it. It works!
notes:
1: The NHET controller has enough resources to simulate more than one UART. The appnote at the start of this post hints on how to use the NHET offset construct to add another one.
2: Also check that appnote to get a good overview of a UART state machine, with and without interrupts. The document has flow diagrams for both scenarios.
3: The NHET module can also simulate an i²c bus. Also useful for the TMS570LS03x/04x that doesn't have one.
4: Traditionally, you use the HET IDE to assemble the NHET script into C code, and then regenerate your project with HALCoGen to get the results included into your project. That's two programs where you have to do manual tasks. To automate that HET assembler cycle, you can add these three instructions to the pre-build steps of your CCS project.: ${HET_COMPILER} -n0 -v2 -hc32 ..\HET_EMU_SCI\HET_EMU_SCI.het copy /Y ..\HET_EMU_SCI\HET_EMU_SCI.h ..\HALCoGen\include\HET_EMU_SCI.h copy /Y ..\HET_EMU_SCI\HET_EMU_SCI.c ..\HALCoGen\source\HET_EMU_SCI.c Define the ${HET_COMPILER} path in Resources -> Linked Resources. In my case: D:\Program Files (x86)\Texas Instruments\Hercules\HET IDE\03.05.01\bin\hetp.exe That will invoke the HET assembler to convert the UART script to a .h and .c file, and copy the generated files into the include and source folders of your CCS project. |
Top Comments