-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added receive functionality to the cc26x0 uart #2
base: master
Are you sure you want to change the base?
Conversation
chips/cc26x0/src/uart.rs
Outdated
|
||
for i in 0..rx_len { | ||
rx_buffer[i] = self.read_byte(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading all the bytes here one at a time is likely a bad choice. The entire system will hang until all the requested bytes are read, which could be an indefinite period of time (for instance, forever if there is a mismatch between expected number of bytes and what actually got received, or several seconds if a human is typing data in through a keyboard).
For the SAM4L platform, we implemented a non-blocking receive interface using the DMA. https://github.com/tock/tock/blob/master/chips/sam4l/src/usart.rs#L853 It starts a reception when commanded, but allows it to run in the background and performs the callback whenever all the data is received. Non-blocking code is certainly a lot more effort to write, but it allows the platform to be more robust because the kernel and applications can continue running while waiting on a reception.
Now, if the goal is to support a bootloader, blocking code might be acceptable. But, in my opinion, I don't think it should be exported through the UART HIL for userland code to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That totally makes sense. I avoided the use of DMA because I'm having trouble debugging Rust code (it won't let me set breakpoints in gdb). Any advice you'd have there would be appreciated.
I'm also noticing that the power-management-exp branch is like 75 commits ahead of master - should I be developing on that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, probably worth pointing out that the transmit
method is blocking if the message is larger than the FIFO buffer. I attempted to set it up with interrupts (similar to the NRF52 UART), but had trouble getting the TX_FIFO_READY interrupt to flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dcellucci sorry for the late answer, it's been a holiday-extended-weekend here in Sweden!
The power-management-exp is mostly experiments to reduce the energy consumption, and most of those 75 commits is experiments; not including many changes. It's easier if you'd continue to develop against master, and we'll merge later on (there won't be any substantial conflicts in the UART implementation apart from pin configurations).
Okay I've started implementing the DMA code, and I've hit sort of a roadblock: The DMA block requires a contiguous, 1024-byte block of memory, aligned to the nearest 1024-byte boundary. This block stores source/destination pointers and I'm thinking of implementing this as a static array in main.rs- is there a particularly Rust way of doing this you'd recommend? |
Making a contiguous block of memory could definitely just be done with a static array in main.rs. But aligning it to the nearest boundary is difficult. You'd have to go into the LD file, add it as a memory section, and align it there. For example, APP_MEMORY is linked to the .app_memory section. Requiring something like this would definitely make the cc26xx different from the SAM4L and nRF5x chips that we currently support DMA for. After you can get something working, we'll have to think more generally about how to support needs like that in Tock. I'm not very familiar with the DMA on the cc26xx series. What page in the Technical Reference Manual describes the need for this block of memory? |
The section is: 12.3.5 Channel Configuration. It's on page 1155 of my copy of the TRM. Okay linker it is (I was kind of hoping you wouldn't say that!). |
I understand. Yup, doing something like the .app_memory section and a static allocation in main.rs is exactly what you want to do. This interface makes a lot of sense, but it is annoying that it has to be constrained to 1024 Byte boundaries. For now, just make the change directly to the |
…trol table, in the linker and main.rs and verified their locations in memory. dma_control_table: 0x20002000, app_memory: 0x20002400. App memory actually has more space than before (not sure if the top of the ram was being used)
I think you can also use |
Good lead! I’ll check it out |
Good call Amit. A start could be something like:
Eventually, we'd have to really think about the types of the source and destination pointers, since they're a vulnerability. |
works! Thanks for the insight! I'll get to work fleshing it out, but it does align the table to the nearest 1024 byte boundary (i'm getting 0x20000400). On a side note, I'm using the launchpad and not the sensortag as my dev board. It uses different LED/UART pins- any thoughts on a low-footprint way to allow me to keep track of these two different pinouts? Right now I keep separate branches and merge them whenever I push, it's kind of clunky... |
The easiest option is probably create a new board file for the launchpad. All you'd have to do is copy the entire directory including your changes into a new folder in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a quick look. It will be a hassle to merge this with the power-management-exp PR ( #1 ) due to the formatall. I think it's better to perform a formatall in the master branch and rebase both this PR and #1. It'd also make the files-changed in this branch less and more relevant to the changes made.
boards/sensortag/src/main.rs
Outdated
@@ -246,6 +233,7 @@ pub unsafe fn reset_handler() { | |||
let mut chip = cc26x0::chip::Cc26x0::new(); | |||
|
|||
debug!("Initialization complete. Entering main loop\r"); | |||
debug!("Location of DMA memory: {:p}",&udma::DMACTRLTAB); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably remove both of these debug!
s later on
chips/cc26x0/src/uart.rs
Outdated
pub fn read_byte(&self) -> u8 { | ||
// Get byte from RX FIFO | ||
|
||
while !self.rx_ready() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UART implementation can cause problems due to these "blocking-if-not-ready" in both RX and TX if the debugger isn't attached (if you're using the launchpad, you can test by removing the jumpers for UART between the board and the debugger), since it'll be blocking indefinitely.
We should probably add a way to detect whether or not UART is attached, and either not invoke any of these or simply return (eg. if !self.initialized.get() { return }
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to rewrite the whole UART to look similar to the NRF52 interface, once I get DMA working, and I will make sure that this is included!
@@ -0,0 +1,56 @@ | |||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome that you started on this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'm going to shoot for an MVP right now, but I think there's a larger conversation about what the DMA manager should look like. It seems like the SAM4L model might be the closest analog...
boards/sensortag/src/main.rs
Outdated
|
||
#[link_section = ".app_memory"] | ||
static mut APP_MEMORY: [u8; 10240] = [0; 10240]; | ||
static mut APP_MEMORY: [u8; 11264] = [0; 11264]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you increase the app memory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I had initially partitioned off a section of .app_memory
in the linker for the DMA control table, and in doing so had to recalculate the size of that section. I'll change it back :)
re: the |
chips/cc26x0/src/udma.rs
Outdated
struct DMAConfig { | ||
source_ptr: usize, | ||
dest_ptr: usize, | ||
control: usize, | ||
control: ReadWrite<u32,DMATableControl::Register>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having trouble here- I'd like to implement the control pointer as a register, since it has a register-like structure. Unfortunately, I need to implement copy
and clone
in order to get this to work in its current form. That means I need to modify the mod.rs
file in the kernel (which I don't want to do just for this...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrm... that's a bummer. I don't see any harm in adding Copy
and Clone
to ReadWrite
(since it's really just a wrapper around an IntLike
(which already needs to implement Copy
and Clone
)
chips/cc26x0/src/udma.rs
Outdated
ControlTable { | ||
config_array: [0; 1024] | ||
config_array: [DMAConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another option is to replace this array initialization with some kind of manual initialization where 32 separate instances of DMAConfig are cast from 4 byte blocks of memory. This also doesn't seem like the right thing to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see, it only needs to be copyable in order to initialize cleanly? Would a macro do the trick?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, this is a thing in Rust. rust-lang/rfcs#1109
None doesn't implement copy either, so it's why the process array needs to be:
static mut PROCESSES: [Option<&'static mut kernel::Process<'static>>; NUM_PROCS] = [
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None,
];
A general macro-based solution here would solve both of the problems. This answer claims to do it (https://stackoverflow.com/questions/28656387/initialize-a-large-fixed-size-array-with-non-copy-types/28666474#28666474) but I'm not sure if it's what we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brghena exactly- I was balking at the mem::uninitialized call (the compiler gave me all sort of red flags)
maybe the process array approach is the best idea- each channel needs to be enabled anyway. I'll try it out and report back!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having trouble implementing an array of ReadWrite registers, basically. what would be the best way to do this? The only way I could get it to compile was by modifying mod.rs
to derive clone and copy for the ReadWrite struct. Doesn't seem right...
Hi all, Sorry for not working more on this- I've been pretty swamped I seem to have hit a roadblock- I'm maxing out the RAM available to the CC26x0. It could be because I had to add that 1024-byte block of control vectors for the DMA, but that seems like a pretty razor thin margin to begin with. If you all have any thoughts I'd appreciate it! Maybe I'm doing something stupid with Rust's reference system that is causing me to make a bunch of copies... Alternatively I could switch to the CC26x2, which has 4x the RAM, if this just looks like how things are. Edit: A little update. I modded the linker so it would build and I could see how big everything is. This is the linker output for master:
This is the linker output for launchpad-uart-rx (I cheated and made the RAM region larger):
So we only had like 2 KB of RAM to begin with even before implementing DMA, and we lose another KB of RAM just to the DMA table. Probably makes sense to graduate to the CC26x2. |
I'm reexamining my code and I think I should do some editing. I made some pretty memory-poor choices and will fix and then repush.... |
Okay my problem was (surprise surprise) the changes I made to the .app-memory section . turns out there were good reasons for not making it bigger... Anyway, working DMA w/ UART, tested using the console example. Maybe not ready for merge but ready for feedback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome that you got it working, nice job! :)
I added some review comments regarding the code - more nit comments rather than actual implementation feedback
chips/cc26x0/src/uart.rs
Outdated
} | ||
} | ||
|
||
pub fn configure_dma(&self) { | ||
unsafe{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsafe {
// Do unsafe stuff
}
To make the unsafe block more explicit use indentation. No need for two separate unsafe by the way, imo it would be better to make the entire function configure_dma
as unsafe instead
chips/cc26x0/src/uart.rs
Outdated
|
||
self.disable_interrupts(); | ||
|
||
if unsafe{udma::UDMA.transfer_complete(TX_DMA)} { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather have the unsafe block outside of an if-conditional
, such as
let transfer_complete = unsafe { ... };
if transfer_complete {
Otherwise the conditional becomes kind of messy
chips/cc26x0/src/uart.rs
Outdated
//initialize the UART with the passed settings, and then transfer them | ||
//to the DMA controller | ||
//is there a particular reason we use tx_len rather than truncated_len? | ||
//(as with the NRF52, etc.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tx_data.len()
will return the allocated size of the slice (e.g. [0; 32] will return 32), which is why we use tx_len
as we don't want to transmit the entire buffer every time :)
assuming tx_len
is correctly set then tx_len <= tx_data.len()
should hold
chips/cc26x0/src/udma.rs
Outdated
&self, | ||
dma_channel: DMAPeripheral, | ||
width: DMAWidth, | ||
xfer_type: DMATransferType, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use more descriptive variable names if it's not too long, transfer_type
is more explicit and clear than xfer_type
chips/cc26x0/src/udma.rs
Outdated
} | ||
|
||
|
||
pub fn start_xfer( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
start_transfer
chips/cc26x0/src/udma.rs
Outdated
registers.set_channel_en.set(1<<(dma_channel as u32)); | ||
} | ||
|
||
pub fn prepare_xfer( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prepare_transfer
chips/cc26x0/src/udma.rs
Outdated
|
||
} | ||
|
||
pub fn do_xfer( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do_transfer
, or prepare_and_start_transfer
would be even more explicit; I get the impression from do
that the transfer will be complete once the function returns
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
honestly, I borrowed it from the SAM4L DMA interface, as with the xfer vs. transfer naming conventions. I don't actually use the method, and it might be worth deleting it (I prefer being more explicit when it comes to DMA)
Inspired by tock/tock-sensortag#2 (comment) We can afford the four extra characters.
I just merged in the new Also is it just me or is anyone else not able to drop breakpoints in using gdb? |
Deep sleep is probably interfering with the interrupts (we don't wake up on all interrupts in deep sleep), change this to return
|
Another solution would be to add a new wakeup event, see p. 248 of the TRM. It's easy enough to test by modifying L157 of aon.rs :) Should probably rewrite and refactor the aon module to allow changes to the wakeup sources to occur dynamically |
Thanks alot for the reply! The link you attached drops me at the top of the diff- where exactly should I put that Also it doesn't appear that UART RX is a wakeup event. I'm thinking I should attach it to an Edge Detect on the RX pin instead, though that seems like it would work better when there's a dynamic interface (so we can set it based on the UART settings) |
Adding that check appears to produce a working console app! :D thanks for all your help everyone!!! |
I think it's ready for review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! There seems to be some commented code in the dma implementation still, is there a reason to keep this?
} | ||
|
||
let rx_transfer_complete = unsafe{udma::UDMA.transfer_complete(RX_DMA)}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can rx_transfer_complete
and tx_transfer_complete
both be true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so- they're separate channels in the DMA controller so they can both be running, and while they point to the same register (UART DR), one is purely reading the other writing.
chips/cc26x0/src/uart.rs
Outdated
@@ -214,25 +388,50 @@ impl kernel::hil::uart::UART for UART { | |||
|
|||
self.disable_interrupts(); | |||
self.set_params(params); | |||
self.configure(); | |||
self.configure(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this space?
} | ||
|
||
fn transmit(&self, tx_data: &'static mut [u8], tx_len: usize) { | ||
if tx_len == 0 { | ||
let truncated_len = min(tx_data.len(), tx_len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really necessary, why not use tx_len
and trust that the arguments are correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth including in this case. We tend to do it in other similar places in the kernel.
In general it doesn't hurt to be defensive since the compiler will add bounds checks (which will panic if out of bounds) and will slide them if it can tell the bounds have been checked explicitly.
chips/cc26x0/src/uart.rs
Outdated
chip::SleepMode::DeepSleep as u32 | ||
} else { | ||
chip::SleepMode::Sleep as u32 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does it work if the MCU goes into deep sleep, can it still receive and wake up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not right now. I tried to implement a mod to aon.rs
and I couldn't get that mod to work. For the interim I'm just going to leave it at Sleep (which can receive) until we figure out a way to get waking from deep sleep working.
chips/cc26x0/src/uart.rs
Outdated
chip::SleepMode::Sleep as u32 | ||
} | ||
fn lowest_sleep_mode(&self) -> u32 { | ||
chip::SleepMode::Sleep as u32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you change this back? This way, it'll never be able to enter deep sleep since UART is registered as a peripheral
Adding RX Functionality
This is part of a larger push to add a bootloader to the CC26xx series micros. I added RX functionality to the CC26x0 UART (which should be transferable to the CC26x2 micro).
I also needed to update the tock submodule with the latest pull, because it contains the updated console capsule. In order to get it to build, I added the
sleep
function tochip.rs
.I tested with the
test/console
app, and it was happy.